gopkg.in/alecthomas/gometalinter.v3@v3.0.0/_linters/src/golang.org/x/tools/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 package packagestest 13 14 import ( 15 "flag" 16 "fmt" 17 "go/token" 18 "io/ioutil" 19 "log" 20 "os" 21 "path/filepath" 22 "strings" 23 "testing" 24 25 "golang.org/x/tools/go/expect" 26 "golang.org/x/tools/go/packages" 27 ) 28 29 var ( 30 skipCleanup = flag.Bool("skip-cleanup", false, "Do not delete the temporary export folders") // for debugging 31 ) 32 33 // Module is a representation of a go module. 34 type Module struct { 35 // Name is the base name of the module as it would be in the go.mod file. 36 Name string 37 // Files is the set of source files for all packages that make up the module. 38 // The keys are the file fragment that follows the module name, the value can 39 // be a string or byte slice, in which case it is the contents of the 40 // file, otherwise it must be a Writer function. 41 Files map[string]interface{} 42 43 // Overlay is the set of source file overlays for the module. 44 // The keys are the file fragment as in the Files configuration. 45 // The values are the in memory overlay content for the file. 46 Overlay map[string][]byte 47 } 48 49 // A Writer is a function that writes out a test file. 50 // It is provided the name of the file to write, and may return an error if it 51 // cannot write the file. 52 // These are used as the content of the Files map in a Module. 53 type Writer func(filename string) error 54 55 // Exported is returned by the Export function to report the structure that was produced on disk. 56 type Exported struct { 57 // Config is a correctly configured packages.Config ready to be passed to packages.Load. 58 // Exactly what it will contain varies depending on the Exporter being used. 59 Config *packages.Config 60 61 // Modules is the module description that was used to produce this exported data set. 62 Modules []Module 63 64 temp string // the temporary directory that was exported to 65 primary string // the first non GOROOT module that was exported 66 written map[string]map[string]string // the full set of exported files 67 fset *token.FileSet // The file set used when parsing expectations 68 notes []*expect.Note // The list of expectations extracted from go source files 69 markers map[string]Range // The set of markers extracted from go source files 70 } 71 72 // Exporter implementations are responsible for converting from the generic description of some 73 // test data to a driver specific file layout. 74 type Exporter interface { 75 // Name reports the name of the exporter, used in logging and sub-test generation. 76 Name() string 77 // Filename reports the system filename for test data source file. 78 // It is given the base directory, the module the file is part of and the filename fragment to 79 // work from. 80 Filename(exported *Exported, module, fragment string) string 81 // Finalize is called once all files have been written to write any extra data needed and modify 82 // the Config to match. It is handed the full list of modules that were encountered while writing 83 // files. 84 Finalize(exported *Exported) error 85 } 86 87 // All is the list of known exporters. 88 // This is used by TestAll to run tests with all the exporters. 89 var All []Exporter 90 91 // TestAll invokes the testing function once for each exporter registered in 92 // the All global. 93 // Each exporter will be run as a sub-test named after the exporter being used. 94 func TestAll(t *testing.T, f func(*testing.T, Exporter)) { 95 t.Helper() 96 for _, e := range All { 97 t.Run(e.Name(), func(t *testing.T) { 98 t.Helper() 99 f(t, e) 100 }) 101 } 102 } 103 104 // BenchmarkAll invokes the testing function once for each exporter registered in 105 // the All global. 106 // Each exporter will be run as a sub-test named after the exporter being used. 107 func BenchmarkAll(b *testing.B, f func(*testing.B, Exporter)) { 108 b.Helper() 109 for _, e := range All { 110 b.Run(e.Name(), func(b *testing.B) { 111 b.Helper() 112 f(b, e) 113 }) 114 } 115 } 116 117 // Export is called to write out a test directory from within a test function. 118 // It takes the exporter and the build system agnostic module descriptions, and 119 // uses them to build a temporary directory. 120 // It returns an Exported with the results of the export. 121 // The Exported.Config is prepared for loading from the exported data. 122 // You must invoke Exported.Cleanup on the returned value to clean up. 123 // The file deletion in the cleanup can be skipped by setting the skip-cleanup 124 // flag when invoking the test, allowing the temporary directory to be left for 125 // debugging tests. 126 func Export(t testing.TB, exporter Exporter, modules []Module) *Exported { 127 t.Helper() 128 dirname := strings.Replace(t.Name(), "/", "_", -1) 129 dirname = strings.Replace(dirname, "#", "_", -1) // duplicate subtests get a #NNN suffix. 130 temp, err := ioutil.TempDir("", dirname) 131 if err != nil { 132 t.Fatal(err) 133 } 134 exported := &Exported{ 135 Config: &packages.Config{ 136 Dir: temp, 137 Env: append(os.Environ(), "GOPACKAGESDRIVER=off"), 138 Overlay: make(map[string][]byte), 139 }, 140 Modules: modules, 141 temp: temp, 142 primary: modules[0].Name, 143 written: map[string]map[string]string{}, 144 fset: token.NewFileSet(), 145 } 146 defer func() { 147 if t.Failed() || t.Skipped() { 148 exported.Cleanup() 149 } 150 }() 151 for _, module := range modules { 152 for fragment, value := range module.Files { 153 fullpath := exporter.Filename(exported, module.Name, filepath.FromSlash(fragment)) 154 written, ok := exported.written[module.Name] 155 if !ok { 156 written = map[string]string{} 157 exported.written[module.Name] = written 158 } 159 written[fragment] = fullpath 160 if err := os.MkdirAll(filepath.Dir(fullpath), 0755); err != nil { 161 t.Fatal(err) 162 } 163 switch value := value.(type) { 164 case Writer: 165 if err := value(fullpath); err != nil { 166 t.Fatal(err) 167 } 168 case string: 169 if err := ioutil.WriteFile(fullpath, []byte(value), 0644); err != nil { 170 t.Fatal(err) 171 } 172 default: 173 t.Fatalf("Invalid type %T in files, must be string or Writer", value) 174 } 175 } 176 for fragment, value := range module.Overlay { 177 fullpath := exporter.Filename(exported, module.Name, filepath.FromSlash(fragment)) 178 exported.Config.Overlay[fullpath] = value 179 } 180 } 181 if err := exporter.Finalize(exported); err != nil { 182 t.Fatal(err) 183 } 184 return exported 185 } 186 187 // Script returns a Writer that writes out contents to the file and sets the 188 // executable bit on the created file. 189 // It is intended for source files that are shell scripts. 190 func Script(contents string) Writer { 191 return func(filename string) error { 192 return ioutil.WriteFile(filename, []byte(contents), 0755) 193 } 194 } 195 196 // Link returns a Writer that creates a hard link from the specified source to 197 // the required file. 198 // This is used to link testdata files into the generated testing tree. 199 func Link(source string) Writer { 200 return func(filename string) error { 201 return os.Link(source, filename) 202 } 203 } 204 205 // Symlink returns a Writer that creates a symlink from the specified source to the 206 // required file. 207 // This is used to link testdata files into the generated testing tree. 208 func Symlink(source string) Writer { 209 if !strings.HasPrefix(source, ".") { 210 if abspath, err := filepath.Abs(source); err == nil { 211 if _, err := os.Stat(source); !os.IsNotExist(err) { 212 source = abspath 213 } 214 } 215 } 216 return func(filename string) error { 217 return os.Symlink(source, filename) 218 } 219 } 220 221 // Copy returns a Writer that copies a file from the specified source to the 222 // required file. 223 // This is used to copy testdata files into the generated testing tree. 224 func Copy(source string) Writer { 225 return func(filename string) error { 226 stat, err := os.Stat(source) 227 if err != nil { 228 return err 229 } 230 if !stat.Mode().IsRegular() { 231 // cannot copy non-regular files (e.g., directories, 232 // symlinks, devices, etc.) 233 return fmt.Errorf("Cannot copy non regular file %s", source) 234 } 235 contents, err := ioutil.ReadFile(source) 236 if err != nil { 237 return err 238 } 239 return ioutil.WriteFile(filename, contents, stat.Mode()) 240 } 241 } 242 243 // MustCopyFileTree returns a file set for a module based on a real directory tree. 244 // It scans the directory tree anchored at root and adds a Copy writer to the 245 // map for every file found. 246 // This is to enable the common case in tests where you have a full copy of the 247 // package in your testdata. 248 // This will panic if there is any kind of error trying to walk the file tree. 249 func MustCopyFileTree(root string) map[string]interface{} { 250 result := map[string]interface{}{} 251 if err := filepath.Walk(filepath.FromSlash(root), func(path string, info os.FileInfo, err error) error { 252 if err != nil { 253 return err 254 } 255 if info.IsDir() { 256 return nil 257 } 258 fragment, err := filepath.Rel(root, path) 259 if err != nil { 260 return err 261 } 262 result[fragment] = Copy(path) 263 return nil 264 }); err != nil { 265 log.Panic(fmt.Sprintf("MustCopyFileTree failed: %v", err)) 266 } 267 return result 268 } 269 270 // Cleanup removes the temporary directory (unless the --skip-cleanup flag was set) 271 // It is safe to call cleanup multiple times. 272 func (e *Exported) Cleanup() { 273 if e.temp == "" { 274 return 275 } 276 if *skipCleanup { 277 log.Printf("Skipping cleanup of temp dir: %s", e.temp) 278 return 279 } 280 // Make everything read-write so that the Module exporter's module cache can be deleted. 281 filepath.Walk(e.temp, func(path string, info os.FileInfo, err error) error { 282 if err != nil { 283 return nil 284 } 285 if info.IsDir() { 286 os.Chmod(path, 0777) 287 } 288 return nil 289 }) 290 os.RemoveAll(e.temp) // ignore errors 291 e.temp = "" 292 } 293 294 // Temp returns the temporary directory that was generated. 295 func (e *Exported) Temp() string { 296 return e.temp 297 } 298 299 // File returns the full path for the given module and file fragment. 300 func (e *Exported) File(module, fragment string) string { 301 if m := e.written[module]; m != nil { 302 return m[fragment] 303 } 304 return "" 305 } 306 307 // FileContents returns the contents of the specified file. 308 // It will use the overlay if the file is present, otherwise it will read it 309 // from disk. 310 func (e *Exported) FileContents(filename string) ([]byte, error) { 311 if content, found := e.Config.Overlay[filename]; found { 312 return content, nil 313 } 314 content, err := ioutil.ReadFile(filename) 315 if err != nil { 316 return nil, err 317 } 318 return content, nil 319 }