test: remove ambient timing from double-click contracts
This commit is contained in:
parent
3684e7b5e7
commit
c8c764a40e
5 changed files with 151 additions and 40 deletions
|
|
@ -129,7 +129,7 @@ identifiable in the lane report.
|
||||||
| T-006 misleading installed-DAT reason | resolved in batch B | Shared opt-in is now `ACDREAM_RUN_INSTALLED_DAT_TESTS=1` (legacy switch retained), the reason names the lane, and all nine owners carry `Lane=InstalledDat`. |
|
| T-006 misleading installed-DAT reason | resolved in batch B | Shared opt-in is now `ACDREAM_RUN_INSTALLED_DAT_TESTS=1` (legacy switch retained), the reason names the lane, and all nine owners carry `Lane=InstalledDat`. |
|
||||||
| T-007 271 silent passing gates | lane classification complete in batch D; body cleanup active | Of 282 current candidates, 280 are true prerequisite gates and now have an explicit lane. The two unlaned methods are reviewed false positives that assert the current Windows/Linux branch before returning. Empty-return removal remains, lane by lane. |
|
| T-007 271 silent passing gates | lane classification complete in batch D; body cleanup active | Of 282 current candidates, 280 are true prerequisite gates and now have an explicit lane. The two unlaned methods are reviewed false positives that assert the current Windows/Linux branch before returning. Empty-return removal remains, lane by lane. |
|
||||||
| T-008 incapable-of-failing diagnostics | batch A plus diagnostic lane work | Delete the literal wiring smoke test; repair the GPU contract tautology; later re-home output-only apparatus. |
|
| T-008 incapable-of-failing diagnostics | batch A plus diagnostic lane work | Delete the literal wiring smoke test; repair the GPU contract tautology; later re-home output-only apparatus. |
|
||||||
| T-009 wall-clock double-click tests | open | Introduce a behavior-preserving injectable monotonic clock and deterministic fake time. |
|
| T-009 wall-clock double-click tests | resolved in batch E | Four sleeps were replaced by a deterministic test clock behind an internal factory overload. The production factory still reads `Environment.TickCount64` exactly as before. |
|
||||||
| T-010 two useless cases | high-confidence cleanup batch A | Delete `SmokeTest.TestProject_IsWired` and `ChaseCameraTests.ImplementsICamera`; compilation already proves both claims. |
|
| T-010 two useless cases | high-confidence cleanup batch A | Delete `SmokeTest.TestProject_IsWired` and `ChaseCameraTests.ImplementsICamera`; compilation already proves both claims. |
|
||||||
| T-011 51 output-only methods | classified in batch C | The reviewed current set is 51 methods / 70 cases. All carry `Purpose=Diagnostic`, preserving the apparatus while removing it from release pass totals. Contract-shaped names remain explicitly flagged until a stable oracle exists. |
|
| T-011 51 output-only methods | classified in batch C | The reviewed current set is 51 methods / 70 cases. All carry `Purpose=Diagnostic`, preserving the apparatus while removing it from release pass totals. Contract-shaped names remain explicitly flagged until a stable oracle exists. |
|
||||||
| T-012 source-text freezes | requires semantic replacement map | Retain whole-tree dependency rules; remove exact-text freezes only when an equivalent semantic/behavioral guard is identified. |
|
| T-012 source-text freezes | requires semantic replacement map | Retain whole-tree dependency rules; remove exact-text freezes only when an equivalent semantic/behavioral guard is identified. |
|
||||||
|
|
@ -310,3 +310,23 @@ netted minus one case; batch B removed six non-hermetic passes; batch C removed
|
||||||
passes; and consolidating the six Avalonia sessions into one removed five case
|
passes; and consolidating the six Avalonia sessions into one removed five case
|
||||||
IDs without removing any assertion phase. All 77 former default skips are also
|
IDs without removing any assertion phase. All 77 former default skips are also
|
||||||
accounted for by the PVS deletion and batch-B lane/deletion decisions.
|
accounted for by the PVS deletion and batch-B lane/deletion decisions.
|
||||||
|
|
||||||
|
## Batch E indirect gates and deterministic input time
|
||||||
|
|
||||||
|
The inventory now follows same-file helper calls when looking for prerequisite
|
||||||
|
returns. It found one additional path:
|
||||||
|
`LauncherSelfUpdateProcessTests.BackupJunctionOrSymlinkAfterCanonicalCrashCannotMutateOutsideOrLaunch`
|
||||||
|
calls `CreateDirectoryLink`, whose non-Windows branch creates the symbolic link
|
||||||
|
and then returns before the Windows `mklink /J` implementation. This is a
|
||||||
|
reviewed cross-platform control-flow branch, not a prerequisite gate or silent
|
||||||
|
pass. The combined direct/indirect candidate count is therefore 283: 280 true
|
||||||
|
lane-owned gates and three reviewed branch false positives.
|
||||||
|
|
||||||
|
The four `InputDispatcherDoubleClickTests` no longer sleep for 10 or 600 real
|
||||||
|
milliseconds. `InputDispatcher` has an internal, test-assembly-only factory
|
||||||
|
overload accepting the same millisecond tick delegate used by double-click
|
||||||
|
recognition. The public production factory remains wired directly to
|
||||||
|
`Environment.TickCount64`; only the tests use a manually advanced counter.
|
||||||
|
The focused class passes 4/4 in 17 ms with exact 10 ms and 600 ms virtual
|
||||||
|
intervals. The complete Release build then passed with 0 warnings/errors, and
|
||||||
|
the no-retry hermetic gate passed 14,392/14,392 with zero skips or failures.
|
||||||
|
|
|
||||||
|
|
@ -13,4 +13,7 @@
|
||||||
<ProjectReference Include="..\AcDream.Core\AcDream.Core.csproj" />
|
<ProjectReference Include="..\AcDream.Core\AcDream.Core.csproj" />
|
||||||
<ProjectReference Include="..\AcDream.Runtime\AcDream.Runtime.csproj" />
|
<ProjectReference Include="..\AcDream.Runtime\AcDream.Runtime.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<InternalsVisibleTo Include="AcDream.UI.Abstractions.Tests" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ public sealed class InputDispatcher : IDisposable
|
||||||
{
|
{
|
||||||
private readonly IKeyboardSource _keyboard;
|
private readonly IKeyboardSource _keyboard;
|
||||||
private readonly IMouseSource _mouse;
|
private readonly IMouseSource _mouse;
|
||||||
|
private readonly Func<long> _getTickCount64;
|
||||||
private KeyBindings _bindings;
|
private KeyBindings _bindings;
|
||||||
private readonly Stack<InputScope> _scopes = new();
|
private readonly Stack<InputScope> _scopes = new();
|
||||||
private InputScope? _combatScope;
|
private InputScope? _combatScope;
|
||||||
|
|
@ -67,11 +68,14 @@ public sealed class InputDispatcher : IDisposable
|
||||||
private InputDispatcher(
|
private InputDispatcher(
|
||||||
IKeyboardSource keyboard,
|
IKeyboardSource keyboard,
|
||||||
IMouseSource mouse,
|
IMouseSource mouse,
|
||||||
KeyBindings bindings)
|
KeyBindings bindings,
|
||||||
|
Func<long> getTickCount64)
|
||||||
{
|
{
|
||||||
_keyboard = keyboard ?? throw new ArgumentNullException(nameof(keyboard));
|
_keyboard = keyboard ?? throw new ArgumentNullException(nameof(keyboard));
|
||||||
_mouse = mouse ?? throw new ArgumentNullException(nameof(mouse));
|
_mouse = mouse ?? throw new ArgumentNullException(nameof(mouse));
|
||||||
_bindings = bindings ?? throw new ArgumentNullException(nameof(bindings));
|
_bindings = bindings ?? throw new ArgumentNullException(nameof(bindings));
|
||||||
|
_getTickCount64 = getTickCount64
|
||||||
|
?? throw new ArgumentNullException(nameof(getTickCount64));
|
||||||
|
|
||||||
_scopes.Push(InputScope.Always); // bottom of the stack
|
_scopes.Push(InputScope.Always); // bottom of the stack
|
||||||
_scopes.Push(InputScope.Game); // default top for normal play
|
_scopes.Push(InputScope.Game); // default top for normal play
|
||||||
|
|
@ -87,7 +91,18 @@ public sealed class InputDispatcher : IDisposable
|
||||||
IKeyboardSource keyboard,
|
IKeyboardSource keyboard,
|
||||||
IMouseSource mouse,
|
IMouseSource mouse,
|
||||||
KeyBindings bindings) =>
|
KeyBindings bindings) =>
|
||||||
new(keyboard, mouse, bindings);
|
new(keyboard, mouse, bindings, static () => Environment.TickCount64);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deterministic test seam for time-sensitive input contracts. Production
|
||||||
|
/// construction always uses <see cref="Environment.TickCount64"/>.
|
||||||
|
/// </summary>
|
||||||
|
internal static InputDispatcher CreateDetached(
|
||||||
|
IKeyboardSource keyboard,
|
||||||
|
IMouseSource mouse,
|
||||||
|
KeyBindings bindings,
|
||||||
|
Func<long> getTickCount64) =>
|
||||||
|
new(keyboard, mouse, bindings, getTickCount64);
|
||||||
|
|
||||||
public bool IsDisposalComplete =>
|
public bool IsDisposalComplete =>
|
||||||
_sourceAttached.All(static attached => !attached);
|
_sourceAttached.All(static attached => !attached);
|
||||||
|
|
@ -570,7 +585,7 @@ public sealed class InputDispatcher : IDisposable
|
||||||
// -> additionally fire ActivationType.DoubleClick for any matching
|
// -> additionally fire ActivationType.DoubleClick for any matching
|
||||||
// binding. Press has already fired for the second click (same as a
|
// binding. Press has already fired for the second click (same as a
|
||||||
// single click); DoubleClick is the *additional* signal.
|
// single click); DoubleClick is the *additional* signal.
|
||||||
long nowMs = Environment.TickCount64;
|
long nowMs = _getTickCount64();
|
||||||
if (_lastMouseDownButton == button
|
if (_lastMouseDownButton == button
|
||||||
&& nowMs - _lastMouseDownTickMs <= DoubleClickThresholdMs)
|
&& nowMs - _lastMouseDownTickMs <= DoubleClickThresholdMs)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
|
||||||
using AcDream.UI.Abstractions.Input;
|
using AcDream.UI.Abstractions.Input;
|
||||||
using Silk.NET.Input;
|
using Silk.NET.Input;
|
||||||
|
|
||||||
|
|
@ -19,7 +18,7 @@ public class InputDispatcherDoubleClickTests
|
||||||
/// Build a dispatcher wired with LMB Press → SelectLeft,
|
/// Build a dispatcher wired with LMB Press → SelectLeft,
|
||||||
/// LMB DoubleClick → SelectDblLeft, and RMB Press → SelectRight.
|
/// LMB DoubleClick → SelectDblLeft, and RMB Press → SelectRight.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static (InputDispatcher dispatcher, FakeMouseSource mouse, List<(InputAction, ActivationType)> fired)
|
private static (InputDispatcher dispatcher, FakeMouseSource mouse, ManualTickClock clock, List<(InputAction, ActivationType)> fired)
|
||||||
Build()
|
Build()
|
||||||
{
|
{
|
||||||
var kb = new FakeKeyboardSource();
|
var kb = new FakeKeyboardSource();
|
||||||
|
|
@ -33,11 +32,12 @@ public class InputDispatcherDoubleClickTests
|
||||||
bindings.Add(new Binding(lmbChord, InputAction.SelectDblLeft, ActivationType.DoubleClick));
|
bindings.Add(new Binding(lmbChord, InputAction.SelectDblLeft, ActivationType.DoubleClick));
|
||||||
bindings.Add(new Binding(rmbChord, InputAction.SelectRight));
|
bindings.Add(new Binding(rmbChord, InputAction.SelectRight));
|
||||||
|
|
||||||
var dispatcher = InputDispatcher.CreateDetached(kb, mouse, bindings);
|
var clock = new ManualTickClock();
|
||||||
|
var dispatcher = InputDispatcher.CreateDetached(kb, mouse, bindings, clock.Read);
|
||||||
dispatcher.Attach();
|
dispatcher.Attach();
|
||||||
var fired = new List<(InputAction, ActivationType)>();
|
var fired = new List<(InputAction, ActivationType)>();
|
||||||
dispatcher.Fired += (a, t) => fired.Add((a, t));
|
dispatcher.Fired += (a, t) => fired.Add((a, t));
|
||||||
return (dispatcher, mouse, fired);
|
return (dispatcher, mouse, clock, fired);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -47,10 +47,10 @@ public class InputDispatcherDoubleClickTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void SecondClick_WithinThreshold_FiresDoubleClick()
|
public void SecondClick_WithinThreshold_FiresDoubleClick()
|
||||||
{
|
{
|
||||||
var (_, mouse, fired) = Build();
|
var (_, mouse, clock, fired) = Build();
|
||||||
|
|
||||||
mouse.EmitMouseDown(MouseButton.Left, ModifierMask.None);
|
mouse.EmitMouseDown(MouseButton.Left, ModifierMask.None);
|
||||||
Thread.Sleep(10);
|
clock.Advance(10);
|
||||||
mouse.EmitMouseDown(MouseButton.Left, ModifierMask.None);
|
mouse.EmitMouseDown(MouseButton.Left, ModifierMask.None);
|
||||||
|
|
||||||
// Two SelectLeft Press events (one per click).
|
// Two SelectLeft Press events (one per click).
|
||||||
|
|
@ -67,10 +67,10 @@ public class InputDispatcherDoubleClickTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void SecondClick_BeyondThreshold_DoesNotFireDoubleClick()
|
public void SecondClick_BeyondThreshold_DoesNotFireDoubleClick()
|
||||||
{
|
{
|
||||||
var (_, mouse, fired) = Build();
|
var (_, mouse, clock, fired) = Build();
|
||||||
|
|
||||||
mouse.EmitMouseDown(MouseButton.Left, ModifierMask.None);
|
mouse.EmitMouseDown(MouseButton.Left, ModifierMask.None);
|
||||||
Thread.Sleep(600);
|
clock.Advance(600);
|
||||||
mouse.EmitMouseDown(MouseButton.Left, ModifierMask.None);
|
mouse.EmitMouseDown(MouseButton.Left, ModifierMask.None);
|
||||||
|
|
||||||
Assert.Equal(2, fired.FindAll(e => e == (InputAction.SelectLeft, ActivationType.Press)).Count);
|
Assert.Equal(2, fired.FindAll(e => e == (InputAction.SelectLeft, ActivationType.Press)).Count);
|
||||||
|
|
@ -83,10 +83,10 @@ public class InputDispatcherDoubleClickTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void DifferentButtons_DoNotFireDoubleClick()
|
public void DifferentButtons_DoNotFireDoubleClick()
|
||||||
{
|
{
|
||||||
var (_, mouse, fired) = Build();
|
var (_, mouse, clock, fired) = Build();
|
||||||
|
|
||||||
mouse.EmitMouseDown(MouseButton.Left, ModifierMask.None);
|
mouse.EmitMouseDown(MouseButton.Left, ModifierMask.None);
|
||||||
Thread.Sleep(10);
|
clock.Advance(10);
|
||||||
mouse.EmitMouseDown(MouseButton.Right, ModifierMask.None);
|
mouse.EmitMouseDown(MouseButton.Right, ModifierMask.None);
|
||||||
|
|
||||||
Assert.Empty(fired.FindAll(e => e.Item2 == ActivationType.DoubleClick));
|
Assert.Empty(fired.FindAll(e => e.Item2 == ActivationType.DoubleClick));
|
||||||
|
|
@ -100,12 +100,12 @@ public class InputDispatcherDoubleClickTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ThirdClick_AfterDoubleClick_RequiresFreshPair()
|
public void ThirdClick_AfterDoubleClick_RequiresFreshPair()
|
||||||
{
|
{
|
||||||
var (_, mouse, fired) = Build();
|
var (_, mouse, clock, fired) = Build();
|
||||||
|
|
||||||
mouse.EmitMouseDown(MouseButton.Left, ModifierMask.None); // click 1
|
mouse.EmitMouseDown(MouseButton.Left, ModifierMask.None); // click 1
|
||||||
Thread.Sleep(10);
|
clock.Advance(10);
|
||||||
mouse.EmitMouseDown(MouseButton.Left, ModifierMask.None); // click 2 → DoubleClick fires, state reset
|
mouse.EmitMouseDown(MouseButton.Left, ModifierMask.None); // click 2 → DoubleClick fires, state reset
|
||||||
Thread.Sleep(10);
|
clock.Advance(10);
|
||||||
mouse.EmitMouseDown(MouseButton.Left, ModifierMask.None); // click 3 → no DoubleClick (fresh pair started)
|
mouse.EmitMouseDown(MouseButton.Left, ModifierMask.None); // click 3 → no DoubleClick (fresh pair started)
|
||||||
|
|
||||||
// Three Press events total.
|
// Three Press events total.
|
||||||
|
|
@ -114,4 +114,14 @@ public class InputDispatcherDoubleClickTests
|
||||||
// Exactly one DoubleClick (between clicks 1 and 2).
|
// Exactly one DoubleClick (between clicks 1 and 2).
|
||||||
Assert.Single(fired.FindAll(e => e == (InputAction.SelectDblLeft, ActivationType.DoubleClick)));
|
Assert.Single(fired.FindAll(e => e == (InputAction.SelectDblLeft, ActivationType.DoubleClick)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class ManualTickClock
|
||||||
|
{
|
||||||
|
private long _tickCount64;
|
||||||
|
|
||||||
|
public long Read() => _tickCount64;
|
||||||
|
|
||||||
|
public void Advance(long milliseconds) =>
|
||||||
|
_tickCount64 = checked(_tickCount64 + milliseconds);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@
|
||||||
Parses tracked C# files under tests/ with the Roslyn assemblies bundled in
|
Parses tracked C# files under tests/ with the Roslyn assemblies bundled in
|
||||||
the active .NET SDK. The report is evidence for the R3 test-truth audit: it
|
the active .NET SDK. The report is evidence for the R3 test-truth audit: it
|
||||||
records test attributes and traits, static skips, empty-return sites,
|
records test attributes and traits, static skips, empty-return sites,
|
||||||
assertion/throw signals (including same-file helper calls), diagnostic
|
prerequisite gates reached through same-file helper calls, assertion/throw
|
||||||
output signals, wall-clock waits, environment-variable dependencies, and
|
signals (including same-file helper calls), diagnostic output signals,
|
||||||
source-text reads.
|
wall-clock waits, environment-variable dependencies, and source-text reads.
|
||||||
|
|
||||||
This is a candidate generator, not a semantic proof. In particular, an
|
This is a candidate generator, not a semantic proof. In particular, an
|
||||||
output-only candidate can call a failure-capable helper declared in another
|
output-only candidate can call a failure-capable helper declared in another
|
||||||
|
|
@ -107,6 +107,35 @@ function Get-ReturnGateKind {
|
||||||
return $null
|
return $null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Get-EmptyReturnSites {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]$Tree,
|
||||||
|
[Parameter(Mandatory)]$Method
|
||||||
|
)
|
||||||
|
|
||||||
|
$sites = [Collections.Generic.List[object]]::new()
|
||||||
|
foreach ($returnNode in @($Method.DescendantNodes() | Where-Object {
|
||||||
|
$_.GetType().Name -eq 'ReturnStatementSyntax' -and
|
||||||
|
$null -eq $_.Expression
|
||||||
|
})) {
|
||||||
|
$condition = '<unconditional>'
|
||||||
|
$cursor = $returnNode.Parent
|
||||||
|
while ($null -ne $cursor -and $cursor -ne $Method) {
|
||||||
|
if ($cursor.GetType().Name -eq 'IfStatementSyntax') {
|
||||||
|
$condition = $cursor.Condition.ToString()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
$cursor = $cursor.Parent
|
||||||
|
}
|
||||||
|
$sites.Add([ordered]@{
|
||||||
|
Line = Get-NodeLine $Tree $returnNode
|
||||||
|
Condition = $condition
|
||||||
|
GateKind = Get-ReturnGateKind $condition
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return @($sites)
|
||||||
|
}
|
||||||
|
|
||||||
function Get-InvocationName {
|
function Get-InvocationName {
|
||||||
param([Parameter(Mandatory)]$Invocation)
|
param([Parameter(Mandatory)]$Invocation)
|
||||||
|
|
||||||
|
|
@ -188,6 +217,44 @@ function Test-RecursiveSignal {
|
||||||
return $false
|
return $false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Get-RecursivePrerequisiteGateSites {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)][string]$MethodKey,
|
||||||
|
[Parameter(Mandatory)][hashtable]$MethodMap,
|
||||||
|
[Parameter(Mandatory)][hashtable]$GateMap,
|
||||||
|
[Parameter(Mandatory)][hashtable]$CallMap,
|
||||||
|
[Parameter(Mandatory)][hashtable]$Visited
|
||||||
|
)
|
||||||
|
|
||||||
|
if ($Visited.ContainsKey($MethodKey)) {
|
||||||
|
return @()
|
||||||
|
}
|
||||||
|
$Visited[$MethodKey] = $true
|
||||||
|
|
||||||
|
$sites = [Collections.Generic.List[object]]::new()
|
||||||
|
foreach ($calledName in @($CallMap[$MethodKey])) {
|
||||||
|
foreach ($candidateKey in @($MethodMap.Keys | Where-Object {
|
||||||
|
$_.EndsWith("::$calledName", [StringComparison]::Ordinal)
|
||||||
|
})) {
|
||||||
|
foreach ($gate in @($GateMap[$candidateKey] | Where-Object {
|
||||||
|
$null -ne $_.GateKind
|
||||||
|
})) {
|
||||||
|
$sites.Add([ordered]@{
|
||||||
|
Helper = $candidateKey
|
||||||
|
Line = $gate.Line
|
||||||
|
Condition = $gate.Condition
|
||||||
|
GateKind = $gate.GateKind
|
||||||
|
})
|
||||||
|
}
|
||||||
|
foreach ($nestedSite in @(Get-RecursivePrerequisiteGateSites `
|
||||||
|
$candidateKey $MethodMap $GateMap $CallMap $Visited)) {
|
||||||
|
$sites.Add($nestedSite)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return @($sites | Sort-Object Helper, Line, Condition -Unique)
|
||||||
|
}
|
||||||
|
|
||||||
$trackedFiles = @(& git -C $repoRoot ls-files tests | Where-Object {
|
$trackedFiles = @(& git -C $repoRoot ls-files tests | Where-Object {
|
||||||
$_.EndsWith('.cs', [StringComparison]::OrdinalIgnoreCase)
|
$_.EndsWith('.cs', [StringComparison]::OrdinalIgnoreCase)
|
||||||
})
|
})
|
||||||
|
|
@ -214,6 +281,7 @@ foreach ($relativePath in $trackedFiles) {
|
||||||
$methodMap = @{}
|
$methodMap = @{}
|
||||||
$failureMap = @{}
|
$failureMap = @{}
|
||||||
$outputMap = @{}
|
$outputMap = @{}
|
||||||
|
$gateMap = @{}
|
||||||
$callMap = @{}
|
$callMap = @{}
|
||||||
|
|
||||||
foreach ($method in $allMethods) {
|
foreach ($method in $allMethods) {
|
||||||
|
|
@ -222,6 +290,7 @@ foreach ($relativePath in $trackedFiles) {
|
||||||
$methodMap[$key] = $method
|
$methodMap[$key] = $method
|
||||||
$failureMap[$key] = Test-DirectFailureSignal $method
|
$failureMap[$key] = Test-DirectFailureSignal $method
|
||||||
$outputMap[$key] = Test-DirectOutputSignal $method
|
$outputMap[$key] = Test-DirectOutputSignal $method
|
||||||
|
$gateMap[$key] = @(Get-EmptyReturnSites $tree $method)
|
||||||
$callMap[$key] = @($method.DescendantNodes() |
|
$callMap[$key] = @($method.DescendantNodes() |
|
||||||
Where-Object { $_.GetType().Name -eq 'InvocationExpressionSyntax' } |
|
Where-Object { $_.GetType().Name -eq 'InvocationExpressionSyntax' } |
|
||||||
ForEach-Object { Get-InvocationName $_ } |
|
ForEach-Object { Get-InvocationName $_ } |
|
||||||
|
|
@ -247,26 +316,9 @@ foreach ($relativePath in $trackedFiles) {
|
||||||
$hasOutputSignal = Test-RecursiveSignal `
|
$hasOutputSignal = Test-RecursiveSignal `
|
||||||
$key $methodMap $outputMap $callMap @{}
|
$key $methodMap $outputMap $callMap @{}
|
||||||
|
|
||||||
$emptyReturns = [Collections.Generic.List[object]]::new()
|
$emptyReturns = @($gateMap[$key])
|
||||||
foreach ($returnNode in @($method.DescendantNodes() | Where-Object {
|
$helperPrerequisiteReturns = @(Get-RecursivePrerequisiteGateSites `
|
||||||
$_.GetType().Name -eq 'ReturnStatementSyntax' -and
|
$key $methodMap $gateMap $callMap @{})
|
||||||
$null -eq $_.Expression
|
|
||||||
})) {
|
|
||||||
$condition = '<unconditional>'
|
|
||||||
$cursor = $returnNode.Parent
|
|
||||||
while ($null -ne $cursor -and $cursor -ne $method) {
|
|
||||||
if ($cursor.GetType().Name -eq 'IfStatementSyntax') {
|
|
||||||
$condition = $cursor.Condition.ToString()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
$cursor = $cursor.Parent
|
|
||||||
}
|
|
||||||
$emptyReturns.Add([ordered]@{
|
|
||||||
Line = Get-NodeLine $tree $returnNode
|
|
||||||
Condition = $condition
|
|
||||||
GateKind = Get-ReturnGateKind $condition
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
$traits = [Collections.Generic.List[string]]::new()
|
$traits = [Collections.Generic.List[string]]::new()
|
||||||
$staticSkip = $null
|
$staticSkip = $null
|
||||||
|
|
@ -315,9 +367,11 @@ foreach ($relativePath in $trackedFiles) {
|
||||||
Traits = @($traits)
|
Traits = @($traits)
|
||||||
StaticSkip = $staticSkip
|
StaticSkip = $staticSkip
|
||||||
EmptyReturns = @($emptyReturns)
|
EmptyReturns = @($emptyReturns)
|
||||||
|
HelperPrerequisiteReturns = @($helperPrerequisiteReturns)
|
||||||
PrerequisiteReturnCandidate = @($emptyReturns | Where-Object {
|
PrerequisiteReturnCandidate = @($emptyReturns | Where-Object {
|
||||||
$null -ne $_.GateKind
|
$null -ne $_.GateKind
|
||||||
}).Count -gt 0
|
}).Count -gt 0
|
||||||
|
HelperPrerequisiteReturnCandidate = $helperPrerequisiteReturns.Count -gt 0
|
||||||
HasFailureSignal = $hasFailureSignal
|
HasFailureSignal = $hasFailureSignal
|
||||||
HasOutputSignal = $hasOutputSignal
|
HasOutputSignal = $hasOutputSignal
|
||||||
OutputOnlyCandidate = $hasOutputSignal -and -not $hasFailureSignal
|
OutputOnlyCandidate = $hasOutputSignal -and -not $hasFailureSignal
|
||||||
|
|
@ -347,6 +401,15 @@ $summary = [ordered]@{
|
||||||
PrerequisiteReturnCandidates = @($orderedRecords | Where-Object {
|
PrerequisiteReturnCandidates = @($orderedRecords | Where-Object {
|
||||||
$_.PrerequisiteReturnCandidate
|
$_.PrerequisiteReturnCandidate
|
||||||
}).Count
|
}).Count
|
||||||
|
HelperPrerequisiteReturnCandidates = @($orderedRecords | Where-Object {
|
||||||
|
$_.HelperPrerequisiteReturnCandidate
|
||||||
|
}).Count
|
||||||
|
HelperPrerequisiteReturnSites = @($orderedRecords |
|
||||||
|
ForEach-Object { $_.HelperPrerequisiteReturns }).Count
|
||||||
|
AnyPrerequisiteReturnCandidates = @($orderedRecords | Where-Object {
|
||||||
|
$_.PrerequisiteReturnCandidate -or
|
||||||
|
$_.HelperPrerequisiteReturnCandidate
|
||||||
|
}).Count
|
||||||
OutputOnlyCandidates = @($orderedRecords | Where-Object { $_.OutputOnlyCandidate }).Count
|
OutputOnlyCandidates = @($orderedRecords | Where-Object { $_.OutputOnlyCandidate }).Count
|
||||||
DiagnosticMethods = @($orderedRecords | Where-Object {
|
DiagnosticMethods = @($orderedRecords | Where-Object {
|
||||||
$_.Traits -contains 'Purpose, Diagnostic'
|
$_.Traits -contains 'Purpose, Diagnostic'
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue