Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ go install github.com/keyskey/gobce/cmd/gobce@latest
For CI and team-wide reproducibility, pin a version tag:

```bash
go install github.com/keyskey/gobce/cmd/gobce@v0.1.0
go install github.com/keyskey/gobce/cmd/gobce@v0.2.0
```

## Binary Distribution
Expand All @@ -37,7 +37,7 @@ Recommended archive set:
- linux-arm64
- linux-amd64

Install from a prebuilt archive (example: `v0.1.0`, `darwin-arm64`):
Install from a prebuilt archive (example: `v0.2.0`, `darwin-arm64`):

```bash
curl -fsSL "https://raw.githubusercontent.com/keyskey/gobce/main/scripts/install.sh" | sh
Expand All @@ -46,7 +46,7 @@ curl -fsSL "https://raw.githubusercontent.com/keyskey/gobce/main/scripts/install
Install a specific version:

```bash
VERSION=v0.1.0 curl -fsSL "https://raw.githubusercontent.com/keyskey/gobce/main/scripts/install.sh" | sh
VERSION=v0.2.0 curl -fsSL "https://raw.githubusercontent.com/keyskey/gobce/main/scripts/install.sh" | sh
```

Install to a custom directory:
Expand Down Expand Up @@ -125,7 +125,7 @@ During the pre-1.0 phase, this project is versioned as `0.x` while coverage logi

Please assume that interfaces and output formats may change between `0.x` minor versions.

Version tags use the `vX.Y.Z` format (for example, `v0.1.0`).
Version tags use the `vX.Y.Z` format (for example, `v0.2.0`).

## Release Flow

Expand All @@ -145,13 +145,13 @@ make tag-next TYPE=major
```

```bash
git tag v0.1.0
git push origin v0.1.0
git tag v0.2.0
git push origin v0.2.0
```

After push:
- GitHub Actions workflow `release` runs automatically
- GitHub Release is created for `v0.1.0`
- GitHub Release is created for `v0.2.0`
- Release notes are generated from commit history
- OS/arch archives and `checksums.txt` are uploaded

Expand Down
40 changes: 40 additions & 0 deletions analyzer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,46 @@ func score(v int) int {
}
}

// Regression: coverprofile ends the if-body block at the closing "}", but the next
// statement often starts on the following line; fallthrough must not require an exact
// start position match with body.End().
func TestAnalyzeIfWithoutElseFallthroughNextLine(t *testing.T) {
tmp := t.TempDir()
srcPath := filepath.Join(tmp, "sample.go")
src := `package sample

func Get(v int) int {
if v > 10 {
return 1
}
return 0
}
`
if err := os.WriteFile(srcPath, []byte(src), 0o644); err != nil {
t.Fatalf("write source: %v", err)
}

coverPath := filepath.Join(tmp, "coverage.out")
coverage := strings.Join([]string{
"mode: set",
srcPath + ":3.23,4.13 1 1",
srcPath + ":4.13,6.3 1 0",
srcPath + ":7.2,7.10 1 1",
}, "\n")
if err := os.WriteFile(coverPath, []byte(coverage), 0o644); err != nil {
t.Fatalf("write coverage: %v", err)
}

result, err := Analyze(AnalyzeInput{CoverProfilePath: coverPath})
if err != nil {
t.Fatalf("analyze: %v", err)
}

if hasUncoveredBranchKind(result, "if_implicit_false_path") {
t.Fatalf("expected if_implicit_false_path covered when next-line fallthrough block has count")
}
}

func TestAnalyzeIfWithoutElseNonTerminatingBody(t *testing.T) {
tmp := t.TempDir()
srcPath := filepath.Join(tmp, "sample.go")
Expand Down
16 changes: 11 additions & 5 deletions internal/analyzer/branches.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,17 +148,23 @@ func ifBodyTerminates(body *ast.BlockStmt) bool {
}
}

// fallthroughCovered reports whether execution can reach the first statement after body
// (implicit else) according to coverage. The next cover block rarely starts at body.End()
// (closing brace); it usually begins at the following statement, so we take the nearest
// block in this file whose start is at or after body.End().
func fallthroughCovered(blocks []coverageBlock, fset *token.FileSet, body *ast.BlockStmt) bool {
endPos := fset.Position(body.End())
for _, b := range blocks {
if b.Count <= 0 {
var nearest *coverageBlock
for i := range blocks {
b := &blocks[i]
if positionLess(b.StartLine, b.StartCol, endPos.Line, endPos.Column) {
continue
}
if b.StartLine == endPos.Line && b.StartCol == endPos.Column {
return true
if nearest == nil || positionLess(b.StartLine, b.StartCol, nearest.StartLine, nearest.StartCol) {
nearest = b
}
}
return false
return nearest != nil && nearest.Count > 0
}

func blockStmtCovered(blocks []coverageBlock, fset *token.FileSet, body *ast.BlockStmt) bool {
Expand Down
Loading