Skip to content

testing: add TB.TempDir() string #35998

Description

@bradfitz

It's pretty usual to have tests make temp files.

And it's pretty usual to have tests leak temp files. (#23619 tracks detecting it for Go's own tests, as it keeps coming up)

Doing things properly also gets kinda boilerplatey.

I propose adding a method to testing.TB, *testing.T, and *testing.B something like this:

// TempDir returns a temporary directory for the test to use.
// It is lazily created on first access, and calls t.Fatal if the directory
// creation fails.
// Subsequent calls to t.TempDir return the same directory.
// The directory is automatically cleaned up when the test exits.
func (t *T) TempDir() string {
	t.tempDirOnce.Do(func() {
		t.tempDir, t.tempDirErr = ioutil.TempDir("", t.Name())
		if t.tempDirErr == nil {
			t.Cleanup(func() {
				if err := os.RemoveAll(t.tempDir); err != nil {
					t.Errorf("TempDir RemoveAll cleanup: %v", err)
				}
			})
		}
	})
	if t.tempDirErr != nil {
		t.Fatalf("TempDir: %v", err)
	}
	return t.tempDir
}

Activity

  1. added this to the Proposal milestone on Dec 5, 2019
  2. cherrymui commented on Dec 6, 2019

    @cherrymui
    Member

    Yeah, the artifacts could be useful for debugging. If there is an explicit os.RemoveAll, I can just comment that out when debugging. With the new function I guess I can do the same by digging into the testing package and commenting out that line, but that is more complicated. Maybe add a new helper that keeps the artifacts that can be used for debugging? t.Keep()? t.NoCleanup?

  3. bradfitz commented on Dec 6, 2019

    @bradfitz
    ContributorAuthor

    @cherrymui, no need. You can write:

       defer func() { if t.Failed() { os.Exit(1) } }

    Or since you know it's already failing:

        defer log.Fatalf("check out the failing files in %v", t.TempDir())

    ... then no cleanup will happen due to the os.Exit(1) (which log.Fatalf also does).

  4. zikaeroh commented on Dec 6, 2019

    @zikaeroh
    Contributor

    Why not have a flag which prevents tempdir deletion, something like -test.keeptmp? I'd think that'd be simpler than manually inserting defers (for most uses), and within this proposal the testing framework is already in charge of managing the directory's lifetime.

  5. cherrymui commented on Dec 6, 2019

    @cherrymui
    Member

    @bradfitz Good to know. Thanks!

  6. bradfitz commented on Dec 6, 2019

    @bradfitz
    ContributorAuthor

    @zikaeroh, flag/knob fatigue. Too much crap can be overwhelming. Especially for stuff that's rarely needed. "It might be useful sometimes" is not the bar to add stuff. You also have to consider the fatigue costs for more docs and more -help spew.

  7. dmitshur commented on Dec 6, 2019

    @dmitshur
    Member

    Since I’m not seeing it mentioned, have you considered a more verbose and explicit API variation that returns a cleanup closure?

    Somewhat inspired by context.WithCancel, it may look like:

    func (*T) TempDir() (dir string, cleanup func())

    It would make idiomatic first usage require two extra lines:

    func Test(t *testing.T) {
    	// ...
    	tempDir, cleanup := t.TempDir()
    	defer cleanup()
    	// ... use tempDir
    }

    The advantage would be that it’s easier to temporarily modify not to clean up, more readable that there’s no leak to first-time readers, possible to detect when cleanup isn’t called, but at the cost of some additional verbosity. But maybe two lines isn’t bad compared to the 4+ it requires now.

    Throwing it out there for consideration, but I’m not convinced this would be better.

  8. bradfitz commented on Dec 6, 2019

    @bradfitz
    ContributorAuthor

    @dmitshur, if I wanted to write verbose code, I already can today? 🤷‍♂️

  9. bradfitz commented on Dec 6, 2019

    @bradfitz
    Author
  10. dmitshur commented on Dec 6, 2019

    @dmitshur
  11. bradfitz commented on Dec 6, 2019

    @bradfitz
    Author
  12. cespare commented on Dec 6, 2019

    @cespare
    Contributor

    I wrote some tempdir helpers that we use for a lot of tests at work. A trick I used (which I took from @tv42) is to detect whether the test is being run under go test and, if it is, locate the tempdir inside the go tool's own temp directory. (In this case the deferred td.Remove() does nothing.) Then if you want to inspect the tempdir, you use -work to print the directory and not delete it.

  13. bradfitz commented on Dec 6, 2019

    @bradfitz
    ContributorAuthor

    @cespare, @tv42, cute! (maybe a little fragile and thus gross, though?)

  14. 17 remaining items

  15. gopherbot commented on Apr 3, 2020

    @gopherbot
    Contributor

    Change https://golang.org/cl/226983 mentions this issue: doc: document testing.TB.TempDir in release notes

  16. Deleplace commented on May 5, 2020

    @Deleplace
    Contributor

    What's the guideline for making sure the package testing respects the Go 1 compatibility promise? Adding methods like Cleanup and TempDir to the interface testing.TB breaks custom implementations.

    The test code is not explicitly excluded from the document, and it's unclear to me if we're in the "it may be necessary to add methods to types" case, which seems to be more concerned about structs and embedding.

  17. mvdan commented on May 5, 2020

    @mvdan
    Member

    @Deleplace note that the docs already say TB is the interface common to T and B, and the implementation explicitly forbids any other type from ever implementing TB:

    https://golang.org/src/testing/testing.go?s=19485:20043#L536

    	// A private method to prevent users implementing the
    	// interface and so future additions to it will not
    	// violate Go 1 compatibility.
    	private()
    

    If someone wants to use a subset of the interface that's also implemented by other types, they should declare their own interface with the few methods they are interested in. Backwards compatibility will apply there, because we will only ever add methods to TB, not modify or remove existing ones.

  18. Deleplace commented on May 5, 2020

    @Deleplace
    Contributor

    Thanks Daniel, that's exactly the info I was looking for! Indeed I was not aware of TB.private.

  19. locked and limited conversation to collaborators on May 5, 2021
  20. moved this to Accepted in Proposalson Aug 10, 2022
  21. removed this from Proposalson Oct 19, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions