feat(diagnostics): VM emits trace events + CallScriptDispatches stat
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -31,8 +31,8 @@ public class CallScriptIntegrationTests
|
||||
var host = new NullHost();
|
||||
var vm = new VirtualMachine(script, t, host, null, provider);
|
||||
vm.Run();
|
||||
Assert.Equal(2, host.CallScripts); // ADDILLSUB + CALCREVISE both dispatched
|
||||
Assert.Equal("exit", vm.HaltReason); // subroutines returned; ADDILL reached its own exit
|
||||
Assert.Equal(2, vm.CallScriptDispatches); // ADDILLSUB + CALCREVISE both dispatched
|
||||
Assert.Equal("exit", vm.HaltReason); // subroutines returned; ADDILL reached its own exit
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -58,12 +58,13 @@ public class CallScriptTests
|
||||
OP_MOV, 3, 0x11, 3, 0x10,
|
||||
OP_EXIT);
|
||||
var host = new NullHost();
|
||||
var vm = new VirtualMachine(caller, t, host, null, new MapProvider(new() { [5] = callee }));
|
||||
var sink = new RecordingTraceSink();
|
||||
var vm = new VirtualMachine(caller, t, host, null, new MapProvider(new() { [5] = callee }), sink);
|
||||
vm.Run();
|
||||
Assert.Equal(7, vm.Globals[0x10]); // callee wrote a shared global
|
||||
Assert.Equal(7, vm.Globals[0x11]); // caller read it AFTER the call returned
|
||||
Assert.Equal("exit", vm.HaltReason); // top-level exit
|
||||
Assert.Contains(5L, host.Calls); // host notified
|
||||
Assert.Contains(5L, sink.CallScriptIds); // dispatch observed via the trace sink
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -72,10 +73,11 @@ public class CallScriptTests
|
||||
var t = Table();
|
||||
var caller = Asm(t, "CALLER", OP_CALLSCRIPT, 0, 5, OP_EXIT);
|
||||
var host = new NullHost();
|
||||
var vm = new VirtualMachine(caller, t, host, null, null); // no provider
|
||||
var sink = new RecordingTraceSink();
|
||||
var vm = new VirtualMachine(caller, t, host, null, null, sink); // no provider
|
||||
vm.Run();
|
||||
Assert.Equal("exit", vm.HaltReason); // did not halt on the call; stub + continue
|
||||
Assert.Contains(5L, host.Calls);
|
||||
Assert.Contains(5L, sink.CallScriptIds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Age.Engine.Diagnostics;
|
||||
using Age.Engine.Hosting;
|
||||
using Age.Engine.Model;
|
||||
|
||||
@@ -36,3 +38,14 @@ internal sealed class AnyProvider : IScriptProvider
|
||||
public AnyProvider(Script s) => _s = s;
|
||||
public Script? GetById(long id) => _s;
|
||||
}
|
||||
|
||||
/// <summary>Captures every trace event for assertions; TracingSteps is settable so a test can
|
||||
/// exercise the Step gate both ways.</summary>
|
||||
internal sealed class RecordingTraceSink : ITraceSink
|
||||
{
|
||||
public bool TracingSteps { get; init; }
|
||||
public readonly List<TraceEvent> Events = new();
|
||||
public void Emit(in TraceEvent e) => Events.Add(e);
|
||||
public List<long> CallScriptIds =>
|
||||
Events.Where(e => e.Kind == TraceEventKind.CallScript).Select(e => e.Id).ToList();
|
||||
}
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Age.Engine.Diagnostics;
|
||||
using Age.Engine.Model;
|
||||
using Age.Engine.Sys4;
|
||||
using Age.Engine.Vm;
|
||||
using Xunit;
|
||||
|
||||
public class TraceSinkTests
|
||||
{
|
||||
private static OpcodeTable Table() => OpcodeTableJson.Load(Paths.OpcodesJson);
|
||||
|
||||
[Fact]
|
||||
public void FactoriesSetKindAndFields()
|
||||
{
|
||||
@@ -41,4 +48,49 @@ public class TraceSinkTests
|
||||
Assert.Contains("call-script 0x1ab =ADDITEM (resolved)", outp);
|
||||
Assert.Contains("halt: exit @ 27994 steps", outp);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VmEmitsFrameCallScriptAndHaltEvents()
|
||||
{
|
||||
var t = Table();
|
||||
// callee: exit. caller: call-script 5 ; exit.
|
||||
var callee = ScriptAssembler.Assemble(t, "CALLEE",
|
||||
new List<(int, Operand[])> { (0x2, Array.Empty<Operand>()) }, Array.Empty<string>());
|
||||
var caller = ScriptAssembler.Assemble(t, "CALLER",
|
||||
new List<(int, Operand[])> { (0x3, new[] { new Operand(0, 5) }), (0x2, Array.Empty<Operand>()) },
|
||||
Array.Empty<string>());
|
||||
var sink = new RecordingTraceSink();
|
||||
var vm = new VirtualMachine(caller, t, new RecordingHost(), null,
|
||||
new MapProvider(new() { [5] = callee }), sink);
|
||||
vm.Run();
|
||||
|
||||
var kinds = sink.Events.Select(e => e.Kind).ToList();
|
||||
Assert.Equal(TraceEventKind.FrameEnter, kinds[0]); // caller enters first
|
||||
Assert.Equal(TraceEventKind.Halt, kinds[^1]); // halt is last
|
||||
Assert.Equal(2, sink.Events.Count(e => e.Kind == TraceEventKind.FrameEnter)); // caller + callee
|
||||
Assert.Equal(2, sink.Events.Count(e => e.Kind == TraceEventKind.FrameExit));
|
||||
Assert.Contains(5L, sink.CallScriptIds);
|
||||
Assert.Equal(1, vm.CallScriptDispatches);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StepEventsGatedByTracingSteps()
|
||||
{
|
||||
var t = Table();
|
||||
// mov g[0x10]=7 ; exit => 2 executed instructions.
|
||||
var body = new List<(int, Operand[])>
|
||||
{
|
||||
(0x55, new[] { new Operand(3, 0x10), new Operand(0, 7) }),
|
||||
(0x2, Array.Empty<Operand>()),
|
||||
};
|
||||
var s = ScriptAssembler.Assemble(t, "S", body, Array.Empty<string>());
|
||||
|
||||
var off = new RecordingTraceSink { TracingSteps = false };
|
||||
new VirtualMachine(s, t, new RecordingHost(), null, null, off).Run();
|
||||
Assert.Empty(off.Events.Where(e => e.Kind == TraceEventKind.Step));
|
||||
|
||||
var on = new RecordingTraceSink { TracingSteps = true };
|
||||
new VirtualMachine(s, t, new RecordingHost(), null, null, on).Run();
|
||||
Assert.Equal(2, on.Events.Count(e => e.Kind == TraceEventKind.Step));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user