feat(testing): expose complete iKvxx test suite
This commit is contained in:
@@ -1,56 +1,55 @@
|
|||||||
name: C++ bindings
|
name: Build
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
cmake-build:
|
||||||
name: ${{ matrix.compiler }} / ${{ matrix.build_type }}
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 10
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- compiler: gcc
|
|
||||||
cc: gcc
|
|
||||||
cxx: g++
|
|
||||||
build_type: Debug
|
|
||||||
- compiler: gcc
|
|
||||||
cc: gcc
|
|
||||||
cxx: g++
|
|
||||||
build_type: Release
|
|
||||||
- compiler: clang
|
|
||||||
cc: clang
|
|
||||||
cxx: clang++
|
|
||||||
build_type: Debug
|
|
||||||
- compiler: clang
|
|
||||||
cc: clang
|
|
||||||
cxx: clang++
|
|
||||||
build_type: Release
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout with submodules
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
- name: Configure
|
- name: Install build tools
|
||||||
env:
|
run: |
|
||||||
CC: ${{ matrix.cc }}
|
if ! command -v c++ >/dev/null || ! command -v cmake >/dev/null || ! command -v ninja >/dev/null; then
|
||||||
CXX: ${{ matrix.cxx }}
|
apt-get update
|
||||||
run: >-
|
DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential cmake ninja-build
|
||||||
cmake -S . -B build
|
fi
|
||||||
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
|
|
||||||
-DIKVXX_BUILD_TESTS=ON
|
|
||||||
-DIKVXX_INSTALL=ON
|
|
||||||
|
|
||||||
- name: Build
|
- name: Configure CMake build
|
||||||
run: cmake --build build --parallel 2
|
run: |
|
||||||
|
cmake -S . -B build -G Ninja -DIKVXX_BUILD_TESTS=OFF
|
||||||
|
|
||||||
- name: Test bindings
|
- name: Build with CMake
|
||||||
run: ctest --test-dir build --build-config ${{ matrix.build_type }} --output-on-failure
|
run: |
|
||||||
|
cmake --build build
|
||||||
|
|
||||||
- name: Verify installation
|
unit-tests:
|
||||||
run: cmake --install build --prefix "${{ runner.temp }}/ikvxx-install"
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Install build tools
|
||||||
|
run: |
|
||||||
|
if ! command -v c++ >/dev/null || ! command -v cmake >/dev/null || ! command -v ninja >/dev/null; then
|
||||||
|
apt-get update
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential cmake ninja-build
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Configure unit tests
|
||||||
|
run: |
|
||||||
|
cmake -S . -B build-tests -G Ninja -DIKVXX_BUILD_TESTS=ON -DIKVXX_INSTALL=OFF
|
||||||
|
|
||||||
|
- name: Run unit tests
|
||||||
|
run: |
|
||||||
|
cmake --build build-tests
|
||||||
|
ctest --test-dir build-tests --output-on-failure
|
||||||
|
|||||||
@@ -5,8 +5,12 @@ project(ikvxx VERSION 0.1.0 LANGUAGES C CXX)
|
|||||||
option(IKVXX_BUILD_TESTS "Build iKvxx unit tests" ON)
|
option(IKVXX_BUILD_TESTS "Build iKvxx unit tests" ON)
|
||||||
option(IKVXX_INSTALL "Install iKvxx" ON)
|
option(IKVXX_INSTALL "Install iKvxx" ON)
|
||||||
|
|
||||||
|
if(IKVXX_BUILD_TESTS)
|
||||||
|
include(CTest)
|
||||||
|
endif()
|
||||||
|
|
||||||
set(IKV_BUILD_DEMOS OFF CACHE BOOL "" FORCE)
|
set(IKV_BUILD_DEMOS OFF CACHE BOOL "" FORCE)
|
||||||
set(IKV_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
set(IKV_BUILD_TESTS ${IKVXX_BUILD_TESTS} CACHE BOOL "" FORCE)
|
||||||
set(IKV_INSTALL OFF CACHE BOOL "" FORCE)
|
set(IKV_INSTALL OFF CACHE BOOL "" FORCE)
|
||||||
add_subdirectory(third_party/iKv)
|
add_subdirectory(third_party/iKv)
|
||||||
set_target_properties(ikv PROPERTIES
|
set_target_properties(ikv PROPERTIES
|
||||||
@@ -27,11 +31,24 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(IKVXX_BUILD_TESTS)
|
if(IKVXX_BUILD_TESTS)
|
||||||
include(CTest)
|
|
||||||
add_executable(ikvxx_tests tests/value_tests.cpp)
|
add_executable(ikvxx_tests tests/value_tests.cpp)
|
||||||
target_link_libraries(ikvxx_tests PRIVATE ikvxx::ikvxx)
|
target_link_libraries(ikvxx_tests PRIVATE ikvxx::ikvxx)
|
||||||
target_compile_features(ikvxx_tests PRIVATE cxx_std_17)
|
target_compile_features(ikvxx_tests PRIVATE cxx_std_17)
|
||||||
add_test(NAME ikvxx.value COMMAND ikvxx_tests)
|
|
||||||
|
set(IKVXX_TEST_CASES
|
||||||
|
construction_and_type_inspection
|
||||||
|
assignments_and_conversions
|
||||||
|
objects_members_and_lookup_overloads
|
||||||
|
arrays_and_index_overloads
|
||||||
|
text_parsing_and_files
|
||||||
|
binary_memory_and_files
|
||||||
|
refresh_and_ownership)
|
||||||
|
|
||||||
|
foreach(IKVXX_TEST_CASE IN LISTS IKVXX_TEST_CASES)
|
||||||
|
add_test(
|
||||||
|
NAME "ikvxx.${IKVXX_TEST_CASE}"
|
||||||
|
COMMAND ikvxx_tests --case "${IKVXX_TEST_CASE}")
|
||||||
|
endforeach()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(IKVXX_INSTALL)
|
if(IKVXX_INSTALL)
|
||||||
|
|||||||
10
README.md
10
README.md
@@ -243,10 +243,12 @@ cmake --build build
|
|||||||
ctest --test-dir build --output-on-failure
|
ctest --test-dir build --output-on-failure
|
||||||
```
|
```
|
||||||
|
|
||||||
The test suite covers construction, every value type, object and array access,
|
CTest runs every iKvxx test group separately and also includes the complete upstream
|
||||||
assignment and append overloads, conversion failures, bounds checks, text and binary
|
iKv suite. The binding tests cover construction, every value type, object and array
|
||||||
I/O, format versions, refresh behavior, and shared ownership. Gitea Actions runs the
|
access, assignment and append overloads, conversion failures, bounds checks, text and
|
||||||
suite on Ubuntu with GCC and Clang in both Debug and Release configurations.
|
binary I/O, format versions, refresh behavior, and shared ownership. The Gitea Actions
|
||||||
|
workflow follows iKv's Ubuntu build-and-test structure, with one CMake build job and
|
||||||
|
one job that runs the complete test suite.
|
||||||
|
|
||||||
On Windows, the complete configure, build, and test sequence can be run with:
|
On Windows, the complete configure, build, and test sequence can be run with:
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ struct ikv_node_t;
|
|||||||
|
|
||||||
namespace ikv {
|
namespace ikv {
|
||||||
|
|
||||||
|
/// Runtime value types exposed by iKv.
|
||||||
enum ValueType {
|
enum ValueType {
|
||||||
nullValue = 0,
|
nullValue = 0,
|
||||||
stringValue,
|
stringValue,
|
||||||
@@ -21,26 +22,44 @@ enum ValueType {
|
|||||||
arrayValue
|
arrayValue
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// On-disk iKv format versions supported for explicit writes.
|
||||||
enum class Version : unsigned { v1 = 1, v2 = 2 };
|
enum class Version : unsigned { v1 = 1, v2 = 2 };
|
||||||
|
|
||||||
|
/// Error raised for invalid types, unsupported operations, parse failures,
|
||||||
|
/// and I/O failures.
|
||||||
class Error : public std::runtime_error {
|
class Error : public std::runtime_error {
|
||||||
public:
|
public:
|
||||||
using std::runtime_error::runtime_error;
|
using std::runtime_error::runtime_error;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// RAII handle to an iKv root or a node owned by an iKv tree.
|
||||||
|
///
|
||||||
|
/// Copies share ownership of the same tree. Child handles remain valid while their
|
||||||
|
/// node exists, but should be reacquired after replacing that node or refreshing the root.
|
||||||
class Value {
|
class Value {
|
||||||
public:
|
public:
|
||||||
using ArrayIndex = std::uint32_t;
|
using ArrayIndex = std::uint32_t;
|
||||||
|
|
||||||
|
/// Creates an object or array root. Scalar roots are not supported by iKv.
|
||||||
explicit Value(ValueType type = objectValue, std::string root_name = "root");
|
explicit Value(ValueType type = objectValue, std::string root_name = "root");
|
||||||
Value(const Value&) = default;
|
Value(const Value&) = default;
|
||||||
|
|
||||||
|
/// Structural assignment is unavailable because iKv cannot clone arbitrary subtrees.
|
||||||
Value& operator=(const Value&) = delete;
|
Value& operator=(const Value&) = delete;
|
||||||
|
|
||||||
|
/// Parses auto-detected iKv1 or iKv2 text.
|
||||||
static Value parse(const std::string& text);
|
static Value parse(const std::string& text);
|
||||||
|
|
||||||
|
/// Loads an auto-detected iKv1 or iKv2 text file.
|
||||||
static Value load(const std::string& path);
|
static Value load(const std::string& path);
|
||||||
|
|
||||||
|
/// Parses an iKv binary buffer. The resulting tree owns any required data.
|
||||||
static Value fromBinary(const void* data, std::size_t size);
|
static Value fromBinary(const void* data, std::size_t size);
|
||||||
|
|
||||||
|
/// Loads an auto-detected binary iKv file.
|
||||||
static Value loadBinary(const std::string& path);
|
static Value loadBinary(const std::string& path);
|
||||||
|
|
||||||
|
/// Returns the node's runtime type, or nullValue for a missing member.
|
||||||
ValueType type() const noexcept;
|
ValueType type() const noexcept;
|
||||||
bool isNull() const noexcept;
|
bool isNull() const noexcept;
|
||||||
bool isString() const noexcept;
|
bool isString() const noexcept;
|
||||||
@@ -54,6 +73,8 @@ public:
|
|||||||
std::string name() const;
|
std::string name() const;
|
||||||
|
|
||||||
bool isMember(const std::string& key) const;
|
bool isMember(const std::string& key) const;
|
||||||
|
|
||||||
|
/// Looks up an object member. A missing non-const member returns an assignable null Value.
|
||||||
Value operator[](const std::string& key);
|
Value operator[](const std::string& key);
|
||||||
Value operator[](const char* key);
|
Value operator[](const char* key);
|
||||||
Value operator[](const std::string& key) const;
|
Value operator[](const std::string& key) const;
|
||||||
@@ -62,8 +83,11 @@ public:
|
|||||||
Value operator[](int index);
|
Value operator[](int index);
|
||||||
Value operator[](ArrayIndex index) const;
|
Value operator[](ArrayIndex index) const;
|
||||||
Value operator[](int index) const;
|
Value operator[](int index) const;
|
||||||
|
|
||||||
|
/// Returns an object member or a shared copy of fallback when the key is absent.
|
||||||
Value get(const std::string& key, const Value& fallback) const;
|
Value get(const std::string& key, const Value& fallback) const;
|
||||||
|
|
||||||
|
/// Assigns a scalar to an object member returned by operator[].
|
||||||
Value& operator=(const std::string& value);
|
Value& operator=(const std::string& value);
|
||||||
Value& operator=(const char* value);
|
Value& operator=(const char* value);
|
||||||
Value& operator=(std::int64_t value);
|
Value& operator=(std::int64_t value);
|
||||||
@@ -71,8 +95,13 @@ public:
|
|||||||
Value& operator=(double value);
|
Value& operator=(double value);
|
||||||
Value& operator=(bool value);
|
Value& operator=(bool value);
|
||||||
|
|
||||||
|
/// Adds or replaces an object member and returns a handle to it.
|
||||||
Value makeObject(const std::string& key);
|
Value makeObject(const std::string& key);
|
||||||
|
|
||||||
|
/// Adds an array member. nullValue creates an untyped array.
|
||||||
Value makeArray(const std::string& key, ValueType element_type = nullValue);
|
Value makeArray(const std::string& key, ValueType element_type = nullValue);
|
||||||
|
|
||||||
|
/// Appends to an array. Typed arrays reject incompatible values with Error.
|
||||||
Value append(const std::string& value);
|
Value append(const std::string& value);
|
||||||
Value append(const char* value);
|
Value append(const char* value);
|
||||||
Value append(std::int64_t value);
|
Value append(std::int64_t value);
|
||||||
@@ -87,9 +116,13 @@ public:
|
|||||||
double asDouble() const;
|
double asDouble() const;
|
||||||
bool asBool() const;
|
bool asBool() const;
|
||||||
|
|
||||||
|
/// Writes text or binary output, using iKv2 unless a version is specified.
|
||||||
void write(const std::string& path, Version version = Version::v2) const;
|
void write(const std::string& path, Version version = Version::v2) const;
|
||||||
void writeBinary(const std::string& path, Version version = Version::v2) const;
|
void writeBinary(const std::string& path, Version version = Version::v2) const;
|
||||||
std::vector<std::uint8_t> toBinary(Version version = Version::v2) const;
|
std::vector<std::uint8_t> toBinary(Version version = Version::v2) const;
|
||||||
|
|
||||||
|
/// Replaces this root in place from a text or binary path while preserving shared handles.
|
||||||
|
/// Only mutable root Values can be refreshed; failure leaves the existing tree unchanged.
|
||||||
void refresh(const std::string& path);
|
void refresh(const std::string& path);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -36,6 +36,12 @@ if errorlevel 1 (
|
|||||||
)
|
)
|
||||||
|
|
||||||
echo Running iKvxx tests...
|
echo Running iKvxx tests...
|
||||||
|
ctest --test-dir "%BUILD_DIR%" --build-config "%CONFIG%" --show-only
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo Error: Could not enumerate tests.
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
ctest --test-dir "%BUILD_DIR%" ^
|
ctest --test-dir "%BUILD_DIR%" ^
|
||||||
--build-config "%CONFIG%" ^
|
--build-config "%CONFIG%" ^
|
||||||
--output-on-failure
|
--output-on-failure
|
||||||
|
|||||||
@@ -368,14 +368,40 @@ void testRefreshAndOwnership() {
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
int main() {
|
int main(int argc, char** argv) {
|
||||||
testConstructionAndTypeInspection();
|
struct TestCase {
|
||||||
testAssignmentsAndConversions();
|
const char* name;
|
||||||
testObjectsMembersAndLookupOverloads();
|
void (*function)();
|
||||||
testArraysAndIndexOverloads();
|
};
|
||||||
testTextParsingAndFiles();
|
|
||||||
testBinaryMemoryAndFiles();
|
const TestCase tests[] = {
|
||||||
testRefreshAndOwnership();
|
{"construction_and_type_inspection", testConstructionAndTypeInspection},
|
||||||
|
{"assignments_and_conversions", testAssignmentsAndConversions},
|
||||||
|
{"objects_members_and_lookup_overloads", testObjectsMembersAndLookupOverloads},
|
||||||
|
{"arrays_and_index_overloads", testArraysAndIndexOverloads},
|
||||||
|
{"text_parsing_and_files", testTextParsingAndFiles},
|
||||||
|
{"binary_memory_and_files", testBinaryMemoryAndFiles},
|
||||||
|
{"refresh_and_ownership", testRefreshAndOwnership}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (argc == 3 && std::string(argv[1]) == "--case") {
|
||||||
|
for (const auto& test : tests) {
|
||||||
|
if (argv[2] == std::string(test.name)) {
|
||||||
|
test.function();
|
||||||
|
if (failures != 0) std::cerr << failures << " test(s) failed\n";
|
||||||
|
return failures == 0 ? 0 : 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::cerr << "unknown test case: " << argv[2] << '\n';
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argc != 1) {
|
||||||
|
std::cerr << "usage: ikvxx_tests [--case <name>]\n";
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& test : tests) test.function();
|
||||||
if (failures != 0) std::cerr << failures << " test(s) failed\n";
|
if (failures != 0) std::cerr << failures << " test(s) failed\n";
|
||||||
return failures == 0 ? 0 : 1;
|
return failures == 0 ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user