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(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.