xtool/contrib/fundamentals/TCP/Tests/flcTCPTest_ClientServer.pas

864 lines
22 KiB
ObjectPascal

{ 2020/05/11 5.01 Move tests from unit flcTests into seperate units. }
{$INCLUDE flcTCPTest.inc}
unit flcTCPTest_ClientServer;
interface
{$IFDEF TCPCLIENTSERVER_TEST}
uses
{$IFDEF OS_MSWIN}
Windows,
{$ENDIF}
SysUtils,
SyncObjs,
Classes,
flcStdTypes,
flcTCPConnection,
flcTCPClient,
flcTCPServer;
{$ENDIF}
{$IFDEF TCPCLIENTSERVER_TEST}
{ }
{ TCP ClientServer Test Object }
{ }
type
TTCPClientServerTestObj = class
Lock : TCriticalSection;
constructor Create;
destructor Destroy; override;
procedure Log(Msg: String);
procedure ClientLog(Client: TF5TCPClient; LogType: TTCPClientLogType; Msg: String; LogLevel: Integer);
procedure ServerLog(Sender: TF5TCPServer; LogType: TTCPLogType; Msg: String; LogLevel: Integer);
end;
TTCPClientServerBlockTestObj = class
FinC, FinS : Boolean;
procedure ClientExec(Client: TF5TCPClient; Connection: TTCPBlockingConnection; var CloseOnExit: Boolean);
procedure ServerExec(Sender: TTCPServerClient; Connection: TTCPBlockingConnection; var CloseOnExit: Boolean);
end;
{ }
{ Test functions }
{ }
type
TF5TCPClientArray = array of TF5TCPClient;
TTCPServerClientArray = array of TTCPServerClient;
procedure TestClientServer_ReadWrite_Blocks(
const TestClientCount: Integer;
const TestLargeBlock: Boolean;
const DebugObj : TTCPClientServerTestObj;
const C: TF5TCPClientArray;
const T: TTCPServerClientArray);
{ }
{ Test }
{ }
procedure Test;
{$ENDIF}
implementation
{$IFDEF TCPCLIENTSERVER_TEST}
uses
flcSocketLib,
flcTimers;
{$ENDIF}
{$IFDEF TCPCLIENTSERVER_TEST}
{$ASSERTIONS ON}
{ }
{ TCP ClientServer Test Object }
{ }
{ TTCPClientServerTestObj }
constructor TTCPClientServerTestObj.Create;
begin
inherited Create;
Lock := TCriticalSection.Create;
end;
destructor TTCPClientServerTestObj.Destroy;
begin
FreeAndNil(Lock);
inherited Destroy;
end;
{$IFDEF TCP_TEST_LOG_TO_CONSOLE}
procedure TTCPClientServerTestObj.Log(Msg: String);
var S : String;
begin
S := FormatDateTime('hh:nn:ss.zzz', Now) + ' ' + Msg;
Lock.Acquire;
try
Writeln(S);
finally
Lock.Release;
end;
end;
{$ELSE}
procedure TTCPClientServerTestObj.Log(Msg: String);
begin
end;
{$ENDIF}
procedure TTCPClientServerTestObj.ClientLog(Client: TF5TCPClient; LogType: TTCPClientLogType; Msg: String; LogLevel: Integer);
begin
Log('C[' + IntToStr(Client.Tag) + ']:' + Msg);
end;
procedure TTCPClientServerTestObj.ServerLog(Sender: TF5TCPServer; LogType: TTCPLogType; Msg: String; LogLevel: Integer);
begin
Log('S:' + Msg);
end;
{ TTCPClientServerBlockTestObj }
procedure TTCPClientServerBlockTestObj.ClientExec(Client: TF5TCPClient;Connection: TTCPBlockingConnection; var CloseOnExit: Boolean);
var
B1 : LongWord;
begin
Sleep(200);
B1 := 1234;
Connection.Write(B1, 4, 10000);
B1 := 0;
Connection.Read(B1, 4, 10000);
Assert(B1 = 22222);
FinC := True;
end;
procedure TTCPClientServerBlockTestObj.ServerExec(Sender: TTCPServerClient; Connection: TTCPBlockingConnection; var CloseOnExit: Boolean);
var
B1 : LongWord;
begin
B1 := 0;
Connection.Read(B1, 4, 10000);
Assert(B1 = 1234);
B1 := 22222;
Connection.Write(B1, 4, 10000);
FinS := True;
end;
{ }
{ Test }
{ }
procedure TestClientServer_ReadWrite_Blocks(
const TestClientCount: Integer;
const TestLargeBlock: Boolean;
const DebugObj : TTCPClientServerTestObj;
const C: TF5TCPClientArray;
const T: TTCPServerClientArray);
const
LargeBlockSize = 256 * 1024;
var
K, I, J : Integer;
F : RawByteString;
B : Byte;
begin
// read & write (small block): client to server
for K := 0 to TestClientCount - 1 do
C[K].Connection.WriteByteString('Fundamentals');
for K := 0 to TestClientCount - 1 do
begin
DebugObj.Log('{RWS:' + IntToStr(K + 1) + ':A}');
I := 0;
repeat
Inc(I);
Sleep(1);
until (T[K].Connection.ReadBufferUsed >= 12) or (I >= 5000);
DebugObj.Log('{RWS:' + IntToStr(K + 1) + ':B}');
Assert(T[K].Connection.ReadBufferUsed = 12);
F := T[K].Connection.PeekByteString(3);
Assert(F = 'Fun');
Assert(T[K].Connection.PeekByte(B));
Assert(B = Ord('F'));
F := T[K].Connection.ReadByteString(12);
Assert(F = 'Fundamentals');
DebugObj.Log('{RWS:' + IntToStr(K + 1) + ':Z}');
end;
// read & write (small block): server to client
for K := 0 to TestClientCount - 1 do
T[K].Connection.WriteByteString('123');
for K := 0 to TestClientCount - 1 do
begin
C[K].BlockingConnection.WaitForReceiveData(3, 5000);
F := C[K].Connection.ReadByteString(3);
Assert(F = '123');
end;
if TestLargeBlock then
begin
// read & write (large block): client to server
F := '';
for I := 1 to LargeBlockSize do
F := F + #1;
for K := 0 to TestClientCount - 1 do
C[K].Connection.WriteByteString(F);
for K := 0 to TestClientCount - 1 do
begin
J := LargeBlockSize;
repeat
I := 0;
repeat
Inc(I);
Sleep(1);
Assert(C[K].State = csReady);
Assert(T[K].State = scsReady);
until (T[K].Connection.ReadBufferUsed > 0) or (I >= 5000);
Assert(T[K].Connection.ReadBufferUsed > 0);
F := T[K].Connection.ReadByteString(T[K].Connection.ReadBufferUsed);
Assert(Length(F) > 0);
for I := 1 to Length(F) do
Assert(F[I] = #1);
Dec(J, Length(F));
until J <= 0;
Assert(J = 0);
Sleep(2);
Assert(T[K].Connection.ReadBufferUsed = 0);
end;
// read & write (large block): server to client
F := '';
for I := 1 to LargeBlockSize do
F := F + #1;
for K := 0 to TestClientCount - 1 do
T[K].Connection.WriteByteString(F);
for K := 0 to TestClientCount - 1 do
begin
J := LargeBlockSize;
repeat
C[K].BlockingConnection.WaitForReceiveData(1, 5000);
Assert(C[K].State = csReady);
Assert(T[K].State = scsReady);
Assert(C[K].Connection.ReadBufferUsed > 0);
F := C[K].Connection.ReadByteString(C[K].Connection.ReadBufferUsed);
Assert(Length(F) > 0);
for I := 1 to Length(F) do
Assert(F[I] = #1);
Dec(J, Length(F));
until J <= 0;
Assert(J = 0);
Sleep(2);
Assert(C[K].Connection.ReadBufferUsed = 0);
end;
end;
end;
procedure TestClientServer_ReadWrite(
const TestClientCount: Integer;
const TestLargeBlock: Boolean
);
procedure WaitClientConnected(const Client: TF5TCPClient);
var I : Integer;
begin
I := 0;
repeat
Inc(I);
Sleep(1);
until (I >= 8000) or
(Client.State in [csReady, csClosed]);
Assert(Client.State = csReady);
Assert(Client.Connection.State = cnsConnected);
end;
var C : TF5TCPClientArray;
S : TF5TCPServer;
T : TTCPServerClientArray;
TSC : TTCPServerClient;
I, K : Integer;
DebugObj : TTCPClientServerTestObj;
begin
DebugObj := TTCPClientServerTestObj.Create;
S := TF5TCPServer.Create(nil);
SetLength(C, TestClientCount);
SetLength(T, TestClientCount);
for K := 0 to TestClientCount - 1 do
begin
C[K] := TF5TCPClient.Create(nil);
// init client
C[K].Tag := K + 1;
C[K].OnLog := DebugObj.ClientLog;
end;
try
// init server
S.OnLog := DebugObj.ServerLog;
S.AddressFamily := iaIP4;
S.BindAddress := '127.0.0.1';
S.ServerPort := 12545;
S.MaxClients := -1;
Assert(S.State = ssInit);
Assert(not S.Active);
// start server
S.Start;
Assert(S.Active);
I := 0;
repeat
Inc(I);
Sleep(1);
until (S.State <> ssStarting) or (I >= 5000);
Sleep(100);
Assert(S.State = ssReady);
Assert(S.ClientCount = 0);
for K := 0 to TestClientCount - 1 do
begin
// init client
C[K].AddressFamily := cafIP4;
C[K].Host := '127.0.0.1';
C[K].Port := '12545';
Assert(C[K].State = csInit);
Assert(not C[K].Active);
// start client
C[K].WaitForStartup := True;
C[K].Start;
Assert(Assigned(C[K].Connection));
Assert(C[K].Active);
end;
for K := 0 to TestClientCount - 1 do
// wait for client to connect
WaitClientConnected(C[K]);
// wait for server connections
I := 0;
repeat
Inc(I);
Sleep(1);
until (S.ClientCount >= TestClientCount) or (I >= 5000);
Assert(S.ClientCount = TestClientCount);
// wait for server clients
TSC := S.ClientIterateFirst;
for K := 0 to TestClientCount - 1 do
begin
T[K] := TSC;
Assert(Assigned(T[K]));
Assert(T[K].State in [scsStarting, scsNegotiating, scsReady]);
Assert(T[K].Connection.State in [cnsProxyNegotiation, cnsConnected]);
TSC.ReleaseReference;
TSC := S.ClientIterateNext(TSC);
end;
// test read/write
TestClientServer_ReadWrite_Blocks(
TestClientCount,
TestLargeBlock,
DebugObj,
C, T);
// release reference
for K := 0 to TestClientCount - 1 do
T[K].ReleaseReference;
// stop clients
for K := TestClientCount - 1 downto 0 do
begin
C[K].Stop;
Assert(C[K].State = csStopped);
end;
I := 0;
repeat
Inc(I);
Sleep(1);
until (S.ClientCount = 0) or (I >= 5000);
Assert(S.ClientCount = 0);
// stop server
S.Stop;
Assert(not S.Active);
finally
for K := TestClientCount - 1 downto 0 do
begin
C[K].Finalise;
FreeAndNil(C[K]);
end;
S.Finalise;
FreeAndNil(S);
DebugObj.Free;
end;
end;
procedure Test_ClientServer_StopStart;
const
TestClientCount = 10;
TestRepeatCount = 3;
var C : array of TF5TCPClient;
S : TF5TCPServer;
I, K : Integer;
J : Integer;
DebugObj : TTCPClientServerTestObj;
begin
DebugObj := TTCPClientServerTestObj.Create;
S := TF5TCPServer.Create(nil);
SetLength(C, TestClientCount);
for K := 0 to TestClientCount - 1 do
begin
C[K] := TF5TCPClient.Create(nil);
// init client
C[K].Tag := K + 1;
C[K].OnLog := DebugObj.ClientLog;
end;
try
// init server
S.OnLog := DebugObj.ServerLog;
S.AddressFamily := iaIP4;
S.BindAddress := '127.0.0.1';
S.ServerPort := 12645;
S.MaxClients := -1;
Assert(S.State = ssInit);
Assert(not S.Active);
// start server
S.Start;
Assert(S.Active);
I := 0;
repeat
Inc(I);
Sleep(1);
until (S.State <> ssStarting) or (I >= 5000);
Assert(S.State = ssReady);
Assert(S.ClientCount = 0);
// init clients
for K := 0 to TestClientCount - 1 do
begin
C[K].AddressFamily := cafIP4;
C[K].Host := '127.0.0.1';
C[K].Port := '12645';
C[K].WaitForStartup := True;
Assert(C[K].State = csInit);
Assert(not C[K].Active);
end;
// test quickly starting and stopping clients
for J := 0 to TestRepeatCount - 1 do
begin
// start client
for K := 0 to TestClientCount - 1 do
begin
C[K].Start;
// connection must exist when Start exits
Assert(Assigned(C[K].Connection));
Assert(C[K].Active);
end;
// delay
if J > 0 then
Sleep(J - 1);
// stop clients
for K := TestClientCount - 1 downto 0 do
begin
C[K].Stop;
Assert(C[K].State = csStopped);
Assert(not Assigned(C[K].Connection));
end;
// delay
if J > 0 then
Sleep(J - 1);
// re-start client
for K := 0 to TestClientCount - 1 do
begin
C[K].Start;
// connection must exist when Start exits
Assert(Assigned(C[K].Connection));
Assert(C[K].Active);
end;
// delay
if J > 0 then
Sleep(J - 1);
// re-stop clients
for K := TestClientCount - 1 downto 0 do
begin
C[K].Stop;
Assert(C[K].State = csStopped);
Assert(not Assigned(C[K].Connection));
end;
end;
// stop server
S.Stop;
Assert(not S.Active);
finally
for K := TestClientCount - 1 downto 0 do
begin
C[K].Finalise;
FreeAndNil(C[K]);
end;
S.Finalise;
FreeAndNil(S);
DebugObj.Free;
end;
end;
procedure Test_ClientServer_ReadWrite;
begin
TestClientServer_ReadWrite(1, True);
TestClientServer_ReadWrite(2, True);
TestClientServer_ReadWrite(5, True);
{$IFNDEF LINUX} // FAILS
TestClientServer_ReadWrite(30, False);
{$ENDIF}
end;
procedure Test_ClientServer_Shutdown;
var
S : TF5TCPServer;
C : TF5TCPClient;
T : TTCPServerClient;
B, X : RawByteString;
I, L, N, M : Integer;
begin
S := TF5TCPServer.Create(nil);
S.AddressFamily := iaIP4;
S.BindAddress := '127.0.0.1';
S.ServerPort := 12213;
S.MaxClients := -1;
S.Active := True;
Sleep(100);
C := TF5TCPClient.Create(nil);
C.LocalHost := '0.0.0.0';
C.Host := '127.0.0.1';
C.Port := '12213';
C.WaitForStartup := True;
C.UseWorkerThread := False;
C.Active := True;
Sleep(100);
Assert(C.State = csReady);
SetLength(B, 1024 * 1024);
for I := 1 to Length(B) do
B[I] := #1;
L := C.Connection.WriteByteString(B);
Assert(L > 1024);
Sleep(1);
Assert(not C.IsShutdownComplete);
C.Shutdown;
for I := 1 to 10 do
begin
if C.IsShutdownComplete then
break;
Sleep(50);
end;
Assert(C.IsShutdownComplete);
T := S.ClientIterateFirst;
Assert(Assigned(T));
SetLength(X, L);
M := 0;
repeat
N := T.Connection.Read(X[1], L);
Inc(M, N);
Sleep(10);
until N <= 0;
Assert(M = L);
Sleep(100);
Assert(C.State = csClosed);
Assert(T.State = scsClosed);
T.ReleaseReference;
C.Close;
C.Finalise;
C.Free;
S.Active := False;
S.Finalise;
S.Free;
end;
procedure Test_ClientServer_Block;
var
S : TF5TCPServer;
C : TF5TCPClient;
DebugObj : TTCPClientServerTestObj;
TestObj : TTCPClientServerBlockTestObj;
begin
DebugObj := TTCPClientServerTestObj.Create;
TestObj := TTCPClientServerBlockTestObj.Create;
S := TF5TCPServer.Create(nil);
S.OnLog := DebugObj.ServerLog;
S.AddressFamily := iaIP4;
S.BindAddress := '127.0.0.1';
S.ServerPort := 12145;
S.MaxClients := -1;
S.UseWorkerThread := True;
S.OnClientWorkerExecute := TestObj.ServerExec;
S.Active := True;
Sleep(50);
C := TF5TCPClient.Create(nil);
C.OnLog := DebugObj.ClientLog;
C.LocalHost := '0.0.0.0';
C.Host := '127.0.0.1';
C.Port := '12145';
C.WaitForStartup := True;
C.UseWorkerThread := True;
C.OnWorkerExecute := TestObj.ClientExec;
C.Active := True;
repeat
Sleep(1);
until TestObj.FinC and TestObj.FinS;
C.Active := False;
S.Active := False;
C.Finalise;
FreeAndNil(C);
S.Finalise;
FreeAndNil(S);
TestObj.Free;
DebugObj.Free;
end;
procedure Test_ClientServer_RetryConnect;
var
S : TF5TCPServer;
C : TF5TCPClient;
DebugObj : TTCPClientServerTestObj;
TestObj : TTCPClientServerBlockTestObj;
I : Integer;
begin
DebugObj := TTCPClientServerTestObj.Create;
TestObj := TTCPClientServerBlockTestObj.Create;
S := TF5TCPServer.Create(nil);
S.OnLog := DebugObj.ServerLog;
S.AddressFamily := iaIP4;
S.BindAddress := '127.0.0.1';
S.ServerPort := 12045;
S.MaxClients := -1;
S.UseWorkerThread := True;
S.OnClientWorkerExecute := TestObj.ServerExec;
C := TF5TCPClient.Create(nil);
C.OnLog := DebugObj.ClientLog;
C.LocalHost := '0.0.0.0';
C.Host := '127.0.0.1';
C.Port := '12045';
C.WaitForStartup := True;
C.UseWorkerThread := True;
C.OnWorkerExecute := TestObj.ClientExec;
C.RetryFailedConnect := True;
C.RetryFailedConnectDelaySec := 2;
C.RetryFailedConnectMaxAttempts := 3;
C.ReconnectOnDisconnect := False;
C.Active := True;
Sleep(2000);
S.Active := True;
I := 0;
repeat
Sleep(1);
Inc(I);
until (TestObj.FinC and TestObj.FinS) or (I > 4000);
Assert(TestObj.FinC and TestObj.FinS);
//S.Active := False;
//Sleep(100000);
C.Active := False;
S.Active := False;
C.Finalise;
FreeAndNil(C);
S.Finalise;
FreeAndNil(S);
TestObj.Free;
DebugObj.Free;
end;
procedure Test_ClientServer_Latency;
procedure DoYield;
begin
{$IFDEF DELPHI}
{$IFDEF OS_MSWIN}
Yield;
{$ELSE}
TThread.Yield;
{$ENDIF}
{$ELSE}
TThread.Yield;
{$ENDIF}
end;
procedure WaitClientConnected(const Client: TF5TCPClient);
var I : Integer;
begin
// wait for client to connect
I := 0;
repeat
Inc(I);
Sleep(1);
until (I >= 8000) or
(Client.State in [csReady, csClosed]);
Assert(Client.State = csReady);
Assert(Client.Connection.State = cnsConnected);
end;
var C : TF5TCPClient;
S : TF5TCPServer;
TSC : TTCPServerClient;
I, Nr : Integer;
F : RawByteString;
DebugObj : TTCPClientServerTestObj;
T : Word64;
Buf : array[0..15] of Byte;
const
Test1N = 5;
begin
DebugObj := TTCPClientServerTestObj.Create;
DebugObj.Log('');
DebugObj.Log('Test_ClientServer_Latency:');
S := TF5TCPServer.Create(nil);
C := TF5TCPClient.Create(nil);
// init client
C.Tag := 1;
C.OnLog := DebugObj.ClientLog;
try
// init server
S.OnLog := DebugObj.ServerLog;
S.AddressFamily := iaIP4;
S.BindAddress := '127.0.0.1';
Randomize;
S.ServerPort := 12513 + Random(1000);
S.MaxClients := -1;
Assert(S.State = ssInit);
Assert(not S.Active);
// start server
S.Start;
Assert(S.Active);
I := 0;
repeat
Inc(I);
Sleep(10);
until (S.State <> ssStarting) or (I >= 400);
Sleep(100);
Assert(S.State = ssReady);
Assert(S.ClientCount = 0);
// init client
C.AddressFamily := cafIP4;
C.Host := '127.0.0.1';
C.Port := IntToStr(S.ServerPort);
Assert(C.State = csInit);
Assert(not C.Active);
// start client
C.WaitForStartup := True;
C.Start;
Assert(Assigned(C.Connection));
Assert(C.Active);
// wait for client to connect
WaitClientConnected(C);
// wait for server connections
I := 0;
repeat
Inc(I);
Sleep(10);
until (S.ClientCount >= 1) or (I >= 10000);
Assert(S.ClientCount = 1);
// wait for server clients
TSC := S.ClientIterateFirst;
Assert(Assigned(TSC));
Assert(TSC.State in [scsStarting, scsNegotiating, scsReady]);
Assert(TSC.Connection.State in [cnsProxyNegotiation, cnsConnected]);
for Nr := 1 to Test1N do
begin
// read & write (small block): client to server
// read directly from connection before buffered
T := GetMicroTick;
C.Connection.WriteByteString('Fundamentals');
repeat
DoYield;
F := TSC.Connection.ReadByteString(12);
if F = 'Fundamentals' then
break;
until MicroTickDelta(T, GetMicroTick) >= 5000000; // 5s
T := MicroTickDelta(T, GetMicroTick);
DebugObj.Log(' Latency1_ClientToServer:' + IntToStr(T));
Assert(F = 'Fundamentals');
// read & write (small block): server to client
// read directly from connection before buffered
FillChar(Buf, SizeOf(Buf), 0);
T := GetMicroTick;
TSC.Connection.WriteByteString('123');
repeat
DoYield;
if C.Connection.Read(Buf[0], 3) = 3 then
if (Buf[0] = Ord('1')) and (Buf[1] = Ord('2')) and (Buf[2] = Ord('3')) then
break;
until MicroTickDelta(T, GetMicroTick) >= 5000000; // 5s
T := MicroTickDelta(T, GetMicroTick);
DebugObj.Log(' Latency2_ServerToClient:' + IntToStr(T));
Assert((Buf[0] = Ord('1')) and (Buf[1] = Ord('2')) and (Buf[2] = Ord('3')));
// read & write (small block): client to server
T := GetMicroTick;
C.Connection.WriteByteString('Fundamentals');
repeat
DoYield;
until (TSC.Connection.ReadBufferUsed >= 12) or (MicroTickDelta(T, GetMicroTick) >= 5000000);
T := MicroTickDelta(T, GetMicroTick);
DebugObj.Log(' Latency3_ClientToServer:' + IntToStr(T));
Assert(TSC.Connection.ReadBufferUsed = 12);
F := TSC.Connection.ReadByteString(12);
Assert(F = 'Fundamentals');
// read & write (small block): server to client
T := GetMicroTick;
TSC.Connection.WriteByteString('123');
repeat
DoYield;
until (C.Connection.ReadBufferUsed >= 3) or (MicroTickDelta(T, GetMicroTick) >= 5000000);
T := MicroTickDelta(T, GetMicroTick);
DebugObj.Log(' Latency4_ServerToClient:' + IntToStr(T));
F := C.Connection.ReadByteString(3);
Assert(F = '123');
end;
TSC.ReleaseReference;
finally
C.Free;
S.Free;
end;
FreeAndNil(DebugObj);
end;
procedure Test;
begin
Test_ClientServer_ReadWrite;
Test_ClientServer_StopStart;
Test_ClientServer_Shutdown;
Test_ClientServer_Block;
Test_ClientServer_RetryConnect;
Test_ClientServer_Block;
Test_ClientServer_ReadWrite;
Test_ClientServer_Latency;
end;
{$ENDIF}
end.