diff --git a/.gitignore b/.gitignore index 65fa89b6..921d8446 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,42 @@ libcjson_utils.so.* cmake-build-debug *.lst *.lss + +# Bazel +MODULE.bazel.lock +# Ignore backup files. +*~ +# Ignore Vim swap files. +.*.swp +# macOS-specific excludes +.DS_Store +# Ignore files generated by IDEs. +/.aswb/ +/.bazelbsp/ +/.cache/ +/.classpath +/.clwb/ +/.factorypath +/.idea/ +/.ijwb/ +/.project +/.settings +/.vscode/ +.eclipse/ +.settings/ +.classpath +.project +eclipse-*bin/ +/bazel.iml +# Ignore all bazel-* symlinks. There is no full list since this can change +# based on the name of the directory bazel is cloned into. +/bazel-* +# Ignore outputs generated during Bazel bootstrapping. +/output/ +# Ignore jekyll build output. +/production +/.sass-cache +# Bazelisk version file +.bazelversion +# User-specific .bazelrc +user.bazelrc diff --git a/BUILD.bazel b/BUILD.bazel new file mode 100644 index 00000000..60e3a76f --- /dev/null +++ b/BUILD.bazel @@ -0,0 +1,76 @@ +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") + +CJSON_COPTS = select({ + "@bazel_tools//src/conditions:darwin": [ + "-std=c89", # Standards compliance + "-Wc++-compat", # C++ compatibility + "-fvisibility=hidden", # Symbol visibility + ], + "@bazel_tools//src/conditions:linux": [ + "-std=c89", # Standards compliance + "-Wc++-compat", # C++ compatibility + "-fstack-protector-strong", # Security (GCC/Clang on Linux supports this) + "-fvisibility=hidden", # Symbol visibility + ], + "@bazel_tools//src/conditions:windows": [ + "/Za", # Equivalent to -std=c89 (enforce ANSI C) + "/W3", # Moderate warning level (instead of -Wc++-compat) + "/GS", # Stack protection (equivalent to -fstack-protect-strong) + ], +}) + +cc_library( + name = "cjson", + srcs = ["cJSON.c"], + hdrs = ["cJSON.h"], + copts = CJSON_COPTS + [ + "-DCJSON_EXPORT_SYMBOLS", + "-DCJSON_API_VISIBILITY", + ], + linkopts = select({ + "@bazel_tools//src/conditions:windows": [], + "//conditions:default": ["-lm"], # Link math library on non-Windows + }), + visibility = ["//visibility:public"], +) + +cc_library( + name = "cjson_utils", + srcs = ["cJSON_Utils.c"], + hdrs = ["cJSON_Utils.h"], + copts = CJSON_COPTS + [ + "-DCJSON_EXPORT_SYMBOLS", + "-DCJSON_API_VISIBILITY", + ], + visibility = ["//visibility:public"], + deps = [":cjson"], +) + +cc_library( + name = "cjson_tests", # Some tests directly include the .c file + hdrs = ["cJSON.c"], + visibility = ["//tests:__pkg__"], +) + +cc_test( + name = "cjson_test", + size = "small", + srcs = ["test.c"], + copts = CJSON_COPTS + select({ + "@platforms//os:windows": [], + "//conditions:default": [ + "-fno-sanitize=float-divide-by-zero", + ], + }), + deps = [":cjson"], +) + +test_suite( + name = "all_tests", + tests = [ + ":cjson_test", + "//tests:json_patch_tests", + "//tests:parse_examples", + "//tests:unity_tests", + ], +) diff --git a/MODULE.bazel b/MODULE.bazel new file mode 100644 index 00000000..06f600de --- /dev/null +++ b/MODULE.bazel @@ -0,0 +1,14 @@ +""" +Module: cjson +Purpose: Provides the cjson library compileable as a Bazel target. Includes general and unity tests through Bazel +Note: cjson_utils target is also available +""" + +module( + name = "cjson", + version = "1.7.19-0.20240923110858-12c4bf1986c2", + compatibility_level = 1, +) + +bazel_dep(name = "rules_cc", version = "0.1.1") +bazel_dep(name = "platforms", version = "0.0.11") diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel new file mode 100644 index 00000000..baed64da --- /dev/null +++ b/tests/BUILD.bazel @@ -0,0 +1,70 @@ +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") +load(":tools.bzl", "test_set") + +DEPS = [ + "//:cjson", + "//:cjson_tests", + "//:cjson_utils", + "//tests/unity", + "//tests/unity:unity-examples", +] + select({ + "@platforms//os:windows": [":unity-win"], + "//conditions:default": [], +}) + +DATA = [ + ":patch-test_inputs", + ":test_inputs", +] + +HDRS = glob(["*.h"]) + +cc_library( + name = "unity-win", + srcs = ["unity_setup.c"], +) + +filegroup( + name = "test_inputs", + srcs = glob(["inputs/*"]), + visibility = ["//tests:__pkg__"], +) + +filegroup( + name = "patch-test_inputs", + srcs = glob(["json-patch-tests/*.json"]), + visibility = ["//tests:__pkg__"], +) + +test_suite( + name = "unity_tests", + tests = test_set( + srcs = HDRS, + data = DATA, + test_files = glob( + ["*.c"], + exclude = [ + "unity_setup.c", + "parse_examples.c", + "json_patch_tests.c", + ], + ), + deps = DEPS, + ), +) + +cc_test( + name = "parse_examples", + srcs = HDRS + ["parse_examples.c"], + copts = ['-DTEST_DIR_PATH=\\"tests/inputs/\\"'], + data = DATA, + deps = DEPS, +) + +cc_test( + name = "json_patch_tests", + srcs = HDRS + ["json_patch_tests.c"], + copts = ['-DTEST_DIR_PATH=\\"tests/json-patch-tests/\\"'], + data = DATA, + deps = DEPS, +) diff --git a/tests/json_patch_tests.c b/tests/json_patch_tests.c index 7c3d6aed..731da6e1 100644 --- a/tests/json_patch_tests.c +++ b/tests/json_patch_tests.c @@ -29,6 +29,10 @@ #include "common.h" #include "../cJSON_Utils.h" +#ifndef TEST_DIR_PATH +#define TEST_DIR_PATH "json-patch-tests/" +#endif + static cJSON *parse_test_file(const char * const filename) { char *file = NULL; @@ -182,7 +186,7 @@ static cJSON_bool test_generate_test(cJSON *test) static void cjson_utils_should_pass_json_patch_test_tests(void) { - cJSON *tests = parse_test_file("json-patch-tests/tests.json"); + cJSON *tests = parse_test_file(TEST_DIR_PATH "tests.json"); cJSON *test = NULL; cJSON_bool failed = false; @@ -199,7 +203,7 @@ static void cjson_utils_should_pass_json_patch_test_tests(void) static void cjson_utils_should_pass_json_patch_test_spec_tests(void) { - cJSON *tests = parse_test_file("json-patch-tests/spec_tests.json"); + cJSON *tests = parse_test_file(TEST_DIR_PATH "spec_tests.json"); cJSON *test = NULL; cJSON_bool failed = false; @@ -216,7 +220,7 @@ static void cjson_utils_should_pass_json_patch_test_spec_tests(void) static void cjson_utils_should_pass_json_patch_test_cjson_utils_tests(void) { - cJSON *tests = parse_test_file("json-patch-tests/cjson-utils-tests.json"); + cJSON *tests = parse_test_file(TEST_DIR_PATH "cjson-utils-tests.json"); cJSON *test = NULL; cJSON_bool failed = false; diff --git a/tests/parse_examples.c b/tests/parse_examples.c index d35d6cfb..1e12e89b 100644 --- a/tests/parse_examples.c +++ b/tests/parse_examples.c @@ -58,7 +58,9 @@ static void do_test(const char *test_name) test_name_length = strlen(test_name); /* allocate file paths */ +#ifndef TEST_DIR_PATH #define TEST_DIR_PATH "inputs/" +#endif test_path = (char*)malloc(sizeof(TEST_DIR_PATH) + test_name_length); TEST_ASSERT_NOT_NULL_MESSAGE(test_path, "Failed to allocate test_path buffer."); expected_path = (char*)malloc(sizeof(TEST_DIR_PATH) + test_name_length + sizeof(".expected")); @@ -136,7 +138,12 @@ static void file_test6_should_not_be_parsed(void) char *test6 = NULL; cJSON *tree = NULL; - test6 = read_file("inputs/test6"); + char *test_path = NULL; + const char * test_name = "test6"; + test_path = (char*)malloc(sizeof(TEST_DIR_PATH) + strlen(test_name)); + TEST_ASSERT_NOT_NULL_MESSAGE(test_path, "Failed to allocate test_path buffer."); + sprintf(test_path, TEST_DIR_PATH"%s", test_name); + test6 = read_file(test_path); TEST_ASSERT_NOT_NULL_MESSAGE(test6, "Failed to read test6 data."); tree = cJSON_Parse(test6); diff --git a/tests/tools.bzl b/tests/tools.bzl new file mode 100644 index 00000000..6098b6c5 --- /dev/null +++ b/tests/tools.bzl @@ -0,0 +1,44 @@ +"""Tool for generating C++ test targets efficiently using Bazel rules. +""" + +load("@rules_cc//cc:defs.bzl", "cc_test") + +def test_set( + test_files = None, + size = "small", + srcs = [], + file_extensions = ".c", + **kwargs): + """Creates C++ test targets from a list of test files. + + Args: + test_files: List of test file paths to process. Defaults to None. + size: Test size parameter for cc_test rule. Defaults to "small". + srcs: Additional source files to include in all tests. Defaults to empty list. + file_extensions: Expected extension of test files. Defaults to ".cpp". + **kwargs: Additional arguments to pass to cc_test rule. + + Returns: + List of test target names (e.g., [":test1", ":test2"]). + + Note: + Only files ending with the specified file_extensions are processed. + Each test target is created with the filename (without extension) as its name. + """ + test_targets = [] + + # Process positive tests + for file in test_files: + if not file.endswith(file_extensions): + continue + name = file[:-len(file_extensions)] + target = ":" + name + cc_test( + name = name, + size = size, + srcs = srcs + [file], + **kwargs + ) + test_targets.append(target) + + return test_targets diff --git a/tests/unity/BUILD.bazel b/tests/unity/BUILD.bazel new file mode 100644 index 00000000..e8c4d59a --- /dev/null +++ b/tests/unity/BUILD.bazel @@ -0,0 +1,27 @@ +load("@rules_cc//cc:defs.bzl", "cc_library") + +cc_library( + name = "unity", + srcs = glob(["src/*.c"]), + hdrs = glob(["src/*.h"]), + copts = select({ + "@platforms//os:windows": [ + "/wd4100", # Disable warning C4100: unreferenced formal parameter (similar to -Wno-error) + "/wd4065", # Disable warning C4065: switch statement contains default but no case (similar to -Wno-switch-enum) + ], + "//conditions:default": [ + "-fvisibility=default", + "-fno-sanitize=float-divide-by-zero", + "-Wno-switch-enum", + ], + }), + includes = ["src"], + visibility = ["//tests:__pkg__"], + deps = [":unity-examples"], +) + +cc_library( + name = "unity-examples", + hdrs = ["examples/unity_config.h"], + visibility = ["//tests:__pkg__"], +)