go icon indicating copy to clipboard operation
go copied to clipboard

cmd/go: go test -cover & go test -coverprofile should always output a coverage

Open gustafj opened this issue 6 years ago • 19 comments

Description

Now in 1.10 when go test -cover supports multiple packages, I would expect it to print out a percentage for all packages (including those missing tests). And for go test -coverprofile, I would expect all packages to be included in the calculated total.

Currently only packages that have at least one test (can be a *_test.go with only the package declaration) is included, see pkg2 below.

What version of Go are you using (go version)?

go version go1.10 linux/amd64

Does this issue reproduce with the latest release?

yes

What operating system and processor architecture are you using (go env)?

Linux, amd64

What did you do?

go test ./... -cover go test ./... -coverprofile cover.out; go tool cover -func cover.out

What did you expect to see?

?      path/to/pkg1   0.001s  coverage:   0.0% of statements [no test files]
ok     path/to/pkg2   0.019s  coverage:   0.0% of statements [no tests to run]
ok     path/to/pkg3   0.371s  coverage: 100.0% of statements
path/to/pkg1/pkg1.go:5: String 0.0%
path/to/pkg2/pkg2.go:5: String 0.0%
path/to/pkg3/pkg3.go:5: String 100.0%
total: (statements) 33.3%

What did you see instead?

?      path/to/pkg1   [no test files]
ok     path/to/pkg2   0.019s  coverage:   0.0% of statements [no tests to run]
ok     path/to/pkg3   0.371s  coverage: 100.0% of statements
path/to/pkg2/pkg2.go:5: String 0.0%
path/to/pkg3/pkg3.go:5: String 100.0%
total: (statements) 50.0%

gustafj avatar Mar 27 '18 21:03 gustafj

Change https://golang.org/cl/115095 mentions this issue: cmd/go/internal/test: always output a coverage

gopherbot avatar May 29 '18 15:05 gopherbot

@kyroy noticed that this issue is very similar to #25492. It seems to me like both should be fixed at once, as they suggest changes in opposite directions. The other issue wants to change a 0.0% with a [no statements], and this one wants to change [no test files] with 0.0%.

/cc @bcmills @egonk

mvdan avatar May 30 '18 18:05 mvdan

they suggest changes in opposite directions.

#25492 is about the case where there are no statements to cover (and thus the percentage is undefined). That's materially different from the case here, where pkg1 does have a statement and it is not covered: 0.0% coverage is well-defined and correct for a package with one statement and no tests.

bcmills avatar May 30 '18 19:05 bcmills

My reading of this issue is that go test -cover should always output a percentage if it succeeded. The other issue is precisely about removing a 0.0% output in favor of something else.

mvdan avatar May 30 '18 19:05 mvdan

My reading of this issue is that go test -cover should always output a percentage if it succeeded.

That's the current issue title, but that behavior seems clearly wrong if there are no statements to cover: 0/0 is not a well-defined percentage.

(I guess we could output NaN%, but that seems strictly less helpful than [no statements].)

bcmills avatar May 30 '18 19:05 bcmills

I like the idea of a consistent output that would be created by just implementing this issue.

Another approach is, as in #25492, for all statements it is true that they are covered by the tests. So one could argue for 100% which is obviously mathematically incorrect.

I am also the creator of the CL. Happy to implement a solution that results from the discussion :)

kyroy avatar May 30 '18 21:05 kyroy

Change https://golang.org/cl/122518 mentions this issue: cmd/go: revert "output coverage report even if there are no test files"

gopherbot avatar Jul 06 '18 20:07 gopherbot

CL 115095 was reverted, so reopening this issue.

ianlancetaylor avatar Jul 09 '18 18:07 ianlancetaylor

FYI, f7248f05946c1804b5519d0b3eb0db054dc9c5d6 brought back the pkg{1,2,3} folders inside cmd/go/testdata/testcover that was originally reverted by https://github.com/golang/go/commit/7254cfc37b3a93a6e83dae22c4bfd6f777edb97e. Those files are unused as of now.

agnivade avatar Dec 23 '18 09:12 agnivade

Any news here?

Nerzal avatar Feb 10 '19 17:02 Nerzal

One can use -coverpkg=./... to get accurate results:

$ tree .
.
├── coverage.out
├── package0.go
├── package0_test.go
├── package1
│   ├── package1.go
│   └── package1_test.go
└── package2
    └── package2.go

2 directories, 6 files

Without -coverpkg=./...:

$ go test -v -coverprofile=coverage.out ./...
=== RUN   TestMin
--- PASS: TestMin (0.00s)
PASS
coverage: 100.0% of statements
ok  	github.com/rabadin/brokencoverage	0.007s	coverage: 100.0% of statements
=== RUN   TestAdd
--- PASS: TestAdd (0.00s)
PASS
coverage: 100.0% of statements
ok  	github.com/rabadin/brokencoverage/package1	0.006s	coverage: 100.0% of statements
?   	github.com/rabadin/brokencoverage/package2	[no test files]

$ go tool cover -func=coverage.out
github.com/rabadin/brokencoverage/package0.go:3:		min		100.0%
github.com/rabadin/brokencoverage/package1/package1.go:3:	add		100.0%
total:								(statements)	100.0%

With -coverpkg=./...:

$ rm coverage.out

$ go test -v -coverpkg=./... -coverprofile=coverage.out ./...
=== RUN   TestMin
--- PASS: TestMin (0.00s)
PASS
coverage: 33.3% of statements in ./...
ok  	github.com/rabadin/brokencoverage	0.006s	coverage: 33.3% of statements in ./...
=== RUN   TestAdd
--- PASS: TestAdd (0.00s)
PASS
coverage: 33.3% of statements in ./...
ok  	github.com/rabadin/brokencoverage/package1	0.006s	coverage: 33.3% of statements in ./...
?   	github.com/rabadin/brokencoverage/package2	[no test files]

$ go tool cover -func=coverage.out
github.com/rabadin/brokencoverage/package0.go:3:		min		100.0%
github.com/rabadin/brokencoverage/package1/package1.go:3:	add		100.0%
github.com/rabadin/brokencoverage/package2/package2.go:3:	mult		0.0%
total:								(statements)	66.7%

rabadin avatar Apr 01 '19 14:04 rabadin

Using -coverpkg does not actually help if the package is never called in any test at all. -coverpkg is a cover-up that allows packages from a different package to provide coverage for this package.

So, say package a has no tests, and thus no coverage. But package b has tests, and those tests call into a. As a result, if you start using a -coverpkg that covers both packages, the tests from package b will report its coverage of package a as coverage for a.

However, if we have a package c that is never called by either a or b then that package will still be left out in the dry, even if it would match the -coverpkg given.

project$ find . -name "*_test.go"
project$ go test -coverpkg=./... -coverprofile=coverage.out ./...
?   	project	[no test files]
?   	project/sub/dir1	[no test files]
?   	project/sub/dir2	[no test files]
project$ cat coverage.out
mode: set
project$

Additionally, it is misleading to say that because b has called into a that that coverage of a counts as tests on a, because they are not unit tests of a’s functionality, and b is fundamentally not responsible for testing of a.

puellanivis avatar Jun 07 '19 12:06 puellanivis

@puellanivis:

Using -coverpkg does not actually help if the package is never called in any test at all.

I'm not sure this is true. Here is the code from the example above:

find . -name "*.go" -exec echo '******** {}' \; -exec cat {} \;
******** ./package2/package2.go
package package2

func mult(a int, b int) int {
	return a * b
}
******** ./package0_test.go
package brokencoverage

import "testing"

func TestMin(t *testing.T) {
	if min(3,5) != 3-5 {
		t.Errorf("min is broken")
	}
}
******** ./package1/package1_test.go
package package1

import "testing"

func TestAdd(t *testing.T) {
	if add(3,5) != 3+5 {
		t.Errorf("add is broken")
	}
}
******** ./package1/package1.go

package package1

func add(a int, b int) int {
	return a + b
}
******** ./package0.go
package brokencoverage

func min(a int, b int) int {
	return a - b
}

as you can see nothing is calling package2/package2.go:mult.

rabadin avatar Jun 07 '19 12:06 rabadin

I don’t know exactly what is different, but even with -coverpkg=./... my project with no tests still reports no packages:

project-with-no-tests$ go test -v -coverpkg=./... -coverprofile=coverage.out ./...
?   	project-with-no-tests	[no test files]
?   	project-with-no-tests/subpackage	[no test files]
project-with-no-tests$ go tool cover -func=coverage.out
total:	(statements)	0.0%

While adding just a single _test.go with just the package name yields coverage details for a package.

Meanwhile, if I add a reference to package2.Mult in package0_test.go suddenly, with -coverpkg=./... we report that there is coverage of package2.Mult, even though it is not actually covered by any unit tests. (It would make sense to include such coverage in integration tests though, as there you are testing the system as a whole, but unit tests should never count coverage provided by any other package.)

broken-coverage$ cat package0_test.go 
package brokencoverage

import (
	"testing"
	"github.com/puellanivis/broken-coverage/package2"
)

func TestMin(t *testing.T) {
	if min(3,5) != package2.Mult(-1, 2) {
		t.Errorf("min is broken")
	}
}
broken-coverage$ go test -v -coverpkg=./... -coverprofile=coverage.out ./...
=== RUN   TestMin
--- PASS: TestMin (0.00s)
PASS
coverage: 66.7% of statements in ./...
ok  	github.com/puellanivis/broken-coverage	0.001s	coverage: 66.7% of statements in ./...
=== RUN   TestAdd
--- PASS: TestAdd (0.00s)
PASS
coverage: 33.3% of statements in ./...
ok  	github.com/puellanivis/broken-coverage/package1	0.004s	coverage: 33.3% of statements in ./...
?   	github.com/puellanivis/broken-coverage/package2	[no test files]
broken-coverage$ go tool cover -func=coverage.out
github.com/puellanivis/broken-coverage/package0.go:3:		min		100.0%
github.com/puellanivis/broken-coverage/package1/package1.go:3:	add		100.0%
github.com/puellanivis/broken-coverage/package2/package2.go:3:	Mult		100.0%
total:								(statements)	100.0%

puellanivis avatar Jun 11 '19 08:06 puellanivis

I wrote this simple script to get average metric from coverage.out file: https://gist.github.com/sallyruthstruik/72019ba0041deaa1a2c0a817b4603403

sallyruthstruik avatar Sep 06 '19 13:09 sallyruthstruik

It would be really nice to get this forward.

luklss avatar Oct 28 '20 10:10 luklss

@luklss practically everyone agrees this should be fixed, but the details of the fix are unclear. Someone needs to put in the time and effort to figure that out.

mvdan avatar Oct 28 '20 10:10 mvdan

I wrote this simple script to get average metric from coverage.out file: https://gist.github.com/sallyruthstruik/72019ba0041deaa1a2c0a817b4603403

@sallyruthstruik I don't think this would work, since the problem here is that coverage.out doesn't contain information from packages with no tests.

piersy avatar Jun 28 '21 21:06 piersy

One can use -coverpkg=./... to get accurate results:

It seems this trick works in Go 1.19 but not in Go 1.20. I wonder what is the correct way to force the output of 0.0% in Go 1.20? (In case it is relevant, my workflow uses the legacy text format.)

favonia avatar Feb 03 '23 06:02 favonia

One can use -coverpkg=./... to get accurate results:

It seems this trick works in Go 1.19 but not in Go 1.20. I wonder what is the correct way to force the output of 0.0% in Go 1.20? (In case it is relevant, my workflow uses the legacy text format.)

Yep.

In Go 1.19 it was even possible to use -run ^$ and record all coverage blocks in coverpkg, even if no test binaries depend on given package. Such empty blocks might be useful for custom profile merges and coverage analysis.

Example:

➜ tree .
.
├── go.mod
├── pkg1
│   └── hello.go
└── pkg2
    ├── greetings.go
    └── greetings_test.go

2 directories, 4 files

Go 1.19

➜ go test -coverpkg ./... -coverprofile /tmp/cover.out -run ^$ ./...      
?   	emptycover/pkg1	[no test files]
ok  	emptycover/pkg2	0.001s	coverage: 0.0% of statements in ./... [no tests to run]
➜ go tool cover -func=/tmp/cover.out                                
emptycover/pkg1/hello.go:5:	Hello		0.0%
emptycover/pkg2/greetings.go:5:	Greetings	0.0%
total:				(statements)	0.0%

Go 1.20

➜ go test -coverpkg ./... -coverprofile /tmp/cover.out -run ^$ ./...
?   	emptycover/pkg1	[no test files]
ok  	emptycover/pkg2	0.002s	coverage: 0.0% of statements in ./... [no tests to run]
➜ go tool cover -func=/tmp/cover.out                                                
emptycover/pkg2/greetings.go:5:	Greetings	0.0%
total:				(statements)	0.0%

verytable avatar Feb 10 '23 22:02 verytable

Going deeper into the codebase of the language itself seems like this was never an intended feature, but more of a side-effect. Unfortunately the source code of the testing package is not straightforward, and many things break when I am trying to change it locally. The solution might be to introduce a new flag that enables this explicitly.

ktsivkov avatar Mar 04 '23 01:03 ktsivkov

I wonder what is the correct way to force the output of 0.0% in Go 1.20?

PS: The best workaround (that has been mentioned by others): add dummy <foo>_test.go files with just one line package <foo>_test.

favonia avatar Mar 09 '23 22:03 favonia

Je me demande quelle est la bonne façon de forcer la sortie de 0.0%Go 1.20 ?

PS : La meilleure solution de contournement (qui a été mentionnée par d'autres) : ajoutez <foo>_test.godes fichiers factices avec une seule ligne package <foo>_test.

It's not very optimal but it's the only solution that seems to work for the moment. thx

devleesch001 avatar Apr 24 '23 14:04 devleesch001

You can use export GOEXPERIMENT=nocoverageredesign as a workaround in Go 1.20 until this issue is fixed as per https://github.com/golang/go/issues/58770

simonkotwicz avatar Apr 26 '23 18:04 simonkotwicz

Change https://go.dev/cl/495452 mentions this issue: cmd/go: fix percent covered problems with -coverpkg

gopherbot avatar May 17 '23 13:05 gopherbot

Change https://go.dev/cl/495446 mentions this issue: cmd/cover: add new "emit meta file" mode for packages without tests

gopherbot avatar May 17 '23 13:05 gopherbot

Change https://go.dev/cl/495447 mentions this issue: cmd/go: improve handling of no-test packages for coverage

gopherbot avatar May 17 '23 13:05 gopherbot

@thanm The commit d1cb5c06057ef4 cause regression which makes "go tool cover -html=cover.out -o cover.html" return an error if a package called "internal" in root module does not have test files.

This can be reproduced by running "make" on these Go repository: https://github.com/shuLhan/share/

Using go tip 1e690409206f (one commit before d1cb5c06057ef4),

(ins) 1 $ make                                                                                                                       [132/234]
CGO_ENABLED=1 go test -failfast -timeout=1m -race -coverprofile=cover.out ./...
?       github.com/shuLhan/share        [no test files]
...
?       github.com/shuLhan/share/cmd/xtrk       [no test files]
?       github.com/shuLhan/share/internal/bytes [no test files]
?       github.com/shuLhan/share/lib/contact    [no test files]
...
ok      github.com/shuLhan/share/lib/xmlrpc     1.017s  coverage: 64.1% of statements
go tool cover -html=cover.out -o cover.html
go vet ./...
fieldalignment ./...
shadow ./...
revive ./...
mkdir -p _bin/
go build -ldflags "-s -w -X 'github.com/shuLhan/share.Version=v0.49.1-99-g7cf89da5'" -o _bin/ ./cmd/...

Using go tip d1cb5c06057ef4,

(ins) 1 $ make
CGO_ENABLED=1 go test -failfast -timeout=1m -race -coverprofile=cover.out ./...
?       github.com/shuLhan/share        [no test files]
...
        github.com/shuLhan/share/cmd/totp               coverage: 0.0% of statements
        github.com/shuLhan/share/internal/bytes         coverage: 0.0% of statements
        github.com/shuLhan/share/cmd/smtpcli            coverage: 0.0% of statements
...
ok      github.com/shuLhan/share/lib/xmlrpc     1.017s  coverage: 61.5% of statements
go tool cover -html=cover.out -o cover.html
cover: package e/internal/bytes is not in std (/home/ms/opt/go/src/e/internal/bytes)

Using go tip 638e0d36d2a6b,

 23:35:29 ~/go/src/github.com/shuLhan/share
(ins) 1 $ go version
go version devel go1.22-638e0d36d2 Tue Oct 3 16:01:10 2023 +0000 linux/amd64

 23:47:11 ~/go/src/github.com/shuLhan/share
(ins) 1 $ make
CGO_ENABLED=1 go test -failfast -timeout=1m -race -coverprofile=cover.out ./...
?       github.com/shuLhan/share        [no test files]
...
        github.com/shuLhan/share/cmd/xtrk               coverage: 0.0% of statements
        github.com/shuLhan/share/internal/bytes         coverage: 0.0% of statements
        github.com/shuLhan/share/lib/contact            coverage: 0.0% of statements
...
ok      github.com/shuLhan/share/lib/xmlrpc     1.018s  coverage: 61.5% of statements
go tool cover -html=cover.out -o cover.html
cover: no required module provides package thub.com/shuLhan/share/cmd/bcrypt; to add it:
        go get thub.com/shuLhan/share/cmd/bcrypt
make: *** [Makefile:28: test] Error 1

shuLhan avatar Oct 03 '23 16:10 shuLhan

Thanks for the report. Could you possibly open another issue for this? Thanks.

thanm avatar Oct 03 '23 17:10 thanm

Update: I will go ahead and file an issue for this, I think I see what the problem is.

thanm avatar Oct 03 '23 19:10 thanm