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

util: TempFile improvements #169

Merged
merged 7 commits into from
Aug 29, 2024
Merged

util: TempFile improvements #169

merged 7 commits into from
Aug 29, 2024

Conversation

brandondyck
Copy link
Contributor

This implements more of @ccoVeille's suggestions on #168:

  • Path is now Dir and is better documented.
  • The tests try multiple options at once.
  • There is a new test to ensure TempFile fails if dir does not exist. I reorganized TempFile to make it easier to test failures.
  • The 'Dir' tests now ensure that existing files don't get deleted.

util/tempfile.go Outdated Show resolved Hide resolved
util/tempfile.go Outdated Show resolved Hide resolved
util/tempfile.go Outdated
Comment on lines 92 to 95
// tempFile returns errors instead of relying upon T to stop execution, for ease
// of testing TempFile.
func tempFile(helper func(), tempDir func() string, settings ...TempFileSetting) (path string, err error) {
helper()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I understand the point of testing, so returning an error is easier to test

I'm unsure about the arguments pass T methods

Couldn't you make it work with this?

Suggested change
// tempFile returns errors instead of relying upon T to stop execution, for ease
// of testing TempFile.
func tempFile(helper func(), tempDir func() string, settings ...TempFileSetting) (path string, err error) {
helper()
// tempFile returns errors instead of relying upon T to stop execution, for ease
// of testing TempFile.
func tempFile(t.T, settings ...TempFileSetting) (path string, err error) {
t.Helper()

Then call t.Tempdir where needed

I have never faced a code that needed to do the kind of thing you did.

Also your failureTracker struct seems more than enough.

The only problem I can see is when the code could call fatal, but you rewrote to return an error.

You could use a panic in failureTracker and catch it in the tests. It would replicate the t.FailNow that exits the current scope.

Anyway, these are random ideas that need to be thought about, and tried.

So I would understand if you reply me: nah, cannot do it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I see nothing testing this method, you are only testing the TempFile one right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried detecting failures with panic and recover; one downside is that accidental panics get noisy stack traces that way. Before I made this change, I was working up a way to run it in a goroutine and shut it down with runtime.Goexit like testing.T methods do. Both methods require wrapping the code under test in a closure, and overall rather more complicated machinery than what I did here.

I passed the functions instead of T to remove the capability to fail from inside tempDir. I could easily have defined a supertype of T and passed one of those, instead; it doesn't really matter to me either way.

I only test this method through TempFile; since that's its only caller and does almost nothing on its own, I think it's reasonable to treat them from the outside as a single function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just updated tempFile to take an interface argument with a subset of T's methods.

util/tempfile.go Outdated Show resolved Hide resolved
util/tempfile_test.go Outdated Show resolved Hide resolved
util/tempfile_test.go Show resolved Hide resolved
t.Fatalf("%s: %v", "TempFile", err)
file, err := os.CreateTemp(*allSettings.dir, allSettings.namePattern)
if errors.Is(err, fs.ErrNotExist) {
return "", errors.New("directory does not exist")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually returning a sentinel could be useful. This way people could catch it

Another solution is to wrap the existing error, allowing people to catch the fs.ErrNotExist, if they have to.

Suggested change
return "", errors.New("directory does not exist")
return "", fmt.Errorf("directory does not exist: %w", err)

But these comments might be irrelevant, as you are building a test helper and not a function that people will call and will have to handle its error.

Copy link
Owner

@shoenig shoenig left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Who would've thought there could be so much nuance in opening a temp file 😅

@shoenig shoenig merged commit ebd109b into shoenig:main Aug 29, 2024
6 checks passed
@shoenig
Copy link
Owner

shoenig commented Aug 29, 2024

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants