cmd/cover: avoid repeating positions

When using //line directives and unformatted code it is possible for
positions to repeat. Increment the final column position to avoid that.

Fixes #27350

Change-Id: I2faccc31360075e9814d4a024b0f98b117f8ce97
Reviewed-on: https://go-review.googlesource.com/c/153061
Run-TryBot: Rob Pike <r@golang.org>
Reviewed-by: Rob Pike <r@golang.org>
This commit is contained in:
Ian Lance Taylor 2018-12-06 20:54:35 -08:00
parent 77caea5bf2
commit d689946302
2 changed files with 80 additions and 8 deletions

View file

@ -646,9 +646,21 @@ func (f *File) addVariables(w io.Writer) {
// - 32-bit starting line number
// - 32-bit ending line number
// - (16 bit ending column number << 16) | (16-bit starting column number).
var lastStart, lastEnd token.Position
for i, block := range f.blocks {
start := f.fset.Position(block.startByte)
end := f.fset.Position(block.endByte)
// It is possible for positions to repeat when there is a
// line directive that does not specify column information
// and the input has not been passed through gofmt.
// See issue #27350 and TestHtmlUnformatted.
if samePos(start, lastStart) && samePos(end, lastEnd) {
end.Column++
}
lastStart = start
lastEnd = end
fmt.Fprintf(w, "\t\t%d, %d, %#x, // [%d]\n", start.Line, end.Line, (end.Column&0xFFFF)<<16|(start.Column&0xFFFF), i)
}
@ -697,3 +709,11 @@ func isValidIdentifier(ident string) bool {
}
return true
}
// samePos returns whether two positions have the same file/line/column.
// We don't use p1 == p2 because token.Position also has an Offset field,
// and when the input uses //line directives two Positions can have different
// Offset values while having the same file/line/dolumn.
func samePos(p1, p2 token.Position) bool {
return p1.Filename == p2.Filename && p1.Line == p2.Line && p1.Column == p2.Column
}

View file

@ -40,11 +40,16 @@ var (
htmlGolden = filepath.Join(testdata, "html", "html.golden")
// Temporary files.
tmpTestMain string
coverInput string
coverOutput string
htmlProfile string
htmlHTML string
tmpTestMain string
coverInput string
coverOutput string
htmlProfile string
htmlHTML string
htmlUDir string
htmlU string
htmlUTest string
htmlUProfile string
htmlUHTML string
)
var (
@ -85,6 +90,11 @@ func TestMain(m *testing.M) {
coverOutput = filepath.Join(dir, "test_cover.go")
htmlProfile = filepath.Join(dir, "html.cov")
htmlHTML = filepath.Join(dir, "html.html")
htmlUDir = filepath.Join(dir, "htmlunformatted")
htmlU = filepath.Join(htmlUDir, "htmlunformatted.go")
htmlUTest = filepath.Join(htmlUDir, "htmlunformatted_test.go")
htmlUProfile = filepath.Join(htmlUDir, "htmlunformatted.cov")
htmlUHTML = filepath.Join(htmlUDir, "htmlunformatted.html")
status := m.Run()
@ -427,12 +437,54 @@ func TestCoverHTML(t *testing.T) {
}
}
// Test HTML processing with a source file not run through gofmt.
// Issue #27350.
func TestHtmlUnformatted(t *testing.T) {
t.Parallel()
testenv.MustHaveGoRun(t)
buildCover(t)
if err := os.Mkdir(htmlUDir, 0777); err != nil {
t.Fatal(err)
}
const htmlUContents = `
package htmlunformatted
var g int
func F() {
//line x.go:1
{ { F(); goto lab } }
lab:
}`
const htmlUTestContents = `package htmlunformatted`
if err := ioutil.WriteFile(htmlU, []byte(htmlUContents), 0444); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(htmlUTest, []byte(htmlUTestContents), 0444); err != nil {
t.Fatal(err)
}
// go test -covermode=count -coverprofile TMPDIR/htmlunformatted.cov
cmd := exec.Command(testenv.GoToolPath(t), "test", toolexecArg, "-covermode=count", "-coverprofile", htmlUProfile)
cmd.Dir = htmlUDir
run(cmd, t)
// testcover -html TMPDIR/htmlunformatted.cov -o unformatted.html
cmd = exec.Command(testcover, "-html", htmlUProfile, "-o", htmlUHTML)
run(cmd, t)
}
func run(c *exec.Cmd, t *testing.T) {
t.Helper()
t.Log("running", c.Args)
c.Stdout = os.Stdout
c.Stderr = os.Stderr
err := c.Run()
out, err := c.CombinedOutput()
if len(out) > 0 {
t.Logf("%s", out)
}
if err != nil {
t.Fatal(err)
}