-- File [Ivy]<Nelson>Lupine>LupineExerciserManagerImpl.mesa.
-- Last edited by BZM on 18-Mar-82 13:25:29.
-- Last edited by Andrew Birrell on July 7, 1982 6:35 pm


DIRECTORY
  CWF USING [FWF0, FWF2, FWF4, SWF3],
  LupineExerciser USING [
	Counter, Exercise, ExerciseIndex, ExerciseList,
	StandardPasses, StandardTrialsPerPass, StandardTestsPerTrial,
	String ],
  LupineExerciserPrivate USING [
	ExerciseHandle, ExerciseObject, FinishPrecisionTimings,
	InitPrecisionTimings, InitTestParameters, SpyOperation ],
--  SpyClient USING [DisplayData, StandardSpy, StartSpy, StopSpy, ZeroData],
  LongString USING [InvalidNumber, StringToDecimal],
  RandomInt USING [Choose, InitRandom],
  Runtime USING [GetBcdTime],
  System USING [GetClockPulses, GreenwichMeanTime, Pulses],
  Time USING [Current],
  TTY USING [
	Create, Destroy, GetLine, Handle, LineOverflow,
	PutChar, ResetUserAbort, Rubout, UserAbort ];


LupineExerciserManagerImpl: PROGRAM
  IMPORTS
	CWF, LString: LongString,
	Private: LupineExerciserPrivate,
	RandomInt, Runtime, --SpyClient,-- System, Time, TTY
 EXPORTS LupineExerciser, LupineExerciserPrivate
 = BEGIN OPEN LupineExerciser;


-- Exerciser instance data.

  Handle: PUBLIC TYPE = Private.ExerciseHandle;


-- Runtime inconsistency found; a parameter check failed.

  BadExercise: PUBLIC SIGNAL = CODE;



-- PerformExercises MUST be forked as a separate process!!!


  PerformExercises: PUBLIC PROCEDURE [
	nameOfExercisedInterface: String,
	exercises: ExerciseList ] =
    BEGIN ENABLE ABORTED => CONTINUE;
    logFileString: STRING = [100];
    logFileName: String = UniqueName [
      root: nameOfExercisedInterface, extension: "log"L,
      nameString: logFileString ];
    logStream: TTY.Handle = TTY.Create[name: logFileString];
    LogStreamPutChar: PROC [char: CHARACTER] = {TTY.PutChar[logStream, char]};
    exerciseObject: Private.ExerciseObject ← [
      logStream: logStream, logPut: LogStreamPutChar ];
    DoExercises[
	self: @exerciseObject,
	exerciseName: nameOfExercisedInterface,
	logFileName: logFileName,
	exercises: exercises
      ! ABORTED => CONTINUE ];
    TTY.Destroy[logStream];
    END;


  DoExercises: PROCEDURE [
	self: Handle,
	exerciseName, logFileName: String,
	exercises: ExerciseList ] =
    BEGIN
    DO OPEN tp: self.tp;
      ENABLE {
        QuitExercises => EXIT;
	AbortExercises => CONTINUE };
      pass: Counter;
      numExercises: ExerciseIndex = LENGTH[exercises];
      EstablishTestParameters[self, exerciseName, logFileName];
      IF tp.spying THEN CallSpy[self,
	IF tp.spyOnProcs THEN startAndWatchProcs ELSE startAndWatchModules ];
      FOR pass IN [1..tp.passes] DO
	CWF.FWF2[self.logPut, "*N*NStarting pass %D of %D...*N"L,
	  @pass, @tp.passes];
	FOR exerciseMarch: ExerciseIndex IN [0..numExercises) DO
	  exercise: ExerciseIndex;
	  IF tp.testRandomly
	    THEN BEGIN OPEN RandomInt;
	      exercise ← Choose[min: 0, max: numExercises-1];
	      tp.trialsPerPass ← Choose[min: 1, max: MAX[1,tp.maxTrialsPerPass] ];
	      tp.testsPerTrial ← Choose[min: 0, max: tp.maxTestsPerTrial];
	      END
	    ELSE BEGIN
	      exercise ← exerciseMarch;
	      tp.trialsPerPass ← tp.maxTrialsPerPass;
	      tp.testsPerTrial ← tp.maxTestsPerTrial;
	      END;
	    Private.InitPrecisionTimings[self];
	    exercises[exercise].routine[
	      exerciser: self,
	      name: exercises[exercise].name,
	      trials: tp.trialsPerPass,
	      testsPerTrial: tp.testsPerTrial,
	      checkResults: tp.checkResults
	     ! UNWIND => Private.FinishPrecisionTimings[self] ];
	    Private.FinishPrecisionTimings[self];
	    ENDLOOP;
	ENDLOOP;
      IF tp.spying THEN CallSpy[self,
	(SELECT tp.showSpyData FROM
	  afterEachTest => stop,
	  afterAllTests => stopAndDisplayStats,
	  ENDCASE => ERROR) ];
      ENDLOOP;
    END;


-- User interface routines.

  QuitExercises: PRIVATE ERROR = CODE;
  
  EstablishTestParameters: PROCEDURE [
	self: Handle,
	exerciseName, logFileName: String ] =
    BEGIN
    now: System.GreenwichMeanTime ← Time.Current[];
    built: System.GreenwichMeanTime ← Runtime.GetBcdTime[];
    self.tp ← Private.InitTestParameters;
    CWF.FWF4[self.logPut, "*N
Lupine RPC Exerciser of %LT on %LT.  
Starting %LS exercise; the log is %LS.*N*N"L,
	@built, @now, exerciseName, logFileName ];
    DO OPEN tp: self.tp;
      BEGIN ENABLE LString.InvalidNumber => GOTO TryAgain;
	GetCount: PROC RETURNS [Counter] =
	  INLINE {reply[0] ← ' ;  RETURN[LString.StringToDecimal[reply]]};
	reply: STRING = [100];
	CWF.FWF0[self.logPut, ">>"L];
	CheckAbort[self];
	TTY.GetLine[self.logStream, reply
	 ! TTY.Rubout, TTY.LineOverflow => GOTO TryAgain];
	CheckAbort[self];
	IF reply.length > 0 THEN SELECT reply[0] FROM
	  'd, 'D => {tp.checkResults ← FALSE};
	  'e, 'E => {tp.useDoradoClock ← tp.countOnlyEmulatorCycles ← TRUE};
	  'f, 'F => {tp ← Private.InitTestParameters};
	  'g, 'G => {EXIT};
	  'h, 'H => {tp.useDoradoClock ← TRUE};
	  'm, 'M => {tp.spying ← TRUE; tp.spyOnProcs ← FALSE};
	  'p, 'P => {tp.passes ← GetCount[]};
	  'o, 'O => {tp.maxTrialsPerPass ← GetCount[]};
	  't, 'T => {tp.maxTestsPerTrial ← GetCount[]};
	  'r, 'R => {tp.testRandomly ← TRUE};
	  's, 'S => {tp.spying ← TRUE; tp.showSpyData ← afterAllTests};
	  'x, 'x => {tp.spying ← TRUE; tp.showSpyData ← afterEachTest};
	  'q, 'Q => {ERROR QuitExercises};
	  '-     => {NULL};
	  '?     => {TypeHelp[self]};
	  ENDCASE => GOTO TryAgain;
      EXITS
	TryAgain => CWF.FWF0[self.logPut, " ???*N"L];
      END;
      ENDLOOP;
    END;

TypeHelp: PROCEDURE [self: Handle] =
  BEGIN
  CWF.FWF0[self.logPut, "
The exerciser options are:
  D    Don't check results of remote calls for correctness.
  E    Exclude nonemulator cycles in timings (Dorados only).
  F    Flush and start over.
  G    Go (type this last).
  H    High precision timings (Dorados only).
  M    Module-level (coarse) spying.
  Pn   Number of passes of entire exercise.
  On   Number of trials of each pass.
  Tn   Number of tests (eg, calls) per trial.
  R    Choose trials, tests, and exercises randomly.
  S    Spy after all passes are over.
  X    Spy at the exit of each trial.
  Q    Quit this program immediately.
  --   This line is a comment.
  ?    Type this explanation.
  ↑DEL To abort at any time.
The default options are P1 Q1 R2000 G.*N*N"L ];
  END;


  AbortExercises: PRIVATE ERROR = CODE;
    -- Raised by CheckAbort and caught in DoExercises.

  CheckAbort: PUBLIC PROC [self: Handle] =
    BEGIN
    IF TTY.UserAbort[] THEN BEGIN
      TTY.ResetUserAbort;
      CWF.FWF0[self.logPut, "*NTest aborted..."L];
      ERROR AbortExercises;
      END;
    END;

  UniqueName: PROCEDURE [root, extension, nameString: String]
      RETURNS [rootSuffix: String] =
    INLINE BEGIN
    pulses: System.Pulses ← System.GetClockPulses[];
    rootSuffix ← nameString;
    CWF.SWF3[rootSuffix, "%LS.%LD.%LS"L, root, @pulses, extension];
    END;


-- Procedural access to the spy.

  CallSpy: PUBLIC PROCEDURE [
	self: Handle,
	operation: Private.SpyOperation ] =
  --TEMP, until spy converts to 3.2-- {};
  --  BEGIN OPEN SpyClient;
  --  SELECT operation FROM
  --    startAndWatchProcs, startAndWatchModules => {StandardSpy; ZeroData};
  --    startSpying => StartSpy;
  --    stopSpying => StopSpy;
  --    displayStats, stopAndDisplayStats =>
  --      {StopSpy; DisplayData[herald: "Called by LupineExerciser.", stream: NIL]};
  --    stop => StopSpy;
  --    ENDCASE => ERROR;
  --  END;



-- Module Initialization.

  [] ← RandomInt.InitRandom[seed: -1];

  END.  -- LupineExerciserManagerImpl.


-- Pilot/CoPilot spying interface:

  CallSpy: PROCEDURE[operation: Private.SpyOperation] =
    BEGIN
    SELECT operation FROM
      start =>
	Runtime.CallDebugger["Start Spy..."L];
      startAndWatchDetails =>
	Runtime.CallDebugger["Start Spy and finger specific procs..."L];
      startSpying =>
	SpyClient.StartCounting;
      stopSpying =>
	SpyClient.StopCounting;
      displayStats =>
	Runtime.CallDebugger["Examine Spy statistics..."L];
      stop =>
	Runtime.CallDebugger["Stop Spy..."L];
      stopAndDisplayStats =>
	Runtime.CallDebugger["Stop Spy and examine statistics..."L];
      ENDCASE => ERROR;
    END;


-- Cause random interruptions in RPC processes to test concurrency behavior.

 InterruptControl: TYPE = RECORD [
    state: {started, stopping, finished} ← finished,
    maxInterval, maxBusy: Process.Milliseconds ];

  StartInterruptions: PROCEDURE [self: Handle] =
    BEGIN
    Interrupt: PROC [busyPulses: System.Pulses, idleTicks: Process.Ticks] =
      BEGIN
      self.interrupt.state ← started;
      Process.SetPriority[Process.priorityForeground];
      WHILE self.interrupt.state = started DO
	stopSpinning: System.Pulses =
	  GetClockPulses[] + RandomInt.Choose[min: 0, max: busyPulses];
	UNTIL System.GetClockPulses[] >= stopSpinning DO ENDLOOP;
	Process.Pause[RandomInt.Choose[min: idleTicks/2, max: idleTicks]];
	ENDLOOP;
      self.interrupt.state ← finished;
      END;
    Process.Detach[FORK Interrupt [
	busyPulses: System.MicrosecondsToPulses[self.interruption.maxBusy],
	idleTicks: Process.MsecToTicks[
	  self.interruption.maxInterval-self.interruption.maxBusy] ];
    END;

  StopInterruptions: PROCEDURE [self: Handle] =
    BEGIN
    self.interrupt.state ← stopping;
    UNTIL self.interrupt.state = finished DO
      Process.Pause[Process.MsecToTicks[100]];
      ENDLOOP;
    END;