github.com/inturn/pre-commit-gobuild@v1.0.12/internal/errchecker/errcheck_test.go (about) 1 package errchecker 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "path" 8 "regexp" 9 "runtime" 10 "strings" 11 "testing" 12 13 "golang.org/x/tools/go/packages" 14 ) 15 16 const testPackage = "github.com/inturn/pre-commit-gobuild/internal/errchecker/testdata" 17 18 var ( 19 uncheckedMarkers map[marker]bool 20 blankMarkers map[marker]bool 21 assertMarkers map[marker]bool 22 ) 23 24 type marker struct { 25 file string 26 line int 27 } 28 29 func newMarker(e UncheckedError) marker { 30 return marker{e.Pos.Filename, e.Pos.Line} 31 } 32 33 func (m marker) String() string { 34 return fmt.Sprintf("%s:%d", m.file, m.line) 35 } 36 37 func init() { 38 uncheckedMarkers = make(map[marker]bool) 39 blankMarkers = make(map[marker]bool) 40 assertMarkers = make(map[marker]bool) 41 42 cfg := &packages.Config{ 43 Mode: packages.LoadSyntax, 44 Tests: true, 45 } 46 pkgs, err := packages.Load(cfg, testPackage) 47 if err != nil { 48 panic("failed to import test package") 49 } 50 for _, pkg := range pkgs { 51 for _, file := range pkg.Syntax { 52 for _, comment := range file.Comments { 53 text := comment.Text() 54 pos := pkg.Fset.Position(comment.Pos()) 55 m := marker{pos.Filename, pos.Line} 56 switch text { 57 case "UNCHECKED\n": 58 uncheckedMarkers[m] = true 59 case "BLANK\n": 60 blankMarkers[m] = true 61 case "ASSERT\n": 62 assertMarkers[m] = true 63 } 64 } 65 } 66 } 67 } 68 69 type flags uint 70 71 const ( 72 CheckAsserts flags = 1 << iota 73 CheckBlank 74 ) 75 76 // TestUnchecked runs a test against the example files and ensures all unchecked errors are caught. 77 func TestUnchecked(t *testing.T) { 78 test(t, 0) 79 } 80 81 // TestBlank is like TestUnchecked but also ensures assignments to the blank identifier are caught. 82 func TestBlank(t *testing.T) { 83 test(t, CheckBlank) 84 } 85 86 func TestAll(t *testing.T) { 87 // TODO: CheckAsserts should work independently of CheckBlank 88 test(t, CheckAsserts|CheckBlank) 89 } 90 91 func TestWhitelist(t *testing.T) { 92 93 } 94 95 func TestIgnore(t *testing.T) { 96 const testVendorMain = ` 97 package main 98 99 import "github.com/testlog" 100 101 func main() { 102 // returns an error that is not checked 103 testlog.Info() 104 }` 105 const testLog = ` 106 package testlog 107 108 func Info() error { 109 return nil 110 }` 111 112 if strings.HasPrefix(runtime.Version(), "go1.5") && os.Getenv("GO15VENDOREXPERIMENT") != "1" { 113 // skip tests if running in go1.5 and vendoring is not enabled 114 t.SkipNow() 115 } 116 117 // copy testvendor directory into directory for test 118 tmpGopath, err := ioutil.TempDir("", "testvendor") 119 if err != nil { 120 t.Fatalf("unable to create testvendor directory: %v", err) 121 } 122 testVendorDir := path.Join(tmpGopath, "src", "github.com/testvendor") 123 if err := os.MkdirAll(testVendorDir, 0755); err != nil { 124 t.Fatalf("MkdirAll failed: %v", err) 125 } 126 defer func() { 127 os.RemoveAll(tmpGopath) 128 }() 129 130 if err := ioutil.WriteFile(path.Join(testVendorDir, "main.go"), []byte(testVendorMain), 0755); err != nil { 131 t.Fatalf("Failed to write testvendor main: %v", err) 132 } 133 if err := os.MkdirAll(path.Join(testVendorDir, "vendor/github.com/testlog"), 0755); err != nil { 134 t.Fatalf("MkdirAll failed: %v", err) 135 } 136 if err := ioutil.WriteFile(path.Join(testVendorDir, "vendor/github.com/testlog/testlog.go"), []byte(testLog), 0755); err != nil { 137 t.Fatalf("Failed to write testlog: %v", err) 138 } 139 140 cases := []struct { 141 ignore map[string]*regexp.Regexp 142 numExpectedErrs int 143 }{ 144 // basic case has one error 145 { 146 ignore: nil, 147 numExpectedErrs: 1, 148 }, 149 // ignoring vendored import works 150 { 151 ignore: map[string]*regexp.Regexp{ 152 path.Join("github.com/testvendor/vendor/github.com/testlog"): regexp.MustCompile("Info"), 153 }, 154 }, 155 // non-vendored path ignores vendored import 156 { 157 ignore: map[string]*regexp.Regexp{ 158 "github.com/testlog": regexp.MustCompile("Info"), 159 }, 160 }, 161 } 162 163 for i, currCase := range cases { 164 checker := NewChecker() 165 checker.Ignore = currCase.ignore 166 loadPackages = func(cfg *packages.Config, paths ...string) ([]*packages.Package, error) { 167 cfg.Env = append(os.Environ(), "GOPATH="+tmpGopath) 168 cfg.Dir = testVendorDir 169 pkgs, err := packages.Load(cfg, paths...) 170 return pkgs, err 171 } 172 err := checker.CheckPackages("github.com/testvendor") 173 174 if currCase.numExpectedErrs == 0 { 175 if err != nil { 176 t.Errorf("Case %d: expected no errors, but got: %v", i, err) 177 } 178 continue 179 } 180 181 uerr, ok := err.(*UncheckedErrors) 182 if !ok { 183 t.Errorf("Case %d: wrong error type returned: %v", i, err) 184 continue 185 } 186 187 if currCase.numExpectedErrs != len(uerr.Errors) { 188 t.Errorf("Case %d:\nExpected: %d errors\nActual: %d errors", i, currCase.numExpectedErrs, len(uerr.Errors)) 189 } 190 } 191 } 192 193 func TestWithoutGeneratedCode(t *testing.T) { 194 const testVendorMain = ` 195 // Code generated by protoc-gen-go. DO NOT EDIT. 196 package main 197 198 import "github.com/testlog" 199 200 func main() { 201 // returns an error that is not checked 202 testlog.Info() 203 }` 204 const testLog = ` 205 package testlog 206 207 func Info() error { 208 return nil 209 }` 210 211 if strings.HasPrefix(runtime.Version(), "go1.5") && os.Getenv("GO15VENDOREXPERIMENT") != "1" { 212 // skip tests if running in go1.5 and vendoring is not enabled 213 t.SkipNow() 214 } 215 216 // copy testvendor directory into directory for test 217 tmpGopath, err := ioutil.TempDir("", "testvendor") 218 if err != nil { 219 t.Fatalf("unable to create testvendor directory: %v", err) 220 } 221 testVendorDir := path.Join(tmpGopath, "src", "github.com/testvendor") 222 if err := os.MkdirAll(testVendorDir, 0755); err != nil { 223 t.Fatalf("MkdirAll failed: %v", err) 224 } 225 defer func() { 226 os.RemoveAll(tmpGopath) 227 }() 228 229 if err := ioutil.WriteFile(path.Join(testVendorDir, "main.go"), []byte(testVendorMain), 0755); err != nil { 230 t.Fatalf("Failed to write testvendor main: %v", err) 231 } 232 if err := os.MkdirAll(path.Join(testVendorDir, "vendor/github.com/testlog"), 0755); err != nil { 233 t.Fatalf("MkdirAll failed: %v", err) 234 } 235 if err := ioutil.WriteFile(path.Join(testVendorDir, "vendor/github.com/testlog/testlog.go"), []byte(testLog), 0755); err != nil { 236 t.Fatalf("Failed to write testlog: %v", err) 237 } 238 239 cases := []struct { 240 withoutGeneratedCode bool 241 numExpectedErrs int 242 }{ 243 // basic case has one error 244 { 245 withoutGeneratedCode: false, 246 numExpectedErrs: 1, 247 }, 248 // ignoring generated code works 249 { 250 withoutGeneratedCode: true, 251 numExpectedErrs: 0, 252 }, 253 } 254 255 for i, currCase := range cases { 256 checker := NewChecker() 257 checker.WithoutGeneratedCode = currCase.withoutGeneratedCode 258 loadPackages = func(cfg *packages.Config, paths ...string) ([]*packages.Package, error) { 259 cfg.Env = append(os.Environ(), "GOPATH="+tmpGopath) 260 cfg.Dir = testVendorDir 261 pkgs, err := packages.Load(cfg, paths...) 262 return pkgs, err 263 } 264 err := checker.CheckPackages(path.Join("github.com/testvendor")) 265 266 if currCase.numExpectedErrs == 0 { 267 if err != nil { 268 t.Errorf("Case %d: expected no errors, but got: %v", i, err) 269 } 270 continue 271 } 272 273 uerr, ok := err.(*UncheckedErrors) 274 if !ok { 275 t.Errorf("Case %d: wrong error type returned: %v", i, err) 276 continue 277 } 278 279 if currCase.numExpectedErrs != len(uerr.Errors) { 280 t.Errorf("Case %d:\nExpected: %d errors\nActual: %d errors", i, currCase.numExpectedErrs, len(uerr.Errors)) 281 } 282 } 283 } 284 285 func test(t *testing.T, f flags) { 286 var ( 287 asserts bool = f&CheckAsserts != 0 288 blank bool = f&CheckBlank != 0 289 ) 290 checker := NewChecker() 291 checker.Asserts = asserts 292 checker.Blank = blank 293 checker.SetExclude(map[string]bool{ 294 fmt.Sprintf("(%s.ErrorMakerInterface).MakeNilError", testPackage): true, 295 }) 296 err := checker.CheckPackages(testPackage) 297 uerr, ok := err.(*UncheckedErrors) 298 if !ok { 299 t.Fatalf("wrong error type returned: %v", err) 300 } 301 302 numErrors := len(uncheckedMarkers) 303 if blank { 304 numErrors += len(blankMarkers) 305 } 306 if asserts { 307 numErrors += len(assertMarkers) 308 } 309 310 if len(uerr.Errors) != numErrors { 311 t.Errorf("got %d errors, want %d", len(uerr.Errors), numErrors) 312 unchecked_loop: 313 for k := range uncheckedMarkers { 314 for _, e := range uerr.Errors { 315 if newMarker(e) == k { 316 continue unchecked_loop 317 } 318 } 319 t.Errorf("Expected unchecked at %s", k) 320 } 321 if blank { 322 blank_loop: 323 for k := range blankMarkers { 324 for _, e := range uerr.Errors { 325 if newMarker(e) == k { 326 continue blank_loop 327 } 328 } 329 t.Errorf("Expected blank at %s", k) 330 } 331 } 332 if asserts { 333 assert_loop: 334 for k := range assertMarkers { 335 for _, e := range uerr.Errors { 336 if newMarker(e) == k { 337 continue assert_loop 338 } 339 } 340 t.Errorf("Expected assert at %s", k) 341 } 342 } 343 } 344 345 for i, err := range uerr.Errors { 346 m := marker{err.Pos.Filename, err.Pos.Line} 347 if !uncheckedMarkers[m] && !blankMarkers[m] && !assertMarkers[m] { 348 t.Errorf("%d: unexpected error: %v", i, err) 349 } 350 } 351 }