Skip to content

Commit

Permalink
util: improve TempFile tests
Browse files Browse the repository at this point in the history
  • Loading branch information
brandondyck committed Aug 27, 2024
1 parent 1c2bf2b commit e036a27
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 53 deletions.
46 changes: 30 additions & 16 deletions util/tempfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
package util

import (
"errors"
"fmt"
"io/fs"
"os"
)
Expand Down Expand Up @@ -74,6 +76,23 @@ func Dir(dir string) TempFileSetting {
// after the test is completed.
func TempFile(t T, settings ...TempFileSetting) (path string) {
t.Helper()
path, err := tempFile(t.Helper, t.TempDir, settings...)
t.Cleanup(func() {
err := os.Remove(path)
if err != nil {
t.Fatalf("failed to clean up temp file: %s", path)
}
})
if err != nil {
t.Fatalf("%v", err)
}
return path
}

// 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()
var allSettings TempFileSettings
for _, setting := range settings {
setting(&allSettings)
Expand All @@ -84,37 +103,32 @@ func TempFile(t T, settings ...TempFileSetting) (path string) {
}
if allSettings.dir == nil {
allSettings.dir = new(string)
*allSettings.dir = t.TempDir()
*allSettings.dir = tempDir()
}

var err error
crash := func(t T) {
t.Helper()
t.Fatalf("%s: %v", "TempFile", err)
wrap := func(err error) error {
return fmt.Errorf("TempFile: %w", err)
}
file, err := os.CreateTemp(*allSettings.dir, allSettings.namePattern)
if errors.Is(err, fs.ErrNotExist) {
return "", fmt.Errorf("TempFile: directory does not exist")
}
if err != nil {
crash(t)
return "", wrap(err)
}
path = file.Name()
t.Cleanup(func() {
err := os.Remove(path)
if err != nil {
t.Fatalf("failed to clean up temp file: %s", path)
}
})
_, err = file.Write(allSettings.data)
if err != nil {
file.Close()
crash(t)
return path, wrap(err)
}
err = file.Close()
if err != nil {
crash(t)
return path, wrap(err)
}
err = os.Chmod(path, *allSettings.mode)
if err != nil {
crash(t)
return path, wrap(err)
}
return file.Name()
return file.Name(), nil
}
161 changes: 124 additions & 37 deletions util/tempfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ package util_test

import (
"bytes"
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"slices"
"strings"
"testing"

Expand Down Expand Up @@ -48,6 +51,53 @@ func (t *helperTracker) Cleanup(f func()) {
t.t.Cleanup(f)
}

func trackFailure(t util.T) *failureTracker {
return &failureTracker{t: t}
}

type failureTracker struct {
failed bool
log bytes.Buffer
t util.T
}

func (t *failureTracker) TempDir() string {
t.t.Helper()
return t.t.TempDir()
}

func (t *failureTracker) Helper() {
t.t.Helper()
}

func (t *failureTracker) Errorf(s string, args ...any) {
t.t.Helper()
t.failed = true
fmt.Fprintf(&t.log, s+"\n", args...)
}

func (t *failureTracker) Fatalf(s string, args ...any) {
t.t.Helper()
t.failed = true
fmt.Fprintf(&t.log, s+"\n", args...)
}

func (t *failureTracker) Cleanup(f func()) {
t.t.Helper()
t.t.Cleanup(f)
}

func (t *failureTracker) AssertFailedWith(msg string) {
t.t.Helper()
if !t.failed {
t.t.Fatalf("expected test to fail with message %q", msg)
}
strlog := t.log.String()
if !strings.Contains(strlog, msg) {
t.t.Fatalf("expected test to fail with message %q\ngot message %q", msg, strlog)
}
}

func TestTempFile(t *testing.T) {
t.Run("creates a read/write temp file by default", func(t *testing.T) {
th := trackHelper(t)
Expand All @@ -64,38 +114,44 @@ func TestTempFile(t *testing.T) {
t.Fatalf("expected at least u+rw permission, got %03o", mode)
}
})
t.Run("sets a custom file mode", func(t *testing.T) {

t.Run("using multiple options", func(t *testing.T) {
var expectedMode fs.FileMode = 0444
path := util.TempFile(t, util.Mode(expectedMode))
info, err := os.Stat(path)
if err != nil {
t.Fatalf("failed to stat temp file: %v", err)
}
actualMode := info.Mode()
if expectedMode != actualMode {
t.Fatalf("file has wrong mode\nexpected %03o\ngot %03o", expectedMode, actualMode)
}
})
t.Run("sets a name pattern", func(t *testing.T) {
expectedData := "important data"
prefix := "harvey-"
pattern := prefix + "*"
path := util.TempFile(t, util.Pattern(pattern))
if !strings.Contains(path, prefix) {
t.Fatalf("filename does not match pattern\nexpected to contain %s\ngot %s", prefix, path)
}
})
t.Run("sets string data", func(t *testing.T) {
expectedData := "important data"
path := util.TempFile(t, util.StringData(expectedData))
actualData, err := os.ReadFile(path)
if err != nil {
t.Fatalf("failed to read temp file: %v", err)
}
if expectedData != string(actualData) {
t.Fatalf("temp file contains wrong data\nexpected %q\ngot %q", expectedData, string(actualData))
}
path := util.TempFile(t,
util.Mode(expectedMode),
util.Pattern(pattern),
util.StringData(expectedData))

t.Run("Mode sets a custom file mode", func(t *testing.T) {
info, err := os.Stat(path)
if err != nil {
t.Fatalf("failed to stat temp file: %v", err)
}
actualMode := info.Mode()
if expectedMode != actualMode {
t.Fatalf("file has wrong mode\nexpected %03o\ngot %03o", expectedMode, actualMode)
}
})
t.Run("sets a name pattern", func(t *testing.T) {
if !strings.Contains(path, prefix) {
t.Fatalf("filename does not match pattern\nexpected to contain %s\ngot %s", prefix, path)
}
})
t.Run("sets string data", func(t *testing.T) {
actualData, err := os.ReadFile(path)
if err != nil {
t.Fatalf("failed to read temp file: %v", err)
}
if expectedData != string(actualData) {
t.Fatalf("temp file contains wrong data\nexpected %q\ngot %q", expectedData, string(actualData))
}
})
})
t.Run("sets binary data", func(t *testing.T) {

t.Run("ByteData sets binary data", func(t *testing.T) {
expectedData := []byte("important data")
path := util.TempFile(t, util.ByteData(expectedData))
actualData, err := os.ReadFile(path)
Expand All @@ -107,27 +163,58 @@ func TestTempFile(t *testing.T) {
}
})

t.Run("file is deleted after test", func(t *testing.T) {
t.Run("clean up file (and nothing else) in custom dir", func(t *testing.T) {
dir := t.TempDir()
var path string
existing, err := os.CreateTemp(dir, "")
if err != nil {
t.Fatalf("failed to create temporary file: %v", err)
}
existingPath := existing.Name()
existing.Close()
var newPath string

t.Run("uses custom path", func(t *testing.T) {
path = util.TempFile(t, util.Dir(dir))
t.Run("uses custom directory", func(t *testing.T) {
newPath = util.TempFile(t, util.Dir(dir))
entries, err := os.ReadDir(dir)
if err != nil {
t.Fatalf("failed to read directory: %v", err)
}
if len(entries) == 0 || entries[0].Name() != filepath.Base(path) {
found := slices.ContainsFunc(entries, func(entry fs.DirEntry) bool {
return entry.Name() == filepath.Base(newPath)
})
if !found {
t.Fatalf("did not find temporary file in %s", dir)
}
})

if path == "" {
if newPath == "" {
t.Fatal("expected non-empty path")
}
_, err := os.Stat(path)
if err == nil {
t.Fatalf("expected temp file not to exist: %s", path)
_, err = os.Stat(newPath)
if !errors.Is(err, fs.ErrNotExist) {
if err == nil {
t.Errorf("expected temp file not to exist: %s", newPath)
} else {
t.Errorf("unexpected error: %v", err)
}
}
_, err = os.Stat(existingPath)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
t.Error("expected pre-existing file not to be deleted")
} else {
t.Errorf("unexpected error statting pre-existing file: %v", err)
}
}
})

t.Run("fails if specified dir doesn't exist", func(t *testing.T) {
fakeDir := filepath.Join(t.TempDir(), "fake")
tracker := trackFailure(t)
path := util.TempFile(tracker, util.Dir(fakeDir))
if path != "" {
t.Errorf("expected empty path\ngot %q", path)
}
tracker.AssertFailedWith("TempFile: directory does not exist")
})
}

0 comments on commit e036a27

Please sign in to comment.