Migrate to CMake.

pull/1/head
Joshua Potter 2023-12-19 16:58:19 -07:00
parent 5ad9ed7f06
commit 5dd6035dcd
30 changed files with 1037 additions and 1083 deletions

View File

@ -10,7 +10,7 @@ STAGED=$(
TARGETS=() TARGETS=()
while IFS= read -r FILENAME while IFS= read -r FILENAME
do do
if [[ "$FILENAME" =~ .*\.c$ ]] || [[ "$FILENAME" == .*\.h$ ]]; then if [[ "$FILENAME" =~ .*\.c(pp)?$ ]] || [[ "$FILENAME" =~ .*\.h(pp)?$ ]]; then
TARGETS+=("${FILENAME}") TARGETS+=("${FILENAME}")
fi fi
done <<< "$STAGED" done <<< "$STAGED"

24
.gitignore vendored
View File

@ -1,17 +1,27 @@
# Directory used by clangd LSP. # Directory used by clangd LSP.
.cache/ /.cache/
# Directory used by `direnv` to hold `use flake`-generated profiles. # Directory used by `direnv` to hold `use flake`-generated profiles.
.direnv/ /.direnv/
# The compilation database produced by `bear`.
compile_commands.json
# The directory containing all build outputs. # The directory containing all build outputs.
dist/ /build/
# The directory generated by `Doxygen`. # The directory generated by `Doxygen`.
docs/ /docs/
# A symlink produced by default when running `nix build`. # A symlink produced by default when running `nix build`.
result 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

20
CMakeLists.txt Normal file
View File

@ -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()

View File

@ -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

View File

@ -70,15 +70,6 @@ If flakes is not enabled or your nix version does not support
})).packages.${system}.default; })).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 ## Usage
### Runners ### 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 them. If interested in adding more capabilities, please send a PR or just fork
the project for your own purposes. 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 ### Testing
We use [Sput](https://www.use-strict.de/sput-unit-testing/) for unit tests. To We use [CTest](https://cmake.org/cmake/help/latest/module/CTest.html) (version
run tests, type: 3.27.7) for unit testing. To run the tests, navigate to `build/Debug` and type
the following:
```bash ```bash
$ cmake --build .
$ make test $ make test
``` ```
Tests are located in the `test` directory. `test/suites.c` serves as the
entrypoint for the test runner.
### Documentation ### Documentation
We use [doxygen](https://www.doxygen.nl/index.html) for documentation We use [doxygen](https://www.doxygen.nl/index.html) for documentation
generation. Run either of the following two commands to generate documentation generation. Run the following command to generate documentation locally:
locally:
```bash ```bash
$ make docs
$ doxygen $ doxygen
``` ```
@ -245,10 +262,8 @@ $ doxygen
We use `clang-format` to ensure consistent formatting. A `pre-commit` file is 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` included in `.githooks` to enforce usage. Run the following to configure `git`
to use it: to use it:
```bash ```bash
git config --local core.hooksPath .githooks/ git config --local core.hooksPath .githooks/
``` ```
If running [direnv](https://direnv.net/), this is done automatically upon If running [direnv](https://direnv.net/), this is done automatically upon
entering the project directory. entering the project directory.

View File

@ -24,7 +24,6 @@
pname = "bootstrap"; pname = "bootstrap";
src = ./.; src = ./.;
version = "0.1.3"; version = "0.1.3";
makeFlags = [ "BUILD=release" "PREFIX=$(out)" ];
dontInstall = true; dontInstall = true;
}; };
@ -40,8 +39,8 @@
stdenv = pkgs.clangStdenv; stdenv = pkgs.clangStdenv;
} { } {
packages = with pkgs; [ packages = with pkgs; [
bear
clang-tools clang-tools
cmake
codelldb codelldb
doxygen doxygen
]; ];

12
src/CMakeLists.txt Normal file
View File

@ -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
)

View File

@ -87,5 +87,4 @@ void config_free(struct Config *config) {
return; return;
} }
free(config); free(config);
config = 0;
} }

View File

@ -36,5 +36,4 @@ void dyn_array_free(struct DynArray *a) {
} }
free(a->buf); free(a->buf);
free(a); free(a);
a = 0;
} }

View File

@ -6,5 +6,4 @@ void error_free(struct Error *error) {
} }
free((void *)error->message); free((void *)error->message);
free(error); free(error);
error = 0;
} }

View File

@ -60,5 +60,4 @@ void string_buf_free(struct StringBuf *sb) {
} }
free((void *)sb->buf); free((void *)sb->buf);
free(sb); free(sb);
sb = 0;
} }

106
test/CMakeLists.txt Normal file
View File

@ -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()

View File

View File

@ -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 <alex@use-strict.de>
*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
/* ===================================================================
* 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: */

View File

@ -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();
}

9
test/test_cases.h Normal file
View File

@ -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 */

122
test/test_config.c Normal file
View File

@ -0,0 +1,122 @@
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#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;
}

View File

@ -1,103 +0,0 @@
#ifndef _BOOTSTRAP_TEST_CONFIG
#define _BOOTSTRAP_TEST_CONFIG
#include <unistd.h>
#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 */

111
test/test_dyn_array.c Normal file
View File

@ -0,0 +1,111 @@
#include <stdio.h>
#include <string.h>
#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;
}

View File

@ -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 */

114
test/test_parser.c Normal file
View File

@ -0,0 +1,114 @@
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#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;
}

View File

@ -1,84 +0,0 @@
#ifndef _BOOTSTRAP_TEST_PARSER
#define _BOOTSTRAP_TEST_PARSER
#include <unistd.h>
#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 */

65
test/test_string_buf.c Normal file
View File

@ -0,0 +1,65 @@
#include <stdio.h>
#include <string.h>
#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;
}

View File

@ -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 */

139
test/test_string_utils.c Normal file
View File

@ -0,0 +1,139 @@
#include <stdio.h>
#include <string.h>
#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;
}

View File

@ -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 */

287
test/test_validator.c Normal file
View File

@ -0,0 +1,287 @@
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#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;
}

View File

@ -1,260 +0,0 @@
#ifndef _BOOTSTRAP_TEST_VALIDATOR
#define _BOOTSTRAP_TEST_VALIDATOR
#include <unistd.h>
#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 */