diff --git a/Source/LibationUiBase/ProcessQueue/ProcessQueueViewModel.cs b/Source/LibationUiBase/ProcessQueue/ProcessQueueViewModel.cs
index e6104ab5..3a25b428 100644
--- a/Source/LibationUiBase/ProcessQueue/ProcessQueueViewModel.cs
+++ b/Source/LibationUiBase/ProcessQueue/ProcessQueueViewModel.cs
@@ -88,12 +88,22 @@ public class ProcessQueueViewModel : ReactiveObject
}
private int _maxConcurrentDownloads;
+ ///
+ /// What this machine can usefully manage. Overridable so that tests of the dispatch loop pin it
+ /// instead of inheriting the host's core count - otherwise a test asking for three books at once
+ /// passes on a developer's machine and fails on a two-core CI runner, having tested the runner
+ /// rather than the loop.
+ ///
+ internal int? MachineCeilingOverride { get; set; }
+
+ private int MachineCeiling => MachineCeilingOverride ?? Configuration.MaxAllowedConcurrentDownloads;
+
///
/// How many books actually run at once: what the user asked for, held down to what this machine
/// can usefully manage. Applied here, at the point of use, so the stored setting is left alone.
///
private int EffectiveConcurrentDownloads
- => Math.Clamp(MaxConcurrentDownloads, Configuration.MinConcurrentDownloads, Configuration.MaxAllowedConcurrentDownloads);
+ => Math.Clamp(MaxConcurrentDownloads, Configuration.MinConcurrentDownloads, MachineCeiling);
public bool AutoScrollQueue { get => field; set { RaiseAndSetIfChanged(ref field, value); Configuration.Instance.AutoScrollQueue = value; } }
/// Exposed so UI controls can bind their spinner bounds rather than hardcoding them.
diff --git a/Source/_Tests/LibationUiBase.Tests/ProcessQueueDispatchTests.cs b/Source/_Tests/LibationUiBase.Tests/ProcessQueueDispatchTests.cs
index 09a72a0e..d12a6296 100644
--- a/Source/_Tests/LibationUiBase.Tests/ProcessQueueDispatchTests.cs
+++ b/Source/_Tests/LibationUiBase.Tests/ProcessQueueDispatchTests.cs
@@ -118,10 +118,16 @@ public class ProcessQueueDispatchTests
}
}
+ ///
+ /// The setting under test. Machine capability is pinned to the same number, because the loop
+ /// clamps the setting by it: left to , a test asking for
+ /// three books at once would quietly start two on a small CI runner, wait out its patience and
+ /// fail - having measured the runner rather than the loop.
+ ///
private static (ProcessQueueViewModel Queue, FakeBooks Books) NewQueue(int atOnce)
{
var books = new FakeBooks();
- var queue = new ProcessQueueViewModel { MaxConcurrentDownloads = atOnce };
+ var queue = new ProcessQueueViewModel { MaxConcurrentDownloads = atOnce, MachineCeilingOverride = atOnce };
queue.ProcessBookHandler = books.Handle;
return (queue, books);
}
@@ -304,4 +310,40 @@ public class ProcessQueueDispatchTests
Assert.AreEqual(0, queue.QueuedCount, "Cancel All left books queued.");
Assert.AreEqual(2, books.Started.Count, "Cancel All did not stop new books from starting.");
}
+
+ [TestMethod]
+ public async Task a_machine_smaller_than_the_setting_holds_the_loop_down_without_changing_the_setting()
+ {
+ var books = new FakeBooks();
+ var queue = new ProcessQueueViewModel { MaxConcurrentDownloads = 8, MachineCeilingOverride = 2 };
+ queue.ProcessBookHandler = books.Handle;
+
+ queue.AddToQueue([Book("A"), Book("B"), Book("C"), Book("D")]);
+
+ await books.WaitForStarted(2);
+ await Task.Delay(100);
+ Assert.AreEqual(2, books.Started.Count, "The machine ceiling did not hold the loop down.");
+
+ // The whole point of clamping here rather than on the way in: what the user asked for
+ // survives being opened on a machine that cannot deliver it.
+ Assert.AreEqual(8, queue.MaxConcurrentDownloads, "The stored setting was rewritten to what the machine could manage.");
+
+ books.FinishAll("A", "B");
+ await books.WaitForStarted(4);
+ books.FinishAll("C", "D");
+
+ await RunToCompletion(queue);
+ Assert.AreEqual(2, books.HighWaterMark, "More books ran at once than the machine allows.");
+ }
+
+ [TestMethod]
+ public void the_hint_says_what_the_machine_will_do_and_is_silent_when_it_can_keep_up()
+ {
+ var queue = new ProcessQueueViewModel { MaxConcurrentDownloads = 8, MachineCeilingOverride = 2 };
+ Assert.AreEqual("(2 on this machine)", queue.ConcurrencyHint);
+
+ // Nothing to say once the machine can deliver what was asked for.
+ queue.MachineCeilingOverride = 10;
+ Assert.IsNull(queue.ConcurrencyHint);
+ }
}