<> <> <> <<>> <> <<>> DIRECTORY <> BasicTime USING [GetClockPulses, Pulses, PulsesToMicroseconds], Commander USING [Register, CommandProc], <> Convert USING [AppendChar, RopeFromInt], IO USING [STREAM, rope, PutF, PutFR, int, card, text, atom], LoganBerry, <> PrintEnglish USING [IntToEnglish], Random USING [RandomStream, Create, ChooseInt], Rope USING [ROPE, Equal] ; LoganBerryTest: CEDAR PROGRAM IMPORTS BasicTime, Commander, Convert, IO, LoganBerry, PrintEnglish, Random, Rope ~ BEGIN ROPE: TYPE ~ Rope.ROPE; dbFileName: ROPE _ "TestDB.schema"; database: LoganBerry.OpenDB; rs: Random.RandomStream_ Random.Create[range: 10000, seed: -1]; <<>> <> TestCase: TYPE = {create, enumerate, generate, deleteA, deleteB, replaceA, replaceB, readRandom, checkData, closeAndOpen, bidirectionalGenerate, invalidOps, compactA, compactB}; <> dbSize: CARDINAL _ 1000; -- size of test database, should be less than 10000 numReads: CARDINAL _ 100; numDeletes: CARDINAL _ 10; savedEntries: LIST OF LoganBerry.Entry; RunTest: PROC [n: TestCase, out: IO.STREAM _ NIL] RETURNS [failed: BOOLEAN _ FALSE] ~ { <> ENABLE LoganBerry.Error => { IF out # NIL THEN out.PutF["Error from LoganBerry: %g - %g\n", IO.atom[ec], IO.rope[explanation]]; failed _ TRUE; }; start, stop: BasicTime.Pulses; attr: LoganBerry.Attribute; atype: LoganBerry.AttributeType; avalue: LoganBerry.AttributeValue; entry: LoganBerry.Entry; cursor: LoganBerry.Cursor; i: INT; r: ROPE; count: CARDINAL; start _ BasicTime.GetClockPulses[]; SELECT n FROM $create => { -- build a LoganBerry test database in random order IF out # NIL THEN out.PutF["Creating new test database with %g entries.\n", IO.int[dbSize]]; <> MarkKeysUnused[1, dbSize]; THROUGH [1..dbSize] DO i _ GenerateUnusedKey[1, dbSize]; entry _ LIST[[$Integer, Convert.RopeFromInt[i]], [$Rope, PrintEnglish.IntToEnglish[i]]]; LoganBerry.WriteEntry[db: database, entry: entry]; ENDLOOP; }; $enumerate => { -- enumerate the database to get the number of entries AddOne: LoganBerry.EntryProc = { <<[entry: LoganBerry.Entry] RETURNS [continue: BOOL]>> count _ count + 1; RETURN[TRUE]; }; IF out # NIL THEN out.PutF["Enumerating entries.\n"]; count _ 0; LoganBerry.EnumerateEntries[db: database, key: $Integer, proc: AddOne]; IF count # dbSize AND out # NIL THEN out.PutF["Warning: count # standard database size.\n"]; dbSize _ count; IF out # NIL THEN out.PutF["Test database contains %g entries.\n", IO.card[dbSize]]; }; $generate => { -- generate the complete database and see if the number is correct IF out # NIL THEN out.PutF["Generating entries.\n"]; count _ 0; cursor _ LoganBerry.GenerateEntries[db: database, key: $Integer]; entry _ LoganBerry.NextEntry[cursor: cursor]; UNTIL entry = NIL DO count _ count + 1; entry _ LoganBerry.NextEntry[cursor: cursor]; ENDLOOP; LoganBerry.EndGenerate[cursor: cursor]; IF count # dbSize THEN { IF out # NIL THEN out.PutF["Error: count # database size.\n"]; failed _ TRUE; }; }; $deleteA => { -- delete some entries IF out # NIL THEN out.PutF["Reading and deleting %g entries.\n", IO.card[numDeletes]]; MarkKeysUnused[1, dbSize]; savedEntries _ NIL; THROUGH [1..numDeletes] DO i _ GenerateUnusedKey[1, dbSize]; avalue _ Convert.RopeFromInt[i]; entry _ LoganBerry.ReadEntry[db: database, key: $Integer, value: avalue].entry; savedEntries _ CONS[entry, savedEntries]; LoganBerry.DeleteEntry[db: database, key: $Integer, value: avalue]; ENDLOOP; }; $deleteB => { -- verify that the deleted entries can not be read IF out # NIL THEN out.PutF["Verifying deletes.\n"]; THROUGH [1..3] DO i _ GenerateUsedKey[1, dbSize]; avalue _ Convert.RopeFromInt[i]; IF LoganBerry.ReadEntry[db: database, key: $Integer, value: avalue].entry # NIL THEN { IF out # NIL THEN out.PutF["Error: sucessfully read deleted entry.\n"]; failed _ TRUE; }; ENDLOOP; }; $replaceA => { -- replace the deleted entries IF out # NIL THEN out.PutF["Rewriting deleted entries.\n"]; WHILE savedEntries # NIL DO LoganBerry.WriteEntry[db: database, entry: savedEntries.first]; savedEntries _ savedEntries.rest; ENDLOOP; }; $replaceB => { -- verify that the replaced entries can be read IF out # NIL THEN out.PutF["Verifying rewrites.\n"]; THROUGH [1..3] DO i _ GenerateUsedKey[1, dbSize]; avalue _ Convert.RopeFromInt[i]; IF LoganBerry.ReadEntry[db: database, key: $Integer, value: avalue].entry = NIL THEN { IF out # NIL THEN out.PutF["Error: unable to read replaced entry.\n"]; failed _ TRUE; }; ENDLOOP; }; $readRandom => { -- read some random entries IF out # NIL THEN out.PutF["Reading %g entries.\n", IO.card[numReads]]; THROUGH [1..numReads] DO i _ Random.ChooseInt[rs, 1, dbSize]; avalue _ Convert.RopeFromInt[i]; IF LoganBerry.ReadEntry[db: database, key: $Integer, value: avalue].entry = NIL THEN { IF out # NIL THEN out.PutF["Error: unable to read entry #%g.\n", IO.rope[avalue]]; failed _ TRUE; }; ENDLOOP; }; $checkData => { -- check the consistency of the data IF out # NIL THEN out.PutF["Checking data.\n"]; THROUGH [1..10] DO i _ Random.ChooseInt[rs, 1, dbSize]; avalue _ Convert.RopeFromInt[i]; entry _ LoganBerry.ReadEntry[db: database, key: $Integer, value: avalue].entry; avalue _ GetAttributeValue[entry, $Rope]; IF NOT Rope.Equal[avalue, PrintEnglish.IntToEnglish[i]] THEN { IF out # NIL THEN out.PutF["Error: $Rope value # expected value.\n"]; failed _ TRUE; }; ENDLOOP; }; $closeAndOpen => { -- close and reopen the database caughtError: BOOLEAN _ FALSE; IF out # NIL THEN out.PutF["Closing and reopening the database.\n"]; i _ Random.ChooseInt[rs, 1, dbSize]; avalue _ Convert.RopeFromInt[i]; LoganBerry.Close[db: database]; [] _ LoganBerry.ReadEntry[db: database, key: $Integer, value: avalue ! LoganBerry.Error => { caughtError _ TRUE; IF ec = $DBClosed THEN CONTINUE ELSE REJECT}; ]; IF NOT caughtError THEN { IF out # NIL THEN out.PutF["Error: able to read after close.\n"]; failed _ TRUE; }; database _ LoganBerry.Open[dbName: dbFileName]; IF LoganBerry.ReadEntry[db: database, key: $Integer, value: avalue].entry = NIL THEN { IF out # NIL THEN out.PutF["Error: unable to read entry #%g.\n", IO.rope[avalue]]; failed _ TRUE; }; }; $bidirectionalGenerate => { -- generate a subrange of entries in both directions IF out # NIL THEN out.PutF["Advanced test of generate. -- not implemented\n"]; }; $compactA => { -- compact the database IF out # NIL THEN out.PutF["Compacting log.\n"]; LoganBerry.CompactLogs[db: database]; }; $compactB => { -- verify that the compacted database has the correct number of entries IF out # NIL THEN out.PutF["Counting number of entries.\n"]; count _ 0; cursor _ LoganBerry.GenerateEntries[db: database, key: $Integer]; entry _ LoganBerry.NextEntry[cursor: cursor]; UNTIL entry = NIL DO count _ count + 1; entry _ LoganBerry.NextEntry[cursor: cursor]; ENDLOOP; LoganBerry.EndGenerate[cursor: cursor]; IF count # dbSize THEN { IF out # NIL THEN out.PutF["Error: count # database size.\n"]; failed _ TRUE; }; }; $invalidOps => { -- try to raise errors IF out # NIL THEN out.PutF["Checking generated errors. -- not implemented\n"]; }; ENDCASE => { IF out # NIL THEN out.PutF["Error: No such test.\n"]; failed _ TRUE; }; stop _ BasicTime.GetClockPulses[]; IF out # NIL THEN out.PutF["Running time: %g\n", IO.rope[ElapsedTime[start, stop]]]; }; <<>> ElapsedTime: PROC [start, stop: BasicTime.Pulses] RETURNS [ROPE] ~ { <> microseconds, seconds: LONG CARDINAL; digit: CARDINAL; any: BOOL _ FALSE; text: REF TEXT _ NEW[TEXT[8]]; microseconds _ BasicTime.PulsesToMicroseconds[stop - start]; seconds _ microseconds / 1000000; microseconds _ microseconds MOD 1000000; THROUGH [0..6) DO microseconds _ microseconds * 10; digit _ microseconds / 1000000; microseconds _ microseconds MOD 1000000; IF NOT any THEN { text _ Convert.AppendChar[to: text, from: '., quote: FALSE]; any _ TRUE; }; text _ Convert.AppendChar[to: text, from: digit + '0, quote: FALSE]; IF microseconds = 0 THEN EXIT; ENDLOOP; RETURN [IO.PutFR["%r%g\n", IO.card[seconds], IO.text[text]]]; }; <<>> usedKeys: PACKED ARRAY [1..10000] OF BOOLEAN; MarkKeysUnused: PROC [min, max: INT] RETURNS [] ~ { FOR i: INT IN [min..max] DO usedKeys[i] _ FALSE; ENDLOOP; }; GenerateUnusedKey: PROC [min, max: INT] RETURNS [INT] ~ { i: INT; i _ Random.ChooseInt[rs, min, max]; WHILE usedKeys[i] DO IF i < max THEN i _ i + 1 ELSE i _ min; ENDLOOP; usedKeys[i] _ TRUE; RETURN[i]; }; GenerateUsedKey: PROC [min, max: INT] RETURNS [INT] ~ { i: INT; i _ Random.ChooseInt[rs, min, max]; WHILE NOT usedKeys[i] DO IF i < max THEN i _ i + 1 ELSE i _ min; ENDLOOP; RETURN[i]; }; <<>> GetAttributeValue: PROC [entry: LoganBerry.Entry, type: LoganBerry.AttributeType] RETURNS [LoganBerry.AttributeValue] ~ { FOR e: LoganBerry.Entry _ entry, e.rest WHILE e # NIL DO IF e.first.type = type THEN RETURN[e.first.value]; ENDLOOP; RETURN[NIL]; }; TestLoganBerry: Commander.CommandProc = { <<[cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]>> numErrors: INT _ 0; start, stop: BasicTime.Pulses; start _ BasicTime.GetClockPulses[]; database _ LoganBerry.Open[dbName: dbFileName ! LoganBerry.Error => IO.PutF[cmd.out, "Error from LoganBerry: %g - %g\n", IO.atom[ec], IO.rope[explanation]] ]; <> FOR t: TestCase IN [enumerate..compactA) DO IF RunTest[t, cmd.out] THEN numErrors _ numErrors + 1; ENDLOOP; stop _ BasicTime.GetClockPulses[]; IO.PutF[cmd.out, "%g Errors; elapsed time = %g\n", IO.int[numErrors], IO.rope[ElapsedTime[start, stop]]]; }; <> <<[cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL]>> <> <<};>> Commander.Register[key: "TestLoganBerry", proc: TestLoganBerry, doc: "Test the LoganBerry package." ]; <> END. <> <> <> <> <> <> <> <>