refactor test suites to allow testing one implementation against another

This commit is contained in:
Doug Hoyte 2023-09-02 05:12:00 -04:00
parent 4ef8d9ab5a
commit 7fc18f6fd7
4 changed files with 187 additions and 178 deletions

View File

@ -25,13 +25,10 @@ std::vector<std::string> split(const std::string &s, char delim) {
int main() {
const uint64_t idSize = 16;
uint64_t frameSizeLimit1 = 0, frameSizeLimit2 = 0;
if (::getenv("FRAMESIZELIMIT1")) frameSizeLimit1 = std::stoull(::getenv("FRAMESIZELIMIT1"));
if (::getenv("FRAMESIZELIMIT2")) frameSizeLimit2 = std::stoull(::getenv("FRAMESIZELIMIT2"));
uint64_t frameSizeLimit = 0;
if (::getenv("FRAMESIZELIMIT")) frameSizeLimit = std::stoull(::getenv("FRAMESIZELIMIT"));
// x1 is client, x2 is server
Negentropy x1(idSize, frameSizeLimit1);
Negentropy x2(idSize, frameSizeLimit2);
Negentropy ne(idSize, frameSizeLimit);
std::string line;
while (std::cin) {
@ -39,65 +36,43 @@ int main() {
if (!line.size()) continue;
auto items = split(line, ',');
if (items.size() != 3) throw hoytech::error("too few items");
int mode = std::stoi(items[0]);
uint64_t created = std::stoull(items[1]);
auto id = hoytech::from_hex(items[2]);
if (id.size() != idSize) throw hoytech::error("unexpected id size");
if (items[0] == "item") {
if (items.size() != 3) throw hoytech::error("wrong num of fields");
uint64_t created = std::stoull(items[1]);
auto id = hoytech::from_hex(items[2]);
ne.addItem(created, id);
} else if (items[0] == "seal") {
ne.seal();
} else if (items[0] == "initiate") {
auto q = ne.initiate();
if (frameSizeLimit && q.size() > frameSizeLimit) throw hoytech::error("frameSizeLimit exceeded");
std::cout << "msg," << hoytech::to_hex(q) << std::endl;
} else if (items[0] == "msg") {
std::string q;
if (items.size() >= 2) q = hoytech::from_hex(items[1]);
if (mode == 1) {
x1.addItem(created, id);
} else if (mode == 2) {
x2.addItem(created, id);
} else if (mode == 3) {
x1.addItem(created, id);
x2.addItem(created, id);
if (ne.isInitiator) {
std::vector<std::string> have, need;
q = ne.reconcile(q, have, need);
for (auto &id : have) std::cout << "have," << hoytech::to_hex(id) << "\n";
for (auto &id : need) std::cout << "need," << hoytech::to_hex(id) << "\n";
if (q.size() == 0) {
std::cout << "done" << std::endl;
continue;
}
} else {
q = ne.reconcile(q);
}
if (frameSizeLimit && q.size() > frameSizeLimit) throw hoytech::error("frameSizeLimit exceeded");
std::cout << "msg," << hoytech::to_hex(q) << std::endl;
} else {
throw hoytech::error("unexpected mode");
throw hoytech::error("unknown cmd: ", items[0]);
}
}
x1.seal();
x2.seal();
std::string q;
uint64_t round = 0;
uint64_t totalUp = 0;
uint64_t totalDown = 0;
while (1) {
// CLIENT -> SERVER
if (round == 0) {
q = x1.initiate();
} else {
std::vector<std::string> have, need;
q = x1.reconcile(q, have, need);
for (auto &id : have) std::cout << "xor,HAVE," << hoytech::to_hex(id) << "\n";
for (auto &id : need) std::cout << "xor,NEED," << hoytech::to_hex(id) << "\n";
}
if (q.size() == 0) break;
std::cerr << "[" << round << "] CLIENT -> SERVER: " << q.size() << " bytes" << std::endl;
totalUp += q.size();
if (frameSizeLimit1 && q.size() > frameSizeLimit1) throw hoytech::error("frameSizeLimit1 exceeded");
// SERVER -> CLIENT
q = x2.reconcile(q);
std::cerr << "[" << round << "] SERVER -> CLIENT: " << q.size() << " bytes" << std::endl;
totalDown += q.size();
if (frameSizeLimit2 && q.size() > frameSizeLimit2) throw hoytech::error("frameSizeLimit2 exceeded");
round++;
}
std::cerr << "UP: " << totalUp << " bytes, DOWN: " << totalDown << " bytes" << std::endl;
return 0;
}

View File

@ -1,21 +1,25 @@
#!/usr/bin/env perl
use strict;
$|++;
use IPC::Open2;
use Session::Token;
my $harnessType = shift || die "please provide harness type (cpp, js, etc)";
die "usage: $0 <lang1> <lang2>" if @ARGV < 2;
my $harnessCmd1 = harnessTypeToCmd(shift) || die "please provide harness type (cpp, js, etc)";
my $harnessCmd2 = harnessTypeToCmd(shift) || die "please provide harness type (cpp, js, etc)";
my $idSize = shift || 16;
sub harnessTypeToCmd {
my $harnessType = shift;
my $harnessCmd;
if ($harnessType eq 'cpp') {
return './cpp/harness';
} elsif ($harnessType eq 'js') {
return 'node js/harness.js';
}
if ($harnessType eq 'cpp') {
$harnessCmd = './cpp/harness';
} elsif ($harnessType eq 'js') {
$harnessCmd = 'node js/harness.js';
} else {
die "unknown harness type: $harnessType";
}
@ -24,8 +28,6 @@ srand($ENV{SEED} || 0);
my $stgen = Session::Token->new(seed => "\x00" x 1024, alphabet => '0123456789abcdef', length => $idSize * 2);
my $iters = $ENV{ITERS} // 1;
my $minRecs = $ENV{MIN_RECS} // 1;
my $maxRecs = $ENV{MAX_RECS} // 10_000;
die "MIN_RECS > MAX_RECS" if $minRecs > $maxRecs;
@ -44,67 +46,115 @@ my $prob3 = $ENV{P3} // 98;
}
for (my $i = 0; $i < $iters; $i++) {
my $ids1 = {};
my $ids2 = {};
my $ids1 = {};
my $ids2 = {};
my $pid = open2(my $outfile, my $infile, $harnessCmd);
my ($pid1, $pid2);
my ($infile1, $infile2);
my ($outfile1, $outfile2);
my $num = $minRecs + rnd($maxRecs - $minRecs);
for (1..$num) {
my $mode;
my $modeRnd = rand();
if ($modeRnd < $prob1) {
$mode = 1;
} elsif ($modeRnd < $prob1 + $prob2) {
$mode = 2;
} else {
$mode = 3;
}
my $created = 1677970534 + rnd($num);
my $id = $stgen->get;
$ids1->{$id} = 1 if $mode == 1 || $mode == 3;
$ids2->{$id} = 1 if $mode == 2 || $mode == 3;
print $infile "$mode,$created,$id\n";
}
close($infile);
while (<$outfile>) {
if (/^xor,(HAVE|NEED),(\w+)/) {
my ($action, $id) = ($1, $2);
if ($action eq 'NEED') {
die "duplicate insert of $action,$id" if $ids1->{$id};
$ids1->{$id} = 1;
} elsif ($action eq 'HAVE') {
die "duplicate insert of $action,$id" if $ids2->{$id};
$ids2->{$id} = 1;
}
}
}
waitpid($pid, 0);
my $child_exit_status = $?;
die "failure running test harness" if $child_exit_status;
for my $id (keys %$ids1) {
die "$id not in ids2" if !$ids2->{$id};
}
for my $id (keys %$ids2) {
die "$id not in ids1" if !$ids1->{$id};
}
print "\n-----------OK-----------\n";
{
local $ENV{FRAMESIZELIMIT};
$ENV{FRAMESIZELIMIT} = $ENV{FRAMESIZELIMIT1} if defined $ENV{FRAMESIZELIMIT1};
$pid1 = open2($outfile1, $infile1, $harnessCmd1);
}
{
local $ENV{FRAMESIZELIMIT};
$ENV{FRAMESIZELIMIT} = $ENV{FRAMESIZELIMIT2} if defined $ENV{FRAMESIZELIMIT2};
$pid2 = open2($outfile2, $infile2, $harnessCmd2);
}
my $num = $minRecs + rnd($maxRecs - $minRecs);
for (1..$num) {
my $created = 1677970534 + rnd($num);
my $id = $stgen->get;
my $modeRnd = rand();
if ($modeRnd < $prob1) {
print $infile1 "item,$created,$id\n";
$ids1->{$id} = 1;
} elsif ($modeRnd < $prob1 + $prob2) {
print $infile2 "item,$created,$id\n";
$ids2->{$id} = 1;
} else {
print $infile1 "item,$created,$id\n";
print $infile2 "item,$created,$id\n";
$ids1->{$id} = 1;
$ids2->{$id} = 1;
}
}
print $infile1 "seal\n";
print $infile2 "seal\n";
print $infile1 "initiate\n";
my $round = 0;
my $totalUp = 0;
my $totalDown = 0;
while (1) {
my $msg = <$outfile1>;
if ($msg =~ /^(have|need),(\w+)/) {
my ($action, $id) = ($1, $2);
if ($action eq 'need') {
die "duplicate insert of $action,$id" if $ids1->{$id};
$ids1->{$id} = 1;
} elsif ($action eq 'have') {
die "duplicate insert of $action,$id" if $ids2->{$id};
$ids2->{$id} = 1;
}
next;
} elsif ($msg =~ /^msg,(\w+)/) {
my $data = $1;
print $infile2 "msg,$data\n";
my $bytes = length($data) / 2;
$totalUp += $bytes;
print "[$round] CLIENT -> SERVER: $bytes bytes\n";
} elsif ($msg =~ /^done/) {
last;
} else {
die "unexpected line from 1: '$msg'";
}
$msg = <$outfile2>;
if ($msg =~ /^msg,(\w*)/) {
my $data = $1;
print $infile1 "msg,$data\n";
my $bytes = length($data) / 2;
$totalDown += $bytes;
print "[$round] SERVER -> CLIENT: $bytes bytes\n";
} else {
die "unexpected line from 2: $msg";
}
$round++;
}
kill 'KILL', $pid1, $pid2;
for my $id (keys %$ids1) {
die "$id not in ids2" if !$ids2->{$id};
}
for my $id (keys %$ids2) {
die "$id not in ids1" if !$ids1->{$id};
}
print "UP: $totalUp bytes, DOWN: $totalDown bytes\n";
print "\n-----------OK-----------\n";
sub rnd {
my $n = shift;

View File

@ -3,66 +3,50 @@ const Negentropy = require('../../js/Negentropy.js');
const idSize = 16;
let frameSizeLimit = 0;
if (process.env.FRAMESIZELIMIT) frameSizeLimit = parseInt(process.env.FRAMESIZELIMIT);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
});
let n = 0;
let x1 = new Negentropy(idSize);
let x2 = new Negentropy(idSize);
let ne = new Negentropy(idSize, frameSizeLimit);
rl.on('line', (line) => {
let items = line.split(',');
if (items.length !== 3) throw Error("too few items");
let mode = parseInt(items[0]);
let created = parseInt(items[1]);
let id = items[2].trim();
if (id.length !== idSize*2) throw Error(`id should be ${idSize} bytes`);
if (mode === 1) {
x1.addItem(created, id);
} else if (mode === 2) {
x2.addItem(created, id);
} else if (mode === 3) {
x1.addItem(created, id);
x2.addItem(created, id);
} else {
throw Error("unexpected mode");
}
n++;
});
rl.once('close', () => {
x1.seal();
x2.seal();
let q;
let round = 0;
while (true) {
if (round === 0) {
q = x1.initiate();
} else {
let [newQ, haveIds, needIds] = x1.reconcile(q);
q = newQ;
for (let id of haveIds) console.log(`xor,HAVE,${id}`);
for (let id of needIds) console.log(`xor,NEED,${id}`);
}
if (q.length === 0) break;
console.error(`[${round}] CLIENT -> SERVER: ${q.length / 2} bytes`);
let [newQ, haveIds, needIds] = x2.reconcile(q);
if (items[0] == "item") {
if (items.length !== 3) throw Error("too few items");
let created = parseInt(items[1]);
let id = items[2].trim();
ne.addItem(created, id);
} else if (items[0] == "seal") {
ne.seal();
} else if (items[0] == "initiate") {
let q = ne.initiate();
if (frameSizeLimit && q.length/2 > frameSizeLimit) throw Error("frameSizeLimit exceeded");
console.log(`msg,${q}`);
} else if (items[0] == "msg") {
let q = items[1];
let [newQ, haveIds, needIds] = ne.reconcile(q);
q = newQ;
if (frameSizeLimit && q.length/2 > frameSizeLimit) throw Error("frameSizeLimit exceeded");
console.error(`[${round}] SERVER -> CLIENT: ${q.length / 2} bytes`);
for (let id of haveIds) console.log(`have,${id}`);
for (let id of needIds) console.log(`need,${id}`);
round++;
if (ne.isInitiator && q.length === 0) {
console.log(`done`);
} else {
console.log(`msg,${q}`);
}
} else {
throw Error(`unknown cmd: ${items[0]}`);
}
});
rl.on('close', () => {
process.exit(0);
});

View File

@ -12,16 +12,16 @@ foreach my $lang (split /,/, $langs) {
note("------LANG $lang ------");
note("Full upload");
run("RECS=100000 FRAMESIZELIMIT1=60000 FRAMESIZELIMIT2=500000 P1=1 P2=0 P3=0 perl fuzz.pl $lang");
run("RECS=100000 FRAMESIZELIMIT1=60000 FRAMESIZELIMIT2=500000 P1=1 P2=0 P3=0 perl fuzz.pl $lang $lang");
note("Full download");
run("RECS=100000 FRAMESIZELIMIT1=60000 FRAMESIZELIMIT2=500000 P1=0 P2=1 P3=0 perl fuzz.pl $lang");
run("RECS=100000 FRAMESIZELIMIT1=60000 FRAMESIZELIMIT2=500000 P1=0 P2=1 P3=0 perl fuzz.pl $lang $lang");
note("Identical DBs");
run("RECS=100000 FRAMESIZELIMIT1=60000 FRAMESIZELIMIT2=500000 P1=0 P2=0 P3=1 perl fuzz.pl $lang");
run("RECS=100000 FRAMESIZELIMIT1=60000 FRAMESIZELIMIT2=500000 P1=0 P2=0 P3=1 perl fuzz.pl $lang $lang");
note("Mixed");
run("RECS=100000 FRAMESIZELIMIT1=60000 FRAMESIZELIMIT2=500000 P1=1 P2=1 P3=5 perl fuzz.pl $lang");
run("RECS=100000 FRAMESIZELIMIT1=60000 FRAMESIZELIMIT2=500000 P1=1 P2=1 P3=5 perl fuzz.pl $lang $lang");
}