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  }