github.com/stackb/rules_proto@v0.0.0-20240221195024-5428336c51f1/pkg/goldentest/cases.go (about) 1 package goldentest 2 3 /* Copyright 2020 The Bazel Authors. All rights reserved. 4 5 Licensed under the Apache License, Version 2.0 (the "License"); 6 you may not use this file except in compliance with the License. 7 You may obtain a copy of the License at 8 9 http://www.apache.org/licenses/LICENSE-2.0 10 11 Unless required by applicable law or agreed to in writing, software 12 distributed under the License is distributed on an "AS IS" BASIS, 13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 See the License for the specific language governing permissions and 15 limitations under the License. 16 */ 17 18 import ( 19 "bufio" 20 "bytes" 21 "io" 22 "io/ioutil" 23 "log" 24 "os" 25 "os/exec" 26 "path" 27 "path/filepath" 28 "strings" 29 "testing" 30 31 "github.com/bazelbuild/bazel-gazelle/testtools" 32 "github.com/bazelbuild/rules_go/go/tools/bazel" 33 ) 34 35 var doCleanup = true 36 37 // GoldenTests is a helper for running glob(["testdata/**"]) style test setups. 38 type GoldenTests struct { 39 extensionDir string 40 testDataPath string 41 extraArgs []string 42 dataFiles []bazel.RunfileEntry 43 onlyTests []string 44 } 45 46 type GoldenTestOption func(*GoldenTests) 47 48 func WithExtraArgs(args ...string) GoldenTestOption { 49 return func(g *GoldenTests) { 50 g.extraArgs = args 51 } 52 } 53 54 func WithDataFiles(files ...bazel.RunfileEntry) GoldenTestOption { 55 return func(g *GoldenTests) { 56 g.dataFiles = files 57 } 58 } 59 60 func WithOnlyTests(tests ...string) GoldenTestOption { 61 return func(g *GoldenTests) { 62 g.onlyTests = tests 63 } 64 } 65 66 // FromDir construct a GoldenTests tester that searches the given directory. 67 func FromDir(extensionDir string, options ...GoldenTestOption) *GoldenTests { 68 test := &GoldenTests{ 69 extensionDir: extensionDir, 70 testDataPath: path.Join(extensionDir, "testdata") + "/", 71 } 72 for _, opt := range options { 73 opt(test) 74 } 75 return test 76 } 77 78 func (g *GoldenTests) Run(t *testing.T, gazelleName string) { 79 t.Log("Run", g.extensionDir) 80 // listFiles(".") 81 82 gazellePath, ok := bazel.FindBinary(g.extensionDir, gazelleName) 83 if !ok { 84 t.Fatalf("could not find gazelle: %q in %s", gazelleName, g.extensionDir) 85 } 86 // t.Log("Found gazelle binary:", gazellePath) 87 88 tests := map[string][]bazel.RunfileEntry{} 89 90 files, err := bazel.ListRunfiles() 91 if err != nil { 92 t.Fatalf("bazel.ListRunfiles() error: %v", err) 93 } 94 95 for _, f := range files { 96 if strings.HasPrefix(f.ShortPath, g.testDataPath) { 97 relativePath := strings.TrimPrefix(f.ShortPath, g.testDataPath) 98 parts := strings.SplitN(relativePath, "/", 2) 99 if len(parts) < 2 { 100 // This file is not a part of a testcase since it must be in a dir that 101 // is the test case and then have a path inside of that. 102 continue 103 } 104 105 tests[parts[0]] = append(tests[parts[0]], f) 106 } 107 } 108 if len(tests) == 0 { 109 t.Fatal("no tests found") 110 } 111 112 for testName, files := range tests { 113 shouldTest := true 114 if len(g.onlyTests) > 0 { 115 shouldTest = false 116 for _, name := range g.onlyTests { 117 if name == testName { 118 shouldTest = true 119 break 120 } 121 } 122 } 123 if shouldTest { 124 g.testPath(t, gazellePath, testName, files) 125 } else { 126 log.Println("skipped test:", testName) 127 } 128 } 129 } 130 131 func (g *GoldenTests) testPath(t *testing.T, gazellePath, name string, files []bazel.RunfileEntry) { 132 t.Run(name, func(t *testing.T) { 133 var inputs []testtools.FileSpec 134 var goldens []testtools.FileSpec 135 extraArgs := g.extraArgs 136 137 for _, f := range files { 138 path := f.Path 139 trim := g.testDataPath + name + "/" 140 shortPath := strings.TrimPrefix(f.ShortPath, trim) 141 info, err := os.Stat(path) 142 if err != nil { 143 t.Fatalf("os.Stat(%q) error: %v", path, err) 144 } 145 146 // Skip dirs. 147 if info.IsDir() { 148 continue 149 } 150 151 content, err := ioutil.ReadFile(path) 152 if err != nil { 153 t.Errorf("ioutil.ReadFile(%q) error: %v", path, err) 154 } 155 156 if shortPath == ".gazelle.args" { 157 extraArgs = append(extraArgs, parseArgsFile(bytes.NewReader(content))...) 158 continue 159 } 160 161 // Now trim the common prefix off. 162 if strings.HasSuffix(shortPath, ".in") { 163 inputs = append(inputs, testtools.FileSpec{ 164 Path: strings.TrimSuffix(shortPath, ".in"), 165 Content: string(content), 166 }) 167 } else if strings.HasSuffix(shortPath, ".out") { 168 goldens = append(goldens, testtools.FileSpec{ 169 Path: strings.TrimSuffix(shortPath, ".out"), 170 Content: string(content), 171 }) 172 } else { 173 inputs = append(inputs, testtools.FileSpec{ 174 Path: shortPath, 175 Content: string(content), 176 }) 177 goldens = append(goldens, testtools.FileSpec{ 178 Path: shortPath, 179 Content: string(content), 180 }) 181 } 182 } 183 184 dir, cleanup := testtools.CreateFiles(t, inputs) 185 if doCleanup { 186 defer cleanup() 187 } 188 189 for _, f := range g.dataFiles { 190 newName := filepath.Join(dir, f.ShortPath) 191 newDir := filepath.Dir(newName) 192 if err := os.MkdirAll(newDir, os.ModePerm); err != nil { 193 t.Fatal("data file symlink setup error:", f, err) 194 } 195 if err := os.Symlink(f.Path, newName); err != nil { 196 t.Fatal("data file symlink setup error:", f, err) 197 } 198 } 199 200 t.Log("running test dir:", dir) 201 args := append([]string{"-build_file_name=BUILD"}, extraArgs...) 202 cmd := exec.Command(gazellePath, args...) 203 cmd.Stdout = os.Stdout 204 cmd.Stderr = os.Stderr 205 cmd.Dir = dir 206 if err := cmd.Run(); err != nil { 207 t.Fatal("gazelle command failed!", err) 208 } 209 210 t.Log("checking files:", dir) 211 212 testtools.CheckFiles(t, dir, goldens) 213 214 if t.Failed() { 215 filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { 216 if err != nil { 217 return err 218 } 219 t.Log("file:", path) 220 return nil 221 }) 222 } 223 }) 224 } 225 226 // listFiles - convenience debugging function to log the files under a given dir 227 func listFiles(dir string) error { 228 return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { 229 if err != nil { 230 log.Printf("%v\n", err) 231 return err 232 } 233 if info.Mode()&os.ModeSymlink > 0 { 234 link, err := os.Readlink(path) 235 if err != nil { 236 return err 237 } 238 log.Printf("%s -> %s", path, link) 239 return nil 240 } 241 242 log.Println(path) 243 return nil 244 }) 245 } 246 247 func parseArgsFile(in io.Reader) []string { 248 args := make([]string, 0) 249 scanner := bufio.NewScanner(in) 250 for scanner.Scan() { 251 line := scanner.Text() 252 if strings.HasPrefix(line, "#") { 253 continue 254 } 255 args = append(args, line) 256 } 257 return args 258 }