From 7fc18f6fd774503c7a25d50f0302cc55e16fb3a5 Mon Sep 17 00:00:00 2001 From: Doug Hoyte Date: Sat, 2 Sep 2023 05:12:00 -0400 Subject: [PATCH] refactor test suites to allow testing one implementation against another --- test/cpp/harness.cpp | 95 +++++++++------------- test/fuzz.pl | 182 +++++++++++++++++++++++++++---------------- test/js/harness.js | 80 ++++++++----------- test/test.pl | 8 +- 4 files changed, 187 insertions(+), 178 deletions(-) diff --git a/test/cpp/harness.cpp b/test/cpp/harness.cpp index 05e845d..e70c880 100644 --- a/test/cpp/harness.cpp +++ b/test/cpp/harness.cpp @@ -25,13 +25,10 @@ std::vector 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 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 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; } diff --git a/test/fuzz.pl b/test/fuzz.pl index 14ea445..355087b 100755 --- a/test/fuzz.pl +++ b/test/fuzz.pl @@ -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 " 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; diff --git a/test/js/harness.js b/test/js/harness.js index b9012d1..f5a1d4a 100644 --- a/test/js/harness.js +++ b/test/js/harness.js @@ -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); +}); diff --git a/test/test.pl b/test/test.pl index 17a22cb..59ca00e 100755 --- a/test/test.pl +++ b/test/test.pl @@ -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"); }