diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e1ed570..b1afb54 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,5 @@ variables: + CI_ENABLE_UNIT_TESTING: 1 CI_TARGET_BUILD_DEPENDS: libubox CI_CMAKE_EXTRA_BUILD_ARGS: -DLUAPATH=/usr/lib/lua diff --git a/CMakeLists.txt b/CMakeLists.txt index 7a86854..b80d551 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,6 +42,11 @@ INSTALL(TARGETS ubox ubox-static ADD_SUBDIRECTORY(lua) ADD_SUBDIRECTORY(examples) +IF(UNIT_TESTING) + ENABLE_TESTING() + ADD_SUBDIRECTORY(tests) +ENDIF() + find_library(json NAMES json-c) IF(EXISTS ${json}) ADD_LIBRARY(blobmsg_json SHARED blobmsg_json.c) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index a635535..04f18b0 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -9,15 +9,9 @@ IF (BUILD_EXAMPLES) FIND_LIBRARY(json NAMES json-c json) - ADD_EXECUTABLE(blobmsg-example blobmsg-example.c) - TARGET_LINK_LIBRARIES(blobmsg-example ubox blobmsg_json ${json}) - ADD_EXECUTABLE(ustream-example ustream-example.c) TARGET_LINK_LIBRARIES(ustream-example ubox) - ADD_EXECUTABLE(runqueue-example runqueue-example.c) - TARGET_LINK_LIBRARIES(runqueue-example ubox) - ADD_EXECUTABLE(json_script-example json_script-example.c) TARGET_LINK_LIBRARIES(json_script-example ubox blobmsg_json json_script ${json}) ENDIF() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..1c44825 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,13 @@ +ADD_SUBDIRECTORY(cram) + +MACRO(ADD_UNIT_TEST name) + ADD_EXECUTABLE(${name} ${name}.c) + TARGET_LINK_LIBRARIES(${name} ubox blobmsg_json json_script ${json}) + TARGET_INCLUDE_DIRECTORIES(${name} PRIVATE ${PROJECT_SOURCE_DIR}) +ENDMACRO(ADD_UNIT_TEST) + +FILE(GLOB test_cases "test-*.c") +FOREACH(test_case ${test_cases}) + GET_FILENAME_COMPONENT(test_case ${test_case} NAME_WE) + ADD_UNIT_TEST(${test_case}) +ENDFOREACH(test_case) diff --git a/tests/cram/CMakeLists.txt b/tests/cram/CMakeLists.txt new file mode 100644 index 0000000..bebd821 --- /dev/null +++ b/tests/cram/CMakeLists.txt @@ -0,0 +1,22 @@ +FIND_PACKAGE(PythonInterp 3 REQUIRED) +FILE(GLOB test_cases "test_*.t") + +SET(PYTHON_VENV_DIR "${CMAKE_CURRENT_BINARY_DIR}/.venv") +SET(PYTHON_VENV_PIP "${PYTHON_VENV_DIR}/bin/pip") +SET(PYTHON_VENV_CRAM "${PYTHON_VENV_DIR}/bin/cram") + +ADD_CUSTOM_COMMAND( + OUTPUT ${PYTHON_VENV_CRAM} + COMMAND ${PYTHON_EXECUTABLE} -m venv ${PYTHON_VENV_DIR} + COMMAND ${PYTHON_VENV_PIP} install cram +) +ADD_CUSTOM_TARGET(prepare-cram-venv ALL DEPENDS ${PYTHON_VENV_CRAM}) + +ADD_TEST( + NAME cram + COMMAND ${PYTHON_VENV_CRAM} ${test_cases} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +) + +SET_PROPERTY(TEST cram APPEND PROPERTY ENVIRONMENT "JSHN=$") +SET_PROPERTY(TEST cram APPEND PROPERTY ENVIRONMENT "TEST_BIN_DIR=$") diff --git a/tests/cram/inputs/json-script.json b/tests/cram/inputs/json-script.json new file mode 100644 index 0000000..5328e59 --- /dev/null +++ b/tests/cram/inputs/json-script.json @@ -0,0 +1,38 @@ +[ + [ "exec", "%EXECVAR%", "/%%/" ], + [ "if", + [ "eq", "EQVAR", "eqval" ], + [ "exec_if", "%VAR%", "%%", "jk" ] + ], + [ "case", "CASEVAR", { + "caseval0": ["cmd_case_0", "cmd_case_arg0", "case_cmd_arg1"], + "caseval1": ["cmd_case_1", "cmd_case_arg0", "case_cmd_arg1"] + } ], + + [ "if", + [ "and", [ "eq", "EQVAR", "eqval" ], + [ "has", "HASVAR" ], + [ "regex", "REGEXVAR0", "regexval" ], + [ "regex", "REGEXVAR1", [ "regexval10", "regexval11" ] ], + [ "not", [ "eq", "NOTEQVAR", "noteqval" ] ] ], + [ "exec_if_and", "%ANDVAR%" ] + ], + + [ "if", + [ "or", [ "eq", "EQVAR", "eqval" ], + [ "has", "HASVAR" ], + [ "regex", "REGEXVAR0", "regexval" ], + [ "regex", "REGEXVAR1", [ "regexval10", "regexval11" ] ], + [ "not", [ "eq", "NOTEQVAR", "noteqval" ] ] ], + [ "exec_if_or", "%ORVAR%" ] + ], + + [ "if", + [ "isdir", "%ISDIRVAR%" ], + [ "exec_isdir", "%ISDIRVAR%" ] + ], + + [ "return", "foobar" ], + + [ "exec_non_reachable", "Arghhh" ] +] diff --git a/tests/cram/test_avl.t b/tests/cram/test_avl.t new file mode 100644 index 0000000..19a8d21 --- /dev/null +++ b/tests/cram/test_avl.t @@ -0,0 +1,11 @@ +check that avl is producing expected results: + + $ [ -n "$TEST_BIN_DIR" ] && export PATH="$TEST_BIN_DIR:$PATH" + $ valgrind --quiet --leak-check=full test-avl + test_basics: insert: 0=zero 0=one 0=two 0=three 0=four 0=five 0=six 0=seven 0=eight 0=nine 0=ten 0=eleven 0=twelve + test_basics: insert duplicate: -1=zero -1=one -1=two -1=three -1=four -1=five -1=six -1=seven -1=eight -1=nine -1=ten -1=eleven -1=twelve + test_basics: first=eight last=zero + test_basics: for each element: eight eleven five four nine one seven six ten three twelve two zero + test_basics: delete 'one' element + test_basics: for each element reverse: zero two twelve three ten six seven nine four five eleven eight + test_basics: delete all elements diff --git a/tests/cram/test_base64.t b/tests/cram/test_base64.t new file mode 100644 index 0000000..4f8809f --- /dev/null +++ b/tests/cram/test_base64.t @@ -0,0 +1,21 @@ +set test bin path: + + $ [ -n "$TEST_BIN_DIR" ] && export PATH="$TEST_BIN_DIR:$PATH" + +check that base64 is producing expected results: + + $ valgrind --quiet --leak-check=full test-b64 + 0 + 4 Zg== + 4 Zm8= + 4 Zm9v + 8 Zm9vYg== + 8 Zm9vYmE= + 8 Zm9vYmFy + 0 + 1 f + 2 fo + 3 foo + 4 foob + 5 fooba + 6 foobar diff --git a/tests/cram/test_blobmsg.t b/tests/cram/test_blobmsg.t new file mode 100644 index 0000000..504a056 --- /dev/null +++ b/tests/cram/test_blobmsg.t @@ -0,0 +1,17 @@ +check that blobmsg is producing expected results: + + $ [ -n "$TEST_BIN_DIR" ] && export PATH="$TEST_BIN_DIR:$PATH" + $ valgrind --quiet --leak-check=full test-blobmsg + Message: Hello, world! + List: { + 0 + 1 + 2 + 133.700000 + } + Testdata: { + \tdouble : 133.700000 (esc) + \thello : 1 (esc) + \tworld : 2 (esc) + } + json: {"message":"Hello, world!","testdata":{"double":133.700000,"hello":1,"world":"2"},"list":[0,1,2,133.700000]} diff --git a/tests/cram/test_jshn.t b/tests/cram/test_jshn.t new file mode 100644 index 0000000..d228f0e --- /dev/null +++ b/tests/cram/test_jshn.t @@ -0,0 +1,25 @@ +set jshn for convenience: + + $ [ -n "$JSHN" ] && export PATH="$(dirname "$JSHN"):$PATH" + $ alias jshn="valgrind --quiet --leak-check=full jshn" + +check usage: + + $ jshn + Usage: jshn [-n] [-i] -r |-R |-w + [2] + +test bad json: + + $ jshn -r '[]' + Failed to parse message data + [1] + +test good json: + + $ jshn -r '{"foo": "bar", "baz": {"next": "meep"}}' + json_init; + json_add_string 'foo' 'bar'; + json_add_object 'baz'; + json_add_string 'next' 'meep'; + json_close_object; diff --git a/tests/cram/test_json_script.t b/tests/cram/test_json_script.t new file mode 100644 index 0000000..3e80a5c --- /dev/null +++ b/tests/cram/test_json_script.t @@ -0,0 +1,96 @@ +set test bin path: + + $ [ -n "$TEST_BIN_DIR" ] && export PATH="$TEST_BIN_DIR:$PATH" + $ export TEST_INPUTS="$TESTDIR/inputs" + $ alias js="valgrind --quiet --leak-check=full test-json-script" + +check that json-script is producing expected results: + + $ js + Usage: test-json-script [VARNAME=value] + [254] + + $ echo '}' > test.json; js test.json + load JSON data from test.json failed. + + $ js nada.json 2>&1 | grep load.*failed + load JSON data from nada.json failed. + + $ echo '[ [ ] [ ] ]' > test.json; js test.json + load JSON data from test.json failed. + +check example json-script: + + $ js $TEST_INPUTS/json-script.json + exec /%/ + exec_if_or + + $ js EXECVAR=meh ORVAR=meep $TEST_INPUTS/json-script.json + exec meh /%/ + exec_if_or meep + +check has expression: + + $ echo ' + > [ + > [ "if", + > [ "has", "VAR" ], + > [ "echo", "bar" ], + > [ "echo", "baz" ] + > ] + > ]' > test.json + + $ js VAR=foo test.json + echo bar + + $ js VAR=bar test.json + echo bar + + $ js test.json + echo baz + +check eq expression: + + $ echo ' + > [ + > [ "if", + > [ "eq", "VAR", "bar" ], + > [ "echo", "foo" ], + > [ "echo", "baz" ] + > ] + > ]' > test.json + + $ js VAR=bar test.json + echo foo + + $ js VAR=xxx test.json + echo baz + + $ js test.json + echo baz + +check regex single expression: + + $ echo ' + > [ + > [ "if", + > [ "regex", "VAR", ".ell." ], + > [ "echo", "bar" ], + > [ "echo", "baz" ] + > ] + > ]' > test.json + + $ js VAR=hello test.json + echo bar + + $ js VAR=.ell. test.json + echo bar + + $ js test.json + echo baz + + $ js VAR= test.json + echo baz + + $ js VAR=hell test.json + echo baz diff --git a/tests/cram/test_list.t b/tests/cram/test_list.t new file mode 100644 index 0000000..f7f18bd --- /dev/null +++ b/tests/cram/test_list.t @@ -0,0 +1,22 @@ +check that list is producing expected results: + + $ [ -n "$TEST_BIN_DIR" ] && export PATH="$TEST_BIN_DIR:$PATH" + $ valgrind --quiet --leak-check=full test-list + test_basics: list_empty: yes + test_basics: list_add_tail: zero one two three four five six seven eight nine ten eleven twelve + test_basics: list_empty: no + test_basics: first=zero last=twelve + test_basics: 'zero' is first, yes + test_basics: 'twelve' is last, yes + test_basics: removing 'twelve' and 'zero' + test_basics: first=one last=eleven + test_basics: 'one' is first, yes + test_basics: 'eleven' is last, yes + test_basics: moving 'one' to the tail + test_basics: first=two last=one + test_basics: 'two' is first, yes + test_basics: 'one' is last, yes + test_basics: list_for_each_entry: two three four five six seven eight nine ten eleven one + test_basics: list_for_each_entry_reverse: one eleven ten nine eight seven six five four three two + test_basics: delete all entries + test_basics: list_empty: yes diff --git a/tests/cram/test_runqueue.t b/tests/cram/test_runqueue.t new file mode 100644 index 0000000..4d49110 --- /dev/null +++ b/tests/cram/test_runqueue.t @@ -0,0 +1,14 @@ +check that runqueue is producing expected results: + + $ [ -n "$TEST_BIN_DIR" ] && export PATH="$TEST_BIN_DIR:$PATH" + $ valgrind --quiet --leak-check=full test-runqueue + [1/1] start 'sleep 1' + [1/1] cancel 'sleep 1' + [0/1] finish 'sleep 1' + [1/1] start 'sleep 1' + [1/1] cancel 'sleep 1' + [0/1] finish 'sleep 1' + [1/1] start 'sleep 1' + [1/1] cancel 'sleep 1' + [0/1] finish 'sleep 1' + All done! diff --git a/tests/test-avl.c b/tests/test-avl.c new file mode 100644 index 0000000..18ee9b7 --- /dev/null +++ b/tests/test-avl.c @@ -0,0 +1,87 @@ +#include +#include +#include + +#include "avl.h" +#include "avl-cmp.h" +#include "utils.h" + +#define OUT(fmt, ...) do { \ + fprintf(stdout, "%s: " fmt, __func__, ## __VA_ARGS__); \ +} while (0); + +struct node { + struct avl_node avl; +}; + +static void test_basics() +{ + size_t i; + struct avl_tree t; + struct node *temp; + struct node *elem; + struct node *last; + struct node *first; + const char *vals[] = { + "zero", "one", "two", "three", "four", "five", "six", + "seven", "eight", "nine", "ten", "eleven", "twelve" + }; + + avl_init(&t, avl_strcmp, false, NULL); + + OUT("insert: "); + for (i=0; iavl.key = vals[i]; + + int r = avl_insert(&t, &n->avl); + fprintf(stdout, "%d=%s ", r, (char *)n->avl.key); + } + fprintf(stdout, "\n"); + + OUT("insert duplicate: "); + for (i=0; iavl.key = vals[i]; + + int r = avl_insert(&t, &n->avl); + fprintf(stdout, "%d=%s ", r, (char *)n->avl.key); + + if (r) + free(n); + } + fprintf(stdout, "\n"); + + first = avl_first_element(&t, first, avl); + last = avl_last_element(&t, last, avl); + OUT("first=%s last=%s\n", (char*)first->avl.key, (char*)last->avl.key); + + OUT("for each element: "); + avl_for_each_element(&t, elem, avl) { + fprintf(stdout, "%s ", (char*)elem->avl.key); + } + fprintf(stdout, "\n"); + + OUT("delete 'one' element\n"); + elem = avl_find_element(&t, "one", elem, avl); + avl_delete(&t, &elem->avl); + free(elem); + + OUT("for each element reverse: "); + avl_for_each_element_reverse(&t, elem, avl) { + fprintf(stdout, "%s ", (char*)elem->avl.key); + } + fprintf(stdout, "\n"); + + OUT("delete all elements\n"); + avl_for_each_element_safe(&t, elem, avl, temp) { + avl_delete(&t, &elem->avl); + free(elem); + } +} + +int main() +{ + test_basics(); + return 0; +} diff --git a/tests/test-b64.c b/tests/test-b64.c new file mode 100644 index 0000000..c29b4e2 --- /dev/null +++ b/tests/test-b64.c @@ -0,0 +1,39 @@ +#include +#include + +#include "utils.h" + +static void test_b64_encode(const char *src) +{ + char dst[255] = {0}; + int r = b64_encode(src, strlen(src), dst, sizeof(dst)); + fprintf(stdout, "%d %s\n", r, dst); +} + +static void test_b64_decode(const char *src) +{ + char dst[255] = {0}; + int r = b64_decode(src, dst, sizeof(dst)); + fprintf(stdout, "%d %s\n", r, dst); +} + +int main() +{ + test_b64_encode(""); + test_b64_encode("f"); + test_b64_encode("fo"); + test_b64_encode("foo"); + test_b64_encode("foob"); + test_b64_encode("fooba"); + test_b64_encode("foobar"); + + test_b64_decode(""); + test_b64_decode("Zg=="); + test_b64_decode("Zm8="); + test_b64_decode("Zm9v"); + test_b64_decode("Zm9vYg=="); + test_b64_decode("Zm9vYmE="); + test_b64_decode("Zm9vYmFy"); + + return 0; +} diff --git a/examples/blobmsg-example.c b/tests/test-blobmsg.c similarity index 95% rename from examples/blobmsg-example.c rename to tests/test-blobmsg.c index 56cac27..5e99dc2 100644 --- a/examples/blobmsg-example.c +++ b/tests/test-blobmsg.c @@ -131,15 +131,22 @@ fill_message(struct blob_buf *buf) int main(int argc, char **argv) { + char *json = NULL; static struct blob_buf buf; blobmsg_buf_init(&buf); fill_message(&buf); dump_message(&buf); - fprintf(stderr, "json: %s\n", blobmsg_format_json(buf.head, true)); + + json = blobmsg_format_json(buf.head, true); + if (!json) + exit(EXIT_FAILURE); + + fprintf(stderr, "json: %s\n", json); if (buf.buf) free(buf.buf); + free(json); return 0; } diff --git a/tests/test-json-script.c b/tests/test-json-script.c new file mode 100644 index 0000000..6d93059 --- /dev/null +++ b/tests/test-json-script.c @@ -0,0 +1,84 @@ +#include +#include + +#include +#include "blobmsg.h" +#include "blobmsg_json.h" +#include "json_script.h" + +struct json_script_ctx jctx; +struct blob_buf b_vars; +struct blob_buf b_script; + +static void handle_command(struct json_script_ctx *ctx, const char *name, + struct blob_attr *data, struct blob_attr *vars) +{ + struct blob_attr *cur; + size_t rem; + + fprintf(stdout, "%s", name); + blobmsg_for_each_attr(cur, data, rem) + fprintf(stdout, " %s", (char *) blobmsg_data(cur)); + fprintf(stdout, "\n"); +} + +static struct json_script_file * +handle_file(struct json_script_ctx *ctx, const char *filename) +{ + json_object *obj; + + obj = json_object_from_file(filename); + if (!obj) { + fprintf(stderr, "load JSON data from %s failed.\n", filename); + return NULL; + } + + blob_buf_init(&b_script, 0); + blobmsg_add_json_element(&b_script, "", obj); + json_object_put(obj); + + return json_script_file_from_blobmsg(filename, + blob_data(b_script.head), blob_len(b_script.head)); +} + +static void usage(const char *prog, int exit_code) +{ + fprintf(stderr, "Usage: %s [VARNAME=value] \n", prog); + exit(exit_code); +} + +int main(int argc, char *argv[]) +{ + int i; + char *file = NULL; + const char *prog = argv[0]; + + blobmsg_buf_init(&b_vars); + blobmsg_buf_init(&b_script); + + json_script_init(&jctx); + jctx.handle_command = handle_command; + jctx.handle_file = handle_file; + + for (i = 1; i < argc; i++) { + char *sep = strchr(argv[i], '='); + if (sep) { + *sep = '\0'; + blobmsg_add_string(&b_vars, argv[i], sep + 1); + } else if (!file) { + file = argv[i]; + } else { + usage(prog, -1); + } + } + if (i < argc || !file) + usage(prog, -2); + + json_script_run(&jctx, file, b_vars.head); + + json_script_free(&jctx); + blob_buf_free(&b_script); + blob_buf_free(&b_vars); + + return 0; +} diff --git a/tests/test-list.c b/tests/test-list.c new file mode 100644 index 0000000..cb0f231 --- /dev/null +++ b/tests/test-list.c @@ -0,0 +1,91 @@ +#include +#include +#include + +#include "list.h" +#include "utils.h" + +struct item { + const char *name; + struct list_head list; +}; + +#define OUT(fmt, ...) do { \ + fprintf(stdout, "%s: " fmt, __func__, ## __VA_ARGS__); \ +} while (0); + +static void test_basics() +{ + size_t i; + struct item *tmp; + struct item *item; + struct item *last; + struct item *first; + static struct list_head test_list = LIST_HEAD_INIT(test_list); + + const char *vals[] = { + "zero", "one", "two", "three", "four", "five", "six", + "seven", "eight", "nine", "ten", "eleven", "twelve" + }; + + OUT("list_empty: %s\n", list_empty(&test_list) ? "yes" : "no"); + OUT("list_add_tail: "); + for (i=0; iname = vals[i]; + list_add_tail(&e->list, &test_list); + fprintf(stdout, "%s ", vals[i]); + } + fprintf(stdout, "\n"); + OUT("list_empty: %s\n", list_empty(&test_list) ? "yes" : "no"); + + first = list_first_entry(&test_list, struct item, list); + last = list_last_entry(&test_list, struct item, list); + OUT("first=%s last=%s\n", first->name, last->name); + OUT("'zero' is first, %s\n", list_is_first(&first->list, &test_list) ? "yes" : "no"); + OUT("'twelve' is last, %s\n", list_is_last(&last->list, &test_list) ? "yes" : "no"); + + OUT("removing 'twelve' and 'zero'\n"); + list_del(&first->list); + list_del(&last->list); + free(first); + free(last); + first = list_first_entry(&test_list, struct item, list); + last = list_last_entry(&test_list, struct item, list); + OUT("first=%s last=%s\n", first->name, last->name); + OUT("'one' is first, %s\n", list_is_first(&first->list, &test_list) ? "yes" : "no"); + OUT("'eleven' is last, %s\n", list_is_last(&last->list, &test_list) ? "yes" : "no"); + + OUT("moving 'one' to the tail\n"); + list_move_tail(&first->list, &test_list); + first = list_first_entry(&test_list, struct item, list); + last = list_last_entry(&test_list, struct item, list); + OUT("first=%s last=%s\n", first->name, last->name); + OUT("'two' is first, %s\n", list_is_first(&first->list, &test_list) ? "yes" : "no"); + OUT("'one' is last, %s\n", list_is_last(&last->list, &test_list) ? "yes" : "no"); + + OUT("list_for_each_entry: "); + list_for_each_entry(item, &test_list, list) { + fprintf(stdout, "%s ", item->name); + } + fprintf(stdout, "\n"); + + OUT("list_for_each_entry_reverse: "); + list_for_each_entry_reverse(item, &test_list, list) { + fprintf(stdout, "%s ", item->name); + } + fprintf(stdout, "\n"); + + OUT("delete all entries\n"); + list_for_each_entry_safe(item, tmp, &test_list, list) { + list_del(&item->list); + free(item); + } + OUT("list_empty: %s\n", list_empty(&test_list) ? "yes" : "no"); +} + +int main() +{ + test_basics(); + return 0; +} diff --git a/examples/runqueue-example.c b/tests/test-runqueue.c similarity index 100% rename from examples/runqueue-example.c rename to tests/test-runqueue.c