unit TestTasks; interface uses DUnitX.TestFramework, System.SysUtils, System.Classes, System.SyncObjs, Myc.Core.Tasks, Myc.Signals, Myc.Core.Signals, 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 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 := TLatch.CreateLatch(1); // Changed from Signals.CreateLatch FFactory .Run( procedure begin jobExecuted := True; jobCompletedLatch.Notify; end) .Notify; FFactory.WaitFor(jobCompletedLatch.State); Assert.IsTrue(jobExecuted, 'Immediate job should have executed'); end; procedure TMycTaskFactoryTests.TestRunDelayedJobExecutesAfterAllNotifies; var jobExecuted: Boolean; jobCompletedLatch: IMycLatch; subscriber: IMycSubscriber; begin jobExecuted := False; jobCompletedLatch := TLatch.CreateLatch(1); // Changed from Signals.CreateLatch subscriber := FFactory.Run( 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(TLatch.Null, subscriber, 'Subscriber should not be TMycLatch.Null for StartCount > 0'); subscriber.Notify; FFactory.WaitFor(jobCompletedLatch.State); Assert.IsTrue(jobExecuted, 'Delayed job should execute after all notifications'); end; procedure TMycTaskFactoryTests.TestWaitForAlreadySetState; var alreadySetLatch: IMycLatch; startTime, endTime: Cardinal; begin // TMycLatch.CreateLatch(0) will return TMycLatch.Null alreadySetLatch := TLatch.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 := TLatch.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.EnqueueJob( 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 := TLatch.CreateLatch(1); FFactory.EnqueueJob( 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.EnqueueJob(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 := TLatch.CreateLatch(1); dummyStateToWaitFor := TLatch.CreateLatch(1).State; FFactory.EnqueueJob( 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.