From: Simon Symeonidis Date: Fri, 19 Jul 2019 19:02:28 +0000 (-0400) Subject: Refactor regex with custom wrapper X-Git-Tag: 2019-10-25~109 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=5cd5560b8f4348a7a19ea5afbab59775e400f0e2;p=neomutt Refactor regex with custom wrapper --- diff --git a/browser.c b/browser.c index 4700c33d2..db6602b52 100644 --- a/browser.c +++ b/browser.c @@ -707,8 +707,7 @@ static int examine_directory(struct Menu *menu, struct BrowserState *state, continue; if (prefix && *prefix && !mutt_str_startswith(mdata->group, prefix, CASE_MATCH)) continue; - if (C_Mask && C_Mask->regex && - !((regexec(C_Mask->regex, mdata->group, 0, NULL, 0) == 0) ^ C_Mask->pat_not)) + if (!mutt_regex_match(C_Mask, mdata->group)) { continue; } @@ -767,8 +766,7 @@ static int examine_directory(struct Menu *menu, struct BrowserState *state, { continue; } - if (C_Mask && C_Mask->regex && - !((regexec(C_Mask->regex, de->d_name, 0, NULL, 0) == 0) ^ C_Mask->pat_not)) + if (!mutt_regex_match(C_Mask, de->d_name)) { continue; } diff --git a/commands.c b/commands.c index 0fe02aa51..c7160eb73 100644 --- a/commands.c +++ b/commands.c @@ -150,7 +150,7 @@ static void update_protected_headers(struct Email *e) mutt_str_replace(&e->env->subject, prot_headers->subject); FREE(&e->env->disp_subj); - if (regexec(C_ReplyRegex->regex, e->env->subject, 1, pmatch, 0) == 0) + if (mutt_regex_capture(C_ReplyRegex, e->env->subject, 1, pmatch)) e->env->real_subj = e->env->subject + pmatch[0].rm_eo; else e->env->real_subj = e->env->subject; diff --git a/email/parse.c b/email/parse.c index 6607ede52..59af6ee15 100644 --- a/email/parse.c +++ b/email/parse.c @@ -1135,8 +1135,7 @@ struct Envelope *mutt_rfc822_read_header(FILE *fp, struct Email *e, bool user_hd { regmatch_t pmatch[1]; - if (C_ReplyRegex && C_ReplyRegex->regex && - (regexec(C_ReplyRegex->regex, env->subject, 1, pmatch, 0) == 0)) + if (mutt_regex_capture(C_ReplyRegex, env->subject, 1, pmatch)) { env->real_subj = env->subject + pmatch[0].rm_eo; } diff --git a/email/rfc2047.c b/email/rfc2047.c index e337dde48..99ce89875 100644 --- a/email/rfc2047.c +++ b/email/rfc2047.c @@ -153,8 +153,8 @@ static char *parse_encoded_word(char *str, enum ContentEncoding *enc, char **cha assert(re && "Something is wrong with your RE engine."); char *res = NULL; - int rc = regexec(re->regex, str, nmatch, match, 0); - if (rc == 0) + + if (mutt_regex_capture(re, str, nmatch, match)) { /* Charset */ *charset = str + match[1].rm_so; diff --git a/hook.c b/hook.c index 0d3578a44..6f70bebe3 100644 --- a/hook.c +++ b/hook.c @@ -530,8 +530,7 @@ void mutt_folder_hook(const char *path, const char *desc) if (!(tmp->type & MUTT_FOLDER_HOOK)) continue; - if ((path && (regexec(tmp->regex.regex, path, 0, NULL, 0) == 0) ^ tmp->regex.pat_not) || - (desc && (regexec(tmp->regex.regex, desc, 0, NULL, 0) == 0) ^ tmp->regex.pat_not)) + if (mutt_regex_match(&tmp->regex, path) || mutt_regex_match(&tmp->regex, desc)) { if (mutt_parse_rc_line(tmp->command, token, err) == MUTT_CMD_ERROR) { @@ -562,7 +561,7 @@ char *mutt_find_hook(HookFlags type, const char *pat) { if (tmp->type & type) { - if (regexec(tmp->regex.regex, pat, 0, NULL, 0) == 0) + if (mutt_regex_match(&tmp->regex, pat)) return tmp->command; } } @@ -730,9 +729,7 @@ static void list_hook(struct ListHead *matches, const char *match, HookFlags hoo TAILQ_FOREACH(tmp, &Hooks, entries) { - if ((tmp->type & hook) && - ((match && (regexec(tmp->regex.regex, match, 0, NULL, 0) == 0)) ^ - tmp->regex.pat_not)) + if ((tmp->type & hook) && mutt_regex_match(&tmp->regex, match)) { mutt_list_insert_tail(matches, mutt_str_strdup(tmp->command)); } @@ -775,7 +772,7 @@ void mutt_account_hook(const char *url) if (!(hook->command && (hook->type & MUTT_ACCOUNT_HOOK))) continue; - if ((regexec(hook->regex.regex, url, 0, NULL, 0) == 0) ^ hook->regex.pat_not) + if (mutt_regex_match(&hook->regex, url)) { inhook = true; diff --git a/imap/browse.c b/imap/browse.c index edf0efa16..bc18a6c94 100644 --- a/imap/browse.c +++ b/imap/browse.c @@ -88,8 +88,7 @@ static void add_folder(char delim, char *folder, bool noselect, bool noinferiors /* apply filemask filter. This should really be done at menu setup rather * than at scan, since it's so expensive to scan. But that's big changes * to browser.c */ - if (C_Mask && C_Mask->regex && - !((regexec(C_Mask->regex, relpath, 0, NULL, 0) == 0) ^ C_Mask->pat_not)) + if (!mutt_regex_match(C_Mask, relpath)) { return; } diff --git a/index.c b/index.c index 702a0f422..c3c1b9ccb 100644 --- a/index.c +++ b/index.c @@ -3683,8 +3683,7 @@ int mutt_reply_observer(struct NotifyCallback *nc) if (!e || !e->subject) continue; - if (C_ReplyRegex && C_ReplyRegex->regex && - (regexec(C_ReplyRegex->regex, e->subject, 1, pmatch, 0) == 0)) + if (mutt_regex_capture(C_ReplyRegex, e->subject, 1, pmatch)) { e->real_subj = e->subject + pmatch[0].rm_eo; continue; diff --git a/mutt/charset.c b/mutt/charset.c index 7b4ff6186..edf859607 100644 --- a/mutt/charset.c +++ b/mutt/charset.c @@ -252,7 +252,7 @@ static const char *lookup_charset(enum LookupType type, const char *cs) { if (l->type != type) continue; - if (regexec(l->regex.regex, cs, 0, NULL, 0) == 0) + if (mutt_regex_match(&l->regex, cs)) return l->replacement; } return NULL; diff --git a/mutt/path.c b/mutt/path.c index 1f1a72902..9e31a0d4c 100644 --- a/mutt/path.c +++ b/mutt/path.c @@ -263,7 +263,8 @@ bool mutt_path_canon(char *buf, size_t buflen, const char *homedir) size_t dirlen = mutt_str_strlen(dir); if ((len + dirlen) >= buflen) { - mutt_debug(LL_DEBUG3, "result too big for the buffer %ld >= %ld\n", len + dirlen, buflen); + mutt_debug(LL_DEBUG3, "result too big for the buffer %ld >= %ld\n", + len + dirlen, buflen); return false; } diff --git a/mutt/regex.c b/mutt/regex.c index e3c1365c8..5179c29b5 100644 --- a/mutt/regex.c +++ b/mutt/regex.c @@ -4,6 +4,7 @@ * * @authors * Copyright (C) 2017 Richard Russon + * Copyright (C) 2019 Simon Symeonidis * * @copyright * This program is free software: you can redistribute it and/or modify it under @@ -194,9 +195,7 @@ bool mutt_regexlist_match(struct RegexList *rl, const char *str) struct RegexListNode *np = NULL; STAILQ_FOREACH(np, rl, entries) { - if (!np->regex || !np->regex->regex) - continue; - if (regexec(np->regex->regex, str, 0, NULL, 0) == 0) + if (mutt_regex_match(np->regex, str)) { mutt_debug(LL_DEBUG5, "%s matches %s\n", str, np->regex->pattern); return true; @@ -383,7 +382,7 @@ char *mutt_replacelist_apply(struct ReplaceList *rl, char *buf, size_t buflen, c nmatch = np->nmatch; } - if (regexec(np->regex->regex, src, np->nmatch, pmatch, 0) == 0) + if (mutt_regex_capture(np->regex, src, np->nmatch, pmatch)) { tlen = 0; switcher ^= 1; @@ -494,7 +493,7 @@ bool mutt_replacelist_match(struct ReplaceList *rl, char *buf, size_t buflen, co } /* Does this pattern match? */ - if (regexec(np->regex->regex, str, (size_t) np->nmatch, pmatch, 0) == 0) + if (mutt_regex_capture(np->regex, str, (size_t) np->nmatch, pmatch)) { mutt_debug(LL_DEBUG5, "%s matches %s\n", str, np->regex->pattern); mutt_debug(LL_DEBUG5, "%d subs\n", (int) np->regex->regex->re_nsub); @@ -583,3 +582,33 @@ int mutt_replacelist_remove(struct ReplaceList *rl, const char *pat) return nremoved; } + +/** + * mutt_regex_capture - match a regex against a string, with provided options + * @param regex Regex to execute + * @param str String to apply regex on + * @param nmatch Length of matches + * @param matches regmatch_t to hold match indices + * @param flags Type flags, e.g. #DT_REGEX_MATCH_CASE + * @retval bool true if str match, false if str does not match + */ +bool mutt_regex_capture(const struct Regex *regex, const char *str, + size_t nmatch, regmatch_t matches[]) +{ + if (!regex || !str || !regex->regex) + return false; + + int rc = regexec(regex->regex, str, nmatch, matches, 0); + return ((rc == 0) ^ regex->pat_not); +} + +/** + * mutt_regex_match - Shorthand to mutt_regex_capture() + * @param regex Regex which is desired to match against + * @param str String to search with given regex + * @retval bool true if str match, false if str does not match + */ +bool mutt_regex_match(const struct Regex *regex, const char *str) +{ + return mutt_regex_capture(regex, str, 0, NULL); +} diff --git a/mutt/regex3.h b/mutt/regex3.h index a45e26ab8..34afcb858 100644 --- a/mutt/regex3.h +++ b/mutt/regex3.h @@ -100,4 +100,7 @@ bool mutt_replacelist_match(struct ReplaceList *rl, char *buf struct ReplaceListNode *mutt_replacelist_new(void); int mutt_replacelist_remove(struct ReplaceList *rl, const char *pat); +bool mutt_regex_match (const struct Regex *regex, const char *str); +bool mutt_regex_capture(const struct Regex *regex, const char *str, size_t num, regmatch_t matches[]); + #endif /* MUTT_LIB_REGEX_H */ diff --git a/muttlib.c b/muttlib.c index 90918e110..390342aae 100644 --- a/muttlib.c +++ b/muttlib.c @@ -370,13 +370,10 @@ char *mutt_gecos_name(char *dest, size_t destlen, struct passwd *pw) memset(dest, 0, destlen); - if (C_GecosMask && C_GecosMask->regex) + if (mutt_regex_capture(C_GecosMask, pw->pw_gecos, 1, pat_match)) { - if (regexec(C_GecosMask->regex, pw->pw_gecos, 1, pat_match, 0) == 0) - { - mutt_str_strfcpy(dest, pw->pw_gecos + pat_match[0].rm_so, - MIN(pat_match[0].rm_eo - pat_match[0].rm_so + 1, destlen)); - } + mutt_str_strfcpy(dest, pw->pw_gecos + pat_match[0].rm_so, + MIN(pat_match[0].rm_eo - pat_match[0].rm_so + 1, destlen)); } else if ((p = strchr(pw->pw_gecos, ','))) mutt_str_strfcpy(dest, pw->pw_gecos, MIN(destlen, p - pw->pw_gecos + 1)); diff --git a/ncrypt/pgp.c b/ncrypt/pgp.c index c163dc40f..be98a6c1c 100644 --- a/ncrypt/pgp.c +++ b/ncrypt/pgp.c @@ -263,7 +263,7 @@ static int pgp_copy_checksig(FILE *fp_in, FILE *fp_out) while ((line = mutt_file_read_line(line, &linelen, fp_in, &lineno, 0))) { - if (regexec(C_PgpGoodSign->regex, line, 0, NULL, 0) == 0) + if (mutt_regex_match(C_PgpGoodSign, line)) { mutt_debug(LL_DEBUG2, "\"%s\" matches regex\n", line); rc = 0; @@ -310,7 +310,7 @@ static int pgp_check_pgp_decryption_okay_regex(FILE *fp_in) while ((line = mutt_file_read_line(line, &linelen, fp_in, &lineno, 0))) { - if (regexec(C_PgpDecryptionOkay->regex, line, 0, NULL, 0) == 0) + if (mutt_regex_match(C_PgpDecryptionOkay, line)) { mutt_debug(LL_DEBUG2, "\"%s\" matches regex\n", line); rc = 0; diff --git a/nntp/browse.c b/nntp/browse.c index e4979beb9..3358aabd7 100644 --- a/nntp/browse.c +++ b/nntp/browse.c @@ -106,7 +106,7 @@ const char *group_index_format_str(char *buf, size_t buflen, size_t col, int col case 'n': if (C_MarkOld && (folder->ff->nd->last_cached >= folder->ff->nd->first_message) && - (folder->ff->nd->last_cached <= folder->ff->nd->last_message)) + (folder->ff->nd->last_cached <= folder->ff->nd->last_message)) { snprintf(fmt, sizeof(fmt), "%%%sd", prec); snprintf(buf, buflen, fmt, folder->ff->nd->last_message - folder->ff->nd->last_cached); diff --git a/notmuch/mutt_notmuch.c b/notmuch/mutt_notmuch.c index aa814fba0..071cc50fb 100644 --- a/notmuch/mutt_notmuch.c +++ b/notmuch/mutt_notmuch.c @@ -2225,7 +2225,8 @@ static int nm_mbox_check(struct Mailbox *m, int *index_hint) if (m->mtime.tv_sec >= mtime) { - mutt_debug(LL_DEBUG2, "nm: check unnecessary (db=%lu mailbox=%lu)\n", mtime, m->mtime.tv_sec); + mutt_debug(LL_DEBUG2, "nm: check unnecessary (db=%lu mailbox=%lu)\n", mtime, + m->mtime.tv_sec); return 0; } diff --git a/pager.c b/pager.c index 63484a807..47f51924b 100644 --- a/pager.c +++ b/pager.c @@ -951,18 +951,16 @@ int mutt_is_quote_line(char *line, regmatch_t *pmatch) if (!pmatch) pmatch = pmatch_internal; - if (C_QuoteRegex && C_QuoteRegex->regex && - (regexec(C_QuoteRegex->regex, line, 1, pmatch, 0) == 0)) + if (mutt_regex_capture(C_QuoteRegex, line, 1, pmatch)) { - if (C_Smileys && C_Smileys->regex && - (regexec(C_Smileys->regex, line, 1, smatch, 0) == 0)) + if (mutt_regex_capture(C_Smileys, line, 1, smatch)) { if (smatch[0].rm_so > 0) { char c = line[smatch[0].rm_so]; line[smatch[0].rm_so] = 0; - if (regexec(C_QuoteRegex->regex, line, 1, pmatch, 0) == 0) + if (mutt_regex_capture(C_QuoteRegex, line, 1, pmatch)) is_quote = true; line[smatch[0].rm_so] = c; @@ -1730,8 +1728,8 @@ static int display_line(FILE *fp, LOFF_T *last_pos, struct Line **line_info, (*last)--; goto out; } - if (C_QuoteRegex && C_QuoteRegex->regex && - (regexec(C_QuoteRegex->regex, (char *) fmt, 1, pmatch, 0) == 0)) + + if (mutt_regex_capture(C_QuoteRegex, (char *) fmt, 1, pmatch)) { (*line_info)[n].quote = classify_quote(quote_list, (char *) fmt + pmatch[0].rm_so, diff --git a/send.c b/send.c index 13c5c251f..3c3daba9e 100644 --- a/send.c +++ b/send.c @@ -1542,7 +1542,7 @@ static bool search_attach_keyword(char *filename) { fgets(inputline, 1024, fp_att); if (!mutt_is_quote_line(inputline, NULL) && - (regexec(C_AbortNoattachRegex->regex, inputline, 0, NULL, 0) == 0)) + mutt_regex_match(C_AbortNoattachRegex, inputline)) { found = true; break; diff --git a/test/Makefile.autosetup b/test/Makefile.autosetup index efa5553b3..fac39715c 100644 --- a/test/Makefile.autosetup +++ b/test/Makefile.autosetup @@ -323,6 +323,7 @@ PATTERN_OBJS = test/pattern/comp.o \ REGEX_OBJS = test/regex/mutt_regex_compile.o \ test/regex/mutt_regex_free.o \ + test/regex/mutt_regex_match.o \ test/regex/mutt_regexlist_add.o \ test/regex/mutt_regexlist_free.o \ test/regex/mutt_regexlist_match.o \ diff --git a/test/main.c b/test/main.c index 2f36947ec..2bac7ba4d 100644 --- a/test/main.c +++ b/test/main.c @@ -316,6 +316,7 @@ NEOMUTT_TEST_ITEM(test_mutt_pattern_comp) \ NEOMUTT_TEST_ITEM(test_mutt_regex_compile) \ NEOMUTT_TEST_ITEM(test_mutt_regex_free) \ + NEOMUTT_TEST_ITEM(test_mutt_regex_match) \ NEOMUTT_TEST_ITEM(test_mutt_regexlist_add) \ NEOMUTT_TEST_ITEM(test_mutt_regexlist_free) \ NEOMUTT_TEST_ITEM(test_mutt_regexlist_match) \ diff --git a/test/regex/mutt_regex_match.c b/test/regex/mutt_regex_match.c new file mode 100644 index 000000000..d85130a7f --- /dev/null +++ b/test/regex/mutt_regex_match.c @@ -0,0 +1,210 @@ +#define TEST_NO_MAIN + +#include "acutest.h" +#include "mutt/buffer.h" +#include "mutt/regex3.h" +#include "config/common.h" + +static bool test_simple_cases(void) +{ + log_line(__func__); + + struct Buffer *buf = mutt_buffer_new(); + { /* handle edge cases */ + struct Regex *rx = regex_new("hello bob", 0, buf); + + const bool failed = + !TEST_CHECK(mutt_regex_match(NULL, NULL) == false) || + !TEST_CHECK(mutt_regex_match(NULL, "bob the string") == false) || + !TEST_CHECK(mutt_regex_match(rx, NULL) == false); + regex_free(&rx); + + if (failed) + return false; + } + + { /* handle normal cases */ + struct Regex *rx = regex_new("hell", 0, buf); + + const bool failed = + !TEST_CHECK(mutt_regex_match(rx, "hello there")) || + !TEST_CHECK(mutt_regex_match(rx, "hell is not a greeting")) || + !TEST_CHECK(mutt_regex_match(rx, "a demonic elavator is a hellevator")); + regex_free(&rx); + + if (failed) + return false; + } + + { /* test more elaborate regex */ + const char *input = "bob bob bob mary bob jonny bob jon jon joe bob"; + struct Regex *rx = regex_new("bob", 0, buf); + + const bool result = mutt_regex_capture(rx, input, 0, NULL); + const bool failed = !TEST_CHECK(result); + regex_free(&rx); + + if (failed) + return false; + } + + { /* test passing simple flags */ + const char *input = "BOB"; + struct Regex *rx = regex_new("bob", 0, buf); + const bool failed = !TEST_CHECK(mutt_regex_capture(rx, input, 0, 0)); + regex_free(&rx); + + if (failed) + return false; + } + + mutt_buffer_free(&buf); + return true; +} + +static bool test_old_implementation(void) +{ + log_line(__func__); + + // These tests check that the wrapper has the same behavior as + // prior, similar implementations + + const char *bob_line = "definitely bob haha"; + const char *not_bob_line = "john dave marty nothing else here"; + + struct Buffer *buf = mutt_buffer_new(); + { + // from: if (regexec(C_PgpGoodSign->regex, bob_line, 0, NULL, 0) == 0) + // to: if (mutt_regex_match(C_PgpGoodSign, bob_line)) + + struct Regex *rx = regex_new("bob", 0, buf); + const bool old = regexec(rx->regex, bob_line, 0, NULL, 0) == 0; + const bool new = mutt_regex_match(rx, bob_line); + const bool failed = !TEST_CHECK(old == new); + + regex_free(&rx); + if (failed) + return false; + } + + { + // from: if (regexec(C_QuoteRegex->regex, bob_line, 1, pmatch, 0) == 0) + // to: if (mutt_regex_capture(QuoteRegex, bob_line, 1, pmatch)) + + const int nmatch = 1; + regmatch_t pmatch_1[nmatch], pmatch_2[nmatch]; + struct Regex *rx = regex_new("bob", 0, buf); + const bool old = regexec(rx->regex, bob_line, nmatch, pmatch_1, 0) == 0; + const bool new = mutt_regex_capture(rx, bob_line, 1, pmatch_2); + const bool failed_common_behavior = !TEST_CHECK(old == new); + + regex_free(&rx); + if (failed_common_behavior) + return false; + } + + { + // from: if (C_QuoteRegex && C_QuoteRegex->regex && + // (regexec(C_QuoteRegex->regex, bob_line, 1, pmatch, 0) == 0)) + // to: if (mutt_regex_capture(QuoteRegex, bob_line, 1, pmatch)) + + const int nmatch = 1; + regmatch_t pmatch_1[nmatch], pmatch_2[nmatch]; + + struct Regex *rx = regex_new("bob", 0, buf); + const bool old = rx && rx->regex && + (regexec(rx->regex, bob_line, nmatch, pmatch_1, 0) == 0); + const bool new = mutt_regex_capture(rx, bob_line, 1, pmatch_2); + regex_free(&rx); + + const bool failed_common_behavior = !TEST_CHECK(old == new); + if (failed_common_behavior) + return false; + + const bool failed_pmatch = !TEST_CHECK(pmatch_1->rm_so == pmatch_2->rm_so && + pmatch_2->rm_eo == pmatch_2->rm_eo); + if (failed_pmatch) + return false; + } + + { + // from: if ((tmp->type & hook) && + // ((match && (regexec(tmp->regex.regex, match, 0, NULL, 0) == 0)) ^ + // tmp->regex.pat_not)) + // to: if ((tmp->type & hook) && mutt_regex_match(&tmp->regex, match)) + + struct Regex *rx = regex_new("!bob", DT_REGEX_ALLOW_NOT, buf); + const bool old = + (regexec(rx->regex, not_bob_line, 0, NULL, DT_REGEX_ALLOW_NOT) == 0) ^ rx->pat_not; + const bool new = mutt_regex_match(rx, not_bob_line); + regex_free(&rx); + + const bool failed_common_behavior = !TEST_CHECK(old == new); + if (failed_common_behavior) + return false; + + // make sure that bob is *NOT* found + const bool bob_found = !TEST_CHECK(new == true); + if (bob_found) + return false; + } + + { + // from: if (C_Mask && C_Mask->regex && + // !((regexec(C_Mask->regex, mdata->group, 0, NULL, 0) == 0) + // ^ C_Mask->pat_not)) + // to: if(mutt_regex_match(C_Mask, de->d_name)) + + struct Regex *rx = regex_new("!bob", DT_REGEX_ALLOW_NOT, buf); + const bool old = rx && rx->regex && + !((regexec(rx->regex, not_bob_line, 0, NULL, 0) == 0) ^ rx->pat_not); + const bool new = mutt_regex_match(rx, bob_line); + regex_free(&rx); + + const bool failed_common_behavior = !TEST_CHECK(old == new); + if (failed_common_behavior) + return false; + } + + { + // from: if (C_Mask && C_Mask->regex && + // !((regexec(C_Mask->regex, mdata->group, 0, NULL, 0) == 0) ^ C_Mask->pat_not)) + // to: if (!mutt_regex_match(C_Mask, mdata->group)) + struct Regex *rx = regex_new("!bob", DT_REGEX_ALLOW_NOT, buf); + const bool old = + (rx && rx->regex) && !((regexec(rx->regex, line, 0, NULL, 0) == 0) ^ rx->pat_not); + const bool new = !mutt_regex_match(rx, line); + regex_free(&rx); + + const bool failed_common_behavior = !TEST_CHECK(old == new); + if (failed_common_behavior) + return false; + } + + { + // if ((regexec(hook->regex.regex, url, 0, NULL, 0) == 0) ^ hook->regex.pat_not) + // if (mutt_regex_match(&hook->regex, url)) + + struct Regex *rx = regex_new("bob", 0, buf); + const bool old = (regexec(rx->regex, bob_line, 0, NULL, 0) == 0) ^ rx->pat_not; + const bool new = mutt_regex_match(rx, bob_line); + regex_free(&rx); + + const bool failed_common_behavior = !TEST_CHECK(old == new); + if (failed_common_behavior) + return false; + } + + mutt_buffer_free(&buf); + return true; +} + +void test_mutt_regex_match(void) +{ + bool (*tests[])(void) = { test_simple_cases, test_old_implementation }; + + const size_t num_tests = sizeof(tests) / sizeof(tests[0]); + + for (size_t i = 0; i < num_tests; ++i) + TEST_CHECK(tests[i]()); +}