Units renamed & Chart refactored

This commit is contained in:
Michael Schimmel
2025-07-14 15:07:36 +02:00
parent 661faba75c
commit d0ad547aa3
20 changed files with 340 additions and 1050 deletions
+28 -33
View File
@@ -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
View File
@@ -117,18 +117,11 @@ $(PreBuildEvent)]]></PreBuildEvent>
<Form>/TestNotifier_ChaosStress in &apos;TestNotifier_ChaosStress.pas&apos;,</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>
+240
View File
@@ -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.
+1 -1
View File
@@ -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.