LoganBerryTest.mesa
Copyright © 1986 by Xerox Corporation. All rights reserved.
Doug Terry, October 19, 1986 11:10:20 pm PDT
A test program for LoganBerry. Simply type "TestLoganBerry" to a command tool.
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];
The set of tests must be updated whenever a new test is added
TestCase: TYPE = {create, enumerate, generate, deleteA, deleteB, rewriteA, rewriteB, replace, readRandom, checkData, closeAndOpen, bidirectionalGenerate, invalidOps, compactA, compactB};
The following numbers pertain to specific tests
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.STREAMNIL] RETURNS [failed: BOOLEANFALSE] ~ {
Runs and times the given test.
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;
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]];
somehow need to delete old database entries first
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: BOOLEANFALSE;
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]];
Retrieve [start..end]
count ← 0;
entry ← LoganBerry.NextEntry[cursor: cursor];
UNTIL entry = NIL DO
count ← count + 1;
entry ← LoganBerry.NextEntry[cursor: cursor];
ENDLOOP;
Note: at this point it is hard to predict count since ordering is non-numerical
Retrieve (end..start]
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: BOOLEANFALSE;
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] ~ {
Taken from InitialCommandsImpl.Time.
microseconds, seconds: LONG CARDINAL;
digit: CARDINAL;
any: BOOLFALSE;
text: REF TEXTNEW[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.
Doug Terry, February 27, 1986 10:43:30 am PST
changes to: DIRECTORY, LoganBerryTest, IMPORTS, ~, RunTest, Proc, Proc, Proc, TestLoganBerry, TestLoganQuery
Doug Terry, February 27, 1986 7:21:25 pm PST
changes to: DIRECTORY, LoganBerryTest, IMPORTS, ~, RunTest, ElapsedTime, Proc, Proc, TestLoganBerry, TestLoganQuery, END, RunTest, MarkKeysUnused, GenerateUnusedKey, GenerateUsedKey, TestCase, AddOne (local of RunTest)
Doug Terry, February 27, 1986 8:45:56 pm PST
changes to: TestCase, ~, RunTest, GetAttributeValue, Proc, DIRECTORY, IMPORTS, TestLoganQuery, ElapsedTime, MarkKeysUnused, TestLoganBerry
Doug Terry, February 27, 1986 9:35:37 pm PST
changes to: TestLoganBerry