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 }