diff --git a/CHANGELOG.md b/CHANGELOG.md index f7f53fa..a399288 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,7 +46,9 @@ public API only — no breaking change. - Built against `Wolfgang.Etl.Abstractions` 0.17.0 → **0.18.0**. - The doubles' `CreateProgressReport()` now surfaces the base's `StartedAt`/`Elapsed` timing - (Abstractions 0.14.0) in the `Report`, so `ItemsPerSecond` is computed for reported progress. + (Abstractions 0.14.0) in the `Report`, so `ItemsPerSecond` is computed for reported progress; + the extractor doubles also set `TotalItemCount` from a materialized collection source so + `PercentComplete`/`EstimatedRemaining` compute. ## [0.10.1] - 2026-07-24 diff --git a/src/Wolfgang.Etl.TestKit/FaultyExtractor.cs b/src/Wolfgang.Etl.TestKit/FaultyExtractor.cs index e759082..b07fc55 100644 --- a/src/Wolfgang.Etl.TestKit/FaultyExtractor.cs +++ b/src/Wolfgang.Etl.TestKit/FaultyExtractor.cs @@ -353,7 +353,15 @@ protected override void Dispose(bool disposing) /// protected override Report CreateProgressReport() => - new(CurrentItemCount) { StartedAt = StartedAt, Elapsed = Elapsed }; + new(CurrentItemCount) + { + StartedAt = StartedAt, + Elapsed = Elapsed, + + // When the source is a materialized collection its size is a cheap, known + // total, so PercentComplete / EstimatedRemaining can be computed. + TotalItemCount = (_items as ICollection)?.Count, + }; diff --git a/src/Wolfgang.Etl.TestKit/TestExtractor.cs b/src/Wolfgang.Etl.TestKit/TestExtractor.cs index 1bc1b54..d36a166 100644 --- a/src/Wolfgang.Etl.TestKit/TestExtractor.cs +++ b/src/Wolfgang.Etl.TestKit/TestExtractor.cs @@ -515,7 +515,16 @@ protected override void Dispose(bool disposing) /// protected override Report CreateProgressReport() => - new(CurrentItemCount) { StartedAt = StartedAt, Elapsed = Elapsed }; + new(CurrentItemCount) + { + StartedAt = StartedAt, + Elapsed = Elapsed, + + // When the source is a materialized collection its size is a cheap, known + // total, so PercentComplete / EstimatedRemaining can be computed. An + // enumerator- or factory-backed source has no known total (stays null). + TotalItemCount = (_enumerable as ICollection)?.Count, + }; diff --git a/tests/Wolfgang.Etl.TestKit.Tests.Unit/DoubleReportTimingTests.cs b/tests/Wolfgang.Etl.TestKit.Tests.Unit/DoubleReportTimingTests.cs index 8a129c8..1e783d7 100644 --- a/tests/Wolfgang.Etl.TestKit.Tests.Unit/DoubleReportTimingTests.cs +++ b/tests/Wolfgang.Etl.TestKit.Tests.Unit/DoubleReportTimingTests.cs @@ -50,6 +50,28 @@ public async Task CreateProgressReport_after_a_run_computes_a_non_negative_throu Assert.True(report.ItemsPerSecond >= 0); } + [Fact] + public async Task CreateProgressReport_for_a_collection_source_surfaces_TotalItemCount() + { + var sut = new ExposedTestExtractor(new List { 1, 2, 3, 4, 5 }); + + await sut.ExtractAsync().ToListAsync(); + var report = sut.GetProgressReport(); + + Assert.Equal(5, report.TotalItemCount); + Assert.Equal(100d, report.PercentComplete!.Value); + } + + [Fact] + public void CreateProgressReport_for_an_enumerator_source_has_no_TotalItemCount() + { + var sut = new ExposedTestExtractor(Enumerable.Range(1, 3).GetEnumerator()); + + var report = sut.GetProgressReport(); + + Assert.Null(report.TotalItemCount); + } + private sealed class ExposedTestExtractor : TestExtractor where T : notnull { @@ -58,6 +80,11 @@ public ExposedTestExtractor(IEnumerable items) { } + public ExposedTestExtractor(IEnumerator enumerator) + : base(enumerator) + { + } + public Report GetProgressReport() => CreateProgressReport(); } }