| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526 | /* * 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 pagingimport (	"flag"	"fmt"	"os"	"testing"	"devt.de/krotik/common/fileutil"	"devt.de/krotik/eliasdb/storage/file"	"devt.de/krotik/eliasdb/storage/paging/view")const DBDIR = "pagingtest"const InvalidFileName = "**" + string(0x0)// Main function for all tests in this packagefunc 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 TestPagedStorageFileInitialisation(t *testing.T) {	sf, err := file.NewDefaultStorageFile(DBDIR+"/test1", true)	if err != nil {		t.Error(err.Error())		return	}	record, err := sf.Get(0)	if err != nil {		t.Error(err)		return	}	_, err = NewPagedStorageFile(sf)	if err != file.ErrAlreadyInUse {		t.Error("Init of PageStorageFile should fail if header record is not available")		return	}	sf.ReleaseInUse(record)	psf, err := NewPagedStorageFile(sf)	if err != nil {		t.Error(err)		return	}	if psf.StorageFile() != sf {		t.Error("Unexpected StorageFile contained in PagedStorageFile")		return	}	if psf.Header().record != record {		t.Error("Unexpected Record contained in PagedStorageFileHeader")		return	}	if err := psf.Close(); err != nil {		t.Error(err)		return	}}func TestPagedStorageFilePageManagement(t *testing.T) {	sf, err := file.NewDefaultStorageFile(DBDIR+"/test2", true)	if err != nil {		t.Error(err.Error())		return	}	psf, err := NewPagedStorageFile(sf)	if err != nil {		t.Error(err)		return	}	if psf.FreePage(0) != ErrHeader {		t.Error("Attempting to free the header record should cause a specific error")		return	}	if _, err := psf.AllocatePage(view.TypeFreePage); err != ErrFreePage {		t.Error("It should not be possible to allocate a free page")		return	}	plist := make([]uint64, 0, 5)	for i := 0; i < 5; i++ {		p, err := psf.AllocatePage(view.TypeDataPage)		if err != nil {			t.Error(err)		}		plist = append(plist, p)	}	record, err := sf.Get(3)	if err != nil {		t.Error(err)		return	}	if record.ReadUInt16(0) != 0x1991 {		t.Error("Unexpected page header")		return	}	sf.ReleaseInUse(record)	if psf.First(view.TypeDataPage) != plist[0] {		t.Error("Unexpected first page")		return	}	if psf.Last(view.TypeDataPage) != plist[len(plist)-1] {		t.Error("Unexpected last page")		return	}	if psf.First(view.TypeFreePage) != 0 {		t.Error("Unexpected first free page - no free pages should be available")		return	}	record, err = sf.Get(3)	if err != nil {		t.Error(err)		return	}	if err := psf.FreePage(3); err != file.ErrAlreadyInUse {		t.Error(err)		return	}	sf.ReleaseInUse(record)	if err := psf.FreePage(3); err != nil {		t.Error(err)		return	}	if err := psf.FreePage(3); err != ErrFreePage {		t.Error("Attempting to free a page which is already free should cause an error")		return	}	if psf.First(view.TypeFreePage) != 3 {		t.Error("Unexpected first free page after freeing a page")		return	}	checkPrevAndNext(t, psf, 3, 0, 0)	if psf.FreePage(3) != ErrFreePage {		t.Error("Attempting to free a free page should not be possible")		return	}	if err := psf.FreePage(5); err != nil {		t.Error(err)		return	}	checkPrevAndNext(t, psf, 5, 0, 3)	// Check that the second element has still the prev pointer pointing to 0	checkPrevAndNext(t, psf, 3, 0, 0)	// Check that the pointers for DATA pages are correct	checkPrevAndNext(t, psf, 1, 0, 2)	checkPrevAndNext(t, psf, 2, 1, 4)	checkPrevAndNext(t, psf, 4, 2, 0)	ptr, err := psf.AllocatePage(view.TypeTranslationPage)	if err != nil {		t.Error(err)		return	}	if ptr != 5 {		t.Error("New allocated page should be the last freed page")		return	}	// Check data pointers	checkPrevAndNext(t, psf, 1, 0, 2)	checkPrevAndNext(t, psf, 2, 1, 4)	checkPrevAndNext(t, psf, 4, 2, 0)	// Check free pointers	checkPrevAndNext(t, psf, 3, 0, 0)	// Check translation pointers	checkPrevAndNext(t, psf, 5, 0, 0)	// Check the newly allocated page	record, err = sf.Get(5)	if err != nil {		t.Error(err)		return	}	// Record should have the translation page header	if record.ReadUInt16(0) != 0x1992 {		t.Error("Unexpected page header")		return	}	pv := view.GetPageView(record)	if pv.String() != "PageView: 5 (type:2 previous page:0 next page:0)" {		t.Error("Unexpected pageview was returned:", pv)		return	}	sf.ReleaseInUse(record)	// Test allocation error - Using record 3 causes an error when getting the	// first element of the free list. Using record 5 causes an error when	// inserting the newly allocated record into the list	record, err = sf.Get(3)	if err != nil {		t.Error(err)		return	}	_, err = psf.AllocatePage(view.TypeTranslationPage)	if err != file.ErrAlreadyInUse {		t.Error(err)		return	}	sf.ReleaseInUse(record)	record, err = sf.Get(5)	if err != nil {		t.Error(err)		return	}	_, err = psf.AllocatePage(view.TypeTranslationPage)	if err != file.ErrAlreadyInUse {		t.Error(err)		return	}	sf.ReleaseInUse(record)	_, err = psf.AllocatePage(view.TypeTranslationPage)	if err != nil {		t.Error(err)		return	}	record, err = sf.Get(7)	if err != nil {		t.Error(err)		return	}	_, err = psf.AllocatePage(view.TypeTranslationPage)	if err != file.ErrAlreadyInUse {		t.Error(err)	}	sf.ReleaseInUse(record)	// Check data pointers	checkPrevAndNext(t, psf, 1, 0, 2)	checkPrevAndNext(t, psf, 2, 1, 4)	checkPrevAndNext(t, psf, 4, 2, 0)	// Check error case next record is in use	record, err = sf.Get(4)	if err != nil {		t.Error(err)		return	}	// Check we can't get Prev info when record is in use	if _, err := psf.Prev(4); err != file.ErrAlreadyInUse {		t.Error(err)		return	}	if err := psf.FreePage(2); err != file.ErrAlreadyInUse {		t.Error(err)		return	}	sf.ReleaseInUse(record)	// Check error case previous record is in use	record, err = sf.Get(2)	if err != nil {		t.Error(err)		return	}	if err := psf.FreePage(4); err != file.ErrAlreadyInUse {		t.Error(err)		return	}	sf.ReleaseInUse(record)	if err := psf.FreePage(1); err != nil {		t.Error(err)		return	}	record, err = sf.Get(2)	if err != nil {		t.Error(err)		return	}	if err := psf.Close(); err != file.ErrInUse {		t.Error(err)		return	}	sf.ReleaseInUse(record)	if err := psf.Close(); err != nil {		t.Error(err)		return	}}func TestPagedStorageFileTransactionPageManagement(t *testing.T) {	sf, err := file.NewDefaultStorageFile(DBDIR+"/test3", false)	if err != nil {		t.Error(err.Error())		return	}	psf, err := NewPagedStorageFile(sf)	if err != nil {		t.Error(err)		return	}	if err := psf.Rollback(); err != nil {		t.Error(err)		return	}	plist := make([]uint64, 0, 5)	for i := 0; i < 5; i++ {		p, err := psf.AllocatePage(view.TypeDataPage)		if err != nil {			t.Error(err)		}		plist = append(plist, p)	}	if err := psf.Flush(); err != nil {		t.Error(err)		return	}	// Check that the pointers for DATA pages are correct	checkPrevAndNext(t, psf, 1, 0, 2)	checkPrevAndNext(t, psf, 2, 1, 3)	checkPrevAndNext(t, psf, 3, 2, 4)	checkPrevAndNext(t, psf, 4, 3, 5)	checkPrevAndNext(t, psf, 5, 4, 0)	// Now break it in a way that the datastructure is broken	record, err := sf.Get(2)	if err != nil {		t.Error(err)		return	}	if err := psf.FreePage(3); err != file.ErrAlreadyInUse {		t.Error(err)		return	}	sf.ReleaseInUse(record)	// At this point page 3 is marked as free but the data pointers	// of page 2 and 4 have not been updated	checkPrevAndNext(t, psf, 1, 0, 2)	checkPrevAndNext(t, psf, 2, 1, 3)	checkPrevAndNext(t, psf, 3, 0, 0)	checkPrevAndNext(t, psf, 4, 3, 5)	checkPrevAndNext(t, psf, 5, 4, 0)	if err := psf.Rollback(); err != nil {		t.Error(err)		return	}	checkPrevAndNext(t, psf, 1, 0, 2)	checkPrevAndNext(t, psf, 2, 1, 3)	checkPrevAndNext(t, psf, 3, 2, 4)	checkPrevAndNext(t, psf, 4, 3, 5)	checkPrevAndNext(t, psf, 5, 4, 0)	if err := psf.Close(); err != nil {		t.Error(err)		return	}	// Test error cases	sf, err = file.NewDefaultStorageFile(DBDIR+"/test3-1", false)	if err != nil {		t.Error(err.Error())		return	}	psf, err = NewPagedStorageFile(sf)	if err != nil {		t.Error(err)		return	}	record, err = sf.Get(4)	if err := psf.Flush(); err != file.ErrInUse {		t.Error(err)		return	}	sf.ReleaseInUse(record)	psf.header.record = nil	if err := psf.Flush(); err != file.ErrInUse {		t.Error(err)		return	}	sf.ReleaseInUseID(0, false)	record, err = sf.Get(4)	if err := psf.Rollback(); err != file.ErrInUse {		t.Error(err)		return	}	sf.ReleaseInUse(record)	psf.header.record = nil	sf.ReleaseInUseID(0, false)	if err := psf.Close(); err != nil {		t.Error(err)		return	}}func checkPrevAndNext(t *testing.T, psf *PagedStorageFile, rid uint64,	prev uint64, next uint64) {	p, err := psf.Prev(rid)	if err != nil {		t.Error(err)		return	}	if p != prev {		t.Error("Unexpected previous pointer:", p, "expected:", prev)		return	}	n, err := psf.Next(rid)	if err != nil {		t.Error(err)		return	}	if n != next {		t.Error("Unexpected next pointer:", n, "expected:", next)		return	}}
 |