From 5dd6035dcd5fa585aa88deb280b1b119a1642fb8 Mon Sep 17 00:00:00 2001 From: Joshua Potter Date: Tue, 19 Dec 2023 16:58:19 -0700 Subject: [PATCH] Migrate to CMake. --- .githooks/pre-commit | 2 +- .gitignore | 24 +- CMakeLists.txt | 20 ++ Makefile | 81 ----- README.md | 51 +-- flake.nix | 3 +- src/CMakeLists.txt | 12 + src/config.c | 1 - src/dyn_array.c | 1 - src/error.c | 1 - src/string_buf.c | 1 - test/CMakeLists.txt | 106 +++++++ .../invalid_spec_json/{run.sh => runner} | 0 .../minimal_spec_json/{run.sh => runner} | 0 test/specs/no_spec_json/{run.sh => runner} | 0 test/sput.h | 300 ------------------ test/suites.c | 55 ---- test/test_cases.h | 9 + test/test_config.c | 122 +++++++ test/test_config.h | 103 ------ test/test_dyn_array.c | 111 +++++++ test/test_dyn_array.h | 54 ---- test/test_parser.c | 114 +++++++ test/test_parser.h | 84 ----- test/test_string_buf.c | 65 ++++ test/test_string_buf.h | 42 --- test/test_string_utils.c | 139 ++++++++ test/test_string_utils.h | 72 ----- test/test_validator.c | 287 +++++++++++++++++ test/test_validator.h | 260 --------------- 30 files changed, 1037 insertions(+), 1083 deletions(-) create mode 100644 CMakeLists.txt delete mode 100644 Makefile create mode 100644 src/CMakeLists.txt create mode 100644 test/CMakeLists.txt rename test/specs/invalid_spec_json/{run.sh => runner} (100%) mode change 100644 => 100755 rename test/specs/minimal_spec_json/{run.sh => runner} (100%) mode change 100644 => 100755 rename test/specs/no_spec_json/{run.sh => runner} (100%) mode change 100644 => 100755 delete mode 100644 test/sput.h delete mode 100644 test/suites.c create mode 100644 test/test_cases.h create mode 100644 test/test_config.c delete mode 100644 test/test_config.h create mode 100644 test/test_dyn_array.c delete mode 100644 test/test_dyn_array.h create mode 100644 test/test_parser.c delete mode 100644 test/test_parser.h create mode 100644 test/test_string_buf.c delete mode 100644 test/test_string_buf.h create mode 100644 test/test_string_utils.c delete mode 100644 test/test_string_utils.h create mode 100644 test/test_validator.c delete mode 100644 test/test_validator.h diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 8a0f50e..69db1d4 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -10,7 +10,7 @@ STAGED=$( TARGETS=() while IFS= read -r FILENAME do - if [[ "$FILENAME" =~ .*\.c$ ]] || [[ "$FILENAME" == .*\.h$ ]]; then + if [[ "$FILENAME" =~ .*\.c(pp)?$ ]] || [[ "$FILENAME" =~ .*\.h(pp)?$ ]]; then TARGETS+=("${FILENAME}") fi done <<< "$STAGED" diff --git a/.gitignore b/.gitignore index 9f6167a..4dba1a9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,27 @@ # Directory used by clangd LSP. -.cache/ +/.cache/ # Directory used by `direnv` to hold `use flake`-generated profiles. -.direnv/ - -# The compilation database produced by `bear`. -compile_commands.json +/.direnv/ # The directory containing all build outputs. -dist/ +/build/ # The directory generated by `Doxygen`. -docs/ +/docs/ # A symlink produced by default when running `nix build`. result + +# Files generated by CMake. +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..f43ae47 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.19) +project(bootstrap VERSION 0.1.3 LANGUAGES C) + +add_executable(bootstrap main.c) + +add_subdirectory(src) +target_link_libraries(bootstrap PUBLIC m) +target_include_directories(bootstrap PUBLIC include) + +if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + set_target_properties( + bootstrap PROPERTIES + # The top-level compile_commands.json is a symbolic link to this file. + EXPORT_COMPILE_COMMANDS ON + # Enable so that tests can link against the primary executable. + ENABLE_EXPORTS ON + ) + enable_testing() + add_subdirectory(test) +endif() diff --git a/Makefile b/Makefile deleted file mode 100644 index 2899f48..0000000 --- a/Makefile +++ /dev/null @@ -1,81 +0,0 @@ -# ============================================================ -# Configuration -# ============================================================ - -# To create a release build, run `make BUILD=release`. - -BUILD := debug -PREFIX := ${CURDIR}/dist/${BUILD} -OUT := bootstrap - -CCFLAGS.debug := -DDEBUG -g -Og -CCFLAGS.release := -DNDEBUG -LDFLAGS := -lm - -# ============================================================ -# Build -# ============================================================ - -COMPILE := ${CC} ${CCFLAGS.${BUILD}} -I include src/*.c main.c -o ${PREFIX}/${OUT} ${LDFLAGS} - -all: build all.${BUILD} -all.debug: bear -all.release: - -build: ${PREFIX}/${OUT} - -${PREFIX}/${OUT}: ${PREFIX} include/*.h src/*.c - ${COMPILE} - -${PREFIX}: - mkdir -p $@ - -# ============================================================ -# Compilation Database -# -# Generate a compilation database using [Bear](https://github.com/rizsotto/Bear). -# ============================================================ - -bear: compile_commands.json - -compile_commands.json: include/*.h src/*.c main.c -# This file is only constructed in debug mode. If interested in expanding this -# generation to other build types, add a release-specific dependency. -ifeq ($(BUILD), debug) - mkdir -p dist/debug - bear -- ${COMPILE} -endif - -# ============================================================ -# Documentation. -# -# Generate documentation using [Doxygen](https://www.doxygen.nl/index.html). -# ============================================================ - -docs: docs/index.html - -# The `index.html` file is regenerated on each invocation of `doxygen`. -docs/index.html: Doxyfile include/*.h src/*.c - doxygen - -# ============================================================ -# Testing. -# -# We use [Sput](https://www.use-strict.de/sput-unit-testing/) to run tests. -# ============================================================ - -test: dist/test/suites - dist/test/suites - -dist/test/suites: include/*.h src/*.c test/*.h test/*.c - mkdir -p dist/test - ${CC} ${CCFLAGS.debug} -I include src/*.c test/*.c -o dist/test/suites ${LDFLAGS} - -# ============================================================ -# Other -# ============================================================ - -clean: - rm -r ${PREFIX} - -.PHONY: test diff --git a/README.md b/README.md index 0c4e977..d2dbca9 100644 --- a/README.md +++ b/README.md @@ -70,15 +70,6 @@ If flakes is not enabled or your nix version does not support })).packages.${system}.default; ``` -### Source - -If you do not have Nix or prefer building from source, clone this repository and -run -```bash -$ make BUILD=release -``` -The `bootstrap` binary will be made available in `dist/release` by default. - ## Usage ### Runners @@ -220,23 +211,49 @@ functionality (or lack thereof) reflects my own needs as I have come across them. If interested in adding more capabilities, please send a PR or just fork the project for your own purposes. +### Building + +We use [CMake](https://cmake.org/) (version 3.27.7) to build the project. If a +`build/` directory does not already exist, run the following: +```bash +$ mkdir -p build/{Debug,Release} +$ pushd build/Debug && cmake -DCMAKE_BUILD_TYPE=Debug ../.. && popd +$ pushd build/Release && cmake -DCMAKE_BUILD_TYPE=Release ../.. && popd +``` +These commands will create a CMake cache file in each subdirectory with the +build types set. Now you can build a `Debug` or `Release` variant by navigating +to the corresponding subdirectory and running: +```bash +$ cmake --build . +``` + +The [clangd](https://clangd.llvm.org/) LSP (version 14.0.6) is included in this +flake. The [codelldb](https://github.com/vadimcn/codelldb) VSCode plugin is also +included to interface with the LSP. Note this plugin, despite its name, is +compatible with other editors (e.g. neovim). To configure, refer to your +editor's documentation. + +To use the LSP across files, a +[compilation database](https://clang.llvm.org/docs/JSONCompilationDatabase.html) +must be generated. The `CMakeLists.txt` file already enables this in the Debug +configuration type. A top-level `compile_commands.json` symbolic link already +exists and points to this generated database. + ### Testing -We use [Sput](https://www.use-strict.de/sput-unit-testing/) for unit tests. To -run tests, type: +We use [CTest](https://cmake.org/cmake/help/latest/module/CTest.html) (version +3.27.7) for unit testing. To run the tests, navigate to `build/Debug` and type +the following: ```bash +$ cmake --build . $ make test ``` -Tests are located in the `test` directory. `test/suites.c` serves as the -entrypoint for the test runner. ### Documentation We use [doxygen](https://www.doxygen.nl/index.html) for documentation -generation. Run either of the following two commands to generate documentation -locally: +generation. Run the following command to generate documentation locally: ```bash -$ make docs $ doxygen ``` @@ -245,10 +262,8 @@ $ doxygen We use `clang-format` to ensure consistent formatting. A `pre-commit` file is included in `.githooks` to enforce usage. Run the following to configure `git` to use it: - ```bash git config --local core.hooksPath .githooks/ ``` - If running [direnv](https://direnv.net/), this is done automatically upon entering the project directory. diff --git a/flake.nix b/flake.nix index 2ec7349..4d2b681 100644 --- a/flake.nix +++ b/flake.nix @@ -24,7 +24,6 @@ pname = "bootstrap"; src = ./.; version = "0.1.3"; - makeFlags = [ "BUILD=release" "PREFIX=$(out)" ]; dontInstall = true; }; @@ -40,8 +39,8 @@ stdenv = pkgs.clangStdenv; } { packages = with pkgs; [ - bear clang-tools + cmake codelldb doxygen ]; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..35a3022 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,12 @@ +target_sources( + bootstrap PUBLIC + cJSON.c + config.c + dyn_array.c + error.c + evaluator.c + parser.c + string_buf.c + string_utils.c + validator.c +) diff --git a/src/config.c b/src/config.c index 9428bec..023c91c 100644 --- a/src/config.c +++ b/src/config.c @@ -87,5 +87,4 @@ void config_free(struct Config *config) { return; } free(config); - config = 0; } diff --git a/src/dyn_array.c b/src/dyn_array.c index e338925..ff5ae82 100644 --- a/src/dyn_array.c +++ b/src/dyn_array.c @@ -36,5 +36,4 @@ void dyn_array_free(struct DynArray *a) { } free(a->buf); free(a); - a = 0; } diff --git a/src/error.c b/src/error.c index 7ed0cb0..1c39ffb 100644 --- a/src/error.c +++ b/src/error.c @@ -6,5 +6,4 @@ void error_free(struct Error *error) { } free((void *)error->message); free(error); - error = 0; } diff --git a/src/string_buf.c b/src/string_buf.c index 9365304..3a663bc 100644 --- a/src/string_buf.c +++ b/src/string_buf.c @@ -60,5 +60,4 @@ void string_buf_free(struct StringBuf *sb) { } free((void *)sb->buf); free(sb); - sb = 0; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..fc53693 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,106 @@ +file( + COPY ${CMAKE_CURRENT_SOURCE_DIR}/specs + DESTINATION ${CMAKE_CURRENT_BINARY_DIR} +) + +foreach( + exe + config + dyn_array + parser + string_buf + string_utils + validator +) + add_executable("test-${exe}" "test_${exe}.c") + target_link_libraries("test-${exe}" PRIVATE bootstrap) + target_include_directories("test-${exe}" PRIVATE bootstrap) +endforeach() + +# ======================================== +# config +# ======================================== + +foreach( + arg + new-invalid-args + new-spec-not-found + new-spec-not-dir + new-success +) + add_test(NAME "config: ${arg}" COMMAND test-config ${arg}) +endforeach() + +# ======================================== +# dyn_array +# ======================================== + +foreach( + arg + zero-capacity + nonzero-capacity +) + add_test(NAME "dyn_array: ${arg}" COMMAND test-dyn_array ${arg}) +endforeach() + +# ======================================== +# parser +# ======================================== + +foreach( + arg + missing + minimal + invalid +) + add_test(NAME "parser: ${arg}" COMMAND test-parser ${arg}) +endforeach() + +# ======================================== +# string_buf +# ======================================== + +foreach( + arg + sappend + cappend +) + add_test(NAME "string_buf: ${arg}" COMMAND test-string_buf ${arg}) +endforeach() + +# ======================================== +# string_utils +# ======================================== + +foreach( + arg + join-single + join-multiple + strcmp-ci + trim-leading + trim-trailing +) + add_test(NAME "string_utils: ${arg}" COMMAND test-string_utils ${arg}) +endforeach() + +# ======================================== +# validator +# ======================================== + +foreach( + arg + toplevel-not-object + field-not-object + field-name-leading-digit + field-name-non-alnum + field-type-invalid + field-type-unknown + valid-type-ci + field-required-invalid + field-required-valid + field-prompt-invalid + valid-no-required + field-type-yes +) + add_test(NAME "validator: ${arg}" COMMAND test-validator ${arg}) +endforeach() diff --git a/test/specs/invalid_spec_json/run.sh b/test/specs/invalid_spec_json/runner old mode 100644 new mode 100755 similarity index 100% rename from test/specs/invalid_spec_json/run.sh rename to test/specs/invalid_spec_json/runner diff --git a/test/specs/minimal_spec_json/run.sh b/test/specs/minimal_spec_json/runner old mode 100644 new mode 100755 similarity index 100% rename from test/specs/minimal_spec_json/run.sh rename to test/specs/minimal_spec_json/runner diff --git a/test/specs/no_spec_json/run.sh b/test/specs/no_spec_json/runner old mode 100644 new mode 100755 similarity index 100% rename from test/specs/no_spec_json/run.sh rename to test/specs/no_spec_json/runner diff --git a/test/sput.h b/test/sput.h deleted file mode 100644 index 3ce5129..0000000 --- a/test/sput.h +++ /dev/null @@ -1,300 +0,0 @@ -/* - * sput - Simple, Portable Unit Testing Framework for C/C++ v1.4.0 - * - * http://www.use-strict.de/sput-unit-testing/ - * - * - * Copyright (C) 2011-2015 Lingua-Systems Software GmbH - * Copyright (C) 2016 Alex Linke - * - * All rights reserved. - * - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef HAVE_SPUT_H -#define HAVE_SPUT_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include -#include - -/* =================================================================== - * definitions - * =================================================================== */ - -#define SPUT_VERSION_MAJOR 1 -#define SPUT_VERSION_MINOR 4 -#define SPUT_VERSION_PATCH 0 -#define SPUT_VERSION_STRING "1.4.0" - -#define SPUT_DEFAULT_SUITE_NAME "Unlabeled Suite" -#define SPUT_DEFAULT_CHECK_NAME "Unlabeled Check" - -#define SPUT_INITIALIZED 0x06 /* ACK */ - -/* =================================================================== - * sput global variable - * =================================================================== */ - -static struct sput { - FILE *out; - char initialized; - - struct sput_overall { - unsigned long checks; - unsigned long suites; - unsigned long ok; - unsigned long nok; - } overall; - - struct sput_suite { - const char *name; - unsigned long nr; - unsigned long checks; - unsigned long ok; - unsigned long nok; - } suite; - - struct sput_test { - const char *name; - unsigned long nr; - } test; - - struct sput_check { - const char *name; - const char *cond; - const char *type; - unsigned long line; - } check; - - struct sput_time { - time_t start; - time_t end; - } time; -} __sput; - -/* ================================================================== - * sput internal macros - * ================================================================== */ - -#define _sput_die_unless_initialized() \ - if (__sput.initialized != SPUT_INITIALIZED) { \ - fputs("sput_start_testing() omitted\n", stderr); \ - exit(EXIT_FAILURE); \ - } - -#define _sput_die_unless_suite_set() \ - if (!__sput.suite.name) { \ - fputs("sput_enter_suite() omitted\n", __sput.out); \ - exit(EXIT_FAILURE); \ - } - -#define _sput_die_unless_test_set() \ - if (!__sput.test.name) { \ - fputs("sput_run_test() omitted\n", __sput.out); \ - exit(EXIT_FAILURE); \ - } - -#define _sput_check_failed() \ - { \ - _sput_die_unless_initialized(); \ - _sput_die_unless_suite_set(); \ - __sput.suite.nok++; \ - fprintf( \ - __sput.out, \ - "[%lu:%lu] %s:#%lu \"%s\" FAIL\n" \ - "! Type: %s\n" \ - "! Condition: %s\n" \ - "! Line: %lu\n", \ - __sput.suite.nr, \ - __sput.suite.checks, \ - __sput.test.name, \ - __sput.test.nr, \ - __sput.check.name, \ - __sput.check.type, \ - __sput.check.cond, \ - __sput.check.line \ - ); \ - } - -#define _sput_check_succeeded() \ - { \ - _sput_die_unless_initialized(); \ - _sput_die_unless_suite_set(); \ - __sput.suite.ok++; \ - fprintf( \ - __sput.out, \ - "[%lu:%lu] %s:#%lu \"%s\" pass\n", \ - __sput.suite.nr, \ - __sput.suite.checks, \ - __sput.test.name, \ - __sput.test.nr, \ - __sput.check.name \ - ); \ - } - -/* ================================================================== - * user macros - * ================================================================== */ - -#define sput_start_testing() \ - do { \ - memset(&__sput, 0, sizeof(__sput)); \ - __sput.out = stdout; \ - __sput.time.start = time(NULL); \ - __sput.initialized = SPUT_INITIALIZED; \ - } while (0) - -#define sput_leave_suite() \ - do { \ - float failpls = 0.0f; \ - _sput_die_unless_initialized(); \ - _sput_die_unless_suite_set(); \ - failpls = __sput.suite.checks \ - ? (float)((__sput.suite.nok * 100.0) / __sput.suite.checks) \ - : 0.0f; \ - fprintf( \ - __sput.out, \ - "\n--> %lu check(s), %lu ok, %lu failed (%.2f%%)\n", \ - __sput.suite.checks, \ - __sput.suite.ok, \ - __sput.suite.nok, \ - failpls \ - ); \ - __sput.overall.checks += __sput.suite.checks; \ - __sput.overall.ok += __sput.suite.ok; \ - __sput.overall.nok += __sput.suite.nok; \ - memset(&__sput.suite, 0, sizeof(__sput.suite)); \ - } while (0) - -#define sput_get_return_value() \ - (__sput.overall.nok > 0 ? EXIT_FAILURE : EXIT_SUCCESS) - -#define sput_enter_suite(_name) \ - do { \ - _sput_die_unless_initialized(); \ - if (__sput.suite.name) { \ - sput_leave_suite(); \ - } \ - __sput.suite.name = _name != NULL ? _name : SPUT_DEFAULT_SUITE_NAME; \ - __sput.suite.nr = ++__sput.overall.suites; \ - fprintf( \ - __sput.out, \ - "\n== Entering suite #%lu, \"%s\" ==\n\n", \ - __sput.suite.nr, \ - __sput.suite.name \ - ); \ - } while (0) - -#define sput_finish_testing() \ - do { \ - float failpft = 0.0f; \ - _sput_die_unless_initialized(); \ - if (__sput.suite.name) { \ - sput_leave_suite(); \ - } \ - failpft = \ - __sput.overall.checks \ - ? (float)((__sput.overall.nok * 100.0) / __sput.overall.checks) \ - : 0.0f; \ - __sput.time.end = time(NULL); \ - fprintf( \ - __sput.out, \ - "\n==> %lu check(s) in %lu suite(s) finished after %.2f " \ - "second(s),\n" \ - " %lu succeeded, %lu failed (%.2f%%)\n" \ - "\n[%s]\n", \ - __sput.overall.checks, \ - __sput.overall.suites, \ - difftime(__sput.time.end, __sput.time.start), \ - __sput.overall.ok, \ - __sput.overall.nok, \ - failpft, \ - (sput_get_return_value() == EXIT_SUCCESS) ? "SUCCESS" : "FAILURE" \ - ); \ - } while (0) - -#define sput_set_output_stream(_fp) \ - do { \ - __sput.out = _fp != NULL ? _fp : stdout; \ - } while (0) - -#define sput_fail_if(_cond, _name) \ - do { \ - _sput_die_unless_initialized(); \ - _sput_die_unless_suite_set(); \ - _sput_die_unless_test_set(); \ - __sput.check.name = _name != NULL ? _name : SPUT_DEFAULT_CHECK_NAME; \ - __sput.check.line = __LINE__; \ - __sput.check.cond = #_cond; \ - __sput.check.type = "fail-if"; \ - __sput.test.nr++; \ - __sput.suite.checks++; \ - if ((_cond)) { \ - _sput_check_failed(); \ - } else { \ - _sput_check_succeeded(); \ - } \ - } while (0) - -#define sput_fail_unless(_cond, _name) \ - do { \ - _sput_die_unless_initialized(); \ - _sput_die_unless_suite_set(); \ - _sput_die_unless_test_set(); \ - __sput.check.name = _name != NULL ? _name : SPUT_DEFAULT_CHECK_NAME; \ - __sput.check.line = __LINE__; \ - __sput.check.cond = #_cond; \ - __sput.check.type = "fail-unless"; \ - __sput.test.nr++; \ - __sput.suite.checks++; \ - if (!(_cond)) { \ - _sput_check_failed(); \ - } else { \ - _sput_check_succeeded(); \ - } \ - } while (0) - -#define sput_run_test(_func) \ - do { \ - _sput_die_unless_initialized(); \ - _sput_die_unless_suite_set(); \ - memset(&__sput.test, 0, sizeof(__sput.test)); \ - __sput.test.name = #_func; \ - _func(); \ - } while (0) - -#ifdef __cplusplus -} -#endif - -#endif /* HAVE_SPUT_H */ - -/* vim: set ft=c sts=4 sw=4 ts=4 ai et: */ diff --git a/test/suites.c b/test/suites.c deleted file mode 100644 index 701c13b..0000000 --- a/test/suites.c +++ /dev/null @@ -1,55 +0,0 @@ -#include "sput.h" -#include "test_config.h" -#include "test_dyn_array.h" -#include "test_parser.h" -#include "test_string_buf.h" -#include "test_string_utils.h" -#include "test_validator.h" - -int main(int argc, char *argv[]) { - sput_start_testing(); - - sput_enter_suite("config"); - sput_run_test(test_config_new_invalid_args); - sput_run_test(test_config_new_spec_not_found); - sput_run_test(test_config_new_spec_not_dir); - sput_run_test(test_config_new_success); - - sput_enter_suite("dyn_array"); - sput_run_test(test_dyn_array_zero_capacity); - sput_run_test(test_dyn_array_nonzero_capacity); - - sput_enter_suite("string_buf"); - sput_run_test(test_string_buf_sappend); - sput_run_test(test_string_buf_cappend); - - sput_enter_suite("string_utils"); - sput_run_test(test_join_single); - sput_run_test(test_join_multiple); - sput_run_test(test_strcmp_ci); - sput_run_test(test_trim_leading); - sput_run_test(test_trim_trailing); - - sput_enter_suite("parser"); - sput_run_test(test_parser_missing); - sput_run_test(test_parser_minimal); - sput_run_test(test_parser_invalid); - - sput_enter_suite("validator"); - sput_run_test(test_validator_toplevel_not_object); - sput_run_test(test_validator_field_not_object); - sput_run_test(test_validator_field_name_leading_digit); - sput_run_test(test_validator_field_name_non_alnum); - sput_run_test(test_validator_field_type_invalid); - sput_run_test(test_validator_field_type_unknown); - sput_run_test(test_validator_valid_type_ci); - sput_run_test(test_validator_field_required_invalid); - sput_run_test(test_validator_field_required_valid); - sput_run_test(test_validator_field_prompt_invalid); - sput_run_test(test_validator_valid_no_required); - sput_run_test(test_validator_field_type_yes); - - sput_finish_testing(); - - return sput_get_return_value(); -} diff --git a/test/test_cases.h b/test/test_cases.h new file mode 100644 index 0000000..966d363 --- /dev/null +++ b/test/test_cases.h @@ -0,0 +1,9 @@ +#ifndef _BOOTSTRAP_TEST_CASE_H +#define _BOOTSTRAP_TEST_CASE_H + +struct TestCase { + char *name; + int (*func)(); +}; + +#endif /* _BOOTSTRAP_TEST_CASE_H */ diff --git a/test/test_config.c b/test/test_config.c new file mode 100644 index 0000000..60e0935 --- /dev/null +++ b/test/test_config.c @@ -0,0 +1,122 @@ +#include +#include +#include + +#include "config.h" +#include "string_utils.h" +#include "test_cases.h" + +struct TestConfigFixture { + char *cwd; + char *root_dir; + char *target; +}; + +static struct TestConfigFixture *test_setup() { + char *cwd = getcwd(0, 0); + const char *segments[] = {cwd, "specs"}; + char *root_dir = join(sizeof(segments) / sizeof(char *), segments, '/'); + + struct TestConfigFixture *fixture = malloc(sizeof(struct TestConfigFixture)); + fixture->cwd = getcwd(0, 0); + fixture->root_dir = root_dir; + fixture->target = "minimal_spec_json"; + + return fixture; +} + +static int test_new_invalid_args() { + struct TestConfigFixture *fixture = test_setup(); + struct Error *error = 0; + struct Config *config = 0; + + error = config_new(0, fixture->root_dir, fixture->target, &config); + if (error->code != ERROR_CONFIG_ENV_CWD_INVALID) { + printf("cwd == 0\n"); + return 1; + } + + error_free(error); + error = config_new(fixture->cwd, 0, fixture->target, &config); + if (error->code != ERROR_CONFIG_ENV_ROOT_DIR_INVALID) { + printf("root_dir == 0\n"); + return 1; + } + + return 0; +} + +static int test_new_spec_not_found() { + struct TestConfigFixture *fixture = test_setup(); + struct Error *error = 0; + struct Config *config = 0; + + error = config_new(fixture->cwd, fixture->root_dir, "not_found", &config); + if (error->code != ERROR_CONFIG_TARGET_NOT_FOUND) { + printf("target not found\n"); + return 1; + } + + return 0; +} + +static int test_new_spec_not_dir() { + struct TestConfigFixture *fixture = test_setup(); + struct Error *error = 0; + struct Config *config = 0; + + error = config_new(fixture->cwd, fixture->root_dir, "not_dir", &config); + if (error->code != ERROR_CONFIG_TARGET_NOT_DIR) { + printf("target not dir\n"); + return 1; + } + + return 0; +} + +static int test_new_success() { + struct TestConfigFixture *fixture = test_setup(); + struct Error *error = 0; + struct Config *config = 0; + + error = config_new(fixture->cwd, fixture->root_dir, fixture->target, &config); + if (error != 0) { + error_free(error); + printf("config_new() success\n"); + return 1; + } else if (strcmp(config->cwd, fixture->cwd) != 0) { + printf("config_new() cwd\n"); + return 1; + } else if (strcmp(config->root_dir, fixture->root_dir) != 0) { + printf("config_new() root_dir\n"); + return 1; + } else if (strcmp(config->target, fixture->target) != 0) { + printf("config_new() target\n"); + return 1; + } + + return 0; +} + +int main(int argc, char **argv) { + if (argc != 2) { + printf("Expected exactly one argument."); + return 1; + } + + struct TestCase cases[] = { + {"new-invalid-args", test_new_invalid_args}, + {"new-spec-not-found", test_new_spec_not_found}, + {"new-spec-not-dir", test_new_spec_not_dir}, + {"new-success", test_new_success}, + }; + + for (int i = 0; i < sizeof(cases) / sizeof(struct TestCase); ++i) { + struct TestCase test_case = cases[i]; + if (strcmp(argv[1], test_case.name) == 0) { + return test_case.func(); + } + } + + return 1; +} diff --git a/test/test_config.h b/test/test_config.h deleted file mode 100644 index 5fae6f0..0000000 --- a/test/test_config.h +++ /dev/null @@ -1,103 +0,0 @@ -#ifndef _BOOTSTRAP_TEST_CONFIG -#define _BOOTSTRAP_TEST_CONFIG - -#include - -#include "config.h" -#include "sput.h" -#include "string_utils.h" - -struct TestConfigFixture { - char *cwd; - char *root_dir; - char *target; -}; - -static struct TestConfigFixture *test_config_setup() { - char *cwd = getcwd(0, 0); - const char *segments[] = {cwd, "test", "specs"}; - char *root_dir = join(sizeof(segments) / sizeof(char *), segments, '/'); - - struct TestConfigFixture *fixture = malloc(sizeof(struct TestConfigFixture)); - fixture->cwd = getcwd(0, 0); - fixture->root_dir = root_dir; - fixture->target = "minimal_spec_json"; - - return fixture; -} - -static void test_config_teardown(struct TestConfigFixture *fixture) { - free(fixture->cwd); - free(fixture->root_dir); - free(fixture); -} - -static void test_config_new_invalid_args() { - struct TestConfigFixture *fixture = test_config_setup(); - - struct Error *error = 0; - struct Config *config = 0; - - error = config_new(0, fixture->root_dir, fixture->target, &config); - sput_fail_unless(error->code == ERROR_CONFIG_ENV_CWD_INVALID, "cwd == 0"); - error_free(error); - error = config_new(fixture->cwd, 0, fixture->target, &config); - sput_fail_unless( - error->code == ERROR_CONFIG_ENV_ROOT_DIR_INVALID, "root_dir == 0" - ); - error_free(error); - - test_config_teardown(fixture); -} - -static void test_config_new_spec_not_found() { - struct TestConfigFixture *fixture = test_config_setup(); - - struct Error *error = 0; - struct Config *config = 0; - - error = config_new(fixture->cwd, fixture->root_dir, "not_found", &config); - sput_fail_unless( - error->code == ERROR_CONFIG_TARGET_NOT_FOUND, "target not found" - ); - error_free(error); - - test_config_teardown(fixture); -} - -static void test_config_new_spec_not_dir() { - struct TestConfigFixture *fixture = test_config_setup(); - - struct Error *error = 0; - struct Config *config = 0; - - error = config_new(fixture->cwd, fixture->root_dir, "not_dir", &config); - sput_fail_unless( - error->code == ERROR_CONFIG_TARGET_NOT_DIR, "target not dir" - ); - error_free(error); - - test_config_teardown(fixture); -} - -static void test_config_new_success() { - struct TestConfigFixture *fixture = test_config_setup(); - - struct Error *error = 0; - struct Config *config = 0; - - error = config_new(fixture->cwd, fixture->root_dir, fixture->target, &config); - sput_fail_unless(error == 0, "config_new() success"); - sput_fail_unless(strcmp(config->cwd, fixture->cwd) == 0, "config_new() cwd"); - sput_fail_unless( - strcmp(config->root_dir, fixture->root_dir) == 0, "config_new() root_dir" - ); - sput_fail_unless( - strcmp(config->target, fixture->target) == 0, "config_new() target" - ); - - config_free(config); - test_config_teardown(fixture); -} - -#endif /* _BOOTSTRAP_TEST_CONFIG */ diff --git a/test/test_dyn_array.c b/test/test_dyn_array.c new file mode 100644 index 0000000..b3db441 --- /dev/null +++ b/test/test_dyn_array.c @@ -0,0 +1,111 @@ +#include +#include + +#include "dyn_array.h" +#include "test_cases.h" + +/* +A @DynArray with zero capacity can be instantiated and have entries pushed onto. +*/ +static int test_zero_capacity() { + struct DynArray *a = dyn_array_new(0); + + if (a->size != 0) { + printf("a->size != 0\n"); + return 1; + } + if (a->_capacity != 1) { + printf("a->_capacity != 1\n"); + return 1; + } + + int *x = malloc(sizeof(int)); + dyn_array_push(a, x); + if (a->size != 1) { + printf("a->size != 1\n"); + return 1; + } + if (a->_capacity != 1) { + printf("a->_capacity != 1\n"); + return 1; + } + + int *y = malloc(sizeof(int)); + dyn_array_push(a, y); + if (a->size != 2) { + printf("a->size != 2\n"); + return 1; + } + if (a->_capacity != 2) { + printf("a->_capacity != 2\n"); + return 1; + } + + return 0; +} + +/* +A @DynArray with nonzero capacity can be instantiated and have entries pushed +onto. +*/ +static int test_nonzero_capacity() { + struct DynArray *a = dyn_array_new(3); + + if (a->size != 0) { + printf("a->size != 0\n"); + return 1; + } + if (a->_capacity != 3) { + printf("a->_capacity != 3\n"); + return 1; + } + + int *x = malloc(sizeof(int)); + int *y = malloc(sizeof(int)); + int *z = malloc(sizeof(int)); + dyn_array_push(a, x); + dyn_array_push(a, y); + dyn_array_push(a, z); + if (a->size != 3) { + printf("a->size != 3\n"); + return 1; + } + if (a->_capacity != 3) { + printf("a->_capacity != 3\n"); + return 1; + } + + int *w = malloc(sizeof(int)); + dyn_array_push(a, w); + if (a->size != 4) { + printf("a->size != 4\n"); + return 1; + } + if (a->_capacity != 6) { + printf("a->_capacity != 6\n"); + return 1; + } + + return 0; +} + +int main(int argc, char **argv) { + if (argc != 2) { + printf("Expected exactly one argument."); + return 1; + } + + struct TestCase cases[] = { + {"zero-capacity", test_zero_capacity}, + {"nonzero-capacity", test_nonzero_capacity}, + }; + + for (int i = 0; i < sizeof(cases) / sizeof(struct TestCase); ++i) { + struct TestCase test_case = cases[i]; + if (strcmp(argv[1], test_case.name) == 0) { + return test_case.func(); + } + } + + return 1; +} diff --git a/test/test_dyn_array.h b/test/test_dyn_array.h deleted file mode 100644 index 1540a90..0000000 --- a/test/test_dyn_array.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef _BOOTSTRAP_TEST_DYN_ARRAY -#define _BOOTSTRAP_TEST_DYN_ARRAY - -#include "dyn_array.h" -#include "sput.h" - -/* -A @DynArray with zero capacity can be instantiated and have entries pushed onto. -*/ -static void test_dyn_array_zero_capacity() { - struct DynArray *a = dyn_array_new(0); - sput_fail_unless(a->size == 0, "a->size == 0"); - sput_fail_unless(a->_capacity == 1, "a->_capacity == 1"); - - int *x = malloc(sizeof(int)); - dyn_array_push(a, x); - sput_fail_unless(a->size == 1, "a->size == 1"); - sput_fail_unless(a->_capacity == 1, "a->_capacity == 1"); - - int *y = malloc(sizeof(int)); - dyn_array_push(a, y); - sput_fail_unless(a->size == 2, "a->size == 2"); - sput_fail_unless(a->_capacity == 2, "a->_capacity == 2"); - - dyn_array_free(a); -} - -/* -A @DynArray with nonzero capacity can be instantiated and have entries pushed -onto. -*/ -static void test_dyn_array_nonzero_capacity() { - struct DynArray *a = dyn_array_new(3); - sput_fail_unless(a->size == 0, "a->size == 0"); - sput_fail_unless(a->_capacity == 3, "a->_capacity == 3"); - - int *x = malloc(sizeof(int)); - int *y = malloc(sizeof(int)); - int *z = malloc(sizeof(int)); - dyn_array_push(a, x); - dyn_array_push(a, y); - dyn_array_push(a, z); - sput_fail_unless(a->size == 3, "a->size == 3"); - sput_fail_unless(a->_capacity == 3, "a->_capacity == 3"); - - int *w = malloc(sizeof(int)); - dyn_array_push(a, w); - sput_fail_unless(a->size == 4, "a->size == 4"); - sput_fail_unless(a->_capacity == 6, "a->_capacity == 6"); - - dyn_array_free(a); -} - -#endif /* _BOOTSTRAP_TEST_DYN_ARRAY */ diff --git a/test/test_parser.c b/test/test_parser.c new file mode 100644 index 0000000..5f46cab --- /dev/null +++ b/test/test_parser.c @@ -0,0 +1,114 @@ +#include +#include +#include + +#include "cJSON.h" +#include "config.h" +#include "parser.h" +#include "string_utils.h" +#include "test_cases.h" + +struct TestParserFixture { + char *cwd; + char *root_dir; + const char *target; + struct Config config; +}; + +static struct TestParserFixture *test_setup(const char *target) { + char *cwd = getcwd(0, 0); + const char *segments[] = {cwd, "specs"}; + char *root_dir = join(sizeof(segments) / sizeof(char *), segments, '/'); + + struct TestParserFixture *fixture = malloc(sizeof(struct TestParserFixture)); + fixture->cwd = getcwd(0, 0); + fixture->root_dir = root_dir; + fixture->target = target; + + // Reproduce in `Config` instance for convenience. + fixture->config.cwd = fixture->cwd; + fixture->config.root_dir = fixture->root_dir; + fixture->config.target = fixture->target; + + return fixture; +} + +/* +A missing `spec.json` file is not an error. Our parsed @cJSON instance should +be set to NULL in this case. +*/ +static int test_missing() { + struct TestParserFixture *fixture = test_setup("no_spec_json"); + struct Error *error = 0; + + cJSON *parsed = 0; + error = parse_spec_json(&fixture->config, &parsed); + if (error != 0) { + printf("no spec.json, failure\n"); + return 1; + } + if (parsed != 0) { + printf("no spec.json, parsed\n"); + return 1; + } + + return 0; +} + +static int test_minimal() { + struct TestParserFixture *fixture = test_setup("minimal_spec_json"); + struct Error *error = 0; + cJSON *parsed = 0; + + error = parse_spec_json(&fixture->config, &parsed); + if (error != 0) { + printf("minimal spec.json, failure\n"); + return 1; + } + if (parsed == 0) { + printf("minimal spec.json, no parse\n"); + return 1; + } + + return 0; +} + +static int test_invalid() { + struct TestParserFixture *fixture = test_setup("invalid_spec_json"); + struct Error *error = 0; + cJSON *parsed = 0; + + error = parse_spec_json(&fixture->config, &parsed); + if (error->code != ERROR_PARSER_SPEC_JSON_INVALID_SYNTAX) { + printf("invalid spec, wrong code\n"); + return 1; + } + if (parsed != 0) { + printf("invalid spec, parsed\n"); + return 1; + } + + return 0; +} + +int main(int argc, char **argv) { + if (argc != 2) { + printf("Expected exactly one argument."); + return 1; + } + + struct TestCase cases[] = { + {"missing", test_missing}, + {"minimal", test_minimal}, + {"invalid", test_invalid}, + }; + + for (int i = 0; i < sizeof(cases) / sizeof(struct TestCase); ++i) { + struct TestCase test_case = cases[i]; + if (strcmp(argv[1], test_case.name) == 0) { + return test_case.func(); + } + } + + return 1; +} diff --git a/test/test_parser.h b/test/test_parser.h deleted file mode 100644 index 7abccbb..0000000 --- a/test/test_parser.h +++ /dev/null @@ -1,84 +0,0 @@ -#ifndef _BOOTSTRAP_TEST_PARSER -#define _BOOTSTRAP_TEST_PARSER - -#include - -#include "cJSON.h" -#include "config.h" -#include "parser.h" -#include "sput.h" -#include "string_utils.h" - -struct TestParserFixture { - char *cwd; - char *root_dir; - const char *target; - struct Config config; -}; - -static struct TestParserFixture *test_parser_setup(const char *target) { - char *cwd = getcwd(0, 0); - const char *segments[] = {cwd, "test", "specs"}; - char *root_dir = join(sizeof(segments) / sizeof(char *), segments, '/'); - - struct TestParserFixture *fixture = malloc(sizeof(struct TestParserFixture)); - fixture->cwd = getcwd(0, 0); - fixture->root_dir = root_dir; - fixture->target = target; - - // Reproduce in `Config` instance for convenience. - fixture->config.cwd = fixture->cwd; - fixture->config.root_dir = fixture->root_dir; - fixture->config.target = fixture->target; - - return fixture; -} - -static void test_parser_teardown(struct TestParserFixture *fixture) { - free(fixture->cwd); - free(fixture->root_dir); - free(fixture); -} - -/* -A missing `spec.json` file is not an error. Our parsed @cJSON instance should -be set to NULL in this case. -*/ -static void test_parser_missing() { - struct TestParserFixture *fixture = test_parser_setup("no_spec_json"); - - cJSON *parsed = 0; - struct Error *error = parse_spec_json(&fixture->config, &parsed); - sput_fail_unless(error == 0, "no spec.json, success"); - sput_fail_unless(parsed == 0, "no spec.json, no parsed"); - - test_parser_teardown(fixture); -} - -static void test_parser_minimal() { - struct TestParserFixture *fixture = test_parser_setup("minimal_spec_json"); - - cJSON *parsed = 0; - struct Error *error = parse_spec_json(&fixture->config, &parsed); - sput_fail_unless(error == 0, "minimal spec.json, success"); - sput_fail_unless(parsed != 0, "minimal spec.json, parsed"); - - test_parser_teardown(fixture); -} - -static void test_parser_invalid() { - struct TestParserFixture *fixture = test_parser_setup("invalid_spec_json"); - - cJSON *parsed = 0; - struct Error *error = parse_spec_json(&fixture->config, &parsed); - sput_fail_unless( - error->code == ERROR_PARSER_SPEC_JSON_INVALID_SYNTAX, - "invalid spec.json, INVALID_SYNTAX" - ); - error_free(error); - sput_fail_unless(parsed == 0, "invalid spec.json, not parsed"); - - test_parser_teardown(fixture); -} - -#endif /* _BOOTSTRAP_TEST_PARSER */ diff --git a/test/test_string_buf.c b/test/test_string_buf.c new file mode 100644 index 0000000..af5f9c9 --- /dev/null +++ b/test/test_string_buf.c @@ -0,0 +1,65 @@ +#include +#include + +#include "string_buf.h" +#include "test_cases.h" + +static int test_sappend() { + struct StringBuf *sb = string_buf_new(0); + string_buf_sappend(sb, "hello world"); + string_buf_sappend(sb, "!!"); + + if (string_buf_size(sb) != strlen("hello world!!")) { + printf("sappend unexpected size\n"); + return 1; + } + + const char *cast = string_buf_cast(sb); + if (strcmp(cast, "hello world!!") != 0) { + printf("sappend wrong cast\n"); + return 1; + } + + return 0; +} + +static int test_cappend() { + struct StringBuf *sb = string_buf_new(0); + string_buf_sappend(sb, "hello world"); + string_buf_cappend(sb, '!'); + string_buf_cappend(sb, '!'); + + if (string_buf_size(sb) != strlen("hello world!!")) { + printf("cappend wrong size\n"); + return 1; + } + + const char *cast = string_buf_cast(sb); + if (strcmp(cast, "hello world!!") != 0) { + printf("cappend wrong cast\n"); + return 1; + } + + return 0; +} + +int main(int argc, char **argv) { + if (argc != 2) { + printf("Expected exactly one argument."); + return 1; + } + + struct TestCase cases[] = { + {"sappend", test_sappend}, + {"cappend", test_cappend}, + }; + + for (int i = 0; i < sizeof(cases) / sizeof(struct TestCase); ++i) { + struct TestCase test_case = cases[i]; + if (strcmp(argv[1], test_case.name) == 0) { + return test_case.func(); + } + } + + return 1; +} diff --git a/test/test_string_buf.h b/test/test_string_buf.h deleted file mode 100644 index db776cf..0000000 --- a/test/test_string_buf.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef _BOOTSTRAP_TEST_STRING_BUF -#define _BOOTSTRAP_TEST_STRING_BUF - -#include "sput.h" -#include "string_buf.h" - -static void test_string_buf_sappend() { - struct StringBuf *sb = string_buf_new(0); - string_buf_sappend(sb, "hello world"); - string_buf_sappend(sb, "!!"); - - sput_fail_unless( - string_buf_size(sb) == strlen("hello world!!"), "sappend size" - ); - const char *cast = string_buf_cast(sb); - sput_fail_unless(strcmp(cast, "hello world!!") == 0, "sappend cast"); - free((void *)cast); -} - -static void test_string_buf_cappend() { - struct StringBuf *sb = string_buf_new(0); - string_buf_sappend(sb, "hello world"); - string_buf_cappend(sb, '!'); - string_buf_cappend(sb, '!'); - - sput_fail_unless( - string_buf_size(sb) == strlen("hello world!!"), "cappend size" - ); - const char *cast = string_buf_cast(sb); - sput_fail_unless(strcmp(cast, "hello world!!") == 0, "cappend cast"); - free((void *)cast); -} - -static void test_string_buf_nonzero_capacity() { - struct StringBuf *sb = string_buf_new(100); - string_buf_sappend(sb, "hello world"); - string_buf_cappend(sb, '!'); - string_buf_free(sb); - sput_fail_unless(sb == 0, "free"); -} - -#endif /* _BOOTSTRAP_TEST_STRING_BUF */ diff --git a/test/test_string_utils.c b/test/test_string_utils.c new file mode 100644 index 0000000..33149c0 --- /dev/null +++ b/test/test_string_utils.c @@ -0,0 +1,139 @@ +#include +#include + +#include "string_utils.h" +#include "test_cases.h" + +static int test_join_single() { + const char *segments[] = {"abc"}; + char *joined = join(sizeof(segments) / sizeof(char *), segments, '/'); + if (strcmp(joined, "abc") != 0) { + printf("joined != abc\n"); + return 1; + } + return 0; +} + +static int test_join_multiple() { + const char *segments[] = {"abc", "def", "ghi"}; + char *joined = join(sizeof(segments) / sizeof(char *), segments, '/'); + if (strcmp(joined, "abc/def/ghi") != 0) { + printf("joined != abc/def/ghi\n"); + return 1; + } + return 0; +} + +static int test_strcmp_ci() { + const char *a1 = "aBcD"; + const char *a2 = "AbCd"; + if (strcmp_ci(a1, a2) != 0) { + printf("strcmp_ci != 0\n"); + return 1; + } + + const char *b1 = "aBcDe"; + const char *b2 = "AbCd"; + if (strcmp_ci(b1, b2) <= 0) { + printf("strcmp_ci <= 0\n"); + return 1; + } + if (strcmp_ci(b2, b1) >= 0) { + printf("strcmp_ci >= 0\n"); + return 1; + } + + return 0; +} + +static int test_trim_leading() { + char a1[] = {0}; + char a2[] = {' ', ' ', ' ', 0}; + trim_leading(a1); + trim_leading(a2); + if (a1[0] != 0) { + printf("trim leading empty string\n"); + return 1; + } + if (strcmp(a1, a2) != 0) { + printf("trim leading whitespace string"); + return 1; + } + + char b1[] = {'a', 'b', 'c', 'd', 'e', 'f', 0}; + char b2[] = {' ', ' ', ' ', 'a', 'b', 'c', 'd', 'e', 'f', 0}; + trim_leading(b1); + trim_leading(b2); + if (strcmp(b1, b2) != 0) { + printf("trim leading string\n"); + return 1; + } + + char c1[] = {'a', 'b', 'c', 'd', 'e', 'f', ' ', ' ', ' ', 0}; + char c2[] = {'a', 'b', 'c', 'd', 'e', 'f', ' ', ' ', ' ', 0}; + trim_leading(c1); + if (strcmp(c1, c2) != 0) { + printf("trim leading ignore trailing\n"); + return 1; + } + + return 0; +} + +static int test_trim_trailing() { + char a1[] = {0}; + char a2[] = {' ', ' ', ' ', 0}; + trim_trailing(a1); + trim_trailing(a2); + if (a1[0] != 0) { + printf("trim trailing empty string\n"); + return 1; + } + if (strcmp(a1, a2) != 0) { + printf("trim trailing whitespace string\n"); + return 1; + } + + char b1[] = {'a', 'b', 'c', 'd', 'e', 'f', 0}; + char b2[] = {'a', 'b', 'c', 'd', 'e', 'f', ' ', ' ', ' ', 0}; + trim_trailing(b1); + trim_trailing(b2); + if (strcmp(b1, b2) != 0) { + printf("trim trailing string\n"); + return 1; + } + + char c1[] = {' ', ' ', ' ', 'a', 'b', 'c', 'd', 'e', 'f', 0}; + char c2[] = {' ', ' ', ' ', 'a', 'b', 'c', 'd', 'e', 'f', 0}; + trim_trailing(c1); + if (strcmp(c1, c2) != 0) { + printf("trim trailing ignore leading\n"); + return 1; + } + + return 0; +} + +int main(int argc, char **argv) { + if (argc != 2) { + printf("Expected exactly one argument."); + return 1; + } + + struct TestCase cases[] = { + {"join-single", test_join_single}, + {"join-multiple", test_join_multiple}, + {"strcmp-ci", test_strcmp_ci}, + {"trim-leading", test_trim_leading}, + {"trim-trailing", test_trim_trailing}, + }; + + for (int i = 0; i < sizeof(cases) / sizeof(struct TestCase); ++i) { + struct TestCase test_case = cases[i]; + if (strcmp(argv[1], test_case.name) == 0) { + return test_case.func(); + } + } + + return 1; +} diff --git a/test/test_string_utils.h b/test/test_string_utils.h deleted file mode 100644 index 9fc84cb..0000000 --- a/test/test_string_utils.h +++ /dev/null @@ -1,72 +0,0 @@ -#ifndef _BOOTSTRAP_TEST_STRING_UTILS -#define _BOOTSTRAP_TEST_STRING_UTILS - -#include "sput.h" -#include "string_utils.h" - -static void test_join_single() { - const char *segments[] = {"abc"}; - char *joined = join(sizeof(segments) / sizeof(char *), segments, '/'); - sput_fail_unless(strcmp(joined, "abc") == 0, "abc"); - free(joined); -} - -static void test_join_multiple() { - const char *segments[] = {"abc", "def", "ghi"}; - char *joined = join(sizeof(segments) / sizeof(char *), segments, '/'); - sput_fail_unless(strcmp(joined, "abc/def/ghi") == 0, "abc/def/ghi"); - free(joined); -} - -static void test_strcmp_ci() { - const char *a1 = "aBcD"; - const char *a2 = "AbCd"; - sput_fail_unless(strcmp_ci(a1, a2) == 0, "strcmp_ci == 0"); - - const char *b1 = "aBcDe"; - const char *b2 = "AbCd"; - sput_fail_unless(strcmp_ci(b1, b2) > 0, "strcmp_ci > 0"); - sput_fail_unless(strcmp_ci(b2, b1) < 0, "strcmp_ci < 0"); -} - -static void test_trim_leading() { - char a1[] = {0}; - char a2[] = {' ', ' ', ' ', 0}; - trim_leading(a1); - trim_leading(a2); - sput_fail_unless(a1[0] == 0, "trim leading empty string"); - sput_fail_unless(strcmp(a1, a2) == 0, "trim leading whitespace string"); - - char b1[] = {'a', 'b', 'c', 'd', 'e', 'f', 0}; - char b2[] = {' ', ' ', ' ', 'a', 'b', 'c', 'd', 'e', 'f', 0}; - trim_leading(b1); - trim_leading(b2); - sput_fail_unless(strcmp(b1, b2) == 0, "trim leading string"); - - char c1[] = {'a', 'b', 'c', 'd', 'e', 'f', ' ', ' ', ' ', 0}; - char c2[] = {'a', 'b', 'c', 'd', 'e', 'f', ' ', ' ', ' ', 0}; - trim_leading(c1); - sput_fail_unless(strcmp(c1, c2) == 0, "trim leading ignore trailing"); -} - -static void test_trim_trailing() { - char a1[] = {0}; - char a2[] = {' ', ' ', ' ', 0}; - trim_trailing(a1); - trim_trailing(a2); - sput_fail_unless(a1[0] == 0, "trim trailing empty string"); - sput_fail_unless(strcmp(a1, a2) == 0, "trim trailing whitespace string"); - - char b1[] = {'a', 'b', 'c', 'd', 'e', 'f', 0}; - char b2[] = {'a', 'b', 'c', 'd', 'e', 'f', ' ', ' ', ' ', 0}; - trim_trailing(b1); - trim_trailing(b2); - sput_fail_unless(strcmp(b1, b2) == 0, "trim trailing string"); - - char c1[] = {' ', ' ', ' ', 'a', 'b', 'c', 'd', 'e', 'f', 0}; - char c2[] = {' ', ' ', ' ', 'a', 'b', 'c', 'd', 'e', 'f', 0}; - trim_trailing(c1); - sput_fail_unless(strcmp(c1, c2) == 0, "trim trailing ignore leading"); -} - -#endif /* _BOOTSTRAP_TEST_STRING_UTILS */ diff --git a/test/test_validator.c b/test/test_validator.c new file mode 100644 index 0000000..b767ba4 --- /dev/null +++ b/test/test_validator.c @@ -0,0 +1,287 @@ +#include +#include +#include + +#include "dyn_array.h" +#include "string_utils.h" +#include "test_cases.h" +#include "validator.h" + +struct TestValidatorFixture { + const char *json; + struct DynArray *prompts; + cJSON *parsed; + struct Config config; +}; + +static struct TestValidatorFixture *test_setup(const char *json) { + struct TestValidatorFixture *fixture = + malloc(sizeof(struct TestValidatorFixture)); + fixture->json = json; + fixture->prompts = 0; + fixture->parsed = cJSON_Parse(json); + + char *cwd = getcwd(0, 0); + const char *segments[] = {cwd, "test", "specs"}; + char *root_dir = join(sizeof(segments) / sizeof(char *), segments, '/'); + + fixture->config.cwd = cwd; + fixture->config.root_dir = root_dir; + fixture->config.target = "minimal_spec_json"; + + return fixture; +} + +static int test_toplevel_not_object() { + struct TestValidatorFixture *fixture = test_setup("[]"); + + struct Error *error = + validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts); + if (error->code != ERROR_VALIDATOR_TOP_LEVEL_NOT_OBJECT) { + printf("top-level not object\n"); + return 1; + } + + return 0; +} + +static int test_field_not_object() { + struct TestValidatorFixture *fixture = test_setup("{\"key\": \"$UNKNOWN\"}"); + + struct Error *error = + validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts); + if (error->code != ERROR_VALIDATOR_FIELD_NOT_OBJECT) { + printf("field not object\n"); + return 1; + } + + return 0; +} + +static int test_field_name_leading_digit() { + struct TestValidatorFixture *fixture = test_setup( + "{" + " \"1abc\": {" + " \"type\": \"line\"" + " }" + "}" + ); + + struct Error *error = + validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts); + if (error->code != ERROR_VALIDATOR_FIELD_NAME_INVALID) { + printf("field name leading digit\n"); + return 1; + } + + return 0; +} + +static int test_field_name_non_alnum() { + struct TestValidatorFixture *fixture = test_setup( + "{" + " \"a~bc\": {" + " \"type\": \"line\"" + " }" + "}" + ); + + struct Error *error = + validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts); + if (error->code != ERROR_VALIDATOR_FIELD_NAME_INVALID) { + printf("field name non alnum\n"); + return 1; + } + + return 0; +} + +static int test_field_type_invalid() { + struct TestValidatorFixture *fixture = test_setup( + "{" + " \"key\": {" + " \"type\": 2" + " }" + "}" + ); + + struct Error *error = + validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts); + if (error->code != ERROR_VALIDATOR_FIELD_TYPE_INVALID) { + printf("field type invalid\n"); + return 1; + } + + return 0; +} + +static int test_field_type_unknown() { + struct TestValidatorFixture *fixture = test_setup( + "{" + " \"key\": {" + " \"type\": \"UNKNOWN\"" + " }" + "}" + ); + + struct Error *error = + validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts); + if (error->code != ERROR_VALIDATOR_FIELD_TYPE_UNKNOWN) { + printf("field type unknown\n"); + return 1; + } + + return 0; +} + +static int test_valid_type_ci() { + struct TestValidatorFixture *fixture = test_setup( + "{" + " \"key\": {" + " \"type\": \"LiNe\"," + " \"prompt\": \"What value for key?\"" + " }" + "}" + ); + + struct Error *error = + validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts); + if (error != 0) { + printf("valid ci not working\n"); + return 1; + } + + return 0; +} + +static int test_field_required_invalid() { + struct TestValidatorFixture *fixture = test_setup( + "{" + " \"key\": {" + " \"type\": \"line\"," + " \"required\": 5" + " }" + "}" + ); + + struct Error *error = + validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts); + if (error->code != ERROR_VALIDATOR_FIELD_REQUIRED_INVALID) { + printf("field required invalid\n"); + return 1; + } + + return 0; +} + +static int test_field_required_valid() { + struct TestValidatorFixture *fixture = test_setup( + "{" + " \"key\": {" + " \"type\": \"line\"," + " \"required\": true," + " \"prompt\": \"What value for key? \"" + " }" + "}" + ); + + struct Error *error = + validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts); + if (error != 0) { + printf("required valid\n"); + return 1; + } + + return 0; +} + +static int test_field_prompt_invalid() { + struct TestValidatorFixture *fixture = test_setup( + "{" + " \"key\": {" + " \"type\": \"line\"," + " \"prompt\": 2" + " }" + "}" + ); + + struct Error *error = + validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts); + if (error->code != ERROR_VALIDATOR_FIELD_PROMPT_INVALID) { + printf("field prompt invalid\n"); + return 1; + } + + return 0; +} + +static int test_valid_no_required() { + struct TestValidatorFixture *fixture = test_setup( + "{" + " \"key\": {" + " \"type\": \"line\"," + " \"prompt\": \"What value for key?\"" + " }" + "}" + ); + + struct Error *error = + validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts); + if (error != 0) { + printf("valid\n"); + return 1; + } + + return 0; +} + +static int test_field_type_yes() { + struct TestValidatorFixture *fixture = test_setup( + "{" + " \"abc\": {" + " \"type\": \"yes\"" + " \"prompt\": \"What value for key?\"" + " }" + "}" + ); + + struct Error *error = + validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts); + if (error != 0) { + printf("yes valid\n"); + return 1; + } + + return 0; +} + +int main(int argc, char **argv) { + if (argc != 2) { + printf("Expected exactly one argument."); + return 1; + } + + struct TestCase cases[] = { + {"toplevel-not-object", test_toplevel_not_object}, + {"field-not-object", test_field_not_object}, + {"field-name-leading-digit", test_field_name_leading_digit}, + {"field-name-non-alnum", test_field_name_non_alnum}, + {"field-type-invalid", test_field_type_invalid}, + {"field-type-unknown", test_field_type_unknown}, + {"valid-type-ci", test_valid_type_ci}, + {"field-required-invalid", test_field_required_invalid}, + {"field-required-valid", test_field_required_valid}, + {"field-prompt-invalid", test_field_prompt_invalid}, + {"valid-no-required", test_valid_no_required}, + {"field-type-yes", test_field_type_yes}, + }; + + for (int i = 0; i < sizeof(cases) / sizeof(struct TestCase); ++i) { + struct TestCase test_case = cases[i]; + if (strcmp(argv[1], test_case.name) == 0) { + return test_case.func(); + } + } + + return 1; +} diff --git a/test/test_validator.h b/test/test_validator.h deleted file mode 100644 index 7ba89e6..0000000 --- a/test/test_validator.h +++ /dev/null @@ -1,260 +0,0 @@ -#ifndef _BOOTSTRAP_TEST_VALIDATOR -#define _BOOTSTRAP_TEST_VALIDATOR - -#include - -#include "dyn_array.h" -#include "sput.h" -#include "string_utils.h" -#include "validator.h" - -struct TestValidatorFixture { - const char *json; - struct DynArray *prompts; - cJSON *parsed; - struct Config config; -}; - -static struct TestValidatorFixture *test_validator_setup(const char *json) { - struct TestValidatorFixture *fixture = - malloc(sizeof(struct TestValidatorFixture)); - fixture->json = json; - fixture->prompts = 0; - fixture->parsed = cJSON_Parse(json); - - char *cwd = getcwd(0, 0); - const char *segments[] = {cwd, "test", "specs"}; - char *root_dir = join(sizeof(segments) / sizeof(char *), segments, '/'); - - fixture->config.cwd = cwd; - fixture->config.root_dir = root_dir; - fixture->config.target = "minimal_spec_json"; - - return fixture; -} - -static void test_validator_teardown(struct TestValidatorFixture *fixture) { - if (fixture->parsed) { - cJSON_Delete(fixture->parsed); - } - free((void *)fixture->config.cwd); - free((void *)fixture->config.root_dir); - free(fixture); -} - -static void test_validator_toplevel_not_object() { - struct TestValidatorFixture *fixture = test_validator_setup("[]"); - - struct Error *error = - validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts); - sput_fail_unless( - error->code == ERROR_VALIDATOR_TOP_LEVEL_NOT_OBJECT, "top-level not object" - ); - error_free(error); - - test_validator_teardown(fixture); -} - -static void test_validator_field_not_object() { - struct TestValidatorFixture *fixture = - test_validator_setup("{\"key\": \"$UNKNOWN\"}"); - - struct Error *error = - validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts); - sput_fail_unless( - error->code == ERROR_VALIDATOR_FIELD_NOT_OBJECT, "field not object" - ); - error_free(error); - - test_validator_teardown(fixture); -} - -static void test_validator_field_name_leading_digit() { - struct TestValidatorFixture *fixture = test_validator_setup( - "{" - " \"1abc\": {" - " \"type\": \"line\"" - " }" - "}" - ); - - struct Error *error = - validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts); - sput_fail_unless( - error->code == ERROR_VALIDATOR_FIELD_NAME_INVALID, - "field name leading digit" - ); - error_free(error); - - test_validator_teardown(fixture); -} - -static void test_validator_field_name_non_alnum() { - struct TestValidatorFixture *fixture = test_validator_setup( - "{" - " \"a~bc\": {" - " \"type\": \"line\"" - " }" - "}" - ); - - struct Error *error = - validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts); - sput_fail_unless( - error->code == ERROR_VALIDATOR_FIELD_NAME_INVALID, "field name non alnum" - ); - error_free(error); - - test_validator_teardown(fixture); -} - -static void test_validator_field_type_invalid() { - struct TestValidatorFixture *fixture = test_validator_setup( - "{" - " \"key\": {" - " \"type\": 2" - " }" - "}" - ); - - struct Error *error = - validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts); - sput_fail_unless( - error->code == ERROR_VALIDATOR_FIELD_TYPE_INVALID, "field type invalid" - ); - error_free(error); - - test_validator_teardown(fixture); -} - -static void test_validator_field_type_unknown() { - struct TestValidatorFixture *fixture = test_validator_setup( - "{" - " \"key\": {" - " \"type\": \"UNKNOWN\"" - " }" - "}" - ); - - struct Error *error = - validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts); - sput_fail_unless( - error->code == ERROR_VALIDATOR_FIELD_TYPE_UNKNOWN, "field type unknown" - ); - error_free(error); - - test_validator_teardown(fixture); -} - -static void test_validator_valid_type_ci() { - struct TestValidatorFixture *fixture = test_validator_setup( - "{" - " \"key\": {" - " \"type\": \"LiNe\"," - " \"prompt\": \"What value for key?\"" - " }" - "}" - ); - - struct Error *error = - validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts); - sput_fail_unless(error == 0, "valid"); - - test_validator_teardown(fixture); -} - -static void test_validator_field_required_invalid() { - struct TestValidatorFixture *fixture = test_validator_setup( - "{" - " \"key\": {" - " \"type\": \"line\"," - " \"required\": 5" - " }" - "}" - ); - - struct Error *error = - validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts); - sput_fail_unless( - error->code == ERROR_VALIDATOR_FIELD_REQUIRED_INVALID, - "field required invalid" - ); - error_free(error); - - test_validator_teardown(fixture); -} - -static void test_validator_field_required_valid() { - struct TestValidatorFixture *fixture = test_validator_setup( - "{" - " \"key\": {" - " \"type\": \"line\"," - " \"required\": true," - " \"prompt\": \"What value for key? \"" - " }" - "}" - ); - - struct Error *error = - validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts); - sput_fail_unless(error == 0, "required valid"); - error_free(error); - - test_validator_teardown(fixture); -} - -static void test_validator_field_prompt_invalid() { - struct TestValidatorFixture *fixture = test_validator_setup( - "{" - " \"key\": {" - " \"type\": \"line\"," - " \"prompt\": 2" - " }" - "}" - ); - - struct Error *error = - validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts); - sput_fail_unless( - error->code == ERROR_VALIDATOR_FIELD_PROMPT_INVALID, "field prompt invalid" - ); - error_free(error); - - test_validator_teardown(fixture); -} - -static void test_validator_valid_no_required() { - struct TestValidatorFixture *fixture = test_validator_setup( - "{" - " \"key\": {" - " \"type\": \"line\"," - " \"prompt\": \"What value for key?\"" - " }" - "}" - ); - - struct Error *error = - validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts); - sput_fail_unless(error == 0, "valid"); - - test_validator_teardown(fixture); -} - -static void test_validator_field_type_yes() { - struct TestValidatorFixture *fixture = test_validator_setup( - "{" - " \"abc\": {" - " \"type\": \"yes\"" - " \"prompt\": \"What value for key?\"" - " }" - "}" - ); - - struct Error *error = - validate_spec_json(&fixture->config, fixture->parsed, &fixture->prompts); - sput_fail_unless(error == 0, "yes valid"); - - test_validator_teardown(fixture); -} - -#endif /* _BOOTSTRAP_TEST_VALIDATOR */