procedure TestFindScheduleEnd(); { Reference implementation: brute-force simulation } function SimulateFindScheduleEnd( Schedule: TSchedule; MinTotal, MaxTotal, MaxDuration: Int64; out Entry: PScheduleEntry ): Int64; var T: Int64; I: Integer; RunningTotal: Int64; BestPosIdx, BestNegIdx: Integer; BestPosDelta, BestNegDelta: Int32; AnyFired: Boolean; begin RunningTotal := 0; Entry := nil; { Check initial state before any events } if (RunningTotal > MaxTotal) or (RunningTotal < MinTotal) then begin { Shouldn't happen for well-formed inputs where 0 is in range, but handle gracefully: return time 0 with nil entry } Entry := nil; Result := 0; Exit; end; for T := 1 to MaxDuration do begin BestPosIdx := -1; BestNegIdx := -1; BestPosDelta := 0; BestNegDelta := 0; AnyFired := False; for I := 0 to High(Schedule) do begin { Entry fires at times: TimeOrigin+Period, TimeOrigin+2*Period, ... i.e. when (T - TimeOrigin) mod Period = 0 and T > TimeOrigin Since TimeOrigin <= 0, T >= 1 > TimeOrigin always. Fire condition: (T - Schedule[I].TimeOrigin) mod Schedule[I].Period = 0 } if ((T - Schedule[I].TimeOrigin) mod Schedule[I].Period) = 0 then begin RunningTotal := RunningTotal + Schedule[I].Delta; AnyFired := True; { Track most positive delta (for MaxTotal breach) } if Schedule[I].Delta > 0 then begin if (BestPosIdx = -1) or (Schedule[I].Delta > BestPosDelta) then begin BestPosIdx := I; BestPosDelta := Schedule[I].Delta; end; end; { Track most negative delta (for MinTotal breach) } if Schedule[I].Delta < 0 then begin if (BestNegIdx = -1) or (Schedule[I].Delta < BestNegDelta) then begin BestNegIdx := I; BestNegDelta := Schedule[I].Delta; end; end; end; end; { Check thresholds after applying all deltas for this time step } if RunningTotal > MaxTotal then begin if BestPosIdx >= 0 then Entry := @Schedule[BestPosIdx] else Entry := nil; Result := T; Exit; end; if RunningTotal < MinTotal then begin if BestNegIdx >= 0 then Entry := @Schedule[BestNegIdx] else Entry := nil; Result := T; Exit; end; end; Entry := nil; Result := MaxDuration; end; procedure RunTest( const TestName: string; Schedule: TSchedule; MinTotal, MaxTotal, MaxDuration: Int64 ); var ExpectedResult, ActualResult: Int64; ExpectedEntry, ActualEntry: PScheduleEntry; ExpectedIdx, ActualIdx: Integer; I: Integer; begin ExpectedResult := SimulateFindScheduleEnd(Schedule, MinTotal, MaxTotal, MaxDuration, ExpectedEntry); ActualResult := FindScheduleEnd(Schedule, MinTotal, MaxTotal, MaxDuration, ActualEntry); { Find indices for diagnostic output } ExpectedIdx := -1; ActualIdx := -1; if ExpectedEntry <> nil then for I := 0 to High(Schedule) do if @Schedule[I] = ExpectedEntry then ExpectedIdx := I; if ActualEntry <> nil then for I := 0 to High(Schedule) do if @Schedule[I] = ActualEntry then ActualIdx := I; if ActualResult <> ExpectedResult then begin WriteLn('FAIL [', TestName, ']: result mismatch'); WriteLn(' Expected time: ', ExpectedResult); WriteLn(' Actual time: ', ActualResult); WriteLn(' MinTotal=', MinTotal, ' MaxTotal=', MaxTotal, ' MaxDuration=', MaxDuration); WriteLn(' Schedule entries: ', Length(Schedule)); for I := 0 to High(Schedule) do WriteLn(' [', I, '] TimeOrigin=', Schedule[I].TimeOrigin, ' Period=', Schedule[I].Period, ' Delta=', Schedule[I].Delta); Halt(1); end; { Both nil => OK } if (ExpectedEntry = nil) and (ActualEntry = nil) then Exit; { One nil, one not => fail } if (ExpectedEntry = nil) <> (ActualEntry = nil) then begin WriteLn('FAIL [', TestName, ']: Entry nil mismatch'); WriteLn(' Expected entry index: ', ExpectedIdx); WriteLn(' Actual entry index: ', ActualIdx); WriteLn(' At time: ', ActualResult); for I := 0 to High(Schedule) do WriteLn(' [', I, '] TimeOrigin=', Schedule[I].TimeOrigin, ' Period=', Schedule[I].Period, ' Delta=', Schedule[I].Delta); Halt(1); end; { Both non-nil: compare the deltas (same delta value = correct winner, pointer identity confirms it points into our Schedule array) } if ActualEntry^.Delta <> ExpectedEntry^.Delta then begin WriteLn('FAIL [', TestName, ']: Entry delta mismatch'); WriteLn(' Expected entry index: ', ExpectedIdx, ' Delta=', ExpectedEntry^.Delta); WriteLn(' Actual entry index: ', ActualIdx, ' Delta=', ActualEntry^.Delta); WriteLn(' At time: ', ActualResult); for I := 0 to High(Schedule) do WriteLn(' [', I, '] TimeOrigin=', Schedule[I].TimeOrigin, ' Period=', Schedule[I].Period, ' Delta=', Schedule[I].Delta); Halt(1); end; { When deltas are equal, tiebreaker is order in Schedule => compare indices } if ActualIdx <> ExpectedIdx then begin WriteLn('FAIL [', TestName, ']: Entry tiebreaker mismatch'); WriteLn(' Expected entry index: ', ExpectedIdx, ' Delta=', ExpectedEntry^.Delta); WriteLn(' Actual entry index: ', ActualIdx, ' Delta=', ActualEntry^.Delta); WriteLn(' At time: ', ActualResult); for I := 0 to High(Schedule) do WriteLn(' [', I, '] TimeOrigin=', Schedule[I].TimeOrigin, ' Period=', Schedule[I].Period, ' Delta=', Schedule[I].Delta); Halt(1); end; end; function MakeEntry(TimeOrigin, Period, Delta: Int32): TScheduleEntry; begin Result.TimeOrigin := TimeOrigin; Result.Period := Period; Result.Delta := Delta; Result.Reference := nil; end; var S: TSchedule; I, J: Integer; E: TScheduleEntry; begin { ------------------------------------------------------------------ } { 1. Empty schedule: always stable, returns MaxDuration } { ------------------------------------------------------------------ } SetLength(S, 0); RunTest('Empty schedule', S, -1000, 1000, 100); { ------------------------------------------------------------------ } { 2. Single entry, positive delta, never breaches } { ------------------------------------------------------------------ } SetLength(S, 1); S[0] := MakeEntry(0, 10, 5); { fires at t=10,20,30,... } RunTest('Single +delta, no breach', S, -1000, 1000, 50); { ------------------------------------------------------------------ } { 3. Single entry, positive delta, breaches MaxTotal } { ------------------------------------------------------------------ } SetLength(S, 1); S[0] := MakeEntry(0, 1, 10); { fires every step } RunTest('Single +delta, breach max', S, -1000, 50, 100); { ------------------------------------------------------------------ } { 4. Single entry, negative delta, breaches MinTotal } { ------------------------------------------------------------------ } SetLength(S, 1); S[0] := MakeEntry(0, 1, -10); RunTest('Single -delta, breach min', S, -50, 1000, 100); { ------------------------------------------------------------------ } { 5. Two entries that cancel: stable forever } { ------------------------------------------------------------------ } SetLength(S, 2); S[0] := MakeEntry(0, 5, 10); S[1] := MakeEntry(0, 5, -10); RunTest('Two entries cancel same period', S, -1, 1, 200); { ------------------------------------------------------------------ } { 6. Two entries same period, fire together, net zero } { ------------------------------------------------------------------ } SetLength(S, 2); S[0] := MakeEntry(0, 3, 7); S[1] := MakeEntry(0, 3, -7); RunTest('Net-zero pair', S, -100, 100, 60); { ------------------------------------------------------------------ } { 7. Two entries with different periods, both positive — breach max } { ------------------------------------------------------------------ } SetLength(S, 2); S[0] := MakeEntry(0, 3, 5); S[1] := MakeEntry(0, 7, 5); RunTest('Two +delta diff periods breach max', S, -10000, 30, 500); { ------------------------------------------------------------------ } { 8. Two entries with different periods — oscillating, stable } { ------------------------------------------------------------------ } SetLength(S, 2); S[0] := MakeEntry(0, 4, 3); S[1] := MakeEntry(0, 6, -4); RunTest('Two entries oscillate stable', S, -50, 50, 300); { ------------------------------------------------------------------ } { 9. TimeOrigin not zero (negative) } { ------------------------------------------------------------------ } SetLength(S, 1); S[0] := MakeEntry(-3, 5, 10); { fires at t=2,7,12,... } RunTest('Negative TimeOrigin', S, -1000, 45, 100); { ------------------------------------------------------------------ } { 10. TimeOrigin = -(Period-1), fires at t=1 } { ------------------------------------------------------------------ } SetLength(S, 1); S[0] := MakeEntry(-9, 10, 50); { fires at t=1,11,21,... } RunTest('TimeOrigin=-(Period-1), fires at t=1', S, -1000, 99, 50); { ------------------------------------------------------------------ } { 11. MaxDuration=0 — returns immediately } { ------------------------------------------------------------------ } SetLength(S, 1); S[0] := MakeEntry(0, 1, 999); RunTest('MaxDuration=0', S, -1000, 1000, 0); { ------------------------------------------------------------------ } { 12. MaxDuration=1 — check only t=1 } { ------------------------------------------------------------------ } SetLength(S, 1); S[0] := MakeEntry(0, 1, 5); RunTest('MaxDuration=1, no breach', S, -100, 100, 1); SetLength(S, 1); S[0] := MakeEntry(0, 1, 5); RunTest('MaxDuration=1, exact breach', S, -100, 4, 1); { ------------------------------------------------------------------ } { 13. Tiebreaker: two entries with equal positive delta at same step } { First one in Schedule should be returned } { ------------------------------------------------------------------ } SetLength(S, 2); S[0] := MakeEntry(0, 1, 10); S[1] := MakeEntry(0, 1, 10); RunTest('Tiebreaker +delta, first wins', S, -1000, 15, 50); { ------------------------------------------------------------------ } { 14. Tiebreaker: two entries with equal negative delta } { ------------------------------------------------------------------ } SetLength(S, 2); S[0] := MakeEntry(0, 1, -10); S[1] := MakeEntry(0, 1, -10); RunTest('Tiebreaker -delta, first wins', S, -15, 1000, 50); { ------------------------------------------------------------------ } { 15. Mixed deltas at violation step: most positive wins for max } { ------------------------------------------------------------------ } SetLength(S, 3); S[0] := MakeEntry(0, 1, 3); S[1] := MakeEntry(0, 1, 10); { this should be reported for MaxTotal } S[2] := MakeEntry(0, 1, 1); RunTest('Most +delta reported for max breach', S, -1000, 13, 50); { ------------------------------------------------------------------ } { 16. Mixed deltas: most negative wins for min breach } { ------------------------------------------------------------------ } SetLength(S, 3); S[0] := MakeEntry(0, 1, -3); S[1] := MakeEntry(0, 1, -10); S[2] := MakeEntry(0, 1, -1); RunTest('Most -delta reported for min breach', S, -13, 1000, 50); { ------------------------------------------------------------------ } { 17. Entries not all firing at breach step } { ------------------------------------------------------------------ } SetLength(S, 3); S[0] := MakeEntry(0, 2, 5); { fires at even times } S[1] := MakeEntry(0, 3, 20); { fires at t=3,6,9,... } S[2] := MakeEntry(-1, 4, 1); { fires at t=3,7,11,... — TimeOrigin=-1, Period=4 } RunTest('Entries fire at different times', S, -1000, 30, 100); { ------------------------------------------------------------------ } { 18. Large number of entries (stress test), all small positive delta } { Accumulate and breach } { ------------------------------------------------------------------ } SetLength(S, 100); for I := 0 to 99 do S[I] := MakeEntry(0, 1, 1); { all fire every step, total +100/step } RunTest('100 entries +1 each per step', S, -1000000, 4999, 1000); { ------------------------------------------------------------------ } { 19. Large schedule with varied periods — stability check } { ------------------------------------------------------------------ } SetLength(S, 50); for I := 0 to 49 do begin { Pair up: even index +delta, odd index -delta, same period => net zero within each pair } if (I mod 2) = 0 then S[I] := MakeEntry(0, I div 2 + 1, I + 1) else S[I] := MakeEntry(0, (I-1) div 2 + 1, -(I)); end; RunTest('50 entries paired cancel', S, -1000000, 1000000, 500); { ------------------------------------------------------------------ } { 20. Entry fires at t=1, breach immediately } { ------------------------------------------------------------------ } SetLength(S, 1); S[0] := MakeEntry(-4, 5, 100); { fires at t=1,6,11,... } RunTest('Breach at t=1', S, -1000, 99, 200); { ------------------------------------------------------------------ } { 21. LCM period schedule: breach happens only at LCM step } { ------------------------------------------------------------------ } SetLength(S, 3); S[0] := MakeEntry(0, 2, 1); S[1] := MakeEntry(0, 3, 1); S[2] := MakeEntry(0, 5, 1); { all fire at t=30 (LCM), total += 3 there } RunTest('LCM breach', S, -1000, 19, 100); { ------------------------------------------------------------------ } { 22. Very long period — never fires within MaxDuration } { ------------------------------------------------------------------ } SetLength(S, 1); S[0] := MakeEntry(0, 10000, 999); RunTest('Period > MaxDuration, never fires', S, -1000, 1000, 100); { ------------------------------------------------------------------ } { 23. Multiple entries: one fires and barely does not breach } { ------------------------------------------------------------------ } SetLength(S, 2); S[0] := MakeEntry(0, 1, 5); S[1] := MakeEntry(0, 1, -5); RunTest('Net zero each step, tight range', S, -1, 1, 500); { ------------------------------------------------------------------ } { 24. Schedule sorted by TimeOrigin+Period (first entry fires later) } { Both fire at t=5; sorted so S[0] fires later than S[1] at t=3 } { ------------------------------------------------------------------ } SetLength(S, 2); S[0] := MakeEntry(-4, 5, 1); { first fires at t=1 } S[1] := MakeEntry( 0, 5, 100); { first fires at t=5 } RunTest('Sorted schedule, second entry causes breach', S, -1000, 100, 100); { ------------------------------------------------------------------ } { 25. Negative delta only, check Entry pointer is most negative } { ------------------------------------------------------------------ } SetLength(S, 4); S[0] := MakeEntry(0, 1, -1); S[1] := MakeEntry(0, 1, -50); S[2] := MakeEntry(0, 1, -2); S[3] := MakeEntry(0, 1, -3); RunTest('Most negative delta = S[1]', S, -55, 1000, 100); { ------------------------------------------------------------------ } { 26. Positive delta only at breach, but also zero deltas (no delta) } { ------------------------------------------------------------------ } SetLength(S, 3); S[0] := MakeEntry(0, 1, 0); { zero delta entries } S[1] := MakeEntry(0, 1, 0); S[2] := MakeEntry(0, 1, 10); RunTest('Zero delta entries ignored for Entry selection', S, -1000, 9, 50); { ------------------------------------------------------------------ } { 27. Breach exactly at MaxDuration } { ------------------------------------------------------------------ } SetLength(S, 1); S[0] := MakeEntry(0, 1, 1); { total = T at time T } RunTest('Breach exactly at MaxDuration', S, -1000, 99, 100); { ------------------------------------------------------------------ } { 28. No breach at MaxDuration (total reaches MaxTotal-1 exactly) } { ------------------------------------------------------------------ } SetLength(S, 1); S[0] := MakeEntry(0, 1, 1); RunTest('No breach, total == MaxTotal at MaxDuration', S, -1000, 100, 100); { ------------------------------------------------------------------ } { 29. Varied TimeOrigin values spread over range } { ------------------------------------------------------------------ } SetLength(S, 5); S[0] := MakeEntry( 0, 10, 3); S[1] := MakeEntry( -3, 10, 5); { fires at t=7,17,... } S[2] := MakeEntry( -7, 10, 2); { fires at t=3,13,... } S[3] := MakeEntry( -9, 10, -4); { fires at t=1,11,... } S[4] := MakeEntry( -1, 10, 1); { fires at t=9,19,... } RunTest('Varied TimeOrigins', S, -1000, 30, 200); { ------------------------------------------------------------------ } { 30. Stress: 200 entries with varied periods, check stability } { ------------------------------------------------------------------ } SetLength(S, 200); for I := 0 to 199 do begin { Alternate +/- with increasing magnitude but paired to cancel } J := I div 2 + 1; { period } if (I mod 2) = 0 then S[I] := MakeEntry(0, J, J) else S[I] := MakeEntry(0, J, -J); end; RunTest('200 paired-cancel entries stress', S, -1000000, 1000000, 1000); { ------------------------------------------------------------------ } { 31. Single entry delta=0: stable, returns MaxDuration } { ------------------------------------------------------------------ } SetLength(S, 1); S[0] := MakeEntry(0, 5, 0); RunTest('Delta=0 single entry', S, -100, 100, 50); { ------------------------------------------------------------------ } { 32. Check that entry pointed to is in our Schedule (pointer valid) } { Large delta, immediate breach } { ------------------------------------------------------------------ } SetLength(S, 1); S[0] := MakeEntry(0, 1, 1000000); RunTest('Immediate large breach, entry valid', S, -1, 999999, 10); { ------------------------------------------------------------------ } { 33. Alternating breach direction: first min, make sure Entry right } { ------------------------------------------------------------------ } SetLength(S, 3); S[0] := MakeEntry(0, 1, -100); S[1] := MakeEntry(0, 1, 50); S[2] := MakeEntry(0, 1, 1); RunTest('First breach is min, most -delta is S[0]', S, -50, 1000, 50); { ------------------------------------------------------------------ } { 34. Period=1 entries, breach both sides alternately — first wins } { ------------------------------------------------------------------ } SetLength(S, 2); S[0] := MakeEntry(0, 2, 200); { fires at even times: +200 } S[1] := MakeEntry(0, 2, -200); { fires at even times: -200 } { net 0 every step — stable } RunTest('Even-fire cancel stable', S, -1, 1, 200); { ------------------------------------------------------------------ } { 35. Many entries, only one fires at the breach step } { ------------------------------------------------------------------ } SetLength(S, 10); for I := 0 to 9 do S[I] := MakeEntry(0, 100, 5); { all fire at t=100,200,... } { all 10 fire at t=100: total = 50; breach if MaxTotal < 50 } RunTest('10 entries all fire at t=100', S, -1000, 49, 200); { ------------------------------------------------------------------ } { 36. Entry with Period=1, TimeOrigin=-0 fires from t=1 } { ------------------------------------------------------------------ } SetLength(S, 1); S[0] := MakeEntry(0, 1, -3); RunTest('Period=1 neg delta fires every step', S, -7, 1000, 100); { ------------------------------------------------------------------ } { 37. Three entries, complex interleaving } { ------------------------------------------------------------------ } SetLength(S, 3); S[0] := MakeEntry( 0, 6, 10); S[1] := MakeEntry( -2, 6, 10); { fires at t=4,10,16,... } S[2] := MakeEntry( -4, 6, -15); { fires at t=2,8,14,... } RunTest('Three-entry interleave', S, -100, 100, 300); { ------------------------------------------------------------------ } { 38. All entries fire on the same step, tiebreaker matters } { ------------------------------------------------------------------ } SetLength(S, 5); for I := 0 to 4 do S[I] := MakeEntry(0, 7, 10); { all fire at t=7,14,... } { all same delta=10; tiebreaker => S[0] } RunTest('5 same-delta entries tiebreaker', S, -1000, 49, 100); { ------------------------------------------------------------------ } { 39. Period 1 with accumulation, breach at expected step } { ------------------------------------------------------------------ } SetLength(S, 1); S[0] := MakeEntry(0, 1, 7); { total at t=k is 7k; breach MaxTotal=100 when 7k > 100 => k=15 } RunTest('Period=1 delta=7 breach at t=15', S, -1000000, 100, 1000); { ------------------------------------------------------------------ } { 40. Large mixed schedule with net drift — breach eventually } { ------------------------------------------------------------------ } SetLength(S, 50); for I := 0 to 49 do begin if I < 25 then S[I] := MakeEntry(0, I + 1, 3) { small positive } else S[I] := MakeEntry(0, I - 24, -2); { slightly smaller negative } end; { net positive drift — should breach MaxTotal eventually } RunTest('50-entry net positive drift', S, -100000, 5000, 2000); end;