/// <summary> /// Visit an invoke node /// </summary> /// <param name="plan">Test file plan</param> /// <param name="testSetPlan">TestSetPlan to visit</param> /// <param name="invokeNode">Invoke syntax node</param> /// <returns>Invoke plan</returns> private InvokePlanBase VisitInvoke(TestFilePlan plan, TestSetPlan testSetPlan, InvokeCodeNode invokeNode) { // --- Get start address var start = Eval(plan, testSetPlan, invokeNode.StartExpr); if (start == null) { return(null); } if (invokeNode.IsCall) { return(new CallPlan(start.AsWord())); } if (invokeNode.IsHalt) { return(new StartPlan(start.AsWord(), null)); } // --- Get Stop address var stop = Eval(plan, testSetPlan, invokeNode.StopExpr); return(stop == null ? null : new StartPlan(start.AsWord(), stop.AsWord())); }
/// <summary> /// Visits the machine context of a test set /// </summary> /// <param name="plan">Test file plan</param> /// <param name="testSetPlan">TestSetPlan to visit</param> /// <param name="machineContext">machine context</param> private void VisitMachineContext(TestFilePlan plan, TestSetPlan testSetPlan, MachineContextNode machineContext) { if (machineContext == null) { return; } switch (machineContext.Id.ToUpper()) { case "SPECTRUM48": testSetPlan.MachineType = MachineType.Spectrum48; return; case "SPECTRUM128": testSetPlan.MachineType = MachineType.Spectrum128; return; case "SPECTRUMP3": testSetPlan.MachineType = MachineType.SpectrumP3; return; case "NEXT": testSetPlan.MachineType = MachineType.Next; return; } ReportError(Errors.T0002, plan, machineContext.IdSpan); }
/// <summary> /// Visits a test block /// </summary> /// <param name="plan"></param> /// <param name="testSetPlan"></param> /// <param name="block"></param> /// <returns>Test block plan</returns> private TestBlockPlan VisitTestBlock(TestFilePlan plan, TestSetPlan testSetPlan, TestBlockNode block) { var testBlock = new TestBlockPlan(testSetPlan, block.TestId, block.Category, block.Span); if (block.TestOptions != null) { VisitTestOptions(plan, testSetPlan, block.TestOptions, out var nonmi, out var timeout); testBlock.DisableInterrupt = nonmi; testBlock.TimeoutValue = timeout; } VisitTestParameters(plan, testBlock, block.Params); VisitTestCases(plan, testBlock, block.Cases); var invoke = VisitInvoke(plan, testSetPlan, block.Act); if (invoke != null) { testBlock.Act = invoke; } VisitArrange(plan, testBlock, block.Arrange); if (block.Breakpoints != null) { VisitBreakPoints(plan, testBlock, block.Breakpoints); } testBlock.SignMachineAvalilable(); VisitAssert(plan, testBlock, block.Assert); return(testBlock); }
/// <summary> /// Visits test set options /// </summary> /// <param name="plan">Test file plan</param> /// <param name="testSetPlan">TestSetPlan to visit</param> /// <param name="testOptions">TestOptions syntax node</param> private void VisitTestOptions(TestFilePlan plan, TestSetPlan testSetPlan, TestOptionsNode testOptions) { if (testOptions == null) { return; } VisitTestOptions(plan, testSetPlan, testOptions, out var nonmi, out var timeout); testSetPlan.DisableInterrupt = nonmi ?? false; testSetPlan.TimeoutValue = timeout ?? DEFAULT_TIMEOUT; }
/// <summary> /// Visits a callstub node /// </summary> /// <param name="plan">Test file plan</param> /// <param name="testSetPlan">Test set plan to visit</param> /// <param name="nodeCallStub">Call stub ode to visit</param> private void VisitCallStubContext(TestFilePlan plan, TestSetPlan testSetPlan, CallStubNode nodeCallStub) { if (nodeCallStub == null) { return; } var value = Eval(plan, testSetPlan, nodeCallStub.Value); if (value != null) { testSetPlan.CallStubAddress = value.AsWord(); } }
/// <summary> /// Visits a single test plan /// </summary> /// <param name="plan">Test plan to emit</param> /// <param name="node">TestSetNode to use</param> private TestSetPlan VisitTestSet(TestFilePlan plan, TestSetNode node) { var testSetPlan = new TestSetPlan(node.TestSetId, node.Span); VisitMachineContext(plan, testSetPlan, node.MachineContext); VisitSourceContext(plan, testSetPlan, node.SourceContext); VisitTestOptions(plan, testSetPlan, node.TestOptions); VisitDataBlock(plan, testSetPlan, node.DataBlock); if (node.Init != null) { foreach (var asgn in node.Init.Assignments) { var asgnPlan = VisitAssignment(plan, testSetPlan, asgn); if (asgnPlan != null) { testSetPlan.InitAssignments.Add(asgnPlan); } } } if (node.Setup != null) { testSetPlan.Setup = VisitInvoke(plan, testSetPlan, node.Setup); } if (node.Cleanup != null) { testSetPlan.Cleanup = VisitInvoke(plan, testSetPlan, node.Cleanup); } foreach (var block in node.TestBlocks) { var blockPlan = VisitTestBlock(plan, testSetPlan, block); if (blockPlan != null) { if (testSetPlan.TestBlocks.Any(tb => string.Compare(tb.Id, blockPlan.Id, StringComparison.InvariantCultureIgnoreCase) == 0)) { ReportError(Errors.T0007, plan, block.TestIdSpan, block.TestId); continue; } testSetPlan.TestBlocks.Add(blockPlan); } } return(testSetPlan); }
/// <summary> /// Visits a value data member /// </summary> /// <param name="plan">Test file plan</param> /// <param name="testSetPlan">TestSetPlan to visit</param> /// <param name="valueMember">Value member syntax node</param> private void VisitValueMember(TestFilePlan plan, TestSetPlan testSetPlan, ValueMemberNode valueMember) { // --- Check ID duplication var id = valueMember.Id; if (testSetPlan.ContainsSymbol(id)) { ReportError(Errors.T0006, plan, valueMember.IdSpan, id); return; } // --- Evaluate the expression var value = Eval(plan, testSetPlan, valueMember.Expr); if (value != null) { testSetPlan.SetDataMember(id, value); } }
/// <summary> /// Vistis test set and test node options /// </summary> /// <param name="plan">Test file plan</param> /// <param name="testSetPlan">TestSetPlan to visit</param> /// <param name="testOptions">TestOptions syntax node</param> /// <param name="disableInterrupt">Disable interrupt flag</param> /// <param name="timeout">TIMEOUT value</param> private void VisitTestOptions(TestFilePlan plan, TestSetPlan testSetPlan, TestOptionsNode testOptions, out bool disableInterrupt, out int timeout) { // --- Set default values disableInterrupt = false; timeout = 0; if (testOptions?.Options == null) { return; } // --- Process options var interruptModeFound = false; var timeoutFound = false; foreach (var option in testOptions.Options) { if (option is InterruptOptionNodeBase intNode) { if (interruptModeFound) { ReportError(Errors.T0005, plan, intNode.Span, "DI or EI"); return; } interruptModeFound = true; disableInterrupt = intNode is DiTestOptionNode; } else if (option is TimeoutTestOptionNode timeoutNode) { if (timeoutFound) { ReportError(Errors.T0005, plan, timeoutNode.Span, "TIMEOUT"); return; } timeoutFound = true; var value = Eval(plan, testSetPlan, timeoutNode.Expr); if (value != null) { timeout = (int)value.AsNumber(); } } } }
/// <summary> /// Vistis test set and test node options /// </summary> /// <param name="plan">Test file plan</param> /// <param name="testSetPlan">TestSetPlan to visit</param> /// <param name="testOptions">TestOptions syntax node</param> /// <param name="nonmi">NONMI value</param> /// <param name="timeout">TIMEOUT value</param> private void VisitTestOptions(TestFilePlan plan, TestSetPlan testSetPlan, TestOptionsNode testOptions, out bool?nonmi, out int?timeout) { // --- Set default values nonmi = null; timeout = null; if (testOptions?.Options == null) { return; } // --- Process options var nonmiFound = false; var timeoutFound = false; foreach (var option in testOptions.Options) { if (option is NoNmiTestOptionNode nonmiNode) { if (nonmiFound) { ReportError(Errors.T0005, plan, nonmiNode.Span, "NONMI"); return; } nonmiFound = true; nonmi = true; } else if (option is TimeoutTestOptionNode timeoutNode) { if (timeoutFound) { ReportError(Errors.T0005, plan, timeoutNode.Span, "TIMEOUT"); return; } timeoutFound = true; var value = Eval(plan, testSetPlan, timeoutNode.Expr); if (value != null) { timeout = (int)value.AsNumber(); } } } }
/// <summary> /// Visits the source context of a test set /// </summary> /// <param name="plan">Test file plan</param> /// <param name="testSetPlan">TestSetPlan to visit</param> /// <param name="sourceContext">Machine context</param> private void VisitSourceContext(TestFilePlan plan, TestSetPlan testSetPlan, SourceContextNode sourceContext) { if (sourceContext == null) { return; } // --- Prepare predefined symbols for Z80 compilation var options = new AssemblerOptions(); foreach (var symbol in sourceContext.Symbols) { options.PredefinedSymbols.Add(symbol.Id); } var assembler = new Z80Assembler(); // --- Check filename existence var filename = sourceContext.SourceFile; if (!Path.IsPathRooted(filename)) { filename = Path.Combine(DefaultSourceFolder ?? "", filename); } if (!File.Exists(filename)) { ReportError(Errors.T0003, plan, sourceContext.SourceFileSpan, filename); return; } // --- Compile the Z80 source file var output = assembler.CompileFile(filename, options); if (output.ErrorCount == 0) { testSetPlan.CodeOutput = output; return; } // --- Issue cZ80 ompilation error ReportError(Errors.T0004, plan, sourceContext.SourceFileSpan, filename, output.ErrorCount); }
/// <summary> /// Visits an assignment of a TestSet /// </summary> /// <param name="plan">Test file plan</param> /// <param name="testSetPlan">TestSetPlan to visit</param> /// <param name="asgn">Assignment syntax node</param> /// <returns>Assignment plan</returns> private AssignmentPlanBase VisitAssignment(TestFilePlan plan, TestSetPlan testSetPlan, AssignmentNode asgn) { if (asgn is RegisterAssignmentNode regAsgn) { var value = Eval(plan, testSetPlan, regAsgn.Expr); return(value != null ? new RegisterAssignmentPlan(regAsgn.RegisterName, value.AsWord()) : null); } if (asgn is FlagAssignmentNode flagAsgn) { return(new FlagAssignmentPlan(flagAsgn.FlagName)); } if (asgn is MemoryAssignmentNode memAsgn) { var address = Eval(plan, testSetPlan, memAsgn.Address); var value = Eval(plan, testSetPlan, memAsgn.Value); if (address == null || value == null) { return(null); } ExpressionValue length = null; if (memAsgn.Length != null) { length = Eval(plan, testSetPlan, memAsgn.Length); if (length == null) { return(null); } } return(length == null ? new MemoryAssignmentPlan(address.AsWord(), value.AsByteArray()) : new MemoryAssignmentPlan(address.AsWord(), value.AsByteArray().Take(length.AsWord()).ToArray())); } return(null); }
/// <summary> /// Visit a data block /// </summary> /// <param name="plan">Test file plan</param> /// <param name="testSetPlan">TestSetPlan to visit</param> /// <param name="dataBlock">DataBlock syntax node</param> private void VisitDataBlock(TestFilePlan plan, TestSetPlan testSetPlan, DataBlockNode dataBlock) { if (dataBlock == null) { return; } foreach (var dataMember in dataBlock.DataMembers) { if (dataMember is ValueMemberNode valueMember) { VisitValueMember(plan, testSetPlan, valueMember); } else if (dataMember is MemoryPatternMemberNode mempatMember) { VisitMemoryPatternMember(plan, testSetPlan, mempatMember); } else if (dataMember is PortMockMemberNode portMockMember) { VisitPortMockMember(plan, testSetPlan, portMockMember); } } }
/// <summary> /// Visits a test block /// </summary> /// <param name="plan">Test file plan</param> /// <param name="testSetPlan">Test set plan to visit</param> /// <param name="block">Block to visit</param> /// <returns>Test block plan</returns> private TestBlockPlan VisitTestBlock(TestFilePlan plan, TestSetPlan testSetPlan, TestBlockNode block) { var testBlock = new TestBlockPlan(testSetPlan, block.TestId, block.Category, block.Span); if (block.TestOptions != null) { VisitTestOptions(plan, testSetPlan, block.TestOptions, out var disableInterrupt, out var timeout); testBlock.DisableInterrupt = disableInterrupt; testBlock.TimeoutValue = timeout; } if (block.Setup != null) { testBlock.Setup = VisitInvoke(plan, testSetPlan, block.Setup); } VisitTestParameters(plan, testBlock, block.Params); VisitTestCases(plan, testBlock, block.Cases); var invoke = VisitInvoke(plan, testSetPlan, block.Act); if (invoke != null) { testBlock.Act = invoke; } VisitArrange(plan, testBlock, block.Arrange); if (block.Breakpoints != null) { VisitBreakPoints(plan, testBlock, block.Breakpoints); } testBlock.MachineContext = new CompileTimeMachineContext(); VisitAssert(plan, testBlock, block.Assert); if (block.Cleanup != null) { testBlock.Cleanup = VisitInvoke(plan, testSetPlan, block.Cleanup); } return(testBlock); }
/// <summary> /// Invokes the code and waits for its completion within the specified /// timeout limits. /// </summary> /// <param name="testItem">Test item that invokes this method</param> /// <param name="invokePlan">Invokation plan</param> /// <param name="timeout">Timeout in milliseconds</param> /// <param name="token">Token to cancel test run</param> /// <param name="watch">Optional stopwatch for diagnostics</param> /// <returns>True, if code completed; otherwise, false</returns> private async Task <bool> InvokeCodeAsync(TestItemBase testItem, InvokePlanBase invokePlan, int timeout, CancellationToken token, Stopwatch watch = null) { if (invokePlan == null) { return(true); } if (!(Package.MachineViewModel.SpectrumVm is ISpectrumVmRunCodeSupport runCodeSupport)) { return(false); } // --- Prepare code invocation ExecuteCycleOptions runOptions; bool removeFromHalt = false; var spectrumVm = Package.MachineViewModel.SpectrumVm; var timeoutTacts = timeout * spectrumVm.BaseClockFrequency * spectrumVm.ClockMultiplier / 1000; if (invokePlan is CallPlan callPlan) { // --- Obtain Call stub address TestSetPlan testSetPlan = null; switch (testItem) { case TestSetItem set: testSetPlan = set.Plan; break; case TestItem item: testSetPlan = item.Plan.TestSet; break; case TestCaseItem caseItem: testSetPlan = caseItem.Plan.TestBlock.TestSet; break; } var callStubAddress = testSetPlan?.CallStubAddress ?? DEFAULT_CALL_STUB_ADDRESS; // --- Create CALL stub Package.MachineViewModel.SpectrumVm.Cpu.Registers.PC = callStubAddress; runCodeSupport.InjectCodeToMemory(callStubAddress, new byte[] { 0xCD, (byte)callPlan.Address, (byte)(callPlan.Address >> 8) }); runOptions = new ExecuteCycleOptions(EmulationMode.UntilExecutionPoint, terminationPoint: (ushort)(callStubAddress + 3), fastVmMode: true, timeoutTacts: timeout * spectrumVm.BaseClockFrequency * spectrumVm.ClockMultiplier / 1000); } else if (invokePlan is StartPlan startPlan) { spectrumVm.Cpu.Registers.PC = startPlan.Address; if (startPlan.StopAddress == null) { // --- Start and run until halt runOptions = new ExecuteCycleOptions(EmulationMode.UntilHalt, fastVmMode: true, timeoutTacts: timeoutTacts); removeFromHalt = true; } else { // --- Start and run until the stop address is reached runOptions = new ExecuteCycleOptions(EmulationMode.UntilExecutionPoint, terminationPoint: startPlan.StopAddress.Value, fastVmMode: true, timeoutTacts: timeoutTacts); } } else { return(false); } // --- Prepare the machine to run the code var initialTacts = Package.MachineViewModel.SpectrumVm.Cpu.Tacts; var machine = Package.MachineViewModel.Machine; var cpuSupport = Package.MachineViewModel.SpectrumVm.Cpu as IZ80CpuTestSupport; cpuSupport.ExecutionFlowStatus.ClearAll(); cpuSupport.MemoryReadStatus.ClearAll(); cpuSupport.MemoryWriteStatus.ClearAll(); Package.MachineViewModel.NoToolRefreshMode = true; // --- Start the machine machine.Start(runOptions); ReportTimeDetail("Start VM:", testItem, watch); // --- Waith for completion var completion = machine.CompletionTask; await completion; // --- Report outcome ReportTimeDetail("Complete VM:", testItem, watch); var endTacts = Package.MachineViewModel.SpectrumVm.Cpu.Tacts; if (Package.Options.TestTStateExecutionLogging) { testItem.Log($"#of T-States consumed: {endTacts - initialTacts}"); } // --- Check if code ran successfully var success = !completion.IsFaulted && !completion.IsCanceled && !token.IsCancellationRequested && machine.ExecutionCycleResult; // --- Take care the VM is paused await machine.Pause(); ReportTimeDetail("Pause VM:", testItem, watch); // --- If the CPU was halted, it should be removed from this state for the next run if (removeFromHalt) { var cpu = Package.MachineViewModel.SpectrumVm.Cpu as IZ80CpuTestSupport; cpu?.RemoveFromHaltedState(); } // --- Handle user cancellation/Timeout if (!success) { if (token.IsCancellationRequested) { throw new TaskCanceledException(); } testItem.State = TestState.Aborted; testItem.Log("Timeout expired. Test aborted.", LogEntryType.Fail); } return(success); }
/// <summary> /// Visits a memory pattern member /// </summary> /// <param name="plan">Test file plan</param> /// <param name="testSetPlan">TestSetPlan to visit</param> /// <param name="mempatMember">Memory pattern member syntax node</param> private void VisitMemoryPatternMember(TestFilePlan plan, TestSetPlan testSetPlan, MemoryPatternMemberNode mempatMember) { // --- Check ID duplication var id = mempatMember.Id; if (testSetPlan.ContainsSymbol(id)) { ReportError(Errors.T0006, plan, mempatMember.IdSpan, id); return; } // --- Evaluate byte array var bytes = new List <byte>(); var errorFound = false; foreach (var mempat in mempatMember.Patterns) { if (mempat is BytePatternNode bytePattern) { foreach (var byteExpr in bytePattern.Bytes) { var value = Eval(plan, testSetPlan, byteExpr); if (value != null) { bytes.Add((byte)value.AsNumber()); } else { errorFound = true; } } } else if (mempat is WordPatternNode wordPattern) { foreach (var byteExpr in wordPattern.Words) { var value = Eval(plan, testSetPlan, byteExpr); if (value != null) { var word = value.AsWord(); bytes.Add((byte)word); bytes.Add((byte)(word >> 8)); } else { errorFound = true; } } } else if (mempat is TextPatternNode textPattern) { foreach (var val in SyntaxHelper.SpectrumStringToBytes(textPattern.String)) { bytes.Add(val); } } } if (errorFound) { return; } testSetPlan.SetDataMember(id, new ExpressionValue(bytes.ToArray())); }
/// <summary> /// Visit a port mock member /// </summary> /// <param name="plan">Test file plan</param> /// <param name="testSetPlan">TestSetPlan to visit</param> /// <param name="portMockMember">Port mock syntax node</param> private void VisitPortMockMember(TestFilePlan plan, TestSetPlan testSetPlan, PortMockMemberNode portMockMember) { // --- Check ID duplication var id = portMockMember.Id; if (testSetPlan.ContainsSymbol(id)) { ReportError(Errors.T0006, plan, portMockMember.IdSpan, id); return; } // --- Get port address var portAddress = Eval(plan, testSetPlan, portMockMember.Expr); var portMock = portAddress != null ? new PortMockPlan(portAddress.AsWord()) : null; // --- Get pulses var pulsesOk = true; var nextPulseStart = 0L; foreach (var pulse in portMockMember.Pulses) { // --- Get pulse expression values var value = Eval(plan, testSetPlan, pulse.ValueExpr); var pulse1 = Eval(plan, testSetPlan, pulse.Pulse1Expr); if (value == null || pulse1 == null) { pulsesOk = false; continue; } ExpressionValue pulse2 = null; if (pulse.Pulse2Expr != null) { pulse2 = Eval(plan, testSetPlan, pulse.Pulse2Expr); if (pulse2 == null) { pulsesOk = false; continue; } } // --- Create a new pulse PortPulsePlan pulsePlan; if (pulse2 == null) { // --- We have only value and length pulsePlan = new PortPulsePlan(value.AsByte(), nextPulseStart, nextPulseStart + pulse1.AsNumber() - 1); } else { // --- We have pulse value, start, and end pulsePlan = new PortPulsePlan(value.AsByte(), pulse1.AsNumber(), pulse.IsInterval ? pulse2.AsNumber() : pulse1.AsNumber() + pulse2.AsNumber() - 1); } nextPulseStart = pulsePlan.EndTact + 1; portMock?.AddPulse(pulsePlan); } // --- Create the entire port mock if (portMock != null && pulsesOk) { testSetPlan.SetPortMock(id, portMock); } }