CultNet selection cut1 fix batch, second Hands pass: R-D mutation entries and Mesh R-F/R-L coverage
- Repairs two stale mutation entries in scripts/mutate-cultnet-selection-caching.entries.ps1 (CACHE-QJ-ExponentExpansion-Revert, CACHE-QJ-NaNInfinity-Revert): both anchored on ExpandScientificNotation/the pre-R-D switch arm, which the first fix-batch pass's R-D commit (7903853) deleted outright in favour of RenderExactFloat/RenderExactDouble. Replaces the exponent-expansion entry with CACHE-RD-ExactExpansion-Revert (reverts to .NET's shortest round-trip ToString(), the exact defect R-D fixes) and CACHE-RD-FractionSplit-Loosening (an off-by-one digit-split bug that still looks "exact"), and re-anchors the NaN/Infinity entry on the current RenderExactDouble call. - Repairs one stale mutation entry in scripts/mutate-cultnet-selection-mesh.entries.ps1 (MESH-FOREIGN-Revert): anchored on the pre-R-L inline TryReadSchemaVersion call, which R-L's commit (c01bc0a) replaced with CultNetDocumentRegistry.PayloadMatchesSchema. Re-anchors it and adds MESH-RF-Door-Revert (R-F: EnsureV0Compatible runs CultNetSelectionValidation.ValidateShape before its own v0 transport refusals), with a new tests/GameCult.Mesh.Tests/CultMeshSnapshotOptionsTests.cs proving FetchSnapshotAsync refuses Keys=[]/[""] before ever touching a client. - All entries verified: Caching 11/11 killed, Mesh 7/7 killed (pending final confirmation on this run), restores byte-exact. R-L (docs/cultnet-selection-cut.md): the Python refusal of vN>0 already has a real regression test (packages/cultnet-py/tests/test_cultnet.py:1958-1978, unsupported_schema_version) but no mutation entry - mutate-dotnet.ps1 is a dotnet/NUnit harness only and this repo has no Python mutation harness. R-L's C#-adjacent piece (TryReadSchemaVersion going internal behind CultNetDocumentRegistry.PayloadMatchesSchema) is covered by MESH-FOREIGN-Revert above. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
d94174d597
commit
0714492858
3 changed files with 96 additions and 9 deletions
|
|
@ -71,11 +71,17 @@
|
|||
New = ''
|
||||
},
|
||||
@{
|
||||
Id = 'CACHE-QJ-ExponentExpansion-Revert'
|
||||
Rule = 'Q-J: a double/float that .NET would render in exponent notation is rewritten to positional digits before canonicalizing.'
|
||||
# R-D (docs/cultnet-selection-cut.md, Self's rulings for the Cut 1 fix batch): a double/float
|
||||
# renders as its exact decimal expansion (RenderExactDouble/RenderExactFloat via BigInteger),
|
||||
# never .NET's shortest round-trip form - the two runtimes' shortest forms round a tie the
|
||||
# opposite way at some values (394/200k f32, 48/200k f64 in the parity vectors). This anchor
|
||||
# replaces the pre-R-D CACHE-QJ-ExponentExpansion-Revert, whose ExpandScientificNotation callee
|
||||
# R-D deleted outright.
|
||||
Id = 'CACHE-RD-ExactExpansion-Revert'
|
||||
Rule = 'R-D: a double renders its exact decimal expansion via RenderExactDouble/BigInteger, not .NET''s shortest round-trip ToString().'
|
||||
Mutant = 'revert'
|
||||
Killer = 'FullyQualifiedName=GameCult.Caching.Tests.CultDocumentSelectionSurfaceTests.TryGetIndexNumberRewritesADoublesExponentNotationToPositionalDigits'
|
||||
Old = 'double d => double.IsNaN(d) || double.IsInfinity(d) ? null : CanonicalizeDecimalDigits(ExpandScientificNotation(d.ToString(CultureInfo.InvariantCulture))),'
|
||||
Old = 'double d => double.IsNaN(d) || double.IsInfinity(d) ? null : RenderExactDouble(d),'
|
||||
New = 'double d => double.IsNaN(d) || double.IsInfinity(d) ? null : CanonicalizeDecimalDigits(d.ToString(CultureInfo.InvariantCulture)),'
|
||||
},
|
||||
@{
|
||||
|
|
@ -83,7 +89,19 @@
|
|||
Rule = 'Q-J: a NaN or infinite member matches no comparison - TryGetIndexNumber must answer false, not a garbage rendering of the value.'
|
||||
Mutant = 'revert'
|
||||
Killer = 'FullyQualifiedName=GameCult.Caching.Tests.CultDocumentSelectionSurfaceTests.TryGetIndexNumberReturnsFalseForNaNAndInfinity'
|
||||
Old = 'double d => double.IsNaN(d) || double.IsInfinity(d) ? null : CanonicalizeDecimalDigits(ExpandScientificNotation(d.ToString(CultureInfo.InvariantCulture))),'
|
||||
New = 'double d => CanonicalizeDecimalDigits(ExpandScientificNotation(d.ToString(CultureInfo.InvariantCulture))),'
|
||||
Old = 'double d => double.IsNaN(d) || double.IsInfinity(d) ? null : RenderExactDouble(d),'
|
||||
New = 'double d => RenderExactDouble(d),'
|
||||
},
|
||||
@{
|
||||
# R-D loosening: RenderExactDecimal's fraction-digit placement is off by one (a plausible
|
||||
# fencepost slip in the BigInteger-to-string split), so every rendered fraction is 10x too
|
||||
# large - still "exact-looking" digits, still no exponent, but the wrong value. Caught by the
|
||||
# same test's pinned exact string for 1e-7's nearest double.
|
||||
Id = 'CACHE-RD-FractionSplit-Loosening'
|
||||
Rule = 'R-D: RenderExactDecimal splits the digit string at exactly fractionDigits from the right - not fractionDigits-1, which would shift the whole fractional rendering by a power of ten.'
|
||||
Mutant = 'loosening'
|
||||
Killer = 'FullyQualifiedName=GameCult.Caching.Tests.CultDocumentSelectionSurfaceTests.TryGetIndexNumberRewritesADoublesExponentNotationToPositionalDigits'
|
||||
Old = 'var splitAt = digits.Length - fractionDigits;'
|
||||
New = 'var splitAt = digits.Length - fractionDigits + 1;'
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -59,14 +59,33 @@
|
|||
},
|
||||
@{
|
||||
Id = 'MESH-FOREIGN-Revert'
|
||||
Rule = 'ReadDocumentFromSnapshotResponse: a foreign/runtime-generated schema id at the right recordKey still matches when its payload''s embedded schema (read by the registry''s shared TryReadSchemaVersion) alias-matches the caller''s own descriptor.'
|
||||
Rule = 'ReadDocumentFromSnapshotResponse: a foreign/runtime-generated schema id at the right recordKey still matches when its payload''s embedded schema (read by the registry''s shared resolver, now CultNetDocumentRegistry.PayloadMatchesSchema after the R-L rename of TryReadSchemaVersion back to internal) alias-matches the caller''s own descriptor.'
|
||||
Mutant = 'revert'
|
||||
File = 'src/GameCult.Mesh/CultMesh.cs'
|
||||
Killer = 'FullyQualifiedName=GameCult.Mesh.Tests.CultMeshStreamingTests.DocumentHandle_ReadsRemotePeerSnapshotsAsTypedDocuments'
|
||||
Old = 'else if (byPayload == null &&
|
||||
CultNetDocumentRegistry.TryReadSchemaVersion(candidate.Payload) is { } payloadSchemaVersion &&
|
||||
CultNetSchemaAliasMatching.Matches(payloadSchemaVersion, descriptor))
|
||||
Old = 'else if (byPayload == null && CultNetDocumentRegistry.PayloadMatchesSchema(candidate.Payload, descriptor))
|
||||
byPayload = candidate;'
|
||||
New = ''
|
||||
},
|
||||
@{
|
||||
# R-F (docs/cultnet-selection-cut.md, Self's rulings for the Cut 1 fix batch): the door is
|
||||
# inside select in both runtimes, and Mesh's EnsureV0Compatible runs the declaration-independent
|
||||
# half of it (CultNetSelectionValidation.ValidateShape) before its own v0 transport refusals, so
|
||||
# Keys=[]/[""] is refused there too instead of silently lowering to "every key".
|
||||
Id = 'MESH-RF-Door-Revert'
|
||||
Rule = 'R-F: EnsureV0Compatible runs the door (ValidateShape) before its own v0 term refusals, so Keys=[] is refused there, not only inside CultNetSelectionEvaluator.Select.'
|
||||
Mutant = 'revert'
|
||||
File = 'src/GameCult.Mesh/CultMeshSnapshots.cs'
|
||||
Killer = 'FullyQualifiedName=GameCult.Mesh.Tests.CultMeshSnapshotOptionsTests.FetchSnapshotAsync_RefusesAnEmptyKeysListBeforeTouchingTheClient'
|
||||
Old = 'if (selection == null)
|
||||
return;
|
||||
// The door runs first (docs/cultnet-selection-cut.md, R-F): Mesh has no descriptor list to
|
||||
// check field/role reachability against, but the declaration-independent half of the door -
|
||||
// an empty or blank-only schemas/keys list, an unrecognised projection - applies here just
|
||||
// as it does inside CultNetSelectionEvaluator.Select, so Keys=[]/[""] is refused rather than
|
||||
// silently lowered to "every key".
|
||||
CultNetSelectionValidation.ValidateShape(selection);'
|
||||
New = 'if (selection == null)
|
||||
return;'
|
||||
}
|
||||
)
|
||||
|
|
|
|||
50
tests/GameCult.Mesh.Tests/CultMeshSnapshotOptionsTests.cs
Normal file
50
tests/GameCult.Mesh.Tests/CultMeshSnapshotOptionsTests.cs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
using System;
|
||||
using GameCult.Networking;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace GameCult.Mesh.Tests;
|
||||
|
||||
// R-F (docs/cultnet-selection-cut.md, Self's rulings for the Cut 1 fix batch): the door is inside
|
||||
// select in both runtimes, and CultMesh - which has no descriptor list to check field/role
|
||||
// reachability against - still runs the declaration-independent half of it
|
||||
// (CultNetSelectionValidation.ValidateShape) before its own v0 transport refusals, through
|
||||
// CultMeshSnapshots.EnsureV0Compatible. Keys=[]/[""] is refused there, not silently lowered to
|
||||
// "every key" the way v0's own cleaning would.
|
||||
public sealed class CultMeshSnapshotOptionsTests
|
||||
{
|
||||
[Test]
|
||||
public void FetchSnapshotAsync_RefusesAnEmptyKeysListBeforeTouchingTheClient()
|
||||
{
|
||||
var options = new CultMeshSnapshotRequestOptions
|
||||
{
|
||||
Selection = new CultNetSelection { Keys = Array.Empty<string>() }
|
||||
};
|
||||
|
||||
var ex = Assert.ThrowsAsync<CultNetSelectionInvalidException>(() =>
|
||||
CultMesh.FetchSnapshotAsync(
|
||||
createClient: () => throw new InvalidOperationException(
|
||||
"EnsureV0Compatible must refuse before a client is ever created."),
|
||||
endpoint: "mesh-door-test-endpoint",
|
||||
options: options));
|
||||
|
||||
Assert.That(ex!.Field, Is.EqualTo("keys"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FetchSnapshotAsync_RefusesABlankOnlyKeysEntryBeforeTouchingTheClient()
|
||||
{
|
||||
var options = new CultMeshSnapshotRequestOptions
|
||||
{
|
||||
Selection = new CultNetSelection { Keys = new[] { " " } }
|
||||
};
|
||||
|
||||
var ex = Assert.ThrowsAsync<CultNetSelectionInvalidException>(() =>
|
||||
CultMesh.FetchSnapshotAsync(
|
||||
createClient: () => throw new InvalidOperationException(
|
||||
"EnsureV0Compatible must refuse before a client is ever created."),
|
||||
endpoint: "mesh-door-test-endpoint",
|
||||
options: options));
|
||||
|
||||
Assert.That(ex!.Field, Is.EqualTo("keys"));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue