[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