310 lines
9.2 KiB
ObjectPascal
310 lines
9.2 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]
|
|
TMycTaskFactoryTests = class(TObject)
|
|
private
|
|
FFactory: IMycTaskFactory;
|
|
procedure TestProcExecute(var executed: Boolean; signal: IMycLatch); // Parameter type is IMycLatch
|
|
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.TestProcExecute(var executed: Boolean; signal: IMycLatch);
|
|
begin
|
|
executed := True;
|
|
if Assigned(signal) then
|
|
signal.Notify;
|
|
end;
|
|
|
|
procedure TMycTaskFactoryTests.TestFactoryCreationAndTeardown;
|
|
var
|
|
threadCount: Integer;
|
|
begin
|
|
Assert.IsNotNull(FFactory, 'Factory should be created');
|
|
threadCount := FFactory.ThreadCount;
|
|
Assert.AreEqual(TThread.ProcessorCount, threadCount, 'Thread count should match processor count');
|
|
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;
|
|
jobCompletedLatch: IMycLatch;
|
|
subscriber: IMycSubscriber;
|
|
startCount: Integer;
|
|
dummyWaitLatch: IMycLatch;
|
|
begin
|
|
jobExecuted := False;
|
|
startCount := 3;
|
|
jobCompletedLatch := TMycLatch.CreateLatch(1); // Changed from Signals.CreateLatch
|
|
dummyWaitLatch := 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_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 dummyWaitLatch.Notify; end);
|
|
FFactory.WaitFor(dummyWaitLatch.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); // Changed from Signals.CreateLatch
|
|
|
|
FFactory.Run(0,
|
|
procedure
|
|
var
|
|
dummy: Integer;
|
|
begin
|
|
dummy := 0;
|
|
raise TMycTaskFactory.ETaskException.Create('Test Exception From Job');
|
|
// jobDoneLatch.Notify; // This line will not be reached
|
|
end);
|
|
|
|
FFactory.Run(0, procedure begin jobDoneLatch.Notify; end);
|
|
FFactory.WaitFor(jobDoneLatch.State);
|
|
|
|
Assert.WillRaise(
|
|
procedure
|
|
begin
|
|
(FFactory as TMycTaskFactory).HandleException;
|
|
end,
|
|
TMycTaskFactory.ETaskException,
|
|
'HandleException 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); // Changed from Signals.CreateLatch
|
|
dummyStateToWaitFor := TMycLatch.CreateLatch(1).State; // Changed from Signals.CreateLatch
|
|
|
|
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.
|