run-tests.pl 17 KB

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