[augeas-devel] [PATCH 2/2] Parsing of path expressions

David Lutterkort lutter at redhat.com
Mon Dec 22 23:21:12 UTC 2008


add predicates based on labels and values; add tests to check path
expression evaluation
---
 .gitignore         |    1 +
 src/internal.h     |   13 ++
 src/pathx.c        |  577 +++++++++++++++++++++++++++++++++++++---------------
 tests/Makefile.am  |    5 +-
 tests/test-xpath.c |  239 ++++++++++++++++++++++
 tests/xpath.tests  |   71 +++++++
 6 files changed, 740 insertions(+), 166 deletions(-)
 create mode 100644 tests/test-xpath.c
 create mode 100644 tests/xpath.tests

diff --git a/.gitignore b/.gitignore
index caabf4c..35466a9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,6 +34,7 @@ src/lexer.[ch]
 src/augtool
 src/augparse
 tests/fatest
+tests/test-xpath
 *.aux
 *.dvi
 *.log
diff --git a/src/internal.h b/src/internal.h
index 0094147..11868ea 100644
--- a/src/internal.h
+++ b/src/internal.h
@@ -334,6 +334,19 @@ int close_memstream(struct memstream *ms);
  * Path expressions
  */
 
+typedef enum {
+    PATHX_NOERROR = 0,
+    PATHX_ENAME,
+    PATHX_ESTRING,
+    PATHX_EDELIM,
+    PATHX_ENOEQUAL,
+    PATHX_ENOMEM,
+    PATHX_EPRED,
+    PATHX_ESLASH
+} pathx_errcode_t;
+
+const char *pathx_error(pathx_errcode_t errcode);
+
 struct pathx;
 
 int pathx_parse(const struct tree *root, const char *path, struct pathx **px);
diff --git a/src/pathx.c b/src/pathx.c
index 171f706..dd1dbe7 100644
--- a/src/pathx.c
+++ b/src/pathx.c
@@ -23,219 +23,467 @@
 #include <config.h>
 #include <internal.h>
 #include <memory.h>
+#include <ctype.h>
+
+static const char *const errcodes[] = {
+    "no error",
+    "empty name",
+    "illegal string literal",
+    "string missing ending ' or \"",
+    "expected '='",
+    "allocation failed",
+    "unmatched ']'",
+    "expected a '/'"
+};
 
-#define L_BRACK '['
-#define R_BRACK ']'
+/*
+ * Path expressions are strings that use a notation modelled on XPath, but
+ * support only a much smaller set of features.
+ *
+ * The grammar of Path expressions is
+ *
+ * PathExpr     ::= (("/" | "//")? StepExpr) (("/" | "//") StepExpr)*
+ * StepExpr     ::= (Name | "*") PredicateList
+ * PredicateList ::= "[" PredExpr "]" *
+ * PredExpr     ::= ValueTest | PositionTest
+ * ValueTest    ::= (Name | "value()") "=" ValueLiteral
+ * PositionTest ::= Number | "last()" ( "-" Number )
+ * Name         ::= /[^/\[ \t\n]+/
+ * ValueLiteral ::= '"' /[^"]* / '"' | "'" /[^']* / "'" | "nil"
+ * Number       ::= /[0-9]+/
+ */
+
+struct value {
+    char *name; /* NULL to compare to current node, otherwise name of a child */
+    char *value;
+};
 
-static const char *const last_func = "[last()]";
+/* A predicate for a node test. Predicates are kept in a linked list.
 
-/* Internal representation of a path in the tree */
-struct segment {
-    int          index;
-    unsigned int fixed : 1; /* Match only one index (either INDEX or LAST) */
-    unsigned int last  : 1; /* Match the last node with LABEL */
-    unsigned int any   : 1; /* Match any node with any label, implies !FIXED */
-    char        *label;
+   For value tests, we store what to compare to in a VALUE: if the NAME
+   is NULL, we should test against the node itself.
+
+   For position tests, POSITION >= 1 means check from the beginning of the
+   list of siblings. If POSITION is <= 0, check against sibling last() +
+   POSITION.
+*/
+enum pred_type {
+    P_VALUE = 1,
+    P_POSITION
+};
+
+struct pred {
+    struct pred      *next;
+    enum pred_type    type;
+    union {
+        struct value *value;     /* P_VALUE */
+        int              position;  /* P_POSITION */
+    };
+};
+
+struct step {
+    char           *name;           /* NULL to match any name */
+    struct pred *predicates;
 };
 
 struct pathx {
-    size_t      nsegments;
-    struct segment *segments;
-    struct tree    *root;
+    int          nsteps;
+    struct step *steps;
+    struct tree *root;
 };
 
-#define for_each_segment(seg, path)                                 \
-    for (typeof((path)->segments) (seg) = (path)->segments;         \
-         (seg) < (path)->segments + (path)->nsegments;              \
-         (seg)++)
+#define L_BRACK '['
+#define R_BRACK ']'
 
-#define last_segment(path) (path->segments + path->nsegments - 1)
+static const char * const nil_literal = "nil";
+static const char * const last_literal = "last()";
+static const char * const value_literal = "value()";
 
-static int xstrtoul(const char *nptr, char **endptr, int base) {
-    unsigned long val;
-    char *end;
-    int result;
+/*
+ * Free the various data structures
+ */
+static void free_value(struct value *value) {
+    if (value != NULL) {
+        free(value->name);
+        free(value->value);
+    }
+}
 
-    errno = 0;
-    val = strtoul(nptr, &end, base);
-    if (errno || (!endptr && *end) || end == nptr || (int) val != val) {
-        result = -1;
-    } else {
-        result = val;
+static void free_pred(struct pred *pred) {
+    if (pred != NULL) {
+        if (pred->type == P_VALUE) {
+            free_value(pred->value);
+        }
+        free(pred);
+    }
+}
+
+void free_pathx(struct pathx *pathx) {
+    if (pathx != NULL) {
+        for (int i=0; i < pathx->nsteps; i++) {
+            struct step *step = pathx->steps + i;
+            free(step->name);
+            while (step->predicates != NULL) {
+                struct pred *del = step->predicates;
+                step->predicates = del->next;
+                free_pred(del);
+            }
+        }
+        free(pathx->steps);
+        free(pathx);
     }
-    if (endptr)
-        *endptr = end;
-    return result;
 }
 
-static int sibling_index(struct tree *tree) {
-    int indx = 0;
+/*
+ * Parsing of path expressions
+ */
+
+// FIXME: These are copied from fa.c, should consolidate them
+static int more(const char **px) {
+    return (**px) != '\0';
+}
 
-    list_for_each(t, tree->parent->children) {
-        if (streqv(t->label, tree->label))
-            indx += 1;
-        if (t == tree)
-            return indx;
+static int match(const char **px, char m) {
+    if (!more(px))
+        return 0;
+    if (**px == m) {
+        (*px) += 1;
+        return 1;
     }
-    return indx;
+    return 0;
 }
 
-void free_pathx(struct pathx *path) {
-    if (path == NULL)
-        return;
+//static int peek(const char **px, const char *chars) {
+//    return strchr(chars, **px) != NULL;
+//}
 
-    if (path->segments != NULL) {
-        for (int i=0; i < path->nsegments; i++) {
-            free(path->segments[i].label);
-        }
-        free(path->segments);
+static void skipws(const char **px) {
+    while (isspace(**px)) *px += 1;
+}
+
+static int match_literal(const char **px, const char *literal) {
+    if (STREQLEN(*px, literal, strlen(literal))) {
+        *px += strlen(literal);
+        return 1;
     }
-    free(path);
+    return 0;
 }
 
-/* Take a path expression PATH and turn it into a path structure usable for
- * search. The TREE components of the PATH segments will be NULL. Call
- * PATH_FIRST to initialize them to the first match.
- *
- * A path expression follows the grammar
- * PATH = '/' ? SEGMENT ( '/' SEGMENT )* '/' ?
- * SEGMENT = STRING ('[' N ']') ? | '*'
- * where STRING is any string not containing '/' and N is a positive number
- */
-int pathx_parse(const struct tree *root, const char *path,
-                struct pathx **px) {
+static int parse_name(const char **px, char **name) {
+    const char *s = *px;
+
+    *name = NULL;
+    while (more(px) && **px != L_BRACK && **px != SEP && !isspace(**px))
+        (*px) += 1;
+    if (*px == s)
+        return PATHX_ENAME;
+    *name = strndup(s, *px - s);
+
+    return 0;
+}
+
+static int parse_number(const char **px, int *num) {
+    unsigned long val;
+    char *end;
 
-    if (ALLOC(*px) < 0)
+    errno = 0;
+    val = strtoul(*px, &end, 10);
+    if (errno || end == *px || (int) val != val)
         return -1;
-    (*px)->root = (struct tree *) root;
-
-    if (*path != SEP)
-        (*px)->nsegments = 1;
-    for (const char *p = path; *p != '\0'; p++) {
-        if (*p == SEP) {
-            while (*p == SEP) p++;
-            if (*p == '\0')
-                break;
-            (*px)->nsegments++;
+
+    *px = end;
+    *num = val;
+    return 0;
+}
+
+static int parse_value_literal(const char **px, char **value) {
+    char delim;
+    const char *s;
+
+    *value = NULL;
+
+    if (match_literal(px, nil_literal))
+        return 0;
+
+    if (match(px, '"'))
+        delim = '"';
+    else if (match(px, '\''))
+        delim = '\'';
+    else
+        return PATHX_ESTRING;
+
+    s = *px;
+    while (**px != '\0' && **px != delim) *px += 1;
+
+    if (! match(px, delim))
+        return PATHX_EDELIM;
+
+    *value = strndup(s, *px - s - 1);
+    return 0;
+}
+
+static int parse_value_test(const char **px, struct value **value) {
+    int ret = 0;
+    char *name = NULL;
+    char *val = NULL;
+
+    *value = NULL;
+
+    skipws(px);
+    if (! match_literal(px, value_literal)) {
+        ret = parse_name(px, &name);
+        if (ret != 0)
+            return ret;
+    }
+
+    skipws(px);
+    if (! match(px, '=')) {
+        ret = PATHX_ENOEQUAL;
+        return ret;
+    }
+
+    skipws(px);
+    ret = parse_value_literal(px, &val);
+    if (ret != 0)
+        return ret;
+
+    if (ALLOC(*value) < 0)
+        return PATHX_ENOMEM;
+
+    (*value)->name = name;
+    (*value)->value = val;
+    return 0;
+}
+
+static int parse_position_test(const char **px, int *pos) {
+    int ret = 0;
+
+    skipws(px);
+    if (match_literal(px, last_literal)) {
+        *pos = 0;
+        skipws(px);
+        if (match(px, '-')) {
+            skipws(px);
+            ret = parse_number(px, pos);
+            *pos = - *pos;
         }
+    } else {
+        ret = parse_number(px, pos);
     }
-    if ((*px)->nsegments == 0)
-        goto error;
+    return ret;
+}
 
-    CALLOC((*px)->segments, (*px)->nsegments);
-    if ((*px)->segments == NULL)
-        goto error;
+static int parse_predicate_list(const char **px, struct pred **pl) {
+    int ret = 0;
+    struct value *value = NULL;
+    int position = 0;
 
-    for_each_segment(seg, *px) {
-        const char *next, *brack;
+    *pl = NULL;
+    while (match(px, L_BRACK)) {
+        /* PredExpr, need to see if it's a ValueTest or a PositionTest */
+        enum pred_type type = P_POSITION;
 
-        while (*path == SEP)
-            path += 1;
-        assert(*path);
+        for (const char *s = *px; *s != '\0' && *s != R_BRACK; s++)
+            if (*s == '=') {
+                type = P_VALUE;
+                break;
+            }
+        if (type == P_VALUE) {
+            ret = parse_value_test(px, &value);
+        } else {
+            ret = parse_position_test(px, &position);
+        }
+        if (ret != 0)
+            goto error;
 
-        brack = strchr(path, L_BRACK);
-        next = strchr(path, SEP);
-        if (next == NULL)
-            next = path + strlen(path);
+        skipws(px);
+        if (! match(px, R_BRACK)) {
+            ret = PATHX_EPRED;
+            goto error;
+        }
 
-        if (path[0] == '*') {
-            seg->any = 1;
-        } else if (brack == NULL || brack >= next) {
-            seg->index = 0;
-            seg->label = strndup(path, next - path);
+        struct pred *pred = NULL;
+        if (ALLOC(pred) < 0) {
+            ret = PATHX_ENOMEM;
+            goto error;
+        }
+        pred->type = type;
+        if (value) {
+            pred->value = value;
         } else {
-            seg->label = strndup(path, brack - path);
-            if (STREQLEN(brack, last_func, strlen(last_func))) {
-                seg->last = 1;
-                seg->fixed = 1;
-            } else {
-                char *end;
-                seg->index = xstrtoul(brack + 1, &end, 10);
-                seg->fixed = 1;
-                if (seg->index <= 0)
-                    goto error;
-                if (*end != R_BRACK)
-                    goto error;
-            }
+            pred->position = position;
         }
-        path = next;
-        while (*path == SEP) path += 1;
+        list_append(*pl, pred);
     }
-    return 0;
 
+    return ret;
  error:
-    free_pathx(*px);
-    return -1;
+    return ret;
 }
 
-static void calc_last_index(struct segment *seg, struct tree *tree) {
-    if (seg->last) {
-        seg->index = 0;
-        list_for_each(t, tree->parent->children) {
-            if (streqv(t->label, seg->label))
-                seg->index += 1;
-        }
+static int parse_step_expr(const char **px, struct step *step) {
+    int ret = 0;
+    char *name = NULL;
+    struct pred *pl = NULL;
+
+    if (! match(px, '*')) {
+        ret = parse_name(px, &name);
+        if (ret != 0)
+            goto error;
     }
+    ret = parse_predicate_list(px, &pl);
+    if (ret != 0)
+        goto error;
+
+    step->name = name;
+    step->predicates = pl;
+    return ret;
+
+ error:
+    return ret;
 }
 
-/* Assumes that for SEG->LAST == 1, SEG->INDEX has been calculated
-   amongst the list of TREE's siblings */
-static int seg_match(struct segment *seg, struct tree *tree) {
-    if (seg->any || streqv(tree->label, seg->label)) {
-        int indx = sibling_index(tree);
-        if (!seg->fixed || indx == seg->index) {
-            seg->index = indx;
-            return 1;
+int pathx_parse(const struct tree *tree, const char *px_in,
+                struct pathx **pathx) {
+    int ret = 0;
+    const char **px = &px_in;
+
+    *pathx = NULL;
+    if (ALLOC(*pathx) == -1) {
+        ret = PATHX_ENOMEM;
+        goto error;
+    }
+
+    (*pathx)->root = (struct tree *) tree;
+
+    match(px, SEP);
+    do {
+        int desc = match(px, SEP);
+        if (desc) {
+            TODO;
+            ret = PATHX_ESLASH;
+            goto error;
+        }
+
+        (*pathx)->nsteps += 1;
+        if (REALLOC_N((*pathx)->steps, (*pathx)->nsteps) < 0) {
+            ret = PATHX_ENOMEM;
+            goto error;
+        } else {
+            MEMZERO((*pathx)->steps + (*pathx)->nsteps - 1, 1);
         }
+
+        ret = parse_step_expr(px, (*pathx)->steps + (*pathx)->nsteps - 1);
+        if (ret != 0)
+            goto error;
+    } while (match(px, SEP));
+    if (more(px)) {
+        ret = PATHX_ESLASH;
+        goto error;
     }
-    return 0;
+    return ret;
+
+ error:
+    free_pathx(*pathx);
+    *pathx = NULL;
+    return ret;
 }
 
-static int tree_next(struct tree **tree, int *depth, int mindepth) {
-    while ((*tree)->next == NULL && *depth >= mindepth) {
+/*
+ * Searching in the tree
+ */
+
+static int tree_next(struct tree **tree, int *depth) {
+    while ((*tree)->next == NULL && *depth >= 0) {
         *depth -= 1;
         *tree = (*tree)->parent;
     }
-    if (*depth < mindepth)
+    if (*depth < 0)
         return -1;
     *tree = (*tree)->next;
     return 0;
 }
 
-/* Given a node TREE that should match the segment SEGNR in PATH,
+static int sibling_index(struct tree *tree, int *count) {
+    int pos = 0;
+
+    *count = 0;
+    list_for_each(s, tree->parent->children) {
+        if (streqv(s->label, tree->label)) {
+            if (*count == 0)
+                pos += 1;
+            else
+                *count += 1;
+            if (s == tree)
+                *count = pos;
+        }
+    }
+    return pos;
+}
+
+static int pred_matches(struct pred *pred, struct tree *tree) {
+    if (pred->type == P_VALUE) {
+        struct tree *t = tree;
+        if (pred->value->name != NULL) {
+            for (t = tree->children;
+                 t != NULL && ! streqv(pred->value->name, t->label);
+                 t = t->next);
+            if (t == NULL)
+                return 0;
+        }
+        return streqv(pred->value->value, t->value);
+    } else if (pred->type == P_POSITION) {
+        int pos, count;
+        pos = sibling_index(tree, &count);
+        return (pos == pred->position || pos == count + pred->position);
+    }
+    assert(0);
+}
+
+static int step_matches(struct step *step, struct tree *tree) {
+    if (step->name != NULL && ! streqv(tree->label, step->name))
+        return 0;
+
+    list_for_each(p, step->predicates) {
+        if (! pred_matches(p, tree))
+            return 0;
+    }
+
+    return 1;
+}
+
+/* Given a node TREE that should match the segment STEPNR in PATH,
    find the next node that matches all of PATH, or return NULL */
-static struct tree *complete_path(struct pathx *path, int segnr,
+static struct tree *complete_path(struct pathx *path, int stepnr,
                                   struct tree *tree) {
-    int cur = segnr;
+    int cur = stepnr;
 
-    if (segnr >= path->nsegments || tree == NULL)
+    if (stepnr >= path->nsteps || tree == NULL)
         return NULL;
 
-    calc_last_index(path->segments + cur, tree->parent->children);
     while (1) {
-        int found = seg_match(path->segments + cur, tree);
+        int found = step_matches(path->steps + cur, tree);
 
-        if (found && cur == path->nsegments - 1) {
+        if (found && cur == path->nsteps - 1) {
             return tree;
-        } else if (found && cur + 1 < path->nsegments &&
+        } else if (found && cur + 1 < path->nsteps &&
                    tree->children != NULL) {
             cur += 1;
             tree = tree->children;
-            calc_last_index(path->segments + cur, tree);
         } else {
-            if (tree_next(&tree, &cur, segnr) < 0)
+            if (tree_next(&tree, &cur) < 0)
                 return NULL;
         }
     }
 }
 
 struct tree *pathx_next(struct pathx *path, struct tree *cur) {
-    int segnr = path->nsegments - 1;
+    int stepnr = path->nsteps - 1;
 
-    if (tree_next(&cur, &segnr, -1) < 0)
+    if (tree_next(&cur, &stepnr) < 0)
         return NULL;
 
-    return complete_path(path, segnr, cur);
+    return complete_path(path, stepnr, cur);
 }
 
 /* Find the first node in TREE matching PATH. */
@@ -248,37 +496,36 @@ struct tree *pathx_first(struct pathx *path) {
  * Return 1 if a node was found that exactly matches PATH, 0 if an incomplete
  * prefix matches, and -1 if more than one node in the tree match.
  *
- * MATCH is set to the tree node that matches, and SEGNR to the
+ * MATCH is set to the tree node that matches, and STEPNR to the
  * number of the segment in PATH where MATCH matched. If no node matches,
- * MATCH will be NULL, and SEGNR -1
+ * MATCH will be NULL, and STEPNR -1
  */
-static int path_search(struct pathx *path, struct tree **match, int *segnr) {
+static int path_search(struct pathx *path, struct tree **tmatch, int *stepnr) {
     struct tree **matches = NULL;
     int *nmatches = NULL;
     int result;
 
-    if (ALLOC_N(matches, path->nsegments) < 0)
+    if (ALLOC_N(matches, path->nsteps) < 0)
         return -1;
 
-    if (ALLOC_N(nmatches, path->nsegments) < 0) {
+    if (ALLOC_N(nmatches, path->nsteps) < 0) {
         free(matches);
         return -1;
     }
 
-    if (match)
-        *match = NULL;
-    if (segnr)
-        *segnr = -1;
+    if (tmatch)
+        *tmatch = NULL;
+    if (stepnr)
+        *stepnr = -1;
 
     if (path->root == NULL)
         return -1;
 
     struct tree *tree = path->root;
     int cur = 0;
-    calc_last_index(path->segments, tree);
 
     while (1) {
-        int found = seg_match(path->segments + cur, tree);
+        int found = step_matches(path->steps + cur, tree);
 
         if (found) {
             if (nmatches[cur] == 0)
@@ -286,27 +533,26 @@ static int path_search(struct pathx *path, struct tree **match, int *segnr) {
             nmatches[cur]++;
         }
 
-        if (found && cur + 1 < path->nsegments && tree->children != NULL) {
+        if (found && cur + 1 < path->nsteps && tree->children != NULL) {
             cur += 1;
             tree = tree->children;
-            calc_last_index(path->segments + cur, tree);
         } else {
-            if (tree_next(&tree, &cur, 0) < 0)
+            if (tree_next(&tree, &cur) < 0)
                 break;
         }
     }
 
-    result = nmatches[path->nsegments - 1] == 1;
-    for (cur = path->nsegments-1; cur >=0; cur--) {
+    result = nmatches[path->nsteps - 1] == 1;
+    for (cur = path->nsteps-1; cur >=0; cur--) {
         if (nmatches[cur] > 1) {
             result = -1;
             break;
         }
         if (nmatches[cur] == 1) {
-            if (match)
-                *match = matches[cur];
-            if (segnr)
-                *segnr = cur;
+            if (tmatch)
+                *tmatch = matches[cur];
+            if (stepnr)
+                *stepnr = cur;
             break;
         }
     }
@@ -323,25 +569,25 @@ static int path_search(struct pathx *path, struct tree **match, int *segnr) {
  * error.
  */
 int pathx_expand_tree(struct pathx *path, struct tree **tree) {
-    int r, segnr;
+    int r, stepnr;
 
     *tree = path->root;
-    r = path_search(path, tree, &segnr);
+    r = path_search(path, tree, &stepnr);
     if (r == -1)
         return -1;
 
-    if (segnr == path->nsegments - 1)
+    if (stepnr == path->nsteps - 1)
         return 0;
 
     struct tree *first_child = NULL;
     struct tree *parent = *tree;
-    struct segment *seg = path->segments + segnr + 1;
+    struct step *step = path->steps + stepnr + 1;
 
     if (parent == NULL)
         parent = path->root->parent;
 
-    for (struct segment *s = seg ; s <= last_segment(path); s++) {
-        struct tree *t = make_tree(strdup(s->label), NULL, parent, NULL);
+    for (struct step *s = step; s < path->steps + path->nsteps; s++) {
+        struct tree *t = make_tree(strdup(s->name), NULL, parent, NULL);
         if (first_child == NULL)
             first_child = t;
         if (t == NULL || t->label == NULL)
@@ -374,6 +620,7 @@ int pathx_find_one(struct pathx *path, struct tree **tree) {
     }
     return 1;
 }
+
 /*
  * Local variables:
  *  indent-tabs-mode: nil
diff --git a/tests/Makefile.am b/tests/Makefile.am
index b23b05c..90350b0 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -19,7 +19,7 @@ EXTRA_DIST=augtest $(AUGTESTS) root \
 
 noinst_SCRIPTS = $(check_SCRIPTS)
 
-check_PROGRAMS = fatest
+check_PROGRAMS = fatest test-xpath
 
 TESTS_ENVIRONMENT = \
   PATH='$(abs_top_builddir)/src$(PATH_SEPARATOR)'"$$PATH" \
@@ -32,3 +32,6 @@ INCLUDES = -I$(top_srcdir)/src
 
 fatest_SOURCES = fatest.c cutest.c cutest.h
 fatest_LDADD = $(top_builddir)/src/libaugeas.la $(GNULIB)
+
+test_xpath_SOURCES = test-xpath.c
+test_xpath_LDADD = $(top_builddir)/src/libaugeas.la $(GNULIB)
diff --git a/tests/test-xpath.c b/tests/test-xpath.c
new file mode 100644
index 0000000..0293a31
--- /dev/null
+++ b/tests/test-xpath.c
@@ -0,0 +1,239 @@
+/*
+ * test-xpath.c: check that XPath expressions yield the expected result
+ *
+ * Copyright (C) 2007 Red Hat Inc.
+ *
+ * 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 at redhat.com>
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include <augeas.h>
+#include <internal.h>
+#include <memory.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 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 = token(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");
+        }
+    }
+    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 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;
+        int i;
+        const char *val;
+
+        for (e = t->entries, i = 0; e != NULL; e = e->next, i++) {
+            if (!STREQ(e->path, matches[i]))
+                result = -1;
+            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);
+        }
+    }
+    return result;
+}
+
+static int run_tests(struct test *tests) {
+    char *lensdir;
+    struct augeas *aug = NULL;
+    int 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");
+    list_for_each(t, tests) {
+        if (run_one_test(aug, t) < 0)
+            result = EXIT_FAILURE;
+    }
+    aug_close(aug);
+
+    return result;
+}
+
+int main(void) {
+    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();
+    return run_tests(tests);
+    /*
+    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);
+        }
+    }
+    */
+}
+
+/*
+ * Local variables:
+ *  indent-tabs-mode: nil
+ *  c-indent-level: 4
+ *  c-basic-offset: 4
+ *  tab-width: 4
+ * End:
+ */
diff --git a/tests/xpath.tests b/tests/xpath.tests
new file mode 100644
index 0000000..8fddb7a
--- /dev/null
+++ b/tests/xpath.tests
@@ -0,0 +1,71 @@
+# Tests of the XPath matching
+
+# Blank lines and lines starting with '#' are ignored
+#
+# Each test consists of one line declaring the test followed by a complete
+# list of the expected result of the match.
+#
+# The test is declared with a line 'test NAME MATCH'. A result is either
+# just a path (meaning that the value associated with that node must be
+# NULL) or of the form PATH = VALUE, meaning that the value for the node at
+# PATH must be VALUE
+#
+# The MATCH XPath expression is matched against a fixed tree (the one from the
+# root/ subdirectory) and the result of the aug_match is compared with the
+# results listed in the test
+
+# Very simple to warm up
+test wildcard /files/etc/hosts/*/ipaddr
+     /files/etc/hosts/1/ipaddr = 127.0.0.1
+     /files/etc/hosts/2/ipaddr = 172.31.122.14
+
+# Compare the value of the current node with a constant
+test self-value /files/etc/hosts/*/ipaddr[ value() = '127.0.0.1' ]
+     /files/etc/hosts/1/ipaddr = 127.0.0.1
+
+# Find nodes that have a child named 'ipaddr' with a fixed value
+test child-value /files/etc/hosts/*[ipaddr = '127.0.0.1']
+     /files/etc/hosts/1
+
+# Find nodes that have a child 'ipaddr' that has no value
+test child-nil-value /files/etc/hosts/*[ipaddr = nil]
+
+# Find nodes that have no value
+test self-nil-value /files/etc/hosts/*[value() = nil]
+     /files/etc/hosts/1
+     /files/etc/hosts/2
+
+# Match over two levels of the tree
+test two-wildcards /files/etc/*/*[ipaddr = '127.0.0.1']
+     /files/etc/hosts/1
+
+test pam-system-auth /files/etc/pam.d/*/*[module = 'system-auth']
+     /files/etc/pam.d/login/2
+     /files/etc/pam.d/login/4
+     /files/etc/pam.d/login/5
+     /files/etc/pam.d/login/8
+     /files/etc/pam.d/postgresql/1
+     /files/etc/pam.d/postgresql/2
+     /files/etc/pam.d/newrole/1
+     /files/etc/pam.d/newrole/2
+     /files/etc/pam.d/newrole/3
+
+# Multiple predicates are treated with 'and'
+test pam-two-preds /files/etc/pam.d/*/*[module = 'system-auth'][type = 'account']
+     /files/etc/pam.d/login/4
+     /files/etc/pam.d/postgresql/2
+     /files/etc/pam.d/newrole/2
+
+# Find nodes that have siblings with a given value
+test pam-two-preds-control /files/etc/pam.d/*/*[module = 'system-auth'][type = 'account']/control
+     /files/etc/pam.d/login/4/control = include
+     /files/etc/pam.d/postgresql/2/control = include
+     /files/etc/pam.d/newrole/2/control = include
+
+# last() gives the last node with a certain name
+test last /files/etc/hosts/*[ipaddr = "127.0.0.1"]/alias[last()]
+     /files/etc/hosts/1/alias[3] = galia
+
+# We can get nodes counting from the right with 'last()-N'
+test last-minus-one /files/etc/hosts/*[ipaddr = "127.0.0.1"]/alias[ last() - 1 ]
+     /files/etc/hosts/1/alias[2] = galia.watzmann.net
-- 
1.6.0.4




More information about the augeas-devel mailing list