test: remove ambient timing from double-click contracts

This commit is contained in:
Erik 2026-08-18 11:38:31 +02:00
parent 3684e7b5e7
commit c8c764a40e
5 changed files with 151 additions and 40 deletions

View file

@ -6,9 +6,9 @@
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
records test attributes and traits, static skips, empty-return sites,
assertion/throw signals (including same-file helper calls), diagnostic
output signals, wall-clock waits, environment-variable dependencies, and
source-text reads.
prerequisite gates reached through same-file helper calls, assertion/throw
signals (including same-file helper calls), diagnostic output signals,
wall-clock waits, environment-variable dependencies, and source-text reads.
This is a candidate generator, not a semantic proof. In particular, an
output-only candidate can call a failure-capable helper declared in another
@ -107,6 +107,35 @@ function Get-ReturnGateKind {
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 {
param([Parameter(Mandatory)]$Invocation)
@ -188,6 +217,44 @@ function Test-RecursiveSignal {
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 {
$_.EndsWith('.cs', [StringComparison]::OrdinalIgnoreCase)
})
@ -214,6 +281,7 @@ foreach ($relativePath in $trackedFiles) {
$methodMap = @{}
$failureMap = @{}
$outputMap = @{}
$gateMap = @{}
$callMap = @{}
foreach ($method in $allMethods) {
@ -222,6 +290,7 @@ foreach ($relativePath in $trackedFiles) {
$methodMap[$key] = $method
$failureMap[$key] = Test-DirectFailureSignal $method
$outputMap[$key] = Test-DirectOutputSignal $method
$gateMap[$key] = @(Get-EmptyReturnSites $tree $method)
$callMap[$key] = @($method.DescendantNodes() |
Where-Object { $_.GetType().Name -eq 'InvocationExpressionSyntax' } |
ForEach-Object { Get-InvocationName $_ } |
@ -247,26 +316,9 @@ foreach ($relativePath in $trackedFiles) {
$hasOutputSignal = Test-RecursiveSignal `
$key $methodMap $outputMap $callMap @{}
$emptyReturns = [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
}
$emptyReturns.Add([ordered]@{
Line = Get-NodeLine $tree $returnNode
Condition = $condition
GateKind = Get-ReturnGateKind $condition
})
}
$emptyReturns = @($gateMap[$key])
$helperPrerequisiteReturns = @(Get-RecursivePrerequisiteGateSites `
$key $methodMap $gateMap $callMap @{})
$traits = [Collections.Generic.List[string]]::new()
$staticSkip = $null
@ -315,9 +367,11 @@ foreach ($relativePath in $trackedFiles) {
Traits = @($traits)
StaticSkip = $staticSkip
EmptyReturns = @($emptyReturns)
HelperPrerequisiteReturns = @($helperPrerequisiteReturns)
PrerequisiteReturnCandidate = @($emptyReturns | Where-Object {
$null -ne $_.GateKind
}).Count -gt 0
HelperPrerequisiteReturnCandidate = $helperPrerequisiteReturns.Count -gt 0
HasFailureSignal = $hasFailureSignal
HasOutputSignal = $hasOutputSignal
OutputOnlyCandidate = $hasOutputSignal -and -not $hasFailureSignal
@ -347,6 +401,15 @@ $summary = [ordered]@{
PrerequisiteReturnCandidates = @($orderedRecords | Where-Object {
$_.PrerequisiteReturnCandidate
}).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
DiagnosticMethods = @($orderedRecords | Where-Object {
$_.Traits -contains 'Purpose, Diagnostic'