github.com/scottcagno/storage@v1.8.0/pkg/mmap/mmap_test.go (about) 1 package mmap 2 3 import ( 4 "bytes" 5 "io" 6 "io/ioutil" 7 "math" 8 "os" 9 "path/filepath" 10 "strconv" 11 "testing" 12 ) 13 14 // testFilePath is the template of the path to the test file. 15 var testFilePath = filepath.Join(os.TempDir(), "./testfile.txt") 16 17 // testFileIndex is the current index of the test file. 18 var testFileIndex uint64 = 0 19 20 // testFileMode is the access mode of the test file. 21 var testFileMode = os.FileMode(0600) 22 23 // testData is the non-zero test data. 24 var testData = []byte{'H', 'E', 'L', 'L', 'O'} 25 26 // testDataLength is the length of test data. 27 var testDataLength = len(testData) 28 29 // testZeroData is the zero test data of the same length as test data. 30 var testZeroData = make([]byte, testDataLength) 31 32 // nextTestFilePath returns the path to the test file. 33 func nextTestFilePath(t *testing.T) string { 34 testFileIndex++ 35 filePath := testFilePath + "_" + strconv.FormatUint(testFileIndex, 10) 36 if _, err := os.Stat(filePath); err != nil { 37 if !os.IsNotExist(err) { 38 t.Fatal(err) 39 } 40 } else { 41 if err := os.Remove(filePath); err != nil { 42 t.Fatal(err) 43 } 44 } 45 return filePath 46 } 47 48 // openNextTestFile opens and returns the test file. 49 // if copyPrevious == true previous test file will be copied if exists. 50 func openNextTestFile(t *testing.T, copyPrevious bool) *os.File { 51 filePath := nextTestFilePath(t) 52 fileData := testZeroData 53 if copyPrevious && testFileIndex > 1 { 54 var err error 55 fileData, err = ioutil.ReadFile(testFilePath + "_" + strconv.FormatUint(testFileIndex-1, 10)) 56 if err != nil { 57 t.Fatal(err) 58 } 59 } 60 if err := ioutil.WriteFile(filePath, fileData, testFileMode); err != nil { 61 t.Fatal(err) 62 } 63 f, err := os.OpenFile(filePath, os.O_RDWR, testFileMode) 64 if err != nil { 65 t.Fatal(err) 66 } 67 return f 68 } 69 70 // openTestMapping opens and returns a new mapping of the test file into the memory. 71 func openTestMapping(t *testing.T, mode Mode) *Mapping { 72 f := openNextTestFile(t, false) 73 defer closeTestEntity(t, f) 74 m, err := Open(f.Fd(), 0, uintptr(testDataLength), mode, 0) 75 if err != nil { 76 t.Fatal(err) 77 } 78 return m 79 } 80 81 // closeTestEntity closes the given entity. 82 // It ignores ErrClosed error by the reason this error returns when mapping is closed twice. 83 func closeTestEntity(t *testing.T, closer io.Closer) { 84 if err := closer.Close(); err != nil { 85 if err != ErrClosed { 86 t.Fatal(err) 87 } 88 } 89 } 90 91 // TestWithOpenedFile tests the work with the mapping of file which is not closed before closing mapping. 92 // CASE: The mapping MUST works correctly. 93 func TestWithOpenedFile(t *testing.T) { 94 f := openNextTestFile(t, true) 95 defer closeTestEntity(t, f) 96 m, err := Open(f.Fd(), 0, uintptr(testDataLength), ModeReadWrite, 0) 97 if err != nil { 98 t.Fatal(err) 99 } 100 defer closeTestEntity(t, m) 101 if _, err := m.WriteAt(testData, 0); err != nil { 102 t.Fatal(err) 103 } 104 buf := make([]byte, testDataLength) 105 if _, err := m.ReadAt(buf, 0); err != nil { 106 t.Fatal(err) 107 } 108 if bytes.Compare(buf, testData) != 0 { 109 t.Fatalf("data must be %q, %v found", testData, buf) 110 } 111 if err := m.Close(); err != nil { 112 t.Fatal(err) 113 } 114 } 115 116 // TestWithClosedFile tests the work with the mapping of file which is closed before closing mapping. 117 // CASE: The duplication of the file descriptor MUST works correctly. 118 func TestWithClosedFile(t *testing.T) { 119 m := openTestMapping(t, ModeReadWrite) 120 defer closeTestEntity(t, m) 121 if _, err := m.WriteAt(testData, 0); err != nil { 122 t.Fatal(err) 123 } 124 buf := make([]byte, testDataLength) 125 if _, err := m.ReadAt(buf, 0); err != nil { 126 t.Fatal(err) 127 } 128 if bytes.Compare(buf, testData) != 0 { 129 t.Fatalf("data must be %q, %v found", testData, buf) 130 } 131 if err := m.Close(); err != nil { 132 t.Fatal(err) 133 } 134 } 135 136 // TestUnalignedOffset tests using the unaligned start address of the mapping memory. 137 // CASE: The unaligned offset MUST works correctly. 138 // TODO: This is a strange test... 139 func TestUnalignedOffset(t *testing.T) { 140 f := openNextTestFile(t, false) 141 defer closeTestEntity(t, f) 142 partialLength := uintptr(testDataLength - 1) 143 m, err := Open(f.Fd(), 1, partialLength, ModeReadWrite, 0) 144 if err != nil { 145 t.Fatal(err) 146 } 147 defer closeTestEntity(t, m) 148 partialData := make([]byte, partialLength) 149 copy(partialData, testData[1:]) 150 if _, err := m.WriteAt(partialData, 0); err != nil { 151 t.Fatal(err) 152 } 153 buf := make([]byte, partialLength) 154 if _, err := m.ReadAt(buf, 0); err != nil { 155 t.Fatal(err) 156 } 157 if bytes.Compare(buf, partialData) != 0 { 158 t.Fatalf("data must be %q, %v found", partialData, buf) 159 } 160 } 161 162 // TestSharedSync tests the synchronization of the mapped memory with the underlying file in the shared mode. 163 // CASE: The data which is read directly from the underlying file MUST be exactly the same 164 // as the previously written through the mapped memory. 165 func TestSharedSync(t *testing.T) { 166 m := openTestMapping(t, ModeReadWrite) 167 defer closeTestEntity(t, m) 168 if _, err := m.WriteAt(testData, 0); err != nil { 169 t.Fatal(err) 170 } 171 if err := m.Sync(); err != nil { 172 t.Fatal(err) 173 } 174 f := openNextTestFile(t, true) 175 defer closeTestEntity(t, f) 176 buf := make([]byte, testDataLength) 177 if _, err := f.ReadAt(buf, 0); err != nil { 178 t.Fatal(err) 179 } 180 if bytes.Compare(buf, testData) != 0 { 181 t.Fatalf("data must be %q, %v found", testData, buf) 182 } 183 } 184 185 // TestPrivateSync tests the synchronization of the mapped memory with the underlying file in the private mode. 186 // CASE: The data which is read directly from the underlying file MUST NOT be affected 187 // by the previous write through the mapped memory. 188 func TestPrivateSync(t *testing.T) { 189 m := openTestMapping(t, ModeWriteCopy) 190 defer closeTestEntity(t, m) 191 if _, err := m.WriteAt(testData, 0); err != nil { 192 t.Fatal(err) 193 } 194 if err := m.Sync(); err != nil { 195 t.Fatal(err) 196 } 197 f := openNextTestFile(t, true) 198 defer closeTestEntity(t, f) 199 buf := make([]byte, testDataLength) 200 if _, err := f.ReadAt(buf, 0); err != nil { 201 t.Fatal(err) 202 } 203 if bytes.Compare(buf, testZeroData) != 0 { 204 t.Fatalf("data must be %v, %v found", testZeroData, buf) 205 } 206 } 207 208 // TestPartialRead tests the reading beyond the mapped memory. 209 // CASE 1: The ErrOutOfBounds MUST be returned. 210 // CASE 2: The reading buffer MUST NOT be modified. 211 func TestPartialRead(t *testing.T) { 212 f := openNextTestFile(t, false) 213 defer closeTestEntity(t, f) 214 partialLength := uintptr(testDataLength - 1) 215 m, err := Open(f.Fd(), 0, partialLength, ModeReadWrite, 0) 216 if err != nil { 217 t.Fatal(err) 218 } 219 defer closeTestEntity(t, m) 220 if _, err := m.WriteAt(testData[:partialLength], 0); err != nil { 221 t.Fatal(err) 222 } 223 buf := make([]byte, testDataLength) 224 if _, err := m.ReadAt(buf, 0); err == nil { 225 t.Fatal("expected ErrOutOfBounds, no error found") 226 } else if err != ErrOutOfBounds { 227 t.Fatalf("expected ErrOutOfBounds, [%v] error found", err) 228 } 229 if bytes.Compare(buf, testZeroData) != 0 { 230 t.Fatalf("data must be %v, %v found", testZeroData, buf) 231 } 232 } 233 234 // TestPartialWrite tests the writing beyond the mapped memory. 235 // CASE 1: The ErrOutOfBounds MUST be returned. 236 // CASE 2: The mapped memory MUST NOT be modified. 237 func TestPartialWrite(t *testing.T) { 238 f := openNextTestFile(t, false) 239 defer closeTestEntity(t, f) 240 partialLength := uintptr(testDataLength - 1) 241 m, err := Open(f.Fd(), 0, partialLength, ModeReadWrite, 0) 242 if err != nil { 243 t.Fatal(err) 244 } 245 defer closeTestEntity(t, m) 246 if _, err := m.WriteAt(testData, 0); err == nil { 247 t.Fatal("expected ErrOutOfBounds, no error found") 248 } else if err != ErrOutOfBounds { 249 t.Fatalf("expected ErrOutOfBounds, [%v] error found", err) 250 } 251 buf := make([]byte, partialLength) 252 if _, err := m.ReadAt(buf, 0); err != nil { 253 t.Fatal(err) 254 } 255 if bytes.Compare(buf, testZeroData[:partialLength]) != 0 { 256 t.Fatalf("data must be %v, %v found", testZeroData, buf) 257 } 258 } 259 260 // TestFileOpening tests the OpenMapping function. 261 // CASE 1: The initializer must be called once. 262 // CASE 2: The data read on the second opening must be exactly the same as previously written on the first one. 263 func TestFileOpening(t *testing.T) { 264 initCallCount := 0 265 filePath := nextTestFilePath(t) 266 open := func() (*Mapping, error) { 267 return OpenFileMapping(filePath, testFileMode, uintptr(testDataLength), 0, func(m *Mapping) error { 268 initCallCount++ 269 _, err := m.WriteAt(testData, 0) 270 return err 271 }) 272 } 273 mf, err := open() 274 if err != nil { 275 t.Fatal(err) 276 } 277 closeTestEntity(t, mf) 278 mf, err = open() 279 if err != nil { 280 t.Fatal(err) 281 } 282 defer closeTestEntity(t, mf) 283 if initCallCount > 1 { 284 t.Fatalf("initializer must be called once, %d calls found", initCallCount) 285 } 286 buf := make([]byte, testDataLength) 287 if _, err := mf.ReadAt(buf, 0); err != nil { 288 t.Fatal(err) 289 } 290 if bytes.Compare(buf, testData) != 0 { 291 t.Fatalf("data must be %v, %v found", testData, buf) 292 } 293 } 294 295 // TestSegment tests the data segment. 296 // CASE: The read data must be exactly the same as the previously written unsigned 32-bit integer. 297 func TestSegment(t *testing.T) { 298 m := openTestMapping(t, ModeReadWrite) 299 defer closeTestEntity(t, m) 300 *m.Segment().Uint32(0) = math.MaxUint32 - 1 301 if err := m.Close(); err != nil { 302 t.Fatal(err) 303 } 304 f := openNextTestFile(t, true) 305 defer closeTestEntity(t, f) 306 buf := make([]byte, 4) 307 if _, err := f.ReadAt(buf, 0); err != nil { 308 t.Fatal(err) 309 } 310 uint32Data := []byte{254, 255, 255, 255} 311 if bytes.Compare(buf, uint32Data) != 0 { 312 t.Fatalf("data must be %v, %v found", uint32Data, buf) 313 } 314 }