storagefile_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675
  1. /*
  2. * EliasDB
  3. *
  4. * Copyright 2016 Matthias Ladkau. All rights reserved.
  5. *
  6. * This Source Code Form is subject to the terms of the Mozilla Public
  7. * License, v. 2.0. If a copy of the MPL was not distributed with this
  8. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  9. */
  10. package file
  11. import (
  12. "flag"
  13. "fmt"
  14. "os"
  15. "testing"
  16. "devt.de/krotik/common/fileutil"
  17. )
  18. const DBDir = "storagefiletest"
  19. const InvalidFileName = "**" + "\x00"
  20. func TestMain(m *testing.M) {
  21. flag.Parse()
  22. // Setup
  23. if res, _ := fileutil.PathExists(DBDir); res {
  24. os.RemoveAll(DBDir)
  25. }
  26. err := os.Mkdir(DBDir, 0770)
  27. if err != nil {
  28. fmt.Print("Could not create test directory:", err.Error())
  29. os.Exit(1)
  30. }
  31. // Run the tests
  32. res := m.Run()
  33. // Teardown
  34. err = os.RemoveAll(DBDir)
  35. if err != nil {
  36. fmt.Print("Could not remove test directory:", err.Error())
  37. }
  38. os.Exit(res)
  39. }
  40. func TestStorageFileInitialisation(t *testing.T) {
  41. // \0 and / are the only illegal characters for filenames in unix
  42. sf, err := NewDefaultStorageFile(DBDir+"/"+InvalidFileName, true)
  43. if err == nil {
  44. t.Error("Invalid name should cause an error")
  45. return
  46. }
  47. sf, err = NewDefaultStorageFile(DBDir+"/test1", true)
  48. if err != nil {
  49. t.Error(err.Error())
  50. return
  51. }
  52. if sf.Name() != DBDir+"/test1" {
  53. t.Error("Unexpected name of StorageFile:", sf.Name())
  54. return
  55. }
  56. if sf.RecordSize() != DefaultRecordSize {
  57. t.Error("Unexpected record size:", sf.RecordSize())
  58. return
  59. }
  60. defer sf.Close()
  61. res, err := fileutil.PathExists(DBDir + "/test1.0")
  62. if err != nil {
  63. t.Error(err)
  64. return
  65. }
  66. if !res {
  67. t.Error("Expected db file test1.0 does not exist")
  68. return
  69. }
  70. if len(sf.files) != 1 {
  71. t.Error("Unexpected number of files in StorageFile:", sf.files)
  72. return
  73. }
  74. }
  75. func TestGetFile(t *testing.T) {
  76. sf := &StorageFile{DBDir + "/test2", true, 10, 10, nil, nil, nil, nil,
  77. make([]*os.File, 0), nil}
  78. defer sf.Close()
  79. file, err := sf.getFile(0)
  80. if err != nil {
  81. t.Error(err.Error())
  82. return
  83. }
  84. if file.Name() != DBDir+"/test2.0" {
  85. t.Error("Unexpected file from getFile")
  86. return
  87. }
  88. checkFilesArray(t, sf, 1, 0, DBDir+"/test2.0")
  89. file, err = sf.getFile(42)
  90. if err != nil {
  91. t.Error(err.Error())
  92. return
  93. }
  94. if file.Name() != DBDir+"/test2.4" {
  95. t.Error("Unexpected file from getFile")
  96. return
  97. }
  98. checkFilesArray(t, sf, 5, 0, DBDir+"/test2.0")
  99. checkFilesArray(t, sf, 5, 1, "")
  100. checkFilesArray(t, sf, 5, 2, "")
  101. checkFilesArray(t, sf, 5, 3, "")
  102. checkFilesArray(t, sf, 5, 4, DBDir+"/test2.4")
  103. file, err = sf.getFile(25)
  104. if err != nil {
  105. t.Error(err.Error())
  106. return
  107. }
  108. if file.Name() != DBDir+"/test2.2" {
  109. t.Error("Unexpected file from getFile")
  110. return
  111. }
  112. checkFilesArray(t, sf, 5, 0, DBDir+"/test2.0")
  113. checkFilesArray(t, sf, 5, 1, "")
  114. checkFilesArray(t, sf, 5, 2, DBDir+"/test2.2")
  115. checkFilesArray(t, sf, 5, 3, "")
  116. checkFilesArray(t, sf, 5, 4, DBDir+"/test2.4")
  117. file, err = sf.getFile(11)
  118. if err != nil {
  119. t.Error(err.Error())
  120. return
  121. }
  122. if file.Name() != DBDir+"/test2.1" {
  123. t.Error("Unexpected file from getFile")
  124. return
  125. }
  126. checkFilesArray(t, sf, 5, 0, DBDir+"/test2.0")
  127. checkFilesArray(t, sf, 5, 1, DBDir+"/test2.1")
  128. checkFilesArray(t, sf, 5, 2, DBDir+"/test2.2")
  129. checkFilesArray(t, sf, 5, 3, "")
  130. checkFilesArray(t, sf, 5, 4, DBDir+"/test2.4")
  131. file, err = sf.getFile(49)
  132. if err != nil {
  133. t.Error(err.Error())
  134. return
  135. }
  136. if file.Name() != DBDir+"/test2.4" {
  137. t.Error("Unexpected file from getFile")
  138. return
  139. }
  140. checkFilesArray(t, sf, 5, 0, DBDir+"/test2.0")
  141. checkFilesArray(t, sf, 5, 1, DBDir+"/test2.1")
  142. checkFilesArray(t, sf, 5, 2, DBDir+"/test2.2")
  143. checkFilesArray(t, sf, 5, 3, "")
  144. checkFilesArray(t, sf, 5, 4, DBDir+"/test2.4")
  145. }
  146. func checkFilesArray(t *testing.T, sf *StorageFile, explen int, pos int, name string) {
  147. if len(sf.files) != explen {
  148. t.Error("Unexpected files array:", sf.files, " expected size:", explen)
  149. }
  150. file := sf.files[pos]
  151. if name == "" && file != nil {
  152. t.Error("Unexpected file at pos:", pos, " name:", file.Name())
  153. } else if name != "" && file == nil {
  154. t.Error("Unexpected nil pointer at pos:", pos, " expected name:", name)
  155. } else if file != nil && name != file.Name() {
  156. t.Error("Unexpected file at pos:", pos, " name:", file.Name(), " expected name:", name)
  157. }
  158. }
  159. func TestLowLevelReadWrite(t *testing.T) {
  160. // Create a new record and write it
  161. sf, err := NewDefaultStorageFile(DBDir+"/test3", true)
  162. if err != nil {
  163. t.Error(err.Error())
  164. return
  165. }
  166. record := sf.createRecord(1)
  167. record.WriteSingleByte(5, 0x42)
  168. oldfiles := sf.files
  169. sf.name = DBDir + "/" + InvalidFileName
  170. sf.files = make([]*os.File, 0)
  171. _, err = sf.Get(1)
  172. if err == nil {
  173. t.Error("Invalid filename should cause an error")
  174. return
  175. }
  176. err = sf.writeRecord(record)
  177. if err == nil {
  178. t.Error("Invalid filename should cause an error")
  179. return
  180. }
  181. sf.name = DBDir + "/test3"
  182. sf.files = oldfiles
  183. err = sf.writeRecord(record)
  184. if err != nil {
  185. t.Error("Writing with a correct name should succeed", err)
  186. return
  187. }
  188. sf.Close()
  189. sf, err = NewDefaultStorageFile(DBDir+"/test3", true)
  190. if err != nil {
  191. t.Error(err.Error())
  192. return
  193. }
  194. record = sf.createRecord(1)
  195. oldfiles = sf.files
  196. sf.name = DBDir + "/" + InvalidFileName
  197. sf.files = make([]*os.File, 0)
  198. record.data = nil
  199. err = sf.readRecord(record)
  200. if sfe, ok := err.(*StorageFileError); !ok || sfe.Type != ErrNilData {
  201. t.Error("Nil pointer in record data should cause an error")
  202. return
  203. }
  204. record.ClearData()
  205. err = sf.readRecord(record)
  206. if err == nil {
  207. t.Error("Invalid filename should cause an error")
  208. return
  209. }
  210. sf.name = DBDir + "/test3"
  211. sf.files = oldfiles
  212. oldrecordSize := sf.recordSize
  213. sf.recordSize = DefaultRecordSize - 1
  214. testReadRecordPanic(t, sf, record)
  215. sf.recordSize = oldrecordSize
  216. err = sf.readRecord(record)
  217. if err != nil {
  218. t.Error("Reading with a correct name should succeed")
  219. return
  220. }
  221. sf.Close()
  222. if record.ReadSingleByte(5) != 0x42 {
  223. t.Error("Couldn't read byte which was written before.")
  224. return
  225. }
  226. }
  227. func testReadRecordPanic(t *testing.T, sf *StorageFile, r *Record) {
  228. defer func() {
  229. if r := recover(); r == nil {
  230. t.Error("Changing of the record size did not cause a panic.")
  231. }
  232. }()
  233. sf.readRecord(r)
  234. }
  235. func TestHighLevelGetRelease(t *testing.T) {
  236. // Create some records and write to them
  237. sf, err := NewDefaultStorageFile(DBDir+"/test4", true)
  238. if err != nil {
  239. t.Error(err.Error())
  240. return
  241. }
  242. // Get records and check that the expected files are there
  243. record1, err := sf.Get(1)
  244. if err != nil {
  245. t.Error(err)
  246. return
  247. }
  248. checkPath(t, "test4.0")
  249. checkMap(t, &sf.inUse, record1.ID(), true, "Record1", "in use")
  250. record2, err := sf.Get((DefaultFileSize/DefaultRecordSize)*4 + 5)
  251. if err != nil {
  252. t.Error(err)
  253. return
  254. }
  255. checkPath(t, "test4.4")
  256. checkMap(t, &sf.inUse, record2.ID(), true, "Record2", "in use")
  257. record3, err := sf.Get(2)
  258. if err != nil {
  259. t.Error(err)
  260. return
  261. }
  262. checkPath(t, "test4.0")
  263. // Make sure the retrieved records are marked in use
  264. checkMap(t, &sf.inUse, record3.ID(), true, "Record3", "in use")
  265. checkMap(t, &sf.free, record1.ID(), false, "Record1", "in use")
  266. checkMap(t, &sf.free, record2.ID(), false, "Record2", "in use")
  267. checkMap(t, &sf.free, record3.ID(), false, "Record3", "in use")
  268. // Now use the records and release them
  269. record1.WriteUInt16(2, 0x4268)
  270. record1.WriteUInt16(10, 0x66)
  271. record2.WriteInt32(11, -0x7654321)
  272. sf.ReleaseInUse(record2)
  273. // A rollback should have no consequences
  274. sf.Rollback()
  275. // Check that the records have been released and scheduled for write
  276. // (i.e. they are in the dirty table)
  277. checkMap(t, &sf.dirty, record2.ID(), true, "Record2", "dirty")
  278. checkMap(t, &sf.inUse, record2.ID(), false, "Record2", "in use")
  279. _, err = sf.Get((DefaultFileSize/DefaultRecordSize)*4 + 5)
  280. if err != nil {
  281. t.Error(err)
  282. return
  283. }
  284. checkMap(t, &sf.dirty, record2.ID(), false, "Record2", "dirty")
  285. checkMap(t, &sf.inUse, record2.ID(), true, "Record2", "in use")
  286. sf.ReleaseInUse(record1)
  287. checkMap(t, &sf.dirty, record1.ID(), true, "Record1", "dirty")
  288. checkMap(t, &sf.inUse, record1.ID(), false, "Record1", "in use")
  289. checkMap(t, &sf.dirty, record2.ID(), false, "Record2", "dirty")
  290. checkMap(t, &sf.inUse, record2.ID(), true, "Record2", "in use")
  291. checkMap(t, &sf.dirty, record3.ID(), false, "Record3", "dirty")
  292. checkMap(t, &sf.inUse, record3.ID(), true, "Record3", "in use")
  293. sf.ReleaseInUseID(record2.ID(), true)
  294. checkMap(t, &sf.dirty, record1.ID(), true, "Record1", "dirty")
  295. checkMap(t, &sf.inUse, record1.ID(), false, "Record1", "in use")
  296. checkMap(t, &sf.dirty, record2.ID(), true, "Record2", "dirty")
  297. checkMap(t, &sf.inUse, record2.ID(), false, "Record2", "in use")
  298. checkMap(t, &sf.dirty, record3.ID(), false, "Record3", "dirty")
  299. checkMap(t, &sf.inUse, record3.ID(), true, "Record3", "in use")
  300. err = sf.Flush()
  301. if sfe, ok := err.(*StorageFileError); !ok || sfe.Type != ErrInUse {
  302. t.Error("StorageFile should complain about records being in use")
  303. }
  304. record4, err := sf.Get(5)
  305. if err != nil {
  306. t.Error(err)
  307. return
  308. }
  309. sf.ReleaseInUse(record3)
  310. checkMap(t, &sf.dirty, record1.ID(), true, "Record1", "dirty")
  311. checkMap(t, &sf.inUse, record1.ID(), false, "Record1", "in use")
  312. checkMap(t, &sf.dirty, record2.ID(), true, "Record2", "dirty")
  313. checkMap(t, &sf.inUse, record2.ID(), false, "Record2", "in use")
  314. // Check that a record which has not been written to is put into the
  315. // free map
  316. checkMap(t, &sf.free, record3.ID(), true, "Record3", "free")
  317. checkMap(t, &sf.dirty, record3.ID(), false, "Record3", "dirty")
  318. checkMap(t, &sf.inUse, record3.ID(), false, "Record3", "in use")
  319. // Test string representation of the StorageFile
  320. if sf.String() != "Storage File: storagefiletest/test4 "+
  321. "(transDisabled:true recordSize:4096 maxFileSize:9999998976)\n"+
  322. "====\n"+
  323. "Free Records: 2\n"+
  324. "InUse Records: 5\n"+
  325. "InTrans Records: \n"+
  326. "Dirty Records: 1, 9765629\n"+
  327. "Open files: storagefiletest/test4.0 (0), storagefiletest/test4.4 (4)\n"+
  328. "====\n" {
  329. t.Error("Unexpected string representation of StorageFile:", sf.String())
  330. }
  331. sf.ReleaseInUse(record4)
  332. // Check that after the changes have been written to disk that
  333. // all records are in the free map
  334. sf.Flush()
  335. checkMap(t, &sf.dirty, record1.ID(), false, "Record1", "dirty")
  336. checkMap(t, &sf.inUse, record1.ID(), false, "Record1", "in use")
  337. checkMap(t, &sf.free, record1.ID(), true, "Record1", "in use")
  338. checkMap(t, &sf.dirty, record2.ID(), false, "Record2", "dirty")
  339. checkMap(t, &sf.inUse, record2.ID(), false, "Record2", "in use")
  340. checkMap(t, &sf.free, record2.ID(), true, "Record2", "in use")
  341. checkMap(t, &sf.dirty, record3.ID(), false, "Record3", "dirty")
  342. checkMap(t, &sf.inUse, record3.ID(), false, "Record3", "in use")
  343. checkMap(t, &sf.free, record3.ID(), true, "Record3", "in use")
  344. _, err = sf.Get((DefaultFileSize/DefaultRecordSize)*4 + 5)
  345. if err != nil {
  346. t.Error(err)
  347. return
  348. }
  349. checkMap(t, &sf.free, record2.ID(), false, "Record2", "free")
  350. checkMap(t, &sf.inUse, record2.ID(), true, "Record2", "in use")
  351. sf.ReleaseInUse(record2)
  352. checkMap(t, &sf.free, record2.ID(), true, "Record2", "free")
  353. checkMap(t, &sf.inUse, record2.ID(), false, "Record2", "in use")
  354. sf.Close()
  355. // Open the storage file again with a different object and
  356. // try to read back the written
  357. sf, err = NewDefaultStorageFile(DBDir+"/test4", true)
  358. if err != nil {
  359. t.Error(err.Error())
  360. return
  361. }
  362. record1, err = sf.Get(1)
  363. if err != nil {
  364. t.Error(err)
  365. return
  366. }
  367. // Test that requesting a record twice without releasing it causes an error.
  368. _, err = sf.Get(1)
  369. if sfe, ok := err.(*StorageFileError); !ok || sfe.Type != ErrAlreadyInUse {
  370. t.Error("Requesting a record which is already in use should cause an error")
  371. }
  372. if err.Error() != "Record is already in-use (storagefiletest/test4 - Record 1)" {
  373. t.Error("Unexpected error string:", err)
  374. return
  375. }
  376. checkMap(t, &sf.inUse, record1.ID(), true, "Record1", "in use")
  377. record2, err = sf.Get((DefaultFileSize/DefaultRecordSize)*4 + 5)
  378. if err != nil {
  379. t.Error(err)
  380. return
  381. }
  382. checkMap(t, &sf.inUse, record2.ID(), true, "Record2", "in use")
  383. // Check that we can read back the written data
  384. if d := record1.ReadUInt16(2); d != 0x4268 {
  385. t.Error("Expected value in record1 not found")
  386. return
  387. }
  388. if d := record1.ReadUInt16(10); d != 0x66 {
  389. t.Error("Expected value in record1 not found")
  390. return
  391. }
  392. if d := record2.ReadInt32(11); d != -0x7654321 {
  393. t.Error("Expected value in record1 not found", d)
  394. return
  395. }
  396. sf.ReleaseInUse(record1)
  397. // Since record3 was just created and is empty it should not be in use
  398. record3 = sf.createRecord(5)
  399. checkMap(t, &sf.inUse, record3.ID(), false, "Record3", "in use")
  400. // Record2 has been used
  401. checkMap(t, &sf.inUse, record2.ID(), true, "Record2", "in use")
  402. // An attempt to close the file should return an error
  403. err = sf.Close()
  404. if sfe, ok := err.(*StorageFileError); !ok || sfe.Type != ErrInUse {
  405. t.Error("Attempting to close a StorageFile with records in use should " +
  406. "return an error")
  407. return
  408. }
  409. sf.ReleaseInUse(record2)
  410. err = sf.Close()
  411. if err != nil {
  412. t.Error(err)
  413. return
  414. }
  415. }
  416. func checkPath(t *testing.T, path string) {
  417. res, err := fileutil.PathExists(DBDir + "/" + path)
  418. if err != nil {
  419. t.Error(err)
  420. }
  421. if !res {
  422. t.Error("Expected db file", path, "does not exist")
  423. }
  424. }
  425. func checkMap(t *testing.T, mapvar *map[uint64]*Record, id uint64, expected bool,
  426. name string, mapname string) {
  427. if _, ok := (*mapvar)[id]; expected != ok {
  428. if expected {
  429. t.Error(name, "should be", mapname)
  430. } else {
  431. t.Error(name, "should not be", mapname)
  432. }
  433. }
  434. }
  435. func TestFlushingClosing(t *testing.T) {
  436. sf, err := NewDefaultStorageFile(DBDir+"/test5", true)
  437. if err != nil {
  438. t.Error(err.Error())
  439. return
  440. }
  441. if sf.Flush() != nil {
  442. t.Error("Flushing an unused file should not cause an error")
  443. return
  444. }
  445. record, err := sf.Get(1)
  446. if err != nil {
  447. t.Error(err)
  448. return
  449. }
  450. record.WriteSingleByte(0, 0)
  451. err = sf.Flush()
  452. if sfe, ok := err.(*StorageFileError); !ok || sfe.Type != ErrInUse {
  453. t.Error("Flushing should not be allowed while records are in use")
  454. return
  455. }
  456. sf.ReleaseInUse(nil) // This should not cause a panic
  457. err = sf.ReleaseInUseID(5000, true)
  458. if sfe, ok := err.(*StorageFileError); !ok || sfe.Type != ErrNotInUse {
  459. t.Error("It should not be possible to release records which are not in use")
  460. return
  461. }
  462. record.ClearDirty()
  463. sf.ReleaseInUseID(1, true)
  464. if !record.Dirty() {
  465. t.Error("Record should be marked as dirty after it was released as dirty")
  466. return
  467. }
  468. testReleasePanic(t, sf, record)
  469. // Modifying the id at this point causes unspecified behaviour. Once a
  470. // record was released it should not be modified.
  471. record.data = nil
  472. err = sf.Flush()
  473. if sfe, ok := err.(*StorageFileError); !ok || sfe.Type != ErrNilData {
  474. t.Error("It should not be possible to flush a record with an invalid id to disk")
  475. return
  476. }
  477. checkMap(t, &sf.dirty, 1, true, "Record1", "dirty")
  478. // Get the record again and discard it
  479. record, err = sf.Get(1)
  480. if err != nil {
  481. t.Error(err)
  482. return
  483. }
  484. checkMap(t, &sf.inUse, 1, true, "Record1", "in use")
  485. // Need to correct the id otherwise the discarding will not work
  486. record.SetID(1)
  487. sf.Discard(nil) // This should not cause a panic
  488. sf.Discard(record)
  489. checkMap(t, &sf.dirty, 1, false, "Record1", "dirty")
  490. checkMap(t, &sf.inUse, 1, false, "Record1", "in use")
  491. sf.Sync() // This should just complete and not cause a panic
  492. record, err = sf.Get(5)
  493. if err != nil {
  494. t.Error(err)
  495. return
  496. }
  497. // This should be possible even if the record is not dirty at all
  498. sf.ReleaseInUseID(record.ID(), true)
  499. checkMap(t, &sf.dirty, 5, true, "Record1", "dirty")
  500. recordData := record.data
  501. record.data = nil
  502. err = sf.Close()
  503. if sfe, ok := err.(*StorageFileError); !ok || sfe.Type != ErrNilData {
  504. t.Error("Closing with a dirty record with negative id should not be possible", err)
  505. return
  506. }
  507. record.data = recordData
  508. err = sf.Close()
  509. if err != nil {
  510. t.Error(err)
  511. return
  512. }
  513. // Make sure the close call did flush dirty records
  514. checkMap(t, &sf.dirty, 5, false, "Record1", "dirty")
  515. }
  516. func testReleasePanic(t *testing.T, sf *StorageFile, r *Record) {
  517. defer func() {
  518. if r := recover(); r == nil {
  519. t.Error("Releaseing a record multiple times without using it " +
  520. "did not cause a panic.")
  521. }
  522. }()
  523. sf.ReleaseInUse(r)
  524. }