golang.org/x/tools@v0.21.0/cmd/gonew/main_test.go (about) 1 // Copyright 2023 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 main 6 7 import ( 8 "archive/zip" 9 "bytes" 10 "fmt" 11 "io/fs" 12 "os" 13 "os/exec" 14 "path/filepath" 15 "runtime" 16 "strings" 17 "testing" 18 19 "golang.org/x/tools/internal/diffp" 20 "golang.org/x/tools/internal/testenv" 21 "golang.org/x/tools/txtar" 22 ) 23 24 func init() { 25 if os.Getenv("TestGonewMain") == "1" { 26 main() 27 os.Exit(0) 28 } 29 } 30 31 func Test(t *testing.T) { 32 if !testenv.HasExec() { 33 t.Skipf("skipping test: exec not supported on %s/%s", runtime.GOOS, runtime.GOARCH) 34 } 35 exe, err := os.Executable() 36 if err != nil { 37 t.Fatal(err) 38 } 39 40 // Each file in testdata is a txtar file with the command to run, 41 // the contents of modules to initialize in a fake proxy, 42 // the expected stdout and stderr, and the expected file contents. 43 files, err := filepath.Glob("testdata/*.txt") 44 if err != nil { 45 t.Fatal(err) 46 } 47 if len(files) == 0 { 48 t.Fatal("no test cases") 49 } 50 51 for _, file := range files { 52 t.Run(filepath.Base(file), func(t *testing.T) { 53 data, err := os.ReadFile(file) 54 if err != nil { 55 t.Fatal(err) 56 } 57 ar := txtar.Parse(data) 58 59 // If the command begins with ! it means it should fail. 60 // After the optional ! the first argument must be 'gonew' 61 // followed by the arguments to gonew. 62 args := strings.Fields(string(ar.Comment)) 63 wantFail := false 64 if len(args) > 0 && args[0] == "!" { 65 wantFail = true 66 args = args[1:] 67 } 68 if len(args) == 0 || args[0] != "gonew" { 69 t.Fatalf("invalid command comment") 70 } 71 72 // Collect modules into proxy tree and store in temp directory. 73 dir := t.TempDir() 74 proxyDir := filepath.Join(dir, "proxy") 75 writeProxyFiles(t, proxyDir, ar) 76 extra := "" 77 if runtime.GOOS == "windows" { 78 // Windows absolute paths don't start with / so we need one more. 79 extra = "/" 80 } 81 proxyURL := "file://" + extra + filepath.ToSlash(proxyDir) 82 83 // Run gonew in a fresh 'out' directory. 84 out := filepath.Join(dir, "out") 85 if err := os.Mkdir(out, 0777); err != nil { 86 t.Fatal(err) 87 } 88 cmd := exec.Command(exe, args[1:]...) 89 cmd.Dir = out 90 cmd.Env = append(os.Environ(), "TestGonewMain=1", "GOPROXY="+proxyURL, "GOSUMDB=off") 91 var stdout bytes.Buffer 92 var stderr bytes.Buffer 93 cmd.Stdout = &stdout 94 cmd.Stderr = &stderr 95 if err := cmd.Run(); err == nil && wantFail { 96 t.Errorf("unexpected success exit") 97 } else if err != nil && !wantFail { 98 t.Errorf("unexpected failure exit") 99 } 100 101 // Collect the expected output from the txtar. 102 want := make(map[string]txtar.File) 103 for _, f := range ar.Files { 104 if f.Name == "stdout" || f.Name == "stderr" || strings.HasPrefix(f.Name, "out/") { 105 want[f.Name] = f 106 } 107 } 108 109 // Check stdout and stderr. 110 // Change \ to / so Windows output looks like Unix output. 111 stdoutBuf := bytes.ReplaceAll(stdout.Bytes(), []byte(`\`), []byte("/")) 112 stderrBuf := bytes.ReplaceAll(stderr.Bytes(), []byte(`\`), []byte("/")) 113 // Note that stdout and stderr can be omitted from the archive if empty. 114 if !bytes.Equal(stdoutBuf, want["stdout"].Data) { 115 t.Errorf("wrong stdout: %s", diffp.Diff("want", want["stdout"].Data, "have", stdoutBuf)) 116 } 117 if !bytes.Equal(stderrBuf, want["stderr"].Data) { 118 t.Errorf("wrong stderr: %s", diffp.Diff("want", want["stderr"].Data, "have", stderrBuf)) 119 } 120 delete(want, "stdout") 121 delete(want, "stderr") 122 123 // Check remaining expected outputs. 124 err = filepath.WalkDir(out, func(name string, info fs.DirEntry, err error) error { 125 if err != nil { 126 return err 127 } 128 if info.IsDir() { 129 return nil 130 } 131 data, err := os.ReadFile(name) 132 if err != nil { 133 return err 134 } 135 short := "out" + filepath.ToSlash(strings.TrimPrefix(name, out)) 136 f, ok := want[short] 137 if !ok { 138 t.Errorf("unexpected file %s:\n%s", short, data) 139 return nil 140 } 141 delete(want, short) 142 if !bytes.Equal(data, f.Data) { 143 t.Errorf("wrong %s: %s", short, diffp.Diff("want", f.Data, "have", data)) 144 } 145 return nil 146 }) 147 if err != nil { 148 t.Fatal(err) 149 } 150 for name := range want { 151 t.Errorf("missing file %s", name) 152 } 153 }) 154 } 155 } 156 157 // A Zip is a zip file being written. 158 type Zip struct { 159 buf bytes.Buffer 160 w *zip.Writer 161 } 162 163 // writeProxyFiles collects all the module content from ar and writes 164 // files in the format of the proxy URL space, so that the 'proxy' directory 165 // can be used in a GOPROXY=file:/// URL. 166 func writeProxyFiles(t *testing.T, proxy string, ar *txtar.Archive) { 167 zips := make(map[string]*Zip) 168 others := make(map[string]string) 169 for _, f := range ar.Files { 170 i := strings.Index(f.Name, "@") 171 if i < 0 { 172 continue 173 } 174 j := strings.Index(f.Name[i:], "/") 175 if j < 0 { 176 t.Fatalf("unexpected archive file %s", f.Name) 177 } 178 j += i 179 mod, vers, file := f.Name[:i], f.Name[i+1:j], f.Name[j+1:] 180 zipName := mod + "/@v/" + vers + ".zip" 181 z := zips[zipName] 182 if z == nil { 183 others[mod+"/@v/list"] += vers + "\n" 184 others[mod+"/@v/"+vers+".info"] = fmt.Sprintf("{%q: %q}\n", "Version", vers) 185 z = new(Zip) 186 z.w = zip.NewWriter(&z.buf) 187 zips[zipName] = z 188 } 189 if file == "go.mod" { 190 others[mod+"/@v/"+vers+".mod"] = string(f.Data) 191 } 192 w, err := z.w.Create(f.Name) 193 if err != nil { 194 t.Fatal(err) 195 } 196 if _, err := w.Write(f.Data); err != nil { 197 t.Fatal(err) 198 } 199 } 200 201 for name, z := range zips { 202 if err := z.w.Close(); err != nil { 203 t.Fatal(err) 204 } 205 if err := os.MkdirAll(filepath.Dir(filepath.Join(proxy, name)), 0777); err != nil { 206 t.Fatal(err) 207 } 208 if err := os.WriteFile(filepath.Join(proxy, name), z.buf.Bytes(), 0666); err != nil { 209 t.Fatal(err) 210 } 211 } 212 for name, data := range others { 213 // zip loop already created directory 214 if err := os.WriteFile(filepath.Join(proxy, name), []byte(data), 0666); err != nil { 215 t.Fatal(err) 216 } 217 } 218 }