-
Notifications
You must be signed in to change notification settings - Fork 17.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
cmd/cover: regression in 1.20, cover tool no longer includes packages without tests #58770
Comments
Thanks for the report; I will take a look. |
I took a look at this issue. What's happening here has to do with how the coverage machinery is handling "-coverpkg", specifically regarding imports. You haven't supplied any specific code examples, but I will assume that this example will do for the discussion: https://go.dev/play/p/253gWw8ItV5 Code from the link above:
We have three packages: "a", "b" and "main", where main imports "a" and "b" but neither "a" nor "b" imports anything. If you run this with Go 1.20 at the top level (where the "go.mod" is) you get:
whereas in Go 1.19 you would get
Some background on how "go test" works: when you kick off a run of "go test -cover" in package "a", the Go command builds the test binary by combining the code from the package of interest (package 'a' in this case) with a test harness (`_testmain.go') that includes the test code, the testing package, and a synthetically generated "main" routine. In Go 1.19 and prior versions, when given a command like
When building the test binary for "a", the Go command would construct a
and would register the coverage counter variables for all of these packages with the testing runtime. Forcing imports of "b" and "main" into the package "a" test binary is strange, since package "a" has no imports, and in particular importing "main" into "a" is especially weird given that normally it is the other way around. The forced imports does provide a mechanism for computing the correct result (here 33% of all statements in "./...") for package "a", but it also creates lots of confusion for users, who are surprise when various init functions in the "./..." packages that take actions that perturb the semantics of the run when testing "a". Here are some of the bugs that users have filed regarding this quirk: #21283 There are others as well (variants of this problem have been reported as bugs by Go users internal to Google). For this reason, in the coverage redesign rolled out in Go 1.20 we did away with the forced import mechanism. Getting back to the bug at hand: you are quite correct however that this is a regression, we should be reporting the 33% number for package "a" and not 100%. Rather than going back to forced imports, I believe that the best way to handle this is to have the Go command delay reporting of coverage numbers in this case (when "-coverpkg" is used). Internally the new implementation collects raw profiles for each tested packed ("a" and "main") into a separate directory, so if we process the directories in combinatoin at the end of all test runs it should be possible to get the right result. In the mean time, as a workaround you can restore the 1.19 behavior by setting adding this to your environment:
This will turn off the new implementation and revert back to the 1.19 scheme. Let me know if this works for you until the bug can be fixed. |
thanks for the clear explanation @thanm - I was about to submit a related issue but came across this and figured I'd provide an additional data point. I've put together a reproducer here (which you can clone and run
when I run:
I always get 100% coverage on both 1.19 and 1.20 When I run:
I get 50% coverage on 1.19 and 100% coverage on 1.20 That I'm primarily sharing this here to call out that there is an opportunity in 1.20 not simply to fix the regression that's been identified but also to improve on the behavior in 1.19. I'm also trying to avoid opening a near-duplicate issue but would be happy to do so if y'all would prefer. |
Thanks for that addition @onsi . New issue: probably not needed, but up to you. Regarding fixing things in 1.19, it would be nice, but in general the Golang policy on back-porting fixes is fairly conservative/restrictive. Unless a change is to fix a compiler error or runtime crash or some other serious problem (where there are no workarounds), the release team tends to be discourage this sort of back-port. We'll see. |
oh for sure - i wouldn't backport it to 1.19 either. just noting that previous behavior wasn't quite working here either. |
Change https://go.dev/cl/495452 mentions this issue: |
Change https://go.dev/cl/495446 mentions this issue: |
Change https://go.dev/cl/495447 mentions this issue: |
An update on this issue: I have a CL stack (rooted at CL 495452) that should resolve the problem. There is one area where the coverage percentage numbers are going to be slightly different for the "go test -coverpkg=./..." case, which has to do with how init functions are handled. In the example below, we have a top level dir containing five package subdirectories, "a", "b", "main", "n", and "x". The first three packages have tests and have a user-written init function. If you count up the total number of statements, there are a total of 9 (2 in "a", 2 in "b", 1 in "x", and 4 in "main").
Here is the output from a test run with my fix:
Note the coverage percentage for package "a": 22%, or 2 out of 9 statements total. With Go 1.19, the coverage number is 44%, or 4 out of 9 (this is due to the artifact of force-importing all covered packages into testmain for each test). Also worth noting is that for packages like "x", which have statements but no tests, we don't link or run a test binary for the package but we do report a coverage percentage for them (this is different from a previous attempt in CL 115095 (checked in and then rreverted). |
I think that's totally reasonable. Might be confusing for some, but the results I value most are the total coverages after all the tests have ran since I often have code exercising other packages in opaque and distant ways. Looking great! Thanks so much for taking a look at this @thanm! Really appreciate your work. 💯 |
A dirty workaround is to add an empty file with the name |
Introduce a new mode of execution for instrumenting packages that have no test files. Instead of just skipping packages with no test files (during "go test -cover" runs), the go command will invoke cmd/cover on the package passing in an option in the config file indicating that it should emit a coverage meta-data file directly for the package (if the package has no functions, an empty file is emitted). Note that this CL doesn't actually wire up this functionality in the Go command, that will come in a later patch. Updates #27261. Updates #58770 Updates #24570. Cq-Include-Trybots: luci.golang.try:gotip-linux-amd64-longtest,gotip-windows-amd64-longtest Change-Id: I01e8a3edb62441698c7246596e4bacbd966591c3 Reviewed-on: https://go-review.googlesource.com/c/go/+/495446 Reviewed-by: Bryan Mills <bcmills@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This patch improves the way the go command handles coverage testing of packages that have functions but don't have any test files. Up to this point if you ran "go test -cover" on such a package, you would see: ? mymod/mypack [no test files] While "no test files" is true, it is also very unhelpful; if the package contains functions, it would be better instead to capture the fact that these functions are not executed when "go test -cover" is run on the package. With this patch, for the same no-test package "go test -cover" will output: mymod/mypack coverage: 0.0% of statements The inclusion of such packages in coverage reporting also extends to "-coverprofile" as well (we'll see entries for the "mypack" functions in this case. Note that if a package has no functions at all, then we'll still fall back to reporting "no test files" in this case; it doesn't make sense to report "0.0% statements covered" if there are no statements. Updates #27261. Updates #58770. Updates #18909. Fixes #24570. Cq-Include-Trybots: luci.golang.try:gotip-linux-amd64-longtest,gotip-windows-amd64-longtest Change-Id: I8e916425f4f2beec65861df78265e93db5ce001a Reviewed-on: https://go-review.googlesource.com/c/go/+/495447 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Bryan Mills <bcmills@google.com>
What version of Go are you using (
go version
)?Does this issue reproduce with the latest release?
Yes
What operating system and processor architecture are you using (
go env
)?go env
OutputWhat did you do?
I ran our unit tests under both go1.19 and go1.20 and observed different coverage results.
To calculate coverage, we have this makefile testing target:
What did you expect to see?
I expected the coverage reporting to stay the same between releases, assuming no code changes.
What did you see instead?
The reported coverage falsely increased with go1.20 because it's no longer tracking coverage for packages that are missing tests.
As a workaround, I've put TODO tests in every package missing tests, but can't count on that manual practice to catch packages not being covered that I want to include in the coverage report.
The text was updated successfully, but these errors were encountered: