Repository navigation
test: make SourceLink and TestKit.Concurrency report coverage - #668
Merged
Merged
Conversation
Both assemblies ran tests in Stage 1 without coverlet.collector, so the sync PR's (#667) gate failed them as "no coverage data". Add the collector (8.0.1, as the other net10.0-only test projects). That exposed what they would measure: - TestKit.Tests.Concurrency (10.7%): every test returned early unless the assembly had been Coyote-rewritten, so the normal sweep ran nothing. Run the TestingEngine in both modes: systematic fuzzing when un-rewritten (no false deadlocks), schedule control when rewritten. The race scenarios hand their outcome to a straight-line DrainAsync and assert on it, and three deterministic facts pin each outcome the races accept. Verified both un-rewritten and after `coyote rewrite` with COYOTE_REWRITTEN=1. - Abstractions.Tests.SourceLink (75.2%): the probe's branches depended on what GitHub answered. The retry/classification logic is now ProbeAsync + Classify + IsAcceptable with deterministic tests for every status, and the network test asserts the outcome. A probe URL is always buildable (SourceLink substitutes the SHA on every build), so a missing one now fails instead of returning. Part of #658 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
No blocking issues were identified in the collector additions or test refactors.
Review effort: Balanced
Findings: 1
Open (1)
What changed in this PR
Adds coverage collection for SourceLink and TestKit concurrency tests, addressing the missing coverage rows exposed by #667 and supporting #658.
Changes:
- Adds
coverlet.collectorto both test projects. - Runs concurrency scenarios without rewriting and adds deterministic outcome tests.
- Refactors SourceLink probing for deterministic retry and status tests.
| File | Description |
|---|---|
| tests/Wolfgang.Etl.TestKit.Tests.Concurrency/Wolfgang.Etl.TestKit.Tests.Concurrency.csproj | Enables coverage collection. |
| tests/Wolfgang.Etl.TestKit.Tests.Concurrency/ConcurrencyTests.cs | Enables ordinary-sweep scenarios and tests deterministic outcomes. |
| tests/Wolfgang.Etl.Abstractions.Tests.SourceLink/Wolfgang.Etl.Abstractions.Tests.SourceLink.csproj | Enables coverage collection. |
| tests/Wolfgang.Etl.Abstractions.Tests.SourceLink/SourceLinkPdbTests.cs | Separates probe logic and adds deterministic tests. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…n tests Remove all 70 [ExcludeFromCodeCoverage] attributes from test code (test doubles and helpers in Abstractions.Tests.Unit, ErrorPolicies.Tests.Unit, TestKit.Tests.Unit and TestKit.Xunit.Tests.Unit). The fleet policy allows the attribute only on an infrastructure check; this repo has none. Cover what the removal exposed, plus the pre-existing gaps, with new tests and mechanical changes only. No existing assertion changes. - New BaseClassTests/StageRunner: runs a stage once through its progress overload and returns the items and reports. New tests use it to run each double whose own tests only construct, configure or dispose it (worker and CreateProgressReport). - Timer-injection constructors on SequenceExtractor / ListLoader / IdentityTransformer: one test each fires a ManualProgressTimer mid-run. - Positive controls: the finalizer runs when a Finalizable* double is not disposed; CountingSynchronizationContext counts and runs Post and Send; DualDisposableLoader.Dispose sets its flag when called directly. - Never-routed guards on the CancelOnly* doubles are called directly and pin the WrongOverloadCalledException message. - Recording timers' Start/Dispose, RecordingLogger.BeginScope and the helper iterators (cancelling / empty sources) are pinned by new tests. - Mechanical: GuardParityTests' never-enumerated EmptyAsync() becomes AsyncEnumerable.Empty<int>(); FaultyExtractorMutationTests' local iterator becomes the static CancellingSkipSource (same items, same cancel point); TestKit DocExamples' per-example check moves into CheckExample; DelayingExtractorCancellationContractTests' two drain loops share one DrainAsync(items, onItem) with the same counting and cancel semantics. - Fuzz: new property that a LoaderBase progress run reports every loaded item. - Stale "cannot be covered" comments updated. Local, Stage 1 TFMs (net5.0-net10.0) with the repo-template#702 runsettings: every test assembly 100%; Release build 0 warnings / 0 errors; the touched projects also pass on net462/net472/net48/net481/netcoreapp3.1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Chris-Wolfgang
added a commit
that referenced
this pull request
Oct 12, 2026
#701) #668 makes the TestKit ConcurrencyTests always run: un-rewritten in systematic fuzzing mode in the ordinary test sweep, and under controlled scheduling in this workflow with COYOTE_REWRITTEN=1. The header and the systematic-run step comments still said the tests only run when rewritten and that RunSystematic early-returns without the flag. Comments only; no behaviour change. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 15, 2026
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.

Why
Stage 1 on the sync PR #667 fails with
Wolfgang.Etl.Abstractions.Tests.SourceLink (no coverage data)andWolfgang.Etl.TestKit.Tests.Concurrency (no coverage data)(run 36947263126). The log shows "Unable to find a datacollector with friendly name 'XPlat Code Coverage'" for both.Cause and fix, per assembly
coverlet.collectorcoverlet.collectorWhat the new rows exposed, and the test changes
TestKit.Tests.Concurrency/ConcurrencyTests.csCOYOTE_REWRITTEN=1, so the PR sweep ran no scenario at all.RunSystematicnow always runs Coyote'sTestingEngineand sets.WithSystematicFuzzingEnabled(!rewritten). Un-rewritten, Coyote fuzzes timing without the false "Deadlock detected" that schedule control reports. Rewritten (the coyote workflow), it controls the schedule as before.DrainAsyncreturns(observed, fault)and the scenario asserts withSpecification.Assert(fault is null or OperationCanceledException/ObjectDisposedException).coyote rewrite rewrite.coyote.jsonwithCOYOTE_REWRITTEN=1: 5/5 pass, three runs;Abstractions.Tests.SourceLink/SourceLinkPdbTests.csProbeAsync(injectable status source, TimeSpan pause),ClassifyandIsAcceptable. New deterministic tests cover 200/403/429/503/400, a persistent 404 (3 attempts), a 404 that clears on retry,HttpRequestExceptionandTaskCanceledException, plus the acceptability table. Behaviour is the same as before:https://raw.githubusercontent.com/Chris-Wolfgang/ETL-Abstractions/bdc6c85.../*). So "no mappings" and "no probe URL" are now assertion failures instead of earlyreturns. TheurlPrefix.Contains('*')branch could never be taken and was removed.BuildProbeUrlandReadSourceLinkPayloadare now LINQ queries.Exclusions removed
None existed in these two assemblies. Tests.Unit still carries class-level
[ExcludeFromCodeCoverage]on test doubles. That is out of scope here, because that assembly already reports a row (99.1%).Verified locally
dotnet build -c Release: 0 warnings, 0 errors.dotnet testper test project per net5.0–net10.0 TFM with--collect:"XPlat Code Coverage" --settings coverlet.runsettings, thendotnet reportgenerator→Summary.txt). Every test assembly that ran has a row, and all 34 project/TFM runs pass. Test rows:Unblocks #667; part of #658.
Round 2 (2596884): every test assembly at 100% with no test-code exclusions
Fleet policy: every test assembly at 100%, and
[ExcludeFromCodeCoverage]in tests only on a dedicated infrastructure-check method. This repo has no such method.Exclusions removed / added
[ExcludeFromCodeCoverage]attributes intests/. They sat on test doubles and helpers in Abstractions.Tests.Unit (BaseClassTests/, ConfigureAwaitContextTests, DisposedGuardTests, FinalizationSuppressionTests, ItemErrorHandlingTests, Performance/AllocationFreeTests, PipelineTests/), ErrorPolicies.Tests.Unit, TestKit.Tests.Unit (mutation tests, CountingProgressTimer, CultureSwapper) and TestKit.Xunit.Tests.Unit. The commit message says 70; that is a miscount, the diff removes 71.grep -rn ExcludeFromCodeCoverage testsnow returns nothing.src/is untouched, including its existing exclusions.Test changes (for review)
All of these add tests or make mechanical changes. No existing assertion changes.
BaseClassTests/StageRunner.cs. Runs a stage once through its progress overload and returns the items and reports. New*_when_run_produces_no_items_and_a_final_report_of_zerotests use it to run each double whose own tests only construct, configure or dispose it: GuardParity, OptionsConstructor, ConvenienceBaseOptionsConstructor, DisposedGuard, TimerSeam, BaseCoverage DisposeRecording, ItemErrorHandling, FinalizationSuppression. The runs reach the doubles' worker andCreateProgressReport.ExtractorBaseTests,LoaderBaseTestsandTransformerBaseTestseach get one: the internal timer-injection constructor, with aManualProgressTimerfired mid-run, gives reports[2, 3]. The loader test also readsListLoader.LoadedItems, which nothing read before.Finalizable*double is finalized (the control for the SuppressFinalize tests);CountingSynchronizationContextcounts and runs Post and Send (the control for the "posted nothing" assertions);DualDisposableLoader.Disposesets its flag when called directly.OverloadDoubleCoverageTestscalls the never-routedCancelOnly*guards directly and pins theWrongOverloadCalledExceptionmessage.Start/Dispose(TestKit mutation tests,*BaseCoverageTests; newCountingProgressTimerTests);RecordingLogger.BeginScope(ErrorPolicies);EmptyIgnoring*,CancellingSkipSourceAsync,CancelOnFirst,CancelAt,CancellingSequencethrough its non-generic enumerator;DisposeTrackingEnumerablenon-genericCurrent/Reset;RetryingExtractoron the progress path;ParsingExtractor/CollectingLoaderprogress reports;ConfigurableLoader/ConfigurableTransformerAbort rethrow;CounterHarness.ExtractAsync, which throwsNotSupportedException.LoaderBase_progress_run_reports_every_loaded_item_in_its_final_report.GuardParityTests: the never-enumeratedEmptyAsync()argument becomesAsyncEnumerable.Empty<int>(), and the helper is removed.FaultyExtractorMutationTests: the local iterator inExtractAsync_cancelling_during_the_skip_phase_throws_promptlymoves to the staticCancellingSkipSource(cts). The items and cancel point are the same, and a new test drains it.TestKit.Tests.DocExamples: the per-example rot check moves intoCheckExample(...). New tests cover a clean snippet and a snippet that is rot in every context (int value = "text";→ CS0029). That second test reachesfailures.Addandbest ?? ..., which no test reached before.DelayingExtractorCancellationContractTests: both overrides now share oneDrainAsync(items, onItem). It counts each item before the callback and setscanceledonOperationCanceledException, the same semantics as the two loops it replaces. A new test drives a run wherecancelAfteris never reached, which covers normal completion.Coverage before -> after (local, Linux-equivalent Stage 1 loop)
BEFORE = this branch at 7ac9d97 with its current
coverlet.runsettings, net8.0+net10.0. AFTER = 2596884 with the #667 runsettings (repo-template#702), net5.0–net10.0. A config-only run (7ac9d97 + new runsettings) gave the same numbers as BEFORE, because this repo has noTestModels/*Fixtureclasses. Removing the exclusions before adding tests took Abstractions.Tests.Unit to 96.8%, ErrorPolicies.Tests.Unit to 96.3%, TestKit.Tests.Unit to 98.7% and TestKit.Xunit.Tests.Unit to 99.3%.Wolfgang.Etl.Abstractions.Tests.Concurrency(Coyote CLI, no Test SDK) andAotSmokeare not xunit test projects. Stage 1 does not run them, and they are not measured.Verified locally
dotnet build -c Release: 0 warnings, 0 errors (all TFMs).Merge order
Merge this PR (#668) first, then the config PR #667.
🤖 Generated with Claude Code