Skip to content

proposal: testing: add testing.T.Volatile to exempt test functions from result caching #67568

@Merovius

Description

@Merovius

Proposal Details

While generally, a test function should be self-contained and isolated to prevent flakes, there are cases where a test needs to interact with state not reflected in the build artifact. For example an integration test, that tests interaction with a CLI tool or relies on a separate database server. For such tests, the current behavior of the Go test cache is counterproductive: It might conclude that since the code itself has not changed and re-use a cached test run, even if the external resource has changed and thus the test needs to be re-run.

It is possible to pass -count=1 to force a re-run, but this is flawed as well, as it applies globally. If you want to run all tests in a large project and run go test -count=1 ./..., that will re-run all tests, even though most of them might be perfectly cacheable. There is a reason, after all, that the test cache was introduced.

So it might be useful to have a way to indicate that one specific test function depends on external state, that makes it non-cacheable.

Thus, I propose to add a method to *testing.T to exempt a function from result caching:

// Volatile marks the current test function as exempt from result caching.
// A test function marked as volatile is re-run, even if none of the Go source it uses has been modified.
func (t *T) Volatile()

The call would just be recorded in the *T and after the test function is finished, its result would not be stored in the cache if Volatile was called. Thus no other tests are affected by use of Volatile.

One drawback of this mechanism is that it again runs all volatile tests on every invocation of go test, even if the external resources did not change. Especially if Volatile is overused, that would again increase test running times. Theoretically, it would be possible to prevent that by allowing the test function to include a token that encapsulates the external state (e.g. by hashing the version of the database server or the contents of the called binary), though this might be too finicky, ultimately:

// Volatile controls results-caching for the current test function.
// The given tag is persisted with the test result in the test cache. If a future run calls
// Volatile with the same tag, it behaves like `Skip`, except that the cached test result is output.
func (t *T) Volatile(tag uint64)

I'm not terribly sure this is a good idea. It came up in a discussion on Mastodon and it doesn't seem like a completely unreasonable compromise. So I wanted to propose it formally, to gather some feedback.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions