Units renamed & Chart refactored
This commit is contained in:
+28
-33
@@ -5,39 +5,34 @@ program MycTests;
|
||||
{$ENDIF}
|
||||
{$STRONGLINKTYPES ON}
|
||||
uses
|
||||
FastMM5,
|
||||
DUnitX.MemoryLeakMonitor.FastMM5,
|
||||
System.SysUtils,
|
||||
{$IFDEF TESTINSIGHT}
|
||||
TestInsight.DUnitX,
|
||||
{$ELSE}
|
||||
DUnitX.Loggers.Console,
|
||||
{$ENDIF }
|
||||
DUnitX.TestFramework,
|
||||
TestNotifier in 'TestNotifier.pas',
|
||||
TestNotifier_Threading in 'TestNotifier_Threading.pas' {/TestNotifier_ChaosStress in 'TestNotifier_ChaosStress.pas',},
|
||||
TestNotifier_ChaosStress in 'TestNotifier_ChaosStress.pas',
|
||||
Myc.Core.Tasks in '..\Src\Myc.Core.Tasks.pas',
|
||||
TestTasks in 'TestTasks.pas',
|
||||
Myc.Futures in '..\Src\Myc.Futures.pas',
|
||||
TestCoreFutures in 'TestCoreFutures.pas',
|
||||
Myc.TaskManager in '..\Src\Myc.TaskManager.pas',
|
||||
Myc.Core.Futures in '..\Src\Myc.Core.Futures.pas',
|
||||
Myc.Core.Signals in '..\Src\Myc.Core.Signals.pas',
|
||||
TestFutures in 'TestFutures.pas',
|
||||
Myc.Lazy in '..\Src\Myc.Lazy.pas',
|
||||
Myc.Core.Lazy in '..\Src\Myc.Core.Lazy.pas',
|
||||
Myc.Test.Core.Lazy in '..\Src\Myc.Test.Core.Lazy.pas',
|
||||
Myc.Test.Lazy in '..\Src\Myc.Test.Lazy.pas',
|
||||
Myc.Test.Core.Atomic in '..\Src\Myc.Test.Core.Atomic.pas',
|
||||
Myc.Test.Signals.Latch in '..\Src\Myc.Test.Signals.Latch.pas',
|
||||
Myc.Test.Signals.Dirty in '..\Src\Myc.Test.Signals.Dirty.pas',
|
||||
Myc.Trade.DataPoint in '..\Src\Myc.Trade.DataPoint.pas',
|
||||
Myc.Trade.Node in '..\Src\Myc.Trade.Node.pas',
|
||||
Myc.Trade.DataStream in '..\Src\Myc.Trade.DataStream.pas',
|
||||
Myc.Test.Trade.DataStream in '..\Src\Myc.Test.Trade.DataStream.pas',
|
||||
Myc.Test.Trade.DataPoint in '..\Src\Myc.Test.Trade.DataPoint.pas',
|
||||
Myc.Trade.DataProvider in '..\Src\Myc.Trade.DataProvider.pas';
|
||||
FastMM5,
|
||||
DUnitX.MemoryLeakMonitor.FastMM5,
|
||||
System.SysUtils,
|
||||
{$IFDEF TESTINSIGHT}
|
||||
TestInsight.DUnitX,
|
||||
{$ELSE}
|
||||
DUnitX.Loggers.Console,
|
||||
{$ENDIF }
|
||||
DUnitX.TestFramework,
|
||||
TestNotifier in 'TestNotifier.pas',
|
||||
TestNotifier_Threading in 'TestNotifier_Threading.pas' {/TestNotifier_ChaosStress in 'TestNotifier_ChaosStress.pas',},
|
||||
TestNotifier_ChaosStress in 'TestNotifier_ChaosStress.pas',
|
||||
TestTasks in 'TestTasks.pas',
|
||||
Myc.Futures in '..\Src\Myc.Futures.pas',
|
||||
TestCoreFutures in 'TestCoreFutures.pas',
|
||||
Myc.TaskManager in '..\Src\Myc.TaskManager.pas',
|
||||
TestFutures in 'TestFutures.pas',
|
||||
Myc.Test.Core.Atomic in '..\Src\Myc.Test.Core.Atomic.pas',
|
||||
Myc.Test.Signals.Latch in '..\Src\Myc.Test.Signals.Latch.pas',
|
||||
Myc.Test.Signals.Dirty in '..\Src\Myc.Test.Signals.Dirty.pas',
|
||||
Myc.Trade.DataPoint in '..\Src\Myc.Trade.DataPoint.pas',
|
||||
Myc.Trade.Node in '..\Src\Myc.Trade.Node.pas',
|
||||
Myc.Trade.DataStream in '..\Src\Myc.Trade.DataStream.pas',
|
||||
Myc.Test.Trade.DataStream in '..\Src\Myc.Test.Trade.DataStream.pas',
|
||||
Myc.Test.Trade.DataPoint in '..\Src\Myc.Test.Trade.DataPoint.pas',
|
||||
Myc.Trade.DataProvider in '..\Src\Myc.Trade.DataProvider.pas',
|
||||
Myc.Mutable in '..\Src\Myc.Mutable.pas',
|
||||
Test.Core.Mutable in 'Test.Core.Mutable.pas';
|
||||
|
||||
{ keep comment here to protect the following conditional from being removed by the IDE when adding a unit }
|
||||
{$IFNDEF TESTINSIGHT}
|
||||
|
||||
+2
-7
@@ -117,18 +117,11 @@ $(PreBuildEvent)]]></PreBuildEvent>
|
||||
<Form>/TestNotifier_ChaosStress in 'TestNotifier_ChaosStress.pas',</Form>
|
||||
</DCCReference>
|
||||
<DCCReference Include="TestNotifier_ChaosStress.pas"/>
|
||||
<DCCReference Include="..\Src\Myc.Core.Tasks.pas"/>
|
||||
<DCCReference Include="TestTasks.pas"/>
|
||||
<DCCReference Include="..\Src\Myc.Futures.pas"/>
|
||||
<DCCReference Include="TestCoreFutures.pas"/>
|
||||
<DCCReference Include="..\Src\Myc.TaskManager.pas"/>
|
||||
<DCCReference Include="..\Src\Myc.Core.Futures.pas"/>
|
||||
<DCCReference Include="..\Src\Myc.Core.Signals.pas"/>
|
||||
<DCCReference Include="TestFutures.pas"/>
|
||||
<DCCReference Include="..\Src\Myc.Lazy.pas"/>
|
||||
<DCCReference Include="..\Src\Myc.Core.Lazy.pas"/>
|
||||
<DCCReference Include="..\Src\Myc.Test.Core.Lazy.pas"/>
|
||||
<DCCReference Include="..\Src\Myc.Test.Lazy.pas"/>
|
||||
<DCCReference Include="..\Src\Myc.Test.Core.Atomic.pas"/>
|
||||
<DCCReference Include="..\Src\Myc.Test.Signals.Latch.pas"/>
|
||||
<DCCReference Include="..\Src\Myc.Test.Signals.Dirty.pas"/>
|
||||
@@ -138,6 +131,8 @@ $(PreBuildEvent)]]></PreBuildEvent>
|
||||
<DCCReference Include="..\Src\Myc.Test.Trade.DataStream.pas"/>
|
||||
<DCCReference Include="..\Src\Myc.Test.Trade.DataPoint.pas"/>
|
||||
<DCCReference Include="..\Src\Myc.Trade.DataProvider.pas"/>
|
||||
<DCCReference Include="..\Src\Myc.Mutable.pas"/>
|
||||
<DCCReference Include="Test.Core.Mutable.pas"/>
|
||||
<BuildConfiguration Include="Base">
|
||||
<Key>Base</Key>
|
||||
</BuildConfiguration>
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
unit Test.Core.Mutable;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.SyncObjs,
|
||||
System.Classes,
|
||||
DUnitX.TestFramework,
|
||||
Myc.Signals,
|
||||
Myc.Mutable,
|
||||
Myc.Core.Mutable;
|
||||
|
||||
type
|
||||
[TestFixture]
|
||||
TTestCoreMutable = class
|
||||
private
|
||||
FNotifyCount: Integer;
|
||||
procedure SubscriberCallback;
|
||||
public
|
||||
[Setup]
|
||||
procedure Setup;
|
||||
|
||||
[Test]
|
||||
procedure TestNullMutable;
|
||||
|
||||
[Test]
|
||||
procedure TestFuncMutable_GetValue;
|
||||
|
||||
[Test]
|
||||
procedure TestFuncMutable_Signal;
|
||||
|
||||
[Test]
|
||||
[TestCase('Writeable', '10,20')]
|
||||
procedure TestWriteableMutable(InitialValue, NewValue: Integer);
|
||||
|
||||
[Test]
|
||||
procedure TestWriteableMutable_SignalOnSameValue;
|
||||
|
||||
[Test]
|
||||
[TestCase('Protected', '100,200')]
|
||||
procedure TestProtectedMutable_Delegation(InitialValue, NewValue: Integer);
|
||||
|
||||
[Test]
|
||||
procedure TestProtectedMutable_ThreadSafety;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
System.Generics.Defaults;
|
||||
|
||||
type
|
||||
// A simple subscriber implementation for testing purposes.
|
||||
TTestSubscriber = class(TInterfacedObject, TSignal.ISubscriber)
|
||||
private
|
||||
FOnNotify: TProc;
|
||||
public
|
||||
constructor Create(const AOnNotify: TProc);
|
||||
function Notify: Boolean;
|
||||
end;
|
||||
|
||||
{ TTestSubscriber }
|
||||
|
||||
constructor TTestSubscriber.Create(const AOnNotify: TProc);
|
||||
begin
|
||||
inherited Create;
|
||||
FOnNotify := AOnNotify;
|
||||
end;
|
||||
|
||||
function TTestSubscriber.Notify: Boolean;
|
||||
begin
|
||||
if Assigned(FOnNotify) then
|
||||
begin
|
||||
FOnNotify;
|
||||
end;
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
{ TTestCoreMutable }
|
||||
|
||||
procedure TTestCoreMutable.Setup;
|
||||
begin
|
||||
FNotifyCount := 0;
|
||||
end;
|
||||
|
||||
procedure TTestCoreMutable.SubscriberCallback;
|
||||
begin
|
||||
TInterlocked.Increment(FNotifyCount);
|
||||
end;
|
||||
|
||||
procedure TTestCoreMutable.TestNullMutable;
|
||||
var
|
||||
mutable: TMutable<Integer>;
|
||||
begin
|
||||
mutable := TMutable<Integer>.Create(TMycNullMutable<Integer>.Create(42));
|
||||
Assert.AreEqual(42, mutable.Value);
|
||||
// Explicitly cast record helper to fully qualified interface for comparison
|
||||
Assert.IsTrue(TSignal.ISignal(mutable.Changed) = TSignal.Null, 'Changed signal should be Null');
|
||||
end;
|
||||
|
||||
procedure TTestCoreMutable.TestFuncMutable_GetValue;
|
||||
var
|
||||
mutable: TMutable<Integer>;
|
||||
callCount: Integer;
|
||||
func: TFunc<Integer>;
|
||||
begin
|
||||
callCount := 0;
|
||||
func := function: Integer
|
||||
begin
|
||||
Inc(callCount);
|
||||
Result := 42;
|
||||
end;
|
||||
mutable := TMutable<Integer>.Construct(TEvent.CreateEvent.Signal, func);
|
||||
|
||||
Assert.AreEqual(42, mutable.Value, 'First value access failed');
|
||||
Assert.AreEqual(1, callCount, 'Function should have been called once');
|
||||
end;
|
||||
|
||||
procedure TTestCoreMutable.TestFuncMutable_Signal;
|
||||
var
|
||||
source: TEvent;
|
||||
mutable: TMutable<Integer>;
|
||||
lazy: TLazy<Integer>;
|
||||
func: TFunc<Integer>;
|
||||
begin
|
||||
source := TEvent.CreateEvent;
|
||||
func := function: Integer
|
||||
begin
|
||||
Result := 123;
|
||||
end;
|
||||
mutable := TMutable<Integer>.Construct(source.Signal, func);
|
||||
|
||||
lazy := TLazy<Integer>.Create(mutable);
|
||||
Assert.IsFalse(lazy.Update, 'Lazy wrapper should not detect a change initially.');
|
||||
|
||||
source.Notify;
|
||||
|
||||
Assert.IsTrue(lazy.Update, 'Lazy wrapper should detect the change after signal.');
|
||||
end;
|
||||
|
||||
procedure TTestCoreMutable.TestWriteableMutable(InitialValue, NewValue: Integer);
|
||||
var
|
||||
writeable: TWriteable<Integer>;
|
||||
subscriber: TSignal.ISubscriber;
|
||||
subscription: TSignal.TSubscription;
|
||||
exchangedValue: Integer;
|
||||
begin
|
||||
writeable := TWriteable<Integer>.CreateWriteable(InitialValue);
|
||||
subscriber := TTestSubscriber.Create(SubscriberCallback);
|
||||
subscription := writeable.Changed.Subscribe(subscriber);
|
||||
|
||||
writeable.Value := NewValue;
|
||||
Assert.AreEqual(1, FNotifyCount, 'Changed.Notify should have been called on SetValue');
|
||||
|
||||
subscription.Unsubscribe;
|
||||
end;
|
||||
|
||||
procedure TTestCoreMutable.TestWriteableMutable_SignalOnSameValue;
|
||||
var
|
||||
writeable: TWriteable<Integer>;
|
||||
subscriber: TSignal.ISubscriber;
|
||||
subscription: TSignal.TSubscription;
|
||||
begin
|
||||
writeable := TWriteable<Integer>.CreateWriteable(100);
|
||||
subscriber := TTestSubscriber.Create(SubscriberCallback);
|
||||
subscription := writeable.Changed.Subscribe(subscriber);
|
||||
|
||||
subscription.Unsubscribe;
|
||||
end;
|
||||
|
||||
procedure TTestCoreMutable.TestProtectedMutable_Delegation(InitialValue, NewValue: Integer);
|
||||
var
|
||||
writeable: TWriteable<Integer>;
|
||||
protectedWriteable: TWriteable<Integer>;
|
||||
subscriber: TSignal.ISubscriber;
|
||||
subscription: TSignal.TSubscription;
|
||||
begin
|
||||
writeable := TWriteable<Integer>.CreateWriteable(InitialValue);
|
||||
protectedWriteable := writeable.Protect;
|
||||
subscriber := TTestSubscriber.Create(SubscriberCallback);
|
||||
subscription := protectedWriteable.Changed.Subscribe(subscriber);
|
||||
|
||||
protectedWriteable.Value := NewValue;
|
||||
Assert.AreEqual(1, FNotifyCount, 'Changed.Notify should have been called on SetValue');
|
||||
|
||||
subscription.Unsubscribe;
|
||||
end;
|
||||
|
||||
procedure TTestCoreMutable.TestProtectedMutable_ThreadSafety;
|
||||
const
|
||||
ThreadCount = 8;
|
||||
Iterations = 2500;
|
||||
var
|
||||
writeable: TWriteable<Integer>;
|
||||
threads: TArray<TThread>;
|
||||
i: Integer;
|
||||
begin
|
||||
// This test verifies that the lock prevents crashes under concurrent access.
|
||||
// It does not test for an atomic sum, as the protected mutable does not
|
||||
// offer a combined Compare-and-Exchange operation.
|
||||
writeable := TWriteable<Integer>.CreateWriteable(0).Protect;
|
||||
|
||||
for i := 0 to ThreadCount - 1 do
|
||||
begin
|
||||
threads := threads + [TThread.CreateAnonymousThread(
|
||||
procedure
|
||||
var
|
||||
j: Integer;
|
||||
begin
|
||||
for j := 1 to Iterations do
|
||||
begin
|
||||
// This is an intentional race condition to test lock stability.
|
||||
// The lock protects the GetValue calls individually,
|
||||
// preventing memory corruption during concurrent access.
|
||||
var curr := writeable.Value;
|
||||
writeable.Value := curr + 1;
|
||||
end;
|
||||
end
|
||||
)];
|
||||
end;
|
||||
|
||||
for var thread in threads do
|
||||
begin
|
||||
thread.FreeOnTerminate := false;
|
||||
thread.Start;
|
||||
end;
|
||||
|
||||
for var thread in threads do
|
||||
begin
|
||||
thread.WaitFor;
|
||||
thread.Free;
|
||||
end;
|
||||
|
||||
// We cannot assert a specific value due to the race condition,
|
||||
// but success means the test completed without exceptions.
|
||||
Assert.IsTrue(True, 'Thread safety test completed without exceptions.');
|
||||
end;
|
||||
|
||||
end.
|
||||
@@ -10,7 +10,7 @@ uses
|
||||
Myc.Signals, // For IMycLatch, TMycLatch, IMycState, IMycSubscriber [cite: 62, 68, 53, 45]
|
||||
Myc.Core.Tasks, // For IMycTaskFactory, TMycTaskFactory [cite: 97, 113]
|
||||
Myc.Futures,
|
||||
Myc.Core.Futures; // For IMycFuture, TMycInitStateFuncFuture
|
||||
Myc.Core.Future; // For IMycFuture, TMycInitStateFuncFuture
|
||||
|
||||
type
|
||||
// Helper class to notify a latch when its Notify method is called.
|
||||
|
||||
Reference in New Issue
Block a user