github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/go/packages/packagestest/export.go (about)

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  /*
     6  Package packagestest creates temporary projects on disk for testing go tools on.
     7  
     8  By changing the exporter used, you can create projects for multiple build
     9  systems from the same description, and run the same tests on them in many
    10  cases.
    11  
    12  Example
    13  
    14  As an example of packagestest use, consider the following test that runs
    15  the 'go list' command on the specified modules:
    16  
    17  	// TestGoList exercises the 'go list' command in module mode and in GOPATH mode.
    18  	func TestGoList(t *testing.T) { packagestest.TestAll(t, testGoList) }
    19  	func testGoList(t *testing.T, x packagestest.Exporter) {
    20  		e := packagestest.Export(t, x, []packagestest.Module{
    21  			{
    22  				Name: "gopher.example/repoa",
    23  				Files: map[string]interface{}{
    24  					"a/a.go": "package a",
    25  				},
    26  			},
    27  			{
    28  				Name: "gopher.example/repob",
    29  				Files: map[string]interface{}{
    30  					"b/b.go": "package b",
    31  				},
    32  			},
    33  		})
    34  		defer e.Cleanup()
    35  
    36  		cmd := exec.Command("go", "list", "gopher.example/...")
    37  		cmd.Dir = e.Config.Dir
    38  		cmd.Env = e.Config.Env
    39  		out, err := cmd.Output()
    40  		if err != nil {
    41  			t.Fatal(err)
    42  		}
    43  		t.Logf("'go list gopher.example/...' with %s mode layout:\n%s", x.Name(), out)
    44  	}
    45  
    46  TestGoList uses TestAll to exercise the 'go list' command with all
    47  exporters known to packagestest. Currently, packagestest includes
    48  exporters that produce module mode layouts and GOPATH mode layouts.
    49  Running the test with verbose output will print:
    50  
    51  	=== RUN   TestGoList
    52  	=== RUN   TestGoList/GOPATH
    53  	=== RUN   TestGoList/Modules
    54  	--- PASS: TestGoList (0.21s)
    55  	    --- PASS: TestGoList/GOPATH (0.03s)
    56  	        main_test.go:36: 'go list gopher.example/...' with GOPATH mode layout:
    57  	            gopher.example/repoa/a
    58  	            gopher.example/repob/b
    59  	    --- PASS: TestGoList/Modules (0.18s)
    60  	        main_test.go:36: 'go list gopher.example/...' with Modules mode layout:
    61  	            gopher.example/repoa/a
    62  	            gopher.example/repob/b
    63  
    64  */
    65  package packagestest
    66  
    67  import (
    68  	"errors"
    69  	"flag"
    70  	"fmt"
    71  	"go/token"
    72  	"io"
    73  	"io/ioutil"
    74  	"log"
    75  	"os"
    76  	"path/filepath"
    77  	"runtime"
    78  	"strings"
    79  	"testing"
    80  
    81  	"github.com/powerman/golang-tools/go/expect"
    82  	"github.com/powerman/golang-tools/go/packages"
    83  	"github.com/powerman/golang-tools/internal/span"
    84  	"github.com/powerman/golang-tools/internal/testenv"
    85  	"golang.org/x/xerrors"
    86  )
    87  
    88  var (
    89  	skipCleanup = flag.Bool("skip-cleanup", false, "Do not delete the temporary export folders") // for debugging
    90  )
    91  
    92  // ErrUnsupported indicates an error due to an operation not supported on the
    93  // current platform.
    94  var ErrUnsupported = errors.New("operation is not supported")
    95  
    96  // Module is a representation of a go module.
    97  type Module struct {
    98  	// Name is the base name of the module as it would be in the go.mod file.
    99  	Name string
   100  	// Files is the set of source files for all packages that make up the module.
   101  	// The keys are the file fragment that follows the module name, the value can
   102  	// be a string or byte slice, in which case it is the contents of the
   103  	// file, otherwise it must be a Writer function.
   104  	Files map[string]interface{}
   105  
   106  	// Overlay is the set of source file overlays for the module.
   107  	// The keys are the file fragment as in the Files configuration.
   108  	// The values are the in memory overlay content for the file.
   109  	Overlay map[string][]byte
   110  }
   111  
   112  // A Writer is a function that writes out a test file.
   113  // It is provided the name of the file to write, and may return an error if it
   114  // cannot write the file.
   115  // These are used as the content of the Files map in a Module.
   116  type Writer func(filename string) error
   117  
   118  // Exported is returned by the Export function to report the structure that was produced on disk.
   119  type Exported struct {
   120  	// Config is a correctly configured packages.Config ready to be passed to packages.Load.
   121  	// Exactly what it will contain varies depending on the Exporter being used.
   122  	Config *packages.Config
   123  
   124  	// Modules is the module description that was used to produce this exported data set.
   125  	Modules []Module
   126  
   127  	ExpectFileSet *token.FileSet // The file set used when parsing expectations
   128  
   129  	Exporter Exporter                     // the exporter used
   130  	temp     string                       // the temporary directory that was exported to
   131  	primary  string                       // the first non GOROOT module that was exported
   132  	written  map[string]map[string]string // the full set of exported files
   133  	notes    []*expect.Note               // The list of expectations extracted from go source files
   134  	markers  map[string]span.Range        // The set of markers extracted from go source files
   135  }
   136  
   137  // Exporter implementations are responsible for converting from the generic description of some
   138  // test data to a driver specific file layout.
   139  type Exporter interface {
   140  	// Name reports the name of the exporter, used in logging and sub-test generation.
   141  	Name() string
   142  	// Filename reports the system filename for test data source file.
   143  	// It is given the base directory, the module the file is part of and the filename fragment to
   144  	// work from.
   145  	Filename(exported *Exported, module, fragment string) string
   146  	// Finalize is called once all files have been written to write any extra data needed and modify
   147  	// the Config to match. It is handed the full list of modules that were encountered while writing
   148  	// files.
   149  	Finalize(exported *Exported) error
   150  }
   151  
   152  // All is the list of known exporters.
   153  // This is used by TestAll to run tests with all the exporters.
   154  var All []Exporter
   155  
   156  // TestAll invokes the testing function once for each exporter registered in
   157  // the All global.
   158  // Each exporter will be run as a sub-test named after the exporter being used.
   159  func TestAll(t *testing.T, f func(*testing.T, Exporter)) {
   160  	t.Helper()
   161  	for _, e := range All {
   162  		e := e // in case f calls t.Parallel
   163  		t.Run(e.Name(), func(t *testing.T) {
   164  			t.Helper()
   165  			f(t, e)
   166  		})
   167  	}
   168  }
   169  
   170  // BenchmarkAll invokes the testing function once for each exporter registered in
   171  // the All global.
   172  // Each exporter will be run as a sub-test named after the exporter being used.
   173  func BenchmarkAll(b *testing.B, f func(*testing.B, Exporter)) {
   174  	b.Helper()
   175  	for _, e := range All {
   176  		e := e // in case f calls t.Parallel
   177  		b.Run(e.Name(), func(b *testing.B) {
   178  			b.Helper()
   179  			f(b, e)
   180  		})
   181  	}
   182  }
   183  
   184  // Export is called to write out a test directory from within a test function.
   185  // It takes the exporter and the build system agnostic module descriptions, and
   186  // uses them to build a temporary directory.
   187  // It returns an Exported with the results of the export.
   188  // The Exported.Config is prepared for loading from the exported data.
   189  // You must invoke Exported.Cleanup on the returned value to clean up.
   190  // The file deletion in the cleanup can be skipped by setting the skip-cleanup
   191  // flag when invoking the test, allowing the temporary directory to be left for
   192  // debugging tests.
   193  //
   194  // If the Writer for any file within any module returns an error equivalent to
   195  // ErrUnspported, Export skips the test.
   196  func Export(t testing.TB, exporter Exporter, modules []Module) *Exported {
   197  	t.Helper()
   198  	if exporter == Modules {
   199  		testenv.NeedsTool(t, "go")
   200  	}
   201  
   202  	dirname := strings.Replace(t.Name(), "/", "_", -1)
   203  	dirname = strings.Replace(dirname, "#", "_", -1) // duplicate subtests get a #NNN suffix.
   204  	temp, err := ioutil.TempDir("", dirname)
   205  	if err != nil {
   206  		t.Fatal(err)
   207  	}
   208  	exported := &Exported{
   209  		Config: &packages.Config{
   210  			Dir:     temp,
   211  			Env:     append(os.Environ(), "GOPACKAGESDRIVER=off", "GOROOT="), // Clear GOROOT to work around #32849.
   212  			Overlay: make(map[string][]byte),
   213  			Tests:   true,
   214  			Mode:    packages.LoadImports,
   215  		},
   216  		Modules:       modules,
   217  		Exporter:      exporter,
   218  		temp:          temp,
   219  		primary:       modules[0].Name,
   220  		written:       map[string]map[string]string{},
   221  		ExpectFileSet: token.NewFileSet(),
   222  	}
   223  	defer func() {
   224  		if t.Failed() || t.Skipped() {
   225  			exported.Cleanup()
   226  		}
   227  	}()
   228  	for _, module := range modules {
   229  		// Create all parent directories before individual files. If any file is a
   230  		// symlink to a directory, that directory must exist before the symlink is
   231  		// created or else it may be created with the wrong type on Windows.
   232  		// (See https://golang.org/issue/39183.)
   233  		for fragment := range module.Files {
   234  			fullpath := exporter.Filename(exported, module.Name, filepath.FromSlash(fragment))
   235  			if err := os.MkdirAll(filepath.Dir(fullpath), 0755); err != nil {
   236  				t.Fatal(err)
   237  			}
   238  		}
   239  
   240  		for fragment, value := range module.Files {
   241  			fullpath := exporter.Filename(exported, module.Name, filepath.FromSlash(fragment))
   242  			written, ok := exported.written[module.Name]
   243  			if !ok {
   244  				written = map[string]string{}
   245  				exported.written[module.Name] = written
   246  			}
   247  			written[fragment] = fullpath
   248  			switch value := value.(type) {
   249  			case Writer:
   250  				if err := value(fullpath); err != nil {
   251  					if xerrors.Is(err, ErrUnsupported) {
   252  						t.Skip(err)
   253  					}
   254  					t.Fatal(err)
   255  				}
   256  			case string:
   257  				if err := ioutil.WriteFile(fullpath, []byte(value), 0644); err != nil {
   258  					t.Fatal(err)
   259  				}
   260  			default:
   261  				t.Fatalf("Invalid type %T in files, must be string or Writer", value)
   262  			}
   263  		}
   264  		for fragment, value := range module.Overlay {
   265  			fullpath := exporter.Filename(exported, module.Name, filepath.FromSlash(fragment))
   266  			exported.Config.Overlay[fullpath] = value
   267  		}
   268  	}
   269  	if err := exporter.Finalize(exported); err != nil {
   270  		t.Fatal(err)
   271  	}
   272  	testenv.NeedsGoPackagesEnv(t, exported.Config.Env)
   273  	return exported
   274  }
   275  
   276  // Script returns a Writer that writes out contents to the file and sets the
   277  // executable bit on the created file.
   278  // It is intended for source files that are shell scripts.
   279  func Script(contents string) Writer {
   280  	return func(filename string) error {
   281  		return ioutil.WriteFile(filename, []byte(contents), 0755)
   282  	}
   283  }
   284  
   285  // Link returns a Writer that creates a hard link from the specified source to
   286  // the required file.
   287  // This is used to link testdata files into the generated testing tree.
   288  //
   289  // If hard links to source are not supported on the destination filesystem, the
   290  // returned Writer returns an error for which errors.Is(_, ErrUnsupported)
   291  // returns true.
   292  func Link(source string) Writer {
   293  	return func(filename string) error {
   294  		linkErr := os.Link(source, filename)
   295  
   296  		if linkErr != nil && !builderMustSupportLinks() {
   297  			// Probe to figure out whether Link failed because the Link operation
   298  			// isn't supported.
   299  			if stat, err := openAndStat(source); err == nil {
   300  				if err := createEmpty(filename, stat.Mode()); err == nil {
   301  					// Successfully opened the source and created the destination,
   302  					// but the result is empty and not a hard-link.
   303  					return &os.PathError{Op: "Link", Path: filename, Err: ErrUnsupported}
   304  				}
   305  			}
   306  		}
   307  
   308  		return linkErr
   309  	}
   310  }
   311  
   312  // Symlink returns a Writer that creates a symlink from the specified source to the
   313  // required file.
   314  // This is used to link testdata files into the generated testing tree.
   315  //
   316  // If symlinks to source are not supported on the destination filesystem, the
   317  // returned Writer returns an error for which errors.Is(_, ErrUnsupported)
   318  // returns true.
   319  func Symlink(source string) Writer {
   320  	if !strings.HasPrefix(source, ".") {
   321  		if absSource, err := filepath.Abs(source); err == nil {
   322  			if _, err := os.Stat(source); !os.IsNotExist(err) {
   323  				source = absSource
   324  			}
   325  		}
   326  	}
   327  	return func(filename string) error {
   328  		symlinkErr := os.Symlink(source, filename)
   329  
   330  		if symlinkErr != nil && !builderMustSupportLinks() {
   331  			// Probe to figure out whether Symlink failed because the Symlink
   332  			// operation isn't supported.
   333  			fullSource := source
   334  			if !filepath.IsAbs(source) {
   335  				// Compute the target path relative to the parent of filename, not the
   336  				// current working directory.
   337  				fullSource = filepath.Join(filename, "..", source)
   338  			}
   339  			stat, err := openAndStat(fullSource)
   340  			mode := os.ModePerm
   341  			if err == nil {
   342  				mode = stat.Mode()
   343  			} else if !xerrors.Is(err, os.ErrNotExist) {
   344  				// We couldn't open the source, but it might exist. We don't expect to be
   345  				// able to portably create a symlink to a file we can't see.
   346  				return symlinkErr
   347  			}
   348  
   349  			if err := createEmpty(filename, mode|0644); err == nil {
   350  				// Successfully opened the source (or verified that it does not exist) and
   351  				// created the destination, but we couldn't create it as a symlink.
   352  				// Probably the OS just doesn't support symlinks in this context.
   353  				return &os.PathError{Op: "Symlink", Path: filename, Err: ErrUnsupported}
   354  			}
   355  		}
   356  
   357  		return symlinkErr
   358  	}
   359  }
   360  
   361  // builderMustSupportLinks reports whether we are running on a Go builder
   362  // that is known to support hard and symbolic links.
   363  func builderMustSupportLinks() bool {
   364  	if os.Getenv("GO_BUILDER_NAME") == "" {
   365  		// Any OS can be configured to mount an exotic filesystem.
   366  		// Don't make assumptions about what users are running.
   367  		return false
   368  	}
   369  
   370  	switch runtime.GOOS {
   371  	case "windows", "plan9":
   372  		// Some versions of Windows and all versions of plan9 do not support
   373  		// symlinks by default.
   374  		return false
   375  
   376  	default:
   377  		// All other platforms should support symlinks by default, and our builders
   378  		// should not do anything unusual that would violate that.
   379  		return true
   380  	}
   381  }
   382  
   383  // openAndStat attempts to open source for reading.
   384  func openAndStat(source string) (os.FileInfo, error) {
   385  	src, err := os.Open(source)
   386  	if err != nil {
   387  		return nil, err
   388  	}
   389  	stat, err := src.Stat()
   390  	src.Close()
   391  	if err != nil {
   392  		return nil, err
   393  	}
   394  	return stat, nil
   395  }
   396  
   397  // createEmpty creates an empty file or directory (depending on mode)
   398  // at dst, with the same permissions as mode.
   399  func createEmpty(dst string, mode os.FileMode) error {
   400  	if mode.IsDir() {
   401  		return os.Mkdir(dst, mode.Perm())
   402  	}
   403  
   404  	f, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_EXCL, mode.Perm())
   405  	if err != nil {
   406  		return err
   407  	}
   408  	if err := f.Close(); err != nil {
   409  		os.Remove(dst) // best-effort
   410  		return err
   411  	}
   412  
   413  	return nil
   414  }
   415  
   416  // Copy returns a Writer that copies a file from the specified source to the
   417  // required file.
   418  // This is used to copy testdata files into the generated testing tree.
   419  func Copy(source string) Writer {
   420  	return func(filename string) error {
   421  		stat, err := os.Stat(source)
   422  		if err != nil {
   423  			return err
   424  		}
   425  		if !stat.Mode().IsRegular() {
   426  			// cannot copy non-regular files (e.g., directories,
   427  			// symlinks, devices, etc.)
   428  			return fmt.Errorf("cannot copy non regular file %s", source)
   429  		}
   430  		return copyFile(filename, source, stat.Mode().Perm())
   431  	}
   432  }
   433  
   434  func copyFile(dest, source string, perm os.FileMode) error {
   435  	src, err := os.Open(source)
   436  	if err != nil {
   437  		return err
   438  	}
   439  	defer src.Close()
   440  
   441  	dst, err := os.OpenFile(dest, os.O_WRONLY|os.O_CREATE|os.O_EXCL, perm)
   442  	if err != nil {
   443  		return err
   444  	}
   445  
   446  	_, err = io.Copy(dst, src)
   447  	if closeErr := dst.Close(); err == nil {
   448  		err = closeErr
   449  	}
   450  	return err
   451  }
   452  
   453  // GroupFilesByModules attempts to map directories to the modules within each directory.
   454  // This function assumes that the folder is structured in the following way:
   455  // - dir
   456  //   - primarymod
   457  //     - .go files
   458  //		 - packages
   459  //		 - go.mod (optional)
   460  //	 - modules
   461  // 		 - repoa
   462  //		   - mod1
   463  //	       - .go files
   464  //			   -  packages
   465  //		  	 - go.mod (optional)
   466  // It scans the directory tree anchored at root and adds a Copy writer to the
   467  // map for every file found.
   468  // This is to enable the common case in tests where you have a full copy of the
   469  // package in your testdata.
   470  func GroupFilesByModules(root string) ([]Module, error) {
   471  	root = filepath.FromSlash(root)
   472  	primarymodPath := filepath.Join(root, "primarymod")
   473  
   474  	_, err := os.Stat(primarymodPath)
   475  	if os.IsNotExist(err) {
   476  		return nil, fmt.Errorf("could not find primarymod folder within %s", root)
   477  	}
   478  
   479  	primarymod := &Module{
   480  		Name:    root,
   481  		Files:   make(map[string]interface{}),
   482  		Overlay: make(map[string][]byte),
   483  	}
   484  	mods := map[string]*Module{
   485  		root: primarymod,
   486  	}
   487  	modules := []Module{*primarymod}
   488  
   489  	if err := filepath.Walk(primarymodPath, func(path string, info os.FileInfo, err error) error {
   490  		if err != nil {
   491  			return err
   492  		}
   493  		if info.IsDir() {
   494  			return nil
   495  		}
   496  		fragment, err := filepath.Rel(primarymodPath, path)
   497  		if err != nil {
   498  			return err
   499  		}
   500  		primarymod.Files[filepath.ToSlash(fragment)] = Copy(path)
   501  		return nil
   502  	}); err != nil {
   503  		return nil, err
   504  	}
   505  
   506  	modulesPath := filepath.Join(root, "modules")
   507  	if _, err := os.Stat(modulesPath); os.IsNotExist(err) {
   508  		return modules, nil
   509  	}
   510  
   511  	var currentRepo, currentModule string
   512  	updateCurrentModule := func(dir string) {
   513  		if dir == currentModule {
   514  			return
   515  		}
   516  		// Handle the case where we step into a nested directory that is a module
   517  		// and then step out into the parent which is also a module.
   518  		// Example:
   519  		// - repoa
   520  		//   - moda
   521  		//     - go.mod
   522  		//     - v2
   523  		//       - go.mod
   524  		//     - what.go
   525  		//   - modb
   526  		for dir != root {
   527  			if mods[dir] != nil {
   528  				currentModule = dir
   529  				return
   530  			}
   531  			dir = filepath.Dir(dir)
   532  		}
   533  	}
   534  
   535  	if err := filepath.Walk(modulesPath, func(path string, info os.FileInfo, err error) error {
   536  		if err != nil {
   537  			return err
   538  		}
   539  		enclosingDir := filepath.Dir(path)
   540  		// If the path is not a directory, then we want to add the path to
   541  		// the files map of the currentModule.
   542  		if !info.IsDir() {
   543  			updateCurrentModule(enclosingDir)
   544  			fragment, err := filepath.Rel(currentModule, path)
   545  			if err != nil {
   546  				return err
   547  			}
   548  			mods[currentModule].Files[filepath.ToSlash(fragment)] = Copy(path)
   549  			return nil
   550  		}
   551  		// If the path is a directory and it's enclosing folder is equal to
   552  		// the modules folder, then the path is a new repo.
   553  		if enclosingDir == modulesPath {
   554  			currentRepo = path
   555  			return nil
   556  		}
   557  		// If the path is a directory and it's enclosing folder is not the same
   558  		// as the current repo and it is not of the form `v1`,`v2`,...
   559  		// then the path is a folder/package of the current module.
   560  		if enclosingDir != currentRepo && !versionSuffixRE.MatchString(filepath.Base(path)) {
   561  			return nil
   562  		}
   563  		// If the path is a directory and it's enclosing folder is the current repo
   564  		// then the path is a new module.
   565  		module, err := filepath.Rel(modulesPath, path)
   566  		if err != nil {
   567  			return err
   568  		}
   569  		mods[path] = &Module{
   570  			Name:    filepath.ToSlash(module),
   571  			Files:   make(map[string]interface{}),
   572  			Overlay: make(map[string][]byte),
   573  		}
   574  		currentModule = path
   575  		modules = append(modules, *mods[path])
   576  		return nil
   577  	}); err != nil {
   578  		return nil, err
   579  	}
   580  	return modules, nil
   581  }
   582  
   583  // MustCopyFileTree returns a file set for a module based on a real directory tree.
   584  // It scans the directory tree anchored at root and adds a Copy writer to the
   585  // map for every file found. It skips copying files in nested modules.
   586  // This is to enable the common case in tests where you have a full copy of the
   587  // package in your testdata.
   588  // This will panic if there is any kind of error trying to walk the file tree.
   589  func MustCopyFileTree(root string) map[string]interface{} {
   590  	result := map[string]interface{}{}
   591  	if err := filepath.Walk(filepath.FromSlash(root), func(path string, info os.FileInfo, err error) error {
   592  		if err != nil {
   593  			return err
   594  		}
   595  		if info.IsDir() {
   596  			// skip nested modules.
   597  			if path != root {
   598  				if fi, err := os.Stat(filepath.Join(path, "go.mod")); err == nil && !fi.IsDir() {
   599  					return filepath.SkipDir
   600  				}
   601  			}
   602  			return nil
   603  		}
   604  		fragment, err := filepath.Rel(root, path)
   605  		if err != nil {
   606  			return err
   607  		}
   608  		result[filepath.ToSlash(fragment)] = Copy(path)
   609  		return nil
   610  	}); err != nil {
   611  		log.Panic(fmt.Sprintf("MustCopyFileTree failed: %v", err))
   612  	}
   613  	return result
   614  }
   615  
   616  // Cleanup removes the temporary directory (unless the --skip-cleanup flag was set)
   617  // It is safe to call cleanup multiple times.
   618  func (e *Exported) Cleanup() {
   619  	if e.temp == "" {
   620  		return
   621  	}
   622  	if *skipCleanup {
   623  		log.Printf("Skipping cleanup of temp dir: %s", e.temp)
   624  		return
   625  	}
   626  	// Make everything read-write so that the Module exporter's module cache can be deleted.
   627  	filepath.Walk(e.temp, func(path string, info os.FileInfo, err error) error {
   628  		if err != nil {
   629  			return nil
   630  		}
   631  		if info.IsDir() {
   632  			os.Chmod(path, 0777)
   633  		}
   634  		return nil
   635  	})
   636  	os.RemoveAll(e.temp) // ignore errors
   637  	e.temp = ""
   638  }
   639  
   640  // Temp returns the temporary directory that was generated.
   641  func (e *Exported) Temp() string {
   642  	return e.temp
   643  }
   644  
   645  // File returns the full path for the given module and file fragment.
   646  func (e *Exported) File(module, fragment string) string {
   647  	if m := e.written[module]; m != nil {
   648  		return m[fragment]
   649  	}
   650  	return ""
   651  }
   652  
   653  // FileContents returns the contents of the specified file.
   654  // It will use the overlay if the file is present, otherwise it will read it
   655  // from disk.
   656  func (e *Exported) FileContents(filename string) ([]byte, error) {
   657  	if content, found := e.Config.Overlay[filename]; found {
   658  		return content, nil
   659  	}
   660  	content, err := ioutil.ReadFile(filename)
   661  	if err != nil {
   662  		return nil, err
   663  	}
   664  	return content, nil
   665  }