1
2
3
4
5
64 package packagestest
65
66 import (
67 "errors"
68 "flag"
69 "fmt"
70 "go/token"
71 "io"
72 "io/ioutil"
73 "log"
74 "os"
75 "path/filepath"
76 "runtime"
77 "strings"
78 "testing"
79
80 "golang.org/x/tools/go/expect"
81 "golang.org/x/tools/go/packages"
82 "golang.org/x/tools/internal/testenv"
83 )
84
85 var (
86 skipCleanup = flag.Bool("skip-cleanup", false, "Do not delete the temporary export folders")
87 )
88
89
90
91 var ErrUnsupported = errors.New("operation is not supported")
92
93
94 type Module struct {
95
96 Name string
97
98
99
100
101 Files map[string]interface{}
102
103
104
105
106 Overlay map[string][]byte
107 }
108
109
110
111
112
113 type Writer func(filename string) error
114
115
116 type Exported struct {
117
118
119 Config *packages.Config
120
121
122 Modules []Module
123
124 ExpectFileSet *token.FileSet
125
126 Exporter Exporter
127 temp string
128 primary string
129 written map[string]map[string]string
130 notes []*expect.Note
131 markers map[string]Range
132 }
133
134
135
136 type Exporter interface {
137
138 Name() string
139
140
141
142 Filename(exported *Exported, module, fragment string) string
143
144
145
146 Finalize(exported *Exported) error
147 }
148
149
150
151 var All []Exporter
152
153
154
155
156 func TestAll(t *testing.T, f func(*testing.T, Exporter)) {
157 t.Helper()
158 for _, e := range All {
159 e := e
160 t.Run(e.Name(), func(t *testing.T) {
161 t.Helper()
162 f(t, e)
163 })
164 }
165 }
166
167
168
169
170 func BenchmarkAll(b *testing.B, f func(*testing.B, Exporter)) {
171 b.Helper()
172 for _, e := range All {
173 e := e
174 b.Run(e.Name(), func(b *testing.B) {
175 b.Helper()
176 f(b, e)
177 })
178 }
179 }
180
181
182
183
184
185
186
187
188
189
190
191
192
193 func Export(t testing.TB, exporter Exporter, modules []Module) *Exported {
194 t.Helper()
195 if exporter == Modules {
196 testenv.NeedsTool(t, "go")
197 }
198
199 dirname := strings.Replace(t.Name(), "/", "_", -1)
200 dirname = strings.Replace(dirname, "#", "_", -1)
201 temp, err := ioutil.TempDir("", dirname)
202 if err != nil {
203 t.Fatal(err)
204 }
205 exported := &Exported{
206 Config: &packages.Config{
207 Dir: temp,
208 Env: append(os.Environ(), "GOPACKAGESDRIVER=off", "GOROOT="),
209 Overlay: make(map[string][]byte),
210 Tests: true,
211 Mode: packages.LoadImports,
212 },
213 Modules: modules,
214 Exporter: exporter,
215 temp: temp,
216 primary: modules[0].Name,
217 written: map[string]map[string]string{},
218 ExpectFileSet: token.NewFileSet(),
219 }
220 defer func() {
221 if t.Failed() || t.Skipped() {
222 exported.Cleanup()
223 }
224 }()
225 for _, module := range modules {
226
227
228
229
230 for fragment := range module.Files {
231 fullpath := exporter.Filename(exported, module.Name, filepath.FromSlash(fragment))
232 if err := os.MkdirAll(filepath.Dir(fullpath), 0755); err != nil {
233 t.Fatal(err)
234 }
235 }
236
237 for fragment, value := range module.Files {
238 fullpath := exporter.Filename(exported, module.Name, filepath.FromSlash(fragment))
239 written, ok := exported.written[module.Name]
240 if !ok {
241 written = map[string]string{}
242 exported.written[module.Name] = written
243 }
244 written[fragment] = fullpath
245 switch value := value.(type) {
246 case Writer:
247 if err := value(fullpath); err != nil {
248 if errors.Is(err, ErrUnsupported) {
249 t.Skip(err)
250 }
251 t.Fatal(err)
252 }
253 case string:
254 if err := ioutil.WriteFile(fullpath, []byte(value), 0644); err != nil {
255 t.Fatal(err)
256 }
257 default:
258 t.Fatalf("Invalid type %T in files, must be string or Writer", value)
259 }
260 }
261 for fragment, value := range module.Overlay {
262 fullpath := exporter.Filename(exported, module.Name, filepath.FromSlash(fragment))
263 exported.Config.Overlay[fullpath] = value
264 }
265 }
266 if err := exporter.Finalize(exported); err != nil {
267 t.Fatal(err)
268 }
269 testenv.NeedsGoPackagesEnv(t, exported.Config.Env)
270 return exported
271 }
272
273
274
275
276 func Script(contents string) Writer {
277 return func(filename string) error {
278 return ioutil.WriteFile(filename, []byte(contents), 0755)
279 }
280 }
281
282
283
284
285
286
287
288
289 func Link(source string) Writer {
290 return func(filename string) error {
291 linkErr := os.Link(source, filename)
292
293 if linkErr != nil && !builderMustSupportLinks() {
294
295
296 if stat, err := openAndStat(source); err == nil {
297 if err := createEmpty(filename, stat.Mode()); err == nil {
298
299
300 return &os.PathError{Op: "Link", Path: filename, Err: ErrUnsupported}
301 }
302 }
303 }
304
305 return linkErr
306 }
307 }
308
309
310
311
312
313
314
315
316 func Symlink(source string) Writer {
317 if !strings.HasPrefix(source, ".") {
318 if absSource, err := filepath.Abs(source); err == nil {
319 if _, err := os.Stat(source); !os.IsNotExist(err) {
320 source = absSource
321 }
322 }
323 }
324 return func(filename string) error {
325 symlinkErr := os.Symlink(source, filename)
326
327 if symlinkErr != nil && !builderMustSupportLinks() {
328
329
330 fullSource := source
331 if !filepath.IsAbs(source) {
332
333
334 fullSource = filepath.Join(filename, "..", source)
335 }
336 stat, err := openAndStat(fullSource)
337 mode := os.ModePerm
338 if err == nil {
339 mode = stat.Mode()
340 } else if !errors.Is(err, os.ErrNotExist) {
341
342
343 return symlinkErr
344 }
345
346 if err := createEmpty(filename, mode|0644); err == nil {
347
348
349
350 return &os.PathError{Op: "Symlink", Path: filename, Err: ErrUnsupported}
351 }
352 }
353
354 return symlinkErr
355 }
356 }
357
358
359
360 func builderMustSupportLinks() bool {
361 if os.Getenv("GO_BUILDER_NAME") == "" {
362
363
364 return false
365 }
366
367 switch runtime.GOOS {
368 case "windows", "plan9":
369
370
371 return false
372
373 default:
374
375
376 return true
377 }
378 }
379
380
381 func openAndStat(source string) (os.FileInfo, error) {
382 src, err := os.Open(source)
383 if err != nil {
384 return nil, err
385 }
386 stat, err := src.Stat()
387 src.Close()
388 if err != nil {
389 return nil, err
390 }
391 return stat, nil
392 }
393
394
395
396 func createEmpty(dst string, mode os.FileMode) error {
397 if mode.IsDir() {
398 return os.Mkdir(dst, mode.Perm())
399 }
400
401 f, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_EXCL, mode.Perm())
402 if err != nil {
403 return err
404 }
405 if err := f.Close(); err != nil {
406 os.Remove(dst)
407 return err
408 }
409
410 return nil
411 }
412
413
414
415
416 func Copy(source string) Writer {
417 return func(filename string) error {
418 stat, err := os.Stat(source)
419 if err != nil {
420 return err
421 }
422 if !stat.Mode().IsRegular() {
423
424
425 return fmt.Errorf("cannot copy non regular file %s", source)
426 }
427 return copyFile(filename, source, stat.Mode().Perm())
428 }
429 }
430
431 func copyFile(dest, source string, perm os.FileMode) error {
432 src, err := os.Open(source)
433 if err != nil {
434 return err
435 }
436 defer src.Close()
437
438 dst, err := os.OpenFile(dest, os.O_WRONLY|os.O_CREATE|os.O_EXCL, perm)
439 if err != nil {
440 return err
441 }
442
443 _, err = io.Copy(dst, src)
444 if closeErr := dst.Close(); err == nil {
445 err = closeErr
446 }
447 return err
448 }
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469 func GroupFilesByModules(root string) ([]Module, error) {
470 root = filepath.FromSlash(root)
471 primarymodPath := filepath.Join(root, "primarymod")
472
473 _, err := os.Stat(primarymodPath)
474 if os.IsNotExist(err) {
475 return nil, fmt.Errorf("could not find primarymod folder within %s", root)
476 }
477
478 primarymod := &Module{
479 Name: root,
480 Files: make(map[string]interface{}),
481 Overlay: make(map[string][]byte),
482 }
483 mods := map[string]*Module{
484 root: primarymod,
485 }
486 modules := []Module{*primarymod}
487
488 if err := filepath.Walk(primarymodPath, func(path string, info os.FileInfo, err error) error {
489 if err != nil {
490 return err
491 }
492 if info.IsDir() {
493 return nil
494 }
495 fragment, err := filepath.Rel(primarymodPath, path)
496 if err != nil {
497 return err
498 }
499 primarymod.Files[filepath.ToSlash(fragment)] = Copy(path)
500 return nil
501 }); err != nil {
502 return nil, err
503 }
504
505 modulesPath := filepath.Join(root, "modules")
506 if _, err := os.Stat(modulesPath); os.IsNotExist(err) {
507 return modules, nil
508 }
509
510 var currentRepo, currentModule string
511 updateCurrentModule := func(dir string) {
512 if dir == currentModule {
513 return
514 }
515
516
517
518
519
520
521
522
523
524
525 for dir != root {
526 if mods[dir] != nil {
527 currentModule = dir
528 return
529 }
530 dir = filepath.Dir(dir)
531 }
532 }
533
534 if err := filepath.Walk(modulesPath, func(path string, info os.FileInfo, err error) error {
535 if err != nil {
536 return err
537 }
538 enclosingDir := filepath.Dir(path)
539
540
541 if !info.IsDir() {
542 updateCurrentModule(enclosingDir)
543 fragment, err := filepath.Rel(currentModule, path)
544 if err != nil {
545 return err
546 }
547 mods[currentModule].Files[filepath.ToSlash(fragment)] = Copy(path)
548 return nil
549 }
550
551
552 if enclosingDir == modulesPath {
553 currentRepo = path
554 return nil
555 }
556
557
558
559 if enclosingDir != currentRepo && !versionSuffixRE.MatchString(filepath.Base(path)) {
560 return nil
561 }
562
563
564 module, err := filepath.Rel(modulesPath, path)
565 if err != nil {
566 return err
567 }
568 mods[path] = &Module{
569 Name: filepath.ToSlash(module),
570 Files: make(map[string]interface{}),
571 Overlay: make(map[string][]byte),
572 }
573 currentModule = path
574 modules = append(modules, *mods[path])
575 return nil
576 }); err != nil {
577 return nil, err
578 }
579 return modules, nil
580 }
581
582
583
584
585
586
587
588 func MustCopyFileTree(root string) map[string]interface{} {
589 result := map[string]interface{}{}
590 if err := filepath.Walk(filepath.FromSlash(root), func(path string, info os.FileInfo, err error) error {
591 if err != nil {
592 return err
593 }
594 if info.IsDir() {
595
596 if path != root {
597 if fi, err := os.Stat(filepath.Join(path, "go.mod")); err == nil && !fi.IsDir() {
598 return filepath.SkipDir
599 }
600 }
601 return nil
602 }
603 fragment, err := filepath.Rel(root, path)
604 if err != nil {
605 return err
606 }
607 result[filepath.ToSlash(fragment)] = Copy(path)
608 return nil
609 }); err != nil {
610 log.Panic(fmt.Sprintf("MustCopyFileTree failed: %v", err))
611 }
612 return result
613 }
614
615
616
617 func (e *Exported) Cleanup() {
618 if e.temp == "" {
619 return
620 }
621 if *skipCleanup {
622 log.Printf("Skipping cleanup of temp dir: %s", e.temp)
623 return
624 }
625
626 filepath.Walk(e.temp, func(path string, info os.FileInfo, err error) error {
627 if err != nil {
628 return nil
629 }
630 if info.IsDir() {
631 os.Chmod(path, 0777)
632 }
633 return nil
634 })
635 os.RemoveAll(e.temp)
636 e.temp = ""
637 }
638
639
640 func (e *Exported) Temp() string {
641 return e.temp
642 }
643
644
645 func (e *Exported) File(module, fragment string) string {
646 if m := e.written[module]; m != nil {
647 return m[fragment]
648 }
649 return ""
650 }
651
652
653
654
655 func (e *Exported) FileContents(filename string) ([]byte, error) {
656 if content, found := e.Config.Overlay[filename]; found {
657 return content, nil
658 }
659 content, err := ioutil.ReadFile(filename)
660 if err != nil {
661 return nil, err
662 }
663 return content, nil
664 }
665
View as plain text