<> <> <> <<>> <> <<>> DIRECTORY BasicTime USING [GetClockPulses, Pulses, PulsesToMicroseconds], Commander USING [Register, CommandProc], CommandTool USING [ParseToList], 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, CommandTool, 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, rewriteA, rewriteB, replace, 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; numReplaces: 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; <> <> avalue: LoganBerry.AttributeValue; entry: LoganBerry.Entry; cursor: LoganBerry.Cursor; i: INT; 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; }; $rewriteA => { -- rewrite 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; }; $rewriteB => { -- verify that the rewritten 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; }; $replace => { -- replace some random entries (then restore to original values) replacement: LoganBerry.Entry; IF out # NIL THEN out.PutF["Replacing (then restoring) %g entries.\n", IO.card[numReplaces]]; THROUGH [1..numReplaces] DO i _ Random.ChooseInt[rs, 1, dbSize]; avalue _ Convert.RopeFromInt[i]; entry _ LoganBerry.ReadEntry[db: database, key: $Integer, value: avalue].entry; replacement _ LIST[[$Integer, avalue], [$Rope, "replacement value"]]; LoganBerry.WriteEntry[db: database, entry: replacement, replace: TRUE]; replacement _ LoganBerry.ReadEntry[db: database, key: $Integer, value: avalue].entry; IF NOT Rope.Equal[GetAttributeValue[replacement, $Rope], "replacement value"] THEN { IF out # NIL THEN out.PutF["Error: replacement $Rope value # expected value.\n"]; failed _ TRUE; }; LoganBerry.WriteEntry[db: database, entry: entry, replace: 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.\n"]; i _ Random.ChooseInt[rs, 1, dbSize-10]; avalue _ Convert.RopeFromInt[i]; cursor _ LoganBerry.GenerateEntries[db: database, key: $Integer, start: avalue, end: Convert.RopeFromInt[i+9]]; <> count _ 0; entry _ LoganBerry.NextEntry[cursor: cursor]; UNTIL entry = NIL DO count _ count + 1; entry _ LoganBerry.NextEntry[cursor: cursor]; ENDLOOP; <> <> entry _ LoganBerry.NextEntry[cursor: cursor, dir: decreasing]; UNTIL entry = NIL DO count _ count - 1; entry _ LoganBerry.NextEntry[cursor: cursor, dir: decreasing]; ENDLOOP; IF count # 1 THEN { IF out # NIL THEN out.PutF["Error: generated increasing/decreasing subrange of wrong size.\n"]; failed _ TRUE; }; LoganBerry.EndGenerate[cursor: cursor]; }; $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 caughtError: BOOLEAN _ FALSE; IF out # NIL THEN out.PutF["Checking generated errors.\n"]; [] _ LoganBerry.Open[dbName: "Nonexistent.badname" ! LoganBerry.Error => {caughtError _ TRUE; IF ec = $CantOpenSchema THEN CONTINUE ELSE REJECT};]; IF NOT caughtError THEN { IF out # NIL THEN out.PutF["Error: able to open nonexistent database.\n"]; failed _ TRUE; }; caughtError _ FALSE; [] _ LoganBerry.Describe[db: 1010101 ! LoganBerry.Error => {caughtError _ TRUE; IF ec = $BadDBHandle THEN CONTINUE ELSE REJECT};]; IF NOT caughtError THEN { IF out # NIL THEN out.PutF["Error: able to operate on bad database.\n"]; failed _ TRUE; }; caughtError _ FALSE; [] _ LoganBerry.ReadEntry[db: database, key: $NonIndex, value: avalue ! LoganBerry.Error => {caughtError _ TRUE; IF ec = $NoIndex THEN CONTINUE ELSE REJECT};]; IF NOT caughtError THEN { IF out # NIL THEN out.PutF["Error: able to read using nonexistent index.\n"]; failed _ TRUE; }; i _ Random.ChooseInt[rs, 1, dbSize]; avalue _ Convert.RopeFromInt[i]; entry _ LoganBerry.ReadEntry[db: database, key: $Integer, value: avalue].entry; caughtError _ FALSE; [] _ LoganBerry.WriteEntry[db: database, entry: entry ! LoganBerry.Error => {caughtError _ TRUE; IF ec = $ValueNotUnique THEN CONTINUE ELSE REJECT};]; IF NOT caughtError THEN { IF out # NIL THEN out.PutF["Error: able to write duplicate entry.\n"]; failed _ TRUE; }; caughtError _ FALSE; [] _ LoganBerry.WriteEntry[db: database, entry: entry.rest ! LoganBerry.Error => {caughtError _ TRUE; IF ec = $NoPrimaryKey THEN CONTINUE ELSE REJECT};]; IF NOT caughtError THEN { IF out # NIL THEN out.PutF["Error: able to write without primary key.\n"]; failed _ TRUE; }; }; 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; firstTest, lastTest: TestCase; start, stop: BasicTime.Pulses; args: LIST OF ROPE _ CommandTool.ParseToList[cmd].list; IF args # NIL AND Rope.Equal[args.first, "-f"] THEN { -- full testcase IO.PutF[cmd.out, "Running ALL tests... \n\n"]; firstTest _ FIRST[TestCase]; lastTest _ LAST[TestCase]; } ELSE { -- run all test cases except for create and compact (which take too long) firstTest _ enumerate; lastTest _ invalidOps; }; 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 [firstTest..lastTest] 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]]]; }; Commander.Register[key: "TestLoganBerry", proc: TestLoganBerry, doc: "Test the LoganBerry package." ]; END. <> <> <> <> <> <> <> <>