From 09b28beef413a18d17c7cb99eadbc7c2ac729f5e Mon Sep 17 00:00:00 2001 From: Jakob Olsson <jakob.olsson@iopsys.eu> Date: Mon, 30 Sep 2019 14:52:38 +0200 Subject: [PATCH] add object, string and integer manipulation via string format --- CMakeLists.txt | 56 +++++++ api.c | 261 +++++++++++++++++++++++++++++++++ api.h | 16 ++ cmake/modules/FindCMocka.cmake | 26 ++++ test/CMakeLists.txt | 26 ++++ test/api_test.c | 257 ++++++++++++++++++++++++++++++++ 6 files changed, 642 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 api.c create mode 100644 api.h create mode 100644 cmake/modules/FindCMocka.cmake create mode 100644 test/CMakeLists.txt create mode 100644 test/api_test.c diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..605d998 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,56 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 3.0) +PROJECT(json-editor) + +#set version +SET(MAJOR_VERSION 1) +SET(MINOR_VERSION 0) +SET(PATCH_VERSION 0) +#EXEC_PROGRAM("git" ${CMAKE_CURRENT_SOURCE_DIR} ARGS "log HEAD --format=format:%H -1" OUTPUT_VARIABLE GIT_SHA1) + +#ADD_DEFINITIONS(-D_PROJECT_NAME="${PROJECT_NAME}") +#ADD_DEFINITIONS(-D_PROJECT_VERSION="${MAJOR_VERSION}.${MINOR_VERSION}.${PATCH_VERSION}") +#ADD_DEFINITIONS(-D_GIT_SHA1="${GIT_SHA1}") + +#set compile flags +SET(DEFAULT_FLAGS "--std=gnu99") +SET(RELEASE_FLAGS "-Wno-long-long -Wundef -Wcast-align -Wchar-subscripts -Wall -W -Werror-implicit-function-declaration -Wpointer-arith -Wwrite-strings -Wformat-security -Wmissing-format-attribute -fno-common -Wsign-compare -Wunused-result") + +SET(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} ${DEFAULT_FLAGS} -Wall") +SET(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -Os ${DEFAULT_FLAGS} ${RELEASE_FLAGS}") +SET(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} ${DEFAULT_FLAGS} ${RELEASE_FLAGS}") +SET(CMAKE_C_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_MINSIZEREL} ${DEFAULT_FLAGS} ${RELEASE_FLAGS}") + +#INCLUDE(LogOptions.cmake) + +#create executable +FILE(GLOB SOURCES "*.c" "*.h") +ADD_EXECUTABLE(${PROJECT_NAME} ${SOURCES}) + +#link libraries +SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/modules") +find_library(JSON_LIBRARIES NAMES json-c) +TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${JSON_LIBRARIES}) + +#testing +IF(CMAKE_BUILD_TYPE STREQUAL Debug) + OPTION(ENABLE_BUILD_TESTS "Build tests" ON) +# OPTION(ENABLE_VALGRIND_TESTS "Build tests with valgrind" ON) +ELSE() + OPTION(ENABLE_BUILD_TESTS "Build tests" OFF) +# OPTION(ENABLE_VALGRIND_TESTS "Build tests with valgrind" OFF) +ENDIF() + +IF(ENABLE_BUILD_TESTS) + FIND_PACKAGE(CMocka) + if(CMOCKA_FOUND) + ADD_LIBRARY(${PROJECT_NAME}-api SHARED ${SOURCES}) + MESSAGE("-- Building tests") + ENABLE_TESTING() + ADD_SUBDIRECTORY(test) + ELSE(CMOCKA_FOUND) + MESSAGE("-- CMocka not found") + ENDIF(CMOCKA_FOUND) +ENDIF(ENABLE_BUILD_TESTS) + +#install +#INSTALL(TARGETS imonitor RUNTIME DESTINATION bin) diff --git a/api.c b/api.c new file mode 100644 index 0000000..d697ed5 --- /dev/null +++ b/api.c @@ -0,0 +1,261 @@ +#include <unistd.h> +#include <string.h> +#include <stdio.h> +#include <stdbool.h> +#include <pthread.h> + +#include <libubox/blobmsg.h> +#include <libubox/blobmsg_json.h> +#include <json-c/json.h> + +#include "api.h" + +/* will allocate memory! remember to free! */ +char *get_file(const char *path) +{ + FILE *f; + size_t len, nread; + char *buffer; + + f = fopen(path, "r"); + if (!f) + goto out; + + if (fseek(f, 0, SEEK_END)) + goto out_close; + + len = ftell(f); + if (len < 0) + goto out_close; + + if (fseek(f, 0, SEEK_SET)) + goto out_close; + + buffer = calloc(1, len); + if (!buffer) + goto out_close; + + nread = fread(buffer, sizeof(char), len, f); + if (nread < len) + goto out_free; + + fclose(f); + return buffer; +out_free: + free(buffer); +out_close: + fclose(f); +out: + return NULL; +} + +struct json_object *path_to_obj(const char *path) +{ + struct json_object *obj; + char *file_str; + + file_str = get_file(path); + if (!file_str) + goto out; + + obj = json_tokener_parse(file_str); + + free(file_str); + return obj; +out: + return NULL; +} + +int set_by_string(char *fmt, struct json_object **src, char *val, enum json_type type) +{ + struct json_object *ptr, *tmp, *tar; + const char *delimiter = "."; + char buffer[1024] = {0}; + char *p, *prev = fmt; + + if (!*src) { + printf("lets allocate new object\n"); + *src = json_object_new_object(); + } + + ptr = tmp = *src; + + strcpy(buffer, fmt); + + p = strtok(buffer,delimiter); + while (p != NULL) { + prev = p; + p = strtok(NULL, delimiter); + + /* if next key exists, parse prev key, let switch-case add/alter last key */ + if (p) { + json_object_object_get_ex(tmp, prev, &ptr); + + printf("p=%s\n", p); + + /* TODO: if prev contains [x], get idx of x */ + char *open_brace = strchr(prev, '['); + printf("open_brace=%s\n", open_brace); + if (open_brace) { + char *close_brace = strchr(open_brace, ']'); + if (close_brace) { + int len = close_brace - open_brace; + printf("len=%d\n", len); + char *idx_str = calloc(1, len); + strncpy(idx_str, open_brace + 1, len-1); + printf("idx_str = %s\n", idx_str); + int idx = atoi(idx_str); + printf("idx = %d\n", idx); + *open_brace = '\0'; + printf("prev=%s\n", prev); + + if (ptr && json_object_get_type(ptr) == json_type_array) { + ptr = json_object_array_get_idx(ptr, idx); + } else { + ptr = json_object_new_array(); + json_object_object_add(tmp, prev, ptr); + } + } + } + + /* create prev object if it does not exist */ + if (!ptr) { + ptr = json_object_new_object(); + json_object_object_add(tmp, prev, ptr); + } + } + + tmp = ptr; + } + + json_object_object_get_ex(ptr, prev, &tar); + + switch(type) { + case json_type_object: + { + struct json_object *parsed_val = json_tokener_parse(val); + + if (!parsed_val) { + fprintf(stderr, "Invalid object format\n"); + return -1; + } else if (type != json_object_get_type(parsed_val)) { + fprintf(stderr, "Types don't match!\n"); + return -1; + } + + json_object_object_foreach(parsed_val, key1, val1) + json_object_object_add(*src, key1, val1); + } + break; + case json_type_string: + json_object_object_add(ptr, prev, json_object_new_string(val)); + break; + case json_type_int: + json_object_object_add(ptr, prev, json_object_new_int(atoi(val))); + break; + case json_type_array: + { + struct json_object *parsed_val = json_tokener_parse(val); + + if (!parsed_val) { + fprintf(stderr, "Invalid array format\n"); + return -1; + } else if (type != json_object_get_type(parsed_val)) { + fprintf(stderr, "Types don't match!\n"); + return -1; + } + + json_object_object_add(ptr, prev, parsed_val); + } + break; + case json_type_double: + json_object_object_add(ptr, prev, json_object_new_int(atof(val))); + break; + case json_type_boolean: + { + bool bool_val = false; + + if (strncasecmp(val, "true", 4) || atoi(val) == 1) + bool_val = true; + + + json_object_object_add(ptr, prev, json_object_new_boolean(bool_val)); + } + break; + default: + fprintf(stderr, "Not valid input type!\n"); + break; + } + + return 0; +} + +int set(char *fmt, struct json_object *src, struct json_object *val) +{ + const char *delimiter = "."; + char buffer[1024] = {0}; + char *p; + + if (json_object_get_type(val) != json_type_object) { + fprintf(stderr, "object must be of type object!\n"); + return -1; + } + + src = get(fmt, src); + + strcpy(buffer, fmt); + + /* TODO: put this in some separate function */ + for (p = strtok(buffer,delimiter); p != NULL; p = strtok(NULL, delimiter)) { + struct json_object *ptr, *tmp = src; + + json_object_object_get_ex(tmp, p, &ptr); + if (!ptr) { + ptr = json_object_new_object(); + json_object_object_add(tmp, p, ptr); + } + + tmp = ptr; + } + + json_object_object_foreach(val, key, val1) + json_object_object_add(src, key, val1); + + return 0; +} + +struct json_object *get(char *fmt, struct json_object *src) +{ + struct json_object *ptr, *tmp = src; + const char *delimiter = "."; + char buffer[1024] = {0}; + + strcpy(buffer, fmt); + + for (char *p = strtok(buffer,delimiter); p != NULL; p = strtok(NULL, delimiter)) { + json_object_object_get_ex(tmp, p, &ptr); + tmp = ptr; + } + + return ptr; +} + +int main() +{ + struct json_object *obj = path_to_obj("/home/jakob/git/json-editor-api/test.json"); + struct json_object *ptr; + + printf("%s %d obj=%s\n", __func__, __LINE__, json_object_get_string(obj)); + + ptr = get("nested", obj); + + printf("%s\n", json_object_get_string(ptr)); + + set_by_string("nested.api[1].test", &obj, "1", json_type_string); + + printf("%s\n", json_object_get_string(obj)); + + json_object_put(obj); + return 0; +} + diff --git a/api.h b/api.h new file mode 100644 index 0000000..418688c --- /dev/null +++ b/api.h @@ -0,0 +1,16 @@ +#include <unistd.h> +#include <string.h> +#include <stdio.h> +#include <stdbool.h> +#include <pthread.h> + +#include <libubox/blobmsg.h> +#include <libubox/blobmsg_json.h> +#include <json-c/json.h> + +char *get_file(const char *path); +struct json_object *path_to_obj(const char *path); +int set_by_string(char *fmt, struct json_object **src, char *val, enum json_type type); +int set(char *fmt, struct json_object *src, struct json_object *val); + +struct json_object *get(char *fmt, struct json_object *src); diff --git a/cmake/modules/FindCMocka.cmake b/cmake/modules/FindCMocka.cmake new file mode 100644 index 0000000..65e12fd --- /dev/null +++ b/cmake/modules/FindCMocka.cmake @@ -0,0 +1,26 @@ +# CMOCKA_FOUND - System has CMocka +# CMOCKA_INCLUDE_DIRS - The CMocka include directories +# CMOCKA_LIBRARIES - The libraries needed to use CMocka +# CMOCKA_DEFINITIONS - Compiler switches required for using CMocka + +find_package(PkgConfig QUIET) +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_CMOCKA QUIET cmocka) + set(CMOCKA_DEFINITIONS ${PC_CMOCKA_CFLAGS_OTHER}) +endif() + +find_path(CMOCKA_INCLUDE_DIR cmocka.h + HINTS ${PC_CMOCKA_INCLUDEDIR} ${PC_CMOCKA_INCLUDE_DIRS} + PATH_SUFFIXES cmocka) + +find_library(CMOCKA_LIBRARY NAMES cmocka + HINTS ${PC_CMOCKA_LIBDIR} ${PC_CMOCKA_LIBRARY_DIRS}) + +set(CMOCKA_LIBRARIES ${CMOCKA_LIBRARY}) +set(CMOCKA_INCLUDE_DIRS ${CMOCKA_INCLUDE_DIR}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(cmocka DEFAULT_MSG + CMOCKA_LIBRARY CMOCKA_INCLUDE_DIR) + +mark_as_advanced(CMOCKA_INCLUDE_DIR CMOCKA_LIBRARY) \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..9df0139 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,26 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 3.0) +#INCLUDE_DIRECTORIES ("${PROJECT_SOURCE_DIR}/src") +INCLUDE_DIRECTORIES ("${PROJECT_SOURCE_DIR}") +#FILE(COPY files DESTINATION .) + +find_library(JSON_LIBRARIES NAMES json-c) + +SET(api_tests api_test) +FOREACH(test_name IN LISTS api_tests) + ADD_EXECUTABLE(${test_name} ${test_name}.c) + TARGET_LINK_LIBRARIES( + ${test_name} + ${CMOCKA_LIBRARIES} + json-editor-api + ${JSON_LIBRARIES} + ) + ADD_TEST(NAME ${test_name} COMMAND $<TARGET_FILE:${test_name}>) +ENDFOREACH(test_name) + +#SET(COVERAGE_EXCLUDES 'src/main.c' '/usr/include/*') + +#SETUP_TARGET_FOR_COVERAGE( +# NAME test_coverage +# EXECUTABLE ctest +# DEPENDENCIES owsd-api +#) diff --git a/test/api_test.c b/test/api_test.c new file mode 100644 index 0000000..f7af316 --- /dev/null +++ b/test/api_test.c @@ -0,0 +1,257 @@ +#include <stdarg.h> +#include <stddef.h> +#include <setjmp.h> +#include <cmocka.h> +#include <json-c/json.h> +#include <libwebsockets.h> + +#include "api.h" + +struct json_object *file_obj, *modify_obj; + +static void test_cfg_parse_success(void **state) +{ + (void) state; /* unused */ + + struct json_object *file = path_to_obj("/home/jakob/git/json-editor-api/test.json"); + + struct json_object *obj = json_object_new_object(); + struct json_object *tot = json_object_new_object(); + + json_object_object_add(obj, "api", json_object_new_string("test2")); + json_object_object_add(tot, "nested", obj); + json_object_object_add(tot, "test", json_object_new_string("success")); + + assert_int_equal(1, json_object_equal(file, tot)); + +} + +static void test_cfg_parse_fail(void **state) +{ + (void) state; /* unused */ + + struct json_object *obj = path_to_obj("NON_EXISTENT_FILE"); + assert_null(obj); + if (obj) + json_object_put(obj); +} + +static void test_build_from_scratch(void **state) +{ + (void) state; /* unused */ + + struct json_object *file = path_to_obj("/home/jakob/git/json-editor-api/test.json"); + struct json_object *jobj = NULL; + + set_by_string("", &jobj, "{ \"test\":\"success\", \"nested\": { \"api\":\"test2\"} }", json_type_object); + + assert_int_equal(1, json_object_equal(file, jobj)); + +} + +static void test_json_add_object(void **state) +{ + (void) state; + + //struct json_object *obj = json_object_new_object(); + json_object_object_add(file_obj, "test2", json_object_new_string("success")); + //json_object_object_add(file_obj, "string", json_object_new_string("1")); + + set_by_string("string", &modify_obj, "{\"test2\":\"success\"}", json_type_object); + + printf("file_obj=%s\n", json_object_get_string(file_obj)); + printf("modify_obj=%s\n", json_object_get_string(modify_obj)); + + assert_int_equal(1, json_object_equal(modify_obj, file_obj)); +} + +static void test_json_add_array(void **state) +{ + (void) state; + + struct json_object *arr = json_object_new_array(); + + json_object_array_add(arr, json_object_new_int(1)); + json_object_array_add(arr, json_object_new_int(2)); + json_object_array_add(arr, json_object_new_int(3)); + json_object_object_add(file_obj, "ints", arr); + //json_object_object_add(file_obj, "string", json_object_new_string("1")); + + set_by_string("ints", &modify_obj, "[ 1, 2, 3 ]", json_type_array); + + printf("file_obj=%s\n", json_object_get_string(file_obj)); + printf("modify_obj=%s\n", json_object_get_string(modify_obj)); + + assert_int_equal(1, json_object_equal(modify_obj, file_obj)); +} + +static void test_json_add_multi_types(void **state) +{ + (void) state; + + struct json_object *arr = json_object_new_array(); + struct json_object *obj = json_object_new_object(); + struct json_object *nested = json_object_new_object(); + + json_object_object_add(nested, "integer", json_object_new_int(1)); + json_object_object_add(obj, "nested1", nested); + json_object_object_add(file_obj, "nested0", obj); + + + json_object_array_add(arr, json_object_new_int(1)); + json_object_array_add(arr, json_object_new_int(2)); + json_object_array_add(arr, json_object_new_int(3)); + json_object_object_add(file_obj, "ints", arr); + + json_object_object_add(file_obj, "string", json_object_new_string("1")); + json_object_object_add(file_obj, "integer", json_object_new_int(1)); + + set_by_string("nested0.nested1.integer", &modify_obj, "1", json_type_int); + set_by_string("ints", &modify_obj, "[ 1, 2, 3 ]", json_type_array); + set_by_string("string", &modify_obj, "1", json_type_string); + set_by_string("integer", &modify_obj, "1", json_type_int); + + printf("file_obj=%s\n", json_object_get_string(file_obj)); + printf("modify_obj=%s\n", json_object_get_string(modify_obj)); + + assert_int_equal(1, json_object_equal(modify_obj, file_obj)); +} + +static void test_json_add_multi_obj(void **state) +{ + (void) state; + + struct json_object *arr = json_object_new_array(); + struct json_object *obj = json_object_new_object(); + struct json_object *nested = json_object_new_object(); + + json_object_object_add(nested, "integer", json_object_new_int(1)); + json_object_object_add(obj, "nested1", nested); + json_object_object_add(file_obj, "nested0", obj); + + + json_object_array_add(arr, json_object_new_int(1)); + json_object_array_add(arr, json_object_new_int(2)); + json_object_array_add(arr, json_object_new_int(3)); + json_object_object_add(file_obj, "ints", arr); + + json_object_object_add(file_obj, "string", json_object_new_string("1")); + json_object_object_add(file_obj, "integer", json_object_new_int(1)); + + set_by_string("", &modify_obj, "{ \"nested0\": {\"nested1\": {\"integer\": 1}}, \"ints\": [1, 2, 3], \"string\":\"1\", \"integer\": 1}", json_type_object); + + printf("file_obj=%s\n", json_object_get_string(file_obj)); + printf("modify_obj=%s\n", json_object_get_string(modify_obj)); + + assert_int_equal(1, json_object_equal(modify_obj, file_obj)); +} + +static void test_json_add_string(void **state) +{ + (void) state; + + //struct json_object *obj = json_object_new_object(); + + json_object_object_add(file_obj, "string", json_object_new_string("1")); + + set_by_string("string", &modify_obj, "1", json_type_string); + + printf("file_obj=%s\n", json_object_get_string(file_obj)); + printf("modify_obj=%s\n", json_object_get_string(modify_obj)); + + assert_int_equal(1, json_object_equal(modify_obj, file_obj)); +} + + +static void test_json_overwrite_string(void **state) +{ + (void) state; + + //struct json_object *obj = json_object_new_object(); + + json_object_object_add(file_obj, "test", json_object_new_string("1")); + set_by_string("test", &modify_obj, "1", json_type_string); + + printf("file_obj = %s\n", json_object_get_string(file_obj)); + printf("modify_obj = %s\n", json_object_get_string(modify_obj)); + assert_int_equal(1, json_object_equal(modify_obj, file_obj)); + + //json_object_object_add(file_obj, "test", json_object_new_string("1")); + struct json_object *obj, *nested; + + json_object_object_get_ex(file_obj, "nested", &nested); + json_object_object_get_ex(nested, "api", &obj); + json_object_set_string(obj, "2"); + + set_by_string("nested.api", &modify_obj, "2", json_type_string); + + printf("file_obj=%s\n", json_object_get_string(file_obj)); + printf("modify_obj=%s\n", json_object_get_string(modify_obj)); + + assert_int_equal(1, json_object_equal(modify_obj, file_obj)); +} + +static void test_json_add_int(void **state) +{ + (void) state; + + //struct json_object *obj = json_object_new_object(); + + json_object_object_add(file_obj, "integer", json_object_new_int(1)); + + set_by_string("integer", &modify_obj, "1", json_type_int); + + printf("file_obj=%s\n", json_object_get_string(file_obj)); + printf("modify_obj=%s\n", json_object_get_string(modify_obj)); + + assert_int_equal(1, json_object_equal(modify_obj, file_obj)); +} + +static void test_json_add_int_nested(void **state) +{ + (void) state; + + struct json_object *obj = json_object_new_object(); + struct json_object *nested = json_object_new_object(); + + json_object_object_add(nested, "integer", json_object_new_int(1)); + json_object_object_add(obj, "nested1", nested); + json_object_object_add(file_obj, "nested0", obj); + + set_by_string("nested0.nested1.integer", &modify_obj, "1", json_type_int); + + printf("file_obj=%s\n", json_object_get_string(file_obj)); + printf("modify_obj=%s\n", json_object_get_string(modify_obj)); + + assert_int_equal(1, json_object_equal(modify_obj, file_obj)); +} + +static int setup (void** state) { + file_obj = path_to_obj("/home/jakob/git/json-editor-api/test.json"); + modify_obj = path_to_obj("/home/jakob/git/json-editor-api/test.json"); + return 0; +} + +static int teardown (void** state) { + json_object_put(file_obj); + json_object_put(modify_obj); + return 0; +} + +int main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_cfg_parse_success), + cmocka_unit_test(test_cfg_parse_fail), + cmocka_unit_test(test_build_from_scratch), + cmocka_unit_test_setup_teardown(test_json_add_int, setup, teardown), + cmocka_unit_test_setup_teardown(test_json_add_int_nested, setup, teardown), + cmocka_unit_test_setup_teardown(test_json_add_string, setup, teardown), + cmocka_unit_test_setup_teardown(test_json_add_object, setup, teardown), + cmocka_unit_test_setup_teardown(test_json_add_array, setup, teardown), + cmocka_unit_test_setup_teardown(test_json_add_multi_types, setup, teardown), + cmocka_unit_test_setup_teardown(test_json_add_multi_obj, setup, teardown), + cmocka_unit_test_setup_teardown(test_json_overwrite_string, setup, teardown), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} -- GitLab