...
Run Format

Source file src/cmd/cover/cover_test.go

Documentation: cmd/cover

     1  // Copyright 2013 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 main_test
     6  
     7  import (
     8  	"bufio"
     9  	"bytes"
    10  	"flag"
    11  	"fmt"
    12  	"go/ast"
    13  	"go/parser"
    14  	"go/token"
    15  	"internal/testenv"
    16  	"io/ioutil"
    17  	"os"
    18  	"os/exec"
    19  	"path/filepath"
    20  	"regexp"
    21  	"strings"
    22  	"testing"
    23  )
    24  
    25  const (
    26  	// Data directory, also the package directory for the test.
    27  	testdata = "testdata"
    28  
    29  	// Binaries we compile.
    30  	testcover = "./testcover.exe"
    31  )
    32  
    33  var (
    34  	// Files we use.
    35  	testMain     = filepath.Join(testdata, "main.go")
    36  	testTest     = filepath.Join(testdata, "test.go")
    37  	coverInput   = filepath.Join(testdata, "test_line.go")
    38  	coverOutput  = filepath.Join(testdata, "test_cover.go")
    39  	coverProfile = filepath.Join(testdata, "profile.cov")
    40  
    41  	// The HTML test files are in a separate directory
    42  	// so they are a complete package.
    43  	htmlProfile = filepath.Join(testdata, "html", "html.cov")
    44  	htmlHTML    = filepath.Join(testdata, "html", "html.html")
    45  	htmlGolden  = filepath.Join(testdata, "html", "html.golden")
    46  )
    47  
    48  var debug = flag.Bool("debug", false, "keep rewritten files for debugging")
    49  
    50  // Run this shell script, but do it in Go so it can be run by "go test".
    51  //
    52  //	replace the word LINE with the line number < testdata/test.go > testdata/test_line.go
    53  // 	go build -o ./testcover
    54  // 	./testcover -mode=count -var=CoverTest -o ./testdata/test_cover.go testdata/test_line.go
    55  //	go run ./testdata/main.go ./testdata/test.go
    56  //
    57  func TestCover(t *testing.T) {
    58  	testenv.MustHaveGoBuild(t)
    59  
    60  	// Read in the test file (testTest) and write it, with LINEs specified, to coverInput.
    61  	file, err := ioutil.ReadFile(testTest)
    62  	if err != nil {
    63  		t.Fatal(err)
    64  	}
    65  	lines := bytes.Split(file, []byte("\n"))
    66  	for i, line := range lines {
    67  		lines[i] = bytes.Replace(line, []byte("LINE"), []byte(fmt.Sprint(i+1)), -1)
    68  	}
    69  
    70  	// Add a function that is not gofmt'ed. This used to cause a crash.
    71  	// We don't put it in test.go because then we would have to gofmt it.
    72  	// Issue 23927.
    73  	lines = append(lines, []byte("func unFormatted() {"),
    74  		[]byte("\tif true {"),
    75  		[]byte("\t}else{"),
    76  		[]byte("\t}"),
    77  		[]byte("}"))
    78  	lines = append(lines, []byte("func unFormatted2(b bool) {if b{}else{}}"))
    79  
    80  	if err := ioutil.WriteFile(coverInput, bytes.Join(lines, []byte("\n")), 0666); err != nil {
    81  		t.Fatal(err)
    82  	}
    83  
    84  	// defer removal of test_line.go
    85  	if !*debug {
    86  		defer os.Remove(coverInput)
    87  	}
    88  
    89  	// go build -o testcover
    90  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", testcover)
    91  	run(cmd, t)
    92  
    93  	// defer removal of testcover
    94  	defer os.Remove(testcover)
    95  
    96  	// ./testcover -mode=count -var=thisNameMustBeVeryLongToCauseOverflowOfCounterIncrementStatementOntoNextLineForTest -o ./testdata/test_cover.go testdata/test_line.go
    97  	cmd = exec.Command(testcover, "-mode=count", "-var=thisNameMustBeVeryLongToCauseOverflowOfCounterIncrementStatementOntoNextLineForTest", "-o", coverOutput, coverInput)
    98  	run(cmd, t)
    99  
   100  	// defer removal of ./testdata/test_cover.go
   101  	if !*debug {
   102  		defer os.Remove(coverOutput)
   103  	}
   104  
   105  	// go run ./testdata/main.go ./testdata/test.go
   106  	cmd = exec.Command(testenv.GoToolPath(t), "run", testMain, coverOutput)
   107  	run(cmd, t)
   108  
   109  	file, err = ioutil.ReadFile(coverOutput)
   110  	if err != nil {
   111  		t.Fatal(err)
   112  	}
   113  	// compiler directive must appear right next to function declaration.
   114  	if got, err := regexp.MatchString(".*\n//go:nosplit\nfunc someFunction().*", string(file)); err != nil || !got {
   115  		t.Error("misplaced compiler directive")
   116  	}
   117  	// "go:linkname" compiler directive should be present.
   118  	if got, err := regexp.MatchString(`.*go\:linkname some\_name some\_name.*`, string(file)); err != nil || !got {
   119  		t.Error("'go:linkname' compiler directive not found")
   120  	}
   121  
   122  	// Other comments should be preserved too.
   123  	c := ".*// This comment didn't appear in generated go code.*"
   124  	if got, err := regexp.MatchString(c, string(file)); err != nil || !got {
   125  		t.Errorf("non compiler directive comment %q not found", c)
   126  	}
   127  }
   128  
   129  // TestDirectives checks that compiler directives are preserved and positioned
   130  // correctly. Directives that occur before top-level declarations should remain
   131  // above those declarations, even if they are not part of the block of
   132  // documentation comments.
   133  func TestDirectives(t *testing.T) {
   134  	// Read the source file and find all the directives. We'll keep
   135  	// track of whether each one has been seen in the output.
   136  	testDirectives := filepath.Join(testdata, "directives.go")
   137  	source, err := ioutil.ReadFile(testDirectives)
   138  	if err != nil {
   139  		t.Fatal(err)
   140  	}
   141  	sourceDirectives := findDirectives(source)
   142  
   143  	// go tool cover -mode=atomic ./testdata/directives.go
   144  	cmd := exec.Command(testenv.GoToolPath(t), "tool", "cover", "-mode=atomic", testDirectives)
   145  	cmd.Stderr = os.Stderr
   146  	output, err := cmd.Output()
   147  	if err != nil {
   148  		t.Fatal(err)
   149  	}
   150  
   151  	// Check that all directives are present in the output.
   152  	outputDirectives := findDirectives(output)
   153  	foundDirective := make(map[string]bool)
   154  	for _, p := range sourceDirectives {
   155  		foundDirective[p.name] = false
   156  	}
   157  	for _, p := range outputDirectives {
   158  		if found, ok := foundDirective[p.name]; !ok {
   159  			t.Errorf("unexpected directive in output: %s", p.text)
   160  		} else if found {
   161  			t.Errorf("directive found multiple times in output: %s", p.text)
   162  		}
   163  		foundDirective[p.name] = true
   164  	}
   165  	for name, found := range foundDirective {
   166  		if !found {
   167  			t.Errorf("missing directive: %s", name)
   168  		}
   169  	}
   170  
   171  	// Check that directives that start with the name of top-level declarations
   172  	// come before the beginning of the named declaration and after the end
   173  	// of the previous declaration.
   174  	fset := token.NewFileSet()
   175  	astFile, err := parser.ParseFile(fset, testDirectives, output, 0)
   176  	if err != nil {
   177  		t.Fatal(err)
   178  	}
   179  
   180  	prevEnd := 0
   181  	for _, decl := range astFile.Decls {
   182  		var name string
   183  		switch d := decl.(type) {
   184  		case *ast.FuncDecl:
   185  			name = d.Name.Name
   186  		case *ast.GenDecl:
   187  			if len(d.Specs) == 0 {
   188  				// An empty group declaration. We still want to check that
   189  				// directives can be associated with it, so we make up a name
   190  				// to match directives in the test data.
   191  				name = "_empty"
   192  			} else if spec, ok := d.Specs[0].(*ast.TypeSpec); ok {
   193  				name = spec.Name.Name
   194  			}
   195  		}
   196  		pos := fset.Position(decl.Pos()).Offset
   197  		end := fset.Position(decl.End()).Offset
   198  		if name == "" {
   199  			prevEnd = end
   200  			continue
   201  		}
   202  		for _, p := range outputDirectives {
   203  			if !strings.HasPrefix(p.name, name) {
   204  				continue
   205  			}
   206  			if p.offset < prevEnd || pos < p.offset {
   207  				t.Errorf("directive %s does not appear before definition %s", p.text, name)
   208  			}
   209  		}
   210  		prevEnd = end
   211  	}
   212  }
   213  
   214  type directiveInfo struct {
   215  	text   string // full text of the comment, not including newline
   216  	name   string // text after //go:
   217  	offset int    // byte offset of first slash in comment
   218  }
   219  
   220  func findDirectives(source []byte) []directiveInfo {
   221  	var directives []directiveInfo
   222  	directivePrefix := []byte("\n//go:")
   223  	offset := 0
   224  	for {
   225  		i := bytes.Index(source[offset:], directivePrefix)
   226  		if i < 0 {
   227  			break
   228  		}
   229  		i++ // skip newline
   230  		p := source[offset+i:]
   231  		j := bytes.IndexByte(p, '\n')
   232  		if j < 0 {
   233  			// reached EOF
   234  			j = len(p)
   235  		}
   236  		directive := directiveInfo{
   237  			text:   string(p[:j]),
   238  			name:   string(p[len(directivePrefix)-1 : j]),
   239  			offset: offset + i,
   240  		}
   241  		directives = append(directives, directive)
   242  		offset += i + j
   243  	}
   244  	return directives
   245  }
   246  
   247  // Makes sure that `cover -func=profile.cov` reports accurate coverage.
   248  // Issue #20515.
   249  func TestCoverFunc(t *testing.T) {
   250  	// go tool cover -func ./testdata/profile.cov
   251  	cmd := exec.Command(testenv.GoToolPath(t), "tool", "cover", "-func", coverProfile)
   252  	out, err := cmd.Output()
   253  	if err != nil {
   254  		if ee, ok := err.(*exec.ExitError); ok {
   255  			t.Logf("%s", ee.Stderr)
   256  		}
   257  		t.Fatal(err)
   258  	}
   259  
   260  	if got, err := regexp.Match(".*total:.*100.0.*", out); err != nil || !got {
   261  		t.Logf("%s", out)
   262  		t.Errorf("invalid coverage counts. got=(%v, %v); want=(true; nil)", got, err)
   263  	}
   264  }
   265  
   266  // Check that cover produces correct HTML.
   267  // Issue #25767.
   268  func TestCoverHTML(t *testing.T) {
   269  	testenv.MustHaveGoBuild(t)
   270  	if !*debug {
   271  		defer os.Remove(testcover)
   272  		defer os.Remove(htmlProfile)
   273  		defer os.Remove(htmlHTML)
   274  	}
   275  	// go build -o testcover
   276  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", testcover)
   277  	run(cmd, t)
   278  	// go test -coverprofile testdata/html/html.cov cmd/cover/testdata/html
   279  	cmd = exec.Command(testenv.GoToolPath(t), "test", "-coverprofile", htmlProfile, "cmd/cover/testdata/html")
   280  	run(cmd, t)
   281  	// ./testcover -html testdata/html/html.cov -o testdata/html/html.html
   282  	cmd = exec.Command(testcover, "-html", htmlProfile, "-o", htmlHTML)
   283  	run(cmd, t)
   284  
   285  	// Extract the parts of the HTML with comment markers,
   286  	// and compare against a golden file.
   287  	entireHTML, err := ioutil.ReadFile(htmlHTML)
   288  	if err != nil {
   289  		t.Fatal(err)
   290  	}
   291  	var out bytes.Buffer
   292  	scan := bufio.NewScanner(bytes.NewReader(entireHTML))
   293  	in := false
   294  	for scan.Scan() {
   295  		line := scan.Text()
   296  		if strings.Contains(line, "// START") {
   297  			in = true
   298  		}
   299  		if in {
   300  			fmt.Fprintln(&out, line)
   301  		}
   302  		if strings.Contains(line, "// END") {
   303  			in = false
   304  		}
   305  	}
   306  	golden, err := ioutil.ReadFile(htmlGolden)
   307  	if err != nil {
   308  		t.Fatalf("reading golden file: %v", err)
   309  	}
   310  	// Ignore white space differences.
   311  	// Break into lines, then compare by breaking into words.
   312  	goldenLines := strings.Split(string(golden), "\n")
   313  	outLines := strings.Split(out.String(), "\n")
   314  	// Compare at the line level, stopping at first different line so
   315  	// we don't generate tons of output if there's an inserted or deleted line.
   316  	for i, goldenLine := range goldenLines {
   317  		if i > len(outLines) {
   318  			t.Fatalf("output shorter than golden; stops before line %d: %s\n", i+1, goldenLine)
   319  		}
   320  		// Convert all white space to simple spaces, for easy comparison.
   321  		goldenLine = strings.Join(strings.Fields(goldenLine), " ")
   322  		outLine := strings.Join(strings.Fields(outLines[i]), " ")
   323  		if outLine != goldenLine {
   324  			t.Fatalf("line %d differs: got:\n\t%s\nwant:\n\t%s", i+1, outLine, goldenLine)
   325  		}
   326  	}
   327  	if len(goldenLines) != len(outLines) {
   328  		t.Fatalf("output longer than golden; first extra output line %d: %q\n", len(goldenLines)+1, outLines[len(goldenLines)])
   329  	}
   330  }
   331  
   332  func run(c *exec.Cmd, t *testing.T) {
   333  	t.Helper()
   334  	c.Stdout = os.Stdout
   335  	c.Stderr = os.Stderr
   336  	err := c.Run()
   337  	if err != nil {
   338  		t.Fatal(err)
   339  	}
   340  }
   341  

View as plain text