-- 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.