Skip to content

Commit f060daf

Browse files
dolphclaude
andauthored
Replace log.Fatal in tests, add t.Helper, cover NewFile (#64)
Three test-infrastructure fixes from issue #33: 1. Replace every test-side log.Fatal* with t.Fatalf / b.Fatalf. log.Fatal calls os.Exit(1), which kills the entire test binary without running any deferred cleanup (so temp files leak), without printing the test name that failed, and prevents subsequent tests from running. Affected helpers: newTestFile, newTestDir, TestHandleFileWithIgnoredDir. To make newTestFile/newTestDir usable from both *testing.T and *testing.B call sites, their signatures now take testing.TB as the first argument. All call sites are updated. 2. Add t.Helper() / b.Helper() to every test helper that takes *testing.T or *testing.B: assertFileExists, assertFileNonexistent, assertPathExistsAfterRename, assertNewContentsOfFile, assertRandomStringLength, newTestFile, newTestDir, and CloneRepoToTestDir. Failures are now attributed to the call site instead of the helper body. Verified by inducing a failure in assertFileExists from a throwaway test file: the reported file:line was the call site, not the helper. 3. Replace the empty TestNewFile with real, table-driven coverage: absolute-path inputs returned cleaned, redundant separators collapsed, .. resolved, relative paths resolved to absolute, and the working-directory case ("."). The filepath.Abs error path is not covered here because NewFile currently calls log.Fatalf on that failure (kills the test binary); the error path becomes testable when NewFile is refactored per issue #6. No production code is changed. Closes #33 Co-authored-by: Claude <noreply@anthropic.com>
1 parent d1587bc commit f060daf

3 files changed

Lines changed: 110 additions & 29 deletions

File tree

file_handling_test.go

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,80 @@
11
package main
22

3-
import "testing"
3+
import (
4+
"path/filepath"
5+
"testing"
6+
)
47

8+
// TestNewFile exercises NewFile's path-resolution behavior. It does NOT cover
9+
// the filepath.Abs error path: NewFile currently calls log.Fatalf on that
10+
// failure, which would kill the test binary, and that surface is not reachable
11+
// on most platforms in any case. The error path will become testable when
12+
// NewFile is refactored to return an error (see issue #6).
513
func TestNewFile(t *testing.T) {
14+
tmp := t.TempDir()
15+
16+
tests := []struct {
17+
name string
18+
// input is the raw path passed to NewFile.
19+
input string
20+
// want is the absolute path the resulting *File should expose. If
21+
// empty, the test computes the expected value via filepath.Abs(input)
22+
// at runtime (useful for inputs that are inherently relative to the
23+
// test process's working directory).
24+
want string
25+
}{
26+
{
27+
name: "absolute path is returned cleaned",
28+
input: filepath.Join(tmp, "foo"),
29+
want: filepath.Join(tmp, "foo"),
30+
},
31+
{
32+
name: "absolute path with redundant separators is cleaned",
33+
input: tmp + "//foo///bar",
34+
want: filepath.Join(tmp, "foo", "bar"),
35+
},
36+
{
37+
name: "absolute path with .. is resolved",
38+
input: filepath.Join(tmp, "a", "..", "b"),
39+
want: filepath.Join(tmp, "b"),
40+
},
41+
{
42+
name: "relative path is resolved to absolute",
43+
input: "relative/path",
44+
// want is computed below because it depends on the test
45+
// process's working directory.
46+
},
47+
{
48+
name: "relative path with .. is resolved",
49+
input: "a/../b",
50+
},
51+
{
52+
name: "dot is resolved to the working directory",
53+
input: ".",
54+
},
55+
}
56+
57+
for _, tc := range tests {
58+
t.Run(tc.name, func(t *testing.T) {
59+
want := tc.want
60+
if want == "" {
61+
abs, err := filepath.Abs(tc.input)
62+
if err != nil {
63+
t.Fatalf("filepath.Abs(%q) returned unexpected error: %v", tc.input, err)
64+
}
65+
want = abs
66+
}
67+
68+
got := NewFile(tc.input)
69+
if got == nil {
70+
t.Fatalf("NewFile(%q) returned nil", tc.input)
71+
}
72+
if got.Path != want {
73+
t.Errorf("NewFile(%q).Path = %q; want %q", tc.input, got.Path, want)
74+
}
75+
if !filepath.IsAbs(got.Path) {
76+
t.Errorf("NewFile(%q).Path = %q; want an absolute path", tc.input, got.Path)
77+
}
78+
})
79+
}
680
}

find_replace_test.go

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package main
22

33
import (
44
"errors"
5-
"log"
65
"os"
76
"os/exec"
87
"path/filepath"
@@ -19,18 +18,19 @@ import (
1918
// If a baseName is not provided, a random file name is generated. Returns the
2019
// directory where the file was created, the file's directory entry, and the
2120
// actual name of the file.
22-
func newTestFile(path string, baseName string, content string) *File {
21+
func newTestFile(tb testing.TB, path string, baseName string, content string) *File {
22+
tb.Helper()
2323
f, err := os.CreateTemp(path, baseName)
2424
if err != nil {
25-
log.Fatal(err)
25+
tb.Fatalf("CreateTemp(%q, %q): %v", path, baseName, err)
2626
}
2727
if _, err := f.Write([]byte(content)); err != nil {
2828
defer os.Remove(f.Name())
29-
log.Fatal(err)
29+
tb.Fatalf("write to %v: %v", f.Name(), err)
3030
}
3131
if err := f.Close(); err != nil {
3232
defer os.Remove(f.Name())
33-
log.Fatal(err)
33+
tb.Fatalf("close %v: %v", f.Name(), err)
3434
}
3535

3636
return NewFile(f.Name())
@@ -41,10 +41,11 @@ func newTestFile(path string, baseName string, content string) *File {
4141
// a baseName is not provided, a random file name is generated. Returns the
4242
// directory where the file was created, the file's directory entry, and the
4343
// actual name of the file.
44-
func newTestDir(path string, baseName string) *File {
44+
func newTestDir(tb testing.TB, path string, baseName string) *File {
45+
tb.Helper()
4546
dirPath, err := os.MkdirTemp(path, baseName)
4647
if err != nil {
47-
log.Fatal(err)
48+
tb.Fatalf("MkdirTemp(%q, %q): %v", path, baseName, err)
4849
}
4950
return NewFile(dirPath)
5051
}
@@ -59,13 +60,15 @@ func expectedPathAfterRename(f *File, fr *findReplace) string {
5960

6061
// assertFileExists ensures that the given File exists
6162
func assertFileExists(t *testing.T, f *File) {
63+
t.Helper()
6264
if _, err := os.Stat(f.Path); errors.Is(err, os.ErrNotExist) {
6365
t.Errorf("test file %v does not exist", f.Path)
6466
}
6567
}
6668

6769
// assertFileNonexistent ensures that the File does not exist
6870
func assertFileNonexistent(t *testing.T, f *File) {
71+
t.Helper()
6972
if _, err := os.Stat(f.Path); !errors.Is(err, os.ErrNotExist) {
7073
if err == nil {
7174
t.Errorf("test file %v exists", f.Path)
@@ -76,6 +79,7 @@ func assertFileNonexistent(t *testing.T, f *File) {
7679
}
7780

7881
func assertPathExistsAfterRename(t *testing.T, f *File, expectedPath string) *File {
82+
t.Helper()
7983
assertFileNonexistent(t, f)
8084
newFile := NewFile(expectedPath)
8185
assertFileExists(t, newFile)
@@ -95,50 +99,50 @@ func TestWalkDir(t *testing.T) {
9599
find := "wh"
96100
replace := "f"
97101

98-
d := newTestDir("", "*")
102+
d := newTestDir(t, "", "*")
99103
defer os.Remove(d.Path)
100104

101105
// d1: who/
102-
d1 := newTestDir(d.Path, "who")
106+
d1 := newTestDir(t, d.Path, "who")
103107
defer os.Remove(d1.Path)
104108

105109
// d1d1: who/what/
106-
d1d1 := newTestDir(d1.Path, "what")
110+
d1d1 := newTestDir(t, d1.Path, "what")
107111
defer os.Remove(d1d1.Path)
108112

109113
// d1d1f1: who/what/when (contains "where")
110114
d1d1f1Contents := "where"
111-
d1d1f1 := newTestFile(d1d1.Path, "when", d1d1f1Contents)
115+
d1d1f1 := newTestFile(t, d1d1.Path, "when", d1d1f1Contents)
112116
defer os.Remove(d1d1f1.Path)
113117

114118
// d2: what/
115-
d2 := newTestDir(d.Path, "what")
119+
d2 := newTestDir(t, d.Path, "what")
116120
defer os.Remove(d2.Path)
117121

118122
// d2d1: what/when/
119-
d2d1 := newTestDir(d2.Path, "when")
123+
d2d1 := newTestDir(t, d2.Path, "when")
120124
defer os.Remove(d2d1.Path)
121125

122126
// d2d1d1: what/when/where (directories with no files)
123-
d2d1d1 := newTestDir(d2d1.Path, "where")
127+
d2d1d1 := newTestDir(t, d2d1.Path, "where")
124128
defer os.Remove(d2d1d1.Path)
125129

126130
// d3: when/
127-
d3 := newTestDir(d.Path, "when")
131+
d3 := newTestDir(t, d.Path, "when")
128132
defer os.Remove(d3.Path)
129133

130134
// d3f1: when/where (contains "why")
131135
d3f1Contents := "why"
132-
d3f1 := newTestFile(d3.Path, "where", d3f1Contents)
136+
d3f1 := newTestFile(t, d3.Path, "where", d3f1Contents)
133137
defer os.Remove(d3f1.Path)
134138

135139
// d4: where/ (empty directory in base dir)
136-
d4 := newTestDir(d.Path, "where")
140+
d4 := newTestDir(t, d.Path, "where")
137141
defer os.Remove(d4.Path)
138142

139143
// f1: why (file in base dir contains "wh")
140144
f1Contents := "wh\nwh\nwh\n"
141-
f1 := newTestFile(d.Path, "why", f1Contents)
145+
f1 := newTestFile(t, d.Path, "why", f1Contents)
142146
defer os.Remove(f1.Path)
143147

144148
fr := findReplace{find: find, replace: replace}
@@ -193,7 +197,7 @@ func TestHandleFileWithDir(t *testing.T) {
193197
find := "ph"
194198
replace := "f"
195199

196-
f := newTestDir("", initial)
200+
f := newTestDir(t, "", initial)
197201
defer os.Remove(f.Path)
198202
expectedPath := filepath.Join(f.Dir(), strings.Replace(f.Base(), find, replace, -1))
199203
defer os.Remove(expectedPath)
@@ -211,7 +215,7 @@ func TestHandleFileWithIgnoredDir(t *testing.T) {
211215

212216
dirPath := filepath.Join(os.TempDir(), initial)
213217
if err := os.Mkdir(dirPath, 0700); err != nil {
214-
log.Fatal(err)
218+
t.Fatalf("Mkdir(%q): %v", dirPath, err)
215219
}
216220
f := NewFile(dirPath)
217221
defer os.Remove(f.Path)
@@ -233,7 +237,7 @@ func TestHandleFileWithFile(t *testing.T) {
233237
replace := "f"
234238
want := "alfa"
235239

236-
f := newTestFile("", initial, initial)
240+
f := newTestFile(t, "", initial, initial)
237241
defer os.Remove(f.Path)
238242
expectedName := strings.Replace(f.Base(), find, replace, -1)
239243
expectedPath := filepath.Join(f.Dir(), expectedName)
@@ -255,7 +259,7 @@ func TestRenameFile(t *testing.T) {
255259
find := "ph"
256260
replace := "f"
257261

258-
f := newTestFile("", initial, "")
262+
f := newTestFile(t, "", initial, "")
259263
defer os.Remove(f.Path)
260264
expectedName := strings.Replace(f.Base(), find, replace, -1)
261265
expectedPath := filepath.Join(f.Dir(), expectedName)
@@ -270,6 +274,7 @@ func TestRenameFile(t *testing.T) {
270274
// assertNewContentsOfFile ensures that the contents of the file at the given
271275
// path exactly match the desired string.
272276
func assertNewContentsOfFile(t *testing.T, path string, initial string, find string, replace string, want string) {
277+
t.Helper()
273278
got := NewFile(path).Read()
274279
if got != want {
275280
t.Errorf("replace %v with %v in %v, but got %v; want %v", find, replace, initial, got, want)
@@ -282,7 +287,7 @@ func TestReplaceContents(t *testing.T) {
282287
replace := "f"
283288
want := "alfa"
284289

285-
f := newTestFile("", "*", initial)
290+
f := newTestFile(t, "", "*", initial)
286291
defer os.Remove(f.Path)
287292
fr := findReplace{find: find, replace: replace}
288293
fr.ReplaceContents(f)
@@ -295,7 +300,7 @@ func TestReplaceContentsEntireFile(t *testing.T) {
295300
replace := "beta"
296301
want := "beta"
297302

298-
f := newTestFile("", "*", initial)
303+
f := newTestFile(t, "", "*", initial)
299304
defer os.Remove(f.Path)
300305
fr := findReplace{find: find, replace: replace}
301306
fr.ReplaceContents(f)
@@ -308,7 +313,7 @@ func TestReplaceContentsMultipleMatchesSingleLine(t *testing.T) {
308313
replace := "f"
309314
want := "alfaalfa"
310315

311-
f := newTestFile("", "*", initial)
316+
f := newTestFile(t, "", "*", initial)
312317
defer os.Remove(f.Path)
313318
fr := findReplace{find: find, replace: replace}
314319
fr.ReplaceContents(f)
@@ -321,7 +326,7 @@ func TestReplaceContentsMultipleMatchesMultipleLines(t *testing.T) {
321326
replace := "f"
322327
want := "alfa\nalfa"
323328

324-
f := newTestFile("", "*", initial)
329+
f := newTestFile(t, "", "*", initial)
325330
defer os.Remove(f.Path)
326331
fr := findReplace{find: find, replace: replace}
327332
fr.ReplaceContents(f)
@@ -334,15 +339,16 @@ func TestReplaceContentsNoMatches(t *testing.T) {
334339
replace := "xyz"
335340
want := "alpha"
336341

337-
f := newTestFile("", "*", initial)
342+
f := newTestFile(t, "", "*", initial)
338343
defer os.Remove(f.Path)
339344
fr := findReplace{find: find, replace: replace}
340345
fr.ReplaceContents(f)
341346
assertNewContentsOfFile(t, f.Path, initial, find, replace, want)
342347
}
343348

344349
func CloneRepoToTestDir(b *testing.B, repoUrl string) *File {
345-
d := newTestDir("", "*")
350+
b.Helper()
351+
d := newTestDir(b, "", "*")
346352
defer os.Remove(d.Path)
347353

348354
cmd := exec.Command("git", "clone", "--depth=1", "--single-branch", repoUrl, ".")

strings_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
// assertRandomStringLength ensures that the generated string matches the
88
// desired length.
99
func assertRandomStringLength(t *testing.T, ask int, want int) {
10+
t.Helper()
1011
got := len(RandomString(ask))
1112
if got != want {
1213
t.Errorf("len(RandomString(%v)) = %v; want %v", ask, got, want)

0 commit comments

Comments
 (0)