github.com/sirkon/goproxy@v1.4.8/internal/modfile/read_test.go (about) 1 // Copyright 2018 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 modfile 6 7 import ( 8 "bytes" 9 "fmt" 10 "io/ioutil" 11 "os" 12 "os/exec" 13 "path/filepath" 14 "reflect" 15 "strings" 16 "testing" 17 ) 18 19 // exists reports whether the named file exists. 20 func exists(name string) bool { 21 _, err := os.Stat(name) 22 return err == nil 23 } 24 25 // Test that reading and then writing the golden files 26 // does not change their output. 27 func TestPrintGolden(t *testing.T) { 28 outs, err := filepath.Glob("testdata/*.golden") 29 if err != nil { 30 t.Fatal(err) 31 } 32 for _, out := range outs { 33 testPrint(t, out, out) 34 } 35 } 36 37 // testPrint is a helper for testing the printer. 38 // It reads the file named in, reformats it, and compares 39 // the result to the file named out. 40 func testPrint(t *testing.T, in, out string) { 41 data, err := ioutil.ReadFile(in) 42 if err != nil { 43 t.Error(err) 44 return 45 } 46 47 golden, err := ioutil.ReadFile(out) 48 if err != nil { 49 t.Error(err) 50 return 51 } 52 53 base := "testdata/" + filepath.Base(in) 54 f, err := parse(in, data) 55 if err != nil { 56 t.Error(err) 57 return 58 } 59 60 ndata := Format(f) 61 62 if !bytes.Equal(ndata, golden) { 63 t.Errorf("formatted %s incorrectly: diff shows -golden, +ours", base) 64 tdiff(t, string(golden), string(ndata)) 65 return 66 } 67 } 68 69 func TestParseLax(t *testing.T) { 70 badFile := []byte(`module m 71 surprise attack 72 x y ( 73 z 74 ) 75 exclude v1.2.3 76 replace <-!!! 77 `) 78 _, err := ParseLax("file", badFile, nil) 79 if err != nil { 80 t.Fatalf("ParseLax did not ignore irrelevant errors: %v", err) 81 } 82 } 83 84 // Test that when files in the testdata directory are parsed 85 // and printed and parsed again, we get the same parse tree 86 // both times. 87 func TestPrintParse(t *testing.T) { 88 outs, err := filepath.Glob("testdata/*") 89 if err != nil { 90 t.Fatal(err) 91 } 92 for _, out := range outs { 93 data, err := ioutil.ReadFile(out) 94 if err != nil { 95 t.Error(err) 96 continue 97 } 98 99 base := "testdata/" + filepath.Base(out) 100 f, err := parse(base, data) 101 if err != nil { 102 t.Errorf("parsing original: %v", err) 103 continue 104 } 105 106 ndata := Format(f) 107 f2, err := parse(base, ndata) 108 if err != nil { 109 t.Errorf("parsing reformatted: %v", err) 110 continue 111 } 112 113 eq := eqchecker{file: base} 114 if err := eq.check(f, f2); err != nil { 115 t.Errorf("not equal (parse/Format/parse): %v", err) 116 } 117 118 pf1, err := Parse(base, data, nil) 119 if err != nil { 120 switch base { 121 case "testdata/replace2.in", "testdata/gopkg.in.golden": 122 t.Errorf("should parse %v: %v", base, err) 123 } 124 } 125 if err == nil { 126 pf2, err := Parse(base, ndata, nil) 127 if err != nil { 128 t.Errorf("Parsing reformatted: %v", err) 129 continue 130 } 131 eq := eqchecker{file: base} 132 if err := eq.check(pf1, pf2); err != nil { 133 t.Errorf("not equal (parse/Format/Parse): %v", err) 134 } 135 136 ndata2, err := pf1.Format() 137 if err != nil { 138 t.Errorf("reformat: %v", err) 139 } 140 pf3, err := Parse(base, ndata2, nil) 141 if err != nil { 142 t.Errorf("Parsing reformatted2: %v", err) 143 continue 144 } 145 eq = eqchecker{file: base} 146 if err := eq.check(pf1, pf3); err != nil { 147 t.Errorf("not equal (Parse/Format/Parse): %v", err) 148 } 149 ndata = ndata2 150 } 151 152 if strings.HasSuffix(out, ".in") { 153 golden, err := ioutil.ReadFile(strings.TrimSuffix(out, ".in") + ".golden") 154 if err != nil { 155 t.Error(err) 156 continue 157 } 158 if !bytes.Equal(ndata, golden) { 159 t.Errorf("formatted %s incorrectly: diff shows -golden, +ours", base) 160 tdiff(t, string(golden), string(ndata)) 161 return 162 } 163 } 164 } 165 } 166 167 // An eqchecker holds state for checking the equality of two parse trees. 168 type eqchecker struct { 169 file string 170 pos Position 171 } 172 173 // errorf returns an error described by the printf-style format and arguments, 174 // inserting the current file position before the error text. 175 func (eq *eqchecker) errorf(format string, args ...interface{}) error { 176 return fmt.Errorf("%s:%d: %s", eq.file, eq.pos.Line, 177 fmt.Sprintf(format, args...)) 178 } 179 180 // check checks that v and w represent the same parse tree. 181 // If not, it returns an error describing the first difference. 182 func (eq *eqchecker) check(v, w interface{}) error { 183 return eq.checkValue(reflect.ValueOf(v), reflect.ValueOf(w)) 184 } 185 186 var ( 187 posType = reflect.TypeOf(Position{}) 188 commentsType = reflect.TypeOf(Comments{}) 189 ) 190 191 // checkValue checks that v and w represent the same parse tree. 192 // If not, it returns an error describing the first difference. 193 func (eq *eqchecker) checkValue(v, w reflect.Value) error { 194 // inner returns the innermost expression for v. 195 // if v is a non-nil interface value, it returns the concrete 196 // value in the interface. 197 inner := func(v reflect.Value) reflect.Value { 198 for { 199 if v.Kind() == reflect.Interface && !v.IsNil() { 200 v = v.Elem() 201 continue 202 } 203 break 204 } 205 return v 206 } 207 208 v = inner(v) 209 w = inner(w) 210 if v.Kind() == reflect.Invalid && w.Kind() == reflect.Invalid { 211 return nil 212 } 213 if v.Kind() == reflect.Invalid { 214 return eq.errorf("nil interface became %s", w.Type()) 215 } 216 if w.Kind() == reflect.Invalid { 217 return eq.errorf("%s became nil interface", v.Type()) 218 } 219 220 if v.Type() != w.Type() { 221 return eq.errorf("%s became %s", v.Type(), w.Type()) 222 } 223 224 if p, ok := v.Interface().(Expr); ok { 225 eq.pos, _ = p.Span() 226 } 227 228 switch v.Kind() { 229 default: 230 return eq.errorf("unexpected type %s", v.Type()) 231 232 case reflect.Bool, reflect.Int, reflect.String: 233 vi := v.Interface() 234 wi := w.Interface() 235 if vi != wi { 236 return eq.errorf("%v became %v", vi, wi) 237 } 238 239 case reflect.Slice: 240 vl := v.Len() 241 wl := w.Len() 242 for i := 0; i < vl || i < wl; i++ { 243 if i >= vl { 244 return eq.errorf("unexpected %s", w.Index(i).Type()) 245 } 246 if i >= wl { 247 return eq.errorf("missing %s", v.Index(i).Type()) 248 } 249 if err := eq.checkValue(v.Index(i), w.Index(i)); err != nil { 250 return err 251 } 252 } 253 254 case reflect.Struct: 255 // Fields in struct must match. 256 t := v.Type() 257 n := t.NumField() 258 for i := 0; i < n; i++ { 259 tf := t.Field(i) 260 switch { 261 default: 262 if err := eq.checkValue(v.Field(i), w.Field(i)); err != nil { 263 return err 264 } 265 266 case tf.Type == posType: // ignore positions 267 case tf.Type == commentsType: // ignore comment assignment 268 } 269 } 270 271 case reflect.Ptr, reflect.Interface: 272 if v.IsNil() != w.IsNil() { 273 if v.IsNil() { 274 return eq.errorf("unexpected %s", w.Elem().Type()) 275 } 276 return eq.errorf("missing %s", v.Elem().Type()) 277 } 278 if err := eq.checkValue(v.Elem(), w.Elem()); err != nil { 279 return err 280 } 281 } 282 return nil 283 } 284 285 // diff returns the output of running diff on b1 and b2. 286 func diff(b1, b2 []byte) (data []byte, err error) { 287 f1, err := ioutil.TempFile("", "testdiff") 288 if err != nil { 289 return nil, err 290 } 291 defer os.Remove(f1.Name()) 292 defer f1.Close() 293 294 f2, err := ioutil.TempFile("", "testdiff") 295 if err != nil { 296 return nil, err 297 } 298 defer os.Remove(f2.Name()) 299 defer f2.Close() 300 301 f1.Write(b1) 302 f2.Write(b2) 303 304 data, err = exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput() 305 if len(data) > 0 { 306 // diff exits with a non-zero status when the files don't match. 307 // Ignore that failure as long as we get output. 308 err = nil 309 } 310 return 311 } 312 313 // tdiff logs the diff output to t.Error. 314 func tdiff(t *testing.T, a, b string) { 315 data, err := diff([]byte(a), []byte(b)) 316 if err != nil { 317 t.Error(err) 318 return 319 } 320 t.Error(string(data)) 321 } 322 323 var modulePathTests = []struct { 324 input []byte 325 expected string 326 }{ 327 {input: []byte("module \"github.com/rsc/vgotest\""), expected: "github.com/rsc/vgotest"}, 328 {input: []byte("module github.com/rsc/vgotest"), expected: "github.com/rsc/vgotest"}, 329 {input: []byte("module \"github.com/rsc/vgotest\""), expected: "github.com/rsc/vgotest"}, 330 {input: []byte("module github.com/rsc/vgotest"), expected: "github.com/rsc/vgotest"}, 331 {input: []byte("module `github.com/rsc/vgotest`"), expected: "github.com/rsc/vgotest"}, 332 {input: []byte("module \"github.com/rsc/vgotest/v2\""), expected: "github.com/rsc/vgotest/v2"}, 333 {input: []byte("module github.com/rsc/vgotest/v2"), expected: "github.com/rsc/vgotest/v2"}, 334 {input: []byte("module \"gopkg.in/yaml.v2\""), expected: "gopkg.in/yaml.v2"}, 335 {input: []byte("module gopkg.in/yaml.v2"), expected: "gopkg.in/yaml.v2"}, 336 {input: []byte("module \"gopkg.in/check.v1\"\n"), expected: "gopkg.in/check.v1"}, 337 {input: []byte("module \"gopkg.in/check.v1\n\""), expected: ""}, 338 {input: []byte("module gopkg.in/check.v1\n"), expected: "gopkg.in/check.v1"}, 339 {input: []byte("module \"gopkg.in/check.v1\"\r\n"), expected: "gopkg.in/check.v1"}, 340 {input: []byte("module gopkg.in/check.v1\r\n"), expected: "gopkg.in/check.v1"}, 341 {input: []byte("module \"gopkg.in/check.v1\"\n\n"), expected: "gopkg.in/check.v1"}, 342 {input: []byte("module gopkg.in/check.v1\n\n"), expected: "gopkg.in/check.v1"}, 343 {input: []byte("module \n\"gopkg.in/check.v1\"\n\n"), expected: ""}, 344 {input: []byte("module \ngopkg.in/check.v1\n\n"), expected: ""}, 345 {input: []byte("module \"gopkg.in/check.v1\"asd"), expected: ""}, 346 {input: []byte("module \n\"gopkg.in/check.v1\"\n\n"), expected: ""}, 347 {input: []byte("module \ngopkg.in/check.v1\n\n"), expected: ""}, 348 {input: []byte("module \"gopkg.in/check.v1\"asd"), expected: ""}, 349 {input: []byte("module \nmodule a/b/c "), expected: "a/b/c"}, 350 {input: []byte("module \" \""), expected: " "}, 351 {input: []byte("module "), expected: ""}, 352 {input: []byte("module \" a/b/c \""), expected: " a/b/c "}, 353 {input: []byte("module \"github.com/rsc/vgotest1\" // with a comment"), expected: "github.com/rsc/vgotest1"}, 354 } 355 356 func TestModulePath(t *testing.T) { 357 for _, test := range modulePathTests { 358 t.Run(string(test.input), func(t *testing.T) { 359 result := ModulePath(test.input) 360 if result != test.expected { 361 t.Fatalf("ModulePath(%q): %s, want %s", string(test.input), result, test.expected) 362 } 363 }) 364 } 365 }