-- File: FileIOTestImpl.mesa
-- Last edited
-- by MBrown on March 27, 1982 9:01 pm
DIRECTORY
Rope,
Environment,
Inline USING [LongNumber, LowByte, LowHalf, LongCOPY, BITAND],
IO,
FileIO,
Juniper,
Space USING [Create, Map, Handle, LongPointer, CreateUniformSwapUnits, virtualMemory],
Process,
RandomCard USING [Init, Next, Choose],
Runtime,
Transaction USING[Handle, nullHandle, Begin, Commit, Abort];
FileIOTestImpl: PROGRAM
IMPORTS
FileIO,
I: Inline,
IO,
Juniper,
Process,
RAN: RandomCard,
Rope,
Runtime,
Space,
Transaction =
BEGIN
ROPE: TYPE = Rope.ROPE;
bytesPerPage: CARDINAL = Environment.bytesPerPage;
Trans: TYPE = FileIO.Trans;
useJuniper: BOOLEAN;
keys, dpy: IO.Handle;
Atomize: PROC [P: PROC [Trans] RETURNS [BOOLEAN]] = {
done: BOOLEAN ← FALSE;
t: Trans ← [pilot[trans:]];
successiveAbortCount: CARDINAL ← 0;
WHILE NOT done AND successiveAbortCount < MaxSuccessiveAbortCount DO
t ← BeginTransaction[];
BEGIN
ENABLE Juniper.Error, TransactionReset => {
Runtime.CallDebugger["Juniper.Error"]; GOTO transactionAborted };
done ← P[t];
EndTransaction[t];
successiveAbortCount ← 0;
EXITS
transactionAborted =>
BEGIN
successiveAbortCount ← successiveAbortCount + 1;
HandleTransAbort[t];
END;
END;
ENDLOOP
};
MaxSuccessiveAbortCount: CARDINAL = 3;
totalAborts: CARDINAL ← 0;
usePilotTransactions: BOOLEAN;
HandleTransAbort: PROC [t: Trans] = {
dpy.PutF["*Ntransaction just aborted, will clean up and retry*N"];
totalAborts ← totalAborts + 1;
AbortTransaction[t] };
InitForTransactions: PROC [] = {
IF useJuniper THEN Juniper.InitializePine[] };
BeginTransaction: PROC RETURNS [Trans] = {
IF useJuniper THEN {
wt: ROPE; t: Juniper.Transaction;
[t, wt] ← Juniper.BeginTransaction[server: server, user: user, password: password];
dpy.PutF["%g*n", IO.rope[wt]];
RETURN[[juniper[trans: t]]] }
ELSE {
dpy.PutF["Pilot Transaction.Begin*n"];
RETURN[[pilot[IF usePilotTransactions THEN Transaction.Begin[] ELSE Transaction.nullHandle]]] }};
AbortTransaction: PROC [t: Trans] = {
WITH trans: t SELECT FROM
juniper => Juniper.AbortTransaction[trans.trans];
pilot => IF trans.trans # Transaction.nullHandle THEN Transaction.Abort[trans.trans];
ENDCASE => ERROR };
EndTransaction: PROC [t: Trans] = {
WITH trans: t SELECT FROM
juniper => Juniper.EndTransaction[trans.trans];
pilot => IF trans.trans # Transaction.nullHandle THEN Transaction.Commit[trans.trans];
ENDCASE => ERROR };
TransactionReset: SIGNAL = CODE;
-- if not Juniper, raised in test as a random action.
testFileName: ROPE ← "RandomStreamTest.file";
server: ROPE ← "Juniper";
user: ROPE ← "CedarDB";
password: ROPE ← "Cedar";
juniperTestFileName: ROPE ← "[Juniper]<CedarDB>RandomStreamTest.file";
maxFileBytes: LONG CARDINAL ← LONG[140] * bytesPerPage + 1;
-- specific test code
ResetFile: PROC [t: Trans] RETURNS [BOOLEAN] = {
s: IO.Handle;
s ← FileIO.Open[
fileName: IF useJuniper THEN juniperTestFileName ELSE testFileName,
accessOptions: overwrite, closeOptions: FileIO.truncatePagesOnClose,
transaction: t];
s.Close[];
RETURN[TRUE] };
OneTest: PROC [t: Trans] RETURNS [BOOLEAN] = {
StartTest[t];
DriveTest[];
RETURN[TRUE] };
StartTest: PROC [t: Trans] = {
s: IO.Handle;
s ← FileIO.Open[
fileName: IF useJuniper THEN juniperTestFileName ELSE testFileName,
accessOptions: overwrite, closeOptions: FileIO.truncatePagesOnClose,
transaction: t];
InitStreamModel[s, maxFileBytes] };
MainTestCases: DESCRIPTOR FOR ARRAY OF ActionCase = DESCRIPTOR[MainTestCasesBody];
MainTestCasesBody: ARRAY [0..9) OF ActionCase ← [
[190, RandomPutChar, "RandomPutChar", 0],
[180, RandomGetChar, "RandomGetChar", 0],
[90, RandomPutBlock, "RandomPutBlock", 0],
[90, RandomGetBlock, "RandomGetBlock", 0],
[90, RandomSetPosition, "RandomSetPosition", 0],
[90, RandomGetPosition, "RandomGetPosition", 0],
[90, RandomLengthen, "RandomLengthen", 0],
[90, RandomShorten, "RandomShorten", 0],
[90, RandomGetLength, "RandomGetLength", 0]];
DriveTest: PROC = {
Tick[];
WHILE tick < tickLimit DO
IF TryOneRandomAction[MainTestCases, NIL] THEN --did something-- Tick[]; ENDLOOP;
Tick[];
};
PrintStatistics: PROC = {
PrintCaseArrayStatistics[MainTestCases];
dpy.PutF["totalAborts: %g*N", IO.card[totalAborts]] };
SyntaxCheck: PROC = { };
-- Stream model
simFileSpace: Space.Handle;
simFileSwapUnitPages: CARDINAL = 5;
simFileData: LONG POINTER; --TO PACKED ARRAY OF CHAR
simFileCharLength: LONG CARDINAL;
maxSimFileCharLength: LONG CARDINAL;
simStreamCharPointer: LONG CARDINAL;
-- Actual stream under test
realStream: IO.Handle;
randomDataBlockSize: CARDINAL = 1000;
dataBlockForRandomPutGetBlock: REF TEXT;
-- random actions
RandomPutChar: PROC [arg: UNSPECIFIED] RETURNS [BOOLEAN] = {
IF simStreamCharPointer >= maxSimFileCharLength THEN RETURN[FALSE];
SimPutChar[I.LowByte[RAN.Next[]]];
RETURN[TRUE] };
RandomGetChar: PROC [arg: UNSPECIFIED] RETURNS [BOOLEAN] = {
c: CHAR;
IF simStreamCharPointer >= simFileCharLength THEN RETURN[FALSE];
c ← SimGetChar[];
RETURN[TRUE] };
RandomPutBlock: PROC [arg: UNSPECIFIED] RETURNS [BOOLEAN] = {
longBytesLeft: LONG CARDINAL ← maxSimFileCharLength - simStreamCharPointer;
maxBytesToXfer: NAT ← IF longBytesLeft >= randomDataBlockSize THEN randomDataBlockSize
ELSE I.LowHalf[longBytesLeft];
bytesToXfer: NAT ← RAN.Choose[0, maxBytesToXfer];
FOR I: NAT IN [0..bytesToXfer) DO
dataBlockForRandomPutGetBlock[I] ← LOOPHOLE[RAN.Next[],CHAR]
ENDLOOP;
dataBlockForRandomPutGetBlock.length ← bytesToXfer;
SimPutBlock[dataBlockForRandomPutGetBlock];
RETURN[TRUE] };
RandomGetBlock: PROC [arg: UNSPECIFIED] RETURNS [BOOLEAN] = {
longBytesLeft: LONG CARDINAL ← simFileCharLength - simStreamCharPointer;
maxBytesToXfer: NAT ← IF longBytesLeft >= randomDataBlockSize THEN randomDataBlockSize
ELSE I.LowHalf[longBytesLeft];
bytesToXfer: NAT ← RAN.Choose[0, maxBytesToXfer];
SimGetBlock[dataBlockForRandomPutGetBlock, bytesToXfer];
RETURN[TRUE] };
RandomSetPosition: PROC [arg: UNSPECIFIED] RETURNS [BOOLEAN] = {
pos: LONG CARDINAL ←
LOOPHOLE[I.LongNumber[num[lowbits: RAN.Next[], highbits: RAN.Next[]]],
LONG CARDINAL] MOD (simFileCharLength + 1);
SimSetPosition[pos];
RETURN[TRUE] };
RandomGetPosition: PROC [arg: UNSPECIFIED] RETURNS [BOOLEAN] = {
pos: LONG CARDINAL ← SimGetPosition[];
RETURN[TRUE] };
RandomLengthen: PROC [arg: UNSPECIFIED] RETURNS [BOOLEAN] = {
increase: LONG CARDINAL;
maxIncrease: LONG CARDINAL ← maxSimFileCharLength - simFileCharLength;
IF maxIncrease = 0 THEN RETURN[FALSE];
increase ← LOOPHOLE[I.LongNumber[num[lowbits: RAN.Next[], highbits: RAN.Next[]]],
LONG CARDINAL] MOD maxIncrease;
SimSetLength[simFileCharLength + increase];
RETURN[TRUE] };
RandomShorten: PROC [arg: UNSPECIFIED] RETURNS [BOOLEAN] = {
newSize: LONG CARDINAL;
IF simFileCharLength = 0 THEN RETURN[FALSE];
newSize ← LOOPHOLE[I.LongNumber[num[lowbits: RAN.Next[], highbits: RAN.Next[]]],
LONG CARDINAL] MOD simFileCharLength;
SimSetLength[newSize];
RETURN[TRUE] };
RandomGetLength: PROC [arg: UNSPECIFIED] RETURNS [BOOLEAN] = {
len: LONG CARDINAL ← SimGetLength[];
RETURN[TRUE] };
-- basic model code
InitStreamModel: PROC [s: IO.Handle, maxFileBytes: LONG CARDINAL] = {
simFilePages: CARDINAL = I.LowHalf[(maxFileBytes + bytesPerPage - 1)/bytesPerPage];
simFileSpace ← Space.Create[size: simFilePages, parent: Space.virtualMemory];
Space.CreateUniformSwapUnits[size: simFileSwapUnitPages, parent: simFileSpace];
Space.Map[simFileSpace]; --to make it into vmem
simFileData ← Space.LongPointer[space: simFileSpace];
LOOPHOLE[simFileData, LONG POINTER TO WORD]↑ ← 0;
I.LongCOPY[from: simFileData, to: simFileData+1, nwords: simFilePages*bytesPerPage - 1];
simFileCharLength ← 0;
simStreamCharPointer ← 0;
maxSimFileCharLength ← maxFileBytes;
realStream ← s;
dataBlockForRandomPutGetBlock ← NEW[TEXT[randomDataBlockSize] ← [length: 0, text: NULL]];
};
-- following procedures affect model and also real stream
Pair: TYPE = MACHINE DEPENDENT RECORD [highHalf(0: 0..7): CHAR, lowHalf(0: 8..15): CHAR];
AssignToSimFile: PROC [i: LONG CARDINAL, c: CHAR] = INLINE {
ptr: LONG POINTER TO Pair ← LOOPHOLE[simFileData + i/2];
IF I.BITAND[LOOPHOLE[i, I.LongNumber[num]].lowbits,1] = 0 THEN --high char
ptr.highHalf ← c
ELSE ptr.lowHalf ← c };
FetchFromSimFile: PROC [i: LONG CARDINAL] RETURNS [CHAR] = INLINE {
ptr: LONG POINTER TO Pair ← LOOPHOLE[simFileData + i/2];
IF I.BITAND[LOOPHOLE[i, I.LongNumber[num]].lowbits,1] = 0 THEN --high char
RETURN[ptr.highHalf]
ELSE RETURN[ptr.lowHalf] };
SimPutChar: PROC [c: CHAR] = {
IF c = 0C THEN c ← 1C;
IF simStreamCharPointer > simFileCharLength THEN Alarm[];
IF simStreamCharPointer = maxSimFileCharLength THEN Alarm[];
AssignToSimFile[simStreamCharPointer, c];
simStreamCharPointer ← simStreamCharPointer + 1;
IF simStreamCharPointer > simFileCharLength THEN simFileCharLength ← simStreamCharPointer;
Debug["PutChar"];
realStream.PutChar[c] };
SimGetChar: PROC RETURNS [c: CHAR] = {
fetchedC: CHAR;
IF simStreamCharPointer >= simFileCharLength THEN Alarm[];
c ← FetchFromSimFile[simStreamCharPointer];
simStreamCharPointer ← simStreamCharPointer + 1;
fetchedC ← realStream.GetChar[];
Debug["GetChar"];
IF c # 0C AND c # fetchedC THEN Alarm[] };
SimPutBlock: PROC [block: REF TEXT] = {
IF simStreamCharPointer + block.length > maxSimFileCharLength THEN Alarm[];
FOR I: NAT IN [0..block.length) DO
IF block[I] = 0C THEN block[I] ← 1C;
AssignToSimFile[simStreamCharPointer, block[I]];
simStreamCharPointer ← simStreamCharPointer + 1;
ENDLOOP;
IF simStreamCharPointer > simFileCharLength THEN simFileCharLength ← simStreamCharPointer;
Debug["PutBlock, nWords = "];
DebugD[block.length];
realStream.PutBlock[block, 0, block.length] };
SimGetBlock: PROC [block: REF TEXT, nChars: NAT] = {
c: CHAR;
charsLeft: LONG CARDINAL = simFileCharLength - simStreamCharPointer;
charsXferred: NAT;
nCharsToBeXferred: NAT =
MIN[IF charsLeft>=nChars THEN nChars ELSE I.LowHalf[charsLeft], block.maxLength];
Debug["GetBlock, nChars = "];
DebugD[nChars];
charsXferred ← realStream.GetBlock[block, 0, nChars];
IF charsXferred # 0 AND charsXferred # block.length THEN Alarm[];
IF charsXferred # nCharsToBeXferred THEN Alarm[];
FOR I: CARDINAL IN [0..charsXferred) DO
c ← FetchFromSimFile[simStreamCharPointer];
IF c # 0C AND c # block[I] THEN Alarm[];
simStreamCharPointer ← simStreamCharPointer + 1;
ENDLOOP };
SimSetPosition: PROC [position: LONG CARDINAL] = {
IF position > simFileCharLength THEN Alarm[];
simStreamCharPointer ← position;
Debug["SetPosition to "];
DebugLD[position];
realStream.SetIndex[position] };
SimGetPosition: PROC RETURNS [LONG CARDINAL] = {
pos: LONG CARDINAL;
Debug["GetPosition, it should be "];
DebugLD[simStreamCharPointer];
IF simStreamCharPointer # (pos ← realStream.GetIndex[]) THEN Alarm[];
RETURN[simStreamCharPointer] };
SimSetLength: PROC [length: LONG CARDINAL] = {
IF length > maxSimFileCharLength THEN Alarm[];
Debug["SetLength to "];
DebugLD[length];
realStream.SetLength[length];
FOR i: LONG CARDINAL IN [simFileCharLength..length) DO
AssignToSimFile[i, 0C]; -- make value undefined in new region
ENDLOOP;
simFileCharLength ← length;
simStreamCharPointer ← MIN[simStreamCharPointer, length] };
SimGetLength: PROC RETURNS [LONG CARDINAL] = {
len: LONG CARDINAL;
Debug["GetLength, it should be "];
DebugLD[simFileCharLength];
IF simFileCharLength # (len ← realStream.GetLength[]) THEN Alarm[];
RETURN[simFileCharLength] };
Debug: PROC [r: ROPE] = {
IF debugTrace THEN dpy.PutF["*Nduring tick %g, %g", IO.card[tick], IO.rope[r]] };
DebugD: PROC [d: CARDINAL] = {
IF debugTrace THEN dpy.PutF["%g", IO.card[d]] };
DebugLD: PROC [d: LONG CARDINAL] = {
IF debugTrace THEN dpy.PutF["%g", IO.card[d]] };
-- testing support
ActionArray: TYPE = DESCRIPTOR FOR ARRAY OF ActionCase;
ActionCase: TYPE = RECORD [
prob: CARDINAL, -- probs must sum to 1000
action: PROC [UNSPECIFIED] RETURNS [BOOLEAN],
printName: ROPE,
count: LONG CARDINAL];
TryOneRandomAction: PUBLIC PROC [T: ActionArray, arg: UNSPECIFIED]
RETURNS [didSomething: BOOLEAN] = {
r: CARDINAL ← RAN.Choose[0, 999];
FOR I: CARDINAL IN [0..LENGTH[T]) DO
IF r < T[I].prob THEN {
IF T[I].action[arg] THEN { T[I].count ← T[I].count + 1; RETURN[TRUE] }
ELSE RETURN[FALSE] };
r ← r - T[I].prob;
ENDLOOP;
Alarm[] };
GetLen: PROC [h: IO.Handle] RETURNS [LONG CARDINAL] = {
--for debugger invocation
RETURN[h.GetLength[]] };
SetInd: PROC [h: IO.Handle, i: LONG CARDINAL] = {
--for debugger invocation
h.SetIndex[i] };
GetInd: PROC [h: IO.Handle] RETURNS [LONG CARDINAL] = {
--for debugger invocation
RETURN[h.GetIndex[]] };
GetCha: PROC [h: IO.Handle] RETURNS [CARDINAL] = {
--for debugger invocation
RETURN[LOOPHOLE[h.GetChar[]]] };
-- test support parms
tick: LONG CARDINAL ← 37777777777B; --we begin with a Tick[] to make this 0
ticksPerTest: LONG CARDINAL ← 1000;
tickLimit: LONG CARDINAL;
randomSeed: CARDINAL ← 12314B;
debugTrace: BOOLEAN ← TRUE;
Tick: PUBLIC PROC = {
SyntaxCheck[];
tick ← tick + 1;
IF (tick MOD 10 = 0) THEN dpy.PutF["*Ntick becomes: %gD *N", IO.card[tick]];
IF tick MOD 500 = 0 THEN PrintStatistics[] };
PrintCaseArrayStatistics: PROC [A: ActionArray] = {
dpy.PutF["*Nstatistics at tick = %g*N", IO.card[tick]];
FOR I: CARDINAL IN [0..LENGTH[A]) DO
dpy.PutF["%g: prob(**1000): %g, nTimes: %g*N",
IO.rope[A[I].printName], IO.card[A[I].prob], IO.card[A[I].count]];
ENDLOOP };
InitTestSupport: PROC = {
tickLimit ← ticksPerTest;
dpy.PutF["\n%g random actions in test\n", IO.int[ticksPerTest]];
dpy.PutF["initializing random numbers with %g\n", IO.card[RAN.Init[seed: -1]]] };
-- misc
Alarm: SIGNAL = CODE;
TiogaFeatureTest: PROC [] = {
fileName: ROPE ← "Tioga.doc";
dpy: IO.Handle ← IO.CreateTTYStreams[Rope.Cat[fileName, ".plaintext"]].out;
{ file: IO.Handle ← IO.CreateFileStream[
fileName : fileName, accessOptions: write, createOptions: oldOnly ! IO.Error =>
IF ec = CantUpdateTiogaFile THEN GOTO ok ];
ERROR;
EXITS ok => NULL
};
{
file: IO.Handle ← IO.CreateFileStream[
fileName : fileName, accessOptions: read, createOptions: oldOnly];
WHILE NOT file.EndOf[] DO
dpy.PutChar[file.GetChar[]];
ENDLOOP;
};
--dpy.Close[];
};
-- user interaction
Main: PROC [] = {
response: ROPE;
juniperStarted: BOOL ← FALSE;
[in: keys, out: dpy] ← IO.CreateTTYStreams["FileIOTest"];
DO
dpy.PutF["P(ilot), T(ioga feature), J(uniper), or Q(uit)?\n"];
response ← keys.GetToken[];
SELECT TRUE FROM
Rope.Equal[response, "p", FALSE] => { useJuniper ← FALSE; usePilotTransactions ← FALSE };
Rope.Equal[response, "t", FALSE] => { TiogaFeatureTest[]; LOOP };
Rope.Equal[response, "j", FALSE] => { useJuniper ← TRUE; usePilotTransactions ← FALSE };
Rope.Equal[response, "q", FALSE] => EXIT;
ENDCASE => LOOP;
IF useJuniper AND NOT juniperStarted THEN {
Juniper.StartPine[]; juniperStarted ← TRUE };
InitForTransactions[];
InitTestSupport[];
Atomize[ResetFile];
Atomize[OneTest];
ENDLOOP };
Process.Detach[FORK Main[]];
END.