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