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