You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

144 lines
4.7 KiB

  1. package tempfile
  2. // Need access to internal variables, so can't use _test package
  3. import (
  4. "bytes"
  5. "fmt"
  6. mrand "math/rand"
  7. "os"
  8. "testing"
  9. "github.com/stretchr/testify/require"
  10. tmrand "github.com/tendermint/tendermint/libs/rand"
  11. )
  12. func TestWriteFileAtomic(t *testing.T) {
  13. var (
  14. data = []byte(tmrand.Str(mrand.Intn(2048)))
  15. old = tmrand.Bytes(mrand.Intn(2048))
  16. perm os.FileMode = 0600
  17. )
  18. f, err := os.CreateTemp(t.TempDir(), "write-atomic-test-")
  19. if err != nil {
  20. t.Fatal(err)
  21. }
  22. defer os.Remove(f.Name())
  23. if err = os.WriteFile(f.Name(), old, 0600); err != nil {
  24. t.Fatal(err)
  25. }
  26. if err = WriteFileAtomic(f.Name(), data, perm); err != nil {
  27. t.Fatal(err)
  28. }
  29. rData, err := os.ReadFile(f.Name())
  30. if err != nil {
  31. t.Fatal(err)
  32. }
  33. if !bytes.Equal(data, rData) {
  34. t.Fatalf("data mismatch: %v != %v", data, rData)
  35. }
  36. stat, err := os.Stat(f.Name())
  37. if err != nil {
  38. t.Fatal(err)
  39. }
  40. if have, want := stat.Mode().Perm(), perm; have != want {
  41. t.Errorf("have %v, want %v", have, want)
  42. }
  43. }
  44. // This tests atomic write file when there is a single duplicate file.
  45. // Expected behavior is for a new file to be created, and the original write file to be unaltered.
  46. func TestWriteFileAtomicDuplicateFile(t *testing.T) {
  47. var (
  48. defaultSeed uint64 = 1
  49. testString = "This is a glorious test string"
  50. expectedString = "Did the test file's string appear here?"
  51. fileToWrite = "/tmp/TestWriteFileAtomicDuplicateFile-test.txt"
  52. )
  53. // Create a file at the seed, and reset the seed.
  54. atomicWriteFileRand = defaultSeed
  55. firstFileRand := randWriteFileSuffix()
  56. atomicWriteFileRand = defaultSeed
  57. fname := "/tmp/" + atomicWriteFilePrefix + firstFileRand
  58. f, err := os.OpenFile(fname, atomicWriteFileFlag, 0777)
  59. defer os.Remove(fname)
  60. // Defer here, in case there is a panic in WriteFileAtomic.
  61. defer os.Remove(fileToWrite)
  62. require.NoError(t, err)
  63. _, err = f.WriteString(testString)
  64. require.NoError(t, err)
  65. err = WriteFileAtomic(fileToWrite, []byte(expectedString), 0777)
  66. require.NoError(t, err)
  67. // Check that the first atomic file was untouched
  68. firstAtomicFileBytes, err := os.ReadFile(fname)
  69. require.NoError(t, err, "Error reading first atomic file")
  70. require.Equal(t, []byte(testString), firstAtomicFileBytes, "First atomic file was overwritten")
  71. // Check that the resultant file is correct
  72. resultantFileBytes, err := os.ReadFile(fileToWrite)
  73. require.NoError(t, err, "Error reading resultant file")
  74. require.Equal(t, []byte(expectedString), resultantFileBytes, "Written file had incorrect bytes")
  75. // Check that the intermediate write file was deleted
  76. // Get the second write files' randomness
  77. atomicWriteFileRand = defaultSeed
  78. _ = randWriteFileSuffix()
  79. secondFileRand := randWriteFileSuffix()
  80. _, err = os.Stat("/tmp/" + atomicWriteFilePrefix + secondFileRand)
  81. require.True(t, os.IsNotExist(err), "Intermittent atomic write file not deleted")
  82. }
  83. // This tests atomic write file when there are many duplicate files.
  84. // Expected behavior is for a new file to be created under a completely new seed,
  85. // and the original write files to be unaltered.
  86. func TestWriteFileAtomicManyDuplicates(t *testing.T) {
  87. var (
  88. defaultSeed uint64 = 2
  89. testString = "This is a glorious test string, from file %d"
  90. expectedString = "Did any of the test file's string appear here?"
  91. fileToWrite = "/tmp/TestWriteFileAtomicDuplicateFile-test.txt"
  92. )
  93. // Initialize all of the atomic write files
  94. atomicWriteFileRand = defaultSeed
  95. for i := 0; i < atomicWriteFileMaxNumConflicts+2; i++ {
  96. fileRand := randWriteFileSuffix()
  97. fname := "/tmp/" + atomicWriteFilePrefix + fileRand
  98. f, err := os.OpenFile(fname, atomicWriteFileFlag, 0777)
  99. require.NoError(t, err)
  100. _, err = f.WriteString(fmt.Sprintf(testString, i))
  101. require.NoError(t, err)
  102. defer os.Remove(fname)
  103. }
  104. atomicWriteFileRand = defaultSeed
  105. // Defer here, in case there is a panic in WriteFileAtomic.
  106. defer os.Remove(fileToWrite)
  107. err := WriteFileAtomic(fileToWrite, []byte(expectedString), 0777)
  108. require.NoError(t, err)
  109. // Check that all intermittent atomic file were untouched
  110. atomicWriteFileRand = defaultSeed
  111. for i := 0; i < atomicWriteFileMaxNumConflicts+2; i++ {
  112. fileRand := randWriteFileSuffix()
  113. fname := "/tmp/" + atomicWriteFilePrefix + fileRand
  114. firstAtomicFileBytes, err := os.ReadFile(fname)
  115. require.NoError(t, err, "Error reading first atomic file")
  116. require.Equal(t, []byte(fmt.Sprintf(testString, i)), firstAtomicFileBytes,
  117. "atomic write file %d was overwritten", i)
  118. }
  119. // Check that the resultant file is correct
  120. resultantFileBytes, err := os.ReadFile(fileToWrite)
  121. require.NoError(t, err, "Error reading resultant file")
  122. require.Equal(t, []byte(expectedString), resultantFileBytes, "Written file had incorrect bytes")
  123. }