Skip to content

Coverage phase 1: JobScheduler unit tests + exclude Blazor Program bootstrap - #211

Merged
blehnen merged 9 commits into
masterfrom
phase-1-jobscheduler-coverage
Jul 21, 2026
Merged

Coverage phase 1: JobScheduler unit tests + exclude Blazor Program bootstrap#211
blehnen merged 9 commits into
masterfrom
phase-1-jobscheduler-coverage

Conversation

@blehnen

@blehnen blehnen commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Phase 1 — Cheap structural wins (coverage recovery)

Second phase of the 90% coverage recovery (follows #210). No production behavior changes — 73 new unit tests plus one attribute-only file.

Changes

  • ProgramCoverage.cs (new) — attaches [ExcludeFromCodeCoverage] to the compiler-generated top-level-statements Program class, dropping Blazor host bootstrap (218 lines @ 0%) from the coverable denominator. Declared internal to match the accessibility the compiler synthesizes, so the assembly surface is unchanged.
  • PendingEventHeapTests.cs — 20 tests: empty-heap guards, sift-up, grow-and-copy past the initial capacity, sift-down across insertion orders, duplicate timestamps, non-mutating peek, interleaved push/pop.
  • ScheduledJobTests.cs — 24 tests: start/stop/update-schedule incl. all missed-event-window branches; RunPendingEventAsync across stale run id, queued, requeued, already-queued, send-reports-exception, send-throws, and the unusable-next-occurrence path.
  • JobSchedulerTests.cs — 23 tests: both AddUpdateJob overloads, GetAllJobs, RemoveJob, dispose/post-shutdown guards, and the three job→scheduler event relays.
  • JobQueueTests.cs + TestDoubles.cs — 6 tests: ctor guard, dispose idempotency, creation-failure path.

No Thread.Sleep anywhere — the fire-and-forget Raise* events are awaited via TaskCompletionSource, so tests complete the moment the event fires. No new Sonar S2925 debt.

Measured effect (unit-tests-only basis, before → after)

Class before after
PendingEventHeap 0% 100%
ScheduledJob 0% 96.0%
JobScheduler 0% 81.3%
JobQueue 0% 38.0%

898 lines newly covered by unit tests.

Two honest caveats

  1. All four classes had zero unit coverage before this — their prior 62–75% came entirely from integration tests. So these 898 lines overlap with existing integration coverage by an unknown amount, and the net movement on the merged Codecov number will be smaller than the raw figure suggests. The Program.cs denominator reduction is the one part that's independent of overlap. Worth reading the merged number on this PR rather than assuming.
  2. JobQueue did not reach the ~90% target. Its queue-creation success paths need a real transport (CreateMethodJobProducer), which would require a Transport.Memory project reference that was out of scope. Integration tests already cover those paths. Guard/dispose/creation-failure branches are covered.

Verification: dotnet build NoTests.sln -c Release -p:CI=true → 0 warnings / 0 errors; core suite 993/993 green.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Tests
    • Added job scheduler test suites covering job queue lifecycle, validation, disposal idempotency, scheduling start/stop/update behavior, and event relay/error propagation.
    • Added coverage for pending-event heap ordering (including duplicates, resizing, and interleaved push/pop scenarios).
    • Added scheduled job tests for next-event calculation (including missed-event window logic), stale run handling, enqueue outcomes, and termination when no further occurrences exist.
  • Documentation
    • Updated code-coverage guidance, including intentional exclusions and Codecov gate/number interpretation.

blehnen and others added 5 commits July 21, 2026 08:17
Attach [ExcludeFromCodeCoverage] to the compiler-generated top-level-
statements Program class so Coverlet drops Program.cs (218 lines @ 0%)
from the coverable denominator. Declared internal to match the
synthesized accessibility, leaving the assembly surface unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
20 deterministic tests over the binary min-heap: empty-heap guards,
sift-up, grow-and-copy past the initial 16 capacity, sift-down across
insertion orders, duplicate timestamps, non-mutating peek, and
interleaved push/pop. No Thread.Sleep, no clock reads.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… paths

Adds a shared no-op ITransportInit double (needed for the new()
generic constraint, which a substitute cannot satisfy) and 6 tests:
ctor null guard, IsDisposed toggle, idempotent dispose over empty
collections, and the creation-reports-failure branch asserting the
error message is surfaced.

Queue-creation success paths call into a real transport container and
stay uncovered; reaching them would need a transport project reference.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
24 tests over start/stop/update-schedule (including the missed-event
window replay branches) and RunPendingEventAsync: stale run id, queued,
requeued, already-queued, send-reports-exception, send-throws, and the
unusable-next-occurrence path that terminates the schedule.

Time and schedule are substituted, so nothing reads the clock. The
fire-and-forget Raise* events are awaited via TaskCompletionSource
rather than a delay.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…t relay

23 tests over both AddUpdateJob overloads (guards, replace-existing,
autoRun), GetAllJobs, RemoveJob (unknown/stopped/running), dispose and
post-shutdown guards, the Start-then-Dispose path, and the three
job-to-scheduler event relays.

Adds a no-op IJobQueueCreation double to satisfy the TQueue constraint
on the two-generic overload.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6d90e1d6-bb04-45f9-b860-1709a499e5a3

📥 Commits

Reviewing files that changed from the base of the PR and between 2d5958e and 1c5d11a.

📒 Files selected for processing (3)
  • Source/Directory.Build.props
  • codecov.yml
  • docs/code-coverage.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • Source/Directory.Build.props

📝 Walkthrough

Walkthrough

Adds comprehensive tests for the job scheduler, scheduled jobs, pending-event heap, and queue lifecycle. It also configures and documents a scoped Blazor Program.cs coverage exclusion and tightens Codecov project coverage gating.

Changes

Job scheduler test coverage

Layer / File(s) Summary
Queue test contracts
Source/DotNetWorkQueue.Tests/JobScheduler/TestDoubles.cs, Source/DotNetWorkQueue.Tests/JobScheduler/JobQueueTests.cs
Adds no-op queue and transport doubles and tests JobQueue construction, disposal, and queue-creation errors.
Pending event heap ordering
Source/DotNetWorkQueue.Tests/JobScheduler/PendingEventHeapTests.cs
Tests empty-heap behavior, validation, resizing, ordering, duplicate times, stable peeking, and interleaved operations.
Scheduled job lifecycle
Source/DotNetWorkQueue.Tests/JobScheduler/ScheduledJobTests.cs
Tests schedule start, stop, update, metadata, pending-event execution, queue outcomes, exceptions, and schedule termination.
Job scheduler lifecycle and events
Source/DotNetWorkQueue.Tests/JobScheduler/JobSchedulerTests.cs
Tests job registration, replacement, listing, removal, shutdown, disposal, event relay, and post-shutdown pending events.

Coverage configuration

Layer / File(s) Summary
Coverage exclusion and gating policy
Source/Directory.Build.props, codecov.yml, docs/code-coverage.md
Adds a scoped Coverlet exclusion for the Dashboard UI Program.cs, changes the Codecov project threshold to zero drift, and documents coverage interpretation, exclusions, API checks, and diagnostics.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Poem

A rabbit checks the queues at night,
Heap events hop in sorted flight.
Jobs start, stop, and safely rest,
Each little path receives a test.
Coverage rules now shine bright—
Thump, thump, all green and right!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly reflects the JobScheduler tests and Blazor Program coverage exclusion, though it omits other test suites and config/docs updates.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 21, 2026
blehnen and others added 2 commits July 21, 2026 09:23
CA1861: hoist constant array arguments to static readonly fields.
MSTEST0037: use Assert.HasCount instead of Assert.AreEqual on counts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replaces the [ExcludeFromCodeCoverage] partial-class approach, which
tripped two SonarCloud rules that cannot be satisfied for this
construct. S3903 (reported as a BUG) demands a named namespace, but the
partial must stay in the global namespace to bind to the Program class
synthesized from top-level statements; namespacing it creates a
different type and silently breaks the exclusion. S1118 wants static or
a protected ctor, neither of which is possible or meaningful here.

Excluding by file removes the source file entirely, so no suppressions
are needed. Dashboard.Ui/Program.cs is the only Program.cs in the repo.
Verified: 0 Program.cs entries in the coverage report while 110 other
Dashboard.Ui classes remain.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
Source/Directory.Build.props (1)

30-36: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Prevent future Program.cs files from being excluded unintentionally.

The configuration and documentation use **/Program.cs, which currently matches the Blazor bootstrap but will also match any future file with that name.

  • Source/Directory.Build.props#L30-L36: narrow ExcludeByFile to the Dashboard UI path and add a CI guard for unexpected matches.
  • docs/code-coverage.md#L59-L65: document the narrowed pattern and remove the claim that the broad glob is inherently unambiguous.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Source/Directory.Build.props` around lines 30 - 36, Scope the ExcludeByFile
pattern in Source/Directory.Build.props to Dashboard.Ui/Program.cs, and add a CI
guard that fails when unexpected Program.cs files are matched or introduced.
Update docs/code-coverage.md lines 59-65 to document the narrowed pattern and
remove the claim that the broad **/Program.cs glob is inherently unambiguous.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@Source/Directory.Build.props`:
- Around line 30-36: Scope the ExcludeByFile pattern in
Source/Directory.Build.props to Dashboard.Ui/Program.cs, and add a CI guard that
fails when unexpected Program.cs files are matched or introduced. Update
docs/code-coverage.md lines 59-65 to document the narrowed pattern and remove
the claim that the broad **/Program.cs glob is inherently unambiguous.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c9e5d9ab-cd4c-42b0-a22d-8cf4d49ccbb5

📥 Commits

Reviewing files that changed from the base of the PR and between 3896007 and 2d5958e.

📒 Files selected for processing (3)
  • Source/Directory.Build.props
  • Source/DotNetWorkQueue.Tests/JobScheduler/JobSchedulerTests.cs
  • docs/code-coverage.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • Source/DotNetWorkQueue.Tests/JobScheduler/JobSchedulerTests.cs

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 21, 2026
…ject

Addresses CodeRabbit review: **/Program.cs would also match any
Program.cs added to another project later, silently dropping real code
from coverage -- the exact erosion this policy exists to prevent.

Narrowed to **/DotNetWorkQueue.Dashboard.Ui/Program.cs and reworded the
docs, which had claimed the broad glob was unambiguous (true of the
current tree, but not enforced by the pattern).

Verified: 0 Program.cs entries in the coverage report, 110 other
Dashboard.Ui classes still covered.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@blehnen

blehnen commented Jul 21, 2026

Copy link
Copy Markdown
Owner Author

Addressed the nitpick: narrowed ExcludeByFile from **/Program.cs to **/DotNetWorkQueue.Dashboard.Ui/Program.cs.

Agreed on the substance — a bare filename glob would also match any Program.cs added to another project later, silently dropping real code from coverage, which is exactly the erosion this policy exists to prevent. The docs' "unambiguous" claim was true of the current tree but not enforced by the pattern; reworded.

I did not add the suggested CI guard for unexpected Program.cs matches. Once the pattern is scoped to a single path, a Program.cs elsewhere can no longer match, so the guard would be defending against a condition that cannot occur.

Verified after the change: 0 Program.cs entries in the coverage report, 110 other Dashboard.Ui classes still covered.

Reconciliation finished: the Codecov/badge gap is NOT a pipeline defect.
Codecov excludes partially-covered lines from hits; ReportGenerator
counts them as covered. 867 partial lines on master account for the
entire ~2.5 point difference (87.45% vs 90.04% on identical denominators).

Master build #132 was fully green with all 24 coverage inputs merged and
still read 87.45%, which rules out the stale-upload and missing-stage
theories recorded earlier in this doc.

Coverage also did not erode gradually. Removing Dashboard.Ui (1,303 lines
at 23.6%) from today's totals reproduces the historical figure exactly
(29,066/32,282 = 90.04%), so one untested Blazor project accounts for the
whole decline from the 90.25% Codecov reported in 2024.

Changes:
- Codecov is now documented as the authoritative number (it gates PRs).
- project status: auto/2% -> auto/0%. Coverage may rise or stay flat but
  never drop below the parent commit. The old 2% slack is what let the
  number drift down unnoticed, since auto rebaselines every merge.
- Documented that reaching 90% requires Dashboard.Ui specifically, and
  that partial lines are a cheaper secondary lever.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@blehnen
blehnen merged commit 262c807 into master Jul 21, 2026
6 of 7 checks passed
@blehnen
blehnen deleted the phase-1-jobscheduler-coverage branch July 21, 2026 17:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant