Skip to content
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

testing: DO NOT UNPIN panic in Init if flag.Parse has already been called #31859

Closed
vearutop opened this issue May 6, 2019 · 52 comments

Comments

@vearutop
Copy link
Contributor

commented May 6, 2019

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

$ go version
go version devel +b41eee4 Sun May 5 15:17:52 2019 +0000 darwin/amd64

Does this issue reproduce with the latest release?

Yes

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

go env Output
$ go env
GOARCH="amd64"
GOBIN=""
GOCACHE="/Users/viacheslav.poturaev/Library/Caches/go-build"
GOENV="/Users/viacheslav.poturaev/Library/Preferences/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GONOPROXY=""
GONOSUMDB=""
GOOS="darwin"
GOPATH="/Users/viacheslav.poturaev/go"
GOPROXY="direct"
GOROOT="/Users/viacheslav.poturaev/sdk/gotip"
GOSUMDB="off"
GOTMPDIR=""
GOTOOLDIR="/Users/viacheslav.poturaev/sdk/gotip/pkg/tool/darwin_amd64"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/Users/viacheslav.poturaev/go/src/github.com/hellofresh/ro-kit/cc/go.mod"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/lb/j0gz8jln36z4frqkrzmkdb0c0000gp/T/go-build279729567=/tmp/go-build -gno-record-gcc-switches -fno-common"

What did you do?

Ran test having custom command line flags, minimal example:

package my_test

import (
	"flag"
	"testing"
)

func init() {
	flag.Parse()
}

func TestSomething(t *testing.T) {}

What did you expect to see?

go1.12 test .
ok  	github.com/bla/cc	0.007s

What did you see instead?

gotip test .
flag provided but not defined: -test.testlogfile
Usage of /var/folders/lb/j0gz8jln36z4frqkrzmkdb0c0000gp/T/go-build200454244/b001/cc.test:
FAIL	 github.com/bla/cc	 0.007s
@bcmills

This comment has been minimized.

Copy link
Member

commented May 6, 2019

What was the behavior with Go 1.12? (Is this a regression?)

@bcmills bcmills changed the title test fails if flag.Parse() called in init cmd/go: `go test` fails if flag.Parse() called in init May 6, 2019

@bcmills bcmills added this to the Go1.13 milestone May 6, 2019

@vearutop

This comment has been minimized.

Copy link
Contributor Author

commented May 6, 2019

I see this issue as a regression, go1.12 did not fail. So I could run tests with additional custom (application relevant) flags.

@vearutop

This comment has been minimized.

Copy link
Contributor Author

commented May 6, 2019

I think this is likely happening due to changed init precedence, that test framework init ran after app-level init.

@bcmills bcmills removed the WaitingForInfo label May 6, 2019

@bcmills bcmills changed the title cmd/go: `go test` fails if flag.Parse() called in init testing: `go test` fails if flag.Parse() called in init May 6, 2019

@bcmills

This comment has been minimized.

Copy link
Member

commented May 6, 2019

Looks like it's probably due to CL 173722, which was intended to address #21051.

@bcmills

This comment has been minimized.

Copy link
Member

commented May 6, 2019

@vearutop

This comment has been minimized.

Copy link
Contributor Author

commented May 6, 2019

OK, so if that is an intended behavior I can fix my code by adding a go1.13 build tagged init() with testing.Init() call inside.

@bcmills

This comment has been minimized.

Copy link
Member

commented May 6, 2019

I don't think that a breaking change was intended. This seems like something that should be fixed before Go 1.13 is released.

@bradfitz

This comment has been minimized.

Copy link
Member

commented May 6, 2019

We knew that fbc6a97 was going to break some people. It's arguably on the fence on whether it's inside the go1compat umbrella (it's half API, half tooling) and we guessed it would affect very few people. (generally more advanced people who could read the docs/release notes & work around)

That said, I haven't looked at this particular issue yet. I'll let @cespare have a look first.

@bcmills

This comment has been minimized.

Copy link
Member

commented May 6, 2019

It seems possible to fix this by putting the call to testing.Init in an earlier init function injected into the test variant of the package, rather than in testing.MainStart.

If I understand correctly, the init functions within a package run in the order in which the source files were presented to the compiler, right?

@cespare

This comment has been minimized.

Copy link
Contributor

commented May 6, 2019

Fun. I'll take a look today.

@cespare

This comment has been minimized.

Copy link
Contributor

commented May 7, 2019

What we (or at least I) intended with #21051 / CL 173722 was:

  • Anyone using go test shouldn't observe any difference or need to explicitly call testing.Init.
  • People using testing outside of go test (notably, testing.Benchmark within a package main) would need to use testing.Init if they want to use test flags.

So @vearutop this means that you shouldn't need to use testing.Init in your example code. In any case, it certainly shouldn't fail with such a mysterious error.

As @bcmills alluded to, the problem is that code which calls flag.Parse during initialization won't get the test flags. The initialization of the user's test package ("ptest"/"pxtest" in the go tool internal parlance) happens before the package main test runner initialization ("testmain") so there's nothing we can do in the generated testmain code to fix this.

The workaround that makes sense to me, which is what I think @bcmills is suggesting, is that we can insert a bit of code into the ptest/pxtest packages before we build them:

var _ = func() bool {
	testing.Init()
	return true
}()

(Putting this in a variable declaration expression means that it happens before flag.Parse is called in a variable initialization as well as an init function. Calling flag.Parse as part of variable declaration is ill-advised, of course, but it does work today.)

I think this is pretty straightforward. I'll send a CL soon.

If I understand correctly, the init functions within a package run in the order in which the source files were presented to the compiler, right?

Yeah, and same for variable declarations, modulo initialization dependencies. I'll ensure that the synthesized file is passed to the compiler first.

@gopherbot

This comment has been minimized.

Copy link

commented May 9, 2019

Change https://golang.org/cl/176098 mentions this issue: cmd/go: move automatic testing.Init call into generated test code

@vearutop

This comment has been minimized.

Copy link
Contributor Author

commented May 15, 2019

@cespare I'm afraid this is still broken.

Given somewhere/init.go:

package somewhere

import (
	"flag"
)

var StopOnFailure bool

func init() {
	flag.BoolVar(&StopOnFailure, "stop-on-failure", false,
		"Stop processing on first failed scenario.")

	flag.Parse()
}

And mytest/my_test.go:

package my_test

import (
	"fmt"
	"testing"

	"path/to/somewhere"
)

func TestSomething(t *testing.T) {
	_ = somewhere.StopOnFailure
}

func BenchmarkSomething(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		_ = fmt.Sprintf("%d", i)
	}
}

gotip (go version devel +1e3ffb0c90 Tue May 14 22:30:48 2019 +0000 darwin/amd64) fails:

gotip test ./mytest/...
flag provided but not defined: -test.testlogfile
Usage of /var/folders/lb/j0gz8jln36z4frqkrzmkdb0c0000gp/T/go-build623057355/b001/mytest.test:
  -stop-on-failure
    	Stop processing on first failed scenario.
FAIL path/to/mytest	0.005s
FAIL
gotip test -bench=. ./mytest/...
flag provided but not defined: -test.bench
Usage of /var/folders/lb/j0gz8jln36z4frqkrzmkdb0c0000gp/T/go-build638505735/b001/mytest.test:
  -stop-on-failure
    	Stop processing on first failed scenario.
exit status 2
FAIL	 path/to/mytest	0.005s
FAIL
@cespare

This comment has been minimized.

Copy link
Contributor

commented May 15, 2019

@vearutop thanks for bringing it to my attention.

It turns out that this particular repro code relies on undefined behavior: it assumes that the testing package is initialized before the somewhere package. There's nothing in the spec that says it will be. And indeed, it so happens that on tip there are some initialization changes that already broke this code before any of my testing changes landed; see https://golang.org/cl/161337, https://golang.org/cl/170318, and related discussion over on #31636. If I run the repro with Go at 8515d9c, which is this commit where CL 170318 went in and before any of my testing changes, I get a failure:

~/apps/godev/bin/go test
flag provided but not defined: -test.timeout
Usage of /tmp/go-build059416027/b001/aaa.test:
  -stop-on-failure
        Stop processing on first failed scenario.
exit status 2
FAIL    aaa     0.001s

(And if I run with -race, it sometimes fails and sometimes succeeds.)

However, this all kind of doesn't matter because you could fix your repro code by making somewhere import testing, perhaps just by adding _ "testing" to the imports. Then you're guaranteed that testing is initialized first, and so it works up until my changes break it. So this is a problem.

To state it more generally, the "testinginit" workaround I did over in https://golang.org/cl/176098 ensures that testing.Init is called before the package under test is initialized. It does not ensure that testing.Init is called before that package's dependencies are initialized, so if they call flag.Parse during initialization they may not see the testing flags.

I think that a solution is to move the testinginit workaround from the package under test into the testing package. That is, go test would insert this file into the testing package:

package testing
func init() { Init() }

(This would be very simple with a test build tag, but we decided in #21360 not to do that.)

@bcmills thoughts?

@cespare cespare reopened this May 15, 2019

@vearutop

This comment has been minimized.

Copy link
Contributor Author

commented May 15, 2019

Not sure to understand:

And indeed, it so happens that in Go 1.12 there were some initialization changes that already broke this code

With go1.12.5 that example case never fails for me.

@cespare

This comment has been minimized.

Copy link
Contributor

commented May 15, 2019

Sorry, I meant to say that there are some changes on tip (that will be in Go 1.13). I updated my comment.

@vearutop

This comment has been minimized.

Copy link
Contributor Author

commented May 15, 2019

And also I tried adding _ "testing" to the imports:

package somewhere

import (
	"flag"
	_ "testing"
)

var StopOnFailure bool

func init() {
	flag.BoolVar(&StopOnFailure, "stop-on-failure", false,
		"Stop processing on first failed scenario.")

	flag.Parse()
}

but it did not help, same failure on gotip.

@cespare

This comment has been minimized.

Copy link
Contributor

commented May 15, 2019

Right, I'm saying that without the _ "testing" import your repro code is buggy due to depending on undefined behavior, and that bugginess is exposed by the init ordering changes unrelated to my testing changes that are on tip.

By adding the _ "testing" import, your repro code no longer depends on undefined behavior, but it was broken by my change.

So:

  • You need to fix your code if it depends on testing being initialized first without actually importing it
  • We need to fix Go so that any package (not just the package under test) sees that the test flags have been registered when running as a test
@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

commented May 15, 2019

It's hard to understand why code would call flag.Init in an init function that is not in the main package. That will ignore flags defined in other packages that are initialized later. I think I would want to see a clear explanation as to why we need to cater to that case at all, since it seems broken by design.

@robpike robpike unpinned this issue Sep 5, 2019

@katiehockman katiehockman pinned this issue Sep 5, 2019

@robpike robpike changed the title testing: panic in Init if flag.Parse has already been called testing: panic in Init if flag.Parse has already been called DO NOT UNPIN Sep 5, 2019

@robpike robpike changed the title testing: panic in Init if flag.Parse has already been called DO NOT UNPIN testing: DO NOT UNPIN panic in Init if flag.Parse has already been called Sep 5, 2019

@martisch martisch unpinned this issue Sep 6, 2019

@ALTree ALTree pinned this issue Sep 6, 2019

markus-wa added a commit to markus-wa/demoinfocs-golang that referenced this issue Sep 7, 2019
markus-wa added a commit to markus-wa/demoinfocs-golang that referenced this issue Sep 7, 2019
tzdybal added a commit to etclabscore/multi-geth that referenced this issue Sep 12, 2019

@benshi001 benshi001 unpinned this issue Sep 16, 2019

@tsuna

This comment has been minimized.

Copy link
Contributor

commented Sep 17, 2019

In case anyone else lands here and is looking for a simple answer:

With Go 1.13 you can't do this anymore:

// foo_test.go
func init() {
	if !flag.Parsed() {
		flag.Parse()
	}
}

You must do this instead:

// foo_test.go
func TestMain(m *testing.M) {
	flag.Parse()
	os.Exit(m.Run())
}
Observe-Jenkins pushed a commit to observeinc/gosnowflake that referenced this issue Sep 17, 2019
Fix tests for go1.13
See golang/go#31859 (comment)

Also increase timeouts in TestUnitEncodeCertIDGood as the aggressive
timeouts there make the test flaky.

Change-Id: Ibb749457f6db39b69641594a03718d301b8b7a59
tsuna added a commit to observeinc/gosnowflake that referenced this issue Sep 17, 2019
Fix tests for go1.13
See golang/go#31859 (comment)

Also increase timeouts in TestUnitEncodeCertIDGood as the aggressive
timeouts there make the test flaky.

Change-Id: Ibb749457f6db39b69641594a03718d301b8b7a59
yperbasis added a commit to ledgerwatch/turbo-geth that referenced this issue Sep 18, 2019
ChTimTsubasa added a commit to snowflakedb/gosnowflake that referenced this issue Sep 18, 2019
Fix tests for go1.13
See golang/go#31859 (comment)

Also increase timeouts in TestUnitEncodeCertIDGood as the aggressive
timeouts there make the test flaky.

Change-Id: Ibb749457f6db39b69641594a03718d301b8b7a59
pitakill added a commit to pitakill/azure_resources that referenced this issue Sep 19, 2019
Update the GetAllResources method to return a slice of resources and an
error

1. Why is this change necessary?
Because if an error occurs there is no way to handle it in the log.Fatal
handle of the error

2. How does it address the issue?
Returns the error to the caller, so is easy to handle the error

3. What side effects does this change have?
None in the use of the project, but does in the testing. Accordingly
with: golang/go#31859 we need a
`testing.Init()` in the `init` function of the testing file

4. Test
`make test ARGS="-client edae63d9-3f66-47de-860d-8b23b55c8544 -tenant c8cd0425-e7b7-4f3d-9215-7e5fa3f439e8 -secret 1016e85d-3f23-4953-97a1-2cec4dccb0a4"`
pitakill added a commit to pitakill/azure_resources that referenced this issue Sep 19, 2019
Update the GetAllByGroupName method to return a slice of resources an…
…d an

error

1. Why is this change necessary?
Because if an error occurs there is no way to handle it in the log.Fatal
handle of the error

2. How does it address the issue?
Returns the error to the caller, so is easy to handle the error

3. What side effects does this change have?
None in the use of the project, but does in the testing. Accordingly
with: golang/go#31859 we need a
`testing.Init()` in the `init` function of the testing file

4. Test
`make test ARGS="-client edae63d9-3f66-47de-860d-8b23b55c8544 -tenant c8cd0425-e7b7-4f3d-9215-7e5fa3f439e8 -secret 1016e85d-3f23-4953-97a1-2cec4dccb0a4"`
pitakill added a commit to pitakill/azure_resources that referenced this issue Sep 19, 2019
Update the GetAllByGroupName method
1. Why is this change necessary?
Because if an error occurs there is no way to handle it in the log.Fatal
handle of the error

2. How does it address the issue?
Returns the error to the caller, so is easy to handle the error

3. What side effects does this change have?
None in the use of the project, but does in the testing. Accordingly
with: golang/go#31859 we need a
`testing.Init()` in the `init` function of the testing file

4. Test
`make test ARGS="-client edae63d9-3f66-47de-860d-8b23b55c8544 -tenant c8cd0425-e7b7-4f3d-9215-7e5fa3f439e8 -secret 1016e85d-3f23-4953-97a1-2cec4dccb0a4"`
pitakill added a commit to pitakill/azure_resources that referenced this issue Sep 19, 2019
Update the GetAllByGroupName method
1. Why is this change necessary?
Because if an error occurs there is no way to handle it in the log.Fatal
handle of the error

2. How does it address the issue?
Returns the error to the caller, so is easy to handle the error

3. What side effects does this change have?
None in the use of the project, but does in the testing. Accordingly
with: golang/go#31859 we need a
`testing.Init()` in the `init` function of the testing file

4. Test
`make test ARGS="-client edae63d9-3f66-47de-860d-8b23b55c8544 -tenant c8cd0425-e7b7-4f3d-9215-7e5fa3f439e8 -secret 1016e85d-3f23-4953-97a1-2cec4dccb0a4"`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
You can’t perform that action at this time.