Files
MycLib/Test/TestTasks.pas
T
Michael Schimmel 63484cd495 added Futures
2025-06-02 00:45:30 +02:00

296 lines
8.5 KiB
ObjectPascal

unit TestTasks;
interface
uses
DUnitX.TestFramework,
System.SysUtils,
System.Classes,
System.SyncObjs,
Myc.Core.Tasks,
Myc.Signals, // Uses TMycLatch.CreateLatch and TMycLatch.Null
Myc.Core.Atomic;
type
[TestFixture]
[IgnoreMemoryLeaks(true)]
TMycTaskFactoryTests = class(TObject)
private
FFactory: IMycTaskFactory;
public
[Setup]
procedure Setup;
[TearDown]
procedure TearDown;
[Test]
procedure TestFactoryCreationAndTeardown;
[Test]
procedure TestCreateThreadExecutesAndSignals;
[Test]
procedure TestRunImmediateJob;
[Test]
procedure TestRunDelayedJobExecutesAfterAllNotifies;
[Test]
procedure TestRunDelayedJobDoesNotExecutePrematurely;
[Test]
procedure TestWaitForAlreadySetState;
[Test]
procedure TestThreadInfoMethods;
[Test]
procedure TestExceptionPropagationFromJob;
[Test]
procedure TestRunJobAfterFactoryTeardown;
[Test]
procedure TestWaitForInWorkerThreadRaisesException;
end;
implementation
{ TMycTaskFactoryTests }
procedure TMycTaskFactoryTests.Setup;
begin
FFactory := TMycTaskFactory.Create;
end;
procedure TMycTaskFactoryTests.TearDown;
begin
if Assigned(FFactory) then
begin
FFactory.Teardown;
FFactory := nil;
end;
end;
procedure TMycTaskFactoryTests.TestFactoryCreationAndTeardown;
begin
Assert.IsNotNull(FFactory, 'Factory should be created');
end;
procedure TMycTaskFactoryTests.TestCreateThreadExecutesAndSignals;
var
executed: Boolean;
threadState: IMycState;
procWrapper: TProc;
begin
executed := False;
procWrapper := procedure
begin
executed := True;
end;
threadState := FFactory.CreateThread(procWrapper); // CreateThread in TaskFactory now uses TMycLatch.CreateLatch
Assert.IsNotNull(threadState, 'CreateThread should return a valid state object');
FFactory.WaitFor(threadState);
Assert.IsTrue(executed, 'Procedure in created thread should have executed');
end;
procedure TMycTaskFactoryTests.TestRunImmediateJob;
var
jobExecuted: Boolean;
jobCompletedLatch: IMycLatch;
begin
jobExecuted := False;
jobCompletedLatch := TMycLatch.CreateLatch(1); // Changed from Signals.CreateLatch
FFactory.Run(0,
procedure
begin
jobExecuted := True;
jobCompletedLatch.Notify;
end);
FFactory.WaitFor(jobCompletedLatch.State);
Assert.IsTrue(jobExecuted, 'Immediate job should have executed');
end;
procedure TMycTaskFactoryTests.TestRunDelayedJobExecutesAfterAllNotifies;
var
jobExecuted: Boolean;
jobCompletedLatch: IMycLatch;
subscriber: IMycSubscriber;
startCount: Integer;
begin
jobExecuted := False;
startCount := 2;
jobCompletedLatch := TMycLatch.CreateLatch(1); // Changed from Signals.CreateLatch
subscriber := FFactory.Run(startCount,
procedure
begin
jobExecuted := True;
jobCompletedLatch.Notify;
end);
Assert.IsNotNull(subscriber, 'Run should return a subscriber for delayed job');
// Use TMycLatch.Null for comparison
Assert.AreNotEqual<IMycSubscriber>(TMycLatch.Null, subscriber, 'Subscriber should not be TMycLatch.Null for StartCount > 0');
subscriber.Notify;
subscriber.Notify;
FFactory.WaitFor(jobCompletedLatch.State);
Assert.IsTrue(jobExecuted, 'Delayed job should execute after all notifications');
end;
procedure TMycTaskFactoryTests.TestRunDelayedJobDoesNotExecutePrematurely;
var
jobExecuted: Boolean;
jobCompleted: IMycLatch;
subscriber: IMycSubscriber;
startCount: Integer;
dummyWait: IMycLatch;
begin
jobExecuted := False;
startCount := 3;
jobCompleted := TMycLatch.CreateLatch(1); // Changed from Signals.CreateLatch
dummyWait := TMycLatch.CreateLatch(1); // Changed from Signals.CreateLatch
subscriber := FFactory.Run(startCount,
procedure
begin
jobExecuted := True;
jobCompleted.Notify;
end);
Assert.IsNotNull(subscriber, 'Run should return a subscriber for delayed job_P2');
subscriber.Notify;
Assert.IsFalse(jobExecuted, 'Job should not execute after one notification_P2');
subscriber.Notify;
Assert.IsFalse(jobExecuted, 'Job should not execute after two notifications_P2');
FFactory.Run(0, procedure begin dummyWait.Notify; end);
FFactory.WaitFor(dummyWait.State);
Assert.IsFalse(jobExecuted, 'Job should still not have executed before third notify_P2');
end;
procedure TMycTaskFactoryTests.TestWaitForAlreadySetState;
var
alreadySetLatch: IMycLatch;
startTime, endTime: Cardinal;
begin
// TMycLatch.CreateLatch(0) will return TMycLatch.Null
alreadySetLatch := TMycLatch.CreateLatch(0); // Changed from Signals.CreateLatch
Assert.IsTrue(alreadySetLatch.State.IsSet, 'Latch should be initially set');
startTime := TThread.GetTickCount;
FFactory.WaitFor(alreadySetLatch.State);
endTime := TThread.GetTickCount;
Assert.IsTrue((endTime - startTime) < 50, 'WaitFor on an already set state should not block significantly');
end;
procedure TMycTaskFactoryTests.TestThreadInfoMethods;
var
inMainThreadInJob: Boolean;
inWorkerThreadInJob: Boolean;
jobDoneLatch: IMycLatch;
begin
inMainThreadInJob := True;
inWorkerThreadInJob := False;
jobDoneLatch := TMycLatch.CreateLatch(1); // Changed from Signals.CreateLatch
Assert.IsTrue(FFactory.InMainThread, 'Test method itself should be in main thread');
Assert.IsFalse(FFactory.InWorkerThread, 'Test method itself should not be in a factory worker thread');
FFactory.Run(0,
procedure
begin
inMainThreadInJob := FFactory.InMainThread;
inWorkerThreadInJob := FFactory.InWorkerThread;
jobDoneLatch.Notify;
end);
FFactory.WaitFor(jobDoneLatch.State);
Assert.IsFalse(inMainThreadInJob, 'Job executed by factory should not be in main thread');
Assert.IsTrue(inWorkerThreadInJob, 'Job executed by factory should be in a worker thread');
end;
procedure TMycTaskFactoryTests.TestExceptionPropagationFromJob;
var
jobDoneLatch: IMycLatch;
begin
jobDoneLatch := TMycLatch.CreateLatch(1);
FFactory.Run(0,
procedure
begin
try
raise TMycTaskFactory.ETaskException.Create('Test Exception From Job');
finally
jobDoneLatch.Notify;
end;
end);
Assert.WillRaise(
procedure
begin
FFactory.WaitFor(jobDoneLatch.State);
end,
TMycTaskFactory.ETaskException,
'WaitFor should re-raise the exception from the job'
);
var noExceptionRaised: Boolean := True;
try
(FFactory as TMycTaskFactory).HandleException;
except
noExceptionRaised := False;
end;
Assert.IsTrue(noExceptionRaised, 'FException should be cleared after HandleException');
end;
procedure TMycTaskFactoryTests.TestRunJobAfterFactoryTeardown;
begin
FFactory.Teardown;
Assert.WillRaise(
procedure
begin
FFactory.Run(0, procedure begin end);
end,
TMycTaskFactory.ETaskException,
'Running a job on a torn-down factory should raise ETaskException'
);
end;
procedure TMycTaskFactoryTests.TestWaitForInWorkerThreadRaisesException;
var
exceptionCaughtInJob: Boolean;
jobDoneLatch: IMycLatch;
dummyStateToWaitFor: IMycState;
begin
exceptionCaughtInJob := False;
jobDoneLatch := TMycLatch.CreateLatch(1);
dummyStateToWaitFor := TMycLatch.CreateLatch(1).State;
FFactory.Run(0,
procedure
begin
try
FFactory.WaitFor(dummyStateToWaitFor);
except
on E: TMycTaskFactory.ETaskException do
begin
exceptionCaughtInJob := True;
end;
end;
jobDoneLatch.Notify;
end);
FFactory.WaitFor(jobDoneLatch.State);
Assert.IsTrue(exceptionCaughtInJob, 'WaitFor called within a worker thread job should raise ETaskException');
end;
initialization
TDUnitX.RegisterTestFixture(TMycTaskFactoryTests);
end.