Skip to content

Injectable internal time source for deterministic Report timing (#338) - #339

Merged
Chris-Wolfgang merged 1 commit into
vNextfrom
feat/338-clock-seam
Jul 28, 2026
Merged

Injectable internal time source for deterministic Report timing (#338)#339
Chris-Wolfgang merged 1 commit into
vNextfrom
feat/338-clock-seam

Conversation

@Chris-Wolfgang

Copy link
Copy Markdown
Owner

Closes #338. Targets vNext so it ships in 0.19.0.

Summary

Adds an internal injectable time source so the base classes' Report timing metrics (StartedAt, Elapsed, and the throughput values derived from them) can be driven from a fake clock in tests — unblocking ETL-Test-Kit #262.

  • internal interface ITimeSource (UtcNow, GetTimestamp(), TimestampFrequency) + SystemTimeSource default (real DateTimeOffset.UtcNow + Stopwatch). Not TimeProvider — that's net8+-only and we target net462/netstandard2.0.
  • ExtractorBase / LoaderBase / TransformerBase read StartedAt/Elapsed through an internal ITimeSource? TimeSource field (same shape as the existing internal TimerCoreFactory seam). EtlRunState reads its elapsed through an injected ITimeSource (internal ctor). Default behaviour is byte-for-byte identical (real clock).
  • InternalsVisibleTo("Wolfgang.Etl.TestKit") so Test-Kit's doubles can set the fake — the visibility approach chosen for this change.

No public API change

Everything is internal. Pack + Package Validation stay green against the 0.18.1 baseline, so PublicAPI.Shipped/Unshipped are untouched and the 0.19.0 release prep (PR #337) remains valid.

Verification

  • 419 unit tests (6 new ClockSeamTests: fake-driven StartedAt/Elapsed on all 3 bases, before-first-item null/zero, real-clock default path, EtlRunState elapsed).
  • Stryker 100.00 %, 0 survivors. All 11 TFMs build clean.
  • No CHANGELOG entry — internal-only, matching the Mutation survivors (10) — kill these to raise the score #328 timer-seam precedent.

Merge path

Base is vNext. Once green, merging this into vNext folds it into the 0.19.0 release PR (#337) automatically. No public-API/PublicAPI churn, so it does not disturb the prep.

…#338)

Add an internal ITimeSource seam (StartedAt/Elapsed and the throughput metrics derived
from them) so timing can be driven from a fake clock in tests, unblocking ETL-Test-Kit#262.

- Internal ITimeSource + SystemTimeSource (default = DateTimeOffset.UtcNow + Stopwatch,
  net462-safe; NOT TimeProvider which is net8+).
- ExtractorBase/LoaderBase/TransformerBase read StartedAt/Elapsed through an internal
  TimeSource field (mirrors the internal TimerCoreFactory seam); EtlRunState reads elapsed
  through an injected ITimeSource (internal ctor). Default behaviour identical (real clock).
- InternalsVisibleTo("Wolfgang.Etl.TestKit") so Test-Kit doubles can inject a fake.

NO public API change (all internal) — pack + Package Validation green vs 0.18.1. 419 tests
(6 new ClockSeamTests); Stryker 100.00% (0 survivors); all 11 TFMs clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 28, 2026 15:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

BenchmarkDotNet

Details
Benchmark suite Current: 80a987b Previous: 3a14ba9 Ratio
Wolfgang.Etl.Abstractions.Benchmarks.ExtractorBenchmarks.Extract_NoProgress(RecordCount: 1000) 33666.0446879069 ns (± 212.76288582877567) 31930.534067789715 ns (± 658.2371578009119) 1.05
Wolfgang.Etl.Abstractions.Benchmarks.ExtractorBenchmarks.Extract_WithProgress(RecordCount: 1000) 35506.2731628418 ns (± 107.57179946508552) 34010.91799926758 ns (± 129.0833785595842) 1.04
Wolfgang.Etl.Abstractions.Benchmarks.ExtractorBenchmarks.Extract_NoProgress(RecordCount: 100000) 3277068.0911458335 ns (± 8584.100422777798) 3106942.7135416665 ns (± 3078.2601995964997) 1.05
Wolfgang.Etl.Abstractions.Benchmarks.ExtractorBenchmarks.Extract_WithProgress(RecordCount: 100000) 3437550.6783854165 ns (± 1508.719338267254) 3369861.2369791665 ns (± 3214.804757548345) 1.02
Wolfgang.Etl.Abstractions.Benchmarks.PipelineBenchmarks.FluentPipeline(RecordCount: 1000) 32726.99262491862 ns (± 87.5185674075267) 29861.78623453776 ns (± 94.08997512637652) 1.10
Wolfgang.Etl.Abstractions.Benchmarks.PipelineBenchmarks.ManualComposition(RecordCount: 1000) 32577.454701741535 ns (± 135.16839236479976) 29908.161814371746 ns (± 70.64600581703343) 1.09
Wolfgang.Etl.Abstractions.Benchmarks.PipelineBenchmarks.BaseClassComposition(RecordCount: 1000) 70143.9028930664 ns (± 259.1320549837876) 68483.69946289062 ns (± 285.6650714675182) 1.02
Wolfgang.Etl.Abstractions.Benchmarks.PipelineBenchmarks.FluentPipeline(RecordCount: 100000) 3202521.47265625 ns (± 2726.351303727282) 2854886.4140625 ns (± 1462.1467840974312) 1.12
Wolfgang.Etl.Abstractions.Benchmarks.PipelineBenchmarks.ManualComposition(RecordCount: 100000) 3205984.3424479165 ns (± 755.3263901337803) 2870112.7330729165 ns (± 326.26898943322766) 1.12
Wolfgang.Etl.Abstractions.Benchmarks.PipelineBenchmarks.BaseClassComposition(RecordCount: 100000) 6930356.3515625 ns (± 7031.847933564747) 6728828.515625 ns (± 4243.902731655375) 1.03

This comment was automatically generated by workflow using github-action-benchmark.

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.

2 participants