Files
fpc-comet/tests/test_session.pas
Ken Johnson 3f76893fa6 Driver state machine: cm.driver + reference impls + roundtrip test
cm.driver is the TComSession step engine: NextStep pumps one
unit of I/O + state transition.  This commit lands the
handshake half (banner -> INIT exchange -> auth resolution)
and stops at cmpTransfer; cm.xfer (FINFO/DATA/EOF/RPOS) lands
in the next batch.

cmpBanner runs as a non-blocking sub-state machine -- send
once, then accumulate one byte at a time until COMET
classification or BinkP detection.  Recv = -1 in winding-down
phases (cmpShutdown / cmpDone / cmpFailed) is treated as
graceful EOF, not an IO error.  FResult is finalized on
transition into cmpDone so consumers see the result the same
step the phase flips.

Reference impls also land:
  cm.provider.fs  -- TFileStream-backed IComFileProvider
  cm.transport.tcp -- UNIX BSD-socket IComTransport
  (DOS/Win/OS2 skip cm.transport.tcp via build.sh)

tests/cmtestutil pins TByteQueue + TMemPipeEnd (IComTransport)
+ TMemPipePair + TTest (matches fpc-binkp's testutil shape).
tests/test_session wires two TComSession instances over a
TMemPipePair, pumps in lock-step, verifies both sides reach
cmpDone, authenticate (NOPWD path), capture remote SysName /
AKAs, and negotiate caps as the AND of both advertisements.

All 7 cross-targets build clean; 3 test programs pass.
2026-04-22 12:29:43 -07:00

144 lines
4.4 KiB
ObjectPascal

{ test_session -- end-to-end TComSession handshake over an
in-memory pipe.
Wires two TComSession instances (one inbound, one outbound)
to opposite ends of a TMemPipePair and pumps NextStep on
both until each reaches cmpDone. Verifies:
- Both sides terminate cleanly
- Both report success
- Both authenticate (NOPWD path)
- The negotiated capability mask is the AND of both sides
- Banner + INIT + INITACK frames make it across in both
directions
This is the cm.* equivalent of fpc-binkp's
test_session_roundtrip -- the load-bearing test that
proves the session engine can talk to itself. }
program test_session;
{$mode objfpc}{$H+}
{$modeswitch advancedrecords}
uses
Classes, SysUtils,
log.types, mb.address,
cm.types, cm.config, cm.transport, cm.driver,
cmtestutil;
var
T: TTest;
function MakeCfg(Trans: IComTransport; const Name: string;
Z, Net, Node: Word): TCometSessionConfig;
begin
CMConfigDefaults(Result);
Result.Transport := Trans;
Result.Provider := nil; { driver doesn't drive xfer in this batch }
Result.SystemName := Name;
Result.SysOp := 'Test';
Result.MailerName := 'fpc-comet/test';
SetLength(Result.LocalAddrs, 1);
Result.LocalAddrs[0] := MakeFTNAddress(Z, Net, Node, 0);
Result.HandshakeTimeoutSeconds := 5;
Result.TimeoutSeconds := 5;
end;
procedure TestHandshakeRoundtrip;
var
Pipe: TMemPipePair;
Inbound, Outbound: TComSession;
CfgIn, CfgOut: TCometSessionConfig;
Step: Integer;
RIn, ROut: TCometSessionResult;
ProgIn, ProgOut: Boolean;
begin
T.Group('TestHandshakeRoundtrip');
Pipe := TMemPipePair.Create;
try
CfgIn := MakeCfg(Pipe.EndA, 'Inbound', 1, 218, 720);
CfgOut := MakeCfg(Pipe.EndB, 'Outbound', 1, 213, 700);
Inbound := TComSession.Create(cmDirInbound, CfgIn);
Outbound := TComSession.Create(cmDirOutbound, CfgOut);
try
{ Pump in lock-step. Hard cap at 200 iterations to
keep a regression from hanging CI forever. }
for Step := 1 to 200 do
begin
ProgOut := Outbound.NextStep;
ProgIn := Inbound.NextStep;
if (Inbound.Phase = cmpDone) and (Outbound.Phase = cmpDone) then
Break;
if (not ProgIn) and (not ProgOut) and
(Inbound.Phase <> cmpDone) and (Outbound.Phase <> cmpDone) then
begin
Writeln(' no progress at step ', Step,
' inbound.phase=', Ord(Inbound.Phase),
' outbound.phase=', Ord(Outbound.Phase));
Break;
end;
end;
RIn := Inbound.Result_;
ROut := Outbound.Result_;
T.Check(Inbound.Phase = cmpDone,
Format('inbound reached cmpDone (was %d)',
[Ord(Inbound.Phase)]));
T.Check(Outbound.Phase = cmpDone,
Format('outbound reached cmpDone (was %d)',
[Ord(Outbound.Phase)]));
T.Check(RIn.Success,
Format('inbound reports Success (err=%d msg=%s)',
[Ord(RIn.ErrorCode), RIn.ErrorMessage]));
T.Check(ROut.Success,
Format('outbound reports Success (err=%d msg=%s)',
[Ord(ROut.ErrorCode), ROut.ErrorMessage]));
T.Check(RIn.Authenticated, 'inbound authenticated');
T.Check(ROut.Authenticated, 'outbound authenticated');
T.Check(RIn.AuthMethod = cmAuthNoPwd, 'inbound auth = NOPWD');
T.Check(ROut.AuthMethod = cmAuthNoPwd, 'outbound auth = NOPWD');
T.Check(RIn.WireBytesSent > 0, 'inbound sent bytes');
T.Check(RIn.WireBytesRecv > 0, 'inbound received bytes');
T.Check(ROut.WireBytesSent > 0, 'outbound sent bytes');
T.Check(ROut.WireBytesRecv > 0, 'outbound received bytes');
T.CheckEqualStr(ROut.Remote.SysName, 'Inbound',
'outbound sees inbound SysName');
T.CheckEqualStr(RIn.Remote.SysName, 'Outbound',
'inbound sees outbound SysName');
T.Check(Length(RIn.Remote.Addrs) = 1,
'inbound captured one remote AKA');
T.Check(Length(ROut.Remote.Addrs) = 1,
'outbound captured one remote AKA');
T.Check((RIn.Remote.Caps and COPT_SHA256) <> 0,
'negotiated SHA256');
T.Check((RIn.Remote.Caps and COPT_NOPWD) <> 0,
'negotiated NOPWD');
finally
Inbound.Free;
Outbound.Free;
end;
finally
Pipe.Free;
end;
end;
begin
T := TTest.Create('test_session');
try
TestHandshakeRoundtrip;
T.Summary;
Halt(T.ExitCode);
finally
T.Free;
end;
end.