github.com/lixvbnet/courtney@v0.0.0-20221025031132-0dcb02231211/tester/tester_test.go (about)

     1  package tester_test
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"path"
     8  	"path/filepath"
     9  	"reflect"
    10  	"regexp"
    11  	"strconv"
    12  	"strings"
    13  	"testing"
    14  
    15  	"github.com/lixvbnet/courtney/shared"
    16  	"github.com/lixvbnet/courtney/tester"
    17  	"github.com/dave/patsy"
    18  	"github.com/dave/patsy/builder"
    19  	"github.com/dave/patsy/vos"
    20  	"golang.org/x/tools/cover"
    21  )
    22  
    23  func TestTester_ProcessExcludes(t *testing.T) {
    24  	for _, gomod := range []bool{true, false} {
    25  		t.Run(fmt.Sprintf("gomod=%v", gomod), func(t *testing.T) {
    26  			env := vos.Mock()
    27  			b, err := builder.New(env, "ns", gomod)
    28  			if err != nil {
    29  				t.Fatalf("Error creating builder in %s", err)
    30  			}
    31  			defer b.Cleanup()
    32  
    33  			_, pdir, err := b.Package("a", map[string]string{
    34  				"a.go": `package a`,
    35  			})
    36  			if err != nil {
    37  				t.Fatalf("Error creating temp package: %s", err)
    38  			}
    39  
    40  			setup := &shared.Setup{
    41  				Env:   env,
    42  				Paths: patsy.NewCache(env),
    43  			}
    44  			ts := tester.New(setup)
    45  			ts.Results = []*cover.Profile{
    46  				{
    47  					FileName: "ns/a/a.go",
    48  					Blocks: []cover.ProfileBlock{
    49  						{Count: 1, StartLine: 1, EndLine: 10},
    50  						{Count: 0, StartLine: 11, EndLine: 20},
    51  						{Count: 1, StartLine: 21, EndLine: 30},
    52  						{Count: 0, StartLine: 31, EndLine: 40},
    53  					},
    54  				},
    55  			}
    56  			excludes := map[string]map[int]bool{
    57  				filepath.Join(pdir, "a.go"): {
    58  					25: true,
    59  					35: true,
    60  				},
    61  			}
    62  			expected := []cover.ProfileBlock{
    63  				{Count: 1, StartLine: 1, EndLine: 10},
    64  				{Count: 0, StartLine: 11, EndLine: 20},
    65  				{Count: 1, StartLine: 21, EndLine: 30},
    66  			}
    67  			if err := ts.ProcessExcludes(excludes); err != nil {
    68  				t.Fatalf("Processing excludes: %s", err)
    69  			}
    70  			if !reflect.DeepEqual(ts.Results[0].Blocks, expected) {
    71  				t.Fatalf("Processing excludes - got:\n%#v\nexpected:\n%#v\n", ts.Results[0].Blocks, expected)
    72  			}
    73  		})
    74  	}
    75  }
    76  
    77  func TestTester_Enforce(t *testing.T) {
    78  	for _, gomod := range []bool{true, false} {
    79  		t.Run(fmt.Sprintf("gomod=%v", gomod), func(t *testing.T) {
    80  			env := vos.Mock()
    81  			setup := &shared.Setup{
    82  				Env:     env,
    83  				Paths:   patsy.NewCache(env),
    84  				Enforce: true,
    85  			}
    86  			b, err := builder.New(env, "ns", gomod)
    87  			if err != nil {
    88  				t.Fatalf("Error creating builder: %s", err)
    89  			}
    90  			defer b.Cleanup()
    91  
    92  			_, _, _ = b.Package("a", map[string]string{
    93  				"a.go": "package a\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20",
    94  			})
    95  
    96  			ts := tester.New(setup)
    97  			ts.Results = []*cover.Profile{
    98  				{
    99  					FileName: "ns/a/a.go",
   100  					Mode:     "b",
   101  					Blocks: []cover.ProfileBlock{
   102  						{Count: 1},
   103  					},
   104  				},
   105  			}
   106  			if err := ts.Enforce(); err != nil {
   107  				t.Fatalf("Error enforcing: %s", err)
   108  			}
   109  
   110  			ts.Results[0].Blocks = []cover.ProfileBlock{
   111  				{Count: 1, StartLine: 1, EndLine: 2},
   112  				{Count: 0, StartLine: 6, EndLine: 11},
   113  			}
   114  			err = ts.Enforce()
   115  			if err == nil {
   116  				t.Fatal("Error enforcing - should get error, got nil")
   117  			}
   118  			expected := "Error - untested code:\nns/a/a.go:6-11:\n\t5\n\t6\n\t7\n\t8\n\t9\n\t10"
   119  			if err.Error() != expected {
   120  				t.Fatalf("Error enforcing - got \n%s\nexpected:\n%s\n", strconv.Quote(err.Error()), strconv.Quote(expected))
   121  			}
   122  
   123  			// check that blocks next to each other are merged
   124  			ts.Results[0].Blocks = []cover.ProfileBlock{
   125  				{Count: 1, StartLine: 1, EndLine: 2},
   126  				{Count: 0, StartLine: 6, EndLine: 11},
   127  				{Count: 0, StartLine: 12, EndLine: 16},
   128  				{Count: 0, StartLine: 18, EndLine: 21},
   129  			}
   130  			err = ts.Enforce()
   131  			if err == nil {
   132  				t.Fatal("Error enforcing - should get error, got nil")
   133  			}
   134  			expected = "Error - untested code:\nns/a/a.go:6-16:\n\t5\n\t6\n\t7\n\t8\n\t9\n\t10\n\t11\n\t12\n\t13\n\t14\n\t15ns/a/a.go:18-21:\n\t17\n\t18\n\t19\n\t20"
   135  			if err.Error() != expected {
   136  				t.Fatalf("Error enforcing - got \n%s\nexpected:\n%s\n", strconv.Quote(err.Error()), strconv.Quote(expected))
   137  			}
   138  		})
   139  	}
   140  }
   141  
   142  func TestTester_Save_output(t *testing.T) {
   143  	env := vos.Mock()
   144  	dir, err := ioutil.TempDir("", "")
   145  	if err != nil {
   146  		t.Fatalf("Error creating temp dir: %s", err)
   147  	}
   148  	out := filepath.Join(dir, "foo.bar")
   149  	setup := &shared.Setup{
   150  		Env:    env,
   151  		Paths:  patsy.NewCache(env),
   152  		Output: out,
   153  	}
   154  	ts := tester.New(setup)
   155  	ts.Results = []*cover.Profile{
   156  		{
   157  			FileName: "a",
   158  			Mode:     "b",
   159  			Blocks:   []cover.ProfileBlock{{}},
   160  		},
   161  	}
   162  	if err := ts.Save(); err != nil {
   163  		t.Fatalf("Error saving: %s", err)
   164  	}
   165  	if _, err := ioutil.ReadFile(out); err != nil {
   166  		t.Fatalf("Error loading coverage: %s", err)
   167  	}
   168  }
   169  
   170  func TestTester_Save_no_results(t *testing.T) {
   171  	env := vos.Mock()
   172  	sout := &bytes.Buffer{}
   173  	serr := &bytes.Buffer{}
   174  	env.Setstdout(sout)
   175  	env.Setstderr(serr)
   176  	setup := &shared.Setup{
   177  		Env:   env,
   178  		Paths: patsy.NewCache(env),
   179  	}
   180  	ts := tester.New(setup)
   181  	if err := ts.Save(); err != nil {
   182  		t.Fatalf("Error saving: %s", err)
   183  	}
   184  	expected := "No results\n"
   185  	if sout.String() != expected {
   186  		t.Fatalf("Error saving, stdout: got:\n%s\nexpected:\n%s\n", sout.String(), expected)
   187  	}
   188  }
   189  
   190  func TestTester_Test(t *testing.T) {
   191  
   192  	type args []string
   193  	type files map[string]string
   194  	type packages map[string]files
   195  	type test struct {
   196  		args     args
   197  		packages packages
   198  	}
   199  
   200  	tests := map[string]test{
   201  		"simple": {
   202  			args: args{"ns/..."},
   203  			packages: packages{
   204  				"a": files{
   205  					"a.go": `package a
   206  						func Foo(i int) int {
   207  							i++ // 0
   208  							return i
   209  						}
   210  					`,
   211  					"a_test.go": `package a`,
   212  				},
   213  			},
   214  		},
   215  		"simple test": {
   216  			args: args{"ns/..."},
   217  			packages: packages{
   218  				"a": files{
   219  					"a.go": `package a
   220  					
   221  						func Foo(i int) int {
   222  							i++ // 1
   223  							return i
   224  						}
   225  						
   226  						func Bar(i int) int {
   227  							i++ // 0
   228  							return i
   229  						}
   230  					`,
   231  					"a_test.go": `package a
   232  					
   233  					import "testing"
   234  					
   235  					func TestFoo(t *testing.T) {
   236  						i := Foo(1)
   237  						if i != 2 {
   238  							t.Fail()
   239  						}
   240  					}
   241  					`,
   242  				},
   243  			},
   244  		},
   245  		"cross package test": {
   246  			args: args{"ns/a", "ns/b"},
   247  			packages: packages{
   248  				"a": files{
   249  					"a.go": `package a
   250  					
   251  						func Foo(i int) int {
   252  							i++ // 1
   253  							return i
   254  						}
   255  						
   256  						func Bar(i int) int {
   257  							i++ // 1
   258  							return i
   259  						}
   260  					`,
   261  					"a_test.go": `package a
   262  					
   263  					import "testing"
   264  					
   265  					func TestFoo(t *testing.T) {
   266  						i := Foo(1)
   267  						if i != 2 {
   268  							t.Fail()
   269  						}
   270  					}
   271  					`,
   272  				},
   273  				"b": files{
   274  					"b_exclude.go": `package b`,
   275  					"b_test.go": `package b
   276  						
   277  						import (
   278  							"testing"
   279  							"ns/a"
   280  						)
   281  						
   282  						func TestBar(t *testing.T) {
   283  							i := a.Bar(1)
   284  							if i != 2 {
   285  								t.Fail()
   286  							}
   287  						}
   288  					`,
   289  				},
   290  			},
   291  		},
   292  	}
   293  
   294  	for name, test := range tests {
   295  		for _, gomod := range []bool{true, false} {
   296  			t.Run(fmt.Sprintf("%s,gomod=%v", name, gomod), func(t *testing.T) {
   297  				env := vos.Mock()
   298  				b, err := builder.New(env, "ns", gomod)
   299  				if err != nil {
   300  					t.Fatalf("Error creating builder in %s: %+v", name, err)
   301  				}
   302  				defer b.Cleanup()
   303  
   304  				for pname, files := range test.packages {
   305  					if _, _, err := b.Package(pname, files); err != nil {
   306  						t.Fatalf("Error creating package %s in %s: %+v", pname, name, err)
   307  					}
   308  				}
   309  
   310  				paths := patsy.NewCache(env)
   311  
   312  				setup := &shared.Setup{
   313  					Env:   env,
   314  					Paths: paths,
   315  				}
   316  				if err := setup.Parse(test.args); err != nil {
   317  					t.Fatalf("Error in '%s' parsing args: %+v", name, err)
   318  				}
   319  
   320  				ts := tester.New(setup)
   321  
   322  				if err := ts.Test(); err != nil {
   323  					t.Fatalf("Error in '%s' while running test: %+v", name, err)
   324  				}
   325  
   326  				fmt.Printf("Results: %#v\n", ts.Results)
   327  
   328  				filesInOutput := map[string]bool{}
   329  				for _, p := range ts.Results {
   330  
   331  					filesInOutput[p.FileName] = true
   332  					pkg, fname := path.Split(p.FileName)
   333  					pkg = strings.TrimSuffix(pkg, "/")
   334  					dir, err := patsy.Dir(env, pkg)
   335  					if err != nil {
   336  						t.Fatalf("Error in '%s' while getting dir from package: %+v", name, err)
   337  					}
   338  					src, err := ioutil.ReadFile(filepath.Join(dir, fname))
   339  					if err != nil {
   340  						t.Fatalf("Error in '%s' while opening coverage: %+v", name, err)
   341  					}
   342  					lines := strings.Split(string(src), "\n")
   343  					matched := map[int]bool{}
   344  					for _, b := range p.Blocks {
   345  						if !strings.HasSuffix(lines[b.StartLine], fmt.Sprintf("// %d", b.Count)) {
   346  							t.Fatalf("Error in '%s' - incorrect count %d at %s line %d", name, b.Count, p.FileName, b.StartLine)
   347  						}
   348  						matched[b.StartLine] = true
   349  					}
   350  					for i, line := range lines {
   351  						if annotatedLine.MatchString(line) {
   352  							if _, ok := matched[i]; !ok {
   353  								t.Fatalf("Error in '%s' - annotated line doesn't match a coverage block as %s line %d", name, p.FileName, i)
   354  							}
   355  						}
   356  					}
   357  				}
   358  				fmt.Printf("%#v\n", filesInOutput)
   359  				for pname, files := range test.packages {
   360  					for fname := range files {
   361  						if strings.HasSuffix(fname, ".mod") {
   362  							continue
   363  						}
   364  						if strings.HasSuffix(fname, "_test.go") {
   365  							continue
   366  						}
   367  						if strings.HasSuffix(fname, "_exclude.go") {
   368  							// so we can have simple source files with no logic
   369  							// blocks
   370  							continue
   371  						}
   372  						fullFilename := path.Join("ns", pname, fname)
   373  						fmt.Println(fullFilename)
   374  						if _, ok := filesInOutput[fullFilename]; !ok {
   375  							t.Fatalf("Error in '%s' - %s does not appear in coverge output", name, fullFilename)
   376  						}
   377  					}
   378  				}
   379  			})
   380  		}
   381  	}
   382  }
   383  
   384  var annotatedLine = regexp.MustCompile(`// \d+$`)