add cram based unit tests
authorPetr Štetiar <ynezz@true.cz>
Tue, 19 Nov 2019 13:31:44 +0000 (14:31 +0100)
committerPetr Štetiar <ynezz@true.cz>
Sun, 24 Nov 2019 12:26:58 +0000 (13:26 +0100)
For improved QA etc. For the start with initial test cases for avl,
base64, jshn and list components. Moved runqueue and blobmsg from
examples to tests.  Converted just a few first test cases from
json-script example into the new cram based unit test, more to come.

Signed-off-by: Petr Štetiar <ynezz@true.cz>
21 files changed:
.gitlab-ci.yml
CMakeLists.txt
examples/CMakeLists.txt
examples/blobmsg-example.c [deleted file]
examples/runqueue-example.c [deleted file]
tests/CMakeLists.txt [new file with mode: 0644]
tests/cram/CMakeLists.txt [new file with mode: 0644]
tests/cram/inputs/json-script.json [new file with mode: 0644]
tests/cram/test_avl.t [new file with mode: 0644]
tests/cram/test_base64.t [new file with mode: 0644]
tests/cram/test_blobmsg.t [new file with mode: 0644]
tests/cram/test_jshn.t [new file with mode: 0644]
tests/cram/test_json_script.t [new file with mode: 0644]
tests/cram/test_list.t [new file with mode: 0644]
tests/cram/test_runqueue.t [new file with mode: 0644]
tests/test-avl.c [new file with mode: 0644]
tests/test-b64.c [new file with mode: 0644]
tests/test-blobmsg.c [new file with mode: 0644]
tests/test-json-script.c [new file with mode: 0644]
tests/test-list.c [new file with mode: 0644]
tests/test-runqueue.c [new file with mode: 0644]

index e1ed5706e27d081a8481eec4e8c9dad29218160e..b1afb543b23f2f3dfdcc1deff745e28f9a90d72c 100644 (file)
@@ -1,4 +1,5 @@
 variables:
+  CI_ENABLE_UNIT_TESTING: 1
   CI_TARGET_BUILD_DEPENDS: libubox
   CI_CMAKE_EXTRA_BUILD_ARGS: -DLUAPATH=/usr/lib/lua
 
index 7a86854841e311fad850960e45db85e786645efb..b80d551c112ff7f24e87bc5df86b9aaeec40615e 100644 (file)
@@ -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)
index a6355356417b4cbd6bd6b09b7aefe81c541317a2..04f18b0f44453957609491317bca4feea960d8b8 100644 (file)
@@ -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/examples/blobmsg-example.c b/examples/blobmsg-example.c
deleted file mode 100644 (file)
index 56cac27..0000000
+++ /dev/null
@@ -1,145 +0,0 @@
-#include <stdio.h>
-#include <inttypes.h>
-
-#include "blobmsg.h"
-#include "blobmsg_json.h"
-
-static const char *indent_str = "\t\t\t\t\t\t\t\t\t\t\t\t\t";
-
-#define indent_printf(indent, ...) do { \
-       if (indent > 0) \
-               fwrite(indent_str, indent, 1, stderr); \
-       fprintf(stderr, __VA_ARGS__); \
-} while(0)
-
-static void dump_attr_data(struct blob_attr *data, int indent, int next_indent);
-
-static void
-dump_table(struct blob_attr *head, size_t len, int indent, bool array)
-{
-       struct blob_attr *attr;
-       struct blobmsg_hdr *hdr;
-
-       indent_printf(indent, "{\n");
-       __blob_for_each_attr(attr, head, len) {
-               hdr = blob_data(attr);
-               if (!array)
-                       indent_printf(indent + 1, "%s : ", hdr->name);
-               dump_attr_data(attr, 0, indent + 1);
-       }
-       indent_printf(indent, "}\n");
-}
-
-static void dump_attr_data(struct blob_attr *data, int indent, int next_indent)
-{
-       int type = blobmsg_type(data);
-       switch(type) {
-       case BLOBMSG_TYPE_STRING:
-               indent_printf(indent, "%s\n", blobmsg_get_string(data));
-               break;
-       case BLOBMSG_TYPE_INT8:
-               indent_printf(indent, "%d\n", blobmsg_get_u8(data));
-               break;
-       case BLOBMSG_TYPE_INT16:
-               indent_printf(indent, "%d\n", blobmsg_get_u16(data));
-               break;
-       case BLOBMSG_TYPE_INT32:
-               indent_printf(indent, "%d\n", blobmsg_get_u32(data));
-               break;
-       case BLOBMSG_TYPE_INT64:
-               indent_printf(indent, "%"PRIu64"\n", blobmsg_get_u64(data));
-               break;
-       case BLOBMSG_TYPE_DOUBLE:
-               indent_printf(indent, "%lf\n", blobmsg_get_double(data));
-               break;
-       case BLOBMSG_TYPE_TABLE:
-       case BLOBMSG_TYPE_ARRAY:
-               if (!indent)
-                       indent_printf(indent, "\n");
-               dump_table(blobmsg_data(data), blobmsg_data_len(data),
-                          next_indent, type == BLOBMSG_TYPE_ARRAY);
-               break;
-       }
-}
-
-enum {
-       FOO_MESSAGE,
-       FOO_LIST,
-       FOO_TESTDATA
-};
-
-static const struct blobmsg_policy pol[] = {
-       [FOO_MESSAGE] = {
-               .name = "message",
-               .type = BLOBMSG_TYPE_STRING,
-       },
-       [FOO_LIST] = {
-               .name = "list",
-               .type = BLOBMSG_TYPE_ARRAY,
-       },
-       [FOO_TESTDATA] = {
-               .name = "testdata",
-               .type = BLOBMSG_TYPE_TABLE,
-       },
-};
-
-#ifndef ARRAY_SIZE
-#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
-#endif
-
-static void dump_message(struct blob_buf *buf)
-{
-       struct blob_attr *tb[ARRAY_SIZE(pol)];
-
-       if (blobmsg_parse(pol, ARRAY_SIZE(pol), tb, blob_data(buf->head), blob_len(buf->head)) != 0) {
-               fprintf(stderr, "Parse failed\n");
-               return;
-       }
-       if (tb[FOO_MESSAGE])
-               fprintf(stderr, "Message: %s\n", (char *) blobmsg_data(tb[FOO_MESSAGE]));
-
-       if (tb[FOO_LIST]) {
-               fprintf(stderr, "List: ");
-               dump_table(blobmsg_data(tb[FOO_LIST]), blobmsg_data_len(tb[FOO_LIST]), 0, true);
-       }
-       if (tb[FOO_TESTDATA]) {
-               fprintf(stderr, "Testdata: ");
-               dump_table(blobmsg_data(tb[FOO_TESTDATA]), blobmsg_data_len(tb[FOO_TESTDATA]), 0, false);
-       }
-}
-
-static void
-fill_message(struct blob_buf *buf)
-{
-       void *tbl;
-
-       blobmsg_add_string(buf, "message", "Hello, world!");
-
-       tbl = blobmsg_open_table(buf, "testdata");
-       blobmsg_add_double(buf, "double", 1.337e2);
-       blobmsg_add_u32(buf, "hello", 1);
-       blobmsg_add_string(buf, "world", "2");
-       blobmsg_close_table(buf, tbl);
-
-       tbl = blobmsg_open_array(buf, "list");
-       blobmsg_add_u32(buf, NULL, 0);
-       blobmsg_add_u32(buf, NULL, 1);
-       blobmsg_add_u32(buf, NULL, 2);
-       blobmsg_add_double(buf, "double", 1.337e2);
-       blobmsg_close_table(buf, tbl);
-}
-
-int main(int argc, char **argv)
-{
-       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));
-
-       if (buf.buf)
-               free(buf.buf);
-
-       return 0;
-}
diff --git a/examples/runqueue-example.c b/examples/runqueue-example.c
deleted file mode 100644 (file)
index 13ab864..0000000
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * runqueue-example.c
- *
- * Copyright (C) 2013 Felix Fietkau <nbd@openwrt.org>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <unistd.h>
-
-#include "uloop.h"
-#include "runqueue.h"
-
-static struct runqueue q;
-
-struct sleeper {
-       struct runqueue_process proc;
-       int val;
-};
-
-static void q_empty(struct runqueue *q)
-{
-       fprintf(stderr, "All done!\n");
-       uloop_end();
-}
-
-static void q_sleep_run(struct runqueue *q, struct runqueue_task *t)
-{
-       struct sleeper *s = container_of(t, struct sleeper, proc.task);
-       char str[32];
-       pid_t pid;
-
-       fprintf(stderr, "[%d/%d] start 'sleep %d'\n", q->running_tasks, q->max_running_tasks, s->val);
-
-       pid = fork();
-       if (pid < 0)
-               return;
-
-       if (pid) {
-               runqueue_process_add(q, &s->proc, pid);
-               return;
-       }
-
-       sprintf(str, "%d", s->val);
-       execlp("sleep", "sleep", str, NULL);
-       exit(1);
-}
-
-static void q_sleep_cancel(struct runqueue *q, struct runqueue_task *t, int type)
-{
-       struct sleeper *s = container_of(t, struct sleeper, proc.task);
-
-       fprintf(stderr, "[%d/%d] cancel 'sleep %d'\n", q->running_tasks, q->max_running_tasks, s->val);
-       runqueue_process_cancel_cb(q, t, type);
-}
-
-static void q_sleep_complete(struct runqueue *q, struct runqueue_task *p)
-{
-       struct sleeper *s = container_of(p, struct sleeper, proc.task);
-
-       fprintf(stderr, "[%d/%d] finish 'sleep %d'\n", q->running_tasks, q->max_running_tasks, s->val);
-       free(s);
-}
-
-static void add_sleeper(int val)
-{
-       static const struct runqueue_task_type sleeper_type = {
-               .run = q_sleep_run,
-               .cancel = q_sleep_cancel,
-               .kill = runqueue_process_kill_cb,
-       };
-       struct sleeper *s;
-
-       s = calloc(1, sizeof(*s));
-       s->proc.task.type = &sleeper_type;
-       s->proc.task.run_timeout = 500;
-       s->proc.task.complete = q_sleep_complete;
-       s->val = val;
-       runqueue_task_add(&q, &s->proc.task, false);
-}
-
-int main(int argc, char **argv)
-{
-       uloop_init();
-
-       runqueue_init(&q);
-       q.empty_cb = q_empty;
-       q.max_running_tasks = 1;
-
-       if (argc > 1)
-               q.max_running_tasks = atoi(argv[1]);
-
-       add_sleeper(1);
-       add_sleeper(1);
-       add_sleeper(1);
-       uloop_run();
-       uloop_done();
-
-       return 0;
-}
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
new file mode 100644 (file)
index 0000000..1c44825
--- /dev/null
@@ -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 (file)
index 0000000..bebd821
--- /dev/null
@@ -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=$<TARGET_FILE:jshn>")
+SET_PROPERTY(TEST cram APPEND PROPERTY ENVIRONMENT "TEST_BIN_DIR=$<TARGET_FILE_DIR:test-avl>")
diff --git a/tests/cram/inputs/json-script.json b/tests/cram/inputs/json-script.json
new file mode 100644 (file)
index 0000000..5328e59
--- /dev/null
@@ -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 (file)
index 0000000..19a8d21
--- /dev/null
@@ -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 (file)
index 0000000..4f8809f
--- /dev/null
@@ -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 (file)
index 0000000..504a056
--- /dev/null
@@ -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 (file)
index 0000000..d228f0e
--- /dev/null
@@ -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 <message>|-R <file>|-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 (file)
index 0000000..3e80a5c
--- /dev/null
@@ -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] <filename_json_script>
+  [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 (file)
index 0000000..f7f18bd
--- /dev/null
@@ -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 (file)
index 0000000..4d49110
--- /dev/null
@@ -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 (file)
index 0000000..18ee9b7
--- /dev/null
@@ -0,0 +1,87 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#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; i<ARRAY_SIZE(vals); i++) {
+               struct node *n = malloc(sizeof(struct node));
+               n->avl.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; i<ARRAY_SIZE(vals); i++) {
+               struct node *n = malloc(sizeof(struct node));
+               n->avl.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 (file)
index 0000000..c29b4e2
--- /dev/null
@@ -0,0 +1,39 @@
+#include <stdio.h>
+#include <string.h>
+
+#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/tests/test-blobmsg.c b/tests/test-blobmsg.c
new file mode 100644 (file)
index 0000000..5e99dc2
--- /dev/null
@@ -0,0 +1,152 @@
+#include <stdio.h>
+#include <inttypes.h>
+
+#include "blobmsg.h"
+#include "blobmsg_json.h"
+
+static const char *indent_str = "\t\t\t\t\t\t\t\t\t\t\t\t\t";
+
+#define indent_printf(indent, ...) do { \
+       if (indent > 0) \
+               fwrite(indent_str, indent, 1, stderr); \
+       fprintf(stderr, __VA_ARGS__); \
+} while(0)
+
+static void dump_attr_data(struct blob_attr *data, int indent, int next_indent);
+
+static void
+dump_table(struct blob_attr *head, size_t len, int indent, bool array)
+{
+       struct blob_attr *attr;
+       struct blobmsg_hdr *hdr;
+
+       indent_printf(indent, "{\n");
+       __blob_for_each_attr(attr, head, len) {
+               hdr = blob_data(attr);
+               if (!array)
+                       indent_printf(indent + 1, "%s : ", hdr->name);
+               dump_attr_data(attr, 0, indent + 1);
+       }
+       indent_printf(indent, "}\n");
+}
+
+static void dump_attr_data(struct blob_attr *data, int indent, int next_indent)
+{
+       int type = blobmsg_type(data);
+       switch(type) {
+       case BLOBMSG_TYPE_STRING:
+               indent_printf(indent, "%s\n", blobmsg_get_string(data));
+               break;
+       case BLOBMSG_TYPE_INT8:
+               indent_printf(indent, "%d\n", blobmsg_get_u8(data));
+               break;
+       case BLOBMSG_TYPE_INT16:
+               indent_printf(indent, "%d\n", blobmsg_get_u16(data));
+               break;
+       case BLOBMSG_TYPE_INT32:
+               indent_printf(indent, "%d\n", blobmsg_get_u32(data));
+               break;
+       case BLOBMSG_TYPE_INT64:
+               indent_printf(indent, "%"PRIu64"\n", blobmsg_get_u64(data));
+               break;
+       case BLOBMSG_TYPE_DOUBLE:
+               indent_printf(indent, "%lf\n", blobmsg_get_double(data));
+               break;
+       case BLOBMSG_TYPE_TABLE:
+       case BLOBMSG_TYPE_ARRAY:
+               if (!indent)
+                       indent_printf(indent, "\n");
+               dump_table(blobmsg_data(data), blobmsg_data_len(data),
+                          next_indent, type == BLOBMSG_TYPE_ARRAY);
+               break;
+       }
+}
+
+enum {
+       FOO_MESSAGE,
+       FOO_LIST,
+       FOO_TESTDATA
+};
+
+static const struct blobmsg_policy pol[] = {
+       [FOO_MESSAGE] = {
+               .name = "message",
+               .type = BLOBMSG_TYPE_STRING,
+       },
+       [FOO_LIST] = {
+               .name = "list",
+               .type = BLOBMSG_TYPE_ARRAY,
+       },
+       [FOO_TESTDATA] = {
+               .name = "testdata",
+               .type = BLOBMSG_TYPE_TABLE,
+       },
+};
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+#endif
+
+static void dump_message(struct blob_buf *buf)
+{
+       struct blob_attr *tb[ARRAY_SIZE(pol)];
+
+       if (blobmsg_parse(pol, ARRAY_SIZE(pol), tb, blob_data(buf->head), blob_len(buf->head)) != 0) {
+               fprintf(stderr, "Parse failed\n");
+               return;
+       }
+       if (tb[FOO_MESSAGE])
+               fprintf(stderr, "Message: %s\n", (char *) blobmsg_data(tb[FOO_MESSAGE]));
+
+       if (tb[FOO_LIST]) {
+               fprintf(stderr, "List: ");
+               dump_table(blobmsg_data(tb[FOO_LIST]), blobmsg_data_len(tb[FOO_LIST]), 0, true);
+       }
+       if (tb[FOO_TESTDATA]) {
+               fprintf(stderr, "Testdata: ");
+               dump_table(blobmsg_data(tb[FOO_TESTDATA]), blobmsg_data_len(tb[FOO_TESTDATA]), 0, false);
+       }
+}
+
+static void
+fill_message(struct blob_buf *buf)
+{
+       void *tbl;
+
+       blobmsg_add_string(buf, "message", "Hello, world!");
+
+       tbl = blobmsg_open_table(buf, "testdata");
+       blobmsg_add_double(buf, "double", 1.337e2);
+       blobmsg_add_u32(buf, "hello", 1);
+       blobmsg_add_string(buf, "world", "2");
+       blobmsg_close_table(buf, tbl);
+
+       tbl = blobmsg_open_array(buf, "list");
+       blobmsg_add_u32(buf, NULL, 0);
+       blobmsg_add_u32(buf, NULL, 1);
+       blobmsg_add_u32(buf, NULL, 2);
+       blobmsg_add_double(buf, "double", 1.337e2);
+       blobmsg_close_table(buf, tbl);
+}
+
+int main(int argc, char **argv)
+{
+       char *json = NULL;
+       static struct blob_buf buf;
+
+       blobmsg_buf_init(&buf);
+       fill_message(&buf);
+       dump_message(&buf);
+
+       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 (file)
index 0000000..6d93059
--- /dev/null
@@ -0,0 +1,84 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <json.h>
+#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] <filename_json_script>\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 (file)
index 0000000..cb0f231
--- /dev/null
@@ -0,0 +1,91 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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; i<ARRAY_SIZE(vals); i++) {
+               struct item *e = malloc(sizeof(struct item));
+               e->name = 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/tests/test-runqueue.c b/tests/test-runqueue.c
new file mode 100644 (file)
index 0000000..13ab864
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * runqueue-example.c
+ *
+ * Copyright (C) 2013 Felix Fietkau <nbd@openwrt.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "uloop.h"
+#include "runqueue.h"
+
+static struct runqueue q;
+
+struct sleeper {
+       struct runqueue_process proc;
+       int val;
+};
+
+static void q_empty(struct runqueue *q)
+{
+       fprintf(stderr, "All done!\n");
+       uloop_end();
+}
+
+static void q_sleep_run(struct runqueue *q, struct runqueue_task *t)
+{
+       struct sleeper *s = container_of(t, struct sleeper, proc.task);
+       char str[32];
+       pid_t pid;
+
+       fprintf(stderr, "[%d/%d] start 'sleep %d'\n", q->running_tasks, q->max_running_tasks, s->val);
+
+       pid = fork();
+       if (pid < 0)
+               return;
+
+       if (pid) {
+               runqueue_process_add(q, &s->proc, pid);
+               return;
+       }
+
+       sprintf(str, "%d", s->val);
+       execlp("sleep", "sleep", str, NULL);
+       exit(1);
+}
+
+static void q_sleep_cancel(struct runqueue *q, struct runqueue_task *t, int type)
+{
+       struct sleeper *s = container_of(t, struct sleeper, proc.task);
+
+       fprintf(stderr, "[%d/%d] cancel 'sleep %d'\n", q->running_tasks, q->max_running_tasks, s->val);
+       runqueue_process_cancel_cb(q, t, type);
+}
+
+static void q_sleep_complete(struct runqueue *q, struct runqueue_task *p)
+{
+       struct sleeper *s = container_of(p, struct sleeper, proc.task);
+
+       fprintf(stderr, "[%d/%d] finish 'sleep %d'\n", q->running_tasks, q->max_running_tasks, s->val);
+       free(s);
+}
+
+static void add_sleeper(int val)
+{
+       static const struct runqueue_task_type sleeper_type = {
+               .run = q_sleep_run,
+               .cancel = q_sleep_cancel,
+               .kill = runqueue_process_kill_cb,
+       };
+       struct sleeper *s;
+
+       s = calloc(1, sizeof(*s));
+       s->proc.task.type = &sleeper_type;
+       s->proc.task.run_timeout = 500;
+       s->proc.task.complete = q_sleep_complete;
+       s->val = val;
+       runqueue_task_add(&q, &s->proc.task, false);
+}
+
+int main(int argc, char **argv)
+{
+       uloop_init();
+
+       runqueue_init(&q);
+       q.empty_cb = q_empty;
+       q.max_running_tasks = 1;
+
+       if (argc > 1)
+               q.max_running_tasks = atoi(argv[1]);
+
+       add_sleeper(1);
+       add_sleeper(1);
+       add_sleeper(1);
+       uloop_run();
+       uloop_done();
+
+       return 0;
+}