Migrate to CMake.
parent
5ad9ed7f06
commit
5dd6035dcd
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
81
Makefile
81
Makefile
|
@ -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
|
51
README.md
51
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.
|
||||
|
|
|
@ -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
|
||||
];
|
||||
|
|
|
@ -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
|
||||
)
|
|
@ -87,5 +87,4 @@ void config_free(struct Config *config) {
|
|||
return;
|
||||
}
|
||||
free(config);
|
||||
config = 0;
|
||||
}
|
||||
|
|
|
@ -36,5 +36,4 @@ void dyn_array_free(struct DynArray *a) {
|
|||
}
|
||||
free(a->buf);
|
||||
free(a);
|
||||
a = 0;
|
||||
}
|
||||
|
|
|
@ -6,5 +6,4 @@ void error_free(struct Error *error) {
|
|||
}
|
||||
free((void *)error->message);
|
||||
free(error);
|
||||
error = 0;
|
||||
}
|
||||
|
|
|
@ -60,5 +60,4 @@ void string_buf_free(struct StringBuf *sb) {
|
|||
}
|
||||
free((void *)sb->buf);
|
||||
free(sb);
|
||||
sb = 0;
|
||||
}
|
||||
|
|
|
@ -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()
|
0
test/specs/invalid_spec_json/run.sh → test/specs/invalid_spec_json/runner
Normal file → Executable file
0
test/specs/invalid_spec_json/run.sh → test/specs/invalid_spec_json/runner
Normal file → Executable file
0
test/specs/minimal_spec_json/run.sh → test/specs/minimal_spec_json/runner
Normal file → Executable file
0
test/specs/minimal_spec_json/run.sh → test/specs/minimal_spec_json/runner
Normal file → Executable file
300
test/sput.h
300
test/sput.h
|
@ -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: */
|
|
@ -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();
|
||||
}
|
|
@ -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 */
|
|
@ -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;
|
||||
}
|
|
@ -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 */
|
|
@ -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;
|
||||
}
|
|
@ -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 */
|
|
@ -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;
|
||||
}
|
|
@ -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 */
|
|
@ -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;
|
||||
}
|
|
@ -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 */
|
|
@ -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;
|
||||
}
|
|
@ -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 */
|
|
@ -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;
|
||||
}
|
|
@ -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 */
|
Loading…
Reference in New Issue