| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| #include "parse_tree.h" |
| #include "test_case.h" |
| |
| #include <stdio.h> |
| #include <string.h> |
| |
| static char *test_add_remove(void *context) |
| { |
| qd_iterator_t *piter = qd_iterator_string("I.am.Sam", ITER_VIEW_ALL); |
| qd_iterator_t *piter2 = qd_iterator_string("Sam.I.Am", ITER_VIEW_ALL); |
| qd_parse_tree_t *node = qd_parse_tree_new(QD_PARSE_TREE_ADDRESS); |
| void *payload; |
| |
| if (qd_parse_tree_remove_pattern(node, piter)) { |
| qd_parse_tree_free(node); |
| qd_iterator_free(piter); |
| qd_iterator_free(piter2); |
| return "Failed to remove a non-existing pattern"; |
| } |
| |
| if (qd_parse_tree_get_pattern(node, piter, &payload)) { |
| qd_parse_tree_free(node); |
| qd_iterator_free(piter); |
| qd_iterator_free(piter2); |
| return "Got a non-existing pattern"; |
| } |
| |
| if (qd_parse_tree_add_pattern(node, piter, "Hi Sam")) { |
| qd_parse_tree_free(node); |
| qd_iterator_free(piter); |
| qd_iterator_free(piter2); |
| return "Add returned existing value"; |
| } |
| |
| if (qd_parse_tree_add_pattern(node, piter2, "Bye Sam")) { |
| qd_parse_tree_free(node); |
| qd_iterator_free(piter); |
| qd_iterator_free(piter2); |
| return "Add returned existing value"; |
| } |
| |
| if (!qd_parse_tree_get_pattern(node, piter, &payload)) { |
| qd_parse_tree_free(node); |
| qd_iterator_free(piter); |
| qd_iterator_free(piter2); |
| return "Could not get pattern"; |
| } |
| |
| if (!payload || strcmp("Hi Sam", (char *)payload)) { |
| qd_parse_tree_free(node); |
| qd_iterator_free(piter); |
| qd_iterator_free(piter2); |
| return "Got bad pattern"; |
| } |
| |
| if (!qd_parse_tree_get_pattern(node, piter2, &payload)) { |
| qd_parse_tree_free(node); |
| qd_iterator_free(piter); |
| qd_iterator_free(piter2); |
| return "Could not get pattern"; |
| } |
| |
| if (!payload || strcmp("Bye Sam", (char *)payload)) { |
| qd_parse_tree_free(node); |
| qd_iterator_free(piter); |
| qd_iterator_free(piter2); |
| return "Got bad pattern"; |
| } |
| |
| if (!qd_parse_tree_remove_pattern(node, piter)) { |
| qd_parse_tree_free(node); |
| qd_iterator_free(piter); |
| qd_iterator_free(piter2); |
| return "Failed to remove an existing pattern"; |
| } |
| |
| if (!qd_parse_tree_remove_pattern(node, piter2)) { |
| qd_parse_tree_free(node); |
| qd_iterator_free(piter); |
| qd_iterator_free(piter2); |
| return "Failed to remove an existing pattern"; |
| } |
| |
| qd_parse_tree_free(node); |
| qd_iterator_free(piter); |
| qd_iterator_free(piter2); |
| return NULL; |
| } |
| |
| static char *test_add_and_match_str(void *context) |
| { |
| const char *str1 = "I.am.Sam"; |
| const char *str2 = "Sam.I.Am"; |
| qd_parse_tree_t *node = qd_parse_tree_new(QD_PARSE_TREE_ADDRESS); |
| void *payload; |
| |
| if (qd_parse_tree_add_pattern_str(node, str1, "Hi Sam")) { |
| qd_parse_tree_free(node); |
| return "Add returned existing value (1)"; |
| } |
| |
| if (qd_parse_tree_add_pattern_str(node, str2, "Bye Sam")) { |
| qd_parse_tree_free(node); |
| return "Add returned existing value (2)"; |
| } |
| |
| if (!qd_parse_tree_retrieve_match_str(node, str1, &payload)) { |
| qd_parse_tree_free(node); |
| return "Failed to get expected match (1)"; |
| } |
| |
| if (!qd_parse_tree_retrieve_match_str(node, str2, &payload)) { |
| qd_parse_tree_free(node); |
| return "Failed to get expected match (2)"; |
| } |
| |
| if (qd_parse_tree_retrieve_match_str(node, "I", &payload) || |
| qd_parse_tree_retrieve_match_str(node, "I.am", &payload)) { |
| qd_parse_tree_free(node); |
| return "Should not match part of a pattern"; |
| } |
| |
| if (qd_parse_tree_retrieve_match_str(node, "notSoFast", &payload)) { |
| qd_parse_tree_free(node); |
| return "Match pattern should not match but did match"; |
| } |
| |
| if (!qd_parse_tree_remove_pattern_str(node, str1)) { |
| qd_parse_tree_free(node); |
| return "Failed to remove an existing pattern str"; |
| } |
| |
| if (qd_parse_tree_retrieve_match_str(node, str1, &payload)) { |
| qd_parse_tree_free(node); |
| return "Removed pattern should not match but did match"; |
| } |
| |
| qd_parse_tree_free(node); |
| return NULL; |
| } |
| |
| static char *test_duplicate_error_str(void *context) |
| { |
| // While A and B are different strings they are semantically the same |
| // pattern and should trigger duplicate detection in the parse tree |
| const char *A = "#"; |
| const char *B = "#.#.#.#"; |
| qd_parse_tree_t *node = qd_parse_tree_new(QD_PARSE_TREE_ADDRESS); |
| void *payload; |
| qd_error_t rc; |
| |
| rc = qd_parse_tree_add_pattern_str(node, A, (void *)A); |
| if (rc) { |
| qd_parse_tree_free(node); |
| return (char *)qd_error_name(rc); |
| } |
| |
| // matches on A or B both return A |
| if (!qd_parse_tree_retrieve_match_str(node, A, &payload)) { |
| qd_parse_tree_free(node); |
| return "Could not get pattern"; |
| } |
| |
| if (!payload || strcmp(A, (char *)payload)) { |
| qd_parse_tree_free(node); |
| return "Got bad pattern"; |
| } |
| |
| if (!qd_parse_tree_retrieve_match_str(node, B, &payload)) { |
| qd_parse_tree_free(node); |
| return "Could not get pattern"; |
| } |
| |
| if (!payload || strcmp(A, (char *)payload)) { |
| qd_parse_tree_free(node); |
| return "Got bad pattern"; |
| } |
| |
| // attempt to add B pattern, expect duplication error |
| |
| rc = qd_parse_tree_add_pattern_str(node, B, (void *)B); |
| if (!rc) { |
| qd_parse_tree_free(node); |
| return "Duplicate pattern NOT detected"; |
| } |
| |
| // matches on A or B both return B |
| if (!qd_parse_tree_retrieve_match_str(node, A, &payload)) { |
| qd_parse_tree_free(node); |
| return "Could not get pattern"; |
| } |
| |
| if (!payload || strcmp(A, (char *)payload)) { |
| qd_parse_tree_free(node); |
| return "Got bad pattern"; |
| } |
| |
| if (!qd_parse_tree_retrieve_match_str(node, B, &payload)) { |
| qd_parse_tree_free(node); |
| return "Could not get pattern"; |
| } |
| |
| if (!payload || strcmp(A, (char *)payload)) { |
| qd_parse_tree_free(node); |
| return "Got bad pattern"; |
| } |
| |
| // now replace A with B correctly |
| |
| payload = qd_parse_tree_remove_pattern_str(node, A); |
| if (!payload || strcmp(A, (char *)payload)) { |
| qd_parse_tree_free(node); |
| return "remove pattern failed"; |
| } |
| |
| rc = qd_parse_tree_add_pattern_str(node, B, (void *)B); |
| if (rc) { |
| qd_parse_tree_free(node); |
| return "Replace add failed"; |
| } |
| |
| // matches on A or B both return B |
| if (!qd_parse_tree_retrieve_match_str(node, A, &payload)) { |
| qd_parse_tree_free(node); |
| return "Could not get pattern"; |
| } |
| |
| if (!payload || strcmp(B, (char *)payload)) { |
| qd_parse_tree_free(node); |
| return "Got bad pattern"; |
| } |
| |
| if (!qd_parse_tree_retrieve_match_str(node, B, &payload)) { |
| qd_parse_tree_free(node); |
| return "Could not get pattern"; |
| } |
| |
| if (!payload || strcmp(B, (char *)payload)) { |
| qd_parse_tree_free(node); |
| return "Got bad pattern"; |
| } |
| |
| qd_parse_tree_free(node); |
| return NULL; |
| } |
| |
| // for pattern match callback |
| typedef struct { |
| int count; |
| const char **patterns; |
| void **payloads; |
| } visit_handle_t; |
| |
| |
| // callback to visit all matching patterns in tree |
| static bool visit_all(void *handle, |
| const char *pattern, |
| void *payload) |
| { |
| visit_handle_t *h = (visit_handle_t *)handle; |
| h->patterns[h->count] = pattern; |
| h->payloads[h->count] = payload; |
| h->count++; |
| return true; |
| } |
| |
| |
| // callback to return first (best) match |
| static bool find_best(void *handle, |
| const char *pattern, |
| void *payload) |
| { |
| visit_handle_t *h = (visit_handle_t *)handle; |
| h->patterns[0] = pattern; |
| h->payloads[0] = payload; |
| h->count = 1; |
| return false; |
| } |
| |
| |
| // check if input patterns are correctly "normalized" (see parse_tree.c) |
| static char *check_normalize(const char *input, |
| const char *expected) |
| { |
| const char *patterns[1]; |
| void *payloads[1]; |
| visit_handle_t vh = {0, patterns, payloads}; |
| qd_parse_tree_t *node = qd_parse_tree_new(QD_PARSE_TREE_ADDRESS); |
| qd_iterator_t *iter = qd_iterator_string(input, ITER_VIEW_ALL); |
| void *payload; |
| |
| if (qd_parse_tree_add_pattern(node, iter, (void *)input)) { |
| qd_parse_tree_free(node); |
| qd_iterator_free(iter); |
| return "Unexpected duplicate pattern"; |
| } |
| if (!qd_parse_tree_get_pattern(node, iter, &payload)) { |
| qd_parse_tree_free(node); |
| qd_iterator_free(iter); |
| return "Could not find added pattern"; |
| } |
| if (!payload || strcmp((const char *)payload, input)) { |
| qd_parse_tree_free(node); |
| qd_iterator_free(iter); |
| return "Failed to find pattern"; |
| } |
| |
| qd_parse_tree_walk(node, visit_all, &vh); |
| if (vh.count != 1) { |
| qd_parse_tree_free(node); |
| qd_iterator_free(iter); |
| return "Did not find expected pattern"; |
| } |
| if (strcmp(vh.payloads[0], input)) { |
| qd_parse_tree_free(node); |
| qd_iterator_free(iter); |
| return "Unexpected payload!"; |
| } |
| if (strcmp(vh.patterns[0], expected)) { |
| fprintf(stderr, "%s %s\n", vh.patterns[0], expected); |
| qd_parse_tree_free(node); |
| qd_iterator_free(iter); |
| return "Incorrect normalization"; |
| } |
| |
| payload = qd_parse_tree_remove_pattern(node, iter); |
| if (!payload || strcmp((const char *)payload, input)) { |
| qd_parse_tree_free(node); |
| qd_iterator_free(iter); |
| return "Failed to remove pattern"; |
| } |
| |
| qd_parse_tree_free(node); |
| qd_iterator_free(iter); |
| return NULL; |
| } |
| |
| |
| static char *test_normalization(void *context) |
| { |
| char *rc = NULL; |
| char *patterns[][2] = { |
| // normalized raw |
| {"a.b.c", "a.b.c"}, |
| {"a.*.c", "a.*.c"}, |
| {"#", "#"}, |
| {"#", "#.#.#.#"}, |
| {"*.*.*.#", "#.*.#.*.#.#.*"}, |
| {"a.*.*.*.#", "a.*.#.*.#.*.#"}, |
| {"a.*.*.*.#", "a.*.#.*.#.*"}, |
| {"*.*.*.#", "*.#.#.*.*.#"}, |
| {NULL, NULL} |
| }; |
| |
| for (int i = 0; !rc && patterns[i][0]; i++) |
| rc = check_normalize(patterns[i][1], patterns[i][0]); |
| |
| return rc; |
| } |
| |
| |
| typedef struct { |
| const char *address; |
| bool match; |
| } match_test_t; |
| |
| static char *match_test(qd_parse_tree_type_t type, |
| const char *pattern, |
| const match_test_t *tests) |
| { |
| char *rc = NULL; |
| qd_iterator_t *piter = qd_iterator_string(pattern, ITER_VIEW_ALL); |
| qd_parse_tree_t *node = qd_parse_tree_new(type); |
| void *payload = (void *)"found"; |
| |
| if (qd_parse_tree_add_pattern(node, piter, payload)) { |
| qd_parse_tree_free(node); |
| qd_iterator_free(piter); |
| return "Unexpected error when adding pattern"; |
| } |
| |
| for (int i = 0; tests[i].address && !rc; i++) { |
| qd_iterator_t *iter = qd_iterator_string(tests[i].address, ITER_VIEW_ALL); |
| bool match = (int)qd_parse_tree_retrieve_match(node, iter, &payload); |
| if (match != tests[i].match) { |
| printf("match address '%s' to pattern '%s': expected %d got %d\n", |
| tests[i].address, pattern, (int)tests[i].match, (int)match); |
| qd_iterator_free(iter); |
| qd_parse_tree_free(node); |
| qd_iterator_free(piter); |
| return "Match test failed"; |
| } |
| qd_iterator_free(iter); |
| } |
| |
| qd_parse_tree_free(node); |
| qd_iterator_free(piter); |
| return NULL; |
| } |
| |
| |
| // check various pattern matches |
| static char *test_matches(void *context) |
| { |
| match_test_t test1[] = { |
| { "ab.cd.e", true}, |
| { "abx.cd.e", false}, |
| { "ab.cd", false}, |
| { "ab.cd.ef.", false}, |
| { "ab.cd.E", false}, |
| { "x.ab.cd.e", false}, |
| {NULL, false} |
| }; |
| |
| char *rc = match_test(QD_PARSE_TREE_ADDRESS, "ab.cd.e", test1); |
| if (rc) return rc; |
| |
| match_test_t test4[] = { |
| {"a.xx.b", true}, |
| {"a.b", false}, |
| {NULL, false} |
| }; |
| rc = match_test(QD_PARSE_TREE_ADDRESS, "a.*.b", test4); |
| if (rc) return rc; |
| |
| match_test_t test5[] = { |
| {"y.x", true}, |
| {"x", false}, |
| {"x.y", false}, |
| {NULL, false} |
| }; |
| rc = match_test(QD_PARSE_TREE_ADDRESS, "*.x", test5); |
| if (rc) return rc; |
| |
| match_test_t test6[] = { |
| {"x.x.y", true}, |
| {"x.x", false}, |
| {"y.x.x", false}, |
| {"q.x.y", false}, |
| {NULL, false} |
| }; |
| rc = match_test(QD_PARSE_TREE_ADDRESS, "x.x.*", test6); |
| if (rc) return rc; |
| |
| |
| match_test_t test7[] = { |
| {"a.b", true}, |
| {"a.x.b", true}, |
| {"a..x.y.zz.b", true}, |
| {"a.b.z", false}, |
| {"z.a.b", false}, |
| {"q.x.b", false}, |
| {NULL, false} |
| }; |
| rc = match_test(QD_PARSE_TREE_ADDRESS, "a.#.b", test7); |
| if (rc) return rc; |
| |
| match_test_t test8[] = { |
| {"a", true}, |
| {"a.b", true}, |
| {"a.b.c", true}, |
| {"b.a", false}, |
| {NULL, false} |
| }; |
| rc = match_test(QD_PARSE_TREE_ADDRESS, "a.#", test8); |
| if (rc) return rc; |
| |
| match_test_t test9[] = { |
| {"a", true}, |
| {"x.y.a", true}, |
| {"a.b", false}, |
| {NULL, false} |
| }; |
| rc = match_test(QD_PARSE_TREE_ADDRESS, "#.a", test9); |
| if (rc) return rc; |
| |
| match_test_t test10[] = { |
| {"a.b.c", true}, |
| {"a.x.b.y.c", true}, |
| {"a.x.x.b.y.y.c", true}, |
| {"a.b", false}, |
| {NULL, false} |
| }; |
| rc = match_test(QD_PARSE_TREE_ADDRESS, "a.#.b.#.c", test10); |
| if (rc) return rc; |
| |
| match_test_t test11[] = { |
| {"a.x.y", true}, |
| {"a.x.p.qq.y", true}, |
| {"a.a.x.y", false}, |
| {"aa.x.b.c", false}, |
| {"x.p.qq.y", false}, |
| {"a.x.p.qq.y.b", false}, |
| {NULL, false} |
| }; |
| rc = match_test(QD_PARSE_TREE_ADDRESS, "*.x.#.y", test11); |
| if (rc) return rc; |
| |
| match_test_t test12[] = { |
| {"a.b.x", true}, |
| {"a.x.x.x.b.x", true}, |
| {"a.b.b.b.b.y", true}, |
| {"a.b.b.b.b", true}, |
| {"a.b", false}, |
| {NULL, false} |
| }; |
| rc = match_test(QD_PARSE_TREE_ADDRESS, "a.#.b.*", test12); |
| if (rc) return rc; |
| |
| match_test_t test13[] = { |
| {"x/y/z", true}, |
| {"x.y.z/a.b.c", true}, |
| {"x.y/z", true}, |
| {"x.y", false}, |
| {"x/y", false}, |
| {"x", false}, |
| {NULL, false} |
| }; |
| rc = match_test(QD_PARSE_TREE_ADDRESS, "*.*.*.#", test13); |
| if (rc) return rc; |
| |
| match_test_t test14[] = { |
| {"x", false}, |
| {"x.y", true}, |
| {"x.y.z", true}, |
| {"x.y.z.y.z.y.z", true}, |
| {NULL, false} |
| }; |
| rc = match_test(QD_PARSE_TREE_ADDRESS, "*/#/*", test14); |
| |
| match_test_t test15[] = { |
| {"/policy", true}, |
| {"/good/policy", true}, |
| {"/really/really/good/policy", true}, |
| {"help/police", false}, |
| {"bad/polic", false}, |
| {"/bad/p", false}, |
| {NULL, false} |
| }; |
| rc = match_test(QD_PARSE_TREE_ADDRESS, "/#/policy", test15); |
| |
| match_test_t test16[] = { |
| {"some/policy", false}, |
| {"some.policy", true}, |
| {"policy", false}, |
| {"policy.ycilop", false}, |
| {"/hi/there/.policy", true}, |
| {"hi.there.policy", true}, |
| {"hi.there/policy", false}, |
| {NULL, false} |
| }; |
| rc = match_test(QD_PARSE_TREE_AMQP_0_10, ".#.*.policy", test16); |
| |
| match_test_t test17[] = { |
| {"some/policy", false}, |
| {"some.policy", false}, |
| {"policy", false}, |
| {".#./policy", false}, |
| {"hi/.#./policy", true}, |
| {"/hi/.there./policy", false}, |
| {NULL, false} |
| }; |
| rc = match_test(QD_PARSE_TREE_MQTT, "+/.#./policy", test17); |
| |
| match_test_t test18[] = { |
| {"test/?/*", true}, |
| {"test/?/*/one/two/three", true}, |
| {"test/something.or.other/*", true}, |
| {"test/something.or.other/xxx", false}, |
| {"test/something.or.other", false}, |
| {"test", false}, |
| {NULL, false} |
| }; |
| rc = match_test(QD_PARSE_TREE_MQTT, "test/+/*/#", test18); |
| return rc; |
| } |
| |
| // For debug (see parse_tree.c) |
| // void qd_parse_tree_dump(qd_parse_node_t *node, int depth); |
| |
| |
| #define PCOUNT 17 |
| typedef struct { |
| const char *address; |
| int count; |
| const char *matches[PCOUNT]; |
| } multi_match_t; |
| |
| static char *multiple_matches(qd_parse_tree_type_t type, |
| const char *patterns[], |
| multi_match_t *tests) |
| { |
| const char *_patterns[PCOUNT] = {NULL}; |
| void *_payloads[PCOUNT] = {NULL}; |
| visit_handle_t vh = {0, _patterns, _payloads}; |
| qd_parse_tree_t *node = qd_parse_tree_new(type); |
| |
| // build the tree |
| for (int i = 0; i < PCOUNT; i++) { |
| qd_iterator_t *pattern = qd_iterator_string(patterns[i], ITER_VIEW_ALL); |
| if (qd_parse_tree_add_pattern(node, pattern, (void *)patterns[i])) { |
| printf("Failed to add pattern %s to parse tree\n", patterns[i]); |
| qd_iterator_free(pattern); |
| qd_parse_tree_free(node); |
| return "failed adding pattern to tree"; |
| } |
| qd_iterator_free(pattern); |
| } |
| |
| { |
| // read all patterns and verify all are present |
| qd_parse_tree_walk(node, visit_all, &vh); |
| if (vh.count != PCOUNT) { |
| qd_parse_tree_free(node); |
| return "Not all patterns in tree"; |
| } |
| for (int i = 0; i < PCOUNT; i++) { |
| bool found = false; |
| for (int j = 0; j < PCOUNT; j++) { |
| if (strcmp(patterns[i], vh.patterns[j]) == 0) |
| found = true; |
| } |
| if (!found) { |
| qd_parse_tree_free(node); |
| return "All patterns not visited"; |
| } |
| } |
| } |
| |
| // verify all matching patterns are hit and in the correct order |
| for (int k = 0; tests[k].address; k++) { |
| qd_iterator_t *find_me = qd_iterator_string(tests[k].address, ITER_VIEW_ALL); |
| vh.count = 0; |
| qd_parse_tree_search(node, find_me, visit_all, (void *)&vh); |
| //printf("Matches for %s:\n", tests[k].address); |
| //for (int i = 0; i < vh.count; i++) |
| // printf("%s, ", vh.patterns[i]); |
| //printf("count = %d\n", vh.count); |
| if (vh.count != tests[k].count) { |
| qd_iterator_free(find_me); |
| qd_parse_tree_free(node); |
| return "Unexpected match count"; |
| } |
| for (int i = 0; i < tests[k].count; i++) { |
| if (strcmp(vh.patterns[i], tests[k].matches[i])) { |
| qd_iterator_free(find_me); |
| qd_parse_tree_free(node); |
| return "Unexpected pattern match"; |
| } |
| } |
| |
| qd_iterator_free(find_me); |
| } |
| |
| // verify 'best' match is found |
| for (int k = 0; tests[k].address; k++) { |
| qd_iterator_t *find_me = qd_iterator_string(tests[k].address, ITER_VIEW_ALL); |
| vh.count = 0; |
| qd_parse_tree_search(node, find_me, find_best, (void *)&vh); |
| // printf("best match for %s: %s\n", tests[k].address, vh.patterns[0]); |
| if (tests[k].count == 0) { |
| if (vh.count != 0) { |
| qd_iterator_free(find_me); |
| qd_parse_tree_free(node); |
| return "Did not expect to find a best match!"; |
| } |
| } else if (vh.count == 0 || strcmp(vh.patterns[0], tests[k].matches[0])) { |
| qd_iterator_free(find_me); |
| qd_parse_tree_free(node); |
| return "Unexpected best pattern match"; |
| } |
| qd_iterator_free(find_me); |
| } |
| |
| qd_parse_tree_free(node); |
| |
| return NULL; |
| } |
| |
| |
| // search a full parse tree for multiple and best matches |
| static char *test_multiple_matches(void *context) |
| { |
| const char *patterns_amqp_0_10[PCOUNT] = |
| { "alpha", |
| "bravo", |
| "alpha.bravo", |
| "bravo.charlie", |
| "alpha.bravo.charlie.delta", |
| "bravo.charlie.delta.echo", |
| "alpha.*", |
| "alpha.#", |
| "alpha.*.#", |
| "#.bravo", |
| "*.bravo", |
| "*.#.bravo", |
| "alpha.*.bravo", |
| "alpha.#.bravo", |
| "alpha.*.#.bravo", |
| "*.bravo.*", |
| "#.bravo.#", |
| }; |
| // matches are listed in order of best->least best match |
| multi_match_t tests_amqp_0_10[] = { |
| {"alpha", 2, {"alpha", "alpha.#"}}, |
| {"alpha.zulu", 3, { "alpha.*", "alpha.*.#", "alpha.#"}}, |
| {"alpha.bravo", 9, {"alpha.bravo", "alpha.*", "alpha.*.#", "alpha.#.bravo", "alpha.#", "*.bravo", "*.#.bravo", "#.bravo", "#.bravo.#"}}, |
| {"bravo", 3, {"bravo", "#.bravo", "#.bravo.#"}}, |
| {"xray.bravo", 4, {"*.bravo", "*.#.bravo", "#.bravo", "#.bravo.#"}}, |
| {"alpha.bravo.charlie", 4, {"alpha.*.#", "alpha.#", "*.bravo.*", "#.bravo.#"}}, |
| {"xray.yankee.zulu.bravo", 3, {"*.#.bravo", "#.bravo", "#.bravo.#"}}, |
| {"alpha.bravo.charlie.delta", 4, {"alpha.bravo.charlie.delta", "alpha.*.#","alpha.#", "#.bravo.#"}}, |
| {"alpha.charlie.charlie.bravo", 7, {"alpha.*.#.bravo", "alpha.*.#", "alpha.#.bravo", "alpha.#", "*.#.bravo", "#.bravo", "#.bravo.#"}}, |
| {"xray.yankeee.zulu.bravo.alpha.bravo.charlie", 2, {"#.bravo.#", "#.bravo.#"}}, |
| {"I.match.nothing", 0, {NULL}}, |
| {NULL, 0, {NULL}} |
| }; |
| |
| char *rc = multiple_matches(QD_PARSE_TREE_AMQP_0_10, |
| patterns_amqp_0_10, |
| tests_amqp_0_10); |
| if (rc) return rc; |
| |
| const char *patterns_mqtt[PCOUNT] = |
| { "alpha", |
| "bravo", |
| "alpha/bravo", |
| "bravo/charlie", |
| "alpha/bravo/charlie/delta", |
| "alpha/+", |
| "alpha/#", |
| "alpha/+/#", |
| "bravo/charlie/echo", |
| "bravo/charlie/+", |
| "+/bravo", |
| "bravo/#", |
| "alpha/+/bravo", |
| "alpha/+/bravo/#", |
| "+/+/+/#", |
| "+/bravo/+", |
| "xray/+/#" |
| }; |
| |
| // matches are listed in order of best->least best match |
| multi_match_t tests_mqtt[] = { |
| {"alpha", 2, {"alpha", "alpha/#"}}, |
| {"alpha/zulu", 3, { "alpha/+", "alpha/+/#", "alpha/#"}}, |
| {"alpha/bravo", 5, {"alpha/bravo", "alpha/+", "alpha/+/#", "alpha/#", "+/bravo"}}, |
| {"bravo", 2, {"bravo", "bravo/#"}}, |
| {"xray/bravo", 2, {"xray/+/#", "+/bravo"}}, |
| {"alpha/bravo/charlie", 4, {"alpha/+/#", "alpha/#", "+/bravo/+", "+/+/+/#"}}, |
| {"xray/yankee/zulu/bravo", 2, {"xray/+/#", "+/+/+/#"}}, |
| {"alpha/bravo/charlie/delta", 4, {"alpha/bravo/charlie/delta", "alpha/+/#", "alpha/#", "+/+/+/#"}}, |
| {"alpha/charlie/charlie/bravo", 3, {"alpha/+/#", "alpha/#", "+/+/+/#"}}, |
| {"xray/bravo/alpha", 3, {"xray/+/#", "+/bravo/+", "+/+/+/#"}}, |
| {"bravo/charlie/ech", 3, {"bravo/charlie/+", "bravo/#", "+/+/+/#"}}, |
| {"I/match.nothing", 0, {NULL}}, |
| {NULL, 0, {NULL}} |
| }; |
| |
| rc = multiple_matches(QD_PARSE_TREE_MQTT, patterns_mqtt, tests_mqtt); |
| return rc; |
| } |
| |
| |
| static char *test_validation(void *context) |
| { |
| qd_iterator_t *iter = qd_iterator_string("sam.*.am.#", ITER_VIEW_ALL); |
| qd_iterator_t *iter_good = qd_iterator_string("sam/+/a.#.m/#", ITER_VIEW_ALL); |
| qd_iterator_t *iter_bad = qd_iterator_string("", ITER_VIEW_ALL); // no tokens |
| qd_iterator_t *iter_bad_slash = qd_iterator_string("/", ITER_VIEW_ALL); // just separators |
| qd_iterator_t *iter_bad_dot = qd_iterator_string(".", ITER_VIEW_ALL); // just separators |
| qd_iterator_t *iter_bad_mqtt = qd_iterator_string("sam/#/am/+", ITER_VIEW_ALL); // glob must be last |
| qd_iterator_t *iter_const = qd_iterator_string("sam/I/am", ITER_VIEW_ALL); |
| qd_parse_tree_t *mqtt_tree = qd_parse_tree_new(QD_PARSE_TREE_MQTT); |
| qd_parse_tree_t *addr_tree = qd_parse_tree_new(QD_PARSE_TREE_ADDRESS); |
| qd_parse_tree_t *amqp_tree = qd_parse_tree_new(QD_PARSE_TREE_AMQP_0_10); |
| |
| char *error = 0; |
| |
| if (!qd_parse_tree_validate_pattern(addr_tree, iter) || |
| !qd_parse_tree_validate_pattern(amqp_tree, iter)) { |
| error = "expected valid pattern"; |
| goto cleanup; |
| } |
| |
| |
| if (qd_parse_tree_validate_pattern(addr_tree, iter_bad) || |
| qd_parse_tree_validate_pattern(mqtt_tree, iter_bad) || |
| qd_parse_tree_validate_pattern(amqp_tree, iter_bad)) { |
| error = "expected null pattern to be invalid"; |
| goto cleanup; |
| } |
| |
| |
| if (qd_parse_tree_validate_pattern(addr_tree, iter_bad_dot) || |
| qd_parse_tree_validate_pattern(amqp_tree, iter_bad_dot)) { |
| error = "expected separator dot pattern to be invalid"; |
| goto cleanup; |
| } |
| |
| if (qd_parse_tree_validate_pattern(addr_tree, iter_bad_slash) || |
| qd_parse_tree_validate_pattern(mqtt_tree, iter_bad_slash)) { |
| error = "expected separator slash pattern to be invalid"; |
| goto cleanup; |
| } |
| |
| if (!qd_parse_tree_validate_pattern(mqtt_tree, iter_good)) { |
| error = "expected to pass mqtt validation"; |
| goto cleanup; |
| } |
| |
| if (qd_parse_tree_validate_pattern(mqtt_tree, iter_bad_mqtt)) { |
| error = "expected to fail mqtt validation"; |
| goto cleanup; |
| } |
| |
| if (!qd_parse_tree_validate_pattern(mqtt_tree, iter_const)) { |
| error = "expected to pass mqtt constant string validation"; |
| // fallthrough |
| } |
| |
| cleanup: |
| qd_iterator_free(iter); |
| qd_iterator_free(iter_good); |
| qd_iterator_free(iter_bad); |
| qd_iterator_free(iter_bad_slash); |
| qd_iterator_free(iter_bad_dot); |
| qd_iterator_free(iter_bad_mqtt); |
| qd_iterator_free(iter_const); |
| |
| qd_parse_tree_free(mqtt_tree); |
| qd_parse_tree_free(addr_tree); |
| qd_parse_tree_free(amqp_tree); |
| return error ? error : 0; |
| } |
| |
| |
| int parse_tree_tests(void) |
| { |
| int result = 0; |
| char *test_group = "parse_tree_tests"; |
| |
| TEST_CASE(test_add_remove, 0); |
| TEST_CASE(test_add_and_match_str, 0); |
| TEST_CASE(test_duplicate_error_str, 0); |
| TEST_CASE(test_normalization, 0); |
| TEST_CASE(test_matches, 0); |
| TEST_CASE(test_multiple_matches, 0); |
| TEST_CASE(test_validation, 0); |
| return result; |
| } |