Project renamed from message_api → fpc-msgbase. Folder, README title, docs, build.sh, fpc.cfg, and test banners all updated for consistency with the planned remote at kjgr.io:2222/kenjreno/fpc-msgbase.git. Also scrubbed claims that backends were "ported from Allfix" or "match Allfix's msgutil/domsg" — none of this code was ported from Allfix; it was implemented from FTSC documents and the original format authors' published specs (jam.txt, squish.doc, pcboard.doc, EzyCom reference, WildCat 4 SDK headers). Author credits live in docs/ftsc-compliance.md. Real interop facts that mention Allfix-the-product stay documented: the PCB Extra2 sent-bit ($40) Allfix sets when tossing, and the FTSC-registered product code $EB. These describe external software behavior we interoperate with, not provenance. docs/format-notes/hudson.md removed — stale planning doc that predates the working ma.fmt.hudson backend.
157 lines
4.1 KiB
ObjectPascal
157 lines
4.1 KiB
ObjectPascal
{
|
|
test_batch.pas - concurrent packet batch toss.
|
|
|
|
Generates K synthetic packets with M messages each, runs them
|
|
through a TPacketBatch with N worker threads writing into a
|
|
shared destination JAM base, then verifies the destination
|
|
holds K*M messages (no drops, no duplicates, no corruption).
|
|
}
|
|
|
|
program test_batch;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
uses
|
|
{$IFDEF UNIX}cthreads,{$ENDIF}
|
|
SysUtils, Classes, SyncObjs,
|
|
testutil,
|
|
ma.types, ma.events, ma.api, ma.batch,
|
|
ma.fmt.jam, ma.fmt.jam.uni,
|
|
ma.fmt.pkt, ma.fmt.pkt.uni;
|
|
|
|
const
|
|
SCRATCH = '/tmp/ma_batch';
|
|
INBOUND = SCRATCH + '/inbound';
|
|
DESTBASE = SCRATCH + '/echo';
|
|
PACKETS = 5;
|
|
PER_PKT = 10;
|
|
THREADS = 3;
|
|
|
|
type
|
|
TBatchRunner = class
|
|
DestPath: AnsiString;
|
|
Batch: TPacketBatch;
|
|
Written: longint;
|
|
CS: TRTLCriticalSection;
|
|
procedure OnMessage(const APacketPath: AnsiString;
|
|
var Msg: TUniMessage;
|
|
var Stop: boolean);
|
|
end;
|
|
|
|
procedure TBatchRunner.OnMessage(const APacketPath: AnsiString;
|
|
var Msg: TUniMessage;
|
|
var Stop: boolean);
|
|
var
|
|
base: TMessageBase;
|
|
begin
|
|
base := Batch.GetOrCreateBase(mbfJam, DestPath);
|
|
if base = nil then exit;
|
|
if base.WriteMessage(Msg) then begin
|
|
EnterCriticalSection(CS);
|
|
Inc(Written);
|
|
LeaveCriticalSection(CS);
|
|
end;
|
|
end;
|
|
|
|
procedure GeneratePackets;
|
|
var
|
|
i, j: integer;
|
|
pkt: TPktFile;
|
|
hdr: TPktHeaderInfo;
|
|
msg: TPktMessage;
|
|
fname: string;
|
|
begin
|
|
ForceDirectories(INBOUND);
|
|
for i := 1 to PACKETS do begin
|
|
fname := Format('%s/pkt%2.2d.pkt', [INBOUND, i]);
|
|
if FileExists(fname) then DeleteFile(fname);
|
|
hdr := TPktFile.BuildHeaderInfo(1, 1, 1, 0,
|
|
1, 1, 2, 0,
|
|
'');
|
|
pkt := TPktFile.CreateNew(fname, hdr);
|
|
try
|
|
for j := 1 to PER_PKT do begin
|
|
FillChar(msg, SizeOf(msg), 0);
|
|
msg.OrigNode := 1; msg.OrigNet := 1;
|
|
msg.DestNode := 2; msg.DestNet := 1;
|
|
msg.Attr := 0;
|
|
msg.Cost := 0;
|
|
msg.DateTime := '01 Apr 26 12:00:00';
|
|
msg.WhoTo := 'All';
|
|
msg.WhoFrom := Format('pkt%d', [i]);
|
|
msg.Subject := Format('pkt=%d msg=%d', [i, j]);
|
|
msg.Body := Format('AREA:TEST'#13'body %d/%d', [i, j]) + #13;
|
|
pkt.WriteMessage(msg);
|
|
end;
|
|
pkt.WriteTerminator;
|
|
finally
|
|
pkt.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure RunBatch;
|
|
var
|
|
runner: TBatchRunner;
|
|
processed: longint;
|
|
base: TMessageBase;
|
|
begin
|
|
TestBegin(Format('Batch toss: %d pkts * %d msgs, %d threads',
|
|
[PACKETS, PER_PKT, THREADS]));
|
|
|
|
{ Fresh scratch }
|
|
ForceDirectories(SCRATCH);
|
|
if FileExists(DESTBASE + '.jhr') then DeleteFile(DESTBASE + '.jhr');
|
|
if FileExists(DESTBASE + '.jdt') then DeleteFile(DESTBASE + '.jdt');
|
|
if FileExists(DESTBASE + '.jdx') then DeleteFile(DESTBASE + '.jdx');
|
|
if FileExists(DESTBASE + '.jlr') then DeleteFile(DESTBASE + '.jlr');
|
|
|
|
{ Pre-create an empty JAM base so GetOrCreateBase(momReadWrite)
|
|
has files to open. }
|
|
base := MessageBaseOpen(mbfJam, DESTBASE, momCreate);
|
|
try
|
|
AssertTrue('Pre-create dest base', base.Open);
|
|
finally
|
|
base.Close;
|
|
base.Free;
|
|
end;
|
|
|
|
GeneratePackets;
|
|
|
|
runner := TBatchRunner.Create;
|
|
InitCriticalSection(runner.CS);
|
|
runner.DestPath := DESTBASE;
|
|
runner.Written := 0;
|
|
runner.Batch := TPacketBatch.Create(INBOUND, THREADS);
|
|
runner.Batch.Processor := @runner.OnMessage;
|
|
|
|
processed := runner.Batch.Run;
|
|
|
|
AssertEquals('Packets processed', PACKETS, processed);
|
|
AssertEquals('Messages written', PACKETS * PER_PKT, runner.Written);
|
|
|
|
runner.Batch.Free;
|
|
DoneCriticalSection(runner.CS);
|
|
runner.Free;
|
|
|
|
{ Reopen dest base; verify count matches what we claim we wrote. }
|
|
base := MessageBaseOpen(mbfJam, DESTBASE, momReadOnly);
|
|
try
|
|
AssertTrue('Open dest (read)', base.Open);
|
|
AssertEquals('Dest message count',
|
|
PACKETS * PER_PKT, base.MessageCount);
|
|
finally
|
|
base.Close;
|
|
base.Free;
|
|
end;
|
|
|
|
TestOK;
|
|
end;
|
|
|
|
begin
|
|
WriteLn('fpc-msgbase: concurrent batch test');
|
|
WriteLn;
|
|
RunBatch;
|
|
Halt(TestsSummary);
|
|
end.
|