Repository navigation
Merged
Conversation
…ss (refs #2179) P41 Phase 1 Task 1. The memory harness gains the axis the plan measures on: - --threads N appends the xUnit RunSettings override `-- xUnit.MaxParallelThreads=N` to the run. Route taken: the RunSettings override (no MSBuild fallback needed) - it is honoured over xunit.runner.json, which pins 1. - --out DIR (default .claude/tmp/memory-harness) so matrix cells do not clobber each other; --timing PATH exports SHARPY_TEST_TIMING (absolutized) for IntegrationTestBase's breakdown; --trx adds a trx logger at DIR/tests.trx. - One `ps -ww -A -o pid=,ppid=,rss=,args=` snapshot per sample, folded by awk into tree RSS (dotnet child + all descendants) and the largest testhost's own RSS. The testhost is matched by `exec --runtimeconfig <...>.Tests.runtimeconfig.json`: its comm is plain `dotnet`, like every spawned test program's, and args without -ww truncates `testhost.dll` away. - Machine used memory (vm_stat active+wired+compressor) and swap used at lock acquisition, swap again at exit; counts parsed from vstest's multi-line "Total tests:" block (zero lines are omitted, read as 0) or the one-line "Passed! - Failed:" form; "?" when neither is present. - Final line: threads=N peak_rss_mb peak_testhost_mb wall_s passed failed skipped base_used_mb swap_mb=A->B. --threshold and the exit-code conventions are unchanged. Override evidence @ 116c6e2 (worktree build, measured), filter FullyQualifiedName~Sharpy.Compiler.Tests.Lexer|FullyQualifiedName~Sharpy.Compiler.Tests.Parser, 2785 tests in 59 classes, trx start/end intervals swept for concurrency: - --threads 2: max_concurrent=2, 2696 cross-class overlapping pairs, e.g. PropertyDecoratorMatrixTests.SteerSpellingRuns [01:41:06.894758 .. 01:41:07.884882] and WrapperNodeUnwrapConformanceTests.ImportDispatchSites_AreWrapperAware [01:41:07.032700 .. 01:41:07.404927] (372 ms overlap). Summary: threads=2 peak_rss_mb=1105 peak_testhost_mb=800 wall_s=9 passed=2785 failed=0 skipped=0 - --threads 1 (control): max_concurrent=1, 0 cross-class overlapping pairs. Summary: threads=1 peak_rss_mb=1096 peak_testhost_mb=806 wall_s=10 passed=2785 failed=0 skipped=0 - The overlap sweep itself discriminates on the P0 control trx files: Stdlib (runner json pins 1) max_concurrent=1 / 0 pairs; Cli, Core, Lsp (no runner json) max_concurrent=14 each. Parser checks: summary awk -> 1234/2/3 on the one-line form, 441/0/1 (Cli) and 4162/0/0 (Stdlib) on the control logs, "? ? ?" on an empty log; the tree awk on a synthetic tree using the measured args spellings counts the whole tree and matches only the testhost (not vstest.console, not SharpyTestAssembly). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HzQeHzwNfujh3DnznBXsQr
…efs #2180) P41 Phase 1 Task 2. Behind SHARPY_TEST_TIMING=<path> (read once into a static), every call of the three execute arms - CompileAndExecute ("single"), CompileAndExecuteProject ("project"), CompileAndExecuteEntryFile ("entry-file"; the latter two through the shared EmitAndRunProjectAssembly) - records six components plus its own total wall: pipeline (CompilerApi/ProjectCompiler compile; includes C# generation, which the compiler API exposes no seam for), emit (Roslyn parse + compile to the in-memory assembly), write+copy (temp source / assembly / runtimeconfig writes + Core, closure and native copies), process (start to exit), cleanup (temp dir deletes) and forced-gc. Calls are queued in a ConcurrentQueue; at ProcessExit the testhost appends pid-tagged rows per arm x component (n, median, p90, sum ms), the arm's total, and a coverage row comparing sum of components with sum of totals. Unset: CallTiming.Begin returns null, every hook is a `timing?.` no-op and Start() reads no clock; the arms' statements, order and ExecutionResult are untouched. Components are timed independently, not as laps, so a dropped recording surfaces as unattributed time in the coverage row rather than being folded into its neighbour. Instrument check @ 116c6e2 (worktree build, measured; default threads = runner json 1): (a) switch OFF, FullyQualifiedName~Sharpy.Compiler.Tests.Integration.FileBasedIntegrationTests: 3569 passed / 0 failed / 0 skipped = the P0 control's 3569 FileBasedIntegrationTests rows in Compiler.trx; the (fixture, isMultiFile, outcome) multiset is identical. Switch ON, the same 3569 and the 152 EntryFilePathMultiFileTests rows are identical to the control too. (b) switch ON, FileBasedIntegrationTests|EntryFilePathMultiFileTests (3721 passed / 0 / 0, 3718 calls), report written; per arm (ms): single n=3418 pipeline med 5.43 p90 10.44 | emit med 0.98 p90 2.48 (n=2401) write+copy med 1.87 p90 2.08 | process med 48.23 p90 51.10 (n=2400) cleanup med 1.20 p90 1.48 | forced-gc med 63.18 p90 67.69 total med 121.75 p90 137.05; coverage 363663.7 / 363842.1 = 0.9995 project n=150 pipeline med 30.73 | emit 1.47 | write+copy 1.64 | process 48.10 | cleanup 0.84 | forced-gc 62.74 | total med 148.71; coverage 0.9989 entry-file n=150 pipeline med 17.86 | emit 1.51 | write+copy 1.85 | process 48.42 | cleanup 0.85 | forced-gc 65.09 | total med 135.57; coverage 0.9996 (c) mutation - both `timing?.Add(Component.Process, ...)` lines commented out (worktree copy), rebuilt, filter EntryFilePathMultiFileTests|BacktickEscapedForwarderTests| ClrInteropIntegrationTests (173 passed): broken -> 5% check FAILS in all three arms (entry-file ratio 0.7396, project 0.8622, single 0.6650; within_5pct=no; process n=0); restored by cp from the saved copy, rebuilt, same filter -> passes in all three (0.9995, 0.9974, 0.9986; within_5pct=yes; process n=126/2/17). dotnet format whitespace --verify-no-changes clean with a repo-relative --include (the absolute-path spelling was shown vacuous by a positive control: a mis-indented copy exits 0 with the absolute path and 2 with the relative one). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HzQeHzwNfujh3DnznBXsQr
…ms (refs #2180) P41 Phase 3 Task 1. CompileAndExecuteCore and EmitAndRunProjectAssembly (shared by CompileAndExecuteProject and CompileAndExecuteEntryFile) each carried their own copy of the deploy-and-spawn block; both now call one private static helper, RunEmittedProgram, so a per-call cost cured in one copy (#2180) or a behaviour fixed in one cannot stay in the other. Diff of the two blocks before the fold (every line not listed was byte-identical): - assembly / runtimeconfig file stem: "SharpyTestAssembly" vs "SharpyTestProject" -> preserved by the assemblyName parameter, passed as compilation.AssemblyName (the stems were already the compilations' own names, so the values are unchanged). - GeneratedCSharp on the three result shapes: generatedCSharp vs generatedCSharpReport -> preserved by the generatedCSharp parameter. - RawDiagnostics on success: `rawDiagnostics` vs `rawDiagnostics ?? new()` -> deliberate unification on `?? new()`; the single arm's list is never null, so no value changes. - spawn comment wording (the single arm's cited ".NET 10 on Linux x64"); kept that one for now, Task 3 replaces it. Identical in both, carried over unchanged: no environment overrides, WorkingDirectory = temp dir, `dotnet exec "<dll>"`, 30 s default timeout + Kill(entireProcessTree) + TimedOut result, non-zero exit -> "Process exited with code N: <stderr>", async stdout/stderr capture with the second WaitForExit, Sharpy.Core + CopyRuntimeClosure(Core + additional paths) + additional reference copies, the runtimeconfig literal (checked byte-equal), best-effort recursive delete in finally, and the timing hooks. Not in either block (left in the arms): the C# emit, the #line-mapped SPY0908 path (WithGeneratedCodeDiagnostics/RenderCompilationErrors), and the exception ladders - the helper catches nothing, so the single arm keeps its TargetInvocation/InvalidOperation/FileNotFound/TypeLoad/Exception mapping and the project arms their generic catch. Guard: Infrastructure/IntegrationTestBaseExecutePathTests counts, on the syntax tree of IntegrationTestBase.cs, exactly one `new ProcessStartInfo` and exactly one CopyRuntimeClosure(...) call; a third fact shows the counter sees 2 + 2 in an inline source that also mentions both in a comment and a string (positive control; a missing file throws). Mutation: the pre-fold IntegrationTestBase.cs (both blocks inlined) cp'd over the fold -> red (2 failed: Expected 1, Actual 2 for both counts; control 1 passed); restored by cp -> green (3 passed). Count identity @ b9d542c + this change (worktree build, measured; timing switch ON), against the P0 control trx by (testName with path args normalized, outcome) multiset per class: - FileBasedIntegrationTests: 3569/0/0, IDENTICAL. - EntryFilePathMultiFileTests, ClrInteropIntegrationTests, ProjectCompilationTests and every class calling CompileAndExecuteProject/EntryFile (ModuleMemberQualificationMatrixTests, CompileSideCorpusBoundaryTests, HarnessGeneratedCodeDiagnosticTests, BacktickEscapedForwarderTests, CrossModuleParityTests via DualModeIntegrationTestBase, ImportSourceRootParityTests, ExceptTypeResolutionMatrixTests, ModuleMemberImportAccessMatrixTests, OverloadApplicabilityMatrixTests, OverloadModifierResolutionMatrixTests) + Float32ArithmeticMatrixTests: 14 classes, 837/0/0, IDENTICAL. - Sharpy.Stdlib.Tests FileBasedIntegrationTests: 241/0/0, IDENTICAL. Native asset: stdlib_sqlite3_basic (Stdlib FileBased; e_sqlite3 via the copied closure) passes. GetAdditionalReferenceAssemblyPaths(): every Stdlib FileBased fixture (StdlibPaths) and Float32ArithmeticMatrixTests (47 passed) go through it. Timing coverage through the helper stays >= 0.95 in every arm of every run (FileBased single 0.9995 / project 0.9994; rest single 0.9982 / project 0.9996 / entry-file 0.9995; Stdlib single 0.9990 / project 0.9973). dotnet format whitespace --verify-no-changes (repo-relative --include) clean on both files. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HzQeHzwNfujh3DnznBXsQr
…refs #2179) xUnit v2 reads [CollectionDefinition] only from the test assembly itself. HeavyCompilation's definition has lived in Sharpy.TestInfrastructure since #679, so its DisableParallelization never took effect; only the cap of 1 kept those classes serial. Every collection below is therefore defined in its members' own project. HeavyCompilation's definition, its other members, and Sequential are unchanged here (Phase 4). | Global | Classes | Collection (defined in) | |---|---|---| | current directory | ModuleResolverTests (Directory.SetCurrentDirectory) | ProcessCwd, runs alone (Compiler.Tests) | | current directory + env | OsModuleTestsModuleTests (os.chdir, os.putenv; partial twin) | ProcessCwd, runs alone (Stdlib.Tests) | | Console.Out | ReplSessionTests (ReplSession swaps Console.Out on a Task.Run thread, outside ConsoleLock) | ConsoleCapture, runs alone (Compiler.Tests, new) | | Console.Error + logging root level | LoggingModuleTestsModuleTests, LoggingCompleteTestsModuleTests (CapturedStderr; twins) | ConsoleCapture, runs alone (Stdlib.Tests, existing) | | Console.Out | Print_Tests | ConsoleCapture, runs alone (Core.Tests, new) | | random's shared System.Random | Random{,Additional,Additional2}TestsModuleTests, CpythonBisectTestsModuleTests (twins) | PrngState, mutual only (Stdlib.Tests) | | memory (CsCheck Sample uses one thread per core) | 44 compile-heavy CsCheck classes, from HeavyCompilation | PropertySerial, runs alone (Compiler.Tests) | | TZ env + TZ cache | StrftimeHostTimeZoneTests | HostTimeZone (unchanged) | | console under a lock | ProjectCompilationHelper (TestHelpers.ConsoleLock), CliTestHarness (own lock) | none needed | | .claude/tmp reports | 9 Compiler + 4 Lsp writers, one distinct file each | none needed | | fixture bin/ under src | FileBased project arm (Sharpy.Test.*), MultiFileDeterminismTests (Test.*), one writer per file | none needed | | fixed temp names, ports | GUID or ephemeral everywhere (ClrInteropIntegrationTests' fixed /tmp names are distinct per test) | none needed | | culture | AnalysisLatencyLogTests sets CurrentCulture (per-thread) | none needed | Spy-generated classes get their [Collection] from hand-written partial twins in Spy/SpyTestCollections.cs. The file is outside generated/ because the regenerator removes orphan generated/*.cs files. check_spy_tests_staleness.sh: 119 files up to date. Print_Tests is not a measured collision. Nothing else in Core.Tests writes to the console today. It joins a run-alone collection because the alternative is a guard exemption resting on an invariant that nothing checks. Counts @ 116c6e2 + this change (measured; control at 116c6e2 in parentheses): Core.Tests 3989/0/0 (3989/0/0), Stdlib.Tests 4162/0/0 (4162/0/0). Compiler.Tests filtered (ModuleResolverTests | ReplSessionTests | ArithmeticPropertyTests | the guard of the next commit): 73/0/0. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HzQeHzwNfujh3DnznBXsQr
…2179) ProcessGlobalStateConformanceTests (Compiler.Tests/Conformance, CI shard 6). It does a Roslyn syntax walk over the five test projects and Sharpy.TestInfrastructure. Spy/generated is included; bin/obj and the uncompiled Integration/TestFixtures are excluded. A mutation site is a match against a literal roster: - invoked: SetCurrentDirectory, Chdir, SetOut, SetError, SetIn, CapturedOutput, CapturedStderr, SetEnvironmentVariable, Putenv, ClearCachedData - constructed: CapturedOutput, CapturedStderr, ReplSession, CompileServer - assigned: Environment.CurrentDirectory, CultureInfo.DefaultThreadCurrent{,UI}Culture Strings and comments never match. Each site is attributed to its outermost type. That type's [Collection] is merged across its partial declarations in the same project (the spy twins). The site passes only when the collection's [CollectionDefinition] is in the SAME project with DisableParallelization = true. That is the source-level form of the inert-HeavyCompilation finding. Sites in TestInfrastructure can only be exempt. There are 5 literal exemptions with reasons (CliTestHarness and ProjectCompilationHelper redirect under their locks; FrontEndParityTests only probes ReplSession), and a stale exemption fails. Positive control: 10 literal known sites must be found and isolated. Synthetic controls run the same classifier on in-memory sources: - one row per roster entry (17); - strings, comments, reads and CurrentCulture produce no site; - a class with no collection is red; - a definition in another project is red; - DisableParallelization=false is red; - a twin in the same namespace isolates, one in the wrong namespace does not; - a nested type is attributed to the outermost; - a site in TestInfrastructure is always red. The guard reads source at test time, so each mutation ran on the built binary without a rebuild. Each was restored by cp from a backup and checked with cmp: (1) [Collection("ProcessCwd")] removed from ModuleResolverTests broken -> red (2 failed, 25 passed), restored -> green (27 passed) (2) [Collection("ProcessCwd")] removed from the os partial twin in Spy/SpyTestCollections.cs broken -> red (2 failed, 25 passed), restored -> green (27 passed) (3) Compiler's ProcessCwd [CollectionDefinition] moved into Sharpy.TestInfrastructure/TestCollections.cs broken -> red (2 failed, 25 passed: "no [CollectionDefinition("ProcessCwd")] in src/Sharpy.Compiler.Tests"), restored -> green (27 passed) Guard: 27/0/0 (17 roster rows + 10 facts). Compiler.Tests FullyQualifiedName~Conformance&Category!=Benchmark&Category!=GapDiscovery: 17363/0/0 (measured, with the guard in the tree). dotnet format whitespace sharpy.sln --verify-no-changes: clean. Positive control: three trailing spaces in ProcessGlobalCollections.cs gave exit 2 naming the file. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HzQeHzwNfujh3DnznBXsQr
…, Analysis, Helpers, Parser, Stress and Stdlib (refs #2179) 16 classes: Project 8, Fuzz 3, Analysis/Helpers/Parser/Stress 1 each, and Stdlib.Tests' FileBasedIntegrationTests (the only Stdlib member). HeavyCompilation's DisableParallelization never took effect: its [CollectionDefinition] has lived in Sharpy.TestInfrastructure since #679, and xUnit v2 reads definitions only from the test assembly. The member classes therefore formed one ordinary named collection that the cap of 1 kept serial. Dropping the attribute gives each class its own default collection. Audit of every member before removal (Phase 2 terms; grep/read): - no mutable static field; - no static collection mutated at run time; the only shared statics are thread-safe Lazy<T>; - no ClearAll/ClearCache/Reset of a shared cache; every temp directory is GUID-named; - no timing assertion (Stopwatch values are only logged); - no process-global mutation: ProcessGlobalStateConformanceTests is green at 27/0/0. Compiler static state is guarded separately by StaticStateConformanceTests (#1036). Verification: the group's namespace filter, with Category!=Benchmark as in the P0 control, run at `-- xUnit.MaxParallelThreads=4` and `=1`. Each run is compared with the P0 control trx (.claude/tmp/p41-control/*.trx @ 116c6e2) by the multiset of test name x outcome. Walls are trx start-to-finish. Every call still forces a blocking GC until P3.3 lands, so the N=4 speedup is understated, and these classes are only one slice of their namespace. Compiler.Tests (Analysis|Fuzz|Helpers|Parser|Project|Stress namespaces), measured at this tree: N=4: 3428/0/0, wall 49 s; N=1: 3428/0/0, wall 80 s. Both IDENTICAL to P0 by name x outcome. Stdlib.Tests (whole project; its one member is the fixture Theory): N=4: 4162/0/0, wall 86 s; N=1: 4162/0/0, wall 57 s. Both IDENTICAL to P0. N=4 is slower here because the fixture Theory is still one class (xUnit never parallelises inside a class) and the extra threads only add contention; the fixture partition is P4.2. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HzQeHzwNfujh3DnznBXsQr
…onformance, and dissolve Sequential (refs #2179) 38 HeavyCompilation classes (CodeGen 20, Conformance 18). Sequential is dissolved: 15 classes (14 CodeGen and Semantic/Validation/NamingConventionValidatorTests) plus its definition file. HeavyCompilation's DisableParallelization never took effect: its [CollectionDefinition] has lived in Sharpy.TestInfrastructure since #679, and xUnit v2 reads definitions only from the test assembly. The member classes therefore formed one ordinary named collection that the cap of 1 kept serial. Dropping the attribute gives each class its own default collection. Audit of every member before removal (Phase 2 terms; grep/read): - no mutable static field; - no static collection mutated at run time; the only shared statics are thread-safe Lazy<T>; - no ClearAll/ClearCache/Reset of a shared cache; every temp directory is GUID-named; - no timing assertion (Stopwatch values are only logged); - no process-global mutation: ProcessGlobalStateConformanceTests is green at 27/0/0. Compiler static state is guarded separately by StaticStateConformanceTests (#1036). Verification: the group's namespace filter, with Category!=Benchmark as in the P0 control, run at `-- xUnit.MaxParallelThreads=4` and `=1`. Each run is compared with the P0 control trx (.claude/tmp/p41-control/*.trx @ 116c6e2) by the multiset of test name x outcome. Walls are trx start-to-finish. Every call still forces a blocking GC until P3.3 lands, so the N=4 speedup is understated, and these classes are only one slice of their namespace. Sequential (DisableParallelization = true, defined in this assembly, so it did run last and alone) is obsolete. It came from 0e6429c (2025-11-10), whose reason was that the tests shared NameMangler state that added a numeric suffix. That state is gone: - StaticStateConformanceTests (#1036) bans mutable statics in the compiler assembly by reflection, and its allowlist holds only init-once tables, [ThreadStatic] fields and per-instance vending caches; - NameMangler has only readonly lookup tables; - grep of the 15 member files finds no mutable static and no process-global use. Compiler.Tests (CodeGen|Conformance namespaces), measured at this tree: N=4: 18129/0/0, wall 742 s; N=1: 18129/0/0, wall 820 s. Against P0 (18102 passed) the only difference at both N is +27 passed: ProcessGlobalStateConformanceTests, which landed after the control (0aecaa1). No other test is added, removed or changed in outcome. NamingConventionValidatorTests lives in the Semantic namespace, so it was verified by the Semantic filter at N=4 and N=1 (IDENTICAL to P0). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HzQeHzwNfujh3DnznBXsQr
…#2179) 69 classes under Compiler.Tests/Semantic. HeavyCompilation's DisableParallelization never took effect: its [CollectionDefinition] has lived in Sharpy.TestInfrastructure since #679, and xUnit v2 reads definitions only from the test assembly. The member classes therefore formed one ordinary named collection that the cap of 1 kept serial. Dropping the attribute gives each class its own default collection. Audit of every member before removal (Phase 2 terms; grep/read): - no mutable static field; - no static collection mutated at run time; the only shared statics are thread-safe Lazy<T>; - no ClearAll/ClearCache/Reset of a shared cache; every temp directory is GUID-named; - no timing assertion (Stopwatch values are only logged); - no process-global mutation: ProcessGlobalStateConformanceTests is green at 27/0/0. Compiler static state is guarded separately by StaticStateConformanceTests (#1036). Verification: the group's namespace filter, with Category!=Benchmark as in the P0 control, run at `-- xUnit.MaxParallelThreads=4` and `=1`. Each run is compared with the P0 control trx (.claude/tmp/p41-control/*.trx @ 116c6e2) by the multiset of test name x outcome. Walls are trx start-to-finish. Every call still forces a blocking GC until P3.3 lands, so the N=4 speedup is understated, and these classes are only one slice of their namespace. Compiler.Tests (Semantic namespace), measured at this tree: N=4: 11196 passed / 1 not executed, wall 646 s; N=1: same counts, wall 801 s. Both IDENTICAL to P0 by name x outcome. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HzQeHzwNfujh3DnznBXsQr
…refs #2179) 49 classes under Compiler.Tests/Integration, including FileBasedIntegrationTests (the fixture Theory, still one class until P4.2) and EntryFilePathMultiFileTests. HeavyCompilation's DisableParallelization never took effect: its [CollectionDefinition] has lived in Sharpy.TestInfrastructure since #679, and xUnit v2 reads definitions only from the test assembly. The member classes therefore formed one ordinary named collection that the cap of 1 kept serial. Dropping the attribute gives each class its own default collection. Audit of every member before removal (Phase 2 terms; grep/read): - no mutable static field; - no static collection mutated at run time; the only shared statics are thread-safe Lazy<T>; - no ClearAll/ClearCache/Reset of a shared cache; every temp directory is GUID-named; - no timing assertion (Stopwatch values are only logged); - no process-global mutation: ProcessGlobalStateConformanceTests is green at 27/0/0. Compiler static state is guarded separately by StaticStateConformanceTests (#1036). Verification: the group's namespace filter, with Category!=Benchmark as in the P0 control, run at `-- xUnit.MaxParallelThreads=4` and `=1`. Each run is compared with the P0 control trx (.claude/tmp/p41-control/*.trx @ 116c6e2) by the multiset of test name x outcome. Walls are trx start-to-finish. Every call still forces a blocking GC until P3.3 lands, so the N=4 speedup is understated, and these classes are only one slice of their namespace. Fixture bin/ dirs: the project arm writes <fixture>/bin/Debug/net10.0/Sharpy.Test.* (one writer) and MultiFileDeterminismTests writes Test.* (one writer, already uncollected). The entry-file arm emits nothing there (CompileFile writes only with an OutputAssemblyPath), so no two classes write the same file. Compiler.Tests (Integration namespace), measured at this tree: N=4: 8738/0/0, wall 907 s; N=1: 8738/0/0, wall 985 s. Both IDENTICAL to P0 by name x outcome. The fixture Theory dominates and runs serially inside its one class, so most of this namespace cannot use a second thread until P4.2. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HzQeHzwNfujh3DnznBXsQr
…t definition (refs #2179) MetamorphicCorpusSweepTests was the last [Collection("HeavyCompilation")] class. With it gone, HeavyCompilationCollection is removed from Sharpy.TestInfrastructure/TestCollections.cs. The file stays as a comment saying no xUnit collection may be defined there: xUnit v2 reads [CollectionDefinition] only from the test assembly, so a definition in the shared library is inert, which is what HeavyCompilation's was from #679 to now. ProcessGlobalStateConformanceTests' doc comment is updated to the past tense. The other Properties classes were already in PropertySerial (bb02f61) or uncollected. HeavyCompilation's DisableParallelization never took effect: its [CollectionDefinition] has lived in Sharpy.TestInfrastructure since #679, and xUnit v2 reads definitions only from the test assembly. The member classes therefore formed one ordinary named collection that the cap of 1 kept serial. Dropping the attribute gives each class its own default collection. Audit of every member before removal (Phase 2 terms; grep/read): - no mutable static field; - no static collection mutated at run time; the only shared statics are thread-safe Lazy<T>; - no ClearAll/ClearCache/Reset of a shared cache; every temp directory is GUID-named; - no timing assertion (Stopwatch values are only logged); - no process-global mutation: ProcessGlobalStateConformanceTests is green at 27/0/0. Compiler static state is guarded separately by StaticStateConformanceTests (#1036). Verification: the group's namespace filter, with Category!=Benchmark as in the P0 control, run at `-- xUnit.MaxParallelThreads=4` and `=1`. Each run is compared with the P0 control trx (.claude/tmp/p41-control/*.trx @ 116c6e2) by the multiset of test name x outcome. Walls are trx start-to-finish. Every call still forces a blocking GC until P3.3 lands, so the N=4 speedup is understated, and these classes are only one slice of their namespace. Compiler.Tests (Properties namespace), measured at this tree: N=4: 3065 passed / 1 failed / 2 not executed, wall 860 s. The one red is ILCompilesPropertyTests.GeneratedCSharp_CompilesToValidIL, attributed as "ILCompiles fresh seed 8sLgyTw-IOM3 -> open #2139, reproduced at N=1 by lead" (CsCheck_Seed replay on the same build, .claude/tmp/lead-ilseed-n1.log): member access on the None literal emits `null.Res` (CS0023). It is not a concurrency failure and not this change. Against P0 that test's outcome is the ONLY difference (Passed -> Failed); every other name x outcome is equal to P0. N=1: 3066 passed / 2 not executed, wall 833 s. IDENTICAL to P0. Most of this namespace is PropertySerial, which runs alone at any N, so the walls barely move. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HzQeHzwNfujh3DnznBXsQr
P41 Phase 3 Task 3 (+ Task 4, the spawn comment). Design Decision 5's rule: the forced GC was >= 10% of the per-test median (62.4 of 120.8 ms, 52%, FileBasedIntegrationTests single arm @ b057610, measured), so the blocking GC.Collect(2, Forced) + WaitForPendingFinalizers is removed from all three arms (CompileAndExecute, CompileAndExecuteProject, CompileAndExecuteEntryFile). CompileAndExecuteWithGC keeps its own GC for the callers that ask for it by name. The timing instrument loses its forced-gc component accordingly. Stage-once (Task 2) is not done: copies + delete were 2.5% (< 10%). The spawn comment's uncited ".NET 10 on Linux x64 reflection/interpreted mode issues" is replaced by the measured split @ b9d542c and the note that in-process execution (no spawn) is deferred to an owner ruling on #2180. Before/after, FileBasedIntegrationTests, same instrument (SHARPY_TEST_TIMING via test-memory-harness --timing), threads = runner json (1), worktree build @ b057610 (before) and b057610 + this change (after), run back to back 2026-10-05 ~08:15-08:33 (measured; the machine was NOT quiet - load average 3.7-8.3 from other sessions - so absolute values and p90s are inflated, the A/B pair is the comparison): single arm, median ms/call before after pipeline 6.82 9.19 emit 1.42 1.49 write+copy 2.01 1.84 process (start->exit) 59.81 57.36 cleanup 1.33 1.33 forced-gc 73.00 - total 139.77 68.94 (p90 386.98 -> 146.76) wall_s (lock acquired -> exit) 703 259 counts 3569/0/0 3569/0/0 (name x outcome multiset IDENTICAL to P0) coverage (sum components / sum total) 0.9995 / 0.9994 Process share of the per-call median after the GC removal: 57.36 / 68.94 = 83% (> 50%, so the in-process question is the owner's, per DD5). Quieter single runs for reference: before @ b057610 02:48 - total median 120.78, process 47.61, forced-gc 62.44, wall 389 s; after, 14-class project/entry-file set at 04:55 - single total median 54.24 with process 44.12 (81%), entry-file 65.97/42.80, project 59.36/43.47. A third FileBased "after" run at 04:53-05:00 was inflated in EVERY component (pipeline 18.2, process 140.4, wall 444 s) - external load, not this change - and is not used. Lead's measurement for context (whole-suite matrix @ 116c6e2, GC still forced): the (b) subset of 48 un-collected Integration classes took 669/458/509/527 s at N=1/2/4/8, and LetEmissionInvarianceTests 1350 s at N=2 vs 576 s at N=1 - scaling stalls past 2 because the blocking per-call Gen2 GC stops every thread. This change is the precondition for raising N; RSS without the GC at each thread count is re-measured by the lead. Counts after the change (worktree build, timing ON, name x outcome vs the P0 trx): - Compiler FileBasedIntegrationTests 3569/0/0 IDENTICAL (two runs). - 14 project/entry-file classes + Float32ArithmeticMatrixTests 837/0/0 IDENTICAL, plus the 3 IntegrationTestBaseExecutePathTests guards passing (840 total). - Stdlib FileBasedIntegrationTests 241/0/0 IDENTICAL; single median total 91.40 -> 84.52 (the Stdlib testhost's GC was only ~10 ms/call: GC cost tracks the live heap). dotnet format whitespace --verify-no-changes (repo-relative --include) clean. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HzQeHzwNfujh3DnznBXsQr
) P41 Phase 4 Task 2 (Design Decision 4), with the totality guard. xUnit v2 never runs two rows of one class concurrently, so the fixture Theory - one class - was a serial long pole whatever the thread cap. Shape: each FileBasedIntegrationTests.cs (Compiler, Stdlib) is now a NAMESPACE `...Integration.FileBasedIntegrationTests` holding an abstract base (CompilerFixtureShard / StdlibFixtureShard: fixture root, Stdlib's additional references, ShardCount) and classes Shard0..Shard{K-1}, each with its own [Theory] over its shard (MemberData resolves on the declaring type, so the Theory cannot live in the base). Every row's FullyQualifiedName still starts `Sharpy.<Proj>.Tests.Integration.FileBasedIntegrationTests.`, so CI shard 1 / shard 7 (trailing-dot filters, dotnet10.yml), `~FileBasedIntegrationTests` and `DisplayName~` select every row unchanged; dotnet10.yml only gets its shard-1 comment updated (it said "one class, cannot be split further"). FileBasedIntegrationTestsBase.DiscoverTestFixtures(path, shard, shardCount): rows whose ShardOf(relative path, '/'-normalised) == shard. ShardOf is 32-bit FNV-1a over the UTF-16 code units (never string.GetHashCode, randomised per process). The full discovery per root is cached in a ConcurrentDictionary<string, Lazy<...>>, so K classes do one disk walk per root instead of 2K (plan warning). The unsharded DiscoverTestFixtures(path) is unchanged and uncached (the totality test's independent side). K: Compiler K=8 - as one class the corpus took 657 s at N=1 @ 116c6e2 vs 576 s for the next long pole (LetEmissionInvarianceTests), so 8 shards keep an N=8 cell from being bounded by one shard. Stdlib K=4 - 241 rows, ~24 s at N=1. Guard: Infrastructure/FixtureShardTotalityTests (Compiler) and Conformance/StdlibFixtureShardTotalityTests (Stdlib), via TestInfrastructure FixtureShardScan (reflection finds every concrete shard class and reads its own GetTestFixtures): class count == a LITERAL (8 / 4, not ShardCount); union of shard rows == unsharded discovery as a testName multiset (non-empty); pairwise disjoint; no shard empty; plus (Compiler) ShardOf pinned to FNV-1a by the published "foobar" vector 0xbf9cf968 (% 1000 = 720) and two fixture paths. Mutations (main-tree build each time, measured): - Compiler Shard3 modulus 8 -> 9 (DiscoverTestFixtures(root, 3, 9)) + Stdlib Shard1 index 1 -> 0: Compiler red 2 failed (disjoint, union) / 6 passed; Stdlib red 2 failed (disjoint, union) / 2 passed. Restored by cp, rebuilt -> Compiler 8/8, Stdlib 4/4 green. - ShardOf swapped to string.GetHashCode(Ordinal) + Shard7 class deleted: red 6 failed (4x ShardOf_IsFnv1a, union, ShardClassCount_IsTheLiteralK). Restored by cp, rebuilt -> 8/8. Behaviour checks @ 3c91f6a + this change (main-tree build, measured): - --list-tests with FullyQualifiedName~FileBasedIntegrationTests: Compiler 3576, Stdlib 244. The RunTestFixture rows are the pre-change 3568 / 240; the difference is the base class's [Fact] TestFixturesDirectory_Exists, which now runs once per shard class (8 / 4 instead of 1). No fixture row is added or lost. - CI compiler-shard-partition logic run locally (same list/sort/cmp over the workflow env): main filter 48044 = sum of shards 48044, PARTITION OK, no shard empty (shard 1: 3576). - CiFilterCoverageConformanceTests green (3/3). - UPDATE_SNAPSHOTS=true over both FileBased sets: 3576 + 244 passed, all 165 tracked *.expected.cs rewritten (mtime), zero git diff; `git status` identical before/after. - Counts vs the P0 trx, RunTestFixture rows by (testName, outcome) multiset with ShardN folded: Compiler 3568 IDENTICAL at N=4 and N=1; Stdlib 240 IDENTICAL at N=4 and N=1; all passed. Wall (harness, lock -> exit): Compiler N=1 193 s, N=4 101 s (max_concurrent 4 in the trx, 8 classes); Stdlib N=1 22 s, N=4 11 s. - dotnet format whitespace --verify-no-changes clean on every changed .cs (repo-relative --include, one file each). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HzQeHzwNfujh3DnznBXsQr
A collection defined with DisableParallelization = true runs last and alone, so every such definition, and every class in one, costs the whole suite wall clock. #2179 is one 173-class collection that made the suite serial. ProcessGlobalStateConformanceTests gains a literal SerialCollectionRoster: 7 collections, each with its project, why it must run alone (from the P2.1 audit), and its member classes. - Compiler.Tests: ProcessCwd, ConsoleCapture, PropertySerial (44 members). - Stdlib.Tests: ProcessCwd, ConsoleCapture (3), HostTimeZone. - Core.Tests: ConsoleCapture. New facts, all on the same Roslyn scan as the process-global guard: - EveryRunAloneCollection_IsRostered_AndEveryRosterRowIsDefined: the set of run-alone definitions found must equal the roster rows. An unrostered definition fails, and so does a stale row (drain on fix). - EveryMemberOfARunAloneCollection_IsRostered: a class may name a run-alone collection only as a rostered member; a rostered member that no longer names it fails. - PositiveControl_TheRosterIsFoundInFull: roster size and found size are both pinned to literals counted by hand at landing (7 collections, 52 members), so a scan that finds nothing cannot pass. - EveryCollectionAClassNames_IsDefinedInItsOwnProject: any [Collection("X")] with no [CollectionDefinition("X")] in the class's own project fails, because xUnit v2 ignores the definition elsewhere and the attribute is inert. That was HeavyCompilation's state from #679. Mutations, each restored by cp from a backup and checked with cmp. (a1), (a2) and (c) change scanned source, so no rebuild was needed; (b) changes the roster in the test binary, so it was rebuilt: (a1) [Collection("ProcessCwd")] added to the unrostered PathNormalizerTests broken -> red (2 failed, 29 passed: "+ PathNormalizerTests in ProcessCwd"), restored -> green (31 passed) (a2) a new [CollectionDefinition("Extra", DisableParallelization = true)] in Compiler.Tests broken -> red (2 failed, 29 passed: "+ Sharpy.Compiler.Tests: Extra"), restored -> green (31 passed) (b) the Core.Tests ConsoleCapture roster row deleted broken -> red (3 failed, 28 passed: unrostered definition + unrostered member + positive control), restored -> green (31 passed) (c) [Collection("HeavyCompilation")] added to PathNormalizerTests (no definition anywhere) broken -> red (1 failed, 30 passed: "names HeavyCompilation"), restored -> green (31 passed) Guard class: 31/0/0 (27 process-global + 4 roster). Compiler.Tests FullyQualifiedName~Conformance&Category!=Benchmark&Category!=GapDiscovery: 17367/0/0 (17363 at 0aecaa1 + these 4). dotnet format whitespace sharpy.sln --verify-no-changes: clean. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HzQeHzwNfujh3DnznBXsQr
P41 sibling cell (lead-added to the Defect Class table): the LetEmissionInvarianceTests corpus Theory is a fixture-corpus Theory over the same discovery, so xUnit v2 ran it as one serial class - 576 s at N=1 @ 116c6e2, 1350 s at N=2 under contention in the P1.3 matrix, and the suite's floor once the fixture corpus was sharded (ebe4523, 101 s at N=4). Same cure. Shape: LetEmissionInvarianceTests is now a NAMESPACE holding - Controls : FileBasedIntegrationTestsBase - the four non-Theory tests (positive control, site finder, comparison exemption, census) and the inherited TestFixturesDirectory_Exists, ONCE; - abstract LetTwinShard (deliberately not a FileBasedIntegrationTestsBase, whose [Fact] every shard would replicate) with the cell body, and Shard0..Shard7, each carrying only [Theory] LetTwin_EmitsByteIdenticalCSharp over CellsOfShard(n); - internal static Instrument - the unchanged corpus/site/twin/compile/allowlist helpers. Rows are selected by FileBasedIntegrationTestsBase.ShardOf over the fixture's '/'-relative path (the same function and key as the fixture corpus). K=8: matches the fixture corpus so neither bounds an N=8 run. FileBasedIntegrationTestsBase.NormalizeCSharp is widened protected -> public so the static Instrument can reach it without deriving from that base. One behaviour change, attributed: each cell used to find its fixture with ComparableFixtures().Single(...), a full walk of the fixture tree PER CELL (~2400 walks). The shards' discovery and FixtureByName now share one cached walk (Lazy). The unsharded ComparableFixtureNames() and the census still walk fresh (the totality test's independent side). Ratchet across shards (measured, temporary allowlist lines, restored by cp, zero git diff): type_shorthand/list_shorthand (identical twin), basics/hello_world (no site) and nope/does_not_exist (stale) -> exactly 3 failures, each once: Shard2 list_shorthand ("twin now emits identical C# ... delete its line"), Shard2 hello_world ("no let site: delete its entry"), Controls.Census ("every allowlist entry must name a comparable fixture"). The per-row checks run in the one shard owning the row; the stale-entry check stays aggregate in Controls. Totality guard: Infrastructure/LetEmissionShardTotalityTests (via FixtureShardScan) - shard class count == LITERAL 8 (not Instrument.ShardCount), union == unsharded ComparableFixtureNames as a multiset (non-empty), pairwise disjoint, none empty. Mutation: Shard3 -> CellsOfShard(2): red 2 failed (disjoint, union) / 2 passed; restored by cp, rebuilt -> 4/4 green. Filters / counts (main-tree build @ ebe4523 + b335dd6 + this change, measured): - --list-tests FullyQualifiedName~LetEmissionInvarianceTests: 2402 before = 2402 after; the listed-name multiset with Shard<n>/Controls folded is IDENTICAL (2397 cells + 5 facts; no replicated Fact). - CI compiler-shard-partition logic run locally: main 48048 = union 48048, PARTITION OK; shard 6 (Conformance) holds all 2402 LetEmission rows. - CiFilterCoverageConformanceTests, FixtureShardTotalityTests, LetEmissionShardTotalityTests, ProcessGlobalStateConformanceTests: 46/46 green. - docs/design/gap-discovery-contracts.md:51 names the class descriptively (not a filter): unchanged. - Counts vs the P0 trx by (testName with Shard<n>/Controls folded, outcome) multiset: 2402 IDENTICAL at N=4 and at N=1, all passed. - Walls (harness, lock -> exit): before (unsharded class, lane-a2 worktree @ b9d542c + the P3.3 file, N=1) 466 s; after N=1 57 s, N=4 43 s. At N=1 sharding adds no concurrency, so the 466 -> 57 s is the cached fixture lookup; N=4 adds the sharding's share (57 -> 43 s). - dotnet format whitespace --verify-no-changes (repo-relative --include) clean on the three changed .cs files. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HzQeHzwNfujh3DnznBXsQr
maxParallelThreads 1 -> 2 in src/Sharpy.Compiler.Tests/xunit.runner.json. The value is the best wall clock of a whole-project re-measurement after P41 Phases 2-4 (no per-call forced GC, HeavyCompilation dissolved, fixture and LetEmission corpora sharded, LetEmission discovery cached), with peak tree RSS inside the recorded memory budget. Measured @ af6f309 (worktree, full sln build, `Category!=Benchmark`, thread count set by the RunSettings override `-- xUnit.MaxParallelThreads=N`, heap limit 5 GB; machine 36 GB / 14 cores with a local model loaded; .claude/tmp/p41-p51/): N | trx span | peak tree / testhost RSS | counts 1 | 2042 s | 8200 / 6743 MB | 48241/1/3 2 | 1641 s | 7197 / 6090 MB | 48241/1/3 4 | 1981 s | 7126 / 6083 MB | 48241/1/3 8 | 4707 s | 7447 / 6349 MB | 48240/2/3 The Phase 0 control (N=1, 116c6e2) was 5325 s / 7106 MB: -62% at N=1, -69% at N=2. N=4 and N=8 lose to N=2: classes with internal parallelism oversubscribe the cores (ReparseEquivalenceConformanceTests busy 65 s at N=1 -> 654 s at N=4; at N=8 the run-alone PropertySerial phase took 2063 s for BooleanPropertyTests' 2 tests). The red at every N is DispatchSiteInventoryTests' guard-citation check, a regression from ebe4523 fixed separately (refs #2179); the second N=8 red is a concurrency-only compiler nondeterminism (Fuzz Compilation_IsDeterministic seed 2025), recorded for filing, and it does not reproduce at N=1/2/4. Heap-limit axis at N=2 (System.GC.HeapHardLimit edited in the built runtimeconfig): 5 GB 1641 s, 7197 / 6090 MB; 4 GB 1789 s (+9%), 5959 / 5077 MB; 3 GB 1877 s (+14%), 5466 / 4160 MB; zero OutOfMemoryException at every value. The plan's rule (lowest limit with no OOM and <=~5% wall loss) keeps 5 GB, so runtimeconfig.template.json is unchanged. Stdlib.Tests keeps 1 here; its cap is raised in its own commit. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HzQeHzwNfujh3DnznBXsQr
…2179) Regression from ebe4523, caught by the P5.1 whole-suite run at every thread count (N=1 included): DispatchSiteInventoryTests.GuardCitations_ResolveToRealTests_AndGuardedByTestsScanTheirSite reported every refusal-net:FileBasedIntegrationTests row - 42 rows, distinct site keys counted in .claude/tmp/p41-p51/w-n1/memory-harness-tests.log and again here - as "no such test class in any test assembly": CollectGuardCitationViolations resolved a citation by Type.Name only, and FileBasedIntegrationTests is now the namespace of its Shard0..7 classes. My pre-commit filters for ebe4523 (the shard/totality/CI-coverage classes) did not include this roster. Fixed at the resolver, not at the 42 citations, so a sharded group stays citable: - a citation X resolves if a type is named X (unchanged; the LSP source-scan branch unchanged), OR if a namespace whose last segment is X has the SHARD shape: >=1 concrete test class (a non-abstract class with a [Fact]/[Theory] method, inherited included) and EVERY concrete test class there derives from one abstract base declared in that same namespace. Deliberately strict (recorded in a remarks comment): Conformance.LetEmissionInvarianceTests fails it - its Controls class does not derive from LetTwinShard - and nothing cites that name today; relax to "at most one non-shard controls class" only when a row needs it. - guarded-by rows: none cites a group today. A group has no one source that scans the site, so a guarded-by citation of a group is refused with its own message ("a sharded test group, which backs only a refusal-net row ..."); FindTestSourceFile is unchanged. Synthetic control (next to the existing synthetic roster test): GuardCitations_SyntheticRoster_ResolvesAShardedTestGroup_ButNotABroadOrHelperNamespace - refusal-net:FileBasedIntegrationTests resolves; refused as unknown: Transforms (Properties.Metamorphic.Transforms, helper types only), Integration (many unrelated test classes, no shared base) and LetEmissionInvarianceTests (shards + Controls, the strict choice); guarded-by:FileBasedIntegrationTests refused with the group message; exactly 4 violations. Preconditions asserted: no type is named FileBasedIntegrationTests / Transforms / Integration / LetEmissionInvarianceTests; the group namespace holds test classes, Transforms holds types but no test class, Integration holds more than one test class. Mutation (main tree, one script: edited build green -> mutate -> red -> cp restore -> green): resolver reverted to the name-only rule -> red, 2 of 3 GuardCitations failed: the real roster with 42 distinct 'FileBasedIntegrationTests' violations, and the new synthetic control; the pre-existing synthetic test stays green. Restored by cp, rebuilt -> 3/3 green. Pre-commit filter (Compiler.Tests, N=4): FullyQualifiedName~DispatchSiteInventory | ~Census | ~Roster | ~Conformance.CiFilterCoverage | ~Totality | ~Sharpy.Compiler.Tests.Infrastructure. -> 821 passed / 3 failed / 0 skipped @ a24387b + this change + lane B's UNCOMMITTED ProcessGlobalStateConformanceTests.cs / SpyTestCollections.cs edits. The 3 reds are all ProcessGlobalStateConformanceTests ("+ Sharpy.Stdlib.Tests: WallClockWindow ... not rostered"), i.e. lane B's in-progress WallClockWindow collection, not this change (which touches neither file nor any collection). by-name reflection over test classes elsewhere: CiFilterCoverageConformanceTests.ReflectTests uses FullyQualifiedName (unaffected); FixtureShardScan selects by base type; the other GetTypes() calls enumerate compiler/AST types. No other roster cites FileBasedIntegrationTests or LetEmissionInvarianceTests by class name. dotnet format whitespace --verify-no-changes (repo-relative --include) clean. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HzQeHzwNfujh3DnznBXsQr
…hange (refs #2179) P41 Phase 5 Task 3 (docs + wrapper), from the P5.3 inventory. Figures (measured, #2179): Compiler.Tests whole project at the committed maxParallelThreads 2 = 1641 s trx span @ af6f309 (2042 s at 1; 5325 s at 1 before P41 @ 116c6e2); Lsp 107 s, Cli 31 s, Stdlib 66 s, Core 5 s (P0 control @ 116c6e2) -> whole-solution gate ~30 min. GapDiscovery share: the 8 classes carrying [Trait("Category","GapDiscovery")] are 509 s of 2850 s summed test time (17.9%) in the N=2 trx @ af6f309 (class-level sum from that trx; one class carries the trait per method, so this is an upper bound); the slowest classes are MetamorphicCorpusSweep 171 s, ReparseEquivalence 114 s, CPythonDifferentialParse 114 s, BlockScopeRedeclaration 113 s - the sweeps are no longer the long pole. Peak tree RSS at N=2 is 7.2 GB, so the "5-10 GB per run" band stays (CLAUDE.md:24, implement-plan, bench_ab.py:480 untouched). - CLAUDE.md:26 - "~22 min" -> "~30 min (Compiler.Tests ~27 at maxParallelThreads 2 @ af6f309)"; the GapDiscovery advice now gives their share and says filtering saves minutes, not the long pole. Minimal in-place edit (rules-only file, never regenerated). - docs/design/verification-contract.md §6 - "~22 min" -> "~30 min (maxParallelThreads 2, @ af6f309)". - .claude/skills/implement-plan/SKILL.md :49, :126; .claude/skills/compiler-audit/SKILL.md :17; .claude/agents/implementer.md :48; .claude/agents/verification-expert.md :33 - "~22 min" -> "~30 min". - .claude/scripts/dotnet-serialized - lock waiter TIMEOUT 2700 -> 4500 s (75 min), rationale in the comment with its sources: a queued run must outlast one whole-solution gate (~1850 s) plus one more Compiler.Tests run; the lead proposed 3600 s, but that leaves ~4% over gate + an N=2 Compiler.Tests run (3491 s) and is SHORT of gate + an N=1 run (~3900 s), which the measurement matrices queue; 4500 s keeps ~15% over the latter. The old 2700 s was already short of the pre-P41 suite (two queued runs exited 124 on 2026-10-05). "~17 min" for Compiler.Tests in the #1384 comment -> "~27 min @ af6f309"; "45 minutes" in the env-knob comment made number-free. STALL_SECONDS unchanged (not re-measured). - build_tools/bench_ab.py:661 - "the 45-minute timeout" -> "the lock's waiter timeout" (comment). Not changed: the CI shard-balance comment (waits for the P5.3 CI run); historical/dated records (plans, docs/design/test-harness/*, the 2026-08-12 incident docstring in test_dotnet_serialized_lock.py). Checks: bash -n on the wrapper clean; pytest build_tools/tests/test_dotnet_serialized_lock.py 35 passed (env-knob timeouts, unaffected by the default). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HzQeHzwNfujh3DnznBXsQr
test_timer_fires_after_interval (Spy/threading/threading_module_tests.spy:323-330) starts a 50 ms threading.Timer, then gives it a fixed 200 ms window to set a flag. Sharpy's Timer callback is a System.Threading.Timer callback (Sharpy.Stdlib Threading/Timer.cs:31), so it needs a free thread-pool thread. In the Stdlib matrix at xUnit MaxParallelThreads=8 (@ b335dd6) the pool was saturated and the test went red (Assert.True failure on line 330). It is flaky, not deterministic: - an N=8 repeat was green; - run alone at N=1 it was green 4/4, 3 of them with every core busy. The generated test is not edited and its tolerance is not widened. ThreadingModuleTestsModuleTests instead joins a new run-alone collection, WallClockWindow (DisableParallelization = true, defined in Stdlib.Tests via its partial twin in Spy/SpyTestCollections.cs). The new collection is rostered in ProcessGlobalStateConformanceTests with the reason, and the hand-counted positive-control constants go 7 -> 8 collections and 52 -> 53 members. Findings ledger: - (1) Test design: the .spy test should wait on an Event that the callback sets, with a generous timeout. The class then leaves the roster. - (2) Product: Sharpy's Timer runs on a thread-pool timer, while CPython's runs on a dedicated thread. That is an unlisted surface, so under docs/design/python-fidelity-scope.md R-CM it is a docs/deviations.yaml row by default, not an issue. Verified in worktree p41-lane-b-stdlib at 1cf19a1 plus these two files (byte-identical, cmp): - build 0 warnings / 0 errors; ProcessGlobalStateConformanceTests 31/0/0. - Stdlib.Tests whole at N=4: 4169/0/0, 22 s. Against the P0 trx, the only differences are the 7 explained rows (4 StdlibFixtureShardTotalityTests + 3 replicated TestFixturesDirectory_Exists from the K=4 shard classes, ebe4523). - dotnet format whitespace sharpy.sln --verify-no-changes: clean. Mutations, each restored by cp and checked with cmp: (1) the twin's [Collection("WallClockWindow")] removed (no rebuild; the guard scans source) broken -> red (2 failed, 29 passed: "- ThreadingModuleTestsModuleTests in WallClockWindow" + positive control), restored -> green (31 passed) (2) the WallClockWindow roster row deleted (Compiler.Tests rebuilt) broken -> red (3 failed, 28 passed: unrostered definition + unrostered member + positive control), restored and rebuilt -> green (31 passed) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HzQeHzwNfujh3DnznBXsQr
src/Sharpy.Stdlib.Tests/xunit.runner.json: maxParallelThreads 1 -> 4. Stdlib.Tests is raised in its own commit, after the Compiler cap (a24387b), because it carries the higher concurrency risk (the spy-generated module tests call stdlib globals in-process). Measured with .claude/scripts/test-memory-harness --threads N --trx on src/Sharpy.Stdlib.Tests, filter Category!=Benchmark, in worktree p41-lane-b-stdlib @ b335dd6 (the matrix) and @ 1cf19a1 + 8c44659's two files (the confirmation runs). Machine with the local model loaded: base used memory 18.8-24.6 GB, swap flat in every cell. | N | wall (harness / trx) | peak tree RSS | peak testhost RSS | counts | |---|---|---|---|---| | 1 | 142 s / 140 s | 843 MB | 529 MB | 4169/0/0 | | 2 | 33 s / 33 s | 924 MB | 578 MB | 4169/0/0 | | 4 | 26 s / 25 s | 922 MB | 580 MB | 4169/0/0 | | 8 | 23 s / 22 s | 912 MB | 590 MB | 4168/1/0 (the timer test, below) | Repeats at N=4 (@ b335dd6): 4169/0/0 three times, 23 s / 23 s / 25 s, peak tree 838/887/932 MB. N=8 repeat: 4169/0/0, 41 s. Perturbed order: xUnit v2 has no random-order switch, so this run used MaxParallelThreads=3 + ParallelAlgorithm=aggressive (different scheduling and interleaving): 4169/0/0, 28 s. With WallClockWindow (8c44659) at N=4: 4169/0/0, 22 s. With this json and no RunSettings override: 4169/0/0, 21 s. The bin copy of the json reads 4, so the cap is honoured from the file. Against the P0 control trx (.claude/tmp/p41-control/Stdlib.trx @ 116c6e2, 4162 passed), every green cell is identical by test name x outcome except for 7 explained rows. Names are normalised for the P4.2 Shard<k> classes and the checkout path embedded in display names. - 4 StdlibFixtureShardTotalityTests (ebe4523). - 3 extra TestFixturesDirectory_Exists rows: each of the K=4 shard classes inherits the Fact. The 4162 fixture rows are unchanged. The N=8 red was ThreadingModuleTestsModuleTests.TestTimerFiresAfterInterval. Its 50 ms Timer has a fixed 200 ms window, and Sharpy's Timer callback runs on the thread pool, so it starves when the pool is saturated. Triage: - the N=8 repeat was green; - run alone it was green 4/4, 3 of them with every core busy. It is a wall-clock-window hazard of the test, not a stdlib-global collision. The class now runs alone in WallClockWindow (8c44659); the test-design fix and the Timer deviation are ledger rows. Chosen N=4: 5.6x faster than N=1. N=8 buys about 3 s more but is the cell that hit the timing hazard. Memory is not the constraint here: under 1 GB tree RSS at every N. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HzQeHzwNfujh3DnznBXsQr
…(refs #2179) The twin comment in Spy/SpyTestCollections.cs and the roster reason in ProcessGlobalStateConformanceTests name #2251 (threading.Timer is a thread-pool timer, not a Thread). Once that lands, the spy test waits on an Event and ThreadingModuleTestsModuleTests leaves the collection. Text only: the roster guard is 31/0/0, the Stdlib ThreadingModuleTests filter is 43/0/0, the build has 0 warnings, and format is clean. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HzQeHzwNfujh3DnznBXsQr
Verifier finding 2b (in-contract): the three shard totality guards read each shard's
GetTestFixtures() - what a shard OFFERS - not what xUnit RUNS. A `[Theory(Skip = "...")]` on one
shard's method collapses its ~440 rows to one skipped row, and a `[Trait("Category", ...)]` on one
shard lets a CI filter drop it (the main filter excludes Category=GapDiscovery/Benchmark); both
left union/disjoint/non-empty green.
FixtureShardScan.ShardMethodViolations(shardBase, testMethodName) checks every concrete shard
class: it declares the test method with exactly one [Theory] whose Skip is empty, exactly one
[MemberData] naming its own GetTestFixtures (MemberType null or the class itself), and no
trait-bearing attribute (ITraitAttribute, spelled with its arguments) on the method or the class
that the shard base and its ancestors do not carry. New fact
EveryShard_RunsItsRowsAsOneUnskippedUncategorisedTheory in FixtureShardTotalityTests (Compiler,
RunTestFixture), StdlibFixtureShardTotalityTests (Stdlib, RunTestFixture) and
LetEmissionShardTotalityTests (LetTwin_EmitsByteIdenticalCSharp); each also asserts the number of
classes checked equals its literal K, so an empty scan cannot pass.
Mutations (main tree @ dada5fd, one script: green -> mutate -> build -> red -> cp restore ->
build -> green, measured):
1. Compiler Shard3 `[Theory]` -> `[Theory(Skip = "x")]`: Compiler ~Totality 1 failed / 650 passed -
only the new fact ("Shard3.RunTestFixture: Skip = "x" (its rows would not run)"); union,
disjoint and non-empty stayed green, i.e. the gap was real.
2. Stdlib Shard1 + `[Trait("Category", "Benchmark")]`: Stdlib ~Totality 1 failed / 4 passed -
only the new fact ("Shard1: trait(s) the shard base does not carry: TraitAttribute(Category,
Benchmark)").
Restored both by cp, rebuilt: Compiler 651/651, Stdlib 5/5 green.
Pre-commit filter ~Totality | ~DispatchSiteInventory | ~ProcessGlobalState | ~CiFilterCoverage:
Compiler.Tests 702/0/0, Stdlib.Tests 5/0/0 @ dada5fd + this change (measured).
dotnet format whitespace --verify-no-changes (repo-relative --include) clean on all four files.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HzQeHzwNfujh3DnznBXsQr
…2179) PR #2252's run 37378225480 @ 8682582 (maxParallelThreads 2) left shard 5 (Properties) the long pole. Measured per-shard trx spans, in seconds: 1 fixtures 124 · 2 Integration P–V + CodeGen 305 · 3 Semantic P–V 108 · 4 Semantic rest 133 5 Properties 492 · 6 Conformance 150 · 7 everything else 259 Shard 5's span was a 287 s parallel phase, led by CPythonDifferentialParse (178 s) and AmbiguityHotspot (169 s), both in Properties.Parser, followed by a 204 s run-alone PropertySerial phase. Its job took 554 s; the next slowest job took 373 s. New buckets, still 7 jobs: - 3: all of Semantic (old 3 + 4); - 4: Properties.Parser; - 5: Properties minus Properties.Parser. Shards 1, 2, 6 and 7 are unchanged; 7 already negates `Semantic.` and `Properties.`. Expected spans (derived from the same trx, not measured): about 240 s for Semantic, about 210 s for Properties.Parser, and about 300 s for the rest of Properties. That makes shard 2 (305 s) the long pole, roughly 3 minutes off the CI wall. Verified locally on the main-tree build, replaying the compiler-shard-partition job: - the main filter lists 48051 tests, and the union of the 7 shards is identical as a multiset; - shard sizes are 3576 / 3428 / 11074 / 60 / 3001 / 16628 / 10284; - dropping shard 5's `!~Properties.Parser.` makes the replay report 60 double-run rows, so the instrument is not vacuous; - CiFilterCoverageConformanceTests 3/0/0, and the YAML parses. The partition-invariant comments now name the new negation (`!~Properties.` covers 4+5). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HzQeHzwNfujh3DnznBXsQr
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
P41: test-suite throughput (#2179, #2180). This changes tests, test infrastructure, CI, docs and scripts only; no product code changed.
Summary
maxParallelThreadsis now Compiler.Tests 2 and Stdlib.Tests 4.HeapHardLimitstays at 5 GB.HeavyCompilationandSequentialdissolved.HeavyCompilation's[CollectionDefinition]lived in Sharpy.TestInfrastructure, which xUnit v2 ignores, so the attribute chained 173 classes into one serial collection. The only remaining run-alone collections are 8 audited ones, each guarding a process-global hazard.IntegrationTestBaseexecute arms. It was ≈52% of each call. The arms now share one execute path.~FileBasedIntegrationTests,DisplayName~and the CI shard filters are unchanged. LetEmission's per-cell corpus rediscovery is cached: 466 s → 57 s.dotnet-serializedlock timeout is 4500 s.Measured
Whole-solution gate
Category!=Benchmark:Test name × outcome compared against the control, in every project: nothing is missing, and the additions are the new guard rows only.
Order-independence: three full runs and one perturbed-order run (
MaxParallelThreads=3,ParallelAlgorithm=aggressive) gave identical results.Compiler.Tests at N=1/2/4/8: 2042 / 1641 / 1981 / 4707 s, peak tree RSS 7.1–8.2 GB. N≥4 loses to oversubscription by classes that are already internally parallel.
Stdlib.Tests at N=1/2/4/8: 142 / 33 / 26 / 23 s, under 1 GB at every N. At N=8 one fixed-window timer test went red once; that class now runs alone (stdlib: threading.Timer is a .NET thread-pool timer, not a threading.Thread — isinstance(t, Thread) is False, t.join() is SPY0203, the callback waits for a free pool thread #2251).
For CI review
.github/workflows/dotnet10.ymlwill be re-balanced from them in a follow-up.Filed from the round
threading.Timeris a pool timer, not a Thread.Refs #2179 #2180 (both stay open for the cells and the #2180 in-process ruling).
🤖 Generated with Claude Code
https://claude.ai/code/session_01HzQeHzwNfujh3DnznBXsQr