|
|
|
@ -11,11 +11,8 @@ using Testably.Abstractions.Testing; |
|
|
|
|
|
|
|
|
|
|
|
namespace ErsatzTV.Core.Tests.Scheduling.ClassicScheduling; |
|
|
|
namespace ErsatzTV.Core.Tests.Scheduling.ClassicScheduling; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
// unit-test version of the "fast forward a playout" debugging endpoint that used to live in
|
|
|
|
/// Unit-test version of the "fast forward a playout" debugging endpoint that used to live in
|
|
|
|
// ChannelController
|
|
|
|
/// ChannelController: run hourly continue builds across several days and assert invariants at
|
|
|
|
|
|
|
|
/// every step.
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
[TestFixture] |
|
|
|
[TestFixture] |
|
|
|
public class MultiDayBuildTests : PlayoutBuilderTestBase |
|
|
|
public class MultiDayBuildTests : PlayoutBuilderTestBase |
|
|
|
{ |
|
|
|
{ |
|
|
|
@ -25,13 +22,11 @@ public class MultiDayBuildTests : PlayoutBuilderTestBase |
|
|
|
[Test] |
|
|
|
[Test] |
|
|
|
public async Task Continue_Should_Keep_A_Checkpoint_For_The_Current_Day() |
|
|
|
public async Task Continue_Should_Keep_A_Checkpoint_For_The_Current_Day() |
|
|
|
{ |
|
|
|
{ |
|
|
|
// 6 hour movies scheduled 3 at a time, so a block spans 18 hours and regularly
|
|
|
|
// 6 hour movies 3 at a time, so an 18 hour block regularly crosses midnight
|
|
|
|
// crosses midnight - which is expected and intended
|
|
|
|
|
|
|
|
(PlayoutBuilder builder, Playout playout, PlayoutReferenceData referenceData) = MultipleTestData( |
|
|
|
(PlayoutBuilder builder, Playout playout, PlayoutReferenceData referenceData) = MultipleTestData( |
|
|
|
TimeSpan.FromHours(6), |
|
|
|
TimeSpan.FromHours(6), |
|
|
|
multipleCount: "3"); |
|
|
|
multipleCount: "3"); |
|
|
|
|
|
|
|
|
|
|
|
// a playout created at 20:00, then built every hour for a week
|
|
|
|
|
|
|
|
DateTimeOffset start = LocalTime(20); |
|
|
|
DateTimeOffset start = LocalTime(20); |
|
|
|
var missing = new List<string>(); |
|
|
|
var missing = new List<string>(); |
|
|
|
|
|
|
|
|
|
|
|
@ -48,15 +43,13 @@ public class MultiDayBuildTests : PlayoutBuilderTestBase |
|
|
|
|
|
|
|
|
|
|
|
result.IsRight.ShouldBeTrue($"build failed at {now:yyyy-MM-dd HH:mm}"); |
|
|
|
result.IsRight.ShouldBeTrue($"build failed at {now:yyyy-MM-dd HH:mm}"); |
|
|
|
|
|
|
|
|
|
|
|
// a build never writes a checkpoint for its own first day, and doesn't need to:
|
|
|
|
// no checkpoint is written for a build's own first day, and none is needed: there is no
|
|
|
|
// there is no earlier state to restore, so refreshing starts from index 0 and
|
|
|
|
// earlier state, so refreshing from index 0 reproduces the same schedule
|
|
|
|
// reproduces the same schedule
|
|
|
|
|
|
|
|
if (now.Date == start.Date) |
|
|
|
if (now.Date == start.Date) |
|
|
|
{ |
|
|
|
{ |
|
|
|
continue; |
|
|
|
continue; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// from here on, a refresh has to have somewhere to rewind to
|
|
|
|
|
|
|
|
bool hasCheckpointForToday = playout.ProgramScheduleAnchors.Any(a => |
|
|
|
bool hasCheckpointForToday = playout.ProgramScheduleAnchors.Any(a => |
|
|
|
a.AnchorDateOffset.HasValue && a.AnchorDateOffset.Value.Date == now.Date); |
|
|
|
a.AnchorDateOffset.HasValue && a.AnchorDateOffset.Value.Date == now.Date); |
|
|
|
|
|
|
|
|
|
|
|
@ -74,8 +67,7 @@ public class MultiDayBuildTests : PlayoutBuilderTestBase |
|
|
|
[Test] |
|
|
|
[Test] |
|
|
|
public async Task Refresh_Should_Not_Reset_Collection_Progress() |
|
|
|
public async Task Refresh_Should_Not_Reset_Collection_Progress() |
|
|
|
{ |
|
|
|
{ |
|
|
|
// a collection large enough that the index can't wrap over the whole run, so the
|
|
|
|
// large enough that the index can't wrap, so it measures progress directly
|
|
|
|
// index is a straightforward measure of how much progress has been made
|
|
|
|
|
|
|
|
(PlayoutBuilder builder, Playout playout, PlayoutReferenceData referenceData) = MultipleTestData( |
|
|
|
(PlayoutBuilder builder, Playout playout, PlayoutReferenceData referenceData) = MultipleTestData( |
|
|
|
TimeSpan.FromHours(6), |
|
|
|
TimeSpan.FromHours(6), |
|
|
|
multipleCount: "3", |
|
|
|
multipleCount: "3", |
|
|
|
@ -83,8 +75,7 @@ public class MultiDayBuildTests : PlayoutBuilderTestBase |
|
|
|
|
|
|
|
|
|
|
|
DateTimeOffset start = LocalTime(20); |
|
|
|
DateTimeOffset start = LocalTime(20); |
|
|
|
|
|
|
|
|
|
|
|
// three weeks of hourly builds, so that progress is far enough along to be clearly
|
|
|
|
// long enough that progress is clearly distinguishable from a playout that restarted
|
|
|
|
// distinguishable from a playout that restarted at the beginning
|
|
|
|
|
|
|
|
for (var hour = 0; hour < 24 * 21; hour++) |
|
|
|
for (var hour = 0; hour < 24 * 21; hour++) |
|
|
|
{ |
|
|
|
{ |
|
|
|
await builder.Build( |
|
|
|
await builder.Build( |
|
|
|
@ -107,9 +98,8 @@ public class MultiDayBuildTests : PlayoutBuilderTestBase |
|
|
|
|
|
|
|
|
|
|
|
result.IsRight.ShouldBeTrue(); |
|
|
|
result.IsRight.ShouldBeTrue(); |
|
|
|
|
|
|
|
|
|
|
|
// a refresh rewinds to the start of today, so the index legitimately moves back by
|
|
|
|
// rewinding to the start of today legitimately moves the index back by up to a day; a refresh
|
|
|
|
// up to a day. a refresh that lost its checkpoints restarts at 0 and only advances
|
|
|
|
// that lost its checkpoints lands an order of magnitude lower
|
|
|
|
// through the build window, landing an order of magnitude lower
|
|
|
|
|
|
|
|
int after = HeadIndex(playout); |
|
|
|
int after = HeadIndex(playout); |
|
|
|
after.ShouldBeGreaterThan( |
|
|
|
after.ShouldBeGreaterThan( |
|
|
|
before / 2, |
|
|
|
before / 2, |
|
|
|
@ -119,8 +109,7 @@ public class MultiDayBuildTests : PlayoutBuilderTestBase |
|
|
|
[Test] |
|
|
|
[Test] |
|
|
|
public async Task Continue_Should_Keep_A_Checkpoint_For_Every_Day_In_The_Build_Window() |
|
|
|
public async Task Continue_Should_Keep_A_Checkpoint_For_Every_Day_In_The_Build_Window() |
|
|
|
{ |
|
|
|
{ |
|
|
|
// shaped like the playouts that were missing checkpoints in a real user database: a week
|
|
|
|
// shaped like the playouts that were missing checkpoints in a real user database
|
|
|
|
// long build window, shuffled schedule items, and blocks of five ~100 minute items
|
|
|
|
|
|
|
|
(PlayoutBuilder builder, Playout playout, PlayoutReferenceData referenceData) = MultipleTestData( |
|
|
|
(PlayoutBuilder builder, Playout playout, PlayoutReferenceData referenceData) = MultipleTestData( |
|
|
|
TimeSpan.FromMinutes(100), |
|
|
|
TimeSpan.FromMinutes(100), |
|
|
|
multipleCount: "5", |
|
|
|
multipleCount: "5", |
|
|
|
@ -145,9 +134,7 @@ public class MultiDayBuildTests : PlayoutBuilderTestBase |
|
|
|
|
|
|
|
|
|
|
|
result.IsRight.ShouldBeTrue($"build failed at {now:yyyy-MM-dd HH:mm}"); |
|
|
|
result.IsRight.ShouldBeTrue($"build failed at {now:yyyy-MM-dd HH:mm}"); |
|
|
|
|
|
|
|
|
|
|
|
// every day the playout covers needs a checkpoint, so that a refresh on any of them has
|
|
|
|
// every day covered needs a checkpoint, except the build's own first day; past days are pruned
|
|
|
|
// somewhere to rewind to. the build's own first day is the one exception, and days
|
|
|
|
|
|
|
|
// already in the past are pruned
|
|
|
|
|
|
|
|
DateTime firstExpected = now.Date > start.Date ? now.Date : start.Date.AddDays(1); |
|
|
|
DateTime firstExpected = now.Date > start.Date ? now.Date : start.Date.AddDays(1); |
|
|
|
DateTime lastExpected = now.AddDays(WeekOfDaysToBuild).Date; |
|
|
|
DateTime lastExpected = now.AddDays(WeekOfDaysToBuild).Date; |
|
|
|
|
|
|
|
|
|
|
|
@ -263,14 +250,9 @@ public class MultiDayBuildTests : PlayoutBuilderTestBase |
|
|
|
return (builder, playout, referenceData); |
|
|
|
return (builder, playout, referenceData); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
// anchor dates are compared in machine-local time, so the test clock has to be local too or day
|
|
|
|
/// Anchor dates are compared in machine-local time
|
|
|
|
// boundaries won't line up. that means running in whatever zone the machine is in, hence mid-June,
|
|
|
|
/// (<see cref="PlayoutProgramScheduleAnchor.AnchorDateOffset" />), so the test clock has to be local
|
|
|
|
// where no zone has a DST transition
|
|
|
|
/// too, or day boundaries won't line up. That means these tests run in whatever zone the machine is
|
|
|
|
|
|
|
|
/// in - UTC on CI, something else locally - so the window below is deliberately placed in mid-June,
|
|
|
|
|
|
|
|
/// where no zone has a DST transition. Day-boundary behaviour across a DST change is a separate
|
|
|
|
|
|
|
|
/// concern and wants its own test.
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
private static DateTimeOffset LocalTime(int hour) |
|
|
|
private static DateTimeOffset LocalTime(int hour) |
|
|
|
{ |
|
|
|
{ |
|
|
|
var date = new DateTime(2025, 6, 10, 0, 0, 0, DateTimeKind.Unspecified); |
|
|
|
var date = new DateTime(2025, 6, 10, 0, 0, 0, DateTimeKind.Unspecified); |
|
|
|
|