feat(testing): expose complete iKvxx test suite
This commit is contained in:
@@ -1,56 +1,55 @@
|
||||
name: C++ bindings
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: ${{ matrix.compiler }} / ${{ matrix.build_type }}
|
||||
cmake-build:
|
||||
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:
|
||||
- name: Checkout with submodules
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Configure
|
||||
env:
|
||||
CC: ${{ matrix.cc }}
|
||||
CXX: ${{ matrix.cxx }}
|
||||
run: >-
|
||||
cmake -S . -B build
|
||||
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
|
||||
-DIKVXX_BUILD_TESTS=ON
|
||||
-DIKVXX_INSTALL=ON
|
||||
- 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: Build
|
||||
run: cmake --build build --parallel 2
|
||||
- name: Configure CMake build
|
||||
run: |
|
||||
cmake -S . -B build -G Ninja -DIKVXX_BUILD_TESTS=OFF
|
||||
|
||||
- name: Test bindings
|
||||
run: ctest --test-dir build --build-config ${{ matrix.build_type }} --output-on-failure
|
||||
- name: Build with CMake
|
||||
run: |
|
||||
cmake --build build
|
||||
|
||||
- name: Verify installation
|
||||
run: cmake --install build --prefix "${{ runner.temp }}/ikvxx-install"
|
||||
unit-tests:
|
||||
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_INSTALL "Install iKvxx" ON)
|
||||
|
||||
if(IKVXX_BUILD_TESTS)
|
||||
include(CTest)
|
||||
endif()
|
||||
|
||||
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)
|
||||
add_subdirectory(third_party/iKv)
|
||||
set_target_properties(ikv PROPERTIES
|
||||
@@ -27,11 +31,24 @@ else()
|
||||
endif()
|
||||
|
||||
if(IKVXX_BUILD_TESTS)
|
||||
include(CTest)
|
||||
add_executable(ikvxx_tests tests/value_tests.cpp)
|
||||
target_link_libraries(ikvxx_tests PRIVATE ikvxx::ikvxx)
|
||||
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()
|
||||
|
||||
if(IKVXX_INSTALL)
|
||||
|
||||
10
README.md
10
README.md
@@ -243,10 +243,12 @@ cmake --build build
|
||||
ctest --test-dir build --output-on-failure
|
||||
```
|
||||
|
||||
The test suite covers construction, every value type, object and array access,
|
||||
assignment and append overloads, conversion failures, bounds checks, text and binary
|
||||
I/O, format versions, refresh behavior, and shared ownership. Gitea Actions runs the
|
||||
suite on Ubuntu with GCC and Clang in both Debug and Release configurations.
|
||||
CTest runs every iKvxx test group separately and also includes the complete upstream
|
||||
iKv suite. The binding tests cover construction, every value type, object and array
|
||||
access, assignment and append overloads, conversion failures, bounds checks, text and
|
||||
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:
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ struct ikv_node_t;
|
||||
|
||||
namespace ikv {
|
||||
|
||||
/// Runtime value types exposed by iKv.
|
||||
enum ValueType {
|
||||
nullValue = 0,
|
||||
stringValue,
|
||||
@@ -21,26 +22,44 @@ enum ValueType {
|
||||
arrayValue
|
||||
};
|
||||
|
||||
/// On-disk iKv format versions supported for explicit writes.
|
||||
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 {
|
||||
public:
|
||||
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 {
|
||||
public:
|
||||
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");
|
||||
Value(const Value&) = default;
|
||||
|
||||
/// Structural assignment is unavailable because iKv cannot clone arbitrary subtrees.
|
||||
Value& operator=(const Value&) = delete;
|
||||
|
||||
/// Parses auto-detected iKv1 or iKv2 text.
|
||||
static Value parse(const std::string& text);
|
||||
|
||||
/// Loads an auto-detected iKv1 or iKv2 text file.
|
||||
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);
|
||||
|
||||
/// Loads an auto-detected binary iKv file.
|
||||
static Value loadBinary(const std::string& path);
|
||||
|
||||
/// Returns the node's runtime type, or nullValue for a missing member.
|
||||
ValueType type() const noexcept;
|
||||
bool isNull() const noexcept;
|
||||
bool isString() const noexcept;
|
||||
@@ -54,6 +73,8 @@ public:
|
||||
std::string name() 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 char* key);
|
||||
Value operator[](const std::string& key) const;
|
||||
@@ -62,8 +83,11 @@ public:
|
||||
Value operator[](int index);
|
||||
Value operator[](ArrayIndex 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;
|
||||
|
||||
/// Assigns a scalar to an object member returned by operator[].
|
||||
Value& operator=(const std::string& value);
|
||||
Value& operator=(const char* value);
|
||||
Value& operator=(std::int64_t value);
|
||||
@@ -71,8 +95,13 @@ public:
|
||||
Value& operator=(double value);
|
||||
Value& operator=(bool value);
|
||||
|
||||
/// Adds or replaces an object member and returns a handle to it.
|
||||
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);
|
||||
|
||||
/// Appends to an array. Typed arrays reject incompatible values with Error.
|
||||
Value append(const std::string& value);
|
||||
Value append(const char* value);
|
||||
Value append(std::int64_t value);
|
||||
@@ -87,9 +116,13 @@ public:
|
||||
double asDouble() 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 writeBinary(const std::string& path, 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);
|
||||
|
||||
private:
|
||||
|
||||
@@ -36,6 +36,12 @@ if errorlevel 1 (
|
||||
)
|
||||
|
||||
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%" ^
|
||||
--build-config "%CONFIG%" ^
|
||||
--output-on-failure
|
||||
|
||||
@@ -368,14 +368,40 @@ void testRefreshAndOwnership() {
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
testConstructionAndTypeInspection();
|
||||
testAssignmentsAndConversions();
|
||||
testObjectsMembersAndLookupOverloads();
|
||||
testArraysAndIndexOverloads();
|
||||
testTextParsingAndFiles();
|
||||
testBinaryMemoryAndFiles();
|
||||
testRefreshAndOwnership();
|
||||
int main(int argc, char** argv) {
|
||||
struct TestCase {
|
||||
const char* name;
|
||||
void (*function)();
|
||||
};
|
||||
|
||||
const TestCase tests[] = {
|
||||
{"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";
|
||||
return failures == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user