Blob Blame History Raw
/*
 * fatest.c:
 *
 * Copyright (C) 2007-2016 David Lutterkort
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 *
 * Author: David Lutterkort <dlutter@redhat.com>
 */

#include <config.h>

#include "fa.h"
#include "cutest.h"
#include "internal.h"
#include "memory.h"
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

#define FA_DOT_DIR "FA_DOT_DIR"

struct fa_list {
    struct fa_list *next;
    struct fa *fa;
};

static struct fa_list *fa_list;

static void print_regerror(int err, const char *regexp) {
    size_t size;
    char *errbuf;
    size = regerror(err, NULL, NULL, 0);
    if (ALLOC_N(errbuf, size) < 0)
        die_oom();
    regerror(err, NULL, errbuf, size);
    if (strlen(regexp) > 40) {
        char *s = strndup(regexp, 40);
        fprintf(stderr, "Error building fa from %s...:\n", s);
        free(s);
    } else {
        fprintf(stderr, "Error building fa from %s:\n", regexp);
    }
    fprintf(stderr, "  %s\n", errbuf);
    free(errbuf);
}

static void setup(ATTRIBUTE_UNUSED CuTest *tc) {
    fa_list = NULL;
}

static void teardown(ATTRIBUTE_UNUSED CuTest *tc) {
    list_for_each(fl, fa_list) {
        fa_free(fl->fa);
    }
    list_free(fa_list);
}

static struct fa *mark(struct fa *fa) {
    struct fa_list *fl;

    if (fa != NULL) {
        if (ALLOC(fl) < 0)
            die_oom();
        fl->fa = fa;
        list_cons(fa_list, fl);
    }
    return fa;
}

static void assertAsRegexp(CuTest *tc, struct fa *fa) {
    char *re;
    size_t re_len;
    struct fa *fa1, *fa2;
    struct fa *empty = mark(fa_make_basic(FA_EPSILON));
    int r;

    /* Jump through some hoops to make FA1 a copy of FA */
    fa1 = mark(fa_concat(fa, empty));
    /* Minimize FA1, otherwise the regexp returned is enormous for the */
    /* monster (~ 2MB) and fa_compile becomes incredibly slow          */
    r = fa_minimize(fa1);
    if (r < 0)
        die_oom();

    r = fa_as_regexp(fa1, &re, &re_len);
    CuAssertIntEquals(tc, 0, r);

    r = fa_compile(re, re_len, &fa2);
    if (r != REG_NOERROR) {
        print_regerror(r, re);
    }
    CuAssertIntEquals(tc, REG_NOERROR, r);
    CuAssertTrue(tc, fa_equals(fa, fa2));

    fa_free(fa2);
    free(re);
}

static struct fa *make_fa(CuTest *tc,
                          const char *regexp, size_t reglen,
                          int exp_err) {
    struct fa *fa;
    int r;

    r = fa_compile(regexp, reglen, &fa);
    if (r == REG_ESPACE)
        die_oom();
    if (exp_err == REG_NOERROR) {
        if (r != REG_NOERROR)
            print_regerror(r, regexp);
        CuAssertIntEquals(tc, REG_NOERROR, r);
        CuAssertPtrNotNull(tc, fa);
        mark(fa);
        assertAsRegexp(tc, fa);
    } else {
        CuAssertIntEquals(tc, exp_err, r);
        CuAssertPtrEquals(tc, NULL, fa);
    }
    return fa;
}

static struct fa *make_good_fa(CuTest *tc, const char *regexp) {
    return make_fa(tc, regexp, strlen(regexp), REG_NOERROR);
}

static void dot(struct fa *fa) {
    static int count = 0;
    FILE *fp;
    const char *dot_dir;
    char *fname;
    int r;

    if ((dot_dir = getenv(FA_DOT_DIR)) == NULL)
        return;

    r = asprintf(&fname, "%s/fa_test_%02d.dot", dot_dir, count++);
    if (r == -1)
        return;

    if ((fp = fopen(fname, "w")) == NULL) {
        free(fname);
        return;
    }

    fa_dot(fp, fa);
    fclose(fp);

    free(fname);
}

static void testBadRegexps(CuTest *tc) {
    const char *const re1 = "(x";
    const char *const re2 = "a{5,3}";
    make_fa(tc, re1, strlen(re1), REG_EPAREN);
    make_fa(tc, re2, strlen(re2), REG_BADBR);
}

/* Stress test, mostly good to check that allocation is clean */
static void testMonster(CuTest *tc) {
#define WORD "[a-zA-Z_0-9]+"
#define CWS  "([ \\n\\t]+|\\/\\*([^\\*]|\\*[^\\/])*\\*\\/)*"
#define QUOTED "\"[^\"]*\""
#define ELT    "\\(" WORD "(," CWS WORD "){3}\\)"

    static const char *const monster =
        "(" WORD "|" QUOTED "|" "\\(" CWS ELT  "(," CWS ELT ")*" CWS "\\))";

#undef ELT
#undef QUOTED
#undef CWS
#undef WORD

    struct fa *fa, *fas;
    char *upv, *pv, *v;
    size_t upv_len;

    fa = make_good_fa(tc, monster);

    fa_ambig_example(fa, fa, &upv, &upv_len, &pv, &v);

    /* Monster can't be concatenated with itself */
    CuAssertStrEquals(tc, "AAA", upv);
    CuAssertStrEquals(tc, "AA", pv);
    CuAssertStrEquals(tc, "A", v);
    free(upv);

    /* Monster can also not be starred */
    fas = mark(fa_iter(fa, 0, -1));
    /* Minimize FAS, otherwise the example returned is nondeterministic,
       since example generation depends on the structure of the FA.
       FIXME: Explain why UPV with the unminimized FAS changes to a much
       longer string simply when allocation patterns change (e.g., by
       running the test under valgrind vs. plain glibc malloc. Fishy ?
       FA_EXAMPLE should depend on the structure of the FA, but not
       incidental details like sorting of transitions.
     */
    fa_minimize(fas);
    fa_ambig_example(fas, fa, &upv, &upv_len, &pv, &v);

    CuAssertStrEquals(tc, "AA", upv);
    CuAssertStrEquals(tc, "AA", pv);
    CuAssertStrEquals(tc, "A", v);
    free(upv);
}

static void testChars(CuTest *tc) {
    struct fa *fa1, *fa2, *fa3;

    fa1 = make_good_fa(tc, ".");
    fa2 = make_good_fa(tc, "[a-z]");
    CuAssertTrue(tc, fa_contains(fa2, fa1));

    fa1 = make_good_fa(tc, "(.|\n)");
    CuAssertTrue(tc, fa_contains(fa2, fa1));

    fa1 = mark(fa_intersect(fa1, fa2));
    CuAssertTrue(tc, fa_equals(fa1, fa2));

    fa1 = make_good_fa(tc, "[^b-dxyz]");
    fa2 = make_good_fa(tc, "[a-z]");
    fa3 = mark(fa_intersect(fa1, fa2));
    fa2 = make_good_fa(tc, "[ae-w]");
    CuAssertTrue(tc, fa_equals(fa2, fa3));
}

static void testManualAmbig(CuTest *tc) {
    /* The point of this test is mostly to teach me how Anders Moeller's
       algorithm for finding ambiguous strings works.

       For the two languages a1 = a|ab and a2 = a|ba, a1.a2 has one
       ambiguous word aba, which can be split as a.ba and ab.a.

       This uses X and Y as the markers*/

    struct fa *a1f = make_good_fa(tc, "Xa|XaXb");
    struct fa *a1t = make_good_fa(tc, "(YX)*Xa|(YX)*Xa(YX)*Xb");
    struct fa *a2f = make_good_fa(tc, "Xa|XbXa");
    struct fa *a2t = make_good_fa(tc, "(YX)*Xa|((YX)*Xb(YX)*Xa)");
    struct fa *mp = make_good_fa(tc, "YX(X(.|\n))+");
    struct fa *ms = make_good_fa(tc, "YX(X(.|\n))*");
    struct fa *sp = make_good_fa(tc, "(X(.|\n))+YX");
    struct fa *ss = make_good_fa(tc, "(X(.|\n))*YX");

    struct fa *a1f_mp = mark(fa_concat(a1f, mp));
    struct fa *a1f_mp_a1t = mark(fa_intersect(a1f_mp, a1t));
    struct fa *b1 = mark(fa_concat(a1f_mp_a1t, ms));

    struct fa *sp_a2f = mark(fa_concat(sp, a2f));
    struct fa *sp_a2f_a2t = mark(fa_intersect(sp_a2f, a2t));
    struct fa *b2 = mark(fa_concat(ss, sp_a2f_a2t));

    struct fa *amb = mark(fa_intersect(b1, b2));
    struct fa *exp = make_good_fa(tc, "XaYXXbYXXa");
    CuAssertTrue(tc, fa_equals(exp, amb));
}

static void testContains(CuTest *tc) {
    struct fa *fa1, *fa2, *fa3;

    fa1 = make_good_fa(tc, "ab*");
    fa2 = make_good_fa(tc, "ab+");
    fa3 = make_good_fa(tc, "ab+c*|acc");

    CuAssertTrue(tc, fa_contains(fa1, fa1));
    CuAssertTrue(tc, fa_contains(fa2, fa2));
    CuAssertTrue(tc, fa_contains(fa3, fa3));

    CuAssertTrue(tc, ! fa_contains(fa1, fa2));
    CuAssertTrue(tc, fa_contains(fa2, fa1));
    CuAssertTrue(tc, fa_contains(fa2, fa3));
    CuAssertTrue(tc, ! fa_contains(fa3, fa2));
    CuAssertTrue(tc, ! fa_contains(fa1, fa3));
    CuAssertTrue(tc, ! fa_contains(fa3, fa1));
}

static void testIntersect(CuTest *tc) {
    struct fa *fa1, *fa2, *fa;

    fa1 = make_good_fa(tc, "[a-zA-Z]*[.:=]([0-9]|[^A-Z])*");
    fa2 = make_good_fa(tc, "[a-z][:=][0-9a-z]+");
    fa = mark(fa_intersect(fa1, fa2));
    CuAssertPtrNotNull(tc, fa);
    CuAssertTrue(tc, fa_equals(fa, fa2));
    CuAssertTrue(tc, ! fa_equals(fa, fa1));
}

static void testComplement(CuTest *tc) {
    struct fa *fa1 = make_good_fa(tc, "[b-y]+");
    struct fa *fa2 = mark(fa_complement(fa1));
    /* We use '()' to match the empty word explicitly */
    struct fa *fa3 = make_good_fa(tc, "(()|[b-y]*[^b-y](.|\n)*)");

    CuAssertTrue(tc, fa_equals(fa2, fa3));

    fa2 = mark(fa_complement(fa2));
    CuAssertTrue(tc, fa_equals(fa1, fa2));
}

static void testOverlap(CuTest *tc) {
    struct fa *fa1 = make_good_fa(tc, "a|ab");
    struct fa *fa2 = make_good_fa(tc, "a|ba");
    struct fa *p   = mark(fa_overlap(fa1, fa2));
    struct fa *exp = make_good_fa(tc, "b");

    CuAssertTrue(tc, fa_equals(exp, p));

    fa1 = make_good_fa(tc, "a|b|c|abc");
    fa2 = mark(fa_iter(fa1, 0, -1));
    exp = make_good_fa(tc, "bc");
    p = mark(fa_overlap(fa1, fa2));

    CuAssertTrue(tc, fa_equals(exp, p));
}

static void assertExample(CuTest *tc, const char *regexp, const char *exp) {
    struct fa *fa = make_good_fa(tc, regexp);
    size_t xmpl_len;
    char *xmpl;
    fa_example(fa, &xmpl, &xmpl_len);
    CuAssertStrEquals(tc, exp, xmpl);
    free(xmpl);

    fa_nocase(fa);
    char *s = strdup(exp);
    for (int i = 0; i < strlen(s); i++) s[i] = tolower(s[i]);

    fa_example(fa, &xmpl, &xmpl_len);
    CuAssertStrEquals(tc, s, xmpl);
    free(xmpl);
    free(s);
}

static void testExample(CuTest *tc) {
    assertExample(tc, "(.|\n)", "A");
    assertExample(tc, "(\n|\t|x)", "x");
    assertExample(tc, "[^b-y]", "A");
    assertExample(tc, "x*", "x");
    assertExample(tc, "yx*", "y");
    assertExample(tc, "ab+cx*", "abc");
    assertExample(tc, "ab+cx*|y*", "y");
    assertExample(tc, "u*|[0-9]", "u");
    assertExample(tc, "u+|[0-9]", "u");
    assertExample(tc, "vu+|[0-9]", "0");
    assertExample(tc, "vu{2}|[0-9]", "0");
    assertExample(tc, "\\[", "[");
    assertExample(tc, "[\\]", "\\");
    assertExample(tc, "a{3}", "aaa");
    assertExample(tc, "a{3,}", "aaa");

    assertExample(tc, "\001((\002.)*\001)+\002", "\001\001\002");
    assertExample(tc, "\001((\001.)*\002)+\002", "\001\002\002");

    assertExample(tc, "a[^\001-\004]+b", "aAb");

    /* A strange way to write a? - allowed by POSIX */
    assertExample(tc, "(a|)", "a");
    assertExample(tc, "(|a)", "a");

    struct fa *fa1 = mark(fa_make_basic(FA_EMPTY));
    size_t xmpl_len;
    char *xmpl;
    fa_example(fa1, &xmpl, &xmpl_len);
    CuAssertPtrEquals(tc, NULL, xmpl);

    fa1 = mark(fa_make_basic(FA_EPSILON));
    fa_example(fa1, &xmpl, &xmpl_len);
    CuAssertStrEquals(tc, "", xmpl);
    free(xmpl);
}

static void assertAmbig(CuTest *tc, const char *regexp1, const char *regexp2,
                        const char *exp_upv,
                        const char *exp_pv, const char *exp_v) {

    struct fa *fa1 = make_good_fa(tc, regexp1);
    struct fa *fa2 = make_good_fa(tc, regexp2);
    char *upv, *pv, *v;
    size_t upv_len;
    fa_ambig_example(fa1, fa2, &upv, &upv_len, &pv, &v);
    CuAssertPtrNotNull(tc, upv);
    CuAssertPtrNotNull(tc, pv);
    CuAssertPtrNotNull(tc, v);

    CuAssertStrEquals(tc, exp_upv, upv);
    CuAssertIntEquals(tc, strlen(exp_upv), upv_len);
    CuAssertStrEquals(tc, exp_pv, pv);
    CuAssertStrEquals(tc, exp_v, v);
    free(upv);
}

static void assertNotAmbig(CuTest *tc, const char *regexp1,
                           const char *regexp2) {
    struct fa *fa1 = make_good_fa(tc, regexp1);
    struct fa *fa2 = make_good_fa(tc, regexp2);
    char *upv;
    size_t upv_len;
    fa_ambig_example(fa1, fa2, &upv, &upv_len, NULL, NULL);
    CuAssertPtrEquals(tc, NULL, upv);
}

static void testAmbig(CuTest *tc) {
    assertAmbig(tc, "a|ab", "a|ba", "aba", "ba", "a");
    assertAmbig(tc, "(a|ab)*", "a|ba", "aba", "ba", "a");
    assertAmbig(tc, "(a|b|c|d|abcd)", "(a|b|c|d|abcd)*",
                "abcd", "bcd", "");
    assertAmbig(tc, "(a*)*", "a*", "a", "a", "");
    assertAmbig(tc, "(a+)*", "a+", "aa", "aa", "a");

    assertNotAmbig(tc, "a*", "a");
    assertNotAmbig(tc, "(a*b)*", "a*b");
    assertNotAmbig(tc, "(a|b|c|d|abcd)", "(a|b|c|d|abcd)");
}

static void testAmbigWithNuls(CuTest *tc) {
    struct fa *fa1 = make_fa(tc, "X\0ba?", 5, REG_NOERROR);
    struct fa *fa2 = make_fa(tc, "a?\0Y", 4, REG_NOERROR);
    char *upv, *pv, *v;
    size_t upv_len;
    int r = fa_ambig_example(fa1, fa2, &upv, &upv_len, &pv, &v);
    CuAssertIntEquals(tc, 0, r);
    CuAssertIntEquals(tc, 6, (int)upv_len);
    /* u = "X\0b" */
    size_t u_len = pv - upv;
    CuAssertIntEquals(tc, 3, u_len);
    CuAssertIntEquals(tc, (int)'X', upv[0]);
    CuAssertIntEquals(tc, (int)'\0', upv[1]);
    CuAssertIntEquals(tc, (int)'b', upv[2]);
    /* p = "a" */
    size_t p_len = v - pv;
    CuAssertIntEquals(tc, 1, p_len);
    CuAssertIntEquals(tc, (int)'a', pv[0]);
    /* v = "\0Y" */
    size_t v_len = upv_len - (v - upv);
    CuAssertIntEquals(tc, 2, v_len);
    CuAssertIntEquals(tc, (int)'\0', v[0]);
    CuAssertIntEquals(tc, (int)'Y', v[1]);
    free(upv);
}

static void assertFaAsRegexp(CuTest *tc, const char *regexp) {
    char *re;
    size_t re_len;
    struct fa *fa1 = make_good_fa(tc, regexp);
    struct fa *fa2;
    int r;

    r = fa_as_regexp(fa1, &re, &re_len);
    CuAssertIntEquals(tc, 0, r);

    r = fa_compile(re, strlen(re), &fa2);
    CuAssertIntEquals(tc, REG_NOERROR, r);

    CuAssert(tc, regexp, fa_equals(fa1, fa2));

    fa_free(fa2);
    free(re);
}

static void testAsRegexp(CuTest *tc) {
    assertFaAsRegexp(tc, "a*");
    assertFaAsRegexp(tc, "abcd");
    assertFaAsRegexp(tc, "ab|cd");
    assertFaAsRegexp(tc, "[a-z]+");
    assertFaAsRegexp(tc, "[]a-]+");
    assertFaAsRegexp(tc, "[^0-9A-Z]");
    assertFaAsRegexp(tc, "ab|(xy[A-Z0-9])*(uv[^0-9]?)");
    assertFaAsRegexp(tc, "[A-CE-GI-LN-QS-Z]");
}

static void testAsRegexpMinus(CuTest *tc) {
    struct fa *fa1 = make_good_fa(tc, "[A-Za-z]+");
    struct fa *fa2 = make_good_fa(tc, "Deny(Users|Groups|Other)");
    struct fa *fa = mark(fa_minus(fa1, fa2));
    char *re;
    size_t re_len;
    int r;

    r = fa_as_regexp(fa, &re, &re_len);
    CuAssertIntEquals(tc, 0, r);

    struct fa *far = make_good_fa(tc, re);
    CuAssertTrue(tc, fa_equals(fa, far));

    free(re);
}

static void testRangeEnd(CuTest *tc) {
    const char *const re = "[1-0]";
    make_fa(tc, re, strlen(re), REG_ERANGE);
}

static void testNul(CuTest *tc) {
    static const char *const re0 = "a\0b";
    int re0_len = 3;

    struct fa *fa1 = make_fa(tc, "a\0b", re0_len, REG_NOERROR);
    struct fa *fa2 = make_good_fa(tc, "a.b");
    char *re;
    size_t re_len;
    int r;

    CuAssertTrue(tc, fa_contains(fa1, fa2));

    r = fa_as_regexp(fa1, &re, &re_len);
    CuAssertIntEquals(tc, 0, r);
    CuAssertIntEquals(tc, re0_len, re_len);
    CuAssertIntEquals(tc, 0, memcmp(re0, re, re0_len));
    free(re);
}

static void testRestrictAlphabet(CuTest *tc) {
    const char *re = "ab|(xy[B-Z0-9])*(uv[^0-9]?)";
    struct fa *fa_exp = make_good_fa(tc, "((xy[0-9])*)uv[^0-9A-Z]?|ab");
    struct fa *fa_act = NULL;
    size_t nre_len;
    char *nre;
    int r;

    r = fa_restrict_alphabet(re, strlen(re), &nre, &nre_len, 'A', 'Z');
    CuAssertIntEquals(tc, 0, r);
    CuAssertIntEquals(tc, nre_len, strlen(nre));
    fa_act = make_good_fa(tc, nre);
    CuAssertTrue(tc, fa_equals(fa_exp, fa_act));
    free(nre);

    r = fa_restrict_alphabet("HELLO", strlen("HELLO"),
                             &nre, &nre_len, 'A', 'Z');
    CuAssertIntEquals(tc, -2, r);
    CuAssertPtrEquals(tc, NULL, nre);

    r = fa_restrict_alphabet("a{2,", strlen("a{2"), &nre, &nre_len, 'A', 'Z');
    CuAssertIntEquals(tc, REG_EBRACE, r);
}

static void testExpandCharRanges(CuTest *tc) {
    const char *re = "[1-3]*|[a-b]([^\nU-X][^\n])*";
    const char *re2 = "a\\|b";

    char *nre;
    size_t nre_len;
    int r;

    r = fa_expand_char_ranges(re, strlen(re), &nre, &nre_len);
    CuAssertIntEquals(tc, 0, r);
    CuAssertStrEquals(tc, "[123]*|[ab]([^\nUVWX].)*", nre);
    CuAssertIntEquals(tc, strlen(nre), nre_len);
    free(nre);

    r = fa_expand_char_ranges(re2, strlen(re2), &nre, &nre_len);
    CuAssertIntEquals(tc, 0, r);
    CuAssertStrEquals(tc, re2, nre);
    free(nre);
}

static void testNoCase(CuTest *tc) {
    struct fa *fa1 = make_good_fa(tc, "[a-z0-9]");
    struct fa *fa2 = make_good_fa(tc, "B");
    struct fa *fa, *exp;
    int r;

    fa_nocase(fa1);
    fa = fa_intersect(fa1, fa2);
    CuAssertPtrNotNull(tc, fa);
    r = fa_equals(fa, fa2);
    fa_free(fa);
    CuAssertIntEquals(tc, 1, r);

    fa = fa_concat(fa1, fa2);
    exp = make_good_fa(tc, "[a-zA-Z0-9]B");
    r = fa_equals(fa, exp);
    fa_free(fa);
    CuAssertIntEquals(tc, 1, r);
}

static void testExpandNoCase(CuTest *tc) {
    const char *p1 = "aB";
    const char *p2 = "[a-cUV]";
    const char *p3 = "[^a-z]";
    char *s;
    size_t len;
    int r;

    r = fa_expand_nocase(p1, strlen(p1), &s, &len);
    CuAssertIntEquals(tc, 0, r);
    CuAssertStrEquals(tc, "[Aa][Bb]", s);
    free(s);

    r = fa_expand_nocase(p2, strlen(p2), &s, &len);
    CuAssertIntEquals(tc, 0, r);
    CuAssertStrEquals(tc, "[A-CUVa-cuv]", s);
    free(s);

    r = fa_expand_nocase(p3, strlen(p3), &s, &len);
    CuAssertIntEquals(tc, 0, r);
    CuAssertStrEquals(tc, "[^A-Za-z]", s);
    free(s);
}

static void testNoCaseComplement(CuTest *tc) {
    const char *key_s = "keY";
    struct fa *key = make_good_fa(tc, key_s);
    struct fa *isect = NULL;

    fa_nocase(key);

    struct fa *comp = mark(fa_complement(key));

    key = make_good_fa(tc, key_s);

    /* We used to have a bug in totalize that caused the intersection
     * to contain "keY" */
    isect = fa_intersect(key, comp);

    CuAssertIntEquals(tc, 1, fa_is_basic(isect, FA_EMPTY));
    fa_free(isect);
}

static void free_words(int n, char **words) {
    for (int i=0; i < n; i++) {
        free(words[i]);
    }
    free(words);
}

static void testEnumerate(CuTest *tc) {
    struct fa *fa1 = make_good_fa(tc, "[ab](cc|dd)");
    static const char *const fa1_expected[] =
        { "acc", "add", "bcc", "bdd" };
    struct fa *fa_inf = make_good_fa(tc, "a(b*|d)c");
    struct fa *fa_empty = make_good_fa(tc, "a?");

    char **words;
    int r;

    r = fa_enumerate(fa1, 2, &words);
    CuAssertIntEquals(tc, -2, r);
    CuAssertPtrEquals(tc, NULL, words);

    r = fa_enumerate(fa1, 10, &words);
    CuAssertIntEquals(tc, 4, r);
    CuAssertPtrNotNull(tc, words);

    for (int i=0; i < r; i++) {
        int found = 0;
        for (int j=0; j < ARRAY_CARDINALITY(fa1_expected); j++) {
            if (STREQ(words[i], fa1_expected[j]))
                found = 1;
        }
        if (!found) {
            char *msg;
            /* Ignore return of asprintf intentionally */
            r = asprintf(&msg, "Generated word %s not expected", words[i]);
            CuFail(tc, msg);
        }
    }
    free_words(10, words);

    r = fa_enumerate(fa_inf, 100, &words);
    CuAssertIntEquals(tc, -2, r);
    CuAssertPtrEquals(tc, NULL, words);

    r = fa_enumerate(fa_empty, 10, &words);
    CuAssertIntEquals(tc, 2, r);
    CuAssertPtrNotNull(tc, words);
    CuAssertStrEquals(tc, "", words[0]);
    CuAssertStrEquals(tc, "a", words[1]);
    free_words(10, words);
}

int main(int argc, char **argv) {
    if (argc == 1) {
        char *output = NULL;
        CuSuite* suite = CuSuiteNew();
        CuSuiteSetup(suite, setup, teardown);

        SUITE_ADD_TEST(suite, testBadRegexps);
        SUITE_ADD_TEST(suite, testMonster);
        SUITE_ADD_TEST(suite, testChars);
        SUITE_ADD_TEST(suite, testManualAmbig);
        SUITE_ADD_TEST(suite, testContains);
        SUITE_ADD_TEST(suite, testIntersect);
        SUITE_ADD_TEST(suite, testComplement);
        SUITE_ADD_TEST(suite, testOverlap);
        SUITE_ADD_TEST(suite, testExample);
        SUITE_ADD_TEST(suite, testAmbig);
        SUITE_ADD_TEST(suite, testAmbigWithNuls);
        SUITE_ADD_TEST(suite, testAsRegexp);
        SUITE_ADD_TEST(suite, testAsRegexpMinus);
        SUITE_ADD_TEST(suite, testRangeEnd);
        SUITE_ADD_TEST(suite, testNul);
        SUITE_ADD_TEST(suite, testRestrictAlphabet);
        SUITE_ADD_TEST(suite, testExpandCharRanges);
        SUITE_ADD_TEST(suite, testNoCase);
        SUITE_ADD_TEST(suite, testExpandNoCase);
        SUITE_ADD_TEST(suite, testNoCaseComplement);
        SUITE_ADD_TEST(suite, testEnumerate);

        CuSuiteRun(suite);
        CuSuiteSummary(suite, &output);
        CuSuiteDetails(suite, &output);
        printf("%s\n", output);
        free(output);
        int result = suite->failCount;
        CuSuiteFree(suite);
        return result;
    }

    for (int i=1; i<argc; i++) {
        struct fa *fa;
        int r;
        if ((r = fa_compile(argv[i], strlen(argv[i]), &fa)) != REG_NOERROR) {
            print_regerror(r, argv[i]);
        } else {
            dot(fa);
            size_t s_len;
            char *s;
            fa_example(fa, &s, &s_len);
            printf("Example for %s: %s\n", argv[i], s);
            free(s);
            char *re;
            size_t re_len;
            r = fa_as_regexp(fa, &re, &re_len);
            if (r == 0) {
                printf("/%s/ = /%s/\n", argv[i], re);
                free(re);
            } else {
                printf("/%s/ = ***\n", argv[i]);
            }
            fa_free(fa);
        }
    }
}

/*
 * Local variables:
 *  indent-tabs-mode: nil
 *  c-indent-level: 4
 *  c-basic-offset: 4
 *  tab-width: 4
 * End:
 */