golang.org/x/tools@v0.21.1-0.20240520172518-788d39e776b1/cmd/deadcode/deadcode_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 //go:build go1.20 6 7 package main_test 8 9 import ( 10 "bytes" 11 "fmt" 12 "os" 13 "os/exec" 14 "path/filepath" 15 "runtime" 16 "strconv" 17 "strings" 18 "testing" 19 20 "golang.org/x/tools/internal/testenv" 21 "golang.org/x/tools/txtar" 22 ) 23 24 // Test runs the deadcode command on each scenario 25 // described by a testdata/*.txtar file. 26 func Test(t *testing.T) { 27 testenv.NeedsTool(t, "go") 28 if runtime.GOOS == "android" { 29 t.Skipf("the dependencies are not available on android") 30 } 31 32 exe := buildDeadcode(t) 33 34 matches, err := filepath.Glob("testdata/*.txtar") 35 if err != nil { 36 t.Fatal(err) 37 } 38 for _, filename := range matches { 39 filename := filename 40 t.Run(filename, func(t *testing.T) { 41 t.Parallel() 42 43 ar, err := txtar.ParseFile(filename) 44 if err != nil { 45 t.Fatal(err) 46 } 47 48 // Write the archive files to the temp directory. 49 tmpdir := t.TempDir() 50 for _, f := range ar.Files { 51 filename := filepath.Join(tmpdir, f.Name) 52 if err := os.MkdirAll(filepath.Dir(filename), 0777); err != nil { 53 t.Fatal(err) 54 } 55 if err := os.WriteFile(filename, f.Data, 0666); err != nil { 56 t.Fatal(err) 57 } 58 } 59 60 // Parse archive comment as directives of these forms: 61 // 62 // [!]deadcode args... command-line arguments 63 // [!]want arg expected/unwanted string in output (or stderr) 64 // 65 // Args may be Go-quoted strings. 66 type testcase struct { 67 linenum int 68 args []string 69 wantErr bool 70 want map[string]bool // string -> sense 71 } 72 var cases []*testcase 73 var current *testcase 74 for i, line := range strings.Split(string(ar.Comment), "\n") { 75 line = strings.TrimSpace(line) 76 if line == "" || line[0] == '#' { 77 continue // skip blanks and comments 78 } 79 80 words, err := words(line) 81 if err != nil { 82 t.Fatalf("cannot break line into words: %v (%s)", err, line) 83 } 84 switch kind := words[0]; kind { 85 case "deadcode", "!deadcode": 86 current = &testcase{ 87 linenum: i + 1, 88 want: make(map[string]bool), 89 args: words[1:], 90 wantErr: kind[0] == '!', 91 } 92 cases = append(cases, current) 93 case "want", "!want": 94 if current == nil { 95 t.Fatalf("'want' directive must be after 'deadcode'") 96 } 97 if len(words) != 2 { 98 t.Fatalf("'want' directive needs argument <<%s>>", line) 99 } 100 current.want[words[1]] = kind[0] != '!' 101 default: 102 t.Fatalf("%s: invalid directive %q", filename, kind) 103 } 104 } 105 106 for _, tc := range cases { 107 t.Run(fmt.Sprintf("L%d", tc.linenum), func(t *testing.T) { 108 // Run the command. 109 cmd := exec.Command(exe, tc.args...) 110 cmd.Stdout = new(bytes.Buffer) 111 cmd.Stderr = new(bytes.Buffer) 112 cmd.Dir = tmpdir 113 cmd.Env = append(os.Environ(), "GOPROXY=", "GO111MODULE=on") 114 var got string 115 if err := cmd.Run(); err != nil { 116 if !tc.wantErr { 117 t.Fatalf("deadcode failed: %v (stderr=%s)", err, cmd.Stderr) 118 } 119 got = fmt.Sprint(cmd.Stderr) 120 } else { 121 if tc.wantErr { 122 t.Fatalf("deadcode succeeded unexpectedly (stdout=%s)", cmd.Stdout) 123 } 124 got = fmt.Sprint(cmd.Stdout) 125 } 126 127 // Check each want directive. 128 for str, sense := range tc.want { 129 ok := true 130 if strings.Contains(got, str) != sense { 131 if sense { 132 t.Errorf("missing %q", str) 133 } else { 134 t.Errorf("unwanted %q", str) 135 } 136 ok = false 137 } 138 if !ok { 139 t.Errorf("got: <<%s>>", got) 140 } 141 } 142 }) 143 } 144 }) 145 } 146 } 147 148 // buildDeadcode builds the deadcode executable. 149 // It returns its path, and a cleanup function. 150 func buildDeadcode(t *testing.T) string { 151 bin := filepath.Join(t.TempDir(), "deadcode") 152 if runtime.GOOS == "windows" { 153 bin += ".exe" 154 } 155 cmd := exec.Command("go", "build", "-o", bin) 156 if out, err := cmd.CombinedOutput(); err != nil { 157 t.Fatalf("Building deadcode: %v\n%s", err, out) 158 } 159 return bin 160 } 161 162 // words breaks a string into words, respecting 163 // Go string quotations around words with spaces. 164 func words(s string) ([]string, error) { 165 var words []string 166 for s != "" { 167 s = strings.TrimSpace(s) 168 var word string 169 if s[0] == '"' || s[0] == '`' { 170 prefix, err := strconv.QuotedPrefix(s) 171 if err != nil { 172 return nil, err 173 } 174 s = s[len(prefix):] 175 word, _ = strconv.Unquote(prefix) 176 } else { 177 prefix, rest, _ := strings.Cut(s, " ") 178 s = rest 179 word = prefix 180 } 181 words = append(words, word) 182 } 183 return words, nil 184 }