github.com/epfl-dcsl/gotee@v0.0.0-20200909122901-014b35f5e5e9/src/cmd/cover/cover_test.go (about)

     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  	"bytes"
     9  	"flag"
    10  	"fmt"
    11  	"go/ast"
    12  	"go/parser"
    13  	"go/token"
    14  	"internal/testenv"
    15  	"io/ioutil"
    16  	"os"
    17  	"os/exec"
    18  	"path/filepath"
    19  	"regexp"
    20  	"strings"
    21  	"testing"
    22  )
    23  
    24  const (
    25  	// Data directory, also the package directory for the test.
    26  	testdata = "testdata"
    27  
    28  	// Binaries we compile.
    29  	testcover = "./testcover.exe"
    30  )
    31  
    32  var (
    33  	// Files we use.
    34  	testMain     = filepath.Join(testdata, "main.go")
    35  	testTest     = filepath.Join(testdata, "test.go")
    36  	coverInput   = filepath.Join(testdata, "test_line.go")
    37  	coverOutput  = filepath.Join(testdata, "test_cover.go")
    38  	coverProfile = filepath.Join(testdata, "profile.cov")
    39  )
    40  
    41  var debug = flag.Bool("debug", false, "keep rewritten files for debugging")
    42  
    43  // Run this shell script, but do it in Go so it can be run by "go test".
    44  //
    45  //	replace the word LINE with the line number < testdata/test.go > testdata/test_line.go
    46  // 	go build -o ./testcover
    47  // 	./testcover -mode=count -var=CoverTest -o ./testdata/test_cover.go testdata/test_line.go
    48  //	go run ./testdata/main.go ./testdata/test.go
    49  //
    50  func TestCover(t *testing.T) {
    51  	testenv.MustHaveGoBuild(t)
    52  
    53  	// Read in the test file (testTest) and write it, with LINEs specified, to coverInput.
    54  	file, err := ioutil.ReadFile(testTest)
    55  	if err != nil {
    56  		t.Fatal(err)
    57  	}
    58  	lines := bytes.Split(file, []byte("\n"))
    59  	for i, line := range lines {
    60  		lines[i] = bytes.Replace(line, []byte("LINE"), []byte(fmt.Sprint(i+1)), -1)
    61  	}
    62  
    63  	// Add a function that is not gofmt'ed. This used to cause a crash.
    64  	// We don't put it in test.go because then we would have to gofmt it.
    65  	// Issue 23927.
    66  	lines = append(lines, []byte("func unFormatted() {"),
    67  		[]byte("\tif true {"),
    68  		[]byte("\t}else{"),
    69  		[]byte("\t}"),
    70  		[]byte("}"))
    71  	lines = append(lines, []byte("func unFormatted2(b bool) {if b{}else{}}"))
    72  
    73  	if err := ioutil.WriteFile(coverInput, bytes.Join(lines, []byte("\n")), 0666); err != nil {
    74  		t.Fatal(err)
    75  	}
    76  
    77  	// defer removal of test_line.go
    78  	if !*debug {
    79  		defer os.Remove(coverInput)
    80  	}
    81  
    82  	// go build -o testcover
    83  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", testcover)
    84  	run(cmd, t)
    85  
    86  	// defer removal of testcover
    87  	defer os.Remove(testcover)
    88  
    89  	// ./testcover -mode=count -var=thisNameMustBeVeryLongToCauseOverflowOfCounterIncrementStatementOntoNextLineForTest -o ./testdata/test_cover.go testdata/test_line.go
    90  	cmd = exec.Command(testcover, "-mode=count", "-var=thisNameMustBeVeryLongToCauseOverflowOfCounterIncrementStatementOntoNextLineForTest", "-o", coverOutput, coverInput)
    91  	run(cmd, t)
    92  
    93  	// defer removal of ./testdata/test_cover.go
    94  	if !*debug {
    95  		defer os.Remove(coverOutput)
    96  	}
    97  
    98  	// go run ./testdata/main.go ./testdata/test.go
    99  	cmd = exec.Command(testenv.GoToolPath(t), "run", testMain, coverOutput)
   100  	run(cmd, t)
   101  
   102  	file, err = ioutil.ReadFile(coverOutput)
   103  	if err != nil {
   104  		t.Fatal(err)
   105  	}
   106  	// compiler directive must appear right next to function declaration.
   107  	if got, err := regexp.MatchString(".*\n//go:nosplit\nfunc someFunction().*", string(file)); err != nil || !got {
   108  		t.Error("misplaced compiler directive")
   109  	}
   110  	// "go:linkname" compiler directive should be present.
   111  	if got, err := regexp.MatchString(`.*go\:linkname some\_name some\_name.*`, string(file)); err != nil || !got {
   112  		t.Error("'go:linkname' compiler directive not found")
   113  	}
   114  
   115  	// Other comments should be preserved too.
   116  	c := ".*// This comment didn't appear in generated go code.*"
   117  	if got, err := regexp.MatchString(c, string(file)); err != nil || !got {
   118  		t.Errorf("non compiler directive comment %q not found", c)
   119  	}
   120  }
   121  
   122  // TestDirectives checks that compiler directives are preserved and positioned
   123  // correctly. Directives that occur before top-level declarations should remain
   124  // above those declarations, even if they are not part of the block of
   125  // documentation comments.
   126  func TestDirectives(t *testing.T) {
   127  	// Read the source file and find all the directives. We'll keep
   128  	// track of whether each one has been seen in the output.
   129  	testDirectives := filepath.Join(testdata, "directives.go")
   130  	source, err := ioutil.ReadFile(testDirectives)
   131  	if err != nil {
   132  		t.Fatal(err)
   133  	}
   134  	sourceDirectives := findDirectives(source)
   135  
   136  	// go tool cover -mode=atomic ./testdata/directives.go
   137  	cmd := exec.Command(testenv.GoToolPath(t), "tool", "cover", "-mode=atomic", testDirectives)
   138  	cmd.Stderr = os.Stderr
   139  	output, err := cmd.Output()
   140  	if err != nil {
   141  		t.Fatal(err)
   142  	}
   143  
   144  	// Check that all directives are present in the output.
   145  	outputDirectives := findDirectives(output)
   146  	foundDirective := make(map[string]bool)
   147  	for _, p := range sourceDirectives {
   148  		foundDirective[p.name] = false
   149  	}
   150  	for _, p := range outputDirectives {
   151  		if found, ok := foundDirective[p.name]; !ok {
   152  			t.Errorf("unexpected directive in output: %s", p.text)
   153  		} else if found {
   154  			t.Errorf("directive found multiple times in output: %s", p.text)
   155  		}
   156  		foundDirective[p.name] = true
   157  	}
   158  	for name, found := range foundDirective {
   159  		if !found {
   160  			t.Errorf("missing directive: %s", name)
   161  		}
   162  	}
   163  
   164  	// Check that directives that start with the name of top-level declarations
   165  	// come before the beginning of the named declaration and after the end
   166  	// of the previous declaration.
   167  	fset := token.NewFileSet()
   168  	astFile, err := parser.ParseFile(fset, testDirectives, output, 0)
   169  	if err != nil {
   170  		t.Fatal(err)
   171  	}
   172  
   173  	prevEnd := 0
   174  	for _, decl := range astFile.Decls {
   175  		var name string
   176  		switch d := decl.(type) {
   177  		case *ast.FuncDecl:
   178  			name = d.Name.Name
   179  		case *ast.GenDecl:
   180  			if len(d.Specs) == 0 {
   181  				// An empty group declaration. We still want to check that
   182  				// directives can be associated with it, so we make up a name
   183  				// to match directives in the test data.
   184  				name = "_empty"
   185  			} else if spec, ok := d.Specs[0].(*ast.TypeSpec); ok {
   186  				name = spec.Name.Name
   187  			}
   188  		}
   189  		pos := fset.Position(decl.Pos()).Offset
   190  		end := fset.Position(decl.End()).Offset
   191  		if name == "" {
   192  			prevEnd = end
   193  			continue
   194  		}
   195  		for _, p := range outputDirectives {
   196  			if !strings.HasPrefix(p.name, name) {
   197  				continue
   198  			}
   199  			if p.offset < prevEnd || pos < p.offset {
   200  				t.Errorf("directive %s does not appear before definition %s", p.text, name)
   201  			}
   202  		}
   203  		prevEnd = end
   204  	}
   205  }
   206  
   207  type directiveInfo struct {
   208  	text   string // full text of the comment, not including newline
   209  	name   string // text after //go:
   210  	offset int    // byte offset of first slash in comment
   211  }
   212  
   213  func findDirectives(source []byte) []directiveInfo {
   214  	var directives []directiveInfo
   215  	directivePrefix := []byte("\n//go:")
   216  	offset := 0
   217  	for {
   218  		i := bytes.Index(source[offset:], directivePrefix)
   219  		if i < 0 {
   220  			break
   221  		}
   222  		i++ // skip newline
   223  		p := source[offset+i:]
   224  		j := bytes.IndexByte(p, '\n')
   225  		if j < 0 {
   226  			// reached EOF
   227  			j = len(p)
   228  		}
   229  		directive := directiveInfo{
   230  			text:   string(p[:j]),
   231  			name:   string(p[len(directivePrefix)-1 : j]),
   232  			offset: offset + i,
   233  		}
   234  		directives = append(directives, directive)
   235  		offset += i + j
   236  	}
   237  	return directives
   238  }
   239  
   240  // Makes sure that `cover -func=profile.cov` reports accurate coverage.
   241  // Issue #20515.
   242  func TestCoverFunc(t *testing.T) {
   243  	// go tool cover -func ./testdata/profile.cov
   244  	cmd := exec.Command(testenv.GoToolPath(t), "tool", "cover", "-func", coverProfile)
   245  	out, err := cmd.Output()
   246  	if err != nil {
   247  		if ee, ok := err.(*exec.ExitError); ok {
   248  			t.Logf("%s", ee.Stderr)
   249  		}
   250  		t.Fatal(err)
   251  	}
   252  
   253  	if got, err := regexp.Match(".*total:.*100.0.*", out); err != nil || !got {
   254  		t.Logf("%s", out)
   255  		t.Errorf("invalid coverage counts. got=(%v, %v); want=(true; nil)", got, err)
   256  	}
   257  }
   258  
   259  func run(c *exec.Cmd, t *testing.T) {
   260  	t.Helper()
   261  	c.Stdout = os.Stdout
   262  	c.Stderr = os.Stderr
   263  	err := c.Run()
   264  	if err != nil {
   265  		t.Fatal(err)
   266  	}
   267  }