run-tests.pl 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790
  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: [ far, near, state ]
  35. # far/near: [ maxuid, { seq, uid, flags }... ]
  36. # state: [ MaxPulledUid, MaxExpiredFarUid, 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. [ 10, 0, 10,
  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. [ 10, 0, 10,
  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 Near\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. [ 10, 0, 10,
  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 near side", \@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. [ 10, 0, 10,
  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", "Expunge Near");
  146. #show("10", "11", "11");
  147. my @X11 = (
  148. [ 3,
  149. A, 1, "", B, 2, "*", C, 3, "?" ],
  150. [ 3,
  151. C, 1, "*", A, 2, "", B, 3, "?" ],
  152. [ 3, 0, 3,
  153. 3, 1, "<", 1, 2, "", 2, 3, ">" ],
  154. );
  155. test("max size", \@x10, \@X11, @O11);
  156. my @x22 = (
  157. [ 3,
  158. A, 1, "", B, 2, "*", C, 3, "?" ],
  159. [ 3,
  160. C, 1, "F*", A, 2, "", B, 3, "F?" ],
  161. [ 3, 0, 3,
  162. 3, 1, "<", 1, 2, "", 2, 3, ">" ],
  163. );
  164. #show("22", "22", "11");
  165. my @X22 = (
  166. [ 4,
  167. A, 1, "", B, 2, "*", C, 3, "T?", C, 4, "F*" ],
  168. [ 4,
  169. C, 1, "F*", A, 2, "", B, 4, "*" ],
  170. [ 4, 0, 4,
  171. 4, 1, "F", 3, 0, "T", 1, 2, "", 2, 4, "" ],
  172. );
  173. test("max size + flagging", \@x22, \@X22, @O11);
  174. my @x23 = (
  175. [ 2,
  176. A, 1, "", B, 2, "F*" ],
  177. [ 1,
  178. C, 1, "F*" ],
  179. [ 0, 0, 0,
  180. ],
  181. );
  182. my @X23 = (
  183. [ 3,
  184. A, 1, "", B, 2, "F*", C, 3, "F*" ],
  185. [ 3,
  186. C, 1, "F*", A, 2, "", B, 3, "F*" ],
  187. [ 3, 0, 3,
  188. 3, 1, "F", 1, 2, "", 2, 3, "F" ]
  189. );
  190. test("max size + initial flagging", \@x23, \@X23, @O11);
  191. my @x24 = (
  192. [ 3,
  193. A, 1, "", B, 2, "*", C, 3, "F*" ],
  194. [ 1,
  195. A, 1, "" ],
  196. [ 3, 0, 1,
  197. 1, 1, "", 2, 0, "^", 3, 0, "^" ],
  198. );
  199. my @X24 = (
  200. [ 3,
  201. A, 1, "", B, 2, "*", C, 3, "F*" ],
  202. [ 3,
  203. A, 1, "", B, 2, "?", C, 3, "F*" ],
  204. [ 3, 0, 3,
  205. 1, 1, "", 2, 2, ">", 3, 3, "F" ],
  206. );
  207. test("max size (pre-1.4 legacy)", \@x24, \@X24, @O11);
  208. # expiration tests
  209. my @x30 = (
  210. [ 6,
  211. A, 1, "F", B, 2, "", C, 3, "S", D, 4, "", E, 5, "S", F, 6, "" ],
  212. [ 0,
  213. ],
  214. [ 0, 0, 0,
  215. ],
  216. );
  217. my @O31 = ("", "", "MaxMessages 3\n");
  218. #show("30", "31", "31");
  219. my @X31 = (
  220. [ 6,
  221. A, 1, "F", B, 2, "", C, 3, "S", D, 4, "", E, 5, "S", F, 6, "" ],
  222. [ 5,
  223. A, 1, "F", B, 2, "", D, 3, "", E, 4, "S", F, 5, "" ],
  224. [ 6, 3, 5,
  225. 1, 1, "F", 2, 2, "", 4, 3, "", 5, 4, "S", 6, 5, "" ],
  226. );
  227. test("max messages", \@x30, \@X31, @O31);
  228. my @O32 = ("", "", "MaxMessages 3\nExpireUnread yes\n");
  229. #show("30", "32", "32");
  230. my @X32 = (
  231. [ 6,
  232. A, 1, "F", B, 2, "", C, 3, "S", D, 4, "", E, 5, "S", F, 6, "" ],
  233. [ 4,
  234. A, 1, "F", D, 2, "", E, 3, "S", F, 4, "" ],
  235. [ 6, 3, 4,
  236. 1, 1, "F", 4, 2, "", 5, 3, "S", 6, 4, "" ],
  237. );
  238. test("max messages vs. unread", \@x30, \@X32, @O32);
  239. my @x50 = (
  240. [ 6,
  241. A, 1, "FS", B, 2, "FS", C, 3, "S", D, 4, "", E, 5, "", F, 6, "" ],
  242. [ 6,
  243. A, 1, "S", B, 2, "ST", D, 4, "", E, 5, "", F, 6, "" ],
  244. [ 6, 3, 0,
  245. 1, 1, "FS", 2, 2, "~S", 3, 3, "~S", 4, 4, "", 5, 5, "", 6, 6, "" ],
  246. );
  247. my @O51 = ("", "", "MaxMessages 3\nExpunge Both\n");
  248. #show("50", "51", "51");
  249. my @X51 = (
  250. [ 6,
  251. A, 1, "S", B, 2, "FS", C, 3, "S", D, 4, "", E, 5, "", F, 6, "" ],
  252. [ 6,
  253. B, 2, "FS", D, 4, "", E, 5, "", F, 6, "" ],
  254. [ 6, 3, 6,
  255. 2, 2, "FS", 4, 4, "", 5, 5, "", 6, 6, "" ],
  256. );
  257. test("max messages + expunge", \@x50, \@X51, @O51);
  258. ################################################################################
  259. print "OK.\n";
  260. exit 0;
  261. sub qm($)
  262. {
  263. shift;
  264. s/\\/\\\\/g;
  265. s/\"/\\"/g;
  266. s/\"/\\"/g;
  267. s/\n/\\n/g;
  268. return $_;
  269. }
  270. # $far, $near, $channel
  271. sub writecfg($$$)
  272. {
  273. open(FILE, ">", ".mbsyncrc") or
  274. die "Cannot open .mbsyncrc.\n";
  275. print FILE
  276. "FSync no
  277. MaildirStore far
  278. Path ./
  279. Inbox ./far
  280. ".shift()."
  281. MaildirStore near
  282. Path ./
  283. Inbox ./near
  284. ".shift()."
  285. Channel test
  286. Far :far:
  287. Near :near:
  288. SyncState *
  289. ".shift();
  290. close FILE;
  291. }
  292. sub killcfg()
  293. {
  294. unlink $_ for (glob("*.log"));
  295. unlink ".mbsyncrc";
  296. }
  297. # $options
  298. sub runsync($$)
  299. {
  300. my ($flags, $file) = @_;
  301. my $cmd;
  302. if ($use_vg) {
  303. $cmd = "valgrind -q --error-exitcode=1 ";
  304. } else {
  305. $flags .= " -D";
  306. }
  307. $cmd .= "$mbsync -Z $flags -c .mbsyncrc test";
  308. open FILE, "$cmd 2>&1 |";
  309. my @out = <FILE>;
  310. close FILE or push(@out, $! ? "*** error closing mbsync: $!\n" : "*** mbsync exited with signal ".($?&127).", code ".($?>>8)."\n");
  311. if ($file) {
  312. open FILE, ">$file" or die("Cannot create $file: $!\n");
  313. print FILE @out;
  314. close FILE;
  315. }
  316. return $?, @out;
  317. }
  318. # $path
  319. sub readbox($)
  320. {
  321. my $bn = shift;
  322. (-d $bn) or
  323. die "No mailbox '$bn'.\n";
  324. (-d $bn."/tmp" and -d $bn."/new" and -d $bn."/cur") or
  325. die "Invalid mailbox '$bn'.\n";
  326. open(FILE, "<", $bn."/.uidvalidity") or die "Cannot read UID validity of mailbox '$bn'.\n";
  327. my $dummy = <FILE>;
  328. chomp(my $mu = <FILE>);
  329. close FILE;
  330. my %ms = ();
  331. for my $d ("cur", "new") {
  332. opendir(DIR, $bn."/".$d) or next;
  333. for my $f (grep(!/^\.\.?$/, readdir(DIR))) {
  334. my ($uid, $flg, $ph, $num);
  335. if ($f =~ /^\d+\.\d+_\d+\.[-[:alnum:]]+,U=(\d+):2,(.*)$/) {
  336. ($uid, $flg) = ($1, $2);
  337. } else {
  338. print STDERR "unrecognided file name '$f' in '$bn'.\n";
  339. exit 1;
  340. }
  341. open(FILE, "<", $bn."/".$d."/".$f) or die "Cannot read message '$f' in '$bn'.\n";
  342. my $sz = 0;
  343. while (<FILE>) {
  344. /^Subject: (\[placeholder\] )?(\d+)$/ && ($ph = defined($1), $num = $2);
  345. $sz += length($_);
  346. }
  347. close FILE;
  348. if (!defined($num)) {
  349. print STDERR "message '$f' in '$bn' has no identifier.\n";
  350. exit 1;
  351. }
  352. @{ $ms{$uid} } = ($num, $flg.($sz>1000?"*":"").($ph?"?":""));
  353. }
  354. }
  355. return ($mu, %ms);
  356. }
  357. # $boxname
  358. # Output:
  359. # [ maxuid,
  360. # serial, uid, "flags", ... ],
  361. sub showbox($)
  362. {
  363. my ($bn) = @_;
  364. my ($mu, %ms) = readbox($bn);
  365. my @MS = ($mu);
  366. for my $uid (sort { $a <=> $b } keys %ms) {
  367. push @MS, $ms{$uid}[0], $uid, $ms{$uid}[1];
  368. }
  369. printbox($bn, @MS);
  370. }
  371. # $filename
  372. # Output:
  373. # [ maxuid[F], maxxfuid, maxuid[N],
  374. # uid[F], uid[N], "flags", ... ],
  375. sub showstate($)
  376. {
  377. my ($fn) = @_;
  378. if (!open(FILE, "<", $fn)) {
  379. print STDERR " Cannot read sync state $fn: $!\n";
  380. return;
  381. }
  382. chomp(my @ls = <FILE>);
  383. close FILE;
  384. my %hdr;
  385. OUTER: while (1) {
  386. while (@ls) {
  387. $_ = shift(@ls);
  388. last OUTER if (!length($_));
  389. if (!/^([^ ]+) (\d+)$/) {
  390. print STDERR "Malformed sync state header entry: $_\n";
  391. close FILE;
  392. return;
  393. }
  394. $hdr{$1} = $2;
  395. }
  396. print STDERR "Unterminated sync state header.\n";
  397. close FILE;
  398. return;
  399. }
  400. my @T = ($hdr{'MaxPulledUid'} // "missing",
  401. $hdr{'MaxExpiredFarUid'} // "0",
  402. $hdr{'MaxPushedUid'} // "missing");
  403. for (@ls) {
  404. /^(\d+) (\d+) (.*)$/;
  405. push @T, $1, $2, $3;
  406. }
  407. printstate(@T);
  408. }
  409. # $filename
  410. sub showchan($)
  411. {
  412. my ($fn) = @_;
  413. showbox("far");
  414. showbox("near");
  415. showstate($fn);
  416. }
  417. # $source_state_name, $target_state_name, $configs_name
  418. sub show($$$)
  419. {
  420. my ($sx, $tx, $sfxn) = @_;
  421. my (@sp, @sfx);
  422. eval "\@sp = \@x$sx";
  423. eval "\@sfx = \@O$sfxn";
  424. mkchan($sp[0], $sp[1], @{ $sp[2] });
  425. print "my \@x$sx = (\n";
  426. showchan("near/.mbsyncstate");
  427. print ");\n";
  428. &writecfg(@sfx);
  429. runsync("", "");
  430. killcfg();
  431. print "my \@X$tx = (\n";
  432. showchan("near/.mbsyncstate");
  433. print ");\n";
  434. print "test(\"\", \\\@x$sx, \\\@X$tx, \@O$sfxn);\n\n";
  435. rmtree "near";
  436. rmtree "far";
  437. }
  438. # $boxname, $maxuid, @msgs
  439. sub mkbox($$@)
  440. {
  441. my ($bn, $mu, @ms) = @_;
  442. rmtree($bn);
  443. (mkdir($bn) and mkdir($bn."/tmp") and mkdir($bn."/new") and mkdir($bn."/cur")) or
  444. die "Cannot create mailbox $bn.\n";
  445. open(FILE, ">", $bn."/.uidvalidity") or die "Cannot create UID validity for mailbox $bn.\n";
  446. print FILE "1\n$mu\n";
  447. close FILE;
  448. while (@ms) {
  449. my ($num, $uid, $flg) = (shift @ms, shift @ms, shift @ms);
  450. my $big = $flg =~ s/\*//;
  451. my $ph = $flg =~ s/\?//;
  452. open(FILE, ">", $bn."/".($flg =~ /S/ ? "cur" : "new")."/0.1_".$num.".local,U=".$uid.":2,".$flg) or
  453. die "Cannot create message ".mn($num)." in mailbox $bn.\n";
  454. print FILE "From: foo\nTo: bar\nDate: Thu, 1 Jan 1970 00:00:00 +0000\nSubject: ".($ph?"[placeholder] ":"").$num."\n\n".(("A"x50)."\n")x($big*30);
  455. close FILE;
  456. }
  457. }
  458. # \@far, \@near, @syncstate
  459. sub mkchan($$@)
  460. {
  461. my ($m, $s, @t) = @_;
  462. &mkbox("far", @{ $m });
  463. &mkbox("near", @{ $s });
  464. open(FILE, ">", "near/.mbsyncstate") or
  465. die "Cannot create sync state.\n";
  466. print FILE "FarUidValidity 1\nMaxPulledUid ".shift(@t)."\n".
  467. "NearUidValidity 1\nMaxExpiredFarUid ".shift(@t)."\nMaxPushedUid ".shift(@t)."\n\n";
  468. while (@t) {
  469. print FILE shift(@t)." ".shift(@t)." ".shift(@t)."\n";
  470. }
  471. close FILE;
  472. }
  473. # $boxname, $maxuid, @msgs
  474. sub ckbox($$@)
  475. {
  476. my ($bn, $MU, @MS) = @_;
  477. my ($mu, %ms) = readbox($bn);
  478. if ($mu != $MU) {
  479. print STDERR "MAXUID mismatch for '$bn' (got $mu, wanted $MU).\n";
  480. return 1;
  481. }
  482. while (@MS) {
  483. my ($num, $uid, $flg) = (shift @MS, shift @MS, shift @MS);
  484. my $m = delete $ms{$uid};
  485. if (!defined $m) {
  486. print STDERR "No message $bn:$uid.\n";
  487. return 1;
  488. }
  489. if ($$m[0] ne $num) {
  490. print STDERR "Subject mismatch for $bn:$uid.\n";
  491. return 1;
  492. }
  493. if ($$m[1] ne $flg) {
  494. print STDERR "Flag mismatch for $bn:$uid.\n";
  495. return 1;
  496. }
  497. }
  498. if (%ms) {
  499. print STDERR "Excess messages in '$bn': ".join(", ", sort({$a <=> $b } keys(%ms))).".\n";
  500. return 1;
  501. }
  502. return 0;
  503. }
  504. # $filename, @syncstate
  505. sub ckstate($@)
  506. {
  507. my ($fn, $fmaxuid, $maxxfuid, $nmaxuid, @T) = @_;
  508. my %hdr;
  509. $hdr{'FarUidValidity'} = "1";
  510. $hdr{'NearUidValidity'} = "1";
  511. $hdr{'MaxPulledUid'} = $fmaxuid;
  512. $hdr{'MaxPushedUid'} = $nmaxuid;
  513. $hdr{'MaxExpiredFarUid'} = $maxxfuid if ($maxxfuid ne 0);
  514. open(FILE, "<", $fn) or die "Cannot read sync state $fn.\n";
  515. chomp(my @ls = <FILE>);
  516. close FILE;
  517. OUTER: while (1) {
  518. while (@ls) {
  519. my $l = shift(@ls);
  520. last OUTER if (!length($l));
  521. if ($l !~ /^([^ ]+) (\d+)$/) {
  522. print STDERR "Malformed sync state header entry: $l\n";
  523. return 1;
  524. }
  525. my $want = delete $hdr{$1};
  526. if (!defined($want)) {
  527. print STDERR "Unexpected sync state header entry: $1\n";
  528. return 1;
  529. }
  530. if ($2 != $want) {
  531. print STDERR "Sync state header entry $1 mismatch: got $2, wanted $want\n";
  532. return 1;
  533. }
  534. }
  535. print STDERR "Unterminated sync state header.\n";
  536. return 1;
  537. }
  538. my @ky = keys %hdr;
  539. if (@ky) {
  540. print STDERR "Keys missing from sync state header: @ky\n";
  541. return 1;
  542. }
  543. for my $l (@ls) {
  544. if (!@T) {
  545. print STDERR "Excess sync state entry: '$l'.\n";
  546. return 1;
  547. }
  548. my $xl = shift(@T)." ".shift(@T)." ".shift(@T);
  549. if ($l ne $xl) {
  550. print STDERR "Sync state entry mismatch: '$l' instead of '$xl'.\n";
  551. return 1;
  552. }
  553. }
  554. if (@T) {
  555. print STDERR "Missing sync state entry: '".shift(@T)." ".shift(@T)." ".shift(@T)."'.\n";
  556. return 1;
  557. }
  558. return 0;
  559. }
  560. # $statefile, \@chan_state
  561. sub ckchan($$)
  562. {
  563. my ($F, $cs) = @_;
  564. my $rslt = ckstate($F, @{ $$cs[2] });
  565. $rslt |= &ckbox("far", @{ $$cs[0] });
  566. $rslt |= &ckbox("near", @{ $$cs[1] });
  567. return $rslt;
  568. }
  569. # $boxname, $maxuid, @msgs
  570. sub printbox($$@)
  571. {
  572. my ($bn, $mu, @ms) = @_;
  573. print " [ $mu,\n ";
  574. my $frst = 1;
  575. while (@ms) {
  576. if ($frst) {
  577. $frst = 0;
  578. } else {
  579. print ", ";
  580. }
  581. print mn(shift(@ms)).", ".shift(@ms).", \"".shift(@ms)."\"";
  582. }
  583. print " ],\n";
  584. }
  585. # @syncstate
  586. sub printstate(@)
  587. {
  588. my (@t) = @_;
  589. print " [ ".shift(@t).", ".shift(@t).", ".shift(@t).",\n ";
  590. my $frst = 1;
  591. while (@t) {
  592. if ($frst) {
  593. $frst = 0;
  594. } else {
  595. print ", ";
  596. }
  597. print((shift(@t) // "??").", ".(shift(@t) // "??").", \"".(shift(@t) // "??")."\"");
  598. }
  599. print " ],\n";
  600. }
  601. # \@chan_state
  602. sub printchan($)
  603. {
  604. my ($cs) = @_;
  605. &printbox("far", @{ $$cs[0] });
  606. &printbox("near", @{ $$cs[1] });
  607. printstate(@{ $$cs[2] });
  608. }
  609. sub readfile($)
  610. {
  611. my ($file) = @_;
  612. open(FILE, $file) or return;
  613. my @nj = <FILE>;
  614. close FILE;
  615. return @nj;
  616. }
  617. # $title, \@source_state, \@target_state, @channel_configs
  618. sub test($$$@)
  619. {
  620. my ($ttl, $sx, $tx, @sfx) = @_;
  621. return 0 if (scalar(@ARGV) && !grep { $_ eq $ttl } @ARGV);
  622. print "Testing: ".$ttl." ...\n";
  623. &writecfg(@sfx);
  624. mkchan($$sx[0], $$sx[1], @{ $$sx[2] });
  625. my ($xc, @ret) = runsync("-J", "1-initial.log");
  626. if ($xc || ckchan("near/.mbsyncstate.new", $tx)) {
  627. print "Input:\n";
  628. printchan($sx);
  629. print "Options:\n";
  630. print " [ ".join(", ", map('"'.qm($_).'"', @sfx))." ]\n";
  631. if (!$xc) {
  632. print "Expected result:\n";
  633. printchan($tx);
  634. print "Actual result:\n";
  635. showchan("near/.mbsyncstate.new");
  636. }
  637. print "Debug output:\n";
  638. print @ret;
  639. exit 1;
  640. }
  641. my @nj = readfile("near/.mbsyncstate.journal");
  642. my ($jxc, @jret) = runsync("-0 --no-expunge", "2-replay.log");
  643. if ($jxc || ckstate("near/.mbsyncstate", @{ $$tx[2] })) {
  644. print "Journal replay failed.\n";
  645. print "Options:\n";
  646. print " [ ".join(", ", map('"'.qm($_).'"', @sfx))." ], [ \"-0\", \"--no-expunge\" ]\n";
  647. print "Old State:\n";
  648. printstate(@{ $$sx[2] });
  649. print "Journal:\n".join("", @nj)."\n";
  650. if (!$jxc) {
  651. print "Expected New State:\n";
  652. printstate(@{ $$tx[2] });
  653. print "New State:\n";
  654. showstate("near/.mbsyncstate");
  655. }
  656. print "Debug output:\n";
  657. print @jret;
  658. exit 1;
  659. }
  660. my ($ixc, @iret) = runsync("", "3-verify.log");
  661. if ($ixc || ckchan("near/.mbsyncstate", $tx)) {
  662. print "Idempotence verification run failed.\n";
  663. print "Input == Expected result:\n";
  664. printchan($tx);
  665. print "Options:\n";
  666. print " [ ".join(", ", map('"'.qm($_).'"', @sfx))." ]\n";
  667. if (!$ixc) {
  668. print "Actual result:\n";
  669. showchan("near/.mbsyncstate");
  670. }
  671. print "Debug output:\n";
  672. print @iret;
  673. exit 1;
  674. }
  675. rmtree "near";
  676. rmtree "far";
  677. my $njl = (@nj - 1) * 2;
  678. for (my $l = 1; $l <= $njl; $l++) {
  679. mkchan($$sx[0], $$sx[1], @{ $$sx[2] });
  680. my ($nxc, @nret) = runsync("-J$l", "4-interrupt.log");
  681. if ($nxc != (100 + ($l & 1)) << 8) {
  682. print "Interrupting at step $l/$njl failed.\n";
  683. print "Debug output:\n";
  684. print @nret;
  685. exit 1;
  686. }
  687. ($nxc, @nret) = runsync("-J", "5-resume.log");
  688. if ($nxc || ckchan("near/.mbsyncstate.new", $tx)) {
  689. print "Resuming from step $l/$njl failed.\n";
  690. print "Input:\n";
  691. printchan($sx);
  692. print "Options:\n";
  693. print " [ ".join(", ", map('"'.qm($_).'"', @sfx))." ]\n";
  694. my @nnj = readfile("near/.mbsyncstate.journal");
  695. my $ln = int($l / 2);
  696. print "Journal:\n".join("", @nnj[0..$ln])."-------\n".join("", @nnj[($ln + 1)..$#nnj])."\n";
  697. print "Full journal:\n".join("", @nj)."\n";
  698. if (!$nxc) {
  699. print "Expected result:\n";
  700. printchan($tx);
  701. print "Actual result:\n";
  702. showchan("near/.mbsyncstate.new");
  703. }
  704. print "Debug output:\n";
  705. print @nret;
  706. exit 1;
  707. }
  708. rmtree "near";
  709. rmtree "far";
  710. }
  711. killcfg();
  712. }