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