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  }