1 /**
2  * @file
3  * Test code for Patterns
4  *
5  * @authors
6  * Copyright (C) 2018 Naveen Nathan <naveen@lastninja.net>
7  *
8  * @copyright
9  * This program is free software: you can redistribute it and/or modify it under
10  * the terms of the GNU General Public License as published by the Free Software
11  * Foundation, either version 2 of the License, or (at your option) any later
12  * version.
13  *
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
17  * details.
18  *
19  * You should have received a copy of the GNU General Public License along with
20  * this program.  If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 #define TEST_NO_MAIN
24 #define MAIN_C 1
25 #include "config.h"
26 #include "acutest.h"
27 #include <assert.h>
28 #include <string.h>
29 #include "mutt/buffer.h"
30 #include "mutt/memory.h"
31 #include "alias/lib.h"
32 #include "pattern/lib.h"
33 #include "mutt_globals.h"
34 
35 bool ResumeEditedDraftFiles;
36 
37 /* All tests are limited to patterns that are string_match type only,
38  * such as =s, =b, =f, etc.
39  *
40  * Rationale: (1) there is no way to compare regex types as "equal",
41  *            (2) comparing Group is a pain in the arse,
42  *            (3) similarly comparing lists (ListHead) is annoying.
43  */
44 
45 /* canonical representation of Pattern "tree",
46  * s specifies a caller allocated buffer to write the string,
47  * pat specifies the pattern,
48  * indent specifies the indentation level (set to 0 if pat is root of tree),
49  * returns the number of characters written to s (not including trailing '\0')
50  *
51  * A pattern tree with patterns a, b, c, d, e, f, g can be represented graphically
52  * as follows (where a is obviously the root):
53  *
54  *        +-c-+
55  *        |   |
56  *    +-b-+   +-d
57  *    |   |
58  *  a-+   +-e
59  *    |
60  *    +-f-+
61  *        |
62  *        +-g
63  *
64  *  Let left child represent "next" pattern, and right as "child" pattern.
65  *
66  *  Then we can convert the above into a textual representation as follows:
67  *    {a}
68  *      {b}
69  *        {c}
70  *        {d}
71  *      {e}
72  *    {f}
73  *    {g}
74  *
75  *  {a} is the root pattern with child pattern {b} (note: 2 space indent)
76  *  and next pattern {f} (same indent).
77  *  {b} has child {c} and next pattern {e}.
78  *  {c} has next pattern {d}.
79  *  {f} has next pattern {g}.
80  *
81  *  In the representation {a} is expanded to all the pattern fields.
82  */
canonical_pattern(char * s,struct PatternList * pat,int indent)83 static int canonical_pattern(char *s, struct PatternList *pat, int indent)
84 {
85   if (!pat || !s)
86     return 0;
87 
88   char *p = s;
89 
90   for (int i = 0; i < 2 * indent; i++)
91     p += sprintf(p, " ");
92 
93   struct Pattern *e = NULL;
94 
95   // p += sprintf(p, "");
96   *p = '\0';
97 
98   SLIST_FOREACH(e, pat, entries)
99   {
100     p += sprintf(p, "{");
101     p += sprintf(p, "%d,", e->op);
102     p += sprintf(p, "%d,", e->pat_not);
103     p += sprintf(p, "%d,", e->all_addr);
104     p += sprintf(p, "%d,", e->string_match);
105     p += sprintf(p, "%d,", e->group_match);
106     p += sprintf(p, "%d,", e->ign_case);
107     p += sprintf(p, "%d,", e->is_alias);
108     p += sprintf(p, "%d,", e->is_multi);
109     p += sprintf(p, "%d,", e->min);
110     p += sprintf(p, "%d,", e->max);
111     p += sprintf(p, "\"%s\",", e->p.str ? e->p.str : "");
112     p += sprintf(p, "%s,", !e->child ? "(null)" : "(list)");
113     p += sprintf(p, "%s", SLIST_NEXT(e, entries) ? "(next)" : "(null)");
114     p += sprintf(p, "}\n");
115 
116     p += canonical_pattern(p, e->child, indent + 1);
117   }
118 
119   return p - s;
120 }
121 
122 /* best-effort pattern tree compare, returns 0 if equal otherwise 1 */
cmp_pattern(struct PatternList * p1,struct PatternList * p2)123 static int cmp_pattern(struct PatternList *p1, struct PatternList *p2)
124 {
125   if (!p1 || !p2)
126     return !(!p1 && !p2);
127 
128   struct PatternList p1_tmp = *p1;
129   struct PatternList p2_tmp = *p2;
130 
131   while (!SLIST_EMPTY(&p1_tmp))
132   {
133     struct Pattern *l = SLIST_FIRST(&p1_tmp);
134     struct Pattern *r = SLIST_FIRST(&p2_tmp);
135 
136     /* if l is NULL then r must be NULL (and vice-versa) */
137     if ((!l || !r) && !(!l && !r))
138       return 1;
139 
140     SLIST_REMOVE_HEAD(&p1_tmp, entries);
141     SLIST_REMOVE_HEAD(&p2_tmp, entries);
142 
143     if (l->op != r->op)
144       return 1;
145     if (l->pat_not != r->pat_not)
146       return 1;
147     if (l->all_addr != r->all_addr)
148       return 1;
149     if (l->string_match != r->string_match)
150       return 1;
151     if (l->group_match != r->group_match)
152       return 1;
153     if (l->ign_case != r->ign_case)
154       return 1;
155     if (l->is_alias != r->is_alias)
156       return 1;
157     if (l->is_multi != r->is_multi)
158       return 1;
159     if (l->min != r->min)
160       return 1;
161     if (l->max != r->max)
162       return 1;
163 
164     if (l->string_match && strcmp(l->p.str, r->p.str))
165       return 1;
166 
167     if (cmp_pattern(l->child, r->child))
168       return 1;
169   }
170 
171   return 0;
172 }
173 
test_mutt_pattern_comp(void)174 void test_mutt_pattern_comp(void)
175 {
176   struct Buffer err = mutt_buffer_make(1024);
177 
178   { /* empty */
179     char *s = "";
180 
181     mutt_buffer_reset(&err);
182     struct PatternList *pat = mutt_pattern_comp(NULL, NULL, s, 0, &err);
183 
184     if (!TEST_CHECK(!pat))
185     {
186       TEST_MSG("Expected: pat == NULL");
187       TEST_MSG("Actual  : pat != NULL");
188     }
189 
190     char *msg = "empty pattern";
191     if (!TEST_CHECK(!strcmp(err.data, msg)))
192     {
193       TEST_MSG("Expected: %s", msg);
194       TEST_MSG("Actual  : %s", err.data);
195     }
196   }
197 
198   { /* invalid */
199     char *s = "x";
200 
201     mutt_buffer_reset(&err);
202     struct PatternList *pat = mutt_pattern_comp(NULL, NULL, s, 0, &err);
203 
204     if (!TEST_CHECK(!pat))
205     {
206       TEST_MSG("Expected: pat == NULL");
207       TEST_MSG("Actual  : pat != NULL");
208     }
209 
210     char *msg = "error in pattern at: x";
211     if (!TEST_CHECK(!strcmp(err.data, msg)))
212     {
213       TEST_MSG("Expected: %s", msg);
214       TEST_MSG("Actual  : %s", err.data);
215     }
216   }
217 
218   { /* missing parameter */
219     char *s = "=s";
220 
221     mutt_buffer_reset(&err);
222     struct PatternList *pat = mutt_pattern_comp(NULL, NULL, s, 0, &err);
223 
224     if (!TEST_CHECK(!pat))
225     {
226       TEST_MSG("Expected: pat == NULL");
227       TEST_MSG("Actual  : pat != NULL");
228     }
229 
230     char *msg = "missing parameter";
231     if (!TEST_CHECK(!strcmp(err.data, msg)))
232     {
233       TEST_MSG("Expected: %s", msg);
234       TEST_MSG("Actual  : %s", err.data);
235     }
236   }
237 
238   { /* error in pattern */
239     char *s = "| =s foo";
240 
241     mutt_buffer_reset(&err);
242     struct PatternList *pat = mutt_pattern_comp(NULL, NULL, s, 0, &err);
243 
244     if (!TEST_CHECK(!pat))
245     {
246       TEST_MSG("Expected: pat == NULL");
247       TEST_MSG("Actual  : pat != NULL");
248     }
249 
250     char *msg = "error in pattern at: | =s foo";
251     if (!TEST_CHECK(!strcmp(err.data, msg)))
252     {
253       TEST_MSG("Expected: %s", msg);
254       TEST_MSG("Actual  : %s", err.data);
255     }
256   }
257 
258   {
259     char *s = "=s foobar";
260 
261     mutt_buffer_reset(&err);
262     struct PatternList *pat = mutt_pattern_comp(NULL, NULL, s, 0, &err);
263 
264     if (!TEST_CHECK(pat != NULL))
265     {
266       TEST_MSG("Expected: pat != NULL");
267       TEST_MSG("Actual  : pat == NULL");
268     }
269 
270     struct PatternList expected;
271     SLIST_INIT(&expected);
272     struct Pattern e = { .op = MUTT_PAT_SUBJECT,
273                          .pat_not = false,
274                          .all_addr = false,
275                          .string_match = true,
276                          .group_match = false,
277                          .ign_case = true,
278                          .is_alias = false,
279                          .is_multi = false,
280                          .min = 0,
281                          .max = 0,
282                          .p.str = "foobar" };
283     SLIST_INSERT_HEAD(&expected, &e, entries);
284 
285     if (!TEST_CHECK(!cmp_pattern(pat, &expected)))
286     {
287       char s2[1024];
288       canonical_pattern(s2, &expected, 0);
289       TEST_MSG("Expected:\n%s", s2);
290       canonical_pattern(s2, pat, 0);
291       TEST_MSG("Actual:\n%s", s2);
292     }
293 
294     char *msg = "";
295     if (!TEST_CHECK(!strcmp(err.data, msg)))
296     {
297       TEST_MSG("Expected: %s", msg);
298       TEST_MSG("Actual  : %s", err.data);
299     }
300 
301     mutt_pattern_free(&pat);
302   }
303 
304   {
305     char *s = "! =s foobar";
306 
307     mutt_buffer_reset(&err);
308     struct PatternList *pat = mutt_pattern_comp(NULL, NULL, s, 0, &err);
309 
310     if (!TEST_CHECK(pat != NULL))
311     {
312       TEST_MSG("Expected: pat != NULL");
313       TEST_MSG("Actual  : pat == NULL");
314     }
315 
316     struct PatternList expected;
317     SLIST_INIT(&expected);
318     struct Pattern e = { .op = MUTT_PAT_SUBJECT,
319                          .pat_not = true,
320                          .all_addr = false,
321                          .string_match = true,
322                          .group_match = false,
323                          .ign_case = true,
324                          .is_alias = false,
325                          .is_multi = false,
326                          .min = 0,
327                          .max = 0,
328                          .p.str = "foobar" };
329 
330     SLIST_INSERT_HEAD(&expected, &e, entries);
331 
332     if (!TEST_CHECK(!cmp_pattern(pat, &expected)))
333     {
334       char s2[1024];
335       canonical_pattern(s2, &expected, 0);
336       TEST_MSG("Expected:\n%s", s2);
337       canonical_pattern(s2, pat, 0);
338       TEST_MSG("Actual:\n%s", s2);
339     }
340 
341     char *msg = "";
342     if (!TEST_CHECK(!strcmp(err.data, msg)))
343     {
344       TEST_MSG("Expected: %s", msg);
345       TEST_MSG("Actual  : %s", err.data);
346     }
347 
348     mutt_pattern_free(&pat);
349   }
350 
351   {
352     char *s = "=s foo =s bar";
353 
354     mutt_buffer_reset(&err);
355     struct PatternList *pat = mutt_pattern_comp(NULL, NULL, s, 0, &err);
356 
357     if (!TEST_CHECK(pat != NULL))
358     {
359       TEST_MSG("Expected: pat != NULL");
360       TEST_MSG("Actual  : pat == NULL");
361     }
362 
363     struct PatternList expected;
364 
365     struct Pattern e[3] = { /* root */
366                             { .op = MUTT_PAT_AND,
367                               .pat_not = false,
368                               .all_addr = false,
369                               .string_match = false,
370                               .group_match = false,
371                               .ign_case = false,
372                               .is_alias = false,
373                               .is_multi = false,
374                               .min = 0,
375                               .max = 0,
376                               .p.str = NULL },
377                             /* root->child */
378                             { .op = MUTT_PAT_SUBJECT,
379                               .pat_not = false,
380                               .all_addr = false,
381                               .string_match = true,
382                               .group_match = false,
383                               .ign_case = true,
384                               .is_alias = false,
385                               .is_multi = false,
386                               .min = 0,
387                               .max = 0,
388                               .p.str = "foo" },
389                             /* root->child->next */
390                             { .op = MUTT_PAT_SUBJECT,
391                               .pat_not = false,
392                               .all_addr = false,
393                               .string_match = true,
394                               .group_match = false,
395                               .ign_case = true,
396                               .is_alias = false,
397                               .is_multi = false,
398                               .min = 0,
399                               .max = 0,
400                               .p.str = "bar" }
401     };
402 
403     SLIST_INIT(&expected);
404     SLIST_INSERT_HEAD(&expected, &e[0], entries);
405     struct PatternList child;
406     SLIST_INIT(&child);
407     e[0].child = &child;
408     SLIST_INSERT_HEAD(e[0].child, &e[1], entries);
409     SLIST_INSERT_AFTER(&e[1], &e[2], entries);
410 
411     if (!TEST_CHECK(!cmp_pattern(pat, &expected)))
412     {
413       char s2[1024];
414       canonical_pattern(s2, &expected, 0);
415       TEST_MSG("Expected:\n%s", s2);
416       canonical_pattern(s2, pat, 0);
417       TEST_MSG("Actual:\n%s", s2);
418     }
419 
420     char *msg = "";
421     if (!TEST_CHECK(!strcmp(err.data, msg)))
422     {
423       TEST_MSG("Expected: %s", msg);
424       TEST_MSG("Actual  : %s", err.data);
425     }
426 
427     mutt_pattern_free(&pat);
428   }
429 
430   {
431     char *s = "(=s foo =s bar)";
432 
433     mutt_buffer_reset(&err);
434     struct PatternList *pat = mutt_pattern_comp(NULL, NULL, s, 0, &err);
435 
436     if (!TEST_CHECK(pat != NULL))
437     {
438       TEST_MSG("Expected: pat != NULL");
439       TEST_MSG("Actual  : pat == NULL");
440     }
441 
442     struct PatternList expected;
443 
444     struct Pattern e[3] = { /* root */
445                             { .op = MUTT_PAT_AND,
446                               .pat_not = false,
447                               .all_addr = false,
448                               .string_match = false,
449                               .group_match = false,
450                               .ign_case = false,
451                               .is_alias = false,
452                               .is_multi = false,
453                               .min = 0,
454                               .max = 0,
455                               .p.str = NULL },
456                             /* root->child */
457                             { .op = MUTT_PAT_SUBJECT,
458                               .pat_not = false,
459                               .all_addr = false,
460                               .string_match = true,
461                               .group_match = false,
462                               .ign_case = true,
463                               .is_alias = false,
464                               .is_multi = false,
465                               .min = 0,
466                               .max = 0,
467                               .p.str = "foo" },
468                             /* root->child->next */
469                             { .op = MUTT_PAT_SUBJECT,
470                               .pat_not = false,
471                               .all_addr = false,
472                               .string_match = true,
473                               .group_match = false,
474                               .ign_case = true,
475                               .is_alias = false,
476                               .is_multi = false,
477                               .min = 0,
478                               .max = 0,
479                               .p.str = "bar" }
480     };
481 
482     SLIST_INIT(&expected);
483     SLIST_INSERT_HEAD(&expected, &e[0], entries);
484     struct PatternList child;
485     SLIST_INIT(&child);
486     e[0].child = &child;
487     SLIST_INSERT_HEAD(e[0].child, &e[1], entries);
488     SLIST_INSERT_AFTER(&e[1], &e[2], entries);
489 
490     if (!TEST_CHECK(!cmp_pattern(pat, &expected)))
491     {
492       char s2[1024];
493       canonical_pattern(s2, &expected, 0);
494       TEST_MSG("Expected:\n%s", s2);
495       canonical_pattern(s2, pat, 0);
496       TEST_MSG("Actual:\n%s", s2);
497     }
498 
499     char *msg = "";
500     if (!TEST_CHECK(!strcmp(err.data, msg)))
501     {
502       TEST_MSG("Expected: %s", msg);
503       TEST_MSG("Actual  : %s", err.data);
504     }
505 
506     mutt_pattern_free(&pat);
507   }
508 
509   {
510     char *s = "! (=s foo =s bar)";
511 
512     mutt_buffer_reset(&err);
513     struct PatternList *pat = mutt_pattern_comp(NULL, NULL, s, 0, &err);
514 
515     if (!TEST_CHECK(pat != NULL))
516     {
517       TEST_MSG("Expected: pat != NULL");
518       TEST_MSG("Actual  : pat == NULL");
519     }
520 
521     struct PatternList expected;
522 
523     struct Pattern e[3] = { /* root */
524                             { .op = MUTT_PAT_AND,
525                               .pat_not = true,
526                               .all_addr = false,
527                               .string_match = false,
528                               .group_match = false,
529                               .ign_case = false,
530                               .is_alias = false,
531                               .is_multi = false,
532                               .min = 0,
533                               .max = 0,
534                               .p.str = NULL },
535                             /* root->child */
536                             { .op = MUTT_PAT_SUBJECT,
537                               .pat_not = false,
538                               .all_addr = false,
539                               .string_match = true,
540                               .group_match = false,
541                               .ign_case = true,
542                               .is_alias = false,
543                               .is_multi = false,
544                               .min = 0,
545                               .max = 0,
546                               .p.str = "foo" },
547                             /* root->child->next */
548                             { .op = MUTT_PAT_SUBJECT,
549                               .pat_not = false,
550                               .all_addr = false,
551                               .string_match = true,
552                               .group_match = false,
553                               .ign_case = true,
554                               .is_alias = false,
555                               .is_multi = false,
556                               .min = 0,
557                               .max = 0,
558                               .p.str = "bar" }
559     };
560 
561     SLIST_INIT(&expected);
562     SLIST_INSERT_HEAD(&expected, &e[0], entries);
563     struct PatternList child;
564     SLIST_INIT(&child);
565     e[0].child = &child;
566     SLIST_INSERT_HEAD(e[0].child, &e[1], entries);
567     SLIST_INSERT_AFTER(&e[1], &e[2], entries);
568 
569     if (!TEST_CHECK(!cmp_pattern(pat, &expected)))
570     {
571       char s2[1024];
572       canonical_pattern(s2, &expected, 0);
573       TEST_MSG("Expected:\n%s", s2);
574       canonical_pattern(s2, pat, 0);
575       TEST_MSG("Actual:\n%s", s2);
576     }
577 
578     char *msg = "";
579     if (!TEST_CHECK(!strcmp(err.data, msg)))
580     {
581       TEST_MSG("Expected: %s", msg);
582       TEST_MSG("Actual  : %s", err.data);
583     }
584 
585     mutt_pattern_free(&pat);
586   }
587 
588   {
589     char *s = "=s foo =s bar =s quux";
590 
591     mutt_buffer_reset(&err);
592     struct PatternList *pat = mutt_pattern_comp(NULL, NULL, s, 0, &err);
593 
594     if (!TEST_CHECK(pat != NULL))
595     {
596       TEST_MSG("Expected: pat != NULL");
597       TEST_MSG("Actual  : pat == NULL");
598     }
599 
600     struct PatternList expected;
601 
602     struct Pattern e[4] = { /* root */
603                             { .op = MUTT_PAT_AND,
604                               .pat_not = false,
605                               .all_addr = false,
606                               .string_match = false,
607                               .group_match = false,
608                               .ign_case = false,
609                               .is_alias = false,
610                               .is_multi = false,
611                               .min = 0,
612                               .max = 0,
613                               .p.str = NULL },
614                             /* root->child */
615                             { .op = MUTT_PAT_SUBJECT,
616                               .pat_not = false,
617                               .all_addr = false,
618                               .string_match = true,
619                               .group_match = false,
620                               .ign_case = true,
621                               .is_alias = false,
622                               .is_multi = false,
623                               .min = 0,
624                               .max = 0,
625                               .p.str = "foo" },
626                             /* root->child->next */
627                             { .op = MUTT_PAT_SUBJECT,
628                               .pat_not = false,
629                               .all_addr = false,
630                               .string_match = true,
631                               .group_match = false,
632                               .ign_case = true,
633                               .is_alias = false,
634                               .is_multi = false,
635                               .min = 0,
636                               .max = 0,
637                               .p.str = "bar" },
638                             /* root->child->next->next */
639                             { .op = MUTT_PAT_SUBJECT,
640                               .pat_not = false,
641                               .all_addr = false,
642                               .string_match = true,
643                               .group_match = false,
644                               .ign_case = true,
645                               .is_alias = false,
646                               .is_multi = false,
647                               .min = 0,
648                               .max = 0,
649                               .p.str = "quux" }
650     };
651 
652     SLIST_INIT(&expected);
653     SLIST_INSERT_HEAD(&expected, &e[0], entries);
654     struct PatternList child;
655     e[0].child = &child;
656     SLIST_INSERT_HEAD(e[0].child, &e[1], entries);
657     SLIST_INSERT_AFTER(&e[1], &e[2], entries);
658     SLIST_INSERT_AFTER(&e[2], &e[3], entries);
659 
660     if (!TEST_CHECK(!cmp_pattern(pat, &expected)))
661     {
662       char s2[1024];
663       canonical_pattern(s2, &expected, 0);
664       TEST_MSG("Expected:\n%s", s2);
665       canonical_pattern(s2, pat, 0);
666       TEST_MSG("Actual:\n%s", s2);
667     }
668 
669     char *msg = "";
670     if (!TEST_CHECK(!strcmp(err.data, msg)))
671     {
672       TEST_MSG("Expected: %s", msg);
673       TEST_MSG("Actual  : %s", err.data);
674     }
675 
676     mutt_pattern_free(&pat);
677   }
678 
679   {
680     char *s = "!(=s foo|=s bar) =s quux";
681 
682     mutt_buffer_reset(&err);
683     struct PatternList *pat = mutt_pattern_comp(NULL, NULL, s, 0, &err);
684 
685     if (!TEST_CHECK(pat != NULL))
686     {
687       TEST_MSG("Expected: pat != NULL");
688       TEST_MSG("Actual  : pat == NULL");
689     }
690 
691     struct PatternList expected;
692 
693     struct Pattern e[5] = { /* root */
694                             { .op = MUTT_PAT_AND,
695                               .pat_not = false,
696                               .all_addr = false,
697                               .string_match = false,
698                               .group_match = false,
699                               .ign_case = false,
700                               .is_alias = false,
701                               .is_multi = false,
702                               .min = 0,
703                               .max = 0,
704                               .p.str = NULL },
705                             /* root->child */
706                             { .op = MUTT_PAT_OR,
707                               .pat_not = true,
708                               .all_addr = false,
709                               .string_match = false,
710                               .group_match = false,
711                               .ign_case = false,
712                               .is_alias = false,
713                               .is_multi = false,
714                               .min = 0,
715                               .max = 0,
716                               .p.str = NULL },
717                             /* root->child->child */
718                             { .op = MUTT_PAT_SUBJECT,
719                               .pat_not = false,
720                               .all_addr = false,
721                               .string_match = true,
722                               .group_match = false,
723                               .ign_case = true,
724                               .is_alias = false,
725                               .is_multi = false,
726                               .min = 0,
727                               .max = 0,
728                               .p.str = "foo" },
729                             /* root->child->child->next */
730                             { .op = MUTT_PAT_SUBJECT,
731                               .pat_not = false,
732                               .all_addr = false,
733                               .string_match = true,
734                               .group_match = false,
735                               .ign_case = true,
736                               .is_alias = false,
737                               .is_multi = false,
738                               .min = 0,
739                               .max = 0,
740                               .p.str = "bar" },
741                             /* root->child->next */
742                             { .op = MUTT_PAT_SUBJECT,
743                               .pat_not = false,
744                               .all_addr = false,
745                               .string_match = true,
746                               .group_match = false,
747                               .ign_case = true,
748                               .is_alias = false,
749                               .is_multi = false,
750                               .min = 0,
751                               .max = 0,
752                               .p.str = "quux" }
753     };
754 
755     SLIST_INIT(&expected);
756     SLIST_INSERT_HEAD(&expected, &e[0], entries);
757     struct PatternList child1, child2;
758     SLIST_INIT(&child1);
759     e[0].child = &child1;
760     SLIST_INSERT_HEAD(e[0].child, &e[1], entries);
761     SLIST_INIT(&child2);
762     e[1].child = &child2;
763     SLIST_INSERT_HEAD(e[1].child, &e[2], entries);
764     SLIST_INSERT_AFTER(&e[2], &e[3], entries);
765     SLIST_INSERT_AFTER(&e[1], &e[4], entries);
766 
767     if (!TEST_CHECK(!cmp_pattern(pat, &expected)))
768     {
769       char s2[1024];
770       canonical_pattern(s2, &expected, 0);
771       TEST_MSG("Expected:\n%s", s2);
772       canonical_pattern(s2, pat, 0);
773       TEST_MSG("Actual:\n%s", s2);
774     }
775 
776     char *msg = "";
777     if (!TEST_CHECK(!strcmp(err.data, msg)))
778     {
779       TEST_MSG("Expected: %s", msg);
780       TEST_MSG("Actual  : %s", err.data);
781     }
782 
783     mutt_pattern_free(&pat);
784   }
785 
786   mutt_buffer_dealloc(&err);
787 }
788