golang.org/x/tools@v0.21.0/internal/testfiles/testfiles.go (about) 1 // Copyright 2024 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 // Package testfiles provides utilities for writing Go tests with files 6 // in testdata. 7 package testfiles 8 9 import ( 10 "io" 11 "io/fs" 12 "os" 13 "path/filepath" 14 "strings" 15 "testing" 16 17 "golang.org/x/tools/txtar" 18 ) 19 20 // CopyDirToTmp copies dir to a temporary test directory using 21 // CopyTestFiles and returns the path to the test directory. 22 func CopyDirToTmp(t testing.TB, srcdir string) string { 23 dst := t.TempDir() 24 if err := CopyFS(dst, os.DirFS(srcdir)); err != nil { 25 t.Fatal(err) 26 } 27 return dst 28 } 29 30 // CopyFS copies the files and directories in src to a 31 // destination directory dst. Paths to files and directories 32 // ending in a ".test" extension have the ".test" extension 33 // removed. This allows tests to hide files whose names have 34 // special meaning, such as "go.mod" files or "testdata" directories 35 // from the go command, or ill-formed Go source files from gofmt. 36 // 37 // For example if we copy the directory testdata: 38 // 39 // testdata/ 40 // go.mod.test 41 // a/a.go 42 // b/b.go 43 // 44 // The resulting files will be: 45 // 46 // dst/ 47 // go.mod 48 // a/a.go 49 // b/b.go 50 func CopyFS(dstdir string, src fs.FS) error { 51 if err := copyFS(dstdir, src); err != nil { 52 return err 53 } 54 55 // Collect ".test" paths in lexical order. 56 var rename []string 57 err := fs.WalkDir(os.DirFS(dstdir), ".", func(path string, d fs.DirEntry, err error) error { 58 if err != nil { 59 return err 60 } 61 if strings.HasSuffix(path, ".test") { 62 rename = append(rename, path) 63 } 64 return nil 65 }) 66 if err != nil { 67 return err 68 } 69 70 // Rename the .test paths in reverse lexical order, e.g. 71 // in d.test/a.test renames a.test to d.test/a then d.test to d. 72 for i := len(rename) - 1; i >= 0; i-- { 73 oldpath := filepath.Join(dstdir, rename[i]) 74 newpath := strings.TrimSuffix(oldpath, ".test") 75 if err != os.Rename(oldpath, newpath) { 76 return err 77 } 78 } 79 return nil 80 } 81 82 // Copy the files in src to dst. 83 // Use os.CopyFS when 1.23 can be used in x/tools. 84 func copyFS(dstdir string, src fs.FS) error { 85 return fs.WalkDir(src, ".", func(path string, d fs.DirEntry, err error) error { 86 if err != nil { 87 return err 88 } 89 newpath := filepath.Join(dstdir, path) 90 if d.IsDir() { 91 return os.MkdirAll(newpath, 0777) 92 } 93 r, err := src.Open(path) 94 if err != nil { 95 return err 96 } 97 defer r.Close() 98 99 w, err := os.Create(newpath) 100 if err != nil { 101 return err 102 } 103 defer w.Close() 104 _, err = io.Copy(w, r) 105 return err 106 }) 107 } 108 109 // ExtractTxtar writes each archive file to the corresponding location beneath dir. 110 // 111 // TODO(adonovan): move this to txtar package, we need it all the time (#61386). 112 func ExtractTxtar(dstdir string, ar *txtar.Archive) error { 113 for _, file := range ar.Files { 114 name := filepath.Join(dstdir, file.Name) 115 if err := os.MkdirAll(filepath.Dir(name), 0777); err != nil { 116 return err 117 } 118 if err := os.WriteFile(name, file.Data, 0666); err != nil { 119 return err 120 } 121 } 122 return nil 123 } 124 125 // ExtractTxtarToTmp read a txtar archive on a given path, 126 // extracts it to a temporary directory, and returns the 127 // temporary directory. 128 func ExtractTxtarToTmp(t testing.TB, archive string) string { 129 ar, err := txtar.ParseFile(archive) 130 if err != nil { 131 t.Fatal(err) 132 } 133 134 dir := t.TempDir() 135 err = ExtractTxtar(dir, ar) 136 if err != nil { 137 t.Fatal(err) 138 } 139 return dir 140 }