-
Notifications
You must be signed in to change notification settings - Fork 17.8k
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: add func Testing() bool #52600
Comments
|
This may be a dup of #14668. |
It is. New information in this ticket:
|
I'm trying to understand why you would want to create untestable code. |
This proposal has been added to the active column of the proposals project |
The simplified situation that caused me to open the ticket:
I'd like to ensure that It's a pretty non-fun situation when an urgent change in environment forces the person who needs to do it to chase all the implicit assumptions in tests that were violated. |
Another possible use case: Excluding code used for production resiliency that hinders local testing and debugging. The specific example I've seen is disabling the use of |
I just want to note that you could get the same effect by running //go:build !test
func init() { log.Fatal("running a test without -tags test") } So it doesn't seem that hard for people to get this functionality already. The question is whether this is a common enough need to add to the standard tools. Note in particular that actually using the build tag will tend to make the build cache less effective, so it's not something we necessarily want to encourage. |
I have mentioned it in the bug description:
|
For that kind of use case, why not make the debugging-hostile behavior opt-in instead of opt-out? |
The situation I've seen is that the
I'm not sure it's the best approach. Right now I am only trying to report on examples I've seen as additional data points. |
One situation where I've wanted something like this is to support test hooks in the An alternative solution to my problem is that the compiler could detect when an unexported package global is never written to and constant-propagate the zero value of that global. It's not clear to me that that optimization is generally worthwhile, though. |
I would expect that optimization to be generally worthwhile: I've seen that pattern in a lot of packages, both to inject test-only helpers and to reconfigure defaults (for example, shortening timeouts so that a test can actually provoke them). Even better would be to also apply that optimization at link-time for exported package globals. 😁 |
@rsc and I chatted a bit about this and realized there are two somewhat different possible designs:
I lean toward option 1, but I can see arguments either way.
I'd been thinking this optimization would only work for things that are zero-initialized and never written to, but I suppose it would also work with any other constant initializer. We just need to be able to prove that it cannot be read before it's "written to" and that it's never written to again. So it's less limited than I was thinking. |
This will not solve the problem that caused me to open the ticket, described here: #52600 (comment) - files in This will definitely require a lot of explicit documentation about a surprising behaviour of this specific build flag. |
Talked about this with @aclements and others at proposal review today. We generally agree that the package-local option is off the table. It's too different and not worth the hassle. It may be that for runtime testing hooks, the best we can do is a 100% predictable branch For the more general "global" testing build tag, the effect would be to have to compile packages built while testing differently from packages built for production. That's not hard - we already do it for go test -race, for example. It would cache automatically thanks to the build cache, and there are ways to reduce the impact further. So efficiency is not a concern. The big concern is the impact on APIs and tooling. Adding this build tag would mean that any API compatibility checks would need to treat test and non-test builds as completely separate configurations that might have completely different APIs, just as Windows and Linux builds are. It would mean being able to define exported API that is only available in a test binary (test-only code), and it would also mean being able to define exported API that is only available outside a test binary (untestable code). It is not at all clear to us that this is a feature that makes sense for the Go ecosystem. The possibility of defining test-only or non-test-only API introduces significant complexity that both tooling and developers will have to understand. That cost would need to be matched by a very large benefit, and I don't see it here. A while back we arranged that programs can import "testing" without causing any side-effects like flag registration. Perhaps it would suffice to add a new function |
As an aside: by itself it might not be a bad idea: exposing hooks for testing only to unit tests might be useful.
This is a great solution for my problem. With
|
I agree with your concerns about the build tag approach, @rsc. I think a |
Retitled. Sounds like people are in favor of Have all concerns about this proposal been addressed? |
Based on the discussion above, this proposal seems like a likely accept. |
This may be a silly question that has an obvious compiler answer, but why have |
It's not a silly question. A function is easier to implement. A constant would require either something magical, or it would require build tags which is what we are trying to avoid. With a function we can just have an internal variable that is set to true by the test's main function, which is generated by |
The natural implementation of this is going to produce a |
@ianlancetaylor Alas no, then it's no better than the existing buggy workaround of checking presence of flag The obvious case where it will fail, to follow-up an example in #52600 (comment):
|
Those are two very different predicates: one answers “is the current binary a test?”, whereas the other answers “is the current binary running tests?”. That makes a big difference for programs like It's not obvious to me which of those definitions is more useful in practice, but we should absolutely be very explicit about which one it is that we're reporting. FWIW, based on @rsc's description I had assumed that the flag would report the value of a variable set at link time, much like how we inject the data for (In fact, I had sort of assumed that |
I'm interested in getting #33976 fixed for other reasons, but also, the example I shared in #52600 (comment) is from a project that currently still builds in GOPATH mode, although that is hopefully only a short term condition. For the case of tests re-executing their own binary it seems straightforward for the parent process to set an environment variable for the child process to know it's special. That's the common pattern I've seen for those types of tests anyway. |
Change https://go.dev/cl/475496 mentions this issue: |
Hey there, I hope you don't mind me chiming in a bit late to the conversation! 😊 For instance, let's say you're developing a custom logger and want to use STDOUT as the default output. I was wondering if it might be more efficient to create a separate I'd really love to hear your thoughts on this approach! 😊 |
@adamluzsi You're right, there are side-effects of importing Any program that uses @ianlancetaylor Any chance we could address it before it ends up in a release? |
Simply importing the "testing" package will not define any flags. The flags are only defined if something calls I see that it's possible to introduce a dependency on the testing package via something like logging, but the testing package is not all that large. And the linker will discard unused functions, so if the program only calls In any case, if you do think we should do something about this, please open a new issue and mark it as a release blocker for 1.21. Thanks. |
Right, the flags situation is fixed in https://go-review.googlesource.com/c/go/+/173722 (I was looking at old Go source code accidentally), so importing Incidentally this also means that the brittle workarounds applied due to lack of Testing() function are also broken due to this change, which is a good thing, as it will force everyone to use @adamluzsi So this is fine? |
Yes, of course, and thank you so much for taking your time and helping me understand. |
Identifies test using build tags until golang/go#52600 becomes available.
Identifies test using build tags until golang/go#52600 becomes available.
How does the behaviour break @dottedmag? I think this is important to know before this break occurs. |
@ladydascalie Such code (checking |
Started using this function in a program that didn't previously import |
The testing package does import a lot of other packages. |
This is a proposal to add a build tag during compilation of unit tests.
Background & motivation
Sometimes it is handy to have some code not available for unit testing, e.g. some volatile data that should not be relied upon in tests. Code reviews help to catch accidental or intentional misuses, but it's exhausting to check that no test code refers to this data directly or indirectly.
There are workarounds that allow distinguishing if the code is running tests, but they are inadequate:
init()
clauses.testing
package may produce false positives, slow, and OS-dependent.go test
is not the right tool to run tests, and creates toil for integrating with IDEs.Proposal
Add a build tag that is available during compilation of unit tests. This will trivially allow marking some data/functions as "not available in unit tests".
The text was updated successfully, but these errors were encountered: