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: IThreadPool;
    public
        [Setup]
        procedure Setup;
        [TearDown]
        procedure TearDown;

        [Test]
        procedure TestFactoryCreationAndTeardown;
        [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.TestWaitForAlreadySetState;
var
    alreadySetLatch: TLatch.ILatch;
    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: TLatch.ILatch;
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: TLatch.ILatch;
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: TLatch.ILatch;
    dummyStateToWaitFor: TState.IState;
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.
