Skip to content

Commit 10b9305

Browse files
authored
Merge branch 'main' into docs/v2-symbol-relocations
2 parents 50b2d41 + 18eb952 commit 10b9305

2 files changed

Lines changed: 84 additions & 0 deletions

File tree

modules/core/teststate/teststate.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
//
88
// This package lives in core rather than teststructure so that modules such as aws, k8s, packer, and ssh can provide
99
// their own helpers without teststructure having to import every one of them.
10+
// Every t.Fatalf here is followed by an explicit return. They are unreachable with *testing.T, whose FailNow calls
11+
// runtime.Goexit, but TestingT allows other harnesses, and one whose FailNow returns would otherwise carry on past
12+
// the failure.
1013
package teststate
1114

1215
import (
@@ -73,6 +76,8 @@ func save(t testing.TestingT, path string, overwrite bool, value any, loggedVal
7376
bytes, err := json.Marshal(value)
7477
if err != nil {
7578
t.Fatalf("Failed to convert value %s to JSON: %v", path, err)
79+
80+
return
7681
}
7782

7883
if loggedVal {
@@ -83,6 +88,8 @@ func save(t testing.TestingT, path string, overwrite bool, value any, loggedVal
8388

8489
if err := os.MkdirAll(parentDir, 0o755); err != nil {
8590
t.Fatalf("Failed to create folder %s: %v", parentDir, err)
91+
92+
return
8693
}
8794

8895
// 0o600: this file can hold secrets, such as the private key in an aws.Ec2Keypair or an ssh.KeyPair.
@@ -100,6 +107,8 @@ func Load(t testing.TestingT, path string, value any) {
100107
bytes, err := os.ReadFile(path)
101108
if err != nil {
102109
t.Fatalf("Failed to load value from %s: %v", path, err)
110+
111+
return
103112
}
104113

105114
if err := json.Unmarshal(bytes, value); err != nil {
@@ -112,6 +121,8 @@ func IsPresent(t testing.TestingT, path string) bool {
112121
exists, err := files.FileExistsE(path)
113122
if err != nil {
114123
t.Fatalf("Failed to load test data from %s due to unexpected error: %v", path, err)
124+
125+
return false
115126
}
116127

117128
if !exists {
@@ -121,6 +132,8 @@ func IsPresent(t testing.TestingT, path string) bool {
121132
bytes, err := os.ReadFile(path)
122133
if err != nil {
123134
t.Fatalf("Failed to load test data from %s due to unexpected error: %v", path, err)
135+
136+
return false
124137
}
125138

126139
if IsEmptyJSON(t, bytes) {
@@ -141,6 +154,8 @@ func IsEmptyJSON(t testing.TestingT, bytes []byte) bool {
141154

142155
if err := json.Unmarshal(bytes, &value); err != nil {
143156
t.Fatalf("Failed to parse JSON while testing whether it is empty: %v", err)
157+
158+
return false
144159
}
145160

146161
if value == nil {

modules/core/teststate/teststate_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,3 +204,72 @@ func TestSaveWritesOwnerOnlyPermissions(t *testing.T) {
204204
require.NoError(t, err)
205205
assert.Equal(t, os.FileMode(0o600), info.Mode().Perm(), "saved test data must be owner read/write only")
206206
}
207+
208+
// nonStoppingT is a TestingT whose FailNow returns rather than calling runtime.Goexit, so these tests can assert
209+
// that the package stops doing work after it reports a failure.
210+
type nonStoppingT struct {
211+
failed bool
212+
msgs []string
213+
}
214+
215+
func (r *nonStoppingT) Fail() { r.failed = true }
216+
func (r *nonStoppingT) FailNow() { r.failed = true }
217+
func (r *nonStoppingT) Error(args ...any) { r.failed = true }
218+
func (r *nonStoppingT) Errorf(string, ...any) { r.failed = true }
219+
func (r *nonStoppingT) Fatal(args ...any) { r.msgs = append(r.msgs, fmt.Sprint(args...)); r.FailNow() }
220+
func (r *nonStoppingT) Name() string { return "nonStoppingT" }
221+
func (r *nonStoppingT) Helper() {}
222+
func (r *nonStoppingT) Fatalf(f string, a ...any) {
223+
r.msgs = append(r.msgs, fmt.Sprintf(f, a...))
224+
r.FailNow()
225+
}
226+
227+
// unmarshalable fails json.Marshal: encoding/json always rejects a func field.
228+
type unmarshalable struct {
229+
Fn func()
230+
}
231+
232+
// Before the fix, a marshal failure was reported and os.WriteFile still ran with a nil slice, leaving a zero byte
233+
// file for a later stage to load.
234+
func TestSaveWritesNothingAfterAMarshalFailure(t *testing.T) {
235+
t.Parallel()
236+
237+
folder := t.TempDir()
238+
path := teststate.FormatPath(folder, "Broken.json")
239+
240+
recorder := &nonStoppingT{}
241+
teststate.Save(recorder, path, true, unmarshalable{})
242+
243+
assert.True(t, recorder.failed, "the marshal failure must be reported")
244+
assert.NoFileExists(t, path, "no file may be written after a marshal failure")
245+
}
246+
247+
// A read failure must be reported, not reported as "absent", which would invite a caller to overwrite state it
248+
// could not read.
249+
func TestIsPresentDoesNotMaskAnUnreadableFile(t *testing.T) {
250+
t.Parallel()
251+
252+
// A directory where a file is expected: FileExistsE succeeds, os.ReadFile fails with EISDIR.
253+
folder := t.TempDir()
254+
path := teststate.FormatPath(folder, "IsADirectory.json")
255+
require.NoError(t, os.MkdirAll(path, 0o755))
256+
257+
recorder := &nonStoppingT{}
258+
present := teststate.IsPresent(recorder, path)
259+
260+
assert.True(t, recorder.failed, "the read failure must be reported")
261+
assert.False(t, present)
262+
require.NotEmpty(t, recorder.msgs)
263+
assert.Contains(t, recorder.msgs[0], "unexpected error")
264+
}
265+
266+
// Invalid JSON must be reported, not called empty.
267+
func TestIsEmptyJSONReportsAParseFailure(t *testing.T) {
268+
t.Parallel()
269+
270+
recorder := &nonStoppingT{}
271+
empty := teststate.IsEmptyJSON(recorder, []byte("{not json"))
272+
273+
assert.True(t, recorder.failed, "the parse failure must be reported")
274+
assert.False(t, empty, "invalid JSON is not the same as empty JSON")
275+
}

0 commit comments

Comments
 (0)