package tempfile // Need access to internal variables, so can't use _test package import ( "bytes" "fmt" mrand "math/rand" "os" "testing" "github.com/stretchr/testify/require" tmrand "github.com/tendermint/tendermint/libs/rand" ) func TestWriteFileAtomic(t *testing.T) { var ( data = []byte(tmrand.Str(mrand.Intn(2048))) old = tmrand.Bytes(mrand.Intn(2048)) perm os.FileMode = 0600 ) f, err := os.CreateTemp(t.TempDir(), "write-atomic-test-") if err != nil { t.Fatal(err) } defer os.Remove(f.Name()) if err = os.WriteFile(f.Name(), old, 0600); err != nil { t.Fatal(err) } if err = WriteFileAtomic(f.Name(), data, perm); err != nil { t.Fatal(err) } rData, err := os.ReadFile(f.Name()) if err != nil { t.Fatal(err) } if !bytes.Equal(data, rData) { t.Fatalf("data mismatch: %v != %v", data, rData) } stat, err := os.Stat(f.Name()) if err != nil { t.Fatal(err) } if have, want := stat.Mode().Perm(), perm; have != want { t.Errorf("have %v, want %v", have, want) } } // This tests atomic write file when there is a single duplicate file. // Expected behavior is for a new file to be created, and the original write file to be unaltered. func TestWriteFileAtomicDuplicateFile(t *testing.T) { var ( defaultSeed uint64 = 1 testString = "This is a glorious test string" expectedString = "Did the test file's string appear here?" fileToWrite = "/tmp/TestWriteFileAtomicDuplicateFile-test.txt" ) // Create a file at the seed, and reset the seed. atomicWriteFileRand = defaultSeed firstFileRand := randWriteFileSuffix() atomicWriteFileRand = defaultSeed fname := "/tmp/" + atomicWriteFilePrefix + firstFileRand f, err := os.OpenFile(fname, atomicWriteFileFlag, 0777) defer os.Remove(fname) // Defer here, in case there is a panic in WriteFileAtomic. defer os.Remove(fileToWrite) require.NoError(t, err) _, err = f.WriteString(testString) require.NoError(t, err) err = WriteFileAtomic(fileToWrite, []byte(expectedString), 0777) require.NoError(t, err) // Check that the first atomic file was untouched firstAtomicFileBytes, err := os.ReadFile(fname) require.NoError(t, err, "Error reading first atomic file") require.Equal(t, []byte(testString), firstAtomicFileBytes, "First atomic file was overwritten") // Check that the resultant file is correct resultantFileBytes, err := os.ReadFile(fileToWrite) require.NoError(t, err, "Error reading resultant file") require.Equal(t, []byte(expectedString), resultantFileBytes, "Written file had incorrect bytes") // Check that the intermediate write file was deleted // Get the second write files' randomness atomicWriteFileRand = defaultSeed _ = randWriteFileSuffix() secondFileRand := randWriteFileSuffix() _, err = os.Stat("/tmp/" + atomicWriteFilePrefix + secondFileRand) require.True(t, os.IsNotExist(err), "Intermittent atomic write file not deleted") } // This tests atomic write file when there are many duplicate files. // Expected behavior is for a new file to be created under a completely new seed, // and the original write files to be unaltered. func TestWriteFileAtomicManyDuplicates(t *testing.T) { var ( defaultSeed uint64 = 2 testString = "This is a glorious test string, from file %d" expectedString = "Did any of the test file's string appear here?" fileToWrite = "/tmp/TestWriteFileAtomicDuplicateFile-test.txt" ) // Initialize all of the atomic write files atomicWriteFileRand = defaultSeed for i := 0; i < atomicWriteFileMaxNumConflicts+2; i++ { fileRand := randWriteFileSuffix() fname := "/tmp/" + atomicWriteFilePrefix + fileRand f, err := os.OpenFile(fname, atomicWriteFileFlag, 0777) require.NoError(t, err) _, err = f.WriteString(fmt.Sprintf(testString, i)) require.NoError(t, err) defer os.Remove(fname) } atomicWriteFileRand = defaultSeed // Defer here, in case there is a panic in WriteFileAtomic. defer os.Remove(fileToWrite) err := WriteFileAtomic(fileToWrite, []byte(expectedString), 0777) require.NoError(t, err) // Check that all intermittent atomic file were untouched atomicWriteFileRand = defaultSeed for i := 0; i < atomicWriteFileMaxNumConflicts+2; i++ { fileRand := randWriteFileSuffix() fname := "/tmp/" + atomicWriteFilePrefix + fileRand firstAtomicFileBytes, err := os.ReadFile(fname) require.NoError(t, err, "Error reading first atomic file") require.Equal(t, []byte(fmt.Sprintf(testString, i)), firstAtomicFileBytes, "atomic write file %d was overwritten", i) } // Check that the resultant file is correct resultantFileBytes, err := os.ReadFile(fileToWrite) require.NoError(t, err, "Error reading resultant file") require.Equal(t, []byte(expectedString), resultantFileBytes, "Written file had incorrect bytes") }