Blob Blame History Raw
/*
 * test-xpath.c: check that XPath expressions yield the expected result
 *
 * 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 <stdlib.h>
#include <stdio.h>
#include <ctype.h>

#include <augeas.h>
#include <internal.h>
#include <memory.h>

#include "cutest.h"

static const char *abs_top_srcdir;
static char *root;

#define KW_TEST "test"

struct entry {
    struct entry *next;
    char *path;
    char *value;
};

struct test {
    struct test *next;
    char *name;
    char *match;
    struct entry *entries;
};

#define die(msg)                                                    \
    do {                                                            \
        fprintf(stderr, "%d: Fatal error: %s\n", __LINE__, msg);    \
        exit(EXIT_FAILURE);                                         \
    } while(0)

static char *skipws(char *s) {
    while (isspace(*s)) s++;
    return s;
}

static char *findws(char *s) {
    while (*s && ! isspace(*s)) s++;
    return s;
}

static char *token(char *s, char **tok) {
    char *t = skipws(s);
    s = findws(t);
    *tok = strndup(t, s - t);
    return s;
}

static char *token_to_eol(char *s, char **tok) {
    char *t = skipws(s);
    while (*s && *s != '\n') s++;
    *tok = strndup(t, s - t);
    return s;
}

static char *findpath(char *s, char **p) {
    char *t = skipws(s);

    while (*s && *s != '=') s++;
    if (s > t) {
        s -= 1;
        while (*s && isspace(*s)) s -= 1;
        s += 1;
    }
    *p = strndup(t, s - t);
    return s;
}

static void free_tests(struct test *test) {
    while (test != NULL) {
        struct test *del = test;
        test = test->next;
        struct entry *entry = del->entries;
        while (entry != NULL) {
            struct entry *e = entry;
            entry = entry->next;
            free(e->path);
            free(e->value);
            free(e);
        }
        free(del->name);
        free(del->match);
        free(del);
    }
}

static struct test *read_tests(void) {
    char *fname;
    FILE *fp;
    char line[BUFSIZ];
    struct test *result = NULL, *t = NULL;
    int lc = 0;

    if (asprintf(&fname, "%s/tests/xpath.tests", abs_top_srcdir) < 0)
        die("asprintf fname");

    if ((fp = fopen(fname, "r")) == NULL)
        die("fopen xpath.tests");

    while (fgets(line, BUFSIZ, fp) != NULL) {
        lc += 1;
        char *s = skipws(line);
        if (*s == '#' || *s == '\0')
            continue;
        if (STREQLEN(s, KW_TEST, strlen(KW_TEST))) {
            if (ALLOC(t) < 0)
                die("out of memory");
            list_append(result, t);
            s = token(s + strlen(KW_TEST), &(t->name));
            s = token_to_eol(s, &(t->match));
        } else {
            struct entry *e = NULL;
            if (ALLOC(e) < 0)
                die("out of memory");
            list_append(t->entries, e);
            s = findpath(s, &(e->path));
            s = skipws(s);
            if (*s) {
                if (*s != '=') {
                    fprintf(stderr,
                     "line %d: either list only a path or path = value\n", lc);
                    die("xpath.tests has incorrect format");
                }
                s = token_to_eol(s + 1, &(e->value));
            }
        }
        s = skipws(s);
        if (*s != '\0') {
            fprintf(stderr, "line %d: junk at end of line\n", lc);
            die("xpath.tests has incorrect format");
        }
    }
    fclose(fp);
    free(fname);
    return result;
}

static void print_pv(const char *path, const char *value) {
    if (value)
        printf("    %s = %s\n", path, value);
    else
        printf("    %s\n", path);
}

static int has_match(const char *path, char **matches, int nmatches) {
    int found = 0;
    for (int i=0; i < nmatches; i++) {
        if (matches[i] != NULL && STREQ(path, matches[i])) {
            found = 1;
            break;
        }
    }
    return found;
}

static int run_one_test(struct augeas *aug, struct test *t) {
    int nexp = 0, nact;
    char **matches;
    int result = 0;

    printf("%-30s ... ", t->name);
    list_for_each(e, t->entries)
        nexp++;
    nact = aug_match(aug, t->match, &matches);
    if (nact != nexp) {
        result = -1;
    } else {
        struct entry *e;
        const char *val;

        for (e = t->entries; e != NULL; e = e->next) {
            if (! has_match(e->path, matches, nact))
                result = -1;
            if (! streqv(e->value, "...")) {
                aug_get(aug, e->path, &val);
                if (!streqv(e->value, val))
                    result = -1;
            }
        }
    }
    if (result == 0) {
        printf("PASS\n");
    } else {
        printf("FAIL\n");

        printf("  Match: %s\n", t->match);
        printf("  Expected: %d entries\n", nexp);
        list_for_each(e, t->entries) {
            print_pv(e->path, e->value);
        }
        if (nact < 0) {
            printf("  Actual: aug_match failed\n");
            } else {
            printf("  Actual: %d entries\n", nact);
        }
        for (int i=0; i < nact; i++) {
            const char *val;
            aug_get(aug, matches[i], &val);
            print_pv(matches[i], val);
        }
    }
    for (int i=0; i < nact; i++) {
        free(matches[i]);
    }
    free(matches);
    return result;
}

static int test_rm_var(struct augeas *aug) {
    int r;

    printf("%-30s ... ", "rm_var");
    r = aug_defvar(aug, "h", "/files/etc/hosts/2/ipaddr");
    if (r < 0)
        die("aug_defvar failed");

    r = aug_match(aug, "$h", NULL);
    if (r != 1) {
        fprintf(stderr, "expected 1 match, got %d\n", r);
        goto fail;
    }

    r = aug_rm(aug, "/files/etc/hosts/2");
    if (r != 4) {
        fprintf(stderr, "expected 4 nodes removed, got %d\n", r);
        goto fail;
    }

    r = aug_match(aug, "$h", NULL);
    if (r != 0) {
        fprintf(stderr, "expected no match, got %d\n", r);
        goto fail;
    }
    printf("PASS\n");
    return 0;
 fail:
    printf("FAIL\n");
    return -1;
}

static int test_defvar_nonexistent(struct augeas *aug) {
    int r;

    printf("%-30s ... ", "defvar_nonexistent");
    r = aug_defvar(aug, "x", "/foo/bar");
    if (r < 0)
        die("aug_defvar failed");

    r = aug_set(aug, "$x", "baz");
    if (r != -1)
        goto fail;
    printf("PASS\n");
    return 0;
 fail:
    printf("FAIL\n");
    return -1;
}

static int test_defnode_nonexistent(struct augeas *aug) {
    int r, created;

    printf("%-30s ... ", "defnode_nonexistent");
    r = aug_defnode(aug, "x", "/defnode/bar[0 = 1]", "foo", &created);
    if (r != 1)
        die("aug_defnode failed");
    if (created != 1) {
        fprintf(stderr, "defnode did not create a node\n");
        goto fail;
    }
    r = aug_match(aug, "$x", NULL);
    if (r != 1) {
        fprintf(stderr, "$x must have exactly one entry, but has %d\n", r);
        goto fail;
    }

    r = aug_defnode(aug, "x", "/defnode/bar", NULL, &created);
    if (r != 1)
        die("aug_defnode failed");
    if (created != 0) {
        fprintf(stderr, "defnode created node again\n");
        goto fail;
    }

    // FIXME: get values and compare them, too

    r = aug_set(aug, "$x", "baz");
    if (r != 0)
        goto fail;

    r = aug_match(aug, "$x", NULL);
    if (r != 1)
        goto fail;

    printf("PASS\n");
    return 0;
 fail:
    printf("FAIL\n");
    return -1;
}

static int test_invalid_regexp(struct augeas *aug) {
    int r;

    printf("%-30s ... ", "invalid_regexp");
    r = aug_match(aug, "/files/*[ * =~ regexp('.*[aeiou')]", NULL);
    if (r >= 0)
        goto fail;

    printf("PASS\n");
    return 0;
 fail:
    printf("FAIL\n");
    return -1;
}

static int test_wrong_regexp_flag(struct augeas *aug) {
    int r;

    printf("%-30s ... ", "wrong_regexp_flag");
    r = aug_match(aug, "/files/*[ * =~ regexp('abc', 'o')]", NULL);
    if (r >= 0)
        goto fail;

    printf("PASS\n");
    return 0;
 fail:
    printf("FAIL\n");
    return -1;
}

static int test_trailing_ws_in_name(struct augeas *aug) {
    int r;

    printf("%-30s ... ", "trailing_ws_in_name");

    /* We used to incorrectly lop escaped whitespace off the end of a
     * name. Make sure that we really create a tree node with label 'x '
     * with the below set, and look for it in a number of ways to ensure we
     * are not lopping off trailing whitespace. */
    r = aug_set(aug, "/ws\\ ", "1");
    if (r < 0) {
        fprintf(stderr, "failed to set '/ws ': %d\n", r);
        goto fail;
    }
    /* We did not create a node with label 'ws' */
    r = aug_get(aug, "/ws", NULL);
    if (r != 0) {
        fprintf(stderr, "created '/ws' instead: %d\n", r);
        goto fail;
    }

    /* We did not create a node with label 'ws\t' (this also checks that we
     * don't create something like 'ws\\' by dropping the last whitespace
     * character. */
    r = aug_get(aug, "/ws\\\t", NULL);
    if (r != 0) {
        fprintf(stderr, "found '/ws\\t': %d\n", r);
        goto fail;
    }

    /* But we did create 'ws ' */
    r = aug_get(aug, "/ws\\ ", NULL);
    if (r != 1) {
        fprintf(stderr, "could not find '/ws ': %d\n", r);
        goto fail;
    }

    /* If the whitespace is preceded by an even number of '\\' chars,
     * whitespace must be stripped */
    r = aug_set(aug, "/nows\\\\ ", "1");
    if (r < 0) {
        fprintf(stderr, "set of '/nows' failed: %d\n", r);
        goto fail;
    }
    r = aug_get(aug, "/nows\\\\", NULL);
    if (r != 1) {
        fprintf(stderr, "could not get '/nows\\'\n");
        goto fail;
    }
    printf("PASS\n");
    return 0;
 fail:
    printf("FAIL\n");
    return -1;
}

static int run_tests(struct test *tests, int argc, char **argv) {
    char *lensdir;
    struct augeas *aug = NULL;
    int r, result = EXIT_SUCCESS;

    if (asprintf(&lensdir, "%s/lenses", abs_top_srcdir) < 0)
        die("asprintf lensdir failed");

    aug = aug_init(root, lensdir, AUG_NO_STDINC|AUG_SAVE_NEWFILE);
    if (aug == NULL)
        die("aug_init");
    r = aug_defvar(aug, "hosts", "/files/etc/hosts/*");
    if (r != 6)
        die("aug_defvar $hosts");
    r = aug_defvar(aug, "localhost", "'127.0.0.1'");
    if (r != 0)
        die("aug_defvar $localhost");
    r = aug_defvar(aug, "php", "/files/etc/php.ini");
    if (r != 1)
        die("aug_defvar $php");

    list_for_each(t, tests) {
        if (! should_run(t->name, argc, argv))
            continue;
        if (run_one_test(aug, t) < 0)
            result = EXIT_FAILURE;
    }

    if (argc == 0) {
        if (test_rm_var(aug) < 0)
            result = EXIT_FAILURE;

        if (test_defvar_nonexistent(aug) < 0)
            result = EXIT_FAILURE;

        if (test_defnode_nonexistent(aug) < 0)
            result = EXIT_FAILURE;

        if (test_invalid_regexp(aug) < 0)
            result = EXIT_FAILURE;

        if (test_wrong_regexp_flag(aug) < 0)
            result = EXIT_FAILURE;

        if (test_trailing_ws_in_name(aug) < 0)
            result = EXIT_FAILURE;
    }
    aug_close(aug);
    free(lensdir);

    return result;
}

int main(int argc, char **argv) {
    struct test *tests;

    abs_top_srcdir = getenv("abs_top_srcdir");
    if (abs_top_srcdir == NULL)
        die("env var abs_top_srcdir must be set");

    if (asprintf(&root, "%s/tests/root", abs_top_srcdir) < 0) {
        die("failed to set root");
    }

    tests = read_tests();
    int result = run_tests(tests, argc - 1, argv + 1);
    /*
    list_for_each(t, tests) {
        printf("Test %s\n", t->name);
        printf("match %s\n", t->match);
        list_for_each(e, t->entries) {
            if (e->value)
                printf("    %s = %s\n", e->path, e->value);
            else
                printf("    %s\n", e->path);
        }
    }
    */
    free_tests(tests);
    return result;
}

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