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