| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667 | /* * EliasDB * * Copyright 2016 Matthias Ladkau. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */package fileimport (	"flag"	"fmt"	"os"	"testing"	"devt.de/krotik/common/fileutil")const DBDir = "storagefiletest"const InvalidFileName = "**" + string(0x0)func TestMain(m *testing.M) {	flag.Parse()	// Setup	if res, _ := fileutil.PathExists(DBDir); res {		os.RemoveAll(DBDir)	}	err := os.Mkdir(DBDir, 0770)	if err != nil {		fmt.Print("Could not create test directory:", err.Error())		os.Exit(1)	}	// Run the tests	res := m.Run()	// Teardown	err = os.RemoveAll(DBDir)	if err != nil {		fmt.Print("Could not remove test directory:", err.Error())	}	os.Exit(res)}func TestStorageFileInitialisation(t *testing.T) {	// \0 and / are the only illegal characters for filenames in unix	sf, err := NewDefaultStorageFile(DBDir+"/"+InvalidFileName, true)	if err == nil {		t.Error("Invalid name should cause an error")		return	}	sf, err = NewDefaultStorageFile(DBDir+"/test1", true)	if err != nil {		t.Error(err.Error())		return	}	if sf.Name() != DBDir+"/test1" {		t.Error("Unexpected name of StorageFile:", sf.Name())		return	}	if sf.RecordSize() != DefaultRecordSize {		t.Error("Unexpected record size:", sf.RecordSize())		return	}	defer sf.Close()	res, err := fileutil.PathExists(DBDir + "/test1.0")	if err != nil {		t.Error(err)		return	}	if !res {		t.Error("Expected db file test1.0 does not exist")		return	}	if len(sf.files) != 1 {		t.Error("Unexpected number of files in StorageFile:", sf.files)		return	}}func TestGetFile(t *testing.T) {	sf := &StorageFile{DBDir + "/test2", true, 10, 10, nil, nil, nil, nil,		make([]*os.File, 0), nil}	defer sf.Close()	file, err := sf.getFile(0)	if err != nil {		t.Error(err.Error())		return	}	if file.Name() != DBDir+"/test2.0" {		t.Error("Unexpected file from getFile")		return	}	checkFilesArray(t, sf, 1, 0, DBDir+"/test2.0")	file, err = sf.getFile(42)	if err != nil {		t.Error(err.Error())		return	}	if file.Name() != DBDir+"/test2.4" {		t.Error("Unexpected file from getFile")		return	}	checkFilesArray(t, sf, 5, 0, DBDir+"/test2.0")	checkFilesArray(t, sf, 5, 1, "")	checkFilesArray(t, sf, 5, 2, "")	checkFilesArray(t, sf, 5, 3, "")	checkFilesArray(t, sf, 5, 4, DBDir+"/test2.4")	file, err = sf.getFile(25)	if err != nil {		t.Error(err.Error())		return	}	if file.Name() != DBDir+"/test2.2" {		t.Error("Unexpected file from getFile")		return	}	checkFilesArray(t, sf, 5, 0, DBDir+"/test2.0")	checkFilesArray(t, sf, 5, 1, "")	checkFilesArray(t, sf, 5, 2, DBDir+"/test2.2")	checkFilesArray(t, sf, 5, 3, "")	checkFilesArray(t, sf, 5, 4, DBDir+"/test2.4")	file, err = sf.getFile(11)	if err != nil {		t.Error(err.Error())		return	}	if file.Name() != DBDir+"/test2.1" {		t.Error("Unexpected file from getFile")		return	}	checkFilesArray(t, sf, 5, 0, DBDir+"/test2.0")	checkFilesArray(t, sf, 5, 1, DBDir+"/test2.1")	checkFilesArray(t, sf, 5, 2, DBDir+"/test2.2")	checkFilesArray(t, sf, 5, 3, "")	checkFilesArray(t, sf, 5, 4, DBDir+"/test2.4")	file, err = sf.getFile(49)	if err != nil {		t.Error(err.Error())		return	}	if file.Name() != DBDir+"/test2.4" {		t.Error("Unexpected file from getFile")		return	}	checkFilesArray(t, sf, 5, 0, DBDir+"/test2.0")	checkFilesArray(t, sf, 5, 1, DBDir+"/test2.1")	checkFilesArray(t, sf, 5, 2, DBDir+"/test2.2")	checkFilesArray(t, sf, 5, 3, "")	checkFilesArray(t, sf, 5, 4, DBDir+"/test2.4")}func checkFilesArray(t *testing.T, sf *StorageFile, explen int, pos int, name string) {	if len(sf.files) != explen {		t.Error("Unexpected files array:", sf.files, " expected size:", explen)	}	file := sf.files[pos]	if name == "" && file != nil {		t.Error("Unexpected file at pos:", pos, " name:", file.Name())	} else if name != "" && file == nil {		t.Error("Unexpected nil pointer at pos:", pos, " expected name:", name)	} else if file != nil && name != file.Name() {		t.Error("Unexpected file at pos:", pos, " name:", file.Name(), " expected name:", name)	}}func TestLowLevelReadWrite(t *testing.T) {	// Create a new record and write it	sf, err := NewDefaultStorageFile(DBDir+"/test3", true)	if err != nil {		t.Error(err.Error())		return	}	record := sf.createRecord(1)	record.WriteSingleByte(5, 0x42)	oldfiles := sf.files	sf.name = DBDir + "/" + InvalidFileName	sf.files = make([]*os.File, 0)	_, err = sf.Get(1)	if err == nil {		t.Error("Invalid filename should cause an error")		return	}	err = sf.writeRecord(record)	if err == nil {		t.Error("Invalid filename should cause an error")		return	}	sf.name = DBDir + "/test3"	sf.files = oldfiles	err = sf.writeRecord(record)	if err != nil {		t.Error("Writing with a correct name should succeed", err)		return	}	sf.Close()	sf, err = NewDefaultStorageFile(DBDir+"/test3", true)	if err != nil {		t.Error(err.Error())		return	}	record = sf.createRecord(1)	oldfiles = sf.files	sf.name = DBDir + "/" + InvalidFileName	sf.files = make([]*os.File, 0)	record.data = nil	err = sf.readRecord(record)	if err != ErrNilData {		t.Error("Nil pointer in record data should cause an error")		return	}	record.ClearData()	err = sf.readRecord(record)	if err == nil {		t.Error("Invalid filename should cause an error")		return	}	sf.name = DBDir + "/test3"	sf.files = oldfiles	oldrecordSize := sf.recordSize	sf.recordSize = DefaultRecordSize - 1	testReadRecordPanic(t, sf, record)	sf.recordSize = oldrecordSize	err = sf.readRecord(record)	if err != nil {		t.Error("Reading with a correct name should succeed")		return	}	sf.Close()	if record.ReadSingleByte(5) != 0x42 {		t.Error("Couldn't read byte which was written before.")		return	}}func testReadRecordPanic(t *testing.T, sf *StorageFile, r *Record) {	defer func() {		if r := recover(); r == nil {			t.Error("Changing of the record size did not cause a panic.")		}	}()	sf.readRecord(r)}func TestHighLevelGetRelease(t *testing.T) {	// Create some records and write to them	sf, err := NewDefaultStorageFile(DBDir+"/test4", true)	if err != nil {		t.Error(err.Error())		return	}	// Get records and check that the expected files are there	record1, err := sf.Get(1)	if err != nil {		t.Error(err)		return	}	checkPath(t, "test4.0")	checkMap(t, &sf.inUse, record1.ID(), true, "Record1", "in use")	record2, err := sf.Get((DefaultFileSize/DefaultRecordSize)*4 + 5)	if err != nil {		t.Error(err)		return	}	checkPath(t, "test4.4")	checkMap(t, &sf.inUse, record2.ID(), true, "Record2", "in use")	record3, err := sf.Get(2)	if err != nil {		t.Error(err)		return	}	checkPath(t, "test4.0")	// Make sure the retrieved records are marked in use	checkMap(t, &sf.inUse, record3.ID(), true, "Record3", "in use")	checkMap(t, &sf.free, record1.ID(), false, "Record1", "in use")	checkMap(t, &sf.free, record2.ID(), false, "Record2", "in use")	checkMap(t, &sf.free, record3.ID(), false, "Record3", "in use")	// Now use the records and release them	record1.WriteUInt16(2, 0x4268)	record1.WriteUInt16(10, 0x66)	record2.WriteInt32(11, -0x7654321)	sf.ReleaseInUse(record2)	// A rollback should have no consequences	sf.Rollback()	// Check that the records have been released and scheduled for write	// (i.e. they are in the dirty table)	checkMap(t, &sf.dirty, record2.ID(), true, "Record2", "dirty")	checkMap(t, &sf.inUse, record2.ID(), false, "Record2", "in use")	_, err = sf.Get((DefaultFileSize/DefaultRecordSize)*4 + 5)	if err != nil {		t.Error(err)		return	}	checkMap(t, &sf.dirty, record2.ID(), false, "Record2", "dirty")	checkMap(t, &sf.inUse, record2.ID(), true, "Record2", "in use")	sf.ReleaseInUse(record1)	checkMap(t, &sf.dirty, record1.ID(), true, "Record1", "dirty")	checkMap(t, &sf.inUse, record1.ID(), false, "Record1", "in use")	checkMap(t, &sf.dirty, record2.ID(), false, "Record2", "dirty")	checkMap(t, &sf.inUse, record2.ID(), true, "Record2", "in use")	checkMap(t, &sf.dirty, record3.ID(), false, "Record3", "dirty")	checkMap(t, &sf.inUse, record3.ID(), true, "Record3", "in use")	sf.ReleaseInUseID(record2.ID(), true)	checkMap(t, &sf.dirty, record1.ID(), true, "Record1", "dirty")	checkMap(t, &sf.inUse, record1.ID(), false, "Record1", "in use")	checkMap(t, &sf.dirty, record2.ID(), true, "Record2", "dirty")	checkMap(t, &sf.inUse, record2.ID(), false, "Record2", "in use")	checkMap(t, &sf.dirty, record3.ID(), false, "Record3", "dirty")	checkMap(t, &sf.inUse, record3.ID(), true, "Record3", "in use")	if err = sf.Flush(); err != ErrInUse {		t.Error("StorageFile should complain about records being in use")	}	record4, err := sf.Get(5)	if err != nil {		t.Error(err)		return	}	sf.ReleaseInUse(record3)	checkMap(t, &sf.dirty, record1.ID(), true, "Record1", "dirty")	checkMap(t, &sf.inUse, record1.ID(), false, "Record1", "in use")	checkMap(t, &sf.dirty, record2.ID(), true, "Record2", "dirty")	checkMap(t, &sf.inUse, record2.ID(), false, "Record2", "in use")	// Check that a record which has not been written to is put into the	// free map	checkMap(t, &sf.free, record3.ID(), true, "Record3", "free")	checkMap(t, &sf.dirty, record3.ID(), false, "Record3", "dirty")	checkMap(t, &sf.inUse, record3.ID(), false, "Record3", "in use")	// Test string representation of the StorageFile	if sf.String() != "Storage File: storagefiletest/test4 "+		"(transDisabled:true recordSize:4096 maxFileSize:9999998976)\n"+		"====\n"+		"Free Records: 2\n"+		"InUse Records: 5\n"+		"InTrans Records: \n"+		"Dirty Records: 1, 9765629\n"+		"Open files: storagefiletest/test4.0 (0), storagefiletest/test4.4 (4)\n"+		"====\n" {		t.Error("Unexpected string representation of StorageFile:", sf.String())	}	sf.ReleaseInUse(record4)	// Check that after the changes have been written to disk that	// all records are in the free map	sf.Flush()	checkMap(t, &sf.dirty, record1.ID(), false, "Record1", "dirty")	checkMap(t, &sf.inUse, record1.ID(), false, "Record1", "in use")	checkMap(t, &sf.free, record1.ID(), true, "Record1", "in use")	checkMap(t, &sf.dirty, record2.ID(), false, "Record2", "dirty")	checkMap(t, &sf.inUse, record2.ID(), false, "Record2", "in use")	checkMap(t, &sf.free, record2.ID(), true, "Record2", "in use")	checkMap(t, &sf.dirty, record3.ID(), false, "Record3", "dirty")	checkMap(t, &sf.inUse, record3.ID(), false, "Record3", "in use")	checkMap(t, &sf.free, record3.ID(), true, "Record3", "in use")	_, err = sf.Get((DefaultFileSize/DefaultRecordSize)*4 + 5)	if err != nil {		t.Error(err)		return	}	checkMap(t, &sf.free, record2.ID(), false, "Record2", "free")	checkMap(t, &sf.inUse, record2.ID(), true, "Record2", "in use")	sf.ReleaseInUse(record2)	checkMap(t, &sf.free, record2.ID(), true, "Record2", "free")	checkMap(t, &sf.inUse, record2.ID(), false, "Record2", "in use")	sf.Close()	// Open the storage file again with a different object and	// try to read back the written	sf, err = NewDefaultStorageFile(DBDir+"/test4", true)	if err != nil {		t.Error(err.Error())		return	}	record1, err = sf.Get(1)	if err != nil {		t.Error(err)		return	}	// Test that requesting a record twice without releasing it causes an error.	if _, err = sf.Get(1); err != ErrAlreadyInUse {		t.Error("Requesting a record which is already in use should cause an error")	}	if err.Error() != "Record is already in-use (storagefiletest/test4 - Record 1)" {		t.Error("Unexpected error string:", err)		return	}	checkMap(t, &sf.inUse, record1.ID(), true, "Record1", "in use")	record2, err = sf.Get((DefaultFileSize/DefaultRecordSize)*4 + 5)	if err != nil {		t.Error(err)		return	}	checkMap(t, &sf.inUse, record2.ID(), true, "Record2", "in use")	// Check that we can read back the written data	if d := record1.ReadUInt16(2); d != 0x4268 {		t.Error("Expected value in record1 not found")		return	}	if d := record1.ReadUInt16(10); d != 0x66 {		t.Error("Expected value in record1 not found")		return	}	if d := record2.ReadInt32(11); d != -0x7654321 {		t.Error("Expected value in record1 not found", d)		return	}	sf.ReleaseInUse(record1)	// Since record3 was just created and is empty it should not be in use	record3 = sf.createRecord(5)	checkMap(t, &sf.inUse, record3.ID(), false, "Record3", "in use")	// Record2 has been used	checkMap(t, &sf.inUse, record2.ID(), true, "Record2", "in use")	// An attempt to close the file should return an error	err = sf.Close()	if err != ErrInUse {		t.Error("Attempting to close a StorageFile with records in use should " +			"return an error")		return	}	sf.ReleaseInUse(record2)	err = sf.Close()	if err != nil {		t.Error(err)		return	}}func checkPath(t *testing.T, path string) {	res, err := fileutil.PathExists(DBDir + "/" + path)	if err != nil {		t.Error(err)	}	if !res {		t.Error("Expected db file", path, "does not exist")	}}func checkMap(t *testing.T, mapvar *map[uint64]*Record, id uint64, expected bool,	name string, mapname string) {	if _, ok := (*mapvar)[id]; expected != ok {		if expected {			t.Error(name, "should be", mapname)		} else {			t.Error(name, "should not be", mapname)		}	}}func TestFlushingClosing(t *testing.T) {	sf, err := NewDefaultStorageFile(DBDir+"/test5", true)	if err != nil {		t.Error(err.Error())		return	}	if sf.Flush() != nil {		t.Error("Flushing an unused file should not cause an error")		return	}	record, err := sf.Get(1)	if err != nil {		t.Error(err)		return	}	record.WriteSingleByte(0, 0)	if sf.Flush() != ErrInUse {		t.Error("Flushing should not be allowed while records are in use")		return	}	sf.ReleaseInUse(nil) // This should not cause a panic	if sf.ReleaseInUseID(5000, true) != ErrNotInUse {		t.Error("It should not be possible to release records which are not in use")		return	}	record.ClearDirty()	sf.ReleaseInUseID(1, true)	if !record.Dirty() {		t.Error("Record should be marked as dirty after it was released as dirty")		return	}	testReleasePanic(t, sf, record)	// Modifying the id at this point causes unspecified behaviour. Once a	// record was released it should not be modified.	record.data = nil	err = sf.Flush()	if err != ErrNilData {		t.Error("It should not be possible to flush a record with an invalid id to disk")		return	}	checkMap(t, &sf.dirty, 1, true, "Record1", "dirty")	// Get the record again and discard it	record, err = sf.Get(1)	if err != nil {		t.Error(err)		return	}	checkMap(t, &sf.inUse, 1, true, "Record1", "in use")	// Need to correct the id otherwise the discarding will not work	record.SetID(1)	sf.Discard(nil) // This should not cause a panic	sf.Discard(record)	checkMap(t, &sf.dirty, 1, false, "Record1", "dirty")	checkMap(t, &sf.inUse, 1, false, "Record1", "in use")	sf.Sync() // This should just complete and not cause a panic	record, err = sf.Get(5)	if err != nil {		t.Error(err)		return	}	// This should be possible even if the record is not dirty at all	sf.ReleaseInUseID(record.ID(), true)	checkMap(t, &sf.dirty, 5, true, "Record1", "dirty")	recordData := record.data	record.data = nil	if sf.Close() != ErrNilData {		t.Error("Closing with a dirty record with negative id should not be possible", err)		return	}	record.data = recordData	err = sf.Close()	if err != nil {		t.Error(err)		return	}	// Make sure the close call did flush dirty records	checkMap(t, &sf.dirty, 5, false, "Record1", "dirty")}func testReleasePanic(t *testing.T, sf *StorageFile, r *Record) {	defer func() {		if r := recover(); r == nil {			t.Error("Releaseing a record multiple times without using it " +				"did not cause a panic.")		}	}()	sf.ReleaseInUse(r)}
 |