github.com/s1s1ty/go@v0.0.0-20180207192209-104445e3140f/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  	if err := ioutil.WriteFile(coverInput, bytes.Join(lines, []byte("\n")), 0666); err != nil {
    63  		t.Fatal(err)
    64  	}
    65  
    66  	// defer removal of test_line.go
    67  	if !*debug {
    68  		defer os.Remove(coverInput)
    69  	}
    70  
    71  	// go build -o testcover
    72  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", testcover)
    73  	run(cmd, t)
    74  
    75  	// defer removal of testcover
    76  	defer os.Remove(testcover)
    77  
    78  	// ./testcover -mode=count -var=thisNameMustBeVeryLongToCauseOverflowOfCounterIncrementStatementOntoNextLineForTest -o ./testdata/test_cover.go testdata/test_line.go
    79  	cmd = exec.Command(testcover, "-mode=count", "-var=thisNameMustBeVeryLongToCauseOverflowOfCounterIncrementStatementOntoNextLineForTest", "-o", coverOutput, coverInput)
    80  	run(cmd, t)
    81  
    82  	// defer removal of ./testdata/test_cover.go
    83  	if !*debug {
    84  		defer os.Remove(coverOutput)
    85  	}
    86  
    87  	// go run ./testdata/main.go ./testdata/test.go
    88  	cmd = exec.Command(testenv.GoToolPath(t), "run", testMain, coverOutput)
    89  	run(cmd, t)
    90  
    91  	file, err = ioutil.ReadFile(coverOutput)
    92  	if err != nil {
    93  		t.Fatal(err)
    94  	}
    95  	// compiler directive must appear right next to function declaration.
    96  	if got, err := regexp.MatchString(".*\n//go:nosplit\nfunc someFunction().*", string(file)); err != nil || !got {
    97  		t.Error("misplaced compiler directive")
    98  	}
    99  	// "go:linkname" compiler directive should be present.
   100  	if got, err := regexp.MatchString(`.*go\:linkname some\_name some\_name.*`, string(file)); err != nil || !got {
   101  		t.Error("'go:linkname' compiler directive not found")
   102  	}
   103  
   104  	// Other comments should be preserved too.
   105  	c := ".*// This comment didn't appear in generated go code.*"
   106  	if got, err := regexp.MatchString(c, string(file)); err != nil || !got {
   107  		t.Errorf("non compiler directive comment %q not found", c)
   108  	}
   109  }
   110  
   111  // TestDirectives checks that compiler directives are preserved and positioned
   112  // correctly. Directives that occur before top-level declarations should remain
   113  // above those declarations, even if they are not part of the block of
   114  // documentation comments.
   115  func TestDirectives(t *testing.T) {
   116  	// Read the source file and find all the directives. We'll keep
   117  	// track of whether each one has been seen in the output.
   118  	testDirectives := filepath.Join(testdata, "directives.go")
   119  	source, err := ioutil.ReadFile(testDirectives)
   120  	if err != nil {
   121  		t.Fatal(err)
   122  	}
   123  	sourceDirectives := findDirectives(source)
   124  
   125  	// go tool cover -mode=atomic ./testdata/directives.go
   126  	cmd := exec.Command(testenv.GoToolPath(t), "tool", "cover", "-mode=atomic", testDirectives)
   127  	cmd.Stderr = os.Stderr
   128  	output, err := cmd.Output()
   129  	if err != nil {
   130  		t.Fatal(err)
   131  	}
   132  
   133  	// Check that all directives are present in the output.
   134  	outputDirectives := findDirectives(output)
   135  	foundDirective := make(map[string]bool)
   136  	for _, p := range sourceDirectives {
   137  		foundDirective[p.name] = false
   138  	}
   139  	for _, p := range outputDirectives {
   140  		if found, ok := foundDirective[p.name]; !ok {
   141  			t.Errorf("unexpected directive in output: %s", p.text)
   142  		} else if found {
   143  			t.Errorf("directive found multiple times in output: %s", p.text)
   144  		}
   145  		foundDirective[p.name] = true
   146  	}
   147  	for name, found := range foundDirective {
   148  		if !found {
   149  			t.Errorf("missing directive: %s", name)
   150  		}
   151  	}
   152  
   153  	// Check that directives that start with the name of top-level declarations
   154  	// come before the beginning of the named declaration and after the end
   155  	// of the previous declaration.
   156  	fset := token.NewFileSet()
   157  	astFile, err := parser.ParseFile(fset, testDirectives, output, 0)
   158  	if err != nil {
   159  		t.Fatal(err)
   160  	}
   161  
   162  	prevEnd := 0
   163  	for _, decl := range astFile.Decls {
   164  		var name string
   165  		switch d := decl.(type) {
   166  		case *ast.FuncDecl:
   167  			name = d.Name.Name
   168  		case *ast.GenDecl:
   169  			if len(d.Specs) == 0 {
   170  				// An empty group declaration. We still want to check that
   171  				// directives can be associated with it, so we make up a name
   172  				// to match directives in the test data.
   173  				name = "_empty"
   174  			} else if spec, ok := d.Specs[0].(*ast.TypeSpec); ok {
   175  				name = spec.Name.Name
   176  			}
   177  		}
   178  		pos := fset.Position(decl.Pos()).Offset
   179  		end := fset.Position(decl.End()).Offset
   180  		if name == "" {
   181  			prevEnd = end
   182  			continue
   183  		}
   184  		for _, p := range outputDirectives {
   185  			if !strings.HasPrefix(p.name, name) {
   186  				continue
   187  			}
   188  			if p.offset < prevEnd || pos < p.offset {
   189  				t.Errorf("directive %s does not appear before definition %s", p.text, name)
   190  			}
   191  		}
   192  		prevEnd = end
   193  	}
   194  }
   195  
   196  type directiveInfo struct {
   197  	text   string // full text of the comment, not including newline
   198  	name   string // text after //go:
   199  	offset int    // byte offset of first slash in comment
   200  }
   201  
   202  func findDirectives(source []byte) []directiveInfo {
   203  	var directives []directiveInfo
   204  	directivePrefix := []byte("\n//go:")
   205  	offset := 0
   206  	for {
   207  		i := bytes.Index(source[offset:], directivePrefix)
   208  		if i < 0 {
   209  			break
   210  		}
   211  		i++ // skip newline
   212  		p := source[offset+i:]
   213  		j := bytes.IndexByte(p, '\n')
   214  		if j < 0 {
   215  			// reached EOF
   216  			j = len(p)
   217  		}
   218  		directive := directiveInfo{
   219  			text:   string(p[:j]),
   220  			name:   string(p[len(directivePrefix)-1 : j]),
   221  			offset: offset + i,
   222  		}
   223  		directives = append(directives, directive)
   224  		offset += i + j
   225  	}
   226  	return directives
   227  }
   228  
   229  // Makes sure that `cover -func=profile.cov` reports accurate coverage.
   230  // Issue #20515.
   231  func TestCoverFunc(t *testing.T) {
   232  	// go tool cover -func ./testdata/profile.cov
   233  	cmd := exec.Command(testenv.GoToolPath(t), "tool", "cover", "-func", coverProfile)
   234  	out, err := cmd.Output()
   235  	if err != nil {
   236  		if ee, ok := err.(*exec.ExitError); ok {
   237  			t.Logf("%s", ee.Stderr)
   238  		}
   239  		t.Fatal(err)
   240  	}
   241  
   242  	if got, err := regexp.Match(".*total:.*100.0.*", out); err != nil || !got {
   243  		t.Logf("%s", out)
   244  		t.Errorf("invalid coverage counts. got=(%v, %v); want=(true; nil)", got, err)
   245  	}
   246  }
   247  
   248  func run(c *exec.Cmd, t *testing.T) {
   249  	c.Stdout = os.Stdout
   250  	c.Stderr = os.Stderr
   251  	err := c.Run()
   252  	if err != nil {
   253  		t.Fatal(err)
   254  	}
   255  }