github.com/hikaru7719/go@v0.0.0-20181025140707-c8b2ac68906a/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 "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 }