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