run-tests.pl 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741
  1. #! /usr/bin/perl -w
  2. #
  3. # Copyright (C) 2006,2013 Oswald Buddenhagen <ossi@users.sf.net>
  4. #
  5. # This program is free software; you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation; either version 2 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. #
  18. use warnings;
  19. use strict;
  20. use Cwd;
  21. use File::Path;
  22. use File::Temp 'tempdir';
  23. my $use_vg = $ENV{USE_VALGRIND};
  24. my $mbsync = getcwd()."/mbsync";
  25. if (!-d "tmp") {
  26. unlink "tmp";
  27. my $tdir = tempdir();
  28. symlink $tdir, "tmp" or die "Cannot symlink temp directory: $!\n";
  29. }
  30. chdir "tmp" or die "Cannot enter temp direcory.\n";
  31. sub show($$$);
  32. sub test($$$@);
  33. ################################################################################
  34. # Format of the test defs: [ master, slave, state ]
  35. # master/slave: [ maxuid, { seq, uid, flags }... ]
  36. # state: [ MaxPulledUid, MaxExpiredMasterUid, MaxPushedUid, { muid, suid, flags }... ]
  37. use enum qw(:=1 A..Z);
  38. sub mn($) { chr(64 + shift) }
  39. # generic syncing tests
  40. my @x01 = (
  41. [ 9,
  42. A, 1, "F", B, 2, "", C, 3, "FS", D, 4, "", E, 5, "T", F, 6, "F", G, 7, "FT", I, 9, "" ],
  43. [ 9,
  44. A, 1, "", B, 2, "F", C, 3, "F", D, 4, "", E, 5, "", G, 7, "", H, 8, "", J, 9, "" ],
  45. [ 8, 0, 0,
  46. 1, 1, "", 2, 2, "", 3, 3, "", 4, 4, "", 5, 5, "", 6, 6, "", 7, 7, "", 8, 8, "" ],
  47. );
  48. my @O01 = ("", "", "");
  49. #show("01", "01", "01");
  50. my @X01 = (
  51. [ 10,
  52. A, 1, "F", B, 2, "F", C, 3, "FS", D, 4, "", E, 5, "T", F, 6, "FT", G, 7, "FT", I, 9, "", J, 10, "" ],
  53. [ 10,
  54. A, 1, "F", B, 2, "F", C, 3, "FS", D, 4, "", E, 5, "T", G, 7, "FT", H, 8, "T", J, 9, "", I, 10, "" ],
  55. [ 9, 0, 9,
  56. 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 5, 5, "T", 6, 0, "", 7, 7, "FT", 0, 8, "", 10, 9, "", 9, 10, "" ],
  57. );
  58. test("full", \@x01, \@X01, @O01);
  59. my @O02 = ("", "", "Expunge Both\n");
  60. #show("01", "02", "02");
  61. my @X02 = (
  62. [ 10,
  63. A, 1, "F", B, 2, "F", C, 3, "FS", D, 4, "", I, 9, "", J, 10, "" ],
  64. [ 10,
  65. A, 1, "F", B, 2, "F", C, 3, "FS", D, 4, "", J, 9, "", I, 10, "" ],
  66. [ 9, 0, 9,
  67. 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 10, 9, "", 9, 10, "" ],
  68. );
  69. test("full + expunge both", \@x01, \@X02, @O02);
  70. my @O03 = ("", "", "Expunge Slave\n");
  71. #show("01", "03", "03");
  72. my @X03 = (
  73. [ 10,
  74. A, 1, "F", B, 2, "F", C, 3, "FS", D, 4, "", E, 5, "T", F, 6, "FT", G, 7, "FT", I, 9, "", J, 10, "" ],
  75. [ 10,
  76. A, 1, "F", B, 2, "F", C, 3, "FS", D, 4, "", J, 9, "", I, 10, "" ],
  77. [ 9, 0, 9,
  78. 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 5, 0, "T", 6, 0, "", 7, 0, "T", 10, 9, "", 9, 10, "" ],
  79. );
  80. test("full + expunge slave", \@x01, \@X03, @O03);
  81. my @O04 = ("", "", "Sync Pull\n");
  82. #show("01", "04", "04");
  83. my @X04 = (
  84. [ 9,
  85. A, 1, "F", B, 2, "", C, 3, "FS", D, 4, "", E, 5, "T", F, 6, "F", G, 7, "FT", I, 9, "" ],
  86. [ 10,
  87. A, 1, "F", B, 2, "F", C, 3, "FS", D, 4, "", E, 5, "T", G, 7, "FT", H, 8, "T", J, 9, "", I, 10, "" ],
  88. [ 9, 0, 0,
  89. 1, 1, "F", 2, 2, "", 3, 3, "FS", 4, 4, "", 5, 5, "T", 6, 6, "", 7, 7, "FT", 0, 8, "", 9, 10, "" ],
  90. );
  91. test("pull", \@x01, \@X04, @O04);
  92. my @O05 = ("", "", "Sync Flags\n");
  93. #show("01", "05", "05");
  94. my @X05 = (
  95. [ 9,
  96. A, 1, "F", B, 2, "F", C, 3, "FS", D, 4, "", E, 5, "T", F, 6, "F", G, 7, "FT", I, 9, "" ],
  97. [ 9,
  98. A, 1, "F", B, 2, "F", C, 3, "FS", D, 4, "", E, 5, "T", G, 7, "FT", H, 8, "", J, 9, "" ],
  99. [ 8, 0, 0,
  100. 1, 1, "F", 2, 2, "F", 3, 3, "FS", 4, 4, "", 5, 5, "T", 6, 6, "", 7, 7, "FT", 8, 8, "" ],
  101. );
  102. test("flags", \@x01, \@X05, @O05);
  103. my @O06 = ("", "", "Sync Delete\n");
  104. #show("01", "06", "06");
  105. my @X06 = (
  106. [ 9,
  107. A, 1, "F", B, 2, "", C, 3, "FS", D, 4, "", E, 5, "T", F, 6, "FT", G, 7, "FT", I, 9, "" ],
  108. [ 9,
  109. A, 1, "", B, 2, "F", C, 3, "F", D, 4, "", E, 5, "", G, 7, "", H, 8, "T", J, 9, "" ],
  110. [ 8, 0, 0,
  111. 1, 1, "", 2, 2, "", 3, 3, "", 4, 4, "", 5, 5, "", 6, 0, "", 7, 7, "", 0, 8, "" ],
  112. );
  113. test("deletions", \@x01, \@X06, @O06);
  114. my @O07 = ("", "", "Sync New\n");
  115. #show("01", "07", "07");
  116. my @X07 = (
  117. [ 10,
  118. A, 1, "F", B, 2, "", C, 3, "FS", D, 4, "", E, 5, "T", F, 6, "F", G, 7, "FT", I, 9, "", J, 10, "" ],
  119. [ 10,
  120. A, 1, "", B, 2, "F", C, 3, "F", D, 4, "", E, 5, "", G, 7, "", H, 8, "", J, 9, "", I, 10, "" ],
  121. [ 9, 0, 9,
  122. 1, 1, "", 2, 2, "", 3, 3, "", 4, 4, "", 5, 5, "", 6, 6, "", 7, 7, "", 8, 8, "", 10, 9, "", 9, 10, "" ],
  123. );
  124. test("new", \@x01, \@X07, @O07);
  125. my @O08 = ("", "", "Sync PushFlags PullDelete\n");
  126. #show("01", "08", "08");
  127. my @X08 = (
  128. [ 9,
  129. A, 1, "F", B, 2, "F", C, 3, "FS", D, 4, "", E, 5, "T", F, 6, "F", G, 7, "FT", I, 9, "" ],
  130. [ 9,
  131. A, 1, "", B, 2, "F", C, 3, "F", D, 4, "", E, 5, "", G, 7, "", H, 8, "T", J, 9, "" ],
  132. [ 8, 0, 0,
  133. 1, 1, "", 2, 2, "F", 3, 3, "F", 4, 4, "", 5, 5, "", 6, 6, "", 7, 7, "", 0, 8, "" ],
  134. );
  135. test("push flags + pull deletions", \@x01, \@X08, @O08);
  136. # size restriction tests
  137. my @x10 = (
  138. [ 2,
  139. A, 1, "", B, 2, "*" ],
  140. [ 1,
  141. C, 1, "*" ],
  142. [ 0, 0, 0,
  143. ],
  144. );
  145. my @O11 = ("MaxSize 1k\n", "MaxSize 1k\n", "");
  146. #show("10", "11", "11");
  147. my @X11 = (
  148. [ 2,
  149. A, 1, "", B, 2, "*" ],
  150. [ 2,
  151. C, 1, "*", A, 2, "" ],
  152. [ 2, 0, 1,
  153. 0, 1, "^", 1, 2, "", 2, 0, "^" ],
  154. );
  155. test("max size", \@x10, \@X11, @O11);
  156. my @O22 = ("", "MaxSize 1k\n", "");
  157. #show("11", "22", "22");
  158. my @X22 = (
  159. [ 3,
  160. A, 1, "", B, 2, "*", C, 3, "*" ],
  161. [ 2,
  162. C, 1, "*", A, 2, "" ],
  163. [ 2, 0, 1,
  164. 3, 1, "", 1, 2, "", 2, 0, "^" ],
  165. );
  166. test("slave max size", \@X11, \@X22, @O22);
  167. # expiration tests
  168. my @x30 = (
  169. [ 6,
  170. A, 1, "F", B, 2, "", C, 3, "S", D, 4, "", E, 5, "S", F, 6, "" ],
  171. [ 0,
  172. ],
  173. [ 0, 0, 0,
  174. ],
  175. );
  176. my @O31 = ("", "", "MaxMessages 3\n");
  177. #show("30", "31", "31");
  178. my @X31 = (
  179. [ 6,
  180. A, 1, "F", B, 2, "", C, 3, "S", D, 4, "", E, 5, "S", F, 6, "" ],
  181. [ 5,
  182. A, 1, "F", B, 2, "", D, 3, "", E, 4, "S", F, 5, "" ],
  183. [ 6, 3, 0,
  184. 1, 1, "F", 2, 2, "", 4, 3, "", 5, 4, "S", 6, 5, "" ],
  185. );
  186. test("max messages", \@x30, \@X31, @O31);
  187. my @O32 = ("", "", "MaxMessages 3\nExpireUnread yes\n");
  188. #show("30", "32", "32");
  189. my @X32 = (
  190. [ 6,
  191. A, 1, "F", B, 2, "", C, 3, "S", D, 4, "", E, 5, "S", F, 6, "" ],
  192. [ 4,
  193. A, 1, "F", D, 2, "", E, 3, "S", F, 4, "" ],
  194. [ 6, 3, 0,
  195. 1, 1, "F", 4, 2, "", 5, 3, "S", 6, 4, "" ],
  196. );
  197. test("max messages vs. unread", \@x30, \@X32, @O32);
  198. my @x50 = (
  199. [ 6,
  200. A, 1, "FS", B, 2, "FS", C, 3, "S", D, 4, "", E, 5, "", F, 6, "" ],
  201. [ 6,
  202. A, 1, "S", B, 2, "ST", D, 4, "", E, 5, "", F, 6, "" ],
  203. [ 6, 3, 0,
  204. 1, 1, "FS", 2, 2, "~S", 3, 3, "~S", 4, 4, "", 5, 5, "", 6, 6, "" ],
  205. );
  206. my @O51 = ("", "", "MaxMessages 3\nExpunge Both\n");
  207. #show("50", "51", "51");
  208. my @X51 = (
  209. [ 6,
  210. A, 1, "S", B, 2, "FS", C, 3, "S", D, 4, "", E, 5, "", F, 6, "" ],
  211. [ 6,
  212. B, 2, "FS", D, 4, "", E, 5, "", F, 6, "" ],
  213. [ 6, 3, 0,
  214. 2, 2, "FS", 4, 4, "", 5, 5, "", 6, 6, "" ],
  215. );
  216. test("max messages + expunge", \@x50, \@X51, @O51);
  217. ################################################################################
  218. print "OK.\n";
  219. exit 0;
  220. sub qm($)
  221. {
  222. shift;
  223. s/\\/\\\\/g;
  224. s/\"/\\"/g;
  225. s/\"/\\"/g;
  226. s/\n/\\n/g;
  227. return $_;
  228. }
  229. # $master, $slave, $channel
  230. sub writecfg($$$)
  231. {
  232. open(FILE, ">", ".mbsyncrc") or
  233. die "Cannot open .mbsyncrc.\n";
  234. print FILE
  235. "FSync no
  236. MaildirStore master
  237. Path ./
  238. Inbox ./master
  239. ".shift()."
  240. MaildirStore slave
  241. Path ./
  242. Inbox ./slave
  243. ".shift()."
  244. Channel test
  245. Master :master:
  246. Slave :slave:
  247. SyncState *
  248. ".shift();
  249. close FILE;
  250. }
  251. sub killcfg()
  252. {
  253. unlink $_ for (glob("*.log"));
  254. unlink ".mbsyncrc";
  255. }
  256. # $options
  257. sub runsync($$)
  258. {
  259. my ($flags, $file) = @_;
  260. my $cmd;
  261. if ($use_vg) {
  262. $cmd = "valgrind -q --error-exitcode=1 ";
  263. } else {
  264. $flags .= " -D";
  265. }
  266. $cmd .= "$mbsync -Z $flags -c .mbsyncrc test";
  267. open FILE, "$cmd 2>&1 |";
  268. my @out = <FILE>;
  269. close FILE or push(@out, $! ? "*** error closing mbsync: $!\n" : "*** mbsync exited with signal ".($?&127).", code ".($?>>8)."\n");
  270. if ($file) {
  271. open FILE, ">$file" or die("Cannot create $file: $!\n");
  272. print FILE @out;
  273. close FILE;
  274. }
  275. return $?, @out;
  276. }
  277. # $path
  278. sub readbox($)
  279. {
  280. my $bn = shift;
  281. (-d $bn) or
  282. die "No mailbox '$bn'.\n";
  283. (-d $bn."/tmp" and -d $bn."/new" and -d $bn."/cur") or
  284. die "Invalid mailbox '$bn'.\n";
  285. open(FILE, "<", $bn."/.uidvalidity") or die "Cannot read UID validity of mailbox '$bn'.\n";
  286. my $dummy = <FILE>;
  287. chomp(my $mu = <FILE>);
  288. close FILE;
  289. my %ms = ();
  290. for my $d ("cur", "new") {
  291. opendir(DIR, $bn."/".$d) or next;
  292. for my $f (grep(!/^\.\.?$/, readdir(DIR))) {
  293. my ($uid, $flg, $num);
  294. if ($f =~ /^\d+\.\d+_\d+\.[-[:alnum:]]+,U=(\d+):2,(.*)$/) {
  295. ($uid, $flg) = ($1, $2);
  296. } else {
  297. print STDERR "unrecognided file name '$f' in '$bn'.\n";
  298. exit 1;
  299. }
  300. open(FILE, "<", $bn."/".$d."/".$f) or die "Cannot read message '$f' in '$bn'.\n";
  301. my $sz = 0;
  302. while (<FILE>) {
  303. /^Subject: (\d+)$/ && ($num = $1);
  304. $sz += length($_);
  305. }
  306. close FILE;
  307. if (!defined($num)) {
  308. print STDERR "message '$f' in '$bn' has no identifier.\n";
  309. exit 1;
  310. }
  311. @{ $ms{$uid} } = ($num, $flg.($sz>1000?"*":""));
  312. }
  313. }
  314. return ($mu, %ms);
  315. }
  316. # $boxname
  317. # Output:
  318. # [ maxuid,
  319. # serial, uid, "flags", ... ],
  320. sub showbox($)
  321. {
  322. my ($bn) = @_;
  323. my ($mu, %ms) = readbox($bn);
  324. my @MS = ($mu);
  325. for my $uid (sort { $a <=> $b } keys %ms) {
  326. push @MS, $ms{$uid}[0], $uid, $ms{$uid}[1];
  327. }
  328. printbox($bn, @MS);
  329. }
  330. # $filename
  331. # Output:
  332. # [ maxuid[M], mmaxxuid, maxuid[S],
  333. # uid[M], uid[S], "flags", ... ],
  334. sub showstate($)
  335. {
  336. my ($fn) = @_;
  337. if (!open(FILE, "<", $fn)) {
  338. print STDERR " Cannot read sync state $fn: $!\n";
  339. return;
  340. }
  341. chomp(my @ls = <FILE>);
  342. close FILE;
  343. my %hdr;
  344. OUTER: while (1) {
  345. while (@ls) {
  346. $_ = shift(@ls);
  347. last OUTER if (!length($_));
  348. if (!/^([^ ]+) (\d+)$/) {
  349. print STDERR "Malformed sync state header entry: $_\n";
  350. close FILE;
  351. return;
  352. }
  353. $hdr{$1} = $2;
  354. }
  355. print STDERR "Unterminated sync state header.\n";
  356. close FILE;
  357. return;
  358. }
  359. my @T = ($hdr{'MaxPulledUid'} // "missing",
  360. $hdr{'MaxExpiredMasterUid'} // "0",
  361. $hdr{'MaxPushedUid'} // "missing");
  362. for (@ls) {
  363. /^(\d+) (\d+) (.*)$/;
  364. push @T, $1, $2, $3;
  365. }
  366. printstate(@T);
  367. }
  368. # $filename
  369. sub showchan($)
  370. {
  371. my ($fn) = @_;
  372. showbox("master");
  373. showbox("slave");
  374. showstate($fn);
  375. }
  376. # $source_state_name, $target_state_name, $configs_name
  377. sub show($$$)
  378. {
  379. my ($sx, $tx, $sfxn) = @_;
  380. my (@sp, @sfx);
  381. eval "\@sp = \@x$sx";
  382. eval "\@sfx = \@O$sfxn";
  383. mkchan($sp[0], $sp[1], @{ $sp[2] });
  384. print "my \@x$sx = (\n";
  385. showchan("slave/.mbsyncstate");
  386. print ");\n";
  387. &writecfg(@sfx);
  388. runsync("", "");
  389. killcfg();
  390. print "my \@X$tx = (\n";
  391. showchan("slave/.mbsyncstate");
  392. print ");\n";
  393. print "test(\"\", \\\@x$sx, \\\@X$tx, \@O$sfxn);\n\n";
  394. rmtree "slave";
  395. rmtree "master";
  396. }
  397. # $boxname, $maxuid, @msgs
  398. sub mkbox($$@)
  399. {
  400. my ($bn, $mu, @ms) = @_;
  401. rmtree($bn);
  402. (mkdir($bn) and mkdir($bn."/tmp") and mkdir($bn."/new") and mkdir($bn."/cur")) or
  403. die "Cannot create mailbox $bn.\n";
  404. open(FILE, ">", $bn."/.uidvalidity") or die "Cannot create UID validity for mailbox $bn.\n";
  405. print FILE "1\n$mu\n";
  406. close FILE;
  407. while (@ms) {
  408. my ($num, $uid, $flg) = (shift @ms, shift @ms, shift @ms);
  409. my $big = $flg =~ s/\*//;
  410. open(FILE, ">", $bn."/".($flg =~ /S/ ? "cur" : "new")."/0.1_".$num.".local,U=".$uid.":2,".$flg) or
  411. die "Cannot create message ".mn($num)." in mailbox $bn.\n";
  412. print FILE "From: foo\nTo: bar\nDate: Thu, 1 Jan 1970 00:00:00 +0000\nSubject: $num\n\n".(("A"x50)."\n")x($big*30);
  413. close FILE;
  414. }
  415. }
  416. # \@master, \@slave, @syncstate
  417. sub mkchan($$@)
  418. {
  419. my ($m, $s, @t) = @_;
  420. &mkbox("master", @{ $m });
  421. &mkbox("slave", @{ $s });
  422. open(FILE, ">", "slave/.mbsyncstate") or
  423. die "Cannot create sync state.\n";
  424. print FILE "MasterUidValidity 1\nMaxPulledUid ".shift(@t)."\n".
  425. "SlaveUidValidity 1\nMaxExpiredMasterUid ".shift(@t)."\nMaxPushedUid ".shift(@t)."\n\n";
  426. while (@t) {
  427. print FILE shift(@t)." ".shift(@t)." ".shift(@t)."\n";
  428. }
  429. close FILE;
  430. }
  431. # $boxname, $maxuid, @msgs
  432. sub ckbox($$@)
  433. {
  434. my ($bn, $MU, @MS) = @_;
  435. my ($mu, %ms) = readbox($bn);
  436. if ($mu != $MU) {
  437. print STDERR "MAXUID mismatch for '$bn' (got $mu, wanted $MU).\n";
  438. return 1;
  439. }
  440. while (@MS) {
  441. my ($num, $uid, $flg) = (shift @MS, shift @MS, shift @MS);
  442. my $m = delete $ms{$uid};
  443. if (!defined $m) {
  444. print STDERR "No message $bn:$uid.\n";
  445. return 1;
  446. }
  447. if ($$m[0] ne $num) {
  448. print STDERR "Subject mismatch for $bn:$uid.\n";
  449. return 1;
  450. }
  451. if ($$m[1] ne $flg) {
  452. print STDERR "Flag mismatch for $bn:$uid.\n";
  453. return 1;
  454. }
  455. }
  456. if (%ms) {
  457. print STDERR "Excess messages in '$bn': ".join(", ", sort({$a <=> $b } keys(%ms))).".\n";
  458. return 1;
  459. }
  460. return 0;
  461. }
  462. # $filename, @syncstate
  463. sub ckstate($@)
  464. {
  465. my ($fn, $mmaxuid, $mmaxxuid, $smaxuid, @T) = @_;
  466. my %hdr;
  467. $hdr{'MasterUidValidity'} = "1";
  468. $hdr{'SlaveUidValidity'} = "1";
  469. $hdr{'MaxPulledUid'} = $mmaxuid;
  470. $hdr{'MaxPushedUid'} = $smaxuid;
  471. $hdr{'MaxExpiredMasterUid'} = $mmaxxuid if ($mmaxxuid ne 0);
  472. open(FILE, "<", $fn) or die "Cannot read sync state $fn.\n";
  473. chomp(my @ls = <FILE>);
  474. close FILE;
  475. OUTER: while (1) {
  476. while (@ls) {
  477. my $l = shift(@ls);
  478. last OUTER if (!length($l));
  479. if ($l !~ /^([^ ]+) (\d+)$/) {
  480. print STDERR "Malformed sync state header entry: $l\n";
  481. return 1;
  482. }
  483. my $want = delete $hdr{$1};
  484. if (!defined($want)) {
  485. print STDERR "Unexpected sync state header entry: $1\n";
  486. return 1;
  487. }
  488. if ($2 != $want) {
  489. print STDERR "Sync state header entry $1 mismatch: got $2, wanted $want\n";
  490. return 1;
  491. }
  492. }
  493. print STDERR "Unterminated sync state header.\n";
  494. return 1;
  495. }
  496. my @ky = keys %hdr;
  497. if (@ky) {
  498. print STDERR "Keys missing from sync state header: @ky\n";
  499. return 1;
  500. }
  501. for my $l (@ls) {
  502. if (!@T) {
  503. print STDERR "Excess sync state entry: '$l'.\n";
  504. return 1;
  505. }
  506. my $xl = shift(@T)." ".shift(@T)." ".shift(@T);
  507. if ($l ne $xl) {
  508. print STDERR "Sync state entry mismatch: '$l' instead of '$xl'.\n";
  509. return 1;
  510. }
  511. }
  512. if (@T) {
  513. print STDERR "Missing sync state entry: '".shift(@T)." ".shift(@T)." ".shift(@T)."'.\n";
  514. return 1;
  515. }
  516. return 0;
  517. }
  518. # $statefile, \@chan_state
  519. sub ckchan($$)
  520. {
  521. my ($F, $cs) = @_;
  522. my $rslt = ckstate($F, @{ $$cs[2] });
  523. $rslt |= &ckbox("master", @{ $$cs[0] });
  524. $rslt |= &ckbox("slave", @{ $$cs[1] });
  525. return $rslt;
  526. }
  527. sub printbox($$@)
  528. {
  529. my ($bn, $mu, @ms) = @_;
  530. print " [ $mu,\n ";
  531. my $frst = 1;
  532. while (@ms) {
  533. if ($frst) {
  534. $frst = 0;
  535. } else {
  536. print ", ";
  537. }
  538. print mn(shift(@ms)).", ".shift(@ms).", \"".shift(@ms)."\"";
  539. }
  540. print " ],\n";
  541. }
  542. # @syncstate
  543. sub printstate(@)
  544. {
  545. my (@t) = @_;
  546. print " [ ".shift(@t).", ".shift(@t).", ".shift(@t).",\n ";
  547. my $frst = 1;
  548. while (@t) {
  549. if ($frst) {
  550. $frst = 0;
  551. } else {
  552. print ", ";
  553. }
  554. print((shift(@t) // "??").", ".(shift(@t) // "??").", \"".(shift(@t) // "??")."\"");
  555. }
  556. print " ],\n";
  557. }
  558. # \@chan_state
  559. sub printchan($)
  560. {
  561. my ($cs) = @_;
  562. &printbox("master", @{ $$cs[0] });
  563. &printbox("slave", @{ $$cs[1] });
  564. printstate(@{ $$cs[2] });
  565. }
  566. sub readfile($)
  567. {
  568. my ($file) = @_;
  569. open(FILE, $file) or return;
  570. my @nj = <FILE>;
  571. close FILE;
  572. return @nj;
  573. }
  574. # $title, \@source_state, \@target_state, @channel_configs
  575. sub test($$$@)
  576. {
  577. my ($ttl, $sx, $tx, @sfx) = @_;
  578. return 0 if (scalar(@ARGV) && !grep { $_ eq $ttl } @ARGV);
  579. print "Testing: ".$ttl." ...\n";
  580. &writecfg(@sfx);
  581. mkchan($$sx[0], $$sx[1], @{ $$sx[2] });
  582. my ($xc, @ret) = runsync("-J", "1-initial.log");
  583. if ($xc || ckchan("slave/.mbsyncstate.new", $tx)) {
  584. print "Input:\n";
  585. printchan($sx);
  586. print "Options:\n";
  587. print " [ ".join(", ", map('"'.qm($_).'"', @sfx))." ]\n";
  588. if (!$xc) {
  589. print "Expected result:\n";
  590. printchan($tx);
  591. print "Actual result:\n";
  592. showchan("slave/.mbsyncstate.new");
  593. }
  594. print "Debug output:\n";
  595. print @ret;
  596. exit 1;
  597. }
  598. my @nj = readfile("slave/.mbsyncstate.journal");
  599. my ($jxc, @jret) = runsync("-0 --no-expunge", "2-replay.log");
  600. if ($jxc || ckstate("slave/.mbsyncstate", @{ $$tx[2] })) {
  601. print "Journal replay failed.\n";
  602. print "Options:\n";
  603. print " [ ".join(", ", map('"'.qm($_).'"', @sfx))." ], [ \"-0\", \"--no-expunge\" ]\n";
  604. print "Old State:\n";
  605. printstate(@{ $$sx[2] });
  606. print "Journal:\n".join("", @nj)."\n";
  607. if (!$jxc) {
  608. print "Expected New State:\n";
  609. printstate(@{ $$tx[2] });
  610. print "New State:\n";
  611. showstate("slave/.mbsyncstate");
  612. }
  613. print "Debug output:\n";
  614. print @jret;
  615. exit 1;
  616. }
  617. my ($ixc, @iret) = runsync("", "3-verify.log");
  618. if ($ixc || ckchan("slave/.mbsyncstate", $tx)) {
  619. print "Idempotence verification run failed.\n";
  620. print "Input == Expected result:\n";
  621. printchan($tx);
  622. print "Options:\n";
  623. print " [ ".join(", ", map('"'.qm($_).'"', @sfx))." ]\n";
  624. if (!$ixc) {
  625. print "Actual result:\n";
  626. showchan("slave/.mbsyncstate");
  627. }
  628. print "Debug output:\n";
  629. print @iret;
  630. exit 1;
  631. }
  632. rmtree "slave";
  633. rmtree "master";
  634. my $njl = (@nj - 1) * 2;
  635. for (my $l = 2; $l < $njl; $l++) {
  636. mkchan($$sx[0], $$sx[1], @{ $$sx[2] });
  637. my ($nxc, @nret) = runsync("-J$l", "4-interrupt.log");
  638. if ($nxc != (100 + ($l & 1)) << 8) {
  639. print "Interrupting at step $l/$njl failed.\n";
  640. print "Debug output:\n";
  641. print @nret;
  642. exit 1;
  643. }
  644. ($nxc, @nret) = runsync("-J", "5-resume.log");
  645. if ($nxc || ckchan("slave/.mbsyncstate.new", $tx)) {
  646. print "Resuming from step $l/$njl failed.\n";
  647. print "Input:\n";
  648. printchan($sx);
  649. print "Options:\n";
  650. print " [ ".join(", ", map('"'.qm($_).'"', @sfx))." ]\n";
  651. my @nnj = readfile("slave/.mbsyncstate.journal");
  652. print "Journal:\n".join("", @nnj[0..($l / 2 - 1)])."-------\n".join("", @nnj[($l / 2)..$#nnj])."\n";
  653. print "Full journal:\n".join("", @nj)."\n";
  654. if (!$nxc) {
  655. print "Expected result:\n";
  656. printchan($tx);
  657. print "Actual result:\n";
  658. showchan("slave/.mbsyncstate.new");
  659. }
  660. print "Debug output:\n";
  661. print @nret;
  662. exit 1;
  663. }
  664. rmtree "slave";
  665. rmtree "master";
  666. }
  667. killcfg();
  668. }