From f760661adf844b923b4561e43d1ea8b50f308fb4 Mon Sep 17 00:00:00 2001 From: muit Date: Fri, 4 Sep 2026 01:22:11 +0200 Subject: [PATCH 01/25] docs: PipeTests framework design spec --- .../2026-09-04-pipe-tests-framework-design.md | 177 ++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 Docs/Specs/2026-09-04-pipe-tests-framework-design.md diff --git a/Docs/Specs/2026-09-04-pipe-tests-framework-design.md b/Docs/Specs/2026-09-04-pipe-tests-framework-design.md new file mode 100644 index 00000000..dec34bcd --- /dev/null +++ b/Docs/Specs/2026-09-04-pipe-tests-framework-design.md @@ -0,0 +1,177 @@ +# PipeTests Framework — Design + +Date: 2026-09-04 +Status: Draft (pending spec review) + +## Problem + +The Pipe and Rift test suites rely on the vendored **Bandit** third-party test framework (`Extern/Pipe/Extern/Bandit/`) plus its bundled **Snowhouse** assertion library. Bandit is heavy, third-party, and its capture-scoping API does not fit the project's taste. We want to replace it step by step with a small, Pipe-native test framework owned by the project, following Pipe code style and the project's "simple solutions, C-like cpp, minimal templates, comments only when code is not enough" preference. + +## Goals + +- Provide a native test framework module **`PipeTests`** in the Pipe source tree. +- Used by **both** `PipeTests` and `RiftTests`. +- Mirror the bandit structure (`Spec`/`Describe`/`It`/`XIt`/`BeforeEach`/`AfterEach`) so migration is mechanical. +- Replace Snowhouse `AssertThat(x, Equals(y))` with a fluent `Expect(x).ToEqual(y)` style. +- Detailed failure messages (actual vs expected, `file:line`), extensible for user types. +- Remove the Bandit dependency entirely **only at the very end**, once the framework is proven. +- No new runtime burden on the shipped `Pipe` library. + +## Non-Goals + +- No exception-based matchers (`ToThrow`). Project disables RTTI project-wide and no current test throws; YAGNI. +- No (or minimal) CLI reporter/arg machinery in the first iteration. +- Not migrating the existing bandit tests or removing Bandit until the framework is complete and self-tested (see Migration Phasing). + +## API Design + +Style: **imgui-style global context**. All functions are global in namespace `p`; the user writes `using namespace p;`. There is no capture-scoped "only relevant functions" context — context is global and tracked as functions are called. The exception is `Spec`, which is self-registering. + +```cpp +using namespace p; + +// Spec(name, fn) = go_bandit + first-level named group +Spec("Group", []() { + BeforeEach([]() { /* setup */ }); + AfterEach([]() { /* teardown */ }); + + Describe("Sub", []() { + It("does something", []() { + Expect(value).ToEqual(4); + Expect(value).ToNotEqual(5); + Expect(value).ToBeLess(5); + Expect(value).ToBeLessOrEqual(4); + Expect(value).ToBeGreater(2); + }); + XIt("disabled test", []() { /* never runs */ }); + }); + + It("top-level test", []() { + Expect("acidic").ToContain("acid"); + Expect(flag).ToBeTrue(); + Expect(other).ToBeFalse(); + }); +}); +``` + +### Top-level semantics + +- `Spec(name, fn)` — self-registering top-level entry that **also** opens a first-level group named `name`. This is the go_bandit + first `Describe` replacement. +- `Spec(fn)` (nameless) — registers the top level only (like `go_bandit`), adding no extra group; `Describe` is then used inside it. +- `Describe` is **runtime-checked** to only be valid inside a `Spec`. If used outside a `Spec`: **log an error and ignore** the offending group (no exception). + +### Functions (namespace `p`) + +| Function | Role | +|----------|------| +| `Spec(name, fn)` / `Spec(fn)` | Self-registering top-level; optional first named group | +| `Describe(name, fn)` | Push a nested group; runtime-checked inside a `Spec` | +| `It(name, fn)` | Register a runnable leaf test in the current group | +| `XIt(name, fn)` | Register a leaf test marked **skipped** (never runs) | +| `BeforeEach(fn)` | Setup hook attached to the current group | +| `AfterEach(fn)` | Teardown hook attached to the current group | +| `Expect(value)` | Returns a fluent matcher over `value` | +| `RunTests(argc, argv)` | Runs the suite; returns process exit code | + +### Assertions — `Expect(value)` + +Fluent matcher methods, naming in Pipe CamelCase: + +- `ToEqual(x)` / `ToNotEqual(x)` +- `ToBeLess(x)` / `ToBeLessOrEqual(x)` / `ToBeGreater(x)` / `ToBeGreaterOrEqual(x)` +- `ToBeTrue()` / `ToBeFalse()` +- `ToContain(sub)` / `ToNotContain(sub)` (strings / containers) + +### Failure reporting + +- On failure: print `file:line`, a description, and the **actual vs expected** values (detailed). +- Value stringification uses an **extensible formatter** customization point. + - Default support: arithmetic types via `std::format`/`std::to_string`; `StringView` / `const char*` / `std::string_view` for strings. + - Users extend by specializing/overloading a `TestFormatter` (or `TestFormat(value)`) hook for their own types. + - Prefer `StringView` over `String`. + +## Implementation + +### New files (in the Pipe submodule) + +- `Extern/Pipe/Include/PipeTests.h` — public API (global functions, `Expect` matcher, formatter hook). Mostly templates/macros. +- `Extern/Pipe/Src/PipeTests.cpp` — global registration cursor (current-group stack), test registry, `p::RunTests(int, char**)`. + +### Build — separate target, not into the runtime Pipe lib + +`Extern/Pipe/CMakeLists.txt`: + +- Define `add_library(PipeTests ...)` **unconditionally** (alongside `Pipe`, **before** the `PIPE_BUILD_TESTS` gate) so Rift can consume it via the submodule. +- **Exclude `Src/PipeTests.cpp`** from Pipe's `file(GLOB_RECURSE ... Src/*.cpp)` (currently line 65) so the test framework does **not** ship in the runtime `Pipe` library. Add it only to the `PipeTests` target. +- Give `PipeTests` the standard Pipe setup (`pipe_target_define_platform`, `pipe_target_enable_CPP20`, `pipe_target_disable_rtti`, `pipe_target_shared_output_directory`) and link `Pipe`; expose `Include/`. + +### Runner + +`p::RunTests(int argc, char* argv[]) -> int`: + +- Iterates the registered test tree. +- Runs each test, skipping `XIt`. +- Reports pass/fail/skip counts plus the names/locations of failures. +- Returns a process exit code (`0` when all pass). +- Minimal `argc`/`argv` handling now; signature kept for future `--filter` support. + +### Global registration cursor + +A current-group stack in `PipeTests.cpp`. `Describe` pushes its group, runs `fn` (children register against it via the global functions), then pops. `It`/`XIt`/`BeforeEach`/`AfterEach` attach to the current group. + +## Migration Phasing + +**Deliberate: do NOT migrate existing tests and do NOT remove third-party libraries until the very end, when the framework is done and self-tested.** Bandit stays linked and coexists throughout development. + +1. **Add `PipeTests` module** — header + source; `add_library(PipeTests)`; source-glob exclusion. Build succeeds. +2. **Self-test the framework with small new tests** (no migration of existing tests): + - Create small **new** framework tests in `Extern/Pipe/Tests/PipeTests/` (e.g. `PipeTests.spec.cpp`) written with the new API to validate: `Spec`/`Describe`/`It`/`XIt`/`BeforeEach`/`AfterEach`, all `Expect` matchers, failure reporting, skip counting, `RunTests` exit code, and `Describe`-outside-`Spec` behavior. + - Wire a **separate small runner** (its own `main.cpp` calling `p::RunTests`) for this smoke target, running **alongside** the existing bandit `PipeTests` executable. + - Verify via `ctest` that **both** the new self-tests and the untouched bandit suite pass. Iterate until the framework is proven. +3. **Final flip (LAST, only when the system is done):** + - Migrate existing `*spec.cpp` files file-by-file (transform map below). + - `Extern/Pipe/Tests/CMakeLists.txt`: link `Pipe` + `PipeTests`, drop `Bandit`; `main.cpp` → `p::RunTests(argc, argv)`; drop `--reporter=spec`. + - Migrate Rift `Tests/*.spec.cpp` + `Tests/CMakeLists.txt`: replace `Bandit` with `PipeTests`. + - Remove `Bandit`: delete the `Bandit` INTERFACE target from `Extern/Pipe/Extern/CMakeLists.txt`, remove the vendored `Extern/Pipe/Extern/Bandit/` directory, strip remaining bandit includes. + - Final full `ctest` + `ClangFormat`/`ClangTidy` pass. + +### Transform map (for step 3) + +| Before (bandit) | After (PipeTests) | +|-----------------|-------------------| +| `#include ` + `using namespace snowhouse; using namespace bandit;` | `#include ` + `using namespace p;` | +| `go_bandit([](){ describe("G", ...) })` | `Spec("G", [](){ ... })` | +| `describe(...)` | `Describe(...)` | +| `it(...)` | `It(...)` | +| `xit(...)` | `XIt(...)` | +| `before_each(...)` | `BeforeEach(...)` | +| `after_each(...)` | `AfterEach(...)` | +| `AssertThat(v, Equals(x))` | `Expect(v).ToEqual(x)` | +| `AssertThat(v, !Equals(x))` | `Expect(v).ToNotEqual(x)` | +| `AssertThat(x, Equals(true/false))` | `Expect(x).ToBeTrue()/ToBeFalse()` | +| `<` / `<=` relations | `ToBeLess` / `ToBeLessOrEqual` | +| contains checks | `ToContain` / `ToNotContain` | + +Note: existing test files that `using namespace snowhouse; using namespace bandit;` also add `using namespace p;` where Pipe types are used (e.g. `StringView.spec.cpp`). After migration these files use only `using namespace p;`. + +## Wiring facts (verified) + +- `Bandit` INTERFACE target is defined in `Extern/Pipe/Extern/CMakeLists.txt`, added **unconditionally** (Pipe `CMakeLists.txt` line 47 `add_subdirectory(Extern)` runs before the `PIPE_BUILD_TESTS` gate). +- Pipe supplies its own tests executable gated behind `PIPE_BUILD_TESTS` (default `PIPE_IS_PROJECT`, i.e. ON when Pipe is the top project). +- Rift builds its **own** `RiftTests` executable in `Tests/CMakeLists.txt`, linking `RiftASTLib` + `Bandit` (imported through Pipe's `Extern`). +- Therefore the `PipeTests` target must be defined **unconditionally** in Pipe's `CMakeLists.txt` so Rift can link it. + +## Constraints & style + +- Namespace `p`; CamelCase functions; `camelBack` parameters/variables; tabs; 100-col limit; `.clang-format` (Microsoft base). +- Minimal templates; comments only where code is insufficient. +- No exceptions / no RTTI (`-fno-rtti` project-wide). +- Prefer `StringView` over `String`. + +## Open Questions + +- Confirmed during design: no `ToThrow` (see Non-Goals); `Describe` misuse logs + ignores; `RunTests(int, char**)` signature; API names `Spec/Describe/It/XIt/BeforeEach/AfterEach/Expect` in Pipe CamelCase. + +## Out of Scope / Follow-ups + +- CLI `--filter` / reporter selection (deferred; `RunTests` keeps `argc`/`argv` for future use). From f7f26b3a9ab8cd70c317285167cd1560fcea728f Mon Sep 17 00:00:00 2001 From: muit Date: Fri, 4 Sep 2026 01:24:35 +0200 Subject: [PATCH 02/25] docs: PipeTests implementation plan --- Docs/Plans/2026-09-04-pipe-tests-framework.md | 1148 +++++++++++++++++ 1 file changed, 1148 insertions(+) create mode 100644 Docs/Plans/2026-09-04-pipe-tests-framework.md diff --git a/Docs/Plans/2026-09-04-pipe-tests-framework.md b/Docs/Plans/2026-09-04-pipe-tests-framework.md new file mode 100644 index 00000000..4110b1a0 --- /dev/null +++ b/Docs/Plans/2026-09-04-pipe-tests-framework.md @@ -0,0 +1,1148 @@ +# PipeTests Framework Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Build a Pipe-native test framework (`PipeTests` module) that mirrors Bandit's structure with imgui-style global context, used by both PipeTests and RiftTests. + +**Architecture:** A new `PipeTests` module in the Pipe submodule (`Include/PipeTests.h` + `Src/PipeTests.cpp`) built as a **separate CMake library target** (never compiled into the runtime `Pipe` library). Global registration cursor tracks the current test group as functions are called. `Expect(value)` returns a fluent matcher. `p::RunTests(argc, argv)` runs the suite. Existing Bandit tests are NOT migrated and Bandit is NOT removed until the final task. + +**Tech Stack:** C++20, CMake 3.26+, no exceptions, no RTTI (`-fno-rtti`). Pipe core types: `StringView`, `String`, `TArray`, `std::function`, `p::Format`, `p::Info/Warning/Error`. + +## Global Constraints + +- Namespace is `p`; user adds `using namespace p;`. +- Coding style: CamelCase functions, `camelBack` parameters/variables, tabs, 100-col limit, `.clang-format` (Microsoft base). Comments only where code is insufficient. +- No exceptions, no RTTI project-wide. +- Prefer `StringView` over `String`. +- Minimal templates; C-like C++. +- Do NOT modify or remove Bandit or existing `*spec.cpp` files until the final task (Task 6). +- Build/test commands: + - Configure: `cmake -S . -B Build` + - Build: `cmake --build Build --config Release` + - Test: `cd Build && ctest --output-on-failure -j2 -C Release` +- All Pipe work happens in the Pipe submodule directory `Extern/Pipe` (branch `feature/pipe-tests`). Run git commands from inside `Extern/Pipe`. + +--- + +### Task 1: Add `PipeTests` library target and unconditional build + +**Files:** +- Modify: `Extern/Pipe/CMakeLists.txt` (Pipe library block ~lines 50-81; add new target after line 81) + +**Interfaces:** +- Consumes: existing `Pipe` library target. +- Produces: CMake target `PipeTests` (linkable by other targets), available unconditionally (regardless of `PIPE_BUILD_TESTS`). + +- [ ] **Step 1: Add the `PipeTests` library target** + +Append after the `Pipe` library block in `Extern/Pipe/CMakeLists.txt` (after line 81, before the `PIPE_BUILD_TESTS` block): + +```cmake +################################################################################ +# PipeTests (test framework library, not part of the runtime Pipe library) + +add_library(PipeTests STATIC Src/PipeTests.cpp) +add_library(Pipe::TestsLib ALIAS PipeTests) +pipe_target_define_platform(PipeTests) +target_include_directories(PipeTests PUBLIC $) +pipe_target_enable_CPP20(PipeTests) +pipe_target_disable_rtti(PipeTests PRIVATE) +pipe_target_shared_output_directory(PipeTests) +target_link_libraries(PipeTests PUBLIC Pipe) +``` + +Note: `Src/PipeTests.cpp` does not exist yet; CMake will fail until Task 2 creates it. + +- [ ] **Step 2: Ensure `Src/PipeTests.cpp` is excluded from the `Pipe` library glob** + +The `Pipe` library compiles `Src/*.cpp` via `file(GLOB_RECURSE PIPE_SOURCE_FILES CONFIGURE_DEPENDS Src/*.cpp Src/*.c)` (line 65). `PipeTests.cpp` in `Src/` would be globbed into `Pipe`. Since the git repo does not track glob output, verify the exclusion after Task 2 by confirming the `Pipe` target does not include `PipeTests.cpp` (build command in Task 2 will confirm). +**If needed**: remove `Src/PipeTests.cpp` match from the glob by excluding subdirectory — glob includes it. To keep `PipeTests.cpp` out of `Pipe`, place it under a subdirectory instead: put the implementation at `Src/Tests/PipeTests.cpp` (not `Src/PipeTests.cpp`), and point the `PipeTests` target at `Src/Tests/PipeTests.cpp`. The `Pipe` glob `Src/*.cpp` (non-recursive at top level only matches `PipeTests.cpp` if directly in `Src/`; the actual glob is `GLOB_RECURSE ... Src/*.cpp` which is recursive and WILL pick up `Src/Tests/PipeTests.cpp`). + +**Decision (must-follow):** Place the implementation at `Src/Tests/PipeTests.cpp` and exclude the `Src/Tests` directory from the `Pipe` source glob. Modify the `Pipe` glob (line 65) to exclude the `PipeTests` implementation: + +```cmake +file(GLOB_RECURSE PIPE_SOURCE_FILES CONFIGURE_DEPENDS Src/*.cpp Src/*.c) +list(FILTER PIPE_SOURCE_FILES EXCLUDE REGEX ".*/Src/Tests/.*") +target_sources(Pipe PRIVATE ${PIPE_SOURCE_FILES}) +``` + +Then the `PipeTests` target in this task uses `Src/Tests/PipeTests.cpp`: + +```cmake +add_library(PipeTests STATIC Src/Tests/PipeTests.cpp) +``` + +- [ ] **Step 3: Configure + build (may fail until Task 2 creates the source)** + +Run (from `Extern/Pipe`): +``` +cmake -S . -B Build +cmake --build Build --config Release +``` +Expected: fails only because `Src/Tests/PipeTests.cpp` (and `Include/PipeTests.h`) do not exist yet. This is acceptable mid-plan; the target is created and validated in Task 2. + +- [ ] **Step 4: Commit** + +```bash +git add CMakeLists.txt +git commit -m "build: add PipeTests library target" +``` + +--- + +### Task 2: `PipeTests.h` public header — registration functions + +**Files:** +- Create: `Extern/Pipe/Include/PipeTests.h` + +**Interfaces:** +- Consumes: `Pipe/Core/Log.h` (for error logging), `StringView.h`. +- Produces (used by Tasks 3-6): + - `void Spec(StringView name, std::function fn)` + - `void Spec(std::function fn)` (nameless) + - `void Describe(StringView name, std::function fn)` + - `void It(StringView name, std::function fn)` + - `void XIt(StringView name, std::function fn)` + - `void BeforeEach(std::function fn)` + - `void AfterEach(std::function fn)` + - `int RunTests(int argc, char** argv)` + +- [ ] **Step 1: Declare the registration API** + +Create `Extern/Pipe/Include/PipeTests.h`: + +```cpp +// Copyright 2015-2026 Piperift. All Rights Reserved. + +#pragma once + +#include "Pipe/Core/StringView.h" + +#include + + +namespace p +{ + /** + * Test framework for Pipe and Rift. + * Imgui-style global context: registration functions act on a current group. + * Spec is self-registering; Describe/It/BeforeEach/AfterEach attach to the + * current group as functions are called. + */ + + // Self-registering top-level. Spec(name, fn) also opens a first group named `name`. + void Spec(StringView name, std::function fn); + // Nameless top-level (like go_bandit); use Describe inside fn. + void Spec(std::function fn); + + // Nested group. Only valid inside a Spec; otherwise logs an error and ignores. + void Describe(StringView name, std::function fn); + // Register a runnable test in the current group. + void It(StringView name, std::function fn); + // Register a disabled test; never run. + void XIt(StringView name, std::function fn); + // Setup hook attached to the current group. + void BeforeEach(std::function fn); + // Teardown hook attached to the current group. + void AfterEach(std::function fn); + + int RunTests(int argc, char** argv); +}; // namespace p +``` + +- [ ] **Step 2: Commit** + +```bash +git add Include/PipeTests.h +git commit -m "feat: declare PipeTests registration API" +``` + +--- + +### Task 3: `PipeTests.cpp` — registry, cursor, runner (skip + summary) + +**Files:** +- Create: `Extern/Pipe/Src/Tests/PipeTests.cpp` + +**Interfaces:** +- Consumes: `PipeTests.h`, `Pipe.h`, `Pipe/Core/Log.h`, `PipeStrings.h`, `StringView.h`, `TArray`. +- Produces: implementation of `Spec`, `Describe`, `It`, `XIt`, `BeforeEach`, `AfterEach`, `RunTests`. Matcher `Expect` is a separate task (Task 4); until then `It` bodies cannot assert. + +The runner must support: +- Building a registered tree of groups and tests. +- Running each test, invoking `BeforeEach`/`AfterEach` hooks of the enclosing groups (outer → Test beforeEach first, test body, then AfterEach in reverse-within-group order). +- Skipping `XIt` tests (counted as skipped, not failures). +- Printing pass/fail/skip counts and failed test full names/locations. +- Returning `0` on success, non-zero if any test failed. + +- [ ] **Step 1: Implement the registry data structure** + +```cpp +// Copyright 2015-2026 Piperift. All Rights Reserved. + +#ifndef P_OVERRIDE_NEWDELETE + #define P_OVERRIDE_NEWDELETE 1 +#endif +#if P_OVERRIDE_NEWDELETE + #include "PipeNewDelete.h" +#endif + +#include "PipeTests.h" +#include "Pipe.h" +#include "Pipe/Core/Log.h" +#include "PipeStrings.h" + +#include + + +namespace p +{ + namespace + { + struct TestCase + { + String name; + std::function body; + bool skip = false; + }; + + struct TestGroup + { + String name; + TArray groups; // nested describes + TArray tests; // its + std::function beforeEach; + std::function afterEach; + }; + + // Entire registered suite (treat as a single virtual root group). + TestGroup root{"", nullptr, {}, {}, {}, {}}; + + // Pointer into `root.groups` for the currently-adding group. + TestGroup* currentGroup = nullptr; + int failedTests = 0; + int runTests = 0; + int skippedTests = 0; + } // namespace +``` + +Note: `String` and `TArray` require `PipeStrings.h`/`PipeContainers.h` — included via `Pipe.h`? `Pipe.h` only includes `StringView.h` + `Export.h`. Include `PipeStrings.h` explicitly (done above). Ensure `PipeTests.cpp` links against Pipe (done in Task 1 via `target_link_libraries(PipeTests PUBLIC Pipe)`). + +Because `TestGroup` contains `TArray`, `TArray` must be usable with incomplete types or we must define nested groups via `std::vector`. Since Pipe disallows `std::vector` in headers but this is a `.cpp`, use `TArray`. If `TArray` fails to compile due to incomplete type during the initial member declaration, add a forward `struct TestGroup;` before `TestGroup` and store `TArray`. Prefer keeping the above form; adjust only if the compiler requires it. + +- [ ] **Step 2: Implement registration functions** + +```cpp + void Spec(StringView name, std::function fn) + { + TestGroup group; + group.name = String{name}; + root.groups.Emplace(Move(group)); + TestGroup* groupPtr = &root.groups.Back(); + groupPtr->beforeEach = nullptr; + groupPtr->afterEach = nullptr; + currentGroup = groupPtr; + fn(); + currentGroup = nullptr; + } + + void Spec(std::function fn) + { + currentGroup = &root; + fn(); + currentGroup = nullptr; + } + + void Describe(StringView name, std::function fn) + { + if (!currentGroup) + { + Error("PipeTests: Describe('{}') called outside a Spec. Ignoring.", name); + return; + } + + TestGroup group; + group.name = String{name}; + currentGroup->groups.Emplace(Move(group)); + TestGroup* prevGroup = currentGroup; + currentGroup = ¤tGroup->groups.Back(); + fn(); + currentGroup = prevGroup; + } + + void It(StringView name, std::function fn) + { + if (!currentGroup) + { + Error("PipeTests: It('{}') called outside a Spec. Ignoring.", name); + return; + } + TestCase test; + test.name = String{name}; + test.body = fn; + test.skip = false; + currentGroup->tests.Emplace(Move(test)); + } + + void XIt(StringView name, std::function fn) + { + if (!currentGroup) + { + Error("PipeTests: XIt('{}') called outside a Spec. Ignoring.", name); + return; + } + TestCase test; + test.name = String{name}; + test.body = fn; + test.skip = true; + currentGroup->tests.Emplace(Move(test)); + } + + void BeforeEach(std::function fn) + { + if (!currentGroup) + { + Error("PipeTests: BeforeEach called outside a Spec. Ignoring."); + return; + } + currentGroup->beforeEach = fn; + } + + void AfterEach(std::function fn) + { + if (!currentGroup) + { + Error("PipeTests: AfterEach called outside a Spec. Ignoring."); + return; + } + currentGroup->afterEach = fn; + } +``` + +Note: The nameless `Spec(fn)` sets `currentGroup = &root`, which is a group with no name/beforeEach/afterEach and whose `groups`/`tests` are unused (it acts as a namespace root). Describe/It add into `root.groups`/`root.tests` directly. This matches bandit's `go_bandit` behavior. The `Error(StringView, Args...)` overload exists in `Log.h` (verified). + +Verify the exact `TArray` API names: Pipe's `TArray` uses `Emplace`, `Push`, `Back`, `Size`, `Pop`. `Emplace` and `Back` are used in the code above. If the exact member names differ (e.g. `Add` instead of `Emplace`), adjust to Pipe's actual API shown in `Extern/Pipe/Include/PipeContainers.h` (line 768 `struct TArray`). Double-check `root{"", nullptr, {}, {}, {}, {}}` aggregate init is valid for the struct's member order: `name`, `groups`, `tests`, `beforeEach`, `afterEach`. Reorder the init to match the declared order: + +```cpp +TestGroup root{"", {}, {}, {}, {}}; +``` + +- [ ] **Step 3: Implement the runner** + +```cpp + static String FullName(const TestGroup& group, const TestCase& test) + { + // Build "SpecName.SubGroup.TestName" for reporting. Root has empty name. + String result; + if (!group.name.empty()) + { + result += group.name; + result += "."; + } + result += test.name; + return result; + } + + static void RunTest(const TestCase& test, const TestGroup& group) + { + if (test.skip) + { + ++skippedTests; + return; + } + + ++runTests; + + // Run the enclosing beforeEach hooks (outer groups first is handled by + // recursion in RunGroup; here only the single group-level beforeEach + // applies. For nested groups, RunTest is called with the innermost + // group; BeforeEach hooks of outer groups are collected in RunRecogn. + if (group.beforeEach) + { + group.beforeEach(); + } + + bool passed = true; + try + { + test.body(); + } + catch (...) + { + passed = false; + Error("PipeTests: test failed by exception"); + } + + if (group.afterEach) + { + group.afterEach(); + } + + if (passed) + { + Info(" [PASS] {}", FullName(group, test)); + } + else + { + ++failedTests; + Error(" [FAIL] {}", FullName(group, test)); + } + } +``` + +Note on nested groups & BeforeEach: The above only runs the innermost group's beforeEach/afterEach. To match bandit (which runs BeforeEach of **all** enclosing groups outer→inner, then test, then AfterEach inner→outer), the runner must recurse. Implement `RunGroup` to: for each nested group, call `RunGroup` (which itself runs that group's `beforeEach` and `afterEach` around its tests AND its nested groups); for each test, run it. To run the contained `beforeEach` for nested recursion, structure as: + +```cpp + static void RunGroup(TestGroup& group) + { + for (TestGroup& sub : group.groups) + { + RunGroup(sub); + } + for (TestCase& test : group.tests) + { + // Manual hook application with proper nesting depth handled below. + RunTest(test, group); + } + } +``` + +To correctly nest hooks across levels, thread a stack: pass a `TArray>&` of active beforeEach hooks and a matching afterEach stack. Simplest correct approach that matches bandit semantics: + +```cpp + static void RunNested(TestGroup& group, + TArray>& beforeHooks, + TArray>& afterHooks) + { + if (group.beforeEach) + { + beforeHooks.Emplace(group.beforeEach); + } + if (group.afterEach) + { + afterHooks.Emplace(group.afterEach); + } + + for (TestGroup& sub : group.groups) + { + RunNested(sub, beforeHooks, afterHooks); + } + + for (TestCase& test : group.tests) + { + if (test.skip) + { + ++skippedTests; + continue; + } + ++runTests; + + for (auto& hook : beforeHooks) + { + hook(); + } + + bool passed = true; + try + { + test.body(); + } + catch (...) + { + passed = false; + Error("PipeTests: test failed by exception: {}", FullName(group, test)); + } + + for (sizet i = afterHooks.Size(); i > 0; --i) + { + afterHooks[i - 1](); + } + + if (passed) + { + Info(" [PASS] {}", FullName(group, test)); + } + else + { + ++failedTests; + Error(" [FAIL] {}", FullName(group, test)); + } + } + + if (group.beforeEach) + { + beforeHooks.Pop(); + } + if (group.afterEach) + { + afterHooks.Pop(); + } + } + + int RunTests(int argc, char** argv) + { + (void)argc; // kept for future --filter support + (void)argv; + + Info("PipeTests: {} group(s) registered.", root.groups.Size()); + TArray> beforeHooks; + TArray> afterHooks; + RunNested(root, beforeHooks, afterHooks); + + Info("PipeTests complete: {} run, {} passed, {} failed, {} skipped.", + runTests, runTests - failedTests, failedTests, skippedTests); + + return failedTests == 0 ? 0 : 1; + } +``` + +Use the `RunNested` version (correct nested BeforeEach/AfterEach semantics). Verify `TArray` supports `Emplace(std::function)` (it stores by value; `std::function` is default-constructible and move-assignable — fine). `Pop()` removes last element. + +- [ ] **Step 4: Configure + build** + +Run (from `Extern/Pipe`): +``` +cmake -S . -B Build +cmake --build Build --config Release +``` +Expected: target `PipeTests` builds, `Pipe` library does NOT include `PipeTests.cpp`. If `/Src/Tests/` is still picked up by `Pipe`'s glob, fix the `list(FILTER ...)` exclusion (Task 1) and rebuild. + +- [ ] **Step 5: Commit** + +```bash +git add Src/Tests/PipeTests.cpp +git commit -m "feat: add PipeTests registry and runner" +``` + +--- + +### Task 4: Self-test the framework (small new tests, Bandit untouched) + +**Files:** +- Create: `Extern/Pipe/Tests/PipeTests/PipeTests.spec.cpp` +- Create: `Extern/Pipe/Tests/PipeTests/main.cpp` + +**Interfaces:** +- Consumes: `PipeTests.h`, `Pipe.h`, `p::Expect` (Task 5). To avoid depending on Task 5, this task's runner can use a temporary assertion until Task 5 lands. The spec file uses the framework's own `Spec/Describe/It/XIt/BeforeEach/AfterEach` + a minimal in-test check via a temporary macro defined in this file's `main.cpp` (or add `Expect` here as a stub returning `void`). Simpler: implement this task to compile against the header, but **defer the actual `Expect` matcher to Task 5 and add assertions in Task 5**. To keep the framework testable now, use a tiny local macro: + +```cpp +#define CHECK_TRUE(x) do { if (!(x)) { p::Error("CHECK_TRUE failed: {}", #x); } } while (0) +``` + +- Produces: a second test executable `PipeTestsSelf` registered in CTest, proving the framework runs alongside the untouched Bandit suite. + +- [ ] **Step 1: Create the self-test spec + runner** + +`Extern/Pipe/Tests/PipeTests/main.cpp`: + +```cpp +// Copyright 2015-2026 Piperift. All Rights Reserved. + +#include + +#include +#include + + +int main(int argc, char* argv[]) +{ + p::Initialize(); + int result = p::RunTests(argc, argv); + p::Shutdown(); + return result; +} +``` + +`Extern/Pipe/Tests/PipeTests/PipeTests.spec.cpp`: + +```cpp +// Copyright 2015-2026 Piperift. All Rights Reserved. + +#include + +#include + +using namespace p; + +static int beforeEachCount = 0; +static int afterEachCount = 0; +static int topTestResult = 0; + + +Spec("PipeTests", []() { + BeforeEach([]() { ++beforeEachCount; }); + AfterEach([]() { ++afterEachCount; }); + + Describe("Basics", []() { + It("Registers and runs", []() { topTestResult = 42; }); + XIt("Is skipped", []() { topTestResult = -1; }); + }); +}); + + +// NOTE: assertions are added in Task 5 once Expect() exists. +``` + +- [ ] **Step 2: Wire a separate CTest target** + +`Extern/Pipe/Tests/CMakeLists.txt` — add after the existing `PipeTests` executable block, **without modifying** the existing Bandit-based target or its `--reporter=spec`: + +```cmake +# PipeTests self-test (uses the new framework). Bandit-based suite remains unchanged. +add_executable(PipeTestsSelf EXCLUDE_FROM_ALL src_placeholder_main) +``` + +Because the existing `PipeTests/CMakeLists.txt` uses `file(GLOB_RECURSE ...)` and adds its own `PipeTests` executable, adding a second executable in the same glob would collide (two `main.cpp`). Instead, add a **separate subdirectory** `Tests/PipeTests/` with its own `CMakeLists.txt`: + +Create `Extern/Pipe/Tests/PipeTests/CMakeLists.txt`: + +```cmake +add_executable(PipeTestsSelf PipeTests.spec.cpp main.cpp) +pipe_target_define_platform(PipeTestsSelf) +pipe_target_enable_CPP20(PipeTestsSelf) +pipe_target_disable_rtti(PipeTestsSelf PRIVATE) +pipe_target_shared_output_directory(PipeTestsSelf) +target_link_libraries(PipeTestsSelf PUBLIC PipeTests Pipe) +add_test(NAME PipeTestsSelf COMMAND $) +``` + +And in `Extern/Pipe/Tests/CMakeLists.txt`, add `add_subdirectory(PipeTests)` at the end (the parent glob will NOT recurse into `PipeTests` because the existing glob is `GLOB_RECURSE *.cpp *.h *.hpp` which WILL pick up the new `PipeTests.spec.cpp` and `main.cpp` into the Bandit-based `PipeTests` executable — causing duplicate `main()` and redefinition). To prevent that: + +- Change the parent glob to limit scope, OR +- Place the self-test under a path not matched by the parent glob, OR +- The cleanest: **exclude the `PipeTestsSelf` sources from the Bandit glob** by listing them and filtering. Simplest robust approach given CMake: rename the self-test directory so the `GLOB_RECURSE` doesn't include it is not possible (it globs everything). Instead, filter in `Tests/CMakeLists.txt`: + +```cmake +file(GLOB_RECURSE TESTS_SOURCE_FILES CONFIGURE_DEPENDS *.cpp *.h *.hpp) +list(FILTER TESTS_SOURCE_FILES EXCLUDE REGEX ".*/PipeTests/.*") +add_executable(PipeTests ${TESTS_SOURCE_FILES}) +# ... existing target_link_libraries(PipeTests PUBLIC Pipe Bandit) unchanged ... +add_subdirectory(PipeTests) +``` + +This keeps the Bandit-based `PipeTests` exe only on non-`PipeTests/` sources, and adds `PipeTestsSelf` from the subdirectory. Verify the `main()` symbol is only defined once in the Bandit exe. + +- [ ] **Step 3: Configure + build + run** + +Run (from `Extern/Pipe`): +``` +cmake -S . -B Build +cmake --build Build --config Release +ctest --test-dir Build --output-on-failure +``` +Expected: `PipeTestsSelf` appears in CTest output with `0 run, 0 passed` (no assertions yet) and reports the skipped `XIt` count (1 skipped) and 1 pass. The Bandit suite `PipeTests` also still runs (`--reporter=spec`) with its original tests. + +- [ ] **Step 4: Commit** + +```bash +git add Tests/PipeTests +git commit -m "test: add PipeTests self-test suite" +``` + +--- + +### Task 5: `Expect` fluent matcher + extensible formatter + +**Files:** +- Modify: `Extern/Pipe/Include/PipeTests.h` +- Modify: `Extern/Pipe/Src/Tests/PipeTests.cpp` + +**Interfaces:** +- Consumes: `PipeTests.h` registration API (Task 2/3), Pipe `Format`, `StringView`, `Number` concept (`TypeTraits.h`). +- Produces: `Expect(value)` matcher returned by `p::Expect(value)` with methods `ToEqual`, `ToNotEqual`, `ToBeLess`, `ToBeLessOrEqual`, `ToBeGreater`, `ToBeGreaterOrEqual`, `ToBeTrue`, `ToBeFalse`, `ToContain`, `ToNotContain`. Failure prints `file:line` + actual/expected via an extensible `ToString`-style hook (`p::TestString`). + +- [ ] **Step 1: Add the formatter hook and matcher to `PipeTests.h`** + +Append to `PipeTests.h`: + +```cpp + // Extensible value-to-string hook for failure messages. + // Specialize for user types. Default handles numbers and string views. + template + inline String TestString(const T& value); + + namespace details + { + // Format failure message from file:line + description. + P_API void Fail(const char* file, sizet line, StringView message); + } // namespace details +``` + +Implement `TestString` in the header (template): + +```cpp + template + inline String TestString(const T& value) + { + return Format("{}", value); + } + + template<> + inline String TestString(const bool& value) + { + return value ? String{"true"} : String{"false"}; + } + + template<> + inline String TestString(const char& value) + { + return String{value}; + } + + inline String TestString(const StringView value) + { + return String{value}; + } + + inline String TestString(const char* value) + { + return value ? String{value} : String{"(null)"}; + } +``` + +Note: `Format("{}", value)` requires `value` be formattable; Pipe's `STDFormat.h` and `PipeStrings.h` provide `std::format` for arithmetic and `String`/`StringView`/`const char*` (StringView formats as string view). Confirm `StringView` has a `std::formatter`; Pipe formats strings via `std::format` — check `PipeStrings.h`/`Pipe/Core/STDFormat.h` supplies a formatter for `StringView` and `String`. If `Format("{}", StringView)` does not compile, add an overload: + +```cpp + inline String TestString(const String& value) + { + return String{value}; + } +``` + +(Add if needed; Strings.format uses `std::vformat_to` which requires appropriate formatters.) + +Now the matcher class: + +```cpp + // ---- fluent assertion ---- + template + class ExpectValue + { + public: + ExpectValue(const Actual& value, const char* file, sizet line) + : value(value) + , file(file) + , line(line) + {} + + void ToEqual(const Actual& expected) const + { + if (!(value == expected)) + { + details::Fail(file, line, Format( + "Expected {} to equal {}", TestString(value), TestString(expected))); + } + } + + void ToNotEqual(const Actual& expected) const + { + if (!(value != expected)) + { + details::Fail(file, line, Format( + "Expected {} to not equal {}", TestString(value), TestString(expected))); + } + } + + void ToBeLess(const Actual& other) const + { + if (!(value < other)) + { + details::Fail(file, line, Format( + "Expected {} to be less than {}", TestString(value), TestString(other))); + } + } + + void ToBeLessOrEqual(const Actual& other) const + { + if (!(value <= other)) + { + details::Fail(file, line, Format( + "Expected {} to be less or equal to {}", TestString(value), TestString(other))); + } + } + + void ToBeGreater(const Actual& other) const + { + if (!(value > other)) + { + details::Fail(file, line, Format( + "Expected {} to be greater than {}", TestString(value), TestString(other))); + } + } + + void ToBeGreaterOrEqual(const Actual& other) const + { + if (!(value >= other)) + { + details::Fail(file, line, Format( + "Expected {} to be greater or equal to {}", TestString(value), TestString(other))); + } + } + + void ToBeTrue() const + { + if (!value) + { + details::Fail(file, line, "Expected value to be true"); + } + } + + void ToBeFalse() const + { + if (value) + { + details::Fail(file, line, "Expected value to be false"); + } + } + + void ToContain(const StringView sub) const + { + // Actual must be a string-like type. + StringView view{value}; + if (Strings::Find(view, sub) == StringView::npos) + { + details::Fail(file, line, Format( + "Expected {} to contain {}", TestString(value), TestString(sub))); + } + } + + void ToNotContain(const StringView sub) const + { + StringView view{value}; + if (Strings::Find(view, sub) != StringView::npos) + { + details::Fail(file, line, Format( + "Expected {} to not contain {}", TestString(value), TestString(sub))); + } + } + + private: + const Actual& value; + const char* file; + sizet line; + }; +``` + +Note: `ToBeTrue/ToBeFalse` require `value` convertible to bool (works for bool and pointer/integer). For `ToContain`, `StringView view{value}` requires `value` convertible to `StringView` (works for `StringView`, `const char*`, `std::string_view`, `String`). For `Expect(...).ToContain("acid")` with a `String`/`const char*` actual, `StringView view{value}` must be constructible. Confirm `String` is constructible to `StringView` (it exposes a `View` alias and an operator/conversion). If `String` does not implicitly convert, add `StringView{value.c_str(), value.size()}`. + +Finally the entry macro/function: + +```cpp + // Returns a matcher bound to file/line for reporting. + template + ExpectValue Expect(const T& value, const char* file = __FILE__, sizet line = __LINE__) + { + return ExpectValue(value, file, line); + } +``` + +Note: capturing `__FILE__`/`__LINE__` at the `Expect(...)` call gives the caller's location. This is a plain template returning a matcher; no macro needed. This matches the fluent `Expect(value).ToEqual(4)` usage. + +- [ ] **Step 2: Implement `details::Fail` in the `.cpp`** + +Append to `PipeTests.cpp`: + +```cpp + namespace details + { + void Fail(const char* file, sizet line, StringView message) + { + Error("PipeTests: {}:{}: {}", file, line, message); + } + } // namespace details +``` + +`Error` is the Pipe log function (from `Log.h`, already included). This reports a failure inline; the runner counts it as a failure (Task 3 `RunNested` sets `passed=false` only on exception). **Critical:** `Fail` must mark the current test failed. Currently `RunNested` only flips `passed` on exception. Change the failure tracking: add a global `int currentTestFailed = 0;` plus `bool CurrentTestFailed()` accessor, OR have `Fail` set a global flag checked after `test.body()`. Implement: + +In the anonymous namespace add: +```cpp + int currentTestFailureCount = 0; +``` +In `details::Fail`: +```cpp + void Fail(const char* file, sizet line, StringView message) + { + Error("PipeTests: {}:{}: {}", file, line, message); + ++currentTestFailureCount; + } +``` +In `RunNested`, before running the body reset the count, after body if `currentTestFailureCount > 0` mark failed and reset: + +```cpp + currentTestFailureCount = 0; + bool passed = true; + try + { + test.body(); + } + catch (...) + { + passed = false; + Error("PipeTests: test failed by exception: {}", FullName(group, test)); + } + passed = passed && (currentTestFailureCount == 0); + if (!passed) + { + ++failedTests; + Error(" [FAIL] {}", FullName(group, test)); + } + else + { + Info(" [PASS] {}", FullName(group, test)); + } +``` + +- [ ] **Step 3: Add real assertions to the self-test** + +Update `Extern/Pipe/Tests/PipeTests/PipeTests.spec.cpp` to use `Expect`: + +```cpp +#include +#include + +using namespace p; + +Spec("PipeTests", []() { + Describe("Expect", []() { + It("ToEqual / ToNotEqual", []() { + int value = 4; + Expect(value).ToEqual(4); + Expect(value).ToNotEqual(5); + }); + It("Relational", []() { + int value = 4; + Expect(value).ToBeLess(5); + Expect(value).ToBeLessOrEqual(4); + Expect(value).ToBeGreater(3); + Expect(value).ToBeGreaterOrEqual(4); + }); + It("Booleans", []() { + bool flag = true; + Expect(flag).ToBeTrue(); + Expect(!flag).ToBeFalse(); + }); + It("Strings", []() { + Expect("acidic").ToContain("acid"); + Expect(String{"hello"}).ToNotContain("world"); + }); + It("Equals int", []() { + Expect(4).ToEqual(4); + }); + }); +}); +``` + +(note: `Expect(value).ToBeTrue()` requires `value` be usable in `if (!value)`; bool works.) + +- [ ] **Step 4: Build + run, confirm failure counts** + +Run (from `Extern/Pipe`): +``` +cmake --build Build --config Release +ctest --test-dir Build --output-on-failure -R PipeTestsSelf +``` +Expected: `PipeTestsSelf` runs the `Expect` tests, all pass (except we also want to verify a deliberate failure is counted — optional: temporarily add `Expect(1).ToEqual(2);` to confirm the FAIL path, then remove). + +Verify one failure is caught: temporarily add to a test `Expect(1).ToEqual(2);`, run, confirm `failed` count = 1 and exit non-zero, then remove it and re-run to confirm green. + +- [ ] **Step 5: Commit** + +```bash +git add Include/PipeTests.h Src/Tests/PipeTests.cpp Tests/PipeTests +git commit -m "feat: add Expect fluent matcher" +``` + +--- + +### Task 6: Remove Bandit + migrate existing tests (FINAL — only after Tasks 1-5 pass) + +**Files:** +- Modify: `Extern/Pipe/Tests/CMakeLists.txt` +- Modify: `Extern/Pipe/Tests/main.cpp` +- Modify: `Extern/Pipe/Extern/CMakeLists.txt` (remove `Bandit`) +- Delete: `Extern/Pipe/Extern/Bandit/` (vended dir) +- Modify: all `Extern/Pipe/Tests/**/*.spec.cpp` +- Modify: `Extern/Pipe/Tests/PipeTests/CMakeLists.txt` (remove self-only scope if desired) — optional; keep separate target. +- Modify: `Tests/CMakeLists.txt` (Rift) and `Tests/*.spec.cpp` (Rift) in `D:\Projects\Piperift\rift` + +**Interfaces:** +- Consumes: `PipeTests.h`, `p::RunTests` (Tasks 2-5). +- Produces: Bandit fully removed; both Pipe and Rift suites run on the native framework. + +⚠️ **This task is intentionally LAST. Do not start it until Tasks 1-5 are complete and verified.** + +- [ ] **Step 1: Migrate one reference spec file (Pipe)** + +Convert `Extern/Pipe/Tests/Core/StringView.spec.cpp`: + +Old: +```cpp +#include +#include +#include + +using namespace snowhouse; +using namespace bandit; +using namespace p; + +go_bandit([]() +{ + describe("Strings", []() + { + describe("StringView", []() + { + it("Can assign from literal", [&]() + { + StringView v{"Kiwi"}; + AssertThat(v, Equals("Kiwi")); + AssertThat(v.size(), Equals(4)); + }); + // ... other tests ... + }); + }); +}); +``` + +New: +```cpp +#include +#include +#include + +using namespace p; + +Spec("Strings", []() +{ + Describe("StringView", []() + { + It("Can assign from literal", []() + { + StringView v{"Kiwi"}; + Expect(v).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4); + }); + // ... other tests converted similarly ... + }); +}); +``` + +Transform rules (from the spec): +- `#include ` → `#include ` +- `using namespace snowhouse; using namespace bandit;` → remove both; keep `using namespace p;` +- `go_bandit([](){ describe("G", [](){ ...`) → `Spec("G", [](){ ...` (drop the outer `go_bandit` extra nesting and one `describe` level; the top `Spec("Strings", ...)` replaces go_bandit+first describe) +- `describe(` → `Describe(` +- `it(` → `It(` (drop the `[&]` → `[]`; lambdas no longer need `&` capture since framework state is global) +- `xit(` → `XIt(` +- `before_each(` → `BeforeEach(` +- `after_each(` → `AfterEach(` +- `AssertThat(v, Equals(x))` → `Expect(v).ToEqual(x)` +- `AssertThat(v, !Equals(x))` → `Expect(v).ToNotEqual(x)` +- `AssertThat(x, Equals(true))` → `Expect(x).ToBeTrue()` +- `AssertThat(x, !Equals(true))` → `Expect(x).ToBeFalse()` +- `AssertThat(x, Equals(false))` → `Expect(x).ToBeFalse()` +- `AssertThat(x, Is().True())` → `Expect(x).ToBeTrue()` +- `AssertThat(x, Is().False())` → `Expect(x).ToBeFalse()` +- `AssertThat(v.size(), Equals(4u))` → `Expect(v.size()).ToEqual(4u)` + +Important: the top-level transform. Bandit files use `go_bandit([](){ describe("Strings", [](){...}) });`. Our `Spec("Strings", [](){...})` handles the `describe` level directly, so replace the pair with a single `Spec("Strings", fn)` and inside use `Describe`/`It`. For files that use `go_bandit` with a single top describe, keep that one as `Spec` and drop the now-redundant `Describe` wrapper if present. Follow the reference conversion exactly. + +- [ ] **Step 2: Build + run the migrated file only (green)** + +Run (from `Extern/Pipe`): +``` +cmake --build Build --config Release +``` +Ensure `StringView.spec.cpp` id not picked up by the Bandit exe twice. The Pipe tests CMake glob (`GLOB_RECURSE *.cpp` in `Tests/CMakeLists.txt`) picks up all `spec.cpp` including the migrated one — but the Bandit exe will FAIL to compile the migrated file (it no longer includes bandit). **Must switch the whole `PipeTests` exe to the new framework now**, not incrementally. Therefore: + +**Decision:** Because `Tests/CMakeLists.txt` globs all `spec.cpp` into one `PipeTests` exe, migration must flip the entire Pipe suite at once (not file-by-file) to keep it compiling. Steps 1-3 migrate ALL Pipe spec files in one pass, then build once. Verify the whole Pipe suite passes via the new framework. + +- [ ] **Step 3: Migrate ALL remaining Pipe spec files** + +Convert every `Extern/Pipe/Tests/**/*.spec.cpp` using the transform rules above. Remove bandit includes and namespaces, map to `Spec/Describe/It/XIt/BeforeEach/AfterEach` and `Expect`. Use `[]` (no `&` capture) for lambda bodies. + +- [ ] **Step 4: Switch PipeTests exe to the new framework** + +`Extern/Pipe/Tests/CMakeLists.txt`: +- Change `target_link_libraries(PipeTests PUBLIC Pipe Bandit)` → `target_link_libraries(PipeTests PUBLIC Pipe PipeTests)` +- Remove `--reporter=spec` from `add_test(...)`: + `add_test(NAME PipeTests COMMAND $)` +- Remove the `list(FILTER ...)` exclusion added in Task 4 (restore the plain glob) so all spec files (including migrated ones) build into `PipeTests`. + +`Extern/Pipe/Tests/main.cpp`: replace `int result = bandit::run(argc, argv);` with `int result = p::RunTests(argc, argv);`, and remove `#include `. Keep `#include ` and `p::Initialize`/`p::Shutdown`. + +`Extern/Pipe/Tests/PipeTests/CMakeLists.txt`: keep the `PipeTestsSelf` target for framework self-checks, OR fold the self-test spec files into the main `PipeTests` glob (remove the separate subdirectory). Keep `PipeTestsSelf` as-is for now (harmless), unless the main glob re-includes its files. Since the main glob is `GLOB_RECURSE *.cpp` from `Tests/`, it WILL include `Tests/PipeTests/*.cpp` again → duplicate `main()`. So when restoring the plain glob in step 4, re-apply a filter to EXCLUDE `Tests/PipeTests/` from the main `PipeTests` exe (keep `PipeTestsSelf` as a separate target): + +```cmake +file(GLOB_RECURSE TESTS_SOURCE_FILES CONFIGURE_DEPENDS *.cpp *.h *.hpp) +list(FILTER TESTS_SOURCE_FILES EXCLUDE REGEX ".*/PipeTests/.*") +add_executable(PipeTests ${TESTS_SOURCE_FILES}) +``` + +Keep `add_subdirectory(PipeTests)` for `PipeTestsSelf`. + +- [ ] **Step 5: Build + run full Pipe suite (green)** + +Run (from `Extern/Pipe`): +``` +cmake --build Build --config Release +ctest --test-dir Build --output-on-failure +``` +Expected: `PipeTests` runs all migrated tests with names/locations under the new framework; `PipeTestsSelf` still passes. Bandit no longer referenced. + +- [ ] **Step 6: Remove the Bandit dependency** + +- `Extern/Pipe/Extern/CMakeLists.txt`: remove lines 6-7 (`add_library(Bandit INTERFACE)` + include dir). +- Delete `Extern/Pipe/Extern/Bandit/` directory. +- `git rm -r Extern/Bandit` (from `Extern/Pipe`). + +- [ ] **Step 7: Migrate Rift tests + CMake** + +In `D:\Projects\Piperift\rift`: +- `Tests/CMakeLists.txt`: `target_link_libraries(RiftTests PUBLIC RiftASTLib Bandit)` → `target_link_libraries(RiftTests PUBLIC RiftASTLib PipeTests)`. Rift links `PipeTests` from the Pipe submodule; ensure Rift's build reaches the `PipeTests` target (it is defined unconditionally in `Extern/Pipe/CMakeLists.txt` per Task 1). +- Convert Rift `Tests/Project.spec.cpp`, `Tests/AST/Statements.spec.cpp`, `Tests/AST/Expressions.spec.cpp`, `Tests/AST/Namespaces.spec.cpp` per the transform rules (uses `before_each`/`after_each` → `BeforeEach`/`AfterEach`, `AssertThat(result, Equals(true))` → `Expect(result).ToBeTrue()`, etc.). Replace `#include ` and `using namespace snowhouse/bandit`. + +- [ ] **Step 8: Full project build + tests + format** + +Run (from `D:\Projects\Piperift\rift`): +``` +cmake --build Build --config Release +cd Build && ctest --output-on-failure -j2 -C Release +``` +and format: `cmake --build Build --target ClangFormat`. + +Expected: all green; no reference to Bandit anywhere in the build. + +- [ ] **Step 9: Commit (Pipe) + Commit (Rift)** + +```bash +# From Extern/Pipe +git add -A +git commit -m "test: replace Bandit with PipeTests framework" + +# From D:\Projects\Piperift\rift (updated submodule pointer + Rift tests + CMake) +git add Extern/Pipe Tests CMakeLists.txt +git commit -m "test: use PipeTests framework in Rift tests" +``` + +Note: the Rift commit must record the new Pipe submodule hash (`git add Extern/Pipe`). + +--- + +## Self-Review + +**Spec coverage:** +- ✅ Native `PipeTests` module in Pipe tree (Task 1-3) +- ✅ Used by both PipeTests and RiftTests (Tasks 4, 6) +- ✅ Mirrors bandit structure `Spec/Describe/It/XIt/BeforeEach/AfterEach` (Tasks 2, 3, 6) +- ✅ `Expect` fluent matcher + extensible formatter (Task 5) +- ✅ Detailed `file:line` + actual/expected (Task 5 `details::Fail`) +- ✅ Bandit removed only at the very end (Task 6), Bandit coexists during dev (Tasks 1-5) +- ✅ No runtime burden on shipped `Pipe` lib (separate target, `Src/Tests/` excluded — Task 1) +- ✅ No `ToThrow` (no exceptions matchers) — confirmed +- ✅ `Describe` misuse = log + ignore (Task 3) +- ✅ `RunTests(int, char**)` (Tasks 2, 3) +- ✅ imgui-style global context, only `Spec` self-registers (Task 2, 3) + +**Placeholder scan:** All steps carry concrete code. The `Expect` matcher uses `Format("{}", value)` which needs a `StringView` formatter — flagged with an explicit fallback overload if missing. Task adds a note to verify `TArray` member names and add `String` no implicit `StringView` conversion fallback. No TODO/TBD beyond explicit in-task verification notes. + +**Type consistency:** `String`, `StringView`, `sizet`, `Number`, `TestString`, `ExpectValue`, `details::Fail(file, line, message)`, `RunTests(int,char**)` used consistently across tasks. \ No newline at end of file From 31a9b4501090331d230761749b1531541fc21dd8 Mon Sep 17 00:00:00 2001 From: muit Date: Fri, 4 Sep 2026 01:39:23 +0200 Subject: [PATCH 03/25] build: add PipeTests library target --- CMakeLists.txt | 14 ++++++++++++++ Include/PipeTests.h | 20 ++++++++++++++++++++ Src/Tests/PipeTests.cpp | 16 ++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 Include/PipeTests.h create mode 100644 Src/Tests/PipeTests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 71b46354..4d93c3ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,6 +63,7 @@ pipe_target_define_platform(Pipe) target_include_directories(Pipe PUBLIC $) target_include_directories(Pipe PRIVATE $) file(GLOB_RECURSE PIPE_SOURCE_FILES CONFIGURE_DEPENDS Src/*.cpp Src/*.c) +list(FILTER PIPE_SOURCE_FILES EXCLUDE REGEX ".*/Src/Tests/.*") target_sources(Pipe PRIVATE ${PIPE_SOURCE_FILES}) target_compile_definitions(Pipe PRIVATE NOMINMAX) @@ -81,6 +82,19 @@ pipe_target_shared_output_directory(Pipe) pipe_target_disable_rtti(Pipe PRIVATE) +################################################################################ +# PipeTests (test framework library, not part of the runtime Pipe library) + +add_library(PipeTestsLib STATIC Src/Tests/PipeTests.cpp) +add_library(Pipe::TestsLib ALIAS PipeTestsLib) +pipe_target_define_platform(PipeTestsLib) +target_include_directories(PipeTestsLib PUBLIC $) +pipe_target_enable_CPP20(PipeTestsLib) +pipe_target_disable_rtti(PipeTestsLib PRIVATE) +pipe_target_shared_output_directory(PipeTestsLib) +target_link_libraries(PipeTestsLib PUBLIC Pipe) + + ################################################################################ # Pipe Tests (compiled) executable diff --git a/Include/PipeTests.h b/Include/PipeTests.h new file mode 100644 index 00000000..00b62a13 --- /dev/null +++ b/Include/PipeTests.h @@ -0,0 +1,20 @@ +// Copyright 2015-2026 Piperift. All Rights Reserved. + +#pragma once + +#include "Pipe/Core/StringView.h" + +#include + + +namespace p +{ + void Spec(StringView name, std::function fn); + void Spec(std::function fn); + void Describe(StringView name, std::function fn); + void It(StringView name, std::function fn); + void XIt(StringView name, std::function fn); + void BeforeEach(std::function fn); + void AfterEach(std::function fn); + int RunTests(int argc, char** argv); +}; // namespace p diff --git a/Src/Tests/PipeTests.cpp b/Src/Tests/PipeTests.cpp new file mode 100644 index 00000000..d8a62b0e --- /dev/null +++ b/Src/Tests/PipeTests.cpp @@ -0,0 +1,16 @@ +// Copyright 2015-2026 Piperift. All Rights Reserved. + +#include "PipeTests.h" + + +namespace p +{ + void Spec(StringView name, std::function) { (void)name; } + void Spec(std::function) {} + void Describe(StringView name, std::function) { (void)name; } + void It(StringView name, std::function) { (void)name; } + void XIt(StringView name, std::function) { (void)name; } + void BeforeEach(std::function) {} + void AfterEach(std::function) {} + int RunTests(int argc, char** argv) { (void)argc; (void)argv; return 0; } +}; // namespace p From 683e3f6e345ac78b3d2ff727bed711282d7a439f Mon Sep 17 00:00:00 2001 From: muit Date: Fri, 4 Sep 2026 01:41:37 +0200 Subject: [PATCH 04/25] feat: declare PipeTests registration API --- Include/PipeTests.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Include/PipeTests.h b/Include/PipeTests.h index 00b62a13..f6f4d40a 100644 --- a/Include/PipeTests.h +++ b/Include/PipeTests.h @@ -9,12 +9,28 @@ namespace p { + /** + * Test framework for Pipe and Rift. + * Imgui-style global context: registration functions act on a current group. + * Spec is self-registering; Describe/It/BeforeEach/AfterEach attach to the + * current group as functions are called. + */ + + // Self-registering top-level. Spec(name, fn) also opens a first group named `name`. void Spec(StringView name, std::function fn); + // Nameless top-level (like go_bandit); use Describe inside fn. void Spec(std::function fn); + + // Nested group. Only valid inside a Spec; otherwise logs an error and ignores. void Describe(StringView name, std::function fn); + // Register a runnable test in the current group. void It(StringView name, std::function fn); + // Register a disabled test; never run. void XIt(StringView name, std::function fn); + // Setup hook attached to the current group. void BeforeEach(std::function fn); + // Teardown hook attached to the current group. void AfterEach(std::function fn); + int RunTests(int argc, char** argv); }; // namespace p From cbe2f17699923bd76d6090c4b8c3d8b8ba377a8c Mon Sep 17 00:00:00 2001 From: muit Date: Fri, 4 Sep 2026 01:44:58 +0200 Subject: [PATCH 05/25] feat: add PipeTests registry and runner --- Src/Tests/PipeTests.cpp | 236 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 227 insertions(+), 9 deletions(-) diff --git a/Src/Tests/PipeTests.cpp b/Src/Tests/PipeTests.cpp index d8a62b0e..ee8b8924 100644 --- a/Src/Tests/PipeTests.cpp +++ b/Src/Tests/PipeTests.cpp @@ -1,16 +1,234 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. +#ifndef P_OVERRIDE_NEWDELETE + #define P_OVERRIDE_NEWDELETE 1 +#endif +#if P_OVERRIDE_NEWDELETE + #include "PipeNewDelete.h" +#endif + #include "PipeTests.h" +#include "Pipe.h" +#include "Pipe/Core/Log.h" +#include "PipeStrings.h" namespace p { - void Spec(StringView name, std::function) { (void)name; } - void Spec(std::function) {} - void Describe(StringView name, std::function) { (void)name; } - void It(StringView name, std::function) { (void)name; } - void XIt(StringView name, std::function) { (void)name; } - void BeforeEach(std::function) {} - void AfterEach(std::function) {} - int RunTests(int argc, char** argv) { (void)argc; (void)argv; return 0; } -}; // namespace p + namespace + { + struct TestCase + { + String name; + std::function body; + bool skip = false; + }; + + struct TestGroup + { + String name; + TArray groups; // nested describes + TArray tests; // its + std::function beforeEach; + std::function afterEach; + }; + + // Entire registered suite (treat as a single virtual root group). + TestGroup root{"", {}, {}, {}, {}}; + + // Pointer into `root.groups` for the currently-adding group. + TestGroup* currentGroup = nullptr; + int failedTests = 0; + int runTests = 0; + int skippedTests = 0; + } // namespace + + + void Spec(StringView name, std::function fn) + { + TestGroup group; + group.name = String{name}; + group.beforeEach = nullptr; + group.afterEach = nullptr; + root.groups.Add(Move(group)); + TestGroup* groupPtr = &root.groups.Last(); + currentGroup = groupPtr; + fn(); + currentGroup = nullptr; + } + + void Spec(std::function fn) + { + currentGroup = &root; + fn(); + currentGroup = nullptr; + } + + void Describe(StringView name, std::function fn) + { + if (!currentGroup) + { + Error("PipeTests: Describe('{}') called outside a Spec. Ignoring.", name); + return; + } + + TestGroup group; + group.name = String{name}; + currentGroup->groups.Add(Move(group)); + TestGroup* prevGroup = currentGroup; + currentGroup = ¤tGroup->groups.Last(); + fn(); + currentGroup = prevGroup; + } + + void It(StringView name, std::function fn) + { + if (!currentGroup) + { + Error("PipeTests: It('{}') called outside a Spec. Ignoring.", name); + return; + } + TestCase test; + test.name = String{name}; + test.body = fn; + test.skip = false; + currentGroup->tests.Add(Move(test)); + } + + void XIt(StringView name, std::function fn) + { + if (!currentGroup) + { + Error("PipeTests: XIt('{}') called outside a Spec. Ignoring.", name); + return; + } + TestCase test; + test.name = String{name}; + test.body = fn; + test.skip = true; + currentGroup->tests.Add(Move(test)); + } + + void BeforeEach(std::function fn) + { + if (!currentGroup) + { + Error("PipeTests: BeforeEach called outside a Spec. Ignoring."); + return; + } + currentGroup->beforeEach = fn; + } + + void AfterEach(std::function fn) + { + if (!currentGroup) + { + Error("PipeTests: AfterEach called outside a Spec. Ignoring."); + return; + } + currentGroup->afterEach = fn; + } + + + namespace + { + static String FullName(const TestGroup& group, const TestCase& test) + { + // Build "SpecName.SubGroup.TestName" for reporting. Root has empty name. + String result; + if (!group.name.empty()) + { + result += group.name; + result += "."; + } + result += test.name; + return result; + } + + static void RunNested(TestGroup& group, + TArray>& beforeHooks, + TArray>& afterHooks) + { + if (group.beforeEach) + { + beforeHooks.Add(group.beforeEach); + } + if (group.afterEach) + { + afterHooks.Add(group.afterEach); + } + + for (TestGroup& sub : group.groups) + { + RunNested(sub, beforeHooks, afterHooks); + } + + for (TestCase& test : group.tests) + { + if (test.skip) + { + ++skippedTests; + continue; + } + ++runTests; + + for (auto& hook : beforeHooks) + { + hook(); + } + + bool passed = true; + try + { + test.body(); + } + catch (...) + { + passed = false; + Error("PipeTests: test failed by exception: {}", FullName(group, test)); + } + + for (i32 i = afterHooks.Size(); i > 0; --i) + { + afterHooks[i - 1](); + } + + if (passed) + { + Info(" [PASS] {}", FullName(group, test)); + } + else + { + ++failedTests; + Error(" [FAIL] {}", FullName(group, test)); + } + } + + if (group.beforeEach) + { + beforeHooks.RemoveLast(); + } + if (group.afterEach) + { + afterHooks.RemoveLast(); + } + } + } // namespace + + + int RunTests(int argc, char** argv) + { + (void)argc; // kept for future --filter support + (void)argv; + + Info("PipeTests: {} group(s) registered.", root.groups.Size()); + TArray> beforeHooks; + TArray> afterHooks; + RunNested(root, beforeHooks, afterHooks); + + Info("PipeTests complete: {} run, {} passed, {} failed, {} skipped.", + runTests, runTests - failedTests, failedTests, skippedTests); + + return failedTests == 0 ? 0 : 1; + } +} // namespace p From 85ccb14fdf9ed1b3287f934f14814acafd5bef3c Mon Sep 17 00:00:00 2001 From: muit Date: Fri, 4 Sep 2026 02:20:07 +0200 Subject: [PATCH 06/25] test: add PipeTests self-test suite --- Include/PipeTests.h | 8 ++- Src/Tests/PipeTests.cpp | 97 +++++++++++++++++++----------- Tests/CMakeLists.txt | 5 +- Tests/PipeTests/CMakeLists.txt | 8 +++ Tests/PipeTests/PipeTests.spec.cpp | 39 ++++++++++++ Tests/PipeTests/main.cpp | 21 +++++++ 6 files changed, 138 insertions(+), 40 deletions(-) create mode 100644 Tests/PipeTests/CMakeLists.txt create mode 100644 Tests/PipeTests/PipeTests.spec.cpp create mode 100644 Tests/PipeTests/main.cpp diff --git a/Include/PipeTests.h b/Include/PipeTests.h index f6f4d40a..ce2fa926 100644 --- a/Include/PipeTests.h +++ b/Include/PipeTests.h @@ -12,11 +12,13 @@ namespace p /** * Test framework for Pipe and Rift. * Imgui-style global context: registration functions act on a current group. - * Spec is self-registering; Describe/It/BeforeEach/AfterEach attach to the - * current group as functions are called. + * Spec opens a first-level group; Describe/It/BeforeEach/AfterEach attach to + * the current group as functions are called. Registration runs inside a + * function (e.g. a Register*Tests() routine called from main) — the framework + * uses no macros, so specs must not be registered at namespace scope. */ - // Self-registering top-level. Spec(name, fn) also opens a first group named `name`. + // Self-registering top-level. Spec(name, fn) opens a first group named `name`. void Spec(StringView name, std::function fn); // Nameless top-level (like go_bandit); use Describe inside fn. void Spec(std::function fn); diff --git a/Src/Tests/PipeTests.cpp b/Src/Tests/PipeTests.cpp index ee8b8924..b29d39cd 100644 --- a/Src/Tests/PipeTests.cpp +++ b/Src/Tests/PipeTests.cpp @@ -7,10 +7,10 @@ #include "PipeNewDelete.h" #endif -#include "PipeTests.h" #include "Pipe.h" #include "Pipe/Core/Log.h" #include "PipeStrings.h" +#include "PipeTests.h" namespace p @@ -34,39 +34,59 @@ namespace p }; // Entire registered suite (treat as a single virtual root group). - TestGroup root{"", {}, {}, {}, {}}; + struct RegistryState + { + TestGroup root{"", {}, {}, {}, {}}; + + // Pointer into `root.groups` for the currently-adding group. + TestGroup* currentGroup = nullptr; + int failedTests = 0; + int runTests = 0; + int skippedTests = 0; + }; - // Pointer into `root.groups` for the currently-adding group. - TestGroup* currentGroup = nullptr; - int failedTests = 0; - int runTests = 0; - int skippedTests = 0; + // Function-local static: initialized on first use regardless of the + // static-init order of other translation units, so a `Spec` registrar + // defined in a separate TU can safely register during static init. + RegistryState& State() + { + static RegistryState state; + return state; + } + + TestGroup*& CurrentGroup() + { + return State().currentGroup; + } } // namespace void Spec(StringView name, std::function fn) { + RegistryState& state = State(); TestGroup group; group.name = String{name}; group.beforeEach = nullptr; group.afterEach = nullptr; - root.groups.Add(Move(group)); - TestGroup* groupPtr = &root.groups.Last(); - currentGroup = groupPtr; + state.root.groups.Add(Move(group)); + TestGroup* groupPtr = &state.root.groups.Last(); + state.currentGroup = groupPtr; fn(); - currentGroup = nullptr; + state.currentGroup = nullptr; } void Spec(std::function fn) { - currentGroup = &root; + RegistryState& state = State(); + state.currentGroup = &state.root; fn(); - currentGroup = nullptr; + state.currentGroup = nullptr; } void Describe(StringView name, std::function fn) { - if (!currentGroup) + TestGroup*& current = CurrentGroup(); + if (!current) { Error("PipeTests: Describe('{}') called outside a Spec. Ignoring.", name); return; @@ -74,16 +94,17 @@ namespace p TestGroup group; group.name = String{name}; - currentGroup->groups.Add(Move(group)); - TestGroup* prevGroup = currentGroup; - currentGroup = ¤tGroup->groups.Last(); + current->groups.Add(Move(group)); + TestGroup* prevGroup = current; + current = ¤t->groups.Last(); fn(); - currentGroup = prevGroup; + current = prevGroup; } void It(StringView name, std::function fn) { - if (!currentGroup) + TestGroup*& current = CurrentGroup(); + if (!current) { Error("PipeTests: It('{}') called outside a Spec. Ignoring.", name); return; @@ -92,12 +113,13 @@ namespace p test.name = String{name}; test.body = fn; test.skip = false; - currentGroup->tests.Add(Move(test)); + current->tests.Add(Move(test)); } void XIt(StringView name, std::function fn) { - if (!currentGroup) + TestGroup*& current = CurrentGroup(); + if (!current) { Error("PipeTests: XIt('{}') called outside a Spec. Ignoring.", name); return; @@ -106,27 +128,29 @@ namespace p test.name = String{name}; test.body = fn; test.skip = true; - currentGroup->tests.Add(Move(test)); + current->tests.Add(Move(test)); } void BeforeEach(std::function fn) { - if (!currentGroup) + TestGroup*& current = CurrentGroup(); + if (!current) { Error("PipeTests: BeforeEach called outside a Spec. Ignoring."); return; } - currentGroup->beforeEach = fn; + current->beforeEach = fn; } void AfterEach(std::function fn) { - if (!currentGroup) + TestGroup*& current = CurrentGroup(); + if (!current) { Error("PipeTests: AfterEach called outside a Spec. Ignoring."); return; } - currentGroup->afterEach = fn; + current->afterEach = fn; } @@ -145,10 +169,10 @@ namespace p return result; } - static void RunNested(TestGroup& group, - TArray>& beforeHooks, + static void RunNested(TestGroup& group, TArray>& beforeHooks, TArray>& afterHooks) { + RegistryState& state = State(); if (group.beforeEach) { beforeHooks.Add(group.beforeEach); @@ -167,10 +191,10 @@ namespace p { if (test.skip) { - ++skippedTests; + ++state.skippedTests; continue; } - ++runTests; + ++state.runTests; for (auto& hook : beforeHooks) { @@ -199,7 +223,7 @@ namespace p } else { - ++failedTests; + ++state.failedTests; Error(" [FAIL] {}", FullName(group, test)); } } @@ -221,14 +245,15 @@ namespace p (void)argc; // kept for future --filter support (void)argv; - Info("PipeTests: {} group(s) registered.", root.groups.Size()); + RegistryState& state = State(); + Info("PipeTests: {} group(s) registered.", state.root.groups.Size()); TArray> beforeHooks; TArray> afterHooks; - RunNested(root, beforeHooks, afterHooks); + RunNested(state.root, beforeHooks, afterHooks); - Info("PipeTests complete: {} run, {} passed, {} failed, {} skipped.", - runTests, runTests - failedTests, failedTests, skippedTests); + Info("PipeTests complete: {} run, {} passed, {} failed, {} skipped.", state.runTests, + state.runTests - state.failedTests, state.failedTests, state.skippedTests); - return failedTests == 0 ? 0 : 1; + return state.failedTests == 0 ? 0 : 1; } } // namespace p diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 893e1828..01a7e151 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -1,6 +1,7 @@ # Copyright 2015-2023 Piperift - All rights reserved file(GLOB_RECURSE TESTS_SOURCE_FILES CONFIGURE_DEPENDS *.cpp *.h *.hpp) +list(FILTER TESTS_SOURCE_FILES EXCLUDE REGEX ".*/PipeTests/.*") add_executable(PipeTests ${TESTS_SOURCE_FILES}) add_executable(Pipe::Tests ALIAS PipeTests) @@ -12,4 +13,6 @@ pipe_target_shared_output_directory(PipeTests) target_link_libraries(PipeTests PUBLIC Pipe Bandit) pipe_add_sanitizers(PipeTests) -add_test(NAME PipeTests COMMAND $ --reporter=spec) \ No newline at end of file +add_test(NAME PipeTests COMMAND $ --reporter=spec) + +add_subdirectory(PipeTests) \ No newline at end of file diff --git a/Tests/PipeTests/CMakeLists.txt b/Tests/PipeTests/CMakeLists.txt new file mode 100644 index 00000000..85aa890f --- /dev/null +++ b/Tests/PipeTests/CMakeLists.txt @@ -0,0 +1,8 @@ +# PipeTests self-test (uses the new framework). Bandit-based suite remains unchanged. +add_executable(PipeTestsSelf PipeTests.spec.cpp main.cpp) +pipe_target_define_platform(PipeTestsSelf) +pipe_target_enable_CPP20(PipeTestsSelf) +pipe_target_disable_rtti(PipeTestsSelf PRIVATE) +pipe_target_shared_output_directory(PipeTestsSelf) +target_link_libraries(PipeTestsSelf PUBLIC PipeTestsLib Pipe) +add_test(NAME PipeTestsSelf COMMAND $) diff --git a/Tests/PipeTests/PipeTests.spec.cpp b/Tests/PipeTests/PipeTests.spec.cpp new file mode 100644 index 00000000..aa224a3c --- /dev/null +++ b/Tests/PipeTests/PipeTests.spec.cpp @@ -0,0 +1,39 @@ +// Copyright 2015-2026 Piperift. All Rights Reserved. + +#include + +#include + +using namespace p; + +static int beforeEachCount = 0; +static int afterEachCount = 0; +static int topTestResult = 0; + + +void RegisterPipeTests() +{ + Spec("PipeTests", []() + { + BeforeEach([]() + { + ++beforeEachCount; + }); + AfterEach([]() + { + ++afterEachCount; + }); + + Describe("Basics", []() + { + It("Registers and runs", []() + { + topTestResult = 42; + }); + XIt("Is skipped", []() + { + topTestResult = -1; + }); + }); + }); +} \ No newline at end of file diff --git a/Tests/PipeTests/main.cpp b/Tests/PipeTests/main.cpp new file mode 100644 index 00000000..3d7b7c9d --- /dev/null +++ b/Tests/PipeTests/main.cpp @@ -0,0 +1,21 @@ +// Copyright 2015-2026 Piperift. All Rights Reserved. + +// NOTE: PipeNewDelete is deliberately not included here. PipeTestsLib provides the +// replacement operator new/delete (P_OVERRIDE_NEWDELETE) in its own translation unit; +// including it here too would cause duplicate-definition linker errors. + +#include +#include + + +void RegisterPipeTests(); + + +int main(int argc, char* argv[]) +{ + p::Initialize(); + RegisterPipeTests(); + int result = p::RunTests(argc, argv); + p::Shutdown(); + return result; +} From 0c3bd5332dc6d00c38b03f0a351bcc29e58889f2 Mon Sep 17 00:00:00 2001 From: muit Date: Fri, 4 Sep 2026 02:20:18 +0200 Subject: [PATCH 07/25] docs: macro-free registration, PipeTestsLib rename in plan+spec --- Docs/Plans/2026-09-04-pipe-tests-framework.md | 169 +++++++++++------- .../2026-09-04-pipe-tests-framework-design.md | 74 ++++---- 2 files changed, 140 insertions(+), 103 deletions(-) diff --git a/Docs/Plans/2026-09-04-pipe-tests-framework.md b/Docs/Plans/2026-09-04-pipe-tests-framework.md index 4110b1a0..755d28bf 100644 --- a/Docs/Plans/2026-09-04-pipe-tests-framework.md +++ b/Docs/Plans/2026-09-04-pipe-tests-framework.md @@ -6,6 +6,8 @@ **Architecture:** A new `PipeTests` module in the Pipe submodule (`Include/PipeTests.h` + `Src/PipeTests.cpp`) built as a **separate CMake library target** (never compiled into the runtime `Pipe` library). Global registration cursor tracks the current test group as functions are called. `Expect(value)` returns a fluent matcher. `p::RunTests(argc, argv)` runs the suite. Existing Bandit tests are NOT migrated and Bandit is NOT removed until the final task. +**Macro-free registration (decision 2026-09-04):** The framework uses NO macros. `Spec`/`Describe`/`It`/`XIt`/`BeforeEach`/`AfterEach` are plain functions in namespace `p`. Because a bare function call is ill-formed at namespace scope (C++ only permits declarations there), specs **cannot self-register at global scope**. Instead, tests are registered from inside a function: each spec file exports a `Register*Tests()` routine, and the test executable's `main()` calls it (before `p::RunTests`) exactly once. The registry uses a function-local `static` (`State()`), so it initializes on first use regardless of translation-unit order. `Spec(fn)` (nameless, go_bandit-style) and `Spec(name, fn)` are both supported; `Spec(fn)` registers into the virtual root group. + **Tech Stack:** C++20, CMake 3.26+, no exceptions, no RTTI (`-fno-rtti`). Pipe core types: `StringView`, `String`, `TArray`, `std::function`, `p::Format`, `p::Info/Warning/Error`. ## Global Constraints @@ -126,8 +128,10 @@ namespace p /** * Test framework for Pipe and Rift. * Imgui-style global context: registration functions act on a current group. - * Spec is self-registering; Describe/It/BeforeEach/AfterEach attach to the - * current group as functions are called. + * Spec opens a first-level group; Describe/It/BeforeEach/AfterEach attach to + * the current group as functions are called. Registration runs inside a + * function (e.g. a Register*Tests() routine called from main) - the framework + * uses no macros, so specs must not be registered at namespace scope. */ // Self-registering top-level. Spec(name, fn) also opens a first group named `name`. @@ -216,16 +220,34 @@ namespace p }; // Entire registered suite (treat as a single virtual root group). - TestGroup root{"", nullptr, {}, {}, {}, {}}; + struct RegistryState + { + TestGroup root{"", {}, {}, {}, {}}; + + // Pointer into `root.groups` for the currently-adding group. + TestGroup* currentGroup = nullptr; + int failedTests = 0; + int runTests = 0; + int skippedTests = 0; + }; - // Pointer into `root.groups` for the currently-adding group. - TestGroup* currentGroup = nullptr; - int failedTests = 0; - int runTests = 0; - int skippedTests = 0; + // Function-local static: initialized on first use regardless of the + // static-init order of other translation units. + RegistryState& State() + { + static RegistryState state; + return state; + } + + TestGroup*& CurrentGroup() + { + return State().currentGroup; + } } // namespace ``` +Note: registration runs at runtime (from a `Register*Tests()` called in `main`), so the registry uses a function-local `static` (`State()`). This avoids static-init-order hazards if registration is ever invoked before `main`, and keeps all mutable suite state in one lazily-created object. `State()`/`CurrentGroup()` are `inline` file-local accessors used by every registration function. + Note: `String` and `TArray` require `PipeStrings.h`/`PipeContainers.h` — included via `Pipe.h`? `Pipe.h` only includes `StringView.h` + `Export.h`. Include `PipeStrings.h` explicitly (done above). Ensure `PipeTests.cpp` links against Pipe (done in Task 1 via `target_link_libraries(PipeTests PUBLIC Pipe)`). Because `TestGroup` contains `TArray`, `TArray` must be usable with incomplete types or we must define nested groups via `std::vector`. Since Pipe disallows `std::vector` in headers but this is a `.cpp`, use `TArray`. If `TArray` fails to compile due to incomplete type during the initial member declaration, add a forward `struct TestGroup;` before `TestGroup` and store `TArray`. Prefer keeping the above form; adjust only if the compiler requires it. @@ -523,11 +545,7 @@ git commit -m "feat: add PipeTests registry and runner" - Create: `Extern/Pipe/Tests/PipeTests/main.cpp` **Interfaces:** -- Consumes: `PipeTests.h`, `Pipe.h`, `p::Expect` (Task 5). To avoid depending on Task 5, this task's runner can use a temporary assertion until Task 5 lands. The spec file uses the framework's own `Spec/Describe/It/XIt/BeforeEach/AfterEach` + a minimal in-test check via a temporary macro defined in this file's `main.cpp` (or add `Expect` here as a stub returning `void`). Simpler: implement this task to compile against the header, but **defer the actual `Expect` matcher to Task 5 and add assertions in Task 5**. To keep the framework testable now, use a tiny local macro: - -```cpp -#define CHECK_TRUE(x) do { if (!(x)) { p::Error("CHECK_TRUE failed: {}", #x); } } while (0) -``` +- Consumes: `PipeTests.h`, `Pipe.h`, `p::Expect` (Task 5). To avoid depending on Task 5, implement this task to compile against the header and **defer the actual `Expect` matcher to Task 5**, adding assertions there in step 3. The framework uses no macros (registration runs from a `Register*Tests()` function); assertions arrive with `Expect` in Task 5. - Produces: a second test executable `PipeTestsSelf` registered in CTest, proving the framework runs alongside the untouched Bandit suite. @@ -538,22 +556,28 @@ git commit -m "feat: add PipeTests registry and runner" ```cpp // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +// NOTE: PipeNewDelete is deliberately not included here. PipeTestsLib provides the +// replacement operator new/delete (P_OVERRIDE_NEWDELETE) in its own translation unit; +// including it here too would cause duplicate-definition linker errors. #include #include +void RegisterPipeTests(); + + int main(int argc, char* argv[]) { p::Initialize(); + RegisterPipeTests(); int result = p::RunTests(argc, argv); p::Shutdown(); return result; } ``` -`Extern/Pipe/Tests/PipeTests/PipeTests.spec.cpp`: +`Extern/Pipe/Tests/PipeTests/PipeTests.spec.cpp` (registration must run from a function — the framework has no macros): ```cpp // Copyright 2015-2026 Piperift. All Rights Reserved. @@ -569,15 +593,20 @@ static int afterEachCount = 0; static int topTestResult = 0; -Spec("PipeTests", []() { - BeforeEach([]() { ++beforeEachCount; }); - AfterEach([]() { ++afterEachCount; }); +void RegisterPipeTests() +{ + Spec("PipeTests", []() + { + BeforeEach([]() { ++beforeEachCount; }); + AfterEach([]() { ++afterEachCount; }); - Describe("Basics", []() { - It("Registers and runs", []() { topTestResult = 42; }); - XIt("Is skipped", []() { topTestResult = -1; }); + Describe("Basics", []() + { + It("Registers and runs", []() { topTestResult = 42; }); + XIt("Is skipped", []() { topTestResult = -1; }); + }); }); -}); +} // NOTE: assertions are added in Task 5 once Expect() exists. @@ -587,11 +616,6 @@ Spec("PipeTests", []() { `Extern/Pipe/Tests/CMakeLists.txt` — add after the existing `PipeTests` executable block, **without modifying** the existing Bandit-based target or its `--reporter=spec`: -```cmake -# PipeTests self-test (uses the new framework). Bandit-based suite remains unchanged. -add_executable(PipeTestsSelf EXCLUDE_FROM_ALL src_placeholder_main) -``` - Because the existing `PipeTests/CMakeLists.txt` uses `file(GLOB_RECURSE ...)` and adds its own `PipeTests` executable, adding a second executable in the same glob would collide (two `main.cpp`). Instead, add a **separate subdirectory** `Tests/PipeTests/` with its own `CMakeLists.txt`: Create `Extern/Pipe/Tests/PipeTests/CMakeLists.txt`: @@ -602,7 +626,7 @@ pipe_target_define_platform(PipeTestsSelf) pipe_target_enable_CPP20(PipeTestsSelf) pipe_target_disable_rtti(PipeTestsSelf PRIVATE) pipe_target_shared_output_directory(PipeTestsSelf) -target_link_libraries(PipeTestsSelf PUBLIC PipeTests Pipe) +target_link_libraries(PipeTestsSelf PUBLIC PipeTestsLib Pipe) add_test(NAME PipeTestsSelf COMMAND $) ``` @@ -902,34 +926,37 @@ Update `Extern/Pipe/Tests/PipeTests/PipeTests.spec.cpp` to use `Expect`: using namespace p; -Spec("PipeTests", []() { - Describe("Expect", []() { - It("ToEqual / ToNotEqual", []() { - int value = 4; - Expect(value).ToEqual(4); - Expect(value).ToNotEqual(5); - }); - It("Relational", []() { - int value = 4; - Expect(value).ToBeLess(5); - Expect(value).ToBeLessOrEqual(4); - Expect(value).ToBeGreater(3); - Expect(value).ToBeGreaterOrEqual(4); - }); - It("Booleans", []() { - bool flag = true; - Expect(flag).ToBeTrue(); - Expect(!flag).ToBeFalse(); - }); - It("Strings", []() { - Expect("acidic").ToContain("acid"); - Expect(String{"hello"}).ToNotContain("world"); - }); - It("Equals int", []() { - Expect(4).ToEqual(4); +void RegisterPipeTests() +{ + Spec("PipeTests", []() { + Describe("Expect", []() { + It("ToEqual / ToNotEqual", []() { + int value = 4; + Expect(value).ToEqual(4); + Expect(value).ToNotEqual(5); + }); + It("Relational", []() { + int value = 4; + Expect(value).ToBeLess(5); + Expect(value).ToBeLessOrEqual(4); + Expect(value).ToBeGreater(3); + Expect(value).ToBeGreaterOrEqual(4); + }); + It("Booleans", []() { + bool flag = true; + Expect(flag).ToBeTrue(); + Expect(!flag).ToBeFalse(); + }); + It("Strings", []() { + Expect("acidic").ToContain("acid"); + Expect(String{"hello"}).ToNotContain("world"); + }); + It("Equals int", []() { + Expect(4).ToEqual(4); + }); }); }); -}); +} ``` (note: `Expect(value).ToBeTrue()` requires `value` be usable in `if (!value)`; bool works.) @@ -1011,25 +1038,29 @@ New: using namespace p; -Spec("Strings", []() + +void RegisterStringViewTests() { - Describe("StringView", []() + Spec("Strings", []() { - It("Can assign from literal", []() + Describe("StringView", []() { - StringView v{"Kiwi"}; - Expect(v).ToEqual("Kiwi"); - Expect(v.size()).ToEqual(4); + It("Can assign from literal", []() + { + StringView v{"Kiwi"}; + Expect(v).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4); + }); + // ... other tests converted similarly ... }); - // ... other tests converted similarly ... }); -}); +} ``` Transform rules (from the spec): - `#include ` → `#include ` - `using namespace snowhouse; using namespace bandit;` → remove both; keep `using namespace p;` -- `go_bandit([](){ describe("G", [](){ ...`) → `Spec("G", [](){ ...` (drop the outer `go_bandit` extra nesting and one `describe` level; the top `Spec("Strings", ...)` replaces go_bandit+first describe) +- **Top-level:** `go_bandit([](){ describe("G", [](){ ...` → wrap in `void RegisterTests()` and inside use `Spec("G", [](){ ...` (drop the outer `go_bandit` extra nesting and one `describe` level; the top `Spec("Strings", ...)` replaces go_bandit+first describe). Because the framework has no macros and no global-scope self-registration, each migrated `spec.cpp` file exports one `RegisterXxxTests()` routine that `main.cpp` calls. - `describe(` → `Describe(` - `it(` → `It(` (drop the `[&]` → `[]`; lambdas no longer need `&` capture since framework state is global) - `xit(` → `XIt(` @@ -1044,7 +1075,7 @@ Transform rules (from the spec): - `AssertThat(x, Is().False())` → `Expect(x).ToBeFalse()` - `AssertThat(v.size(), Equals(4u))` → `Expect(v.size()).ToEqual(4u)` -Important: the top-level transform. Bandit files use `go_bandit([](){ describe("Strings", [](){...}) });`. Our `Spec("Strings", [](){...})` handles the `describe` level directly, so replace the pair with a single `Spec("Strings", fn)` and inside use `Describe`/`It`. For files that use `go_bandit` with a single top describe, keep that one as `Spec` and drop the now-redundant `Describe` wrapper if present. Follow the reference conversion exactly. +Important: the top-level transform. Bandit files use `go_bandit([](){ describe("Strings", [](){...}) });`. Our `Spec("Strings", [](){...})` handles the `describe` level directly, so replace the pair with a single `Spec("Strings", fn)` and inside use `Describe`/`It`. For files that use `go_bandit` with a single top describe, keep that one as `Spec` and drop the now-redundant `Describe` wrapper if present. Follow the reference conversion exactly. **Each converted file then wraps its top-level `Spec(...)` in a `RegisterXxxTests()` function** (unique name per file, e.g. `RegisterStringViewTests`), and `main.cpp` declares and calls each one. - [ ] **Step 2: Build + run the migrated file only (green)** @@ -1063,12 +1094,12 @@ Convert every `Extern/Pipe/Tests/**/*.spec.cpp` using the transform rules above. - [ ] **Step 4: Switch PipeTests exe to the new framework** `Extern/Pipe/Tests/CMakeLists.txt`: -- Change `target_link_libraries(PipeTests PUBLIC Pipe Bandit)` → `target_link_libraries(PipeTests PUBLIC Pipe PipeTests)` +- Change `target_link_libraries(PipeTests PUBLIC Pipe Bandit)` → `target_link_libraries(PipeTests PUBLIC Pipe PipeTestsLib)` (note: the framework library is `PipeTestsLib`, alias `Pipe::TestsLib`; `PipeTests` is the test-executable target name) - Remove `--reporter=spec` from `add_test(...)`: `add_test(NAME PipeTests COMMAND $)` - Remove the `list(FILTER ...)` exclusion added in Task 4 (restore the plain glob) so all spec files (including migrated ones) build into `PipeTests`. -`Extern/Pipe/Tests/main.cpp`: replace `int result = bandit::run(argc, argv);` with `int result = p::RunTests(argc, argv);`, and remove `#include `. Keep `#include ` and `p::Initialize`/`p::Shutdown`. +`Extern/Pipe/Tests/main.cpp`: replace `int result = bandit::run(argc, argv);` with calls to each migrated spec file's `RegisterXxxTests()` followed by `int result = p::RunTests(argc, argv);`, and remove `#include `. Because the framework has no global-scope self-registration, `main.cpp` must **declare and call every `Register*Tests()`** exported by the migrated spec files (e.g. `void RegisterStringViewTests();` + `RegisterStringViewTests();` before `RunTests`). Keep the `p::Initialize`/`p::Shutdown` calls; `PipeNewDelete.h` no longer needs to be included here if `PipeTestsLib` provides the override. `Extern/Pipe/Tests/PipeTests/CMakeLists.txt`: keep the `PipeTestsSelf` target for framework self-checks, OR fold the self-test spec files into the main `PipeTests` glob (remove the separate subdirectory). Keep `PipeTestsSelf` as-is for now (harmless), unless the main glob re-includes its files. Since the main glob is `GLOB_RECURSE *.cpp` from `Tests/`, it WILL include `Tests/PipeTests/*.cpp` again → duplicate `main()`. So when restoring the plain glob in step 4, re-apply a filter to EXCLUDE `Tests/PipeTests/` from the main `PipeTests` exe (keep `PipeTestsSelf` as a separate target): @@ -1098,8 +1129,8 @@ Expected: `PipeTests` runs all migrated tests with names/locations under the new - [ ] **Step 7: Migrate Rift tests + CMake** In `D:\Projects\Piperift\rift`: -- `Tests/CMakeLists.txt`: `target_link_libraries(RiftTests PUBLIC RiftASTLib Bandit)` → `target_link_libraries(RiftTests PUBLIC RiftASTLib PipeTests)`. Rift links `PipeTests` from the Pipe submodule; ensure Rift's build reaches the `PipeTests` target (it is defined unconditionally in `Extern/Pipe/CMakeLists.txt` per Task 1). -- Convert Rift `Tests/Project.spec.cpp`, `Tests/AST/Statements.spec.cpp`, `Tests/AST/Expressions.spec.cpp`, `Tests/AST/Namespaces.spec.cpp` per the transform rules (uses `before_each`/`after_each` → `BeforeEach`/`AfterEach`, `AssertThat(result, Equals(true))` → `Expect(result).ToBeTrue()`, etc.). Replace `#include ` and `using namespace snowhouse/bandit`. +- `Tests/CMakeLists.txt`: `target_link_libraries(RiftTests PUBLIC RiftASTLib Bandit)` → `target_link_libraries(RiftTests PUBLIC RiftASTLib Pipe::TestsLib)` (the framework library is `PipeTestsLib`, alias `Pipe::TestsLib`; it is defined unconditionally in `Extern/Pipe/CMakeLists.txt` per Task 1). Rift's `Tests/main.cpp` must declare and call the migrated spec files' `RegisterXxxTests()` routines before `p::RunTests`. +- Convert Rift `Tests/Project.spec.cpp`, `Tests/AST/Statements.spec.cpp`, `Tests/AST/Expressions.spec.cpp`, `Tests/AST/Namespaces.spec.cpp` per the transform rules (uses `before_each`/`after_each` → `BeforeEach`/`AfterEach`, `AssertThat(result, Equals(true))` → `Expect(result).ToBeTrue()`, etc.). Wrap each in a `Register*Tests()` function; remove `#include ` and `using namespace snowhouse/bandit`. - [ ] **Step 8: Full project build + tests + format** @@ -1141,7 +1172,7 @@ Note: the Rift commit must record the new Pipe submodule hash (`git add Extern/P - ✅ No `ToThrow` (no exceptions matchers) — confirmed - ✅ `Describe` misuse = log + ignore (Task 3) - ✅ `RunTests(int, char**)` (Tasks 2, 3) -- ✅ imgui-style global context, only `Spec` self-registers (Task 2, 3) +- ✅ imgui-style global context, macro-free functions; registration runs from `Register*Tests()` called in `main` (Task 2, 3) **Placeholder scan:** All steps carry concrete code. The `Expect` matcher uses `Format("{}", value)` which needs a `StringView` formatter — flagged with an explicit fallback overload if missing. Task adds a note to verify `TArray` member names and add `String` no implicit `StringView` conversion fallback. No TODO/TBD beyond explicit in-task verification notes. diff --git a/Docs/Specs/2026-09-04-pipe-tests-framework-design.md b/Docs/Specs/2026-09-04-pipe-tests-framework-design.md index dec34bcd..3ea4f836 100644 --- a/Docs/Specs/2026-09-04-pipe-tests-framework-design.md +++ b/Docs/Specs/2026-09-04-pipe-tests-framework-design.md @@ -25,38 +25,44 @@ The Pipe and Rift test suites rely on the vendored **Bandit** third-party test f ## API Design -Style: **imgui-style global context**. All functions are global in namespace `p`; the user writes `using namespace p;`. There is no capture-scoped "only relevant functions" context — context is global and tracked as functions are called. The exception is `Spec`, which is self-registering. +Style: **imgui-style global context**. All functions are global in namespace `p`; the user writes `using namespace p;`. There is no capture-scoped "only relevant functions" context — context is global and tracked as functions are called. + +**Macro-free registration (2026-09-04):** The framework uses **no macros** — `Spec`/`Describe`/`It`/`XIt`/`BeforeEach`/`AfterEach` are plain functions. Because a bare function call is ill-formed at namespace scope (C++ permits only declarations there), specs **cannot self-register at global scope**. Registrations run from a function: each spec file exports a `RegisterXxxTests()` routine, and the test executable's `main()` calls each one before `p::RunTests`. `Spec(name, fn)` opens a first-level named group; `Spec(fn)` registers the top level only. ```cpp using namespace p; -// Spec(name, fn) = go_bandit + first-level named group -Spec("Group", []() { - BeforeEach([]() { /* setup */ }); - AfterEach([]() { /* teardown */ }); - - Describe("Sub", []() { - It("does something", []() { - Expect(value).ToEqual(4); - Expect(value).ToNotEqual(5); - Expect(value).ToBeLess(5); - Expect(value).ToBeLessOrEqual(4); - Expect(value).ToBeGreater(2); +// Registration runs inside a function — never at namespace scope (no macros). +void RegisterGroupTests() +{ + // Spec(name, fn) = go_bandit + first-level named group + Spec("Group", []() { + BeforeEach([]() { /* setup */ }); + AfterEach([]() { /* teardown */ }); + + Describe("Sub", []() { + It("does something", []() { + Expect(value).ToEqual(4); + Expect(value).ToNotEqual(5); + Expect(value).ToBeLess(5); + Expect(value).ToBeLessOrEqual(4); + Expect(value).ToBeGreater(2); + }); + XIt("disabled test", []() { /* never runs */ }); }); - XIt("disabled test", []() { /* never runs */ }); - }); - It("top-level test", []() { - Expect("acidic").ToContain("acid"); - Expect(flag).ToBeTrue(); - Expect(other).ToBeFalse(); + It("top-level test", []() { + Expect("acidic").ToContain("acid"); + Expect(flag).ToBeTrue(); + Expect(other).ToBeFalse(); + }); }); -}); +} ``` ### Top-level semantics -- `Spec(name, fn)` — self-registering top-level entry that **also** opens a first-level group named `name`. This is the go_bandit + first `Describe` replacement. +- `Spec(name, fn)` — top-level entry that **also** opens a first-level group named `name`. This is the go_bandit + first `Describe` replacement. - `Spec(fn)` (nameless) — registers the top level only (like `go_bandit`), adding no extra group; `Describe` is then used inside it. - `Describe` is **runtime-checked** to only be valid inside a `Spec`. If used outside a `Spec`: **log an error and ignore** the offending group (no exception). @@ -64,7 +70,7 @@ Spec("Group", []() { | Function | Role | |----------|------| -| `Spec(name, fn)` / `Spec(fn)` | Self-registering top-level; optional first named group | +| `Spec(name, fn)` / `Spec(fn)` | Top-level group; called from a `Register*Tests()` routine | | `Describe(name, fn)` | Push a nested group; runtime-checked inside a `Spec` | | `It(name, fn)` | Register a runnable leaf test in the current group | | `XIt(name, fn)` | Register a leaf test marked **skipped** (never runs) | @@ -94,16 +100,16 @@ Fluent matcher methods, naming in Pipe CamelCase: ### New files (in the Pipe submodule) -- `Extern/Pipe/Include/PipeTests.h` — public API (global functions, `Expect` matcher, formatter hook). Mostly templates/macros. -- `Extern/Pipe/Src/PipeTests.cpp` — global registration cursor (current-group stack), test registry, `p::RunTests(int, char**)`. +- `Extern/Pipe/Include/PipeTests.h` — public API (global functions, `Expect` matcher, formatter hook). Mostly templates; **no macros**. +- `Extern/Pipe/Src/Tests/PipeTests.cpp` — function-local static registry state, registration functions (`Spec`/`Describe`/`It`/`XIt`/`BeforeEach`/`AfterEach`), `p::RunTests(int, char**)`. ### Build — separate target, not into the runtime Pipe lib `Extern/Pipe/CMakeLists.txt`: -- Define `add_library(PipeTests ...)` **unconditionally** (alongside `Pipe`, **before** the `PIPE_BUILD_TESTS` gate) so Rift can consume it via the submodule. -- **Exclude `Src/PipeTests.cpp`** from Pipe's `file(GLOB_RECURSE ... Src/*.cpp)` (currently line 65) so the test framework does **not** ship in the runtime `Pipe` library. Add it only to the `PipeTests` target. -- Give `PipeTests` the standard Pipe setup (`pipe_target_define_platform`, `pipe_target_enable_CPP20`, `pipe_target_disable_rtti`, `pipe_target_shared_output_directory`) and link `Pipe`; expose `Include/`. +- Define `add_library(PipeTestsLib ...)` (alias `Pipe::TestsLib`) **unconditionally** (alongside `Pipe`, **before** the `PIPE_BUILD_TESTS` gate) so Rift can consume it via the submodule. (Name is `PipeTestsLib` because `PipeTests` is already the Bandit-based test-executable target.) +- **Exclude `Src/Tests/PipeTests.cpp`** from Pipe's `file(GLOB_RECURSE ... Src/*.cpp)` (currently line 65) so the test framework does **not** ship in the runtime `Pipe` library. Add it only to the `PipeTestsLib` target. +- Give `PipeTestsLib` the standard Pipe setup (`pipe_target_define_platform`, `pipe_target_enable_CPP20`, `pipe_target_disable_rtti`, `pipe_target_shared_output_directory`) and link `Pipe`; expose `Include/`. ### Runner @@ -125,13 +131,13 @@ A current-group stack in `PipeTests.cpp`. `Describe` pushes its group, runs `fn` 1. **Add `PipeTests` module** — header + source; `add_library(PipeTests)`; source-glob exclusion. Build succeeds. 2. **Self-test the framework with small new tests** (no migration of existing tests): - - Create small **new** framework tests in `Extern/Pipe/Tests/PipeTests/` (e.g. `PipeTests.spec.cpp`) written with the new API to validate: `Spec`/`Describe`/`It`/`XIt`/`BeforeEach`/`AfterEach`, all `Expect` matchers, failure reporting, skip counting, `RunTests` exit code, and `Describe`-outside-`Spec` behavior. - - Wire a **separate small runner** (its own `main.cpp` calling `p::RunTests`) for this smoke target, running **alongside** the existing bandit `PipeTests` executable. + - Create small **new** framework tests in `Extern/Pipe/Tests/PipeTests/` (e.g. `PipeTests.spec.cpp`) written with the new API to validate: `Spec`/`Describe`/`It`/`XIt`/`BeforeEach`/`AfterEach`, all `Expect` matchers, failure reporting, skip counting, `RunTests` exit code, and `Describe`-outside-`Spec` behavior. The spec file exports a `Register*Tests()` routine. + - Wire a **separate small runner** (its own `main.cpp` calling the `Register*Tests()` then `p::RunTests`) for this smoke target, running **alongside** the existing bandit `PipeTests` executable. - Verify via `ctest` that **both** the new self-tests and the untouched bandit suite pass. Iterate until the framework is proven. 3. **Final flip (LAST, only when the system is done):** - - Migrate existing `*spec.cpp` files file-by-file (transform map below). - - `Extern/Pipe/Tests/CMakeLists.txt`: link `Pipe` + `PipeTests`, drop `Bandit`; `main.cpp` → `p::RunTests(argc, argv)`; drop `--reporter=spec`. - - Migrate Rift `Tests/*.spec.cpp` + `Tests/CMakeLists.txt`: replace `Bandit` with `PipeTests`. + - Migrate existing `*spec.cpp` files file-by-file (transform map below). Each migrated file exports a `RegisterXxxTests()` routine instead of registering at global scope. + - `Extern/Pipe/Tests/CMakeLists.txt`: link `Pipe` + `PipeTestsLib`, drop `Bandit`; `main.cpp` calls each `Register*Tests()` then `p::RunTests(argc, argv)`; drop `--reporter=spec`. + - Migrate Rift `Tests/*.spec.cpp` + `Tests/CMakeLists.txt`: replace `Bandit` with `Pipe::TestsLib` (the alias), and call the migrated `Register*Tests()` from Rift's `Tests/main.cpp`. - Remove `Bandit`: delete the `Bandit` INTERFACE target from `Extern/Pipe/Extern/CMakeLists.txt`, remove the vendored `Extern/Pipe/Extern/Bandit/` directory, strip remaining bandit includes. - Final full `ctest` + `ClangFormat`/`ClangTidy` pass. @@ -140,7 +146,7 @@ A current-group stack in `PipeTests.cpp`. `Describe` pushes its group, runs `fn` | Before (bandit) | After (PipeTests) | |-----------------|-------------------| | `#include ` + `using namespace snowhouse; using namespace bandit;` | `#include ` + `using namespace p;` | -| `go_bandit([](){ describe("G", ...) })` | `Spec("G", [](){ ... })` | +| `go_bandit([](){ describe("G", ...) })` (global scope) | Wrap in `void RegisterXxxTests() { Spec("G", [](){ ... }) }`; call from `main` | | `describe(...)` | `Describe(...)` | | `it(...)` | `It(...)` | | `xit(...)` | `XIt(...)` | @@ -159,7 +165,7 @@ Note: existing test files that `using namespace snowhouse; using namespace bandi - `Bandit` INTERFACE target is defined in `Extern/Pipe/Extern/CMakeLists.txt`, added **unconditionally** (Pipe `CMakeLists.txt` line 47 `add_subdirectory(Extern)` runs before the `PIPE_BUILD_TESTS` gate). - Pipe supplies its own tests executable gated behind `PIPE_BUILD_TESTS` (default `PIPE_IS_PROJECT`, i.e. ON when Pipe is the top project). - Rift builds its **own** `RiftTests` executable in `Tests/CMakeLists.txt`, linking `RiftASTLib` + `Bandit` (imported through Pipe's `Extern`). -- Therefore the `PipeTests` target must be defined **unconditionally** in Pipe's `CMakeLists.txt` so Rift can link it. +- Therefore the `PipeTestsLib` target (alias `Pipe::TestsLib`) must be defined **unconditionally** in Pipe's `CMakeLists.txt` so Rift can link it. ## Constraints & style From cddd073fe925d03999b194b014487b1aeabf5d3a Mon Sep 17 00:00:00 2001 From: muit Date: Fri, 4 Sep 2026 02:31:25 +0200 Subject: [PATCH 08/25] feat: add Expect fluent matcher --- Include/PipeTests.h | 161 +++++++++++++++++++++++++++++ Src/Tests/PipeTests.cpp | 21 +++- Tests/PipeTests/PipeTests.spec.cpp | 34 ++++++ 3 files changed, 212 insertions(+), 4 deletions(-) diff --git a/Include/PipeTests.h b/Include/PipeTests.h index ce2fa926..be668558 100644 --- a/Include/PipeTests.h +++ b/Include/PipeTests.h @@ -3,6 +3,7 @@ #pragma once #include "Pipe/Core/StringView.h" +#include "PipeStrings.h" #include @@ -35,4 +36,164 @@ namespace p void AfterEach(std::function fn); int RunTests(int argc, char** argv); + + + // Extensible value-to-string hook for failure messages. + // Specialize for user types. Default handles numbers and string views. + template + inline String TestString(const T& value); + + template + inline String TestString(const T& value) + { + return Format("{}", value); + } + + template<> + inline String TestString(const bool& value) + { + return value ? String{"true"} : String{"false"}; + } + + template<> + inline String TestString(const char& value) + { + return String{value}; + } + + inline String TestString(const StringView value) + { + return String{value}; + } + + inline String TestString(const String& value) + { + return String{value}; + } + + inline String TestString(const char* value) + { + return value ? String{value} : String{"(null)"}; + } + + namespace details + { + // Format failure message from file:line + description. + void Fail(const char* file, sizet line, StringView message); + } // namespace details + + + // ---- fluent assertion ---- + template + class ExpectValue + { + public: + ExpectValue(const Actual& value, const char* file, sizet line) + : value(value) + , file(file) + , line(line) + {} + + void ToEqual(const Actual& expected) const + { + if (!(value == expected)) + { + details::Fail(file, line, Format( + "Expected {} to equal {}", TestString(value), TestString(expected))); + } + } + + void ToNotEqual(const Actual& expected) const + { + if (!(value != expected)) + { + details::Fail(file, line, Format( + "Expected {} to not equal {}", TestString(value), TestString(expected))); + } + } + + void ToBeLess(const Actual& other) const + { + if (!(value < other)) + { + details::Fail(file, line, Format( + "Expected {} to be less than {}", TestString(value), TestString(other))); + } + } + + void ToBeLessOrEqual(const Actual& other) const + { + if (!(value <= other)) + { + details::Fail(file, line, Format( + "Expected {} to be less or equal to {}", TestString(value), TestString(other))); + } + } + + void ToBeGreater(const Actual& other) const + { + if (!(value > other)) + { + details::Fail(file, line, Format( + "Expected {} to be greater than {}", TestString(value), TestString(other))); + } + } + + void ToBeGreaterOrEqual(const Actual& other) const + { + if (!(value >= other)) + { + details::Fail(file, line, Format( + "Expected {} to be greater or equal to {}", TestString(value), TestString(other))); + } + } + + void ToBeTrue() const + { + if (!value) + { + details::Fail(file, line, "Expected value to be true"); + } + } + + void ToBeFalse() const + { + if (value) + { + details::Fail(file, line, "Expected value to be false"); + } + } + + void ToContain(const StringView sub) const + { + StringView view{value}; + if (Strings::Find(view, sub) == StringView::npos) + { + details::Fail(file, line, Format( + "Expected {} to contain {}", TestString(value), TestString(sub))); + } + } + + void ToNotContain(const StringView sub) const + { + StringView view{value}; + if (Strings::Find(view, sub) != StringView::npos) + { + details::Fail(file, line, Format( + "Expected {} to not contain {}", TestString(value), TestString(sub))); + } + } + + private: + const Actual& value; + const char* file; + sizet line; + }; + + // Returns a matcher bound to file/line for reporting. + template + ExpectValue Expect(const T& value, const char* file = __FILE__, sizet line = __LINE__) + { + return ExpectValue(value, file, line); + } }; // namespace p diff --git a/Src/Tests/PipeTests.cpp b/Src/Tests/PipeTests.cpp index b29d39cd..6f462700 100644 --- a/Src/Tests/PipeTests.cpp +++ b/Src/Tests/PipeTests.cpp @@ -39,10 +39,11 @@ namespace p TestGroup root{"", {}, {}, {}, {}}; // Pointer into `root.groups` for the currently-adding group. - TestGroup* currentGroup = nullptr; - int failedTests = 0; - int runTests = 0; - int skippedTests = 0; + TestGroup* currentGroup = nullptr; + int failedTests = 0; + int runTests = 0; + int skippedTests = 0; + int currentTestFailureCount = 0; }; // Function-local static: initialized on first use regardless of the @@ -61,6 +62,16 @@ namespace p } // namespace + namespace details + { + void Fail(const char* file, sizet line, StringView message) + { + Error("PipeTests: {}:{}: {}", file, line, message); + ++State().currentTestFailureCount; + } + } // namespace details + + void Spec(StringView name, std::function fn) { RegistryState& state = State(); @@ -201,6 +212,7 @@ namespace p hook(); } + state.currentTestFailureCount = 0; bool passed = true; try { @@ -211,6 +223,7 @@ namespace p passed = false; Error("PipeTests: test failed by exception: {}", FullName(group, test)); } + passed = passed && (state.currentTestFailureCount == 0); for (i32 i = afterHooks.Size(); i > 0; --i) { diff --git a/Tests/PipeTests/PipeTests.spec.cpp b/Tests/PipeTests/PipeTests.spec.cpp index aa224a3c..ce19f3ec 100644 --- a/Tests/PipeTests/PipeTests.spec.cpp +++ b/Tests/PipeTests/PipeTests.spec.cpp @@ -1,6 +1,7 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. #include +#include #include @@ -35,5 +36,38 @@ void RegisterPipeTests() topTestResult = -1; }); }); + + Describe("Expect", []() + { + It("ToEqual / ToNotEqual", []() + { + int value = 4; + Expect(value).ToEqual(4); + Expect(value).ToNotEqual(5); + }); + It("Relational", []() + { + int value = 4; + Expect(value).ToBeLess(5); + Expect(value).ToBeLessOrEqual(4); + Expect(value).ToBeGreater(3); + Expect(value).ToBeGreaterOrEqual(4); + }); + It("Booleans", []() + { + bool flag = true; + Expect(flag).ToBeTrue(); + Expect(!flag).ToBeFalse(); + }); + It("Strings", []() + { + Expect("acidic").ToContain("acid"); + Expect(String{"hello"}).ToNotContain("world"); + }); + It("Equals int", []() + { + Expect(4).ToEqual(4); + }); + }); }); } \ No newline at end of file From e4bb22b783ec1fb746e9d72ccc3a0543adb935e3 Mon Sep 17 00:00:00 2001 From: muit Date: Fri, 4 Sep 2026 02:41:58 +0200 Subject: [PATCH 09/25] fix: use source_location for call-site failure reporting --- Include/PipeTests.h | 39 +++++++++++++++++++-------------------- Src/Tests/PipeTests.cpp | 4 ++-- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/Include/PipeTests.h b/Include/PipeTests.h index be668558..c47d25ff 100644 --- a/Include/PipeTests.h +++ b/Include/PipeTests.h @@ -6,6 +6,7 @@ #include "PipeStrings.h" #include +#include namespace p @@ -78,8 +79,8 @@ namespace p namespace details { - // Format failure message from file:line + description. - void Fail(const char* file, sizet line, StringView message); + // Format failure message from source location + description. + void Fail(const std::source_location& loc, StringView message); } // namespace details @@ -88,17 +89,16 @@ namespace p class ExpectValue { public: - ExpectValue(const Actual& value, const char* file, sizet line) + ExpectValue(const Actual& value, const std::source_location& loc) : value(value) - , file(file) - , line(line) + , loc(loc) {} void ToEqual(const Actual& expected) const { if (!(value == expected)) { - details::Fail(file, line, Format( + details::Fail(loc, Format( "Expected {} to equal {}", TestString(value), TestString(expected))); } } @@ -107,7 +107,7 @@ namespace p { if (!(value != expected)) { - details::Fail(file, line, Format( + details::Fail(loc, Format( "Expected {} to not equal {}", TestString(value), TestString(expected))); } } @@ -116,7 +116,7 @@ namespace p { if (!(value < other)) { - details::Fail(file, line, Format( + details::Fail(loc, Format( "Expected {} to be less than {}", TestString(value), TestString(other))); } } @@ -125,7 +125,7 @@ namespace p { if (!(value <= other)) { - details::Fail(file, line, Format( + details::Fail(loc, Format( "Expected {} to be less or equal to {}", TestString(value), TestString(other))); } } @@ -134,7 +134,7 @@ namespace p { if (!(value > other)) { - details::Fail(file, line, Format( + details::Fail(loc, Format( "Expected {} to be greater than {}", TestString(value), TestString(other))); } } @@ -143,7 +143,7 @@ namespace p { if (!(value >= other)) { - details::Fail(file, line, Format( + details::Fail(loc, Format( "Expected {} to be greater or equal to {}", TestString(value), TestString(other))); } } @@ -152,7 +152,7 @@ namespace p { if (!value) { - details::Fail(file, line, "Expected value to be true"); + details::Fail(loc, "Expected value to be true"); } } @@ -160,7 +160,7 @@ namespace p { if (value) { - details::Fail(file, line, "Expected value to be false"); + details::Fail(loc, "Expected value to be false"); } } @@ -169,7 +169,7 @@ namespace p StringView view{value}; if (Strings::Find(view, sub) == StringView::npos) { - details::Fail(file, line, Format( + details::Fail(loc, Format( "Expected {} to contain {}", TestString(value), TestString(sub))); } } @@ -179,21 +179,20 @@ namespace p StringView view{value}; if (Strings::Find(view, sub) != StringView::npos) { - details::Fail(file, line, Format( + details::Fail(loc, Format( "Expected {} to not contain {}", TestString(value), TestString(sub))); } } private: const Actual& value; - const char* file; - sizet line; + std::source_location loc; }; - // Returns a matcher bound to file/line for reporting. + // Returns a matcher bound to the caller's source location for reporting. template - ExpectValue Expect(const T& value, const char* file = __FILE__, sizet line = __LINE__) + ExpectValue Expect(const T& value, const std::source_location loc = std::source_location::current()) { - return ExpectValue(value, file, line); + return ExpectValue(value, loc); } }; // namespace p diff --git a/Src/Tests/PipeTests.cpp b/Src/Tests/PipeTests.cpp index 6f462700..0e1894db 100644 --- a/Src/Tests/PipeTests.cpp +++ b/Src/Tests/PipeTests.cpp @@ -64,9 +64,9 @@ namespace p namespace details { - void Fail(const char* file, sizet line, StringView message) + void Fail(const std::source_location& loc, StringView message) { - Error("PipeTests: {}:{}: {}", file, line, message); + Error("PipeTests: {}:{}: {}", loc.file_name(), loc.line(), message); ++State().currentTestFailureCount; } } // namespace details From 20136dded9f56c8e55e7ea576c26d4170a949b95 Mon Sep 17 00:00:00 2001 From: muit Date: Fri, 4 Sep 2026 02:43:19 +0200 Subject: [PATCH 10/25] docs: source_location for call-site EXPECT reporting --- Docs/Plans/2026-09-04-pipe-tests-framework.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Docs/Plans/2026-09-04-pipe-tests-framework.md b/Docs/Plans/2026-09-04-pipe-tests-framework.md index 755d28bf..2d50bb63 100644 --- a/Docs/Plans/2026-09-04-pipe-tests-framework.md +++ b/Docs/Plans/2026-09-04-pipe-tests-framework.md @@ -852,15 +852,15 @@ Note: `ToBeTrue/ToBeFalse` require `value` convertible to bool (works for bool a Finally the entry macro/function: ```cpp - // Returns a matcher bound to file/line for reporting. + // Returns a matcher bound to the caller's source location for reporting. template - ExpectValue Expect(const T& value, const char* file = __FILE__, sizet line = __LINE__) + ExpectValue Expect(const T& value, const std::source_location loc = std::source_location::current()) { - return ExpectValue(value, file, line); + return ExpectValue(value, loc); } ``` -Note: capturing `__FILE__`/`__LINE__` at the `Expect(...)` call gives the caller's location. This is a plain template returning a matcher; no macro needed. This matches the fluent `Expect(value).ToEqual(4)` usage. +Note: uses `std::source_location::current()` (C++20) as a default argument — it resolves to the **call site** (the user's `Expect(value)` expression), not the function definition. A default-arg `__LINE__`/`__FILE__` is WRONG on MSVC (it expands at the `Expect` definition in the header), so use `std::source_location`. This keeps the fluent macro-free `Expect(value).ToEqual(4)` usage and reports the correct failing line. - [ ] **Step 2: Implement `details::Fail` in the `.cpp`** From 832d97cd3b5757bd13d2bff32034daa26413ceb1 Mon Sep 17 00:00:00 2001 From: muit Date: Fri, 4 Sep 2026 11:12:08 +0200 Subject: [PATCH 11/25] refactor: TestSettings RunTests, Pipe types, TestDescribe/TestContext, PipeTests/PipeTesting renames --- .gitmodules | 3 - CMakeLists.txt | 16 +- Docs/Plans/2026-09-04-pipe-tests-framework.md | 195 +-- .../2026-09-04-pipe-tests-framework-design.md | 33 +- Extern/Bandit | 1 - Extern/CMakeLists.txt | 4 - Include/PipeTests.h | 117 +- Src/Tests/PipeTests.cpp | 201 +-- Tests/CMakeLists.txt | 22 +- Tests/Containers/Arrays.spec.cpp | 1080 ++++++++--------- Tests/Core/Function.spec.cpp | 40 +- Tests/Core/OwnPtr.spec.cpp | 248 ++-- Tests/Core/PageBuffer.spec.cpp | 68 +- Tests/Core/PlatformProcess.spec.cpp | 20 +- Tests/Core/Set.spec.cpp | 82 +- Tests/Core/SpinLock.spec.cpp | 54 +- Tests/Core/String.spec.cpp | 812 +++++++------ Tests/Core/StringView.spec.cpp | 102 +- Tests/Core/Tag.spec.cpp | 88 +- Tests/ECS/Components.spec.cpp | 202 ++- Tests/ECS/ECS.spec.cpp | 38 +- Tests/ECS/Filtering.spec.cpp | 187 ++- Tests/ECS/Hierarchy.spec.cpp | 370 +++--- Tests/ECS/IdRegistry.spec.cpp | 134 +- Tests/ECS/IdScopes.spec.cpp | 92 +- Tests/ECS/Statics.spec.cpp | 68 +- Tests/Files/Paths.spec.cpp | 310 +++-- Tests/Math/Color.spec.cpp | 185 ++- Tests/Math/Math.spec.cpp | 314 ++--- Tests/Math/Vector.spec.cpp | 46 +- Tests/Memory/BestFitArena.spec.cpp | 190 ++- Tests/Memory/BigBestFitArena.spec.cpp | 188 ++- Tests/Memory/Memory.spec.cpp | 158 ++- Tests/Memory/MemoryStats.spec.cpp | 260 ++-- Tests/Memory/MonoLinearArena.spec.cpp | 84 +- Tests/PipeTests/CMakeLists.txt | 2 +- Tests/PipeTests/main.cpp | 2 +- Tests/Reflection/MacroReflection.spec.cpp | 25 +- Tests/Reflection/Object.spec.cpp | 25 +- Tests/Reflection/Traits.spec.cpp | 69 +- Tests/Reflection/TypeId.spec.cpp | 20 +- Tests/Reflection/TypeName.spec.cpp | 74 +- Tests/Serialization/Binary.spec.cpp | 176 ++- Tests/Serialization/Json.spec.cpp | 184 ++- Tests/Serialization/Serialization.spec.cpp | 46 +- Tests/Time.spec.cpp | 24 +- Tests/main.cpp | 86 +- 47 files changed, 3387 insertions(+), 3358 deletions(-) delete mode 160000 Extern/Bandit diff --git a/.gitmodules b/.gitmodules index ee87c157..e69de29b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "Extern/Bandit"] - path = Extern/Bandit - url = https://github.com/banditcpp/bandit.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d93c3ef..fee93e1f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -85,14 +85,14 @@ pipe_target_disable_rtti(Pipe PRIVATE) ################################################################################ # PipeTests (test framework library, not part of the runtime Pipe library) -add_library(PipeTestsLib STATIC Src/Tests/PipeTests.cpp) -add_library(Pipe::TestsLib ALIAS PipeTestsLib) -pipe_target_define_platform(PipeTestsLib) -target_include_directories(PipeTestsLib PUBLIC $) -pipe_target_enable_CPP20(PipeTestsLib) -pipe_target_disable_rtti(PipeTestsLib PRIVATE) -pipe_target_shared_output_directory(PipeTestsLib) -target_link_libraries(PipeTestsLib PUBLIC Pipe) +add_library(PipeTests STATIC Src/Tests/PipeTests.cpp) +add_library(Pipe::Tests ALIAS PipeTests) +pipe_target_define_platform(PipeTests) +target_include_directories(PipeTests PUBLIC $) +pipe_target_enable_CPP20(PipeTests) +pipe_target_disable_rtti(PipeTests PRIVATE) +pipe_target_shared_output_directory(PipeTests) +target_link_libraries(PipeTests PUBLIC Pipe) ################################################################################ diff --git a/Docs/Plans/2026-09-04-pipe-tests-framework.md b/Docs/Plans/2026-09-04-pipe-tests-framework.md index 2d50bb63..1437cec2 100644 --- a/Docs/Plans/2026-09-04-pipe-tests-framework.md +++ b/Docs/Plans/2026-09-04-pipe-tests-framework.md @@ -6,9 +6,15 @@ **Architecture:** A new `PipeTests` module in the Pipe submodule (`Include/PipeTests.h` + `Src/PipeTests.cpp`) built as a **separate CMake library target** (never compiled into the runtime `Pipe` library). Global registration cursor tracks the current test group as functions are called. `Expect(value)` returns a fluent matcher. `p::RunTests(argc, argv)` runs the suite. Existing Bandit tests are NOT migrated and Bandit is NOT removed until the final task. -**Macro-free registration (decision 2026-09-04):** The framework uses NO macros. `Spec`/`Describe`/`It`/`XIt`/`BeforeEach`/`AfterEach` are plain functions in namespace `p`. Because a bare function call is ill-formed at namespace scope (C++ only permits declarations there), specs **cannot self-register at global scope**. Instead, tests are registered from inside a function: each spec file exports a `Register*Tests()` routine, and the test executable's `main()` calls it (before `p::RunTests`) exactly once. The registry uses a function-local `static` (`State()`), so it initializes on first use regardless of translation-unit order. `Spec(fn)` (nameless, go_bandit-style) and `Spec(name, fn)` are both supported; `Spec(fn)` registers into the virtual root group. +**Macro-free registration (decision 2026-09-04):** The framework uses NO macros. `Spec`/`Describe`/`It`/`XIt`/`BeforeEach`/`AfterEach` are plain functions in namespace `p`. `Spec` calls live at file scope and auto-register via static init (like `go_bandit`); no manual registration calls needed — `main()` only calls `p::RunTests`. The registry uses a function-local `static` (`GetTestContext()`), so it initializes on first use regardless of translation-unit order. `Spec(fn)` (nameless, go_bandit-style) and `Spec(name, fn)` are both supported; `Spec(fn)` registers into the virtual root describe. -**Tech Stack:** C++20, CMake 3.26+, no exceptions, no RTTI (`-fno-rtti`). Pipe core types: `StringView`, `String`, `TArray`, `std::function`, `p::Format`, `p::Info/Warning/Error`. +**Design changes (2026-09-04, post-Task 6):** +- `RunTests` split: `RunTests(int argc, char** argv)` parses argv into a `TestSettings` struct (`StringView filter`; `--filter=X`, `--filter X`, or positional) and forwards to `RunTests(const TestSettings&)`, so other systems can run tests programmatically without text args. +- Pipe types throughout: `i32` counters, `TFunction` for immediately-invoked callbacks (`Spec`/`Describe`), `TArray`/`String`/`StringView`. Stored bodies/hooks (`It`/`XIt`/`BeforeEach`/`AfterEach`, hook stacks) stay `std::function` (owning) because `TFunction` is a non-owning view and would dangle. +- Internals renamed: `TestGroup` → `TestDescribe` (`describes` field), `RegistryState` → `TestContext`, `State()` → `GetTestContext()`, `CurrentGroup()` → `CurrentDescribe()`, `currentGroup` → `currentDescribe`. +- Targets renamed: framework library `PipeTestsLib` → `PipeTests` (alias `Pipe::TestsLib` → `Pipe::Tests`); test executable `PipeTests` → `PipeTesting` (alias `Pipe::Testing`, ctest `PipeTesting`). + +**Tech Stack:** C++20, CMake 3.26+, no exceptions, no RTTI (`-fno-rtti`). Pipe core types: `StringView`, `String`, `TArray`, `TFunction`, `i32`, `std::function` (stored callbacks only), `p::Format`, `p::Info/Warning/Error`. ## Global Constraints @@ -44,7 +50,7 @@ Append after the `Pipe` library block in `Extern/Pipe/CMakeLists.txt` (after lin # PipeTests (test framework library, not part of the runtime Pipe library) add_library(PipeTests STATIC Src/PipeTests.cpp) -add_library(Pipe::TestsLib ALIAS PipeTests) +add_library(Pipe::Tests ALIAS PipeTests) pipe_target_define_platform(PipeTests) target_include_directories(PipeTests PUBLIC $) pipe_target_enable_CPP20(PipeTests) @@ -100,14 +106,16 @@ git commit -m "build: add PipeTests library target" **Interfaces:** - Consumes: `Pipe/Core/Log.h` (for error logging), `StringView.h`. - Produces (used by Tasks 3-6): - - `void Spec(StringView name, std::function fn)` - - `void Spec(std::function fn)` (nameless) - - `void Describe(StringView name, std::function fn)` - - `void It(StringView name, std::function fn)` - - `void XIt(StringView name, std::function fn)` - - `void BeforeEach(std::function fn)` - - `void AfterEach(std::function fn)` - - `int RunTests(int argc, char** argv)` + - `void Spec(StringView name, TFunction fn)` + - `void Spec(TFunction fn)` (nameless) + - `void Describe(StringView name, TFunction fn)` + - `void It(StringView name, std::function fn)` (owning: stored until run) + - `void XIt(StringView name, std::function fn)` (owning) + - `void BeforeEach(std::function fn)` (owning) + - `void AfterEach(std::function fn)` (owning) + - `struct TestSettings { StringView filter; }` + - `int RunTests(const TestSettings& settings)` + - `int RunTests(int argc, char** argv)` (parses argv, forwards to the above) - [ ] **Step 1: Declare the registration API** @@ -127,29 +135,35 @@ namespace p { /** * Test framework for Pipe and Rift. - * Imgui-style global context: registration functions act on a current group. - * Spec opens a first-level group; Describe/It/BeforeEach/AfterEach attach to - * the current group as functions are called. Registration runs inside a - * function (e.g. a Register*Tests() routine called from main) - the framework - * uses no macros, so specs must not be registered at namespace scope. + * Imgui-style global context: registration functions act on a current describe. + * Spec opens a first-level describe; Describe/It/BeforeEach/AfterEach attach to + * the current describe as functions are called. Spec calls live at file scope + * and auto-register via static init (like go_bandit); no macros, no manual + * registration calls needed. */ - // Self-registering top-level. Spec(name, fn) also opens a first group named `name`. - void Spec(StringView name, std::function fn); + // Self-registering top-level. Spec(name, fn) also opens a first describe named `name`. + void Spec(StringView name, TFunction fn); // Nameless top-level (like go_bandit); use Describe inside fn. - void Spec(std::function fn); + void Spec(TFunction fn); - // Nested group. Only valid inside a Spec; otherwise logs an error and ignores. - void Describe(StringView name, std::function fn); - // Register a runnable test in the current group. + // Nested describe. Only valid inside a Spec; otherwise logs an error and ignores. + void Describe(StringView name, TFunction fn); + // Register a runnable test in the current describe (body stored until RunTests). void It(StringView name, std::function fn); // Register a disabled test; never run. void XIt(StringView name, std::function fn); - // Setup hook attached to the current group. + // Setup hook attached to the current describe. void BeforeEach(std::function fn); - // Teardown hook attached to the current group. + // Teardown hook attached to the current describe. void AfterEach(std::function fn); + struct TestSettings + { + StringView filter; // empty = run all; else substring match on full test name + }; + + int RunTests(const TestSettings& settings); int RunTests(int argc, char** argv); }; // namespace p ``` @@ -246,7 +260,7 @@ namespace p } // namespace ``` -Note: registration runs at runtime (from a `Register*Tests()` called in `main`), so the registry uses a function-local `static` (`State()`). This avoids static-init-order hazards if registration is ever invoked before `main`, and keeps all mutable suite state in one lazily-created object. `State()`/`CurrentGroup()` are `inline` file-local accessors used by every registration function. +Note: the registry uses a function-local `static` (`GetTestContext()`). This avoids static-init-order hazards when `Spec` runs at file scope during static init, and keeps all mutable suite state in one lazily-created object. `GetTestContext()`/`CurrentDescribe()` are file-local accessors used by every registration function. (Task 3's code sketches below use the pre-rename identifiers `RegistryState`/`State()`/`TestGroup`/`currentGroup`; read them as `TestContext`/`GetTestContext()`/`TestDescribe`/`currentDescribe`, with `groups` → `describes`.) Note: `String` and `TArray` require `PipeStrings.h`/`PipeContainers.h` — included via `Pipe.h`? `Pipe.h` only includes `StringView.h` + `Export.h`. Include `PipeStrings.h` explicitly (done above). Ensure `PipeTests.cpp` links against Pipe (done in Task 1 via `target_link_libraries(PipeTests PUBLIC Pipe)`). @@ -545,7 +559,7 @@ git commit -m "feat: add PipeTests registry and runner" - Create: `Extern/Pipe/Tests/PipeTests/main.cpp` **Interfaces:** -- Consumes: `PipeTests.h`, `Pipe.h`, `p::Expect` (Task 5). To avoid depending on Task 5, implement this task to compile against the header and **defer the actual `Expect` matcher to Task 5**, adding assertions there in step 3. The framework uses no macros (registration runs from a `Register*Tests()` function); assertions arrive with `Expect` in Task 5. +- Consumes: `PipeTests.h`, `Pipe.h`, `p::Expect` (Task 5). To avoid depending on Task 5, implement this task to compile against the header and **defer the actual `Expect` matcher to Task 5**, adding assertions there in step 3. The framework uses no macros (`Spec` at file scope auto-registers); assertions arrive with `Expect` in Task 5. - Produces: a second test executable `PipeTestsSelf` registered in CTest, proving the framework runs alongside the untouched Bandit suite. @@ -556,7 +570,7 @@ git commit -m "feat: add PipeTests registry and runner" ```cpp // Copyright 2015-2026 Piperift. All Rights Reserved. -// NOTE: PipeNewDelete is deliberately not included here. PipeTestsLib provides the +// NOTE: PipeNewDelete is deliberately not included here. PipeTests provides the // replacement operator new/delete (P_OVERRIDE_NEWDELETE) in its own translation unit; // including it here too would cause duplicate-definition linker errors. @@ -564,20 +578,16 @@ git commit -m "feat: add PipeTests registry and runner" #include -void RegisterPipeTests(); - - int main(int argc, char* argv[]) { p::Initialize(); - RegisterPipeTests(); int result = p::RunTests(argc, argv); p::Shutdown(); return result; } ``` -`Extern/Pipe/Tests/PipeTests/PipeTests.spec.cpp` (registration must run from a function — the framework has no macros): +`Extern/Pipe/Tests/PipeTests/PipeTests.spec.cpp` (`Spec` at file scope auto-registers; no macros): ```cpp // Copyright 2015-2026 Piperift. All Rights Reserved. @@ -593,20 +603,17 @@ static int afterEachCount = 0; static int topTestResult = 0; -void RegisterPipeTests() +Spec("PipeTests", []() { - Spec("PipeTests", []() - { - BeforeEach([]() { ++beforeEachCount; }); - AfterEach([]() { ++afterEachCount; }); + BeforeEach([]() { ++beforeEachCount; }); + AfterEach([]() { ++afterEachCount; }); - Describe("Basics", []() - { - It("Registers and runs", []() { topTestResult = 42; }); - XIt("Is skipped", []() { topTestResult = -1; }); - }); + Describe("Basics", []() + { + It("Registers and runs", []() { topTestResult = 42; }); + XIt("Is skipped", []() { topTestResult = -1; }); }); -} +}); // NOTE: assertions are added in Task 5 once Expect() exists. @@ -626,7 +633,7 @@ pipe_target_define_platform(PipeTestsSelf) pipe_target_enable_CPP20(PipeTestsSelf) pipe_target_disable_rtti(PipeTestsSelf PRIVATE) pipe_target_shared_output_directory(PipeTestsSelf) -target_link_libraries(PipeTestsSelf PUBLIC PipeTestsLib Pipe) +target_link_libraries(PipeTestsSelf PUBLIC PipeTests Pipe) add_test(NAME PipeTestsSelf COMMAND $) ``` @@ -926,37 +933,34 @@ Update `Extern/Pipe/Tests/PipeTests/PipeTests.spec.cpp` to use `Expect`: using namespace p; -void RegisterPipeTests() -{ - Spec("PipeTests", []() { - Describe("Expect", []() { - It("ToEqual / ToNotEqual", []() { - int value = 4; - Expect(value).ToEqual(4); - Expect(value).ToNotEqual(5); - }); - It("Relational", []() { - int value = 4; - Expect(value).ToBeLess(5); - Expect(value).ToBeLessOrEqual(4); - Expect(value).ToBeGreater(3); - Expect(value).ToBeGreaterOrEqual(4); - }); - It("Booleans", []() { - bool flag = true; - Expect(flag).ToBeTrue(); - Expect(!flag).ToBeFalse(); - }); - It("Strings", []() { - Expect("acidic").ToContain("acid"); - Expect(String{"hello"}).ToNotContain("world"); - }); - It("Equals int", []() { - Expect(4).ToEqual(4); - }); +Spec("PipeTests", []() { + Describe("Expect", []() { + It("ToEqual / ToNotEqual", []() { + int value = 4; + Expect(value).ToEqual(4); + Expect(value).ToNotEqual(5); + }); + It("Relational", []() { + int value = 4; + Expect(value).ToBeLess(5); + Expect(value).ToBeLessOrEqual(4); + Expect(value).ToBeGreater(3); + Expect(value).ToBeGreaterOrEqual(4); + }); + It("Booleans", []() { + bool flag = true; + Expect(flag).ToBeTrue(); + Expect(!flag).ToBeFalse(); + }); + It("Strings", []() { + Expect("acidic").ToContain("acid"); + Expect(String{"hello"}).ToNotContain("world"); + }); + It("Equals int", []() { + Expect(4).ToEqual(4); }); }); -} +}); ``` (note: `Expect(value).ToBeTrue()` requires `value` be usable in `if (!value)`; bool works.) @@ -1039,28 +1043,25 @@ New: using namespace p; -void RegisterStringViewTests() +Spec("Strings", []() { - Spec("Strings", []() + Describe("StringView", []() { - Describe("StringView", []() + It("Can assign from literal", []() { - It("Can assign from literal", []() - { - StringView v{"Kiwi"}; - Expect(v).ToEqual("Kiwi"); - Expect(v.size()).ToEqual(4); - }); - // ... other tests converted similarly ... + StringView v{"Kiwi"}; + Expect(v).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4); }); + // ... other tests converted similarly ... }); -} +}); ``` Transform rules (from the spec): - `#include ` → `#include ` - `using namespace snowhouse; using namespace bandit;` → remove both; keep `using namespace p;` -- **Top-level:** `go_bandit([](){ describe("G", [](){ ...` → wrap in `void RegisterTests()` and inside use `Spec("G", [](){ ...` (drop the outer `go_bandit` extra nesting and one `describe` level; the top `Spec("Strings", ...)` replaces go_bandit+first describe). Because the framework has no macros and no global-scope self-registration, each migrated `spec.cpp` file exports one `RegisterXxxTests()` routine that `main.cpp` calls. +- **Top-level:** `go_bandit([](){ describe("G", [](){ ...` → `Spec("G", [](){ ...` at file scope (drop the outer `go_bandit` extra nesting and one `describe` level; the top `Spec("Strings", ...)` replaces go_bandit+first describe and auto-registers via static init — no wrapper function, no `main.cpp` changes). - `describe(` → `Describe(` - `it(` → `It(` (drop the `[&]` → `[]`; lambdas no longer need `&` capture since framework state is global) - `xit(` → `XIt(` @@ -1075,7 +1076,7 @@ Transform rules (from the spec): - `AssertThat(x, Is().False())` → `Expect(x).ToBeFalse()` - `AssertThat(v.size(), Equals(4u))` → `Expect(v.size()).ToEqual(4u)` -Important: the top-level transform. Bandit files use `go_bandit([](){ describe("Strings", [](){...}) });`. Our `Spec("Strings", [](){...})` handles the `describe` level directly, so replace the pair with a single `Spec("Strings", fn)` and inside use `Describe`/`It`. For files that use `go_bandit` with a single top describe, keep that one as `Spec` and drop the now-redundant `Describe` wrapper if present. Follow the reference conversion exactly. **Each converted file then wraps its top-level `Spec(...)` in a `RegisterXxxTests()` function** (unique name per file, e.g. `RegisterStringViewTests`), and `main.cpp` declares and calls each one. +Important: the top-level transform. Bandit files use `go_bandit([](){ describe("Strings", [](){...}) });`. Our `Spec("Strings", [](){...})` handles the `describe` level directly, so replace the pair with a single file-scope `Spec("Strings", fn)` and inside use `Describe`/`It`. For files that use `go_bandit` with a single top describe, keep that one as `Spec` and drop the now-redundant `Describe` wrapper if present. Follow the reference conversion exactly. Each converted file holds its top-level `Spec(...)` at file scope (auto-registers; no wrapper, no `main.cpp` changes). - [ ] **Step 2: Build + run the migrated file only (green)** @@ -1094,19 +1095,19 @@ Convert every `Extern/Pipe/Tests/**/*.spec.cpp` using the transform rules above. - [ ] **Step 4: Switch PipeTests exe to the new framework** `Extern/Pipe/Tests/CMakeLists.txt`: -- Change `target_link_libraries(PipeTests PUBLIC Pipe Bandit)` → `target_link_libraries(PipeTests PUBLIC Pipe PipeTestsLib)` (note: the framework library is `PipeTestsLib`, alias `Pipe::TestsLib`; `PipeTests` is the test-executable target name) +- Rename the suite executable `PipeTests` → `PipeTesting` (alias `Pipe::Testing`); link the framework library: `target_link_libraries(PipeTesting PUBLIC Pipe PipeTests)` (framework library is `PipeTests`, alias `Pipe::Tests`) - Remove `--reporter=spec` from `add_test(...)`: - `add_test(NAME PipeTests COMMAND $)` -- Remove the `list(FILTER ...)` exclusion added in Task 4 (restore the plain glob) so all spec files (including migrated ones) build into `PipeTests`. + `add_test(NAME PipeTesting COMMAND $)` +- Remove the `list(FILTER ...)` exclusion added in Task 4 (restore the plain glob) so all spec files (including migrated ones) build into `PipeTesting`. -`Extern/Pipe/Tests/main.cpp`: replace `int result = bandit::run(argc, argv);` with calls to each migrated spec file's `RegisterXxxTests()` followed by `int result = p::RunTests(argc, argv);`, and remove `#include `. Because the framework has no global-scope self-registration, `main.cpp` must **declare and call every `Register*Tests()`** exported by the migrated spec files (e.g. `void RegisterStringViewTests();` + `RegisterStringViewTests();` before `RunTests`). Keep the `p::Initialize`/`p::Shutdown` calls; `PipeNewDelete.h` no longer needs to be included here if `PipeTestsLib` provides the override. +`Extern/Pipe/Tests/main.cpp`: replace `int result = bandit::run(argc, argv);` with `int result = p::RunTests(argc, argv);`, and remove `#include `. Specs auto-register at file scope, so `main.cpp` needs no per-file calls. Keep the `p::Initialize`/`p::Shutdown` calls; `PipeNewDelete.h` no longer needs to be included here since `PipeTests` provides the override. -`Extern/Pipe/Tests/PipeTests/CMakeLists.txt`: keep the `PipeTestsSelf` target for framework self-checks, OR fold the self-test spec files into the main `PipeTests` glob (remove the separate subdirectory). Keep `PipeTestsSelf` as-is for now (harmless), unless the main glob re-includes its files. Since the main glob is `GLOB_RECURSE *.cpp` from `Tests/`, it WILL include `Tests/PipeTests/*.cpp` again → duplicate `main()`. So when restoring the plain glob in step 4, re-apply a filter to EXCLUDE `Tests/PipeTests/` from the main `PipeTests` exe (keep `PipeTestsSelf` as a separate target): +`Extern/Pipe/Tests/PipeTests/CMakeLists.txt`: keep the `PipeTestsSelf` target for framework self-checks, OR fold the self-test spec files into the main `PipeTesting` glob (remove the separate subdirectory). Keep `PipeTestsSelf` as-is for now (harmless), unless the main glob re-includes its files. Since the main glob is `GLOB_RECURSE *.cpp` from `Tests/`, it WILL include `Tests/PipeTests/*.cpp` again → duplicate `main()`. So when restoring the plain glob in step 4, re-apply a filter to EXCLUDE `Tests/PipeTests/` from the main `PipeTesting` exe (keep `PipeTestsSelf` as a separate target): ```cmake file(GLOB_RECURSE TESTS_SOURCE_FILES CONFIGURE_DEPENDS *.cpp *.h *.hpp) list(FILTER TESTS_SOURCE_FILES EXCLUDE REGEX ".*/PipeTests/.*") -add_executable(PipeTests ${TESTS_SOURCE_FILES}) +add_executable(PipeTesting ${TESTS_SOURCE_FILES}) ``` Keep `add_subdirectory(PipeTests)` for `PipeTestsSelf`. @@ -1129,8 +1130,8 @@ Expected: `PipeTests` runs all migrated tests with names/locations under the new - [ ] **Step 7: Migrate Rift tests + CMake** In `D:\Projects\Piperift\rift`: -- `Tests/CMakeLists.txt`: `target_link_libraries(RiftTests PUBLIC RiftASTLib Bandit)` → `target_link_libraries(RiftTests PUBLIC RiftASTLib Pipe::TestsLib)` (the framework library is `PipeTestsLib`, alias `Pipe::TestsLib`; it is defined unconditionally in `Extern/Pipe/CMakeLists.txt` per Task 1). Rift's `Tests/main.cpp` must declare and call the migrated spec files' `RegisterXxxTests()` routines before `p::RunTests`. -- Convert Rift `Tests/Project.spec.cpp`, `Tests/AST/Statements.spec.cpp`, `Tests/AST/Expressions.spec.cpp`, `Tests/AST/Namespaces.spec.cpp` per the transform rules (uses `before_each`/`after_each` → `BeforeEach`/`AfterEach`, `AssertThat(result, Equals(true))` → `Expect(result).ToBeTrue()`, etc.). Wrap each in a `Register*Tests()` function; remove `#include ` and `using namespace snowhouse/bandit`. +- `Tests/CMakeLists.txt`: `target_link_libraries(RiftTests PUBLIC RiftASTLib Bandit)` → `target_link_libraries(RiftTests PUBLIC RiftASTLib Pipe::Tests)` (the framework library is `PipeTests`, alias `Pipe::Tests`; it is defined unconditionally in `Extern/Pipe/CMakeLists.txt` per Task 1). Rift's `Tests/main.cpp` only swaps `bandit::run` for `p::RunTests` (specs auto-register). +- Convert Rift `Tests/Project.spec.cpp`, `Tests/AST/Statements.spec.cpp`, `Tests/AST/Expressions.spec.cpp`, `Tests/AST/Namespaces.spec.cpp` per the transform rules (uses `before_each`/`after_each` → `BeforeEach`/`AfterEach`, `AssertThat(result, Equals(true))` → `Expect(result).ToBeTrue()`, etc.). Each file holds its `Spec(...)` at file scope; remove `#include ` and `using namespace snowhouse/bandit`. - [ ] **Step 8: Full project build + tests + format** @@ -1171,9 +1172,9 @@ Note: the Rift commit must record the new Pipe submodule hash (`git add Extern/P - ✅ No runtime burden on shipped `Pipe` lib (separate target, `Src/Tests/` excluded — Task 1) - ✅ No `ToThrow` (no exceptions matchers) — confirmed - ✅ `Describe` misuse = log + ignore (Task 3) -- ✅ `RunTests(int, char**)` (Tasks 2, 3) -- ✅ imgui-style global context, macro-free functions; registration runs from `Register*Tests()` called in `main` (Task 2, 3) +- ✅ `RunTests(TestSettings)` + `RunTests(int, char**)` argv→settings forwarder (Tasks 2, 3) +- ✅ imgui-style global context, macro-free functions; `Spec` at file scope auto-registers (Task 2, 3) **Placeholder scan:** All steps carry concrete code. The `Expect` matcher uses `Format("{}", value)` which needs a `StringView` formatter — flagged with an explicit fallback overload if missing. Task adds a note to verify `TArray` member names and add `String` no implicit `StringView` conversion fallback. No TODO/TBD beyond explicit in-task verification notes. -**Type consistency:** `String`, `StringView`, `sizet`, `Number`, `TestString`, `ExpectValue`, `details::Fail(file, line, message)`, `RunTests(int,char**)` used consistently across tasks. \ No newline at end of file +**Type consistency:** `String`, `StringView`, `sizet`, `i32`, `TFunction` (immediate callbacks) / `std::function` (stored bodies/hooks), `Number`, `TestString`, `TestSettings`, `TestContext`/`TestDescribe`, `ExpectValue`, `details::Fail(loc, message)`, `RunTests(settings)` + `RunTests(int,char**)` used consistently across tasks. \ No newline at end of file diff --git a/Docs/Specs/2026-09-04-pipe-tests-framework-design.md b/Docs/Specs/2026-09-04-pipe-tests-framework-design.md index 3ea4f836..eb1678a5 100644 --- a/Docs/Specs/2026-09-04-pipe-tests-framework-design.md +++ b/Docs/Specs/2026-09-04-pipe-tests-framework-design.md @@ -101,29 +101,28 @@ Fluent matcher methods, naming in Pipe CamelCase: ### New files (in the Pipe submodule) - `Extern/Pipe/Include/PipeTests.h` — public API (global functions, `Expect` matcher, formatter hook). Mostly templates; **no macros**. -- `Extern/Pipe/Src/Tests/PipeTests.cpp` — function-local static registry state, registration functions (`Spec`/`Describe`/`It`/`XIt`/`BeforeEach`/`AfterEach`), `p::RunTests(int, char**)`. +- `Extern/Pipe/Src/Tests/PipeTests.cpp` — function-local static `TestContext`, registration functions (`Spec`/`Describe`/`It`/`XIt`/`BeforeEach`/`AfterEach`), `p::RunTests(settings)` + `p::RunTests(int, char**)` argv forwarder. ### Build — separate target, not into the runtime Pipe lib `Extern/Pipe/CMakeLists.txt`: -- Define `add_library(PipeTestsLib ...)` (alias `Pipe::TestsLib`) **unconditionally** (alongside `Pipe`, **before** the `PIPE_BUILD_TESTS` gate) so Rift can consume it via the submodule. (Name is `PipeTestsLib` because `PipeTests` is already the Bandit-based test-executable target.) -- **Exclude `Src/Tests/PipeTests.cpp`** from Pipe's `file(GLOB_RECURSE ... Src/*.cpp)` (currently line 65) so the test framework does **not** ship in the runtime `Pipe` library. Add it only to the `PipeTestsLib` target. -- Give `PipeTestsLib` the standard Pipe setup (`pipe_target_define_platform`, `pipe_target_enable_CPP20`, `pipe_target_disable_rtti`, `pipe_target_shared_output_directory`) and link `Pipe`; expose `Include/`. +- Define `add_library(PipeTests ...)` (alias `Pipe::Tests`) **unconditionally** (alongside `Pipe`, **before** the `PIPE_BUILD_TESTS` gate) so Rift can consume it via the submodule. (The suite executable is `PipeTesting`, alias `Pipe::Testing`, so the `PipeTests` name is free for the framework library.) +- **Exclude `Src/Tests/PipeTests.cpp`** from Pipe's `file(GLOB_RECURSE ... Src/*.cpp)` (currently line 65) so the test framework does **not** ship in the runtime `Pipe` library. Add it only to the `PipeTests` target. +- Give `PipeTests` the standard Pipe setup (`pipe_target_define_platform`, `pipe_target_enable_CPP20`, `pipe_target_disable_rtti`, `pipe_target_shared_output_directory`) and link `Pipe`; expose `Include/`. ### Runner -`p::RunTests(int argc, char* argv[]) -> int`: +`p::RunTests(const TestSettings&) -> int` (plus an `argc`/`argv` overload that parses `--filter=X` / `--filter X` / positional into settings and forwards, so other systems can run tests without text args): -- Iterates the registered test tree. +- Iterates the registered test tree, running only tests whose full name contains `settings.filter` (empty filter runs all). - Runs each test, skipping `XIt`. - Reports pass/fail/skip counts plus the names/locations of failures. - Returns a process exit code (`0` when all pass). -- Minimal `argc`/`argv` handling now; signature kept for future `--filter` support. ### Global registration cursor -A current-group stack in `PipeTests.cpp`. `Describe` pushes its group, runs `fn` (children register against it via the global functions), then pops. `It`/`XIt`/`BeforeEach`/`AfterEach` attach to the current group. +A current-describe cursor in `PipeTests.cpp` (`TestContext::currentDescribe`, accessed via `GetTestContext()`/`CurrentDescribe()`). `Describe` pushes its describe, runs `fn` (children register against it via the global functions), then pops. `It`/`XIt`/`BeforeEach`/`AfterEach` attach to the current describe. Pipe types throughout: `i32` counters, `TFunction` for immediately-invoked `Spec`/`Describe` callbacks, owning `std::function` for stored test bodies/hooks (`TFunction` is non-owning and would dangle). ## Migration Phasing @@ -131,13 +130,13 @@ A current-group stack in `PipeTests.cpp`. `Describe` pushes its group, runs `fn` 1. **Add `PipeTests` module** — header + source; `add_library(PipeTests)`; source-glob exclusion. Build succeeds. 2. **Self-test the framework with small new tests** (no migration of existing tests): - - Create small **new** framework tests in `Extern/Pipe/Tests/PipeTests/` (e.g. `PipeTests.spec.cpp`) written with the new API to validate: `Spec`/`Describe`/`It`/`XIt`/`BeforeEach`/`AfterEach`, all `Expect` matchers, failure reporting, skip counting, `RunTests` exit code, and `Describe`-outside-`Spec` behavior. The spec file exports a `Register*Tests()` routine. - - Wire a **separate small runner** (its own `main.cpp` calling the `Register*Tests()` then `p::RunTests`) for this smoke target, running **alongside** the existing bandit `PipeTests` executable. + - Create small **new** framework tests in `Extern/Pipe/Tests/PipeTests/` (e.g. `PipeTests.spec.cpp`) written with the new API to validate: `Spec`/`Describe`/`It`/`XIt`/`BeforeEach`/`AfterEach`, all `Expect` matchers, failure reporting, skip counting, `RunTests` exit code, and `Describe`-outside-`Spec` behavior. The spec file holds `Spec(...)` at file scope (auto-registers). + - Wire a **separate small runner** (its own `main.cpp` calling `p::RunTests`) for this smoke target, running **alongside** the existing bandit `PipeTesting` executable. - Verify via `ctest` that **both** the new self-tests and the untouched bandit suite pass. Iterate until the framework is proven. 3. **Final flip (LAST, only when the system is done):** - - Migrate existing `*spec.cpp` files file-by-file (transform map below). Each migrated file exports a `RegisterXxxTests()` routine instead of registering at global scope. - - `Extern/Pipe/Tests/CMakeLists.txt`: link `Pipe` + `PipeTestsLib`, drop `Bandit`; `main.cpp` calls each `Register*Tests()` then `p::RunTests(argc, argv)`; drop `--reporter=spec`. - - Migrate Rift `Tests/*.spec.cpp` + `Tests/CMakeLists.txt`: replace `Bandit` with `Pipe::TestsLib` (the alias), and call the migrated `Register*Tests()` from Rift's `Tests/main.cpp`. + - Migrate existing `*spec.cpp` files file-by-file (transform map below). Each migrated file holds `Spec(...)` at file scope (auto-registers; no wrapper, no `main` changes). + - `Extern/Pipe/Tests/CMakeLists.txt`: rename suite exe to `PipeTesting`, link `Pipe` + `PipeTests`, drop `Bandit`; `main.cpp` calls `p::RunTests(argc, argv)`; drop `--reporter=spec`. + - Migrate Rift `Tests/*.spec.cpp` + `Tests/CMakeLists.txt`: replace `Bandit` with `Pipe::Tests` (the alias); Rift's `Tests/main.cpp` only swaps `bandit::run` for `p::RunTests`. - Remove `Bandit`: delete the `Bandit` INTERFACE target from `Extern/Pipe/Extern/CMakeLists.txt`, remove the vendored `Extern/Pipe/Extern/Bandit/` directory, strip remaining bandit includes. - Final full `ctest` + `ClangFormat`/`ClangTidy` pass. @@ -146,7 +145,7 @@ A current-group stack in `PipeTests.cpp`. `Describe` pushes its group, runs `fn` | Before (bandit) | After (PipeTests) | |-----------------|-------------------| | `#include ` + `using namespace snowhouse; using namespace bandit;` | `#include ` + `using namespace p;` | -| `go_bandit([](){ describe("G", ...) })` (global scope) | Wrap in `void RegisterXxxTests() { Spec("G", [](){ ... }) }`; call from `main` | +| `go_bandit([](){ describe("G", ...) })` (global scope) | File-scope `Spec("G", [](){ ... })`; auto-registers, no `main` changes | | `describe(...)` | `Describe(...)` | | `it(...)` | `It(...)` | | `xit(...)` | `XIt(...)` | @@ -165,7 +164,7 @@ Note: existing test files that `using namespace snowhouse; using namespace bandi - `Bandit` INTERFACE target is defined in `Extern/Pipe/Extern/CMakeLists.txt`, added **unconditionally** (Pipe `CMakeLists.txt` line 47 `add_subdirectory(Extern)` runs before the `PIPE_BUILD_TESTS` gate). - Pipe supplies its own tests executable gated behind `PIPE_BUILD_TESTS` (default `PIPE_IS_PROJECT`, i.e. ON when Pipe is the top project). - Rift builds its **own** `RiftTests` executable in `Tests/CMakeLists.txt`, linking `RiftASTLib` + `Bandit` (imported through Pipe's `Extern`). -- Therefore the `PipeTestsLib` target (alias `Pipe::TestsLib`) must be defined **unconditionally** in Pipe's `CMakeLists.txt` so Rift can link it. +- Therefore the `PipeTests` target (alias `Pipe::Tests`) must be defined **unconditionally** in Pipe's `CMakeLists.txt` so Rift can link it. ## Constraints & style @@ -176,8 +175,8 @@ Note: existing test files that `using namespace snowhouse; using namespace bandi ## Open Questions -- Confirmed during design: no `ToThrow` (see Non-Goals); `Describe` misuse logs + ignores; `RunTests(int, char**)` signature; API names `Spec/Describe/It/XIt/BeforeEach/AfterEach/Expect` in Pipe CamelCase. +- Confirmed during design: no `ToThrow` (see Non-Goals); `Describe` misuse logs + ignores; `RunTests(settings)` + `RunTests(int, char**)` signatures; API names `Spec/Describe/It/XIt/BeforeEach/AfterEach/Expect` in Pipe CamelCase. ## Out of Scope / Follow-ups -- CLI `--filter` / reporter selection (deferred; `RunTests` keeps `argc`/`argv` for future use). +- Reporter selection (deferred). diff --git a/Extern/Bandit b/Extern/Bandit deleted file mode 160000 index a16c7427..00000000 --- a/Extern/Bandit +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a16c742727f188d28793b57784eac0ae6df5a1fc diff --git a/Extern/CMakeLists.txt b/Extern/CMakeLists.txt index e35826ea..b2423437 100644 --- a/Extern/CMakeLists.txt +++ b/Extern/CMakeLists.txt @@ -1,7 +1,3 @@ set(BUILD_SHARED_LIBS OFF) set(EFSW_INSTALL OFF) - - -add_library(Bandit INTERFACE) -target_include_directories(Bandit INTERFACE Bandit) diff --git a/Include/PipeTests.h b/Include/PipeTests.h index c47d25ff..3c9d12b2 100644 --- a/Include/PipeTests.h +++ b/Include/PipeTests.h @@ -2,40 +2,66 @@ #pragma once +#include "Pipe/Core/Function.h" #include "Pipe/Core/StringView.h" #include "PipeStrings.h" #include +#include +#include #include +#include +#include +#include namespace p { /** * Test framework for Pipe and Rift. - * Imgui-style global context: registration functions act on a current group. - * Spec opens a first-level group; Describe/It/BeforeEach/AfterEach attach to - * the current group as functions are called. Registration runs inside a - * function (e.g. a Register*Tests() routine called from main) — the framework - * uses no macros, so specs must not be registered at namespace scope. + * Imgui-style global context: registration functions act on a current describe. + * Spec opens a first-level describe; Describe/It/BeforeEach/AfterEach attach to + * the current describe as functions are called. Spec calls live at file scope + * and auto-register via static init (like go_bandit); no macros, no manual + * registration calls needed. */ - // Self-registering top-level. Spec(name, fn) opens a first group named `name`. - void Spec(StringView name, std::function fn); + namespace details + { + // Detects whether a type can be rendered via std::format. + template + concept FormattableType = requires(const T& value) + { + std::formatter, Char>{}; + }; + } // namespace details + + // Self-registering top-level. Spec(name, fn) opens a first describe named `name`. + // fn runs immediately during registration, so TFunction (non-owning) is safe. + void Spec(StringView name, TFunction fn); // Nameless top-level (like go_bandit); use Describe inside fn. - void Spec(std::function fn); + void Spec(TFunction fn); - // Nested group. Only valid inside a Spec; otherwise logs an error and ignores. - void Describe(StringView name, std::function fn); - // Register a runnable test in the current group. + // Nested describe. Only valid inside a Spec; otherwise logs an error and ignores. + void Describe(StringView name, TFunction fn); + // Register a runnable test in the current describe. + // Bodies are stored until RunTests, so std::function (owning) is required here. void It(StringView name, std::function fn); // Register a disabled test; never run. void XIt(StringView name, std::function fn); - // Setup hook attached to the current group. + // Setup hook attached to the current describe. void BeforeEach(std::function fn); - // Teardown hook attached to the current group. + // Teardown hook attached to the current describe. void AfterEach(std::function fn); + // Settings for a test run. Empty filter runs everything; otherwise only tests + // whose full name contains the filter substring run. + struct TestSettings + { + StringView filter; + }; + + int RunTests(const TestSettings& settings); int RunTests(int argc, char** argv); @@ -47,7 +73,17 @@ namespace p template inline String TestString(const T& value) { - return Format("{}", value); + if constexpr (details::FormattableType) + { + return Format("{}", value); + } + else + { + // Non-formattable type (structs, containers, byte views...). + // Render a generic placeholder instead of failing to compile. + (void)value; + return Format("", static_cast(std::addressof(value))); + } } template<> @@ -77,10 +113,53 @@ namespace p return value ? String{value} : String{"(null)"}; } + inline String TestString(const std::string& value) + { + return String{StringView{value}}; + } + + // Any other pointer is shown as its address so failure messages stay formattable. + template + inline String TestString(const T* value) + { + return Format("{}", static_cast(value)); + } + namespace details { // Format failure message from source location + description. void Fail(const std::source_location& loc, StringView message); + + // True when both Actual and Expected can be viewed as a StringView (string-ish). + template + struct IsStringBoth : std::false_type + {}; + + template + struct IsStringBoth()}), + decltype(StringView{std::declval()})>> : std::true_type + {}; + + // Compares two possibly-different types: string-ish values compare by view, + // everything else uses operator==. + template::value> + struct ValuesEqual + { + static bool Eval(const A& a, const E& e) + { + return StringView{a} == StringView{e}; + } + }; + + template + struct ValuesEqual + { + static bool Eval(const A& a, const E& e) + { + return a == e; + } + }; } // namespace details @@ -94,18 +173,20 @@ namespace p , loc(loc) {} - void ToEqual(const Actual& expected) const + template + void ToEqual(const Expected& expected) const { - if (!(value == expected)) + if (!details::ValuesEqual::Eval(value, expected)) { details::Fail(loc, Format( "Expected {} to equal {}", TestString(value), TestString(expected))); } } - void ToNotEqual(const Actual& expected) const + template + void ToNotEqual(const Expected& expected) const { - if (!(value != expected)) + if (details::ValuesEqual::Eval(value, expected)) { details::Fail(loc, Format( "Expected {} to not equal {}", TestString(value), TestString(expected))); diff --git a/Src/Tests/PipeTests.cpp b/Src/Tests/PipeTests.cpp index 0e1894db..f830055c 100644 --- a/Src/Tests/PipeTests.cpp +++ b/Src/Tests/PipeTests.cpp @@ -20,44 +20,46 @@ namespace p struct TestCase { String name; + // Owning: bodies are stored until RunTests runs them. std::function body; bool skip = false; }; - struct TestGroup + struct TestDescribe { String name; - TArray groups; // nested describes - TArray tests; // its + TArray describes; // nested describes + TArray tests; // its tests + // Owning: hooks are stored until RunTests runs them. std::function beforeEach; std::function afterEach; }; - // Entire registered suite (treat as a single virtual root group). - struct RegistryState + // Entire registered suite (treat as a single virtual root describe). + struct TestContext { - TestGroup root{"", {}, {}, {}, {}}; - - // Pointer into `root.groups` for the currently-adding group. - TestGroup* currentGroup = nullptr; - int failedTests = 0; - int runTests = 0; - int skippedTests = 0; - int currentTestFailureCount = 0; + TestDescribe root{"", {}, {}, {}, {}}; + + // Pointer into `root.describes` for the currently-adding describe. + TestDescribe* currentDescribe = nullptr; + i32 failedTests = 0; + i32 runTests = 0; + i32 skippedTests = 0; + i32 currentTestFailureCount = 0; }; // Function-local static: initialized on first use regardless of the - // static-init order of other translation units, so a `Spec` registrar - // defined in a separate TU can safely register during static init. - RegistryState& State() + // static-init order of other translation units, so a `Spec` call + // at file scope in a separate TU can safely register during static init. + TestContext& GetTestContext() { - static RegistryState state; - return state; + static TestContext context; + return context; } - TestGroup*& CurrentGroup() + TestDescribe*& CurrentDescribe() { - return State().currentGroup; + return GetTestContext().currentDescribe; } } // namespace @@ -67,54 +69,54 @@ namespace p void Fail(const std::source_location& loc, StringView message) { Error("PipeTests: {}:{}: {}", loc.file_name(), loc.line(), message); - ++State().currentTestFailureCount; + ++GetTestContext().currentTestFailureCount; } } // namespace details - void Spec(StringView name, std::function fn) + void Spec(StringView name, TFunction fn) { - RegistryState& state = State(); - TestGroup group; - group.name = String{name}; - group.beforeEach = nullptr; - group.afterEach = nullptr; - state.root.groups.Add(Move(group)); - TestGroup* groupPtr = &state.root.groups.Last(); - state.currentGroup = groupPtr; + TestContext& context = GetTestContext(); + TestDescribe describe; + describe.name = String{name}; + describe.beforeEach = nullptr; + describe.afterEach = nullptr; + context.root.describes.Add(Move(describe)); + TestDescribe* describePtr = &context.root.describes.Last(); + context.currentDescribe = describePtr; fn(); - state.currentGroup = nullptr; + context.currentDescribe = nullptr; } - void Spec(std::function fn) + void Spec(TFunction fn) { - RegistryState& state = State(); - state.currentGroup = &state.root; + TestContext& context = GetTestContext(); + context.currentDescribe = &context.root; fn(); - state.currentGroup = nullptr; + context.currentDescribe = nullptr; } - void Describe(StringView name, std::function fn) + void Describe(StringView name, TFunction fn) { - TestGroup*& current = CurrentGroup(); + TestDescribe*& current = CurrentDescribe(); if (!current) { Error("PipeTests: Describe('{}') called outside a Spec. Ignoring.", name); return; } - TestGroup group; - group.name = String{name}; - current->groups.Add(Move(group)); - TestGroup* prevGroup = current; - current = ¤t->groups.Last(); + TestDescribe describe; + describe.name = String{name}; + current->describes.Add(Move(describe)); + TestDescribe* prevDescribe = current; + current = ¤t->describes.Last(); fn(); - current = prevGroup; + current = prevDescribe; } void It(StringView name, std::function fn) { - TestGroup*& current = CurrentGroup(); + TestDescribe*& current = CurrentDescribe(); if (!current) { Error("PipeTests: It('{}') called outside a Spec. Ignoring.", name); @@ -129,7 +131,7 @@ namespace p void XIt(StringView name, std::function fn) { - TestGroup*& current = CurrentGroup(); + TestDescribe*& current = CurrentDescribe(); if (!current) { Error("PipeTests: XIt('{}') called outside a Spec. Ignoring.", name); @@ -144,7 +146,7 @@ namespace p void BeforeEach(std::function fn) { - TestGroup*& current = CurrentGroup(); + TestDescribe*& current = CurrentDescribe(); if (!current) { Error("PipeTests: BeforeEach called outside a Spec. Ignoring."); @@ -155,7 +157,7 @@ namespace p void AfterEach(std::function fn) { - TestGroup*& current = CurrentGroup(); + TestDescribe*& current = CurrentDescribe(); if (!current) { Error("PipeTests: AfterEach called outside a Spec. Ignoring."); @@ -167,52 +169,61 @@ namespace p namespace { - static String FullName(const TestGroup& group, const TestCase& test) + static String FullName(const TestDescribe& describe, const TestCase& test) { - // Build "SpecName.SubGroup.TestName" for reporting. Root has empty name. + // Build "SpecName.SubDescribe.TestName" for reporting. Root has empty name. String result; - if (!group.name.empty()) + if (!describe.name.empty()) { - result += group.name; + result += describe.name; result += "."; } result += test.name; return result; } - static void RunNested(TestGroup& group, TArray>& beforeHooks, - TArray>& afterHooks) + static bool MatchesFilter(StringView fullName, StringView filter) { - RegistryState& state = State(); - if (group.beforeEach) + return filter.empty() || Strings::Contains(fullName, filter); + } + + static void RunNested(TestDescribe& describe, TArray>& beforeHooks, + TArray>& afterHooks, StringView filter) + { + TestContext& context = GetTestContext(); + if (describe.beforeEach) { - beforeHooks.Add(group.beforeEach); + beforeHooks.Add(describe.beforeEach); } - if (group.afterEach) + if (describe.afterEach) { - afterHooks.Add(group.afterEach); + afterHooks.Add(describe.afterEach); } - for (TestGroup& sub : group.groups) + for (TestDescribe& sub : describe.describes) { - RunNested(sub, beforeHooks, afterHooks); + RunNested(sub, beforeHooks, afterHooks, filter); } - for (TestCase& test : group.tests) + for (TestCase& test : describe.tests) { if (test.skip) { - ++state.skippedTests; + ++context.skippedTests; + continue; + } + if (!MatchesFilter(FullName(describe, test), filter)) + { continue; } - ++state.runTests; + ++context.runTests; for (auto& hook : beforeHooks) { hook(); } - state.currentTestFailureCount = 0; + context.currentTestFailureCount = 0; bool passed = true; try { @@ -221,9 +232,9 @@ namespace p catch (...) { passed = false; - Error("PipeTests: test failed by exception: {}", FullName(group, test)); + Error("PipeTests: test failed by exception: {}", FullName(describe, test)); } - passed = passed && (state.currentTestFailureCount == 0); + passed = passed && (context.currentTestFailureCount == 0); for (i32 i = afterHooks.Size(); i > 0; --i) { @@ -232,20 +243,20 @@ namespace p if (passed) { - Info(" [PASS] {}", FullName(group, test)); + Info(" [PASS] {}", FullName(describe, test)); } else { - ++state.failedTests; - Error(" [FAIL] {}", FullName(group, test)); + ++context.failedTests; + Error(" [FAIL] {}", FullName(describe, test)); } } - if (group.beforeEach) + if (describe.beforeEach) { beforeHooks.RemoveLast(); } - if (group.afterEach) + if (describe.afterEach) { afterHooks.RemoveLast(); } @@ -253,20 +264,50 @@ namespace p } // namespace - int RunTests(int argc, char** argv) + int RunTests(const TestSettings& settings) { - (void)argc; // kept for future --filter support - (void)argv; + TestContext& context = GetTestContext(); + context.runTests = 0; + context.failedTests = 0; + context.skippedTests = 0; - RegistryState& state = State(); - Info("PipeTests: {} group(s) registered.", state.root.groups.Size()); + Info("PipeTests: {} describe(s) registered.", context.root.describes.Size()); TArray> beforeHooks; TArray> afterHooks; - RunNested(state.root, beforeHooks, afterHooks); + RunNested(context.root, beforeHooks, afterHooks, settings.filter); + + Info("PipeTests complete: {} run, {} passed, {} failed, {} skipped.", context.runTests, + context.runTests - context.failedTests, context.failedTests, context.skippedTests); - Info("PipeTests complete: {} run, {} passed, {} failed, {} skipped.", state.runTests, - state.runTests - state.failedTests, state.failedTests, state.skippedTests); + return context.failedTests == 0 ? 0 : 1; + } - return state.failedTests == 0 ? 0 : 1; + int RunTests(int argc, char** argv) + { + TestSettings settings; + for (i32 i = 1; i < argc; ++i) + { + const StringView arg{argv[i]}; + if (Strings::StartsWith(arg, StringView{"--filter="})) + { + settings.filter = Strings::RemoveFromStart(arg, StringView{"--filter="}); + } + else if (Strings::Equals(arg, StringView{"--filter"})) + { + if (i + 1 < argc) + { + settings.filter = StringView{argv[++i]}; + } + } + else if (Strings::StartsWith(arg, StringView{"--"})) + { + Warning("PipeTests: unknown argument '{}'. Ignoring.", arg); + } + else if (settings.filter.empty()) + { + settings.filter = arg; + } + } + return RunTests(settings); } } // namespace p diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 01a7e151..cb405df8 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -3,16 +3,16 @@ file(GLOB_RECURSE TESTS_SOURCE_FILES CONFIGURE_DEPENDS *.cpp *.h *.hpp) list(FILTER TESTS_SOURCE_FILES EXCLUDE REGEX ".*/PipeTests/.*") -add_executable(PipeTests ${TESTS_SOURCE_FILES}) -add_executable(Pipe::Tests ALIAS PipeTests) -target_include_directories(PipeTests PUBLIC .) -pipe_target_enable_CPP20(PipeTests) -pipe_target_disable_rtti(PipeTests PRIVATE) -pipe_target_define_platform(PipeTests) -pipe_target_shared_output_directory(PipeTests) -target_link_libraries(PipeTests PUBLIC Pipe Bandit) -pipe_add_sanitizers(PipeTests) +add_executable(PipeTesting ${TESTS_SOURCE_FILES}) +add_executable(Pipe::Testing ALIAS PipeTesting) +target_include_directories(PipeTesting PUBLIC .) +pipe_target_enable_CPP20(PipeTesting) +pipe_target_disable_rtti(PipeTesting PRIVATE) +pipe_target_define_platform(PipeTesting) +pipe_target_shared_output_directory(PipeTesting) +target_link_libraries(PipeTesting PUBLIC Pipe PipeTests) +pipe_add_sanitizers(PipeTesting) -add_test(NAME PipeTests COMMAND $ --reporter=spec) +add_test(NAME PipeTesting COMMAND $) -add_subdirectory(PipeTests) \ No newline at end of file +add_subdirectory(PipeTests) diff --git a/Tests/Containers/Arrays.spec.cpp b/Tests/Containers/Arrays.spec.cpp index a97fbc81..1223d392 100644 --- a/Tests/Containers/Arrays.spec.cpp +++ b/Tests/Containers/Arrays.spec.cpp @@ -1,12 +1,9 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include -#include +#include #include -using namespace snowhouse; -using namespace bandit; using namespace p; template @@ -44,11 +41,11 @@ struct CopyType }; -go_bandit([]() +void RegisterContainersArraysTests() { - describe("Containers.Array", []() + Spec("Containers.Array", []() { - it("Can initialize", [&]() + It("Can initialize", []() { TArray data1{}; TArray data2(3); @@ -56,133 +53,133 @@ go_bandit([]() TArray data4(3, 1); TArray data5(6, 1); - AssertThat(data1.Size(), Equals(0)); - AssertThat(data1.Capacity(), Equals(0)); - AssertThat(data2.Size(), Equals(3)); - AssertThat(data2.Capacity(), Equals(5)); - AssertThat(data3.Size(), Equals(4)); - AssertThat(data3.Capacity(), Equals(5)); - AssertThat(data4.Size(), Equals(3)); - AssertThat(data4.Capacity(), Equals(5)); - AssertThat(data5.Size(), Equals(6)); - AssertThat(data5.Capacity(), Equals(6)); - - AssertThat(data2[0], Equals(0)); - AssertThat(data2[2], Equals(0)); - AssertThat(data3[0], Equals(3)); - AssertThat(data3[3], Equals(3)); - AssertThat(data4[0], Equals(1)); - AssertThat(data4[2], Equals(1)); - AssertThat(data5[0], Equals(1)); - AssertThat(data5[5], Equals(1)); + Expect(data1.Size()).ToEqual(0); + Expect(data1.Capacity()).ToEqual(0); + Expect(data2.Size()).ToEqual(3); + Expect(data2.Capacity()).ToEqual(5); + Expect(data3.Size()).ToEqual(4); + Expect(data3.Capacity()).ToEqual(5); + Expect(data4.Size()).ToEqual(3); + Expect(data4.Capacity()).ToEqual(5); + Expect(data5.Size()).ToEqual(6); + Expect(data5.Capacity()).ToEqual(6); + + Expect(data2[0]).ToEqual(0); + Expect(data2[2]).ToEqual(0); + Expect(data3[0]).ToEqual(3); + Expect(data3[3]).ToEqual(3); + Expect(data4[0]).ToEqual(1); + Expect(data4[2]).ToEqual(1); + Expect(data5[0]).ToEqual(1); + Expect(data5[5]).ToEqual(1); }); - describe("Copy", []() + Describe("Copy", []() { - it("Can copy empty", [&]() + It("Can copy empty", []() { TArray source1{}; TArray target1 = source1; // NOLINT - AssertThat(target1.Data(), Equals(nullptr)); - AssertThat(target1.Size(), Equals(0)); - AssertThat(target1.Capacity(), Equals(0)); + Expect(target1.Data()).ToEqual(nullptr); + Expect(target1.Size()).ToEqual(0); + Expect(target1.Capacity()).ToEqual(0); TArray source2{}; TArray target2 = source2; // NOLINT - AssertThat(target2.Data(), Equals(nullptr)); - AssertThat(target2.Size(), Equals(0)); - AssertThat(target2.Capacity(), Equals(0)); + Expect(target2.Data()).ToEqual(nullptr); + Expect(target2.Size()).ToEqual(0); + Expect(target2.Capacity()).ToEqual(0); }); - it("Can copy dynamic to dynamic", [&]() + It("Can copy dynamic to dynamic", []() { TArray source{3, 4, 5, 6, 7, 8}; // Not inline buffer TArray target = source; - AssertThat(source.Size(), Equals(6)); - AssertThat(source.Capacity(), Equals(6)); - AssertThat(target.Size(), Equals(6)); - AssertThat(target.Capacity(), IsGreaterThanOrEqualTo(6)); - AssertThat(source[0], Equals(3)); - AssertThat(source[5], Equals(8)); - AssertThat(target[0], Equals(3)); - AssertThat(target[5], Equals(8)); - AssertThat(source.Data(), !Equals(source.GetInlineBuffer())); - AssertThat(target.Data(), !Equals(target.GetInlineBuffer())); + Expect(source.Size()).ToEqual(6); + Expect(source.Capacity()).ToEqual(6); + Expect(target.Size()).ToEqual(6); + Expect(target.Capacity()).ToBeGreaterOrEqual(6); + Expect(source[0]).ToEqual(3); + Expect(source[5]).ToEqual(8); + Expect(target[0]).ToEqual(3); + Expect(target[5]).ToEqual(8); + Expect(source.Data()).ToNotEqual(source.GetInlineBuffer()); + Expect(target.Data()).ToNotEqual(target.GetInlineBuffer()); }); - it("Can copy inline to inline", [&]() + It("Can copy inline to inline", []() { TArray source{3, 4, 5, 6}; // Not inline buffer TArray target; target = source; - AssertThat(source.Size(), Equals(4)); - AssertThat(source.Capacity(), Equals(5)); - AssertThat(target.Size(), Equals(4)); - AssertThat(target.Capacity(), Equals(5)); - AssertThat(source[0], Equals(3)); - AssertThat(source[3], Equals(6)); - AssertThat(target[0], Equals(3)); - AssertThat(target[3], Equals(6)); - AssertThat(source.Data(), Equals(source.GetInlineBuffer())); - AssertThat(target.Data(), Equals(target.GetInlineBuffer())); + Expect(source.Size()).ToEqual(4); + Expect(source.Capacity()).ToEqual(5); + Expect(target.Size()).ToEqual(4); + Expect(target.Capacity()).ToEqual(5); + Expect(source[0]).ToEqual(3); + Expect(source[3]).ToEqual(6); + Expect(target[0]).ToEqual(3); + Expect(target[3]).ToEqual(6); + Expect(source.Data()).ToEqual(source.GetInlineBuffer()); + Expect(target.Data()).ToEqual(target.GetInlineBuffer()); TArray target2; // Copy to a different size target2 = source; - AssertThat(source.Data(), Equals(source.GetInlineBuffer())); - AssertThat(target2.Data(), Equals(target2.GetInlineBuffer())); + Expect(source.Data()).ToEqual(source.GetInlineBuffer()); + Expect(target2.Data()).ToEqual(target2.GetInlineBuffer()); }); - it("Can copy dynamic to inline", [&]() + It("Can copy dynamic to inline", []() { TArray source{3, 4, 5, 6}; // Not inline buffer TArray target; target = source; - AssertThat(source.Size(), Equals(4)); - AssertThat(source.Capacity(), Equals(4)); - AssertThat(target.Size(), Equals(4)); - AssertThat(target.Capacity(), Equals(5)); - AssertThat(source[0], Equals(3)); - AssertThat(source[3], Equals(6)); - AssertThat(target[0], Equals(3)); - AssertThat(target[3], Equals(6)); - AssertThat(source.Data(), !Equals(source.GetInlineBuffer())); - AssertThat(target.Data(), Equals(target.GetInlineBuffer())); + Expect(source.Size()).ToEqual(4); + Expect(source.Capacity()).ToEqual(4); + Expect(target.Size()).ToEqual(4); + Expect(target.Capacity()).ToEqual(5); + Expect(source[0]).ToEqual(3); + Expect(source[3]).ToEqual(6); + Expect(target[0]).ToEqual(3); + Expect(target[3]).ToEqual(6); + Expect(source.Data()).ToNotEqual(source.GetInlineBuffer()); + Expect(target.Data()).ToEqual(target.GetInlineBuffer()); }); - it("Can copy inline to dynamic", [&]() + It("Can copy inline to dynamic", []() { TArray source{3, 4, 5, 6}; // Not inline buffer TArray target; target = source; - AssertThat(source.Size(), Equals(4)); - AssertThat(source.Capacity(), Equals(5)); - AssertThat(target.Size(), Equals(4)); - AssertThat(target.Capacity(), IsGreaterThanOrEqualTo(4)); - AssertThat(source[0], Equals(3)); - AssertThat(source[3], Equals(6)); - AssertThat(target[0], Equals(3)); - AssertThat(target[3], Equals(6)); - AssertThat(source.Data(), Equals(source.GetInlineBuffer())); - AssertThat(target.Data(), !Equals(target.GetInlineBuffer())); + Expect(source.Size()).ToEqual(4); + Expect(source.Capacity()).ToEqual(5); + Expect(target.Size()).ToEqual(4); + Expect(target.Capacity()).ToBeGreaterOrEqual(4); + Expect(source[0]).ToEqual(3); + Expect(source[3]).ToEqual(6); + Expect(target[0]).ToEqual(3); + Expect(target[3]).ToEqual(6); + Expect(source.Data()).ToEqual(source.GetInlineBuffer()); + Expect(target.Data()).ToNotEqual(target.GetInlineBuffer()); }); }); - describe("Move", []() + Describe("Move", []() { - it("Can move empty", [&]() + It("Can move empty", []() { TArray source1{}; TArray target1 = Move(source1); - AssertThat(target1.Data(), Equals(nullptr)); - AssertThat(target1.Size(), Equals(0)); - AssertThat(target1.Capacity(), Equals(0)); + Expect(target1.Data()).ToEqual(nullptr); + Expect(target1.Size()).ToEqual(0); + Expect(target1.Capacity()).ToEqual(0); TArray source2{}; TArray target2 = Move(source2); - AssertThat(target2.Data(), Equals(nullptr)); - AssertThat(target2.Size(), Equals(0)); - AssertThat(target2.Capacity(), Equals(0)); + Expect(target2.Data()).ToEqual(nullptr); + Expect(target2.Size()).ToEqual(0); + Expect(target2.Capacity()).ToEqual(0); }); - it("Can move dynamic to dynamic", [&]() + It("Can move dynamic to dynamic", []() { TArray source{}; // Not inline buffer source.Add(3); @@ -193,18 +190,18 @@ go_bandit([]() source.Add(8); MoveType* sourceData = source.Data(); TArray target = Move(source); - AssertThat(source.Size(), Equals(0)); - AssertThat(source.Capacity(), Equals(0)); - AssertThat(target.Size(), Equals(6)); - AssertThat(target.Capacity(), IsGreaterThanOrEqualTo(6)); - AssertThat(target[0].value, Equals(3)); - AssertThat(target[5].value, Equals(8)); - AssertThat(source.Data(), Equals(nullptr)); - AssertThat(target.Data(), !Equals(target.GetInlineBuffer())); - AssertThat(target.Data(), Equals(sourceData)); + Expect(source.Size()).ToEqual(0); + Expect(source.Capacity()).ToEqual(0); + Expect(target.Size()).ToEqual(6); + Expect(target.Capacity()).ToBeGreaterOrEqual(6); + Expect(target[0].value).ToEqual(3); + Expect(target[5].value).ToEqual(8); + Expect(source.Data()).ToEqual(nullptr); + Expect(target.Data()).ToNotEqual(target.GetInlineBuffer()); + Expect(target.Data()).ToEqual(sourceData); }); - it("Can move inline to inline", [&]() + It("Can move inline to inline", []() { TArray source{}; // Not inline buffer source.Add(3); @@ -218,21 +215,21 @@ go_bandit([]() source2.Add(6); TArray target; target = Move(source); - AssertThat(source.Size(), Equals(0)); - AssertThat(source.Capacity(), Equals(0)); - AssertThat(target.Size(), Equals(4)); - AssertThat(target.Capacity(), Equals(5)); - AssertThat(target[0].value, Equals(3)); - AssertThat(target[3].value, Equals(6)); - AssertThat(source.Data(), Equals(nullptr)); - AssertThat(target.Data(), Equals(target.GetInlineBuffer())); + Expect(source.Size()).ToEqual(0); + Expect(source.Capacity()).ToEqual(0); + Expect(target.Size()).ToEqual(4); + Expect(target.Capacity()).ToEqual(5); + Expect(target[0].value).ToEqual(3); + Expect(target[3].value).ToEqual(6); + Expect(source.Data()).ToEqual(nullptr); + Expect(target.Data()).ToEqual(target.GetInlineBuffer()); TArray target2; // Copy to a different size target2 = Move(source2); - AssertThat(source2.Data(), Equals(nullptr)); - AssertThat(target2.Data(), Equals(target2.GetInlineBuffer())); + Expect(source2.Data()).ToEqual(nullptr); + Expect(target2.Data()).ToEqual(target2.GetInlineBuffer()); }); - it("Can move dynamic to inline", [&]() + It("Can move dynamic to inline", []() { TArray source{}; // Not inline buffer source.Add(3); @@ -242,20 +239,20 @@ go_bandit([]() MoveType* sourceData = source.Data(); TArray target; target = Move(source); - AssertThat(source.Size(), Equals(0)); - AssertThat(source.Capacity(), Equals(0)); - AssertThat(target.Size(), Equals(4)); - AssertThat(target.Capacity(), Equals(4)); + Expect(source.Size()).ToEqual(0); + Expect(source.Capacity()).ToEqual(0); + Expect(target.Size()).ToEqual(4); + Expect(target.Capacity()).ToEqual(4); - AssertThat(target[0].value, Equals(3)); - AssertThat(target[3].value, Equals(6)); + Expect(target[0].value).ToEqual(3); + Expect(target[3].value).ToEqual(6); - AssertThat(source.Data(), Equals(nullptr)); - AssertThat(target.Data(), !Equals(target.GetInlineBuffer())); - AssertThat(target.Data(), Equals(sourceData)); + Expect(source.Data()).ToEqual(nullptr); + Expect(target.Data()).ToNotEqual(target.GetInlineBuffer()); + Expect(target.Data()).ToEqual(sourceData); }); - it("Can move inline to dynamic", [&]() + It("Can move inline to dynamic", []() { TArray source{}; // Inline buffer source.Add(3); @@ -264,666 +261,665 @@ go_bandit([]() source.Add(6); TArray target; target = Move(source); - AssertThat(source.Size(), Equals(0)); - AssertThat(source.Capacity(), Equals(0)); - AssertThat(target.Size(), Equals(4)); - AssertThat(target.Capacity(), IsGreaterThanOrEqualTo(4)); + Expect(source.Size()).ToEqual(0); + Expect(source.Capacity()).ToEqual(0); + Expect(target.Size()).ToEqual(4); + Expect(target.Capacity()).ToBeGreaterOrEqual(4); - AssertThat(target[0].value, Equals(3)); - AssertThat(target[3].value, Equals(6)); + Expect(target[0].value).ToEqual(3); + Expect(target[3].value).ToEqual(6); - AssertThat(source.Data(), Equals(nullptr)); - AssertThat(target.Data(), !Equals(target.GetInlineBuffer())); + Expect(source.Data()).ToEqual(nullptr); + Expect(target.Data()).ToNotEqual(target.GetInlineBuffer()); }); }); - it("Can access data", [&]() + It("Can access data", []() { TArray data1; TArray data2{1}; TArray data3{1}; - AssertThat(data1.Data(), Equals(nullptr)); - AssertThat(data2.Data(), !Equals(nullptr)); - AssertThat(data3.Data(), !Equals(nullptr)); + Expect(data1.Data()).ToEqual(nullptr); + Expect(data2.Data()).ToNotEqual(nullptr); + Expect(data3.Data()).ToNotEqual(nullptr); }); - describe("Add", []() + Describe("Add", []() { - it("Can add to dynamic", [&]() + It("Can add to dynamic", []() { TArray data; data.Reserve(2); // Reserve because we are not testing reallocation here data.Add(3); - AssertThat(data.Size(), Equals(1)); - AssertThat(data[0], Equals(3)); + Expect(data.Size()).ToEqual(1); + Expect(data[0]).ToEqual(3); data.Add(4); - AssertThat(data.Size(), Equals(2)); - AssertThat(data[1], Equals(4)); + Expect(data.Size()).ToEqual(2); + Expect(data[1]).ToEqual(4); }); - it("Can add to inline", [&]() + It("Can add to inline", []() { TArray data; data.Add(3); - AssertThat(data.Size(), Equals(1)); - AssertThat(data[0], Equals(3)); + Expect(data.Size()).ToEqual(1); + Expect(data[0]).ToEqual(3); data.Add(4); - AssertThat(data.Size(), Equals(2)); - AssertThat(data[1], Equals(4)); + Expect(data.Size()).ToEqual(2); + Expect(data[1]).ToEqual(4); }); - it("Can add to correct buffers", [&]() + It("Can add to correct buffers", []() { TArray data; data.Add(3); data.Add(4); - AssertThat(data.Data(), Equals(data.GetInlineBuffer())); + Expect(data.Data()).ToEqual(data.GetInlineBuffer()); data.Add(5); // Grow here to dynamic buffer - AssertThat(data.Size(), Equals(3)); - AssertThat(data[0], Equals(3)); - AssertThat(data[1], Equals(4)); - AssertThat(data[2], Equals(5)); - AssertThat(data.Data(), !Equals(data.GetInlineBuffer())); + Expect(data.Size()).ToEqual(3); + Expect(data[0]).ToEqual(3); + Expect(data[1]).ToEqual(4); + Expect(data[2]).ToEqual(5); + Expect(data.Data()).ToNotEqual(data.GetInlineBuffer()); }); - it("Can add value by move", [&]() + It("Can add value by move", []() { TArray data; MoveType tmp{2}; data.Add(Move(tmp)); data.Add(MoveType{3}); - AssertThat(data[0].value, Equals(2)); - AssertThat(data[1].value, Equals(3)); - AssertThat(tmp.value, Equals(0)); + Expect(data[0].value).ToEqual(2); + Expect(data[1].value).ToEqual(3); + Expect(tmp.value).ToEqual(0); }); - it("Can add value by copy", [&]() + It("Can add value by copy", []() { TArray data; i32 tmp = 2; data.Add(tmp); data.Add(3); - AssertThat(tmp, Equals(2)); - AssertThat(data[0], Equals(2)); - AssertThat(data[1], Equals(3)); + Expect(tmp).ToEqual(2); + Expect(data[0]).ToEqual(2); + Expect(data[1]).ToEqual(3); }); - it("Can add defaulted", [&]() + It("Can add defaulted", []() { TArray data; data.Add(); - AssertThat(data[0], Equals(0)); + Expect(data[0]).ToEqual(0); }); }); - describe("Append", []() + Describe("Append", []() { - it("Can append defaulted", [&]() + It("Can append defaulted", []() { TArray data; data.Append(2); - AssertThat(data.Size(), Equals(2)); - AssertThat(data[0], Equals(0)); - AssertThat(data[1], Equals(0)); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(0); + Expect(data[1]).ToEqual(0); data.Append(0); - AssertThat(data.Size(), Equals(2)); + Expect(data.Size()).ToEqual(2); data.Append(2); - AssertThat(data.Size(), Equals(4)); - AssertThat(data[2], Equals(0)); - AssertThat(data[3], Equals(0)); + Expect(data.Size()).ToEqual(4); + Expect(data[2]).ToEqual(0); + Expect(data[3]).ToEqual(0); }); - it("Can append value", [&]() + It("Can append value", []() { TArray data; data.Append(2, 234); - AssertThat(data.Size(), Equals(2)); - AssertThat(data[0], Equals(234)); - AssertThat(data[1], Equals(234)); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(234); + Expect(data[1]).ToEqual(234); data.Append(0, 234); - AssertThat(data.Size(), Equals(2)); + Expect(data.Size()).ToEqual(2); data.Append(2, 235); - AssertThat(data.Size(), Equals(4)); - AssertThat(data[2], Equals(235)); - AssertThat(data[3], Equals(235)); + Expect(data.Size()).ToEqual(4); + Expect(data[2]).ToEqual(235); + Expect(data[3]).ToEqual(235); }); - it("Can assign multiple values", [&]() + It("Can assign multiple values", []() { TArray data; i32 buffer[]{24, 53}; i32 buffer2[]{74, 51}; data.Append(buffer, 2); - AssertThat(data.Size(), Equals(2)); - AssertThat(data[0], Equals(24)); - AssertThat(data[1], Equals(53)); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(24); + Expect(data[1]).ToEqual(53); data.Append(nullptr, 0); - AssertThat(data.Size(), Equals(2)); + Expect(data.Size()).ToEqual(2); data.Append(buffer2, 2); - AssertThat(data.Size(), Equals(4)); - AssertThat(data[2], Equals(74)); - AssertThat(data[3], Equals(51)); + Expect(data.Size()).ToEqual(4); + Expect(data[2]).ToEqual(74); + Expect(data[3]).ToEqual(51); }); - it("Can append to dynamic", [&]() + It("Can append to dynamic", []() { TArray data; data.Reserve(2); // Reserve because we are not testing reallocation here data.Append(2, 33); - AssertThat(data.Size(), Equals(2)); - AssertThat(data[0], Equals(33)); - AssertThat(data[1], Equals(33)); - AssertThat(data.Data(), !Equals(data.GetInlineBuffer())); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(33); + Expect(data[1]).ToEqual(33); + Expect(data.Data()).ToNotEqual(data.GetInlineBuffer()); }); - it("Can assign to inline", [&]() + It("Can assign to inline", []() { TArray data; data.Reserve(2); // Reserve because we are not testing reallocation here data.Append(2, 33); - AssertThat(data.Size(), Equals(2)); - AssertThat(data[0], Equals(33)); - AssertThat(data[1], Equals(33)); - AssertThat(data.Data(), Equals(data.GetInlineBuffer())); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(33); + Expect(data[1]).ToEqual(33); + Expect(data.Data()).ToEqual(data.GetInlineBuffer()); }); }); - describe("Assign", []() + Describe("Assign", []() { - it("Can assign defaulted", [&]() + It("Can assign defaulted", []() { TArray data; data.Assign(2); - AssertThat(data.Size(), Equals(2)); - AssertThat(data[0], Equals(0)); - AssertThat(data[1], Equals(0)); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(0); + Expect(data[1]).ToEqual(0); data.Assign(0); - AssertThat(data.Size(), Equals(0)); + Expect(data.Size()).ToEqual(0); data.Assign(2); - AssertThat(data.Size(), Equals(2)); - AssertThat(data[0], Equals(0)); - AssertThat(data[1], Equals(0)); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(0); + Expect(data[1]).ToEqual(0); }); - it("Can assign value", [&]() + It("Can assign value", []() { TArray data; data.Assign(2, 234); - AssertThat(data.Size(), Equals(2)); - AssertThat(data[0], Equals(234)); - AssertThat(data[1], Equals(234)); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(234); + Expect(data[1]).ToEqual(234); data.Assign(0, 234); - AssertThat(data.Size(), Equals(0)); + Expect(data.Size()).ToEqual(0); data.Assign(2, 235); - AssertThat(data.Size(), Equals(2)); - AssertThat(data[0], Equals(235)); - AssertThat(data[1], Equals(235)); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(235); + Expect(data[1]).ToEqual(235); }); - it("Can assign multiple values", [&]() + It("Can assign multiple values", []() { TArray data; i32 buffer[]{24, 53}; i32 buffer2[]{74, 51}; data.Assign(buffer, 2); - AssertThat(data.Size(), Equals(2)); - AssertThat(data[0], Equals(24)); - AssertThat(data[1], Equals(53)); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(24); + Expect(data[1]).ToEqual(53); data.Assign(nullptr, 0); - AssertThat(data.Size(), Equals(0)); + Expect(data.Size()).ToEqual(0); data.Assign(buffer2, 2); - AssertThat(data.Size(), Equals(2)); - AssertThat(data[0], Equals(74)); - AssertThat(data[1], Equals(51)); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(74); + Expect(data[1]).ToEqual(51); }); - it("Can assign to dynamic", [&]() + It("Can assign to dynamic", []() { TArray data; data.Reserve(2); // Reserve because we are not testing reallocation here data.Assign(2, 33); - AssertThat(data.Size(), Equals(2)); - AssertThat(data[0], Equals(33)); - AssertThat(data[1], Equals(33)); - AssertThat(data.Data(), !Equals(data.GetInlineBuffer())); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(33); + Expect(data[1]).ToEqual(33); + Expect(data.Data()).ToNotEqual(data.GetInlineBuffer()); }); - it("Can assign to inline", [&]() + It("Can assign to inline", []() { TArray data; data.Reserve(2); // Reserve because we are not testing reallocation here data.Assign(2, 33); - AssertThat(data.Size(), Equals(2)); - AssertThat(data[0], Equals(33)); - AssertThat(data[1], Equals(33)); - AssertThat(data.Data(), Equals(data.GetInlineBuffer())); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(33); + Expect(data[1]).ToEqual(33); + Expect(data.Data()).ToEqual(data.GetInlineBuffer()); }); }); - describe("Insert", []() + Describe("Insert", []() { - it("Can insert at empty", [&]() + It("Can insert at empty", []() { TArray data; data.Insert(0, 12); - AssertThat(data.Size(), Equals(1)); - AssertThat(data[0], Equals(12)); + Expect(data.Size()).ToEqual(1); + Expect(data[0]).ToEqual(12); data.Insert(0, 21); - AssertThat(data.Size(), Equals(2)); - AssertThat(data[0], Equals(21)); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(21); }); - it("Can insert at end", [&]() + It("Can insert at end", []() { TArray data{12, 34}; data.Insert(2, 12); - AssertThat(data.Size(), Equals(3)); - AssertThat(data[2], Equals(12)); + Expect(data.Size()).ToEqual(3); + Expect(data[2]).ToEqual(12); }); - it("Can insert to inline", [&]() + It("Can insert to inline", []() { TArray data; data.Insert(0, 12); data.Insert(0, 21); - AssertThat(data.Size(), Equals(2)); - AssertThat(data[0], Equals(21)); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(21); }); - it("Can insert copied value", [&]() + It("Can insert copied value", []() { TArray data; data.Insert(0, 32); // Insert at empty - AssertThat(data.Size(), Equals(1)); - AssertThat(data[0], Equals(32)); + Expect(data.Size()).ToEqual(1); + Expect(data[0]).ToEqual(32); data.Insert(0, 65); // Insert at start - AssertThat(data.Size(), Equals(2)); - AssertThat(data[0], Equals(65)); - AssertThat(data[1], Equals(32)); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(65); + Expect(data[1]).ToEqual(32); data.Add(85); data.Insert(1, 27); // Insert in the middle - AssertThat(data.Size(), Equals(4)); - AssertThat(data[1], Equals(27)); + Expect(data.Size()).ToEqual(4); + Expect(data[1]).ToEqual(27); data.Insert(4, 43); // Insert in the end - AssertThat(data.Size(), Equals(5)); - AssertThat(data[4], Equals(43)); + Expect(data.Size()).ToEqual(5); + Expect(data[4]).ToEqual(43); }); - it("Can insert many values", [&]() + It("Can insert many values", []() { TArray data; data.Insert(0, 2, 32); // Insert at empty - AssertThat(data.Size(), Equals(2)); - AssertThat(data[0], Equals(32)); - AssertThat(data[1], Equals(32)); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(32); + Expect(data[1]).ToEqual(32); data.Insert(0, 2, 5); // Insert at start - AssertThat(data.Size(), Equals(4)); - AssertThat(data[0], Equals(5)); - AssertThat(data[1], Equals(5)); - AssertThat(data[2], Equals(32)); - AssertThat(data[3], Equals(32)); + Expect(data.Size()).ToEqual(4); + Expect(data[0]).ToEqual(5); + Expect(data[1]).ToEqual(5); + Expect(data[2]).ToEqual(32); + Expect(data[3]).ToEqual(32); data.Insert(3, 2, 6); // Insert in the middle - AssertThat(data.Size(), Equals(6)); - AssertThat(data[3], Equals(6)); - AssertThat(data[4], Equals(6)); + Expect(data.Size()).ToEqual(6); + Expect(data[3]).ToEqual(6); + Expect(data[4]).ToEqual(6); data.Insert(6, 2, 9); // Insert in the end - AssertThat(data.Size(), Equals(8)); - AssertThat(data[6], Equals(9)); - AssertThat(data[7], Equals(9)); + Expect(data.Size()).ToEqual(8); + Expect(data[6]).ToEqual(9); + Expect(data[7]).ToEqual(9); }); - it("Can insert many values inline", [&]() + It("Can insert many values inline", []() { TArray data{1, 2}; data.Insert(1, 3, 9); // More values than trailing elements - AssertThat(data.Size(), Equals(5)); - AssertThat(data[0], Equals(1)); - AssertThat(data[1], Equals(9)); - AssertThat(data[2], Equals(9)); - AssertThat(data[3], Equals(9)); - AssertThat(data[4], Equals(2)); + Expect(data.Size()).ToEqual(5); + Expect(data[0]).ToEqual(1); + Expect(data[1]).ToEqual(9); + Expect(data[2]).ToEqual(9); + Expect(data[3]).ToEqual(9); + Expect(data[4]).ToEqual(2); data.Insert(0, 2, 7); // Fewer values than trailing elements - AssertThat(data.Size(), Equals(7)); - AssertThat(data[0], Equals(7)); - AssertThat(data[1], Equals(7)); - AssertThat(data[2], Equals(1)); - AssertThat(data[3], Equals(9)); - AssertThat(data[4], Equals(9)); - AssertThat(data[5], Equals(9)); - AssertThat(data[6], Equals(2)); + Expect(data.Size()).ToEqual(7); + Expect(data[0]).ToEqual(7); + Expect(data[1]).ToEqual(7); + Expect(data[2]).ToEqual(1); + Expect(data[3]).ToEqual(9); + Expect(data[4]).ToEqual(9); + Expect(data[5]).ToEqual(9); + Expect(data[6]).ToEqual(2); data.Insert(5, 3, 4); // One more value than trailing elements - AssertThat(data.Size(), Equals(10)); - AssertThat(data[5], Equals(4)); - AssertThat(data[6], Equals(4)); - AssertThat(data[7], Equals(4)); - AssertThat(data[8], Equals(9)); - AssertThat(data[9], Equals(2)); + Expect(data.Size()).ToEqual(10); + Expect(data[5]).ToEqual(4); + Expect(data[6]).ToEqual(4); + Expect(data[7]).ToEqual(4); + Expect(data[8]).ToEqual(9); + Expect(data[9]).ToEqual(2); data.Insert(3, 8, 6); // One more value than trailing elements - AssertThat(data.Size(), Equals(18)); - AssertThat(data[3], Equals(6)); - AssertThat(data[10], Equals(6)); - AssertThat(data[11], Equals(9)); - AssertThat(data[17], Equals(2)); + Expect(data.Size()).ToEqual(18); + Expect(data[3]).ToEqual(6); + Expect(data[10]).ToEqual(6); + Expect(data[11]).ToEqual(9); + Expect(data[17]).ToEqual(2); }); - it("Can insert buffer inline", [&]() + It("Can insert buffer inline", []() { TArray data{1, 2, 3}; i32 src[]{4, 5, 6, 7}; data.Insert(1, src, 4); // More values than trailing elements - AssertThat(data.Size(), Equals(7)); - AssertThat(data[0], Equals(1)); - AssertThat(data[1], Equals(4)); - AssertThat(data[4], Equals(7)); - AssertThat(data[5], Equals(2)); - AssertThat(data[6], Equals(3)); + Expect(data.Size()).ToEqual(7); + Expect(data[0]).ToEqual(1); + Expect(data[1]).ToEqual(4); + Expect(data[4]).ToEqual(7); + Expect(data[5]).ToEqual(2); + Expect(data[6]).ToEqual(3); }); - it("Can insert many non trivial values inline", [&]() + It("Can insert many non trivial values inline", []() { TArray data; data.Add(CopyType{1}); data.Add(CopyType{2}); data.Insert(1, 3, CopyType{9}); // More values than trailing elements - AssertThat(data.Size(), Equals(5)); - AssertThat(data[0].value, Equals(1)); - AssertThat(data[1].value, Equals(9)); - AssertThat(data[2].value, Equals(9)); - AssertThat(data[3].value, Equals(9)); - AssertThat(data[4].value, Equals(2)); + Expect(data.Size()).ToEqual(5); + Expect(data[0].value).ToEqual(1); + Expect(data[1].value).ToEqual(9); + Expect(data[2].value).ToEqual(9); + Expect(data[3].value).ToEqual(9); + Expect(data[4].value).ToEqual(2); }); - it("Can insert moved value", [&]() + It("Can insert moved value", []() { TArray data; MoveType tmp{34}; data.Insert(0, Move(tmp)); // Insert at empty - AssertThat(data.Size(), Equals(1)); - AssertThat(data[0].value, Equals(34)); - AssertThat(tmp.value, Equals(0)); + Expect(data.Size()).ToEqual(1); + Expect(data[0].value).ToEqual(34); + Expect(tmp.value).ToEqual(0); MoveType tmp2{4}; data.Insert(0, Move(tmp2)); // Insert at start - AssertThat(data.Size(), Equals(2)); - AssertThat(data[0].value, Equals(4)); - AssertThat(data[1].value, Equals(34)); - AssertThat(tmp2.value, Equals(0)); + Expect(data.Size()).ToEqual(2); + Expect(data[0].value).ToEqual(4); + Expect(data[1].value).ToEqual(34); + Expect(tmp2.value).ToEqual(0); MoveType tmp3{3}; data.Add(MoveType{85}); data.Insert(1, Move(tmp3)); // Insert in the middle - AssertThat(data.Size(), Equals(4)); - AssertThat(data[1].value, Equals(3)); - AssertThat(tmp3.value, Equals(0)); + Expect(data.Size()).ToEqual(4); + Expect(data[1].value).ToEqual(3); + Expect(tmp3.value).ToEqual(0); MoveType tmp4{7}; data.Insert(4, Move(tmp4)); // Insert in the end - AssertThat(data.Size(), Equals(5)); - AssertThat(data[4].value, Equals(7)); - AssertThat(tmp4.value, Equals(0)); + Expect(data.Size()).ToEqual(5); + Expect(data[4].value).ToEqual(7); + Expect(tmp4.value).ToEqual(0); }); - it("Can insert buffer", [&]() + It("Can insert buffer", []() { TArray data; i32 src[]{34, 23, 844}; data.Insert(0, src, 3); // Insert at empty - AssertThat(data.Size(), Equals(3)); - AssertThat(data[0], Equals(34)); - AssertThat(data[1], Equals(23)); - AssertThat(data[2], Equals(844)); + Expect(data.Size()).ToEqual(3); + Expect(data[0]).ToEqual(34); + Expect(data[1]).ToEqual(23); + Expect(data[2]).ToEqual(844); i32 src2[]{2, 71, 21}; data.Insert(0, src2, 3); // Insert at start - AssertThat(data.Size(), Equals(6)); - AssertThat(data[0], Equals(2)); - AssertThat(data[1], Equals(71)); - AssertThat(data[2], Equals(21)); - AssertThat(data[3], Equals(34)); - AssertThat(data[4], Equals(23)); - AssertThat(data[5], Equals(844)); + Expect(data.Size()).ToEqual(6); + Expect(data[0]).ToEqual(2); + Expect(data[1]).ToEqual(71); + Expect(data[2]).ToEqual(21); + Expect(data[3]).ToEqual(34); + Expect(data[4]).ToEqual(23); + Expect(data[5]).ToEqual(844); i32 src3[]{4, 3, 6}; data.Insert(3, src3, 3); // Insert in the middle - AssertThat(data.Size(), Equals(9)); - AssertThat(data[3], Equals(4)); - AssertThat(data[4], Equals(3)); - AssertThat(data[5], Equals(6)); + Expect(data.Size()).ToEqual(9); + Expect(data[3]).ToEqual(4); + Expect(data[4]).ToEqual(3); + Expect(data[5]).ToEqual(6); i32 src4[]{7, 2, 3}; data.Insert(9, src4, 3); // Insert in the end - AssertThat(data.Size(), Equals(12)); - AssertThat(data[9], Equals(7)); - AssertThat(data[10], Equals(2)); - AssertThat(data[11], Equals(3)); + Expect(data.Size()).ToEqual(12); + Expect(data[9]).ToEqual(7); + Expect(data[10]).ToEqual(2); + Expect(data[11]).ToEqual(3); }); }); - describe("Remove", []() + Describe("Remove", []() { - it("Can remove at index", []() + It("Can remove at index", []() { TArray data{1, 2, 3, 4}; // Check invalid inputs - AssertThat(data.RemoveAt(-1), Equals(false)); - AssertThat(data.RemoveAt(4), Equals(false)); + Expect(data.RemoveAt(-1)).ToEqual(false); + Expect(data.RemoveAt(4)).ToEqual(false); - AssertThat(data.RemoveAt(3), Equals(true)); // Remove last - AssertThat(data, Equals(TArray{1, 2, 3})); + Expect(data.RemoveAt(3)).ToEqual(true); // Remove last + Expect(data).ToEqual(TArray{1, 2, 3}); - AssertThat(data.RemoveAt(1), Equals(true)); // Remove in the middle - AssertThat(data, Equals(TArray{1, 3})); + Expect(data.RemoveAt(1)).ToEqual(true); // Remove in the middle + Expect(data).ToEqual(TArray{1, 3}); - AssertThat(data.RemoveAt(0), Equals(true)); // remove first - AssertThat(data, Equals(TArray{3})); + Expect(data.RemoveAt(0)).ToEqual(true); // remove first + Expect(data).ToEqual(TArray{3}); }); - it("Can remove many at index", []() + It("Can remove many at index", []() { TArray data{1, 2, 3, 4, 5, 6, 7, 8}; // Check invalid inputs - AssertThat(data.RemoveAt(-1, 2), Equals(false)); - AssertThat(data.RemoveAt(8, 2), Equals(false)); - AssertThat(data.RemoveAt(7, 2), Equals(false)); + Expect(data.RemoveAt(-1, 2)).ToEqual(false); + Expect(data.RemoveAt(8, 2)).ToEqual(false); + Expect(data.RemoveAt(7, 2)).ToEqual(false); - AssertThat(data.RemoveAt(6, 2), Equals(true)); // Remove last - AssertThat(data, Equals(TArray{1, 2, 3, 4, 5, 6})); + Expect(data.RemoveAt(6, 2)).ToEqual(true); // Remove last + Expect(data).ToEqual(TArray{1, 2, 3, 4, 5, 6}); - AssertThat(data.RemoveAt(2, 2), Equals(true)); // Remove in the middle - AssertThat(data, Equals(TArray{1, 2, 5, 6})); + Expect(data.RemoveAt(2, 2)).ToEqual(true); // Remove in the middle + Expect(data).ToEqual(TArray{1, 2, 5, 6}); - AssertThat(data.RemoveAt(0, 2), Equals(true)); // Remove first - AssertThat(data, Equals(TArray{5, 6})); + Expect(data.RemoveAt(0, 2)).ToEqual(true); // Remove first + Expect(data).ToEqual(TArray{5, 6}); }); - it("Can remove swap at index", []() + It("Can remove swap at index", []() { TArray data{1, 2, 3, 4, 5}; // Check invalid inputs - AssertThat(data.RemoveAtSwap(-1), Equals(false)); - AssertThat(data.RemoveAtSwap(5), Equals(false)); + Expect(data.RemoveAtSwap(-1)).ToEqual(false); + Expect(data.RemoveAtSwap(5)).ToEqual(false); - AssertThat(data.RemoveAtSwap(3), Equals(true)); // Remove last - AssertThat(data, Equals(TArray{1, 2, 3, 5})); + Expect(data.RemoveAtSwap(3)).ToEqual(true); // Remove last + Expect(data).ToEqual(TArray{1, 2, 3, 5}); - AssertThat(data.RemoveAtSwap(1), Equals(true)); // Remove swapping - AssertThat(data, Equals(TArray{1, 5, 3})); + Expect(data.RemoveAtSwap(1)).ToEqual(true); // Remove swapping + Expect(data).ToEqual(TArray{1, 5, 3}); - AssertThat(data.RemoveAtSwap(0), Equals(true)); // Remove first - AssertThat(data, Equals(TArray{3, 5})); + Expect(data.RemoveAtSwap(0)).ToEqual(true); // Remove first + Expect(data).ToEqual(TArray{3, 5}); }); - it("Can remove swap many at index", []() + It("Can remove swap many at index", []() { TArray data{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // Check invalid inputs - AssertThat(data.RemoveAtSwap(-1, 2), Equals(false)); - AssertThat(data.RemoveAtSwap(10, 2), Equals(false)); - AssertThat(data.RemoveAtSwap(9, 2), Equals(false)); + Expect(data.RemoveAtSwap(-1, 2)).ToEqual(false); + Expect(data.RemoveAtSwap(10, 2)).ToEqual(false); + Expect(data.RemoveAtSwap(9, 2)).ToEqual(false); - AssertThat(data.RemoveAtSwap(8, 2), Equals(true)); // Remove last - AssertThat(data, Equals(TArray{1, 2, 3, 4, 5, 6, 7, 8})); + Expect(data.RemoveAtSwap(8, 2)).ToEqual(true); // Remove last + Expect(data).ToEqual(TArray{1, 2, 3, 4, 5, 6, 7, 8}); - AssertThat(data.RemoveAtSwap(1, 2), Equals(true)); // Removes swapping - AssertThat(data, Equals(TArray{1, 7, 8, 4, 5, 6})); + Expect(data.RemoveAtSwap(1, 2)).ToEqual(true); // Removes swapping + Expect(data).ToEqual(TArray{1, 7, 8, 4, 5, 6}); - AssertThat( - data.RemoveAtSwap(1, 3), Equals(true)); // Removes swapping with less left - AssertThat(data, Equals(TArray{1, 5, 6})); + Expect(data.RemoveAtSwap(1, 3)).ToEqual(true); // Removes swapping with less left + Expect(data).ToEqual(TArray{1, 5, 6}); - AssertThat(data.RemoveAtSwap(0, 2), Equals(true)); // Remove first - AssertThat(data, Equals(TArray{6})); + Expect(data.RemoveAtSwap(0, 2)).ToEqual(true); // Remove first + Expect(data).ToEqual(TArray{6}); }); - it("Can RemoveLast", [&]() + It("Can RemoveLast", []() { TArray data{1, 4, 6}; data.RemoveLast(); - AssertThat(data.Size(), Equals(2)); - AssertThat(data[0], Equals(1)); - AssertThat(data[1], Equals(4)); - AssertThat(data.Capacity(), Equals(2)); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(1); + Expect(data[1]).ToEqual(4); + Expect(data.Capacity()).ToEqual(2); }); - it("Can RemoveLast N", [&]() + It("Can RemoveLast N", []() { TArray dataA{1, 4, 6}; dataA.RemoveLast(2); - AssertThat(dataA.Size(), Equals(1)); - AssertThat(dataA[0], Equals(1)); - AssertThat(dataA.Capacity(), Equals(1)); + Expect(dataA.Size()).ToEqual(1); + Expect(dataA[0]).ToEqual(1); + Expect(dataA.Capacity()).ToEqual(1); TArray dataB{1, 4, 6}; dataB.RemoveLast(3); - AssertThat(dataB.Size(), Equals(0)); - AssertThat(dataB.Capacity(), Equals(0)); + Expect(dataB.Size()).ToEqual(0); + Expect(dataB.Capacity()).ToEqual(0); }); - it("Can RemoveIf", [&]() + It("Can RemoveIf", []() { TArray data{1, 4, 5, 6}; - AssertThat(data.Size(), Equals(4)); + Expect(data.Size()).ToEqual(4); data.RemoveIf([](i32 v) { return v == 1 || v == 6; }); - AssertThat(data.Size(), Equals(2)); - AssertThat(data[0], Equals(4)); - AssertThat(data[1], Equals(5)); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(4); + Expect(data[1]).ToEqual(5); }); - it("Can RemoveIfSwap", [&]() + It("Can RemoveIfSwap", []() { TArray data{1, 4, 5, 6}; - AssertThat(data.Size(), Equals(4)); + Expect(data.Size()).ToEqual(4); data.RemoveIfSwap([](i32 v) { return v == 1 || v == 6; }); - AssertThat(data.Size(), Equals(2)); - AssertThat(data[0], Equals(5)); - AssertThat(data[1], Equals(4)); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(5); + Expect(data[1]).ToEqual(4); }); }); - it("Can Sort", [&]() + It("Can Sort", []() { TArray data0{34, 1, 5}; data0.Sort(); // Default sort is less - AssertThat(data0[0], Equals(1)); - AssertThat(data0[1], Equals(5)); - AssertThat(data0[2], Equals(34)); + Expect(data0[0]).ToEqual(1); + Expect(data0[1]).ToEqual(5); + Expect(data0[2]).ToEqual(34); TArray data1{34, 1, 5}; data1.Sort(TGreater{}); - AssertThat(data1[0], Equals(34)); - AssertThat(data1[1], Equals(5)); - AssertThat(data1[2], Equals(1)); + Expect(data1[0]).ToEqual(34); + Expect(data1[1]).ToEqual(5); + Expect(data1[2]).ToEqual(1); }); - it("Can find in AddUniqueSorted", [&]() + It("Can find in AddUniqueSorted", []() { TArray data{1, 5, 5, 34}; - AssertThat(data.AddUniqueSorted(1), Equals(0)); - AssertThat(data.AddUniqueSorted(5), Equals(1)); - AssertThat(data.AddUniqueSorted(34), Equals(3)); - AssertThat(data.Size(), Equals(4)); + Expect(data.AddUniqueSorted(1)).ToEqual(0); + Expect(data.AddUniqueSorted(5)).ToEqual(1); + Expect(data.AddUniqueSorted(34)).ToEqual(3); + Expect(data.Size()).ToEqual(4); }); - it("Can add in AddUniqueSorted", [&]() + It("Can add in AddUniqueSorted", []() { TArray data{1, 5, 5, 34}; - AssertThat(data.AddUniqueSorted(2), Equals(1)); - AssertThat(data.Size(), Equals(5)); + Expect(data.AddUniqueSorted(2)).ToEqual(1); + Expect(data.Size()).ToEqual(5); - AssertThat(data.AddUniqueSorted(6), Equals(4)); - AssertThat(data.Size(), Equals(6)); + Expect(data.AddUniqueSorted(6)).ToEqual(4); + Expect(data.Size()).ToEqual(6); - AssertThat(data.AddUniqueSorted(36), Equals(6)); - AssertThat(data.Size(), Equals(7)); + Expect(data.AddUniqueSorted(36)).ToEqual(6); + Expect(data.Size()).ToEqual(7); }); - it("Can slice", [&]() + It("Can slice", []() { TArray data{1, 2, 3, 4, 5}; auto mid = data.Slice(1, 2); // Elements 1 to 3 - AssertThat(mid.Size(), Equals(2)); - AssertThat(mid[0], Equals(2)); - AssertThat(mid[1], Equals(3)); + Expect(mid.Size()).ToEqual(2); + Expect(mid[0]).ToEqual(2); + Expect(mid[1]).ToEqual(3); auto tail = data.Slice(3, 100); // Clamped to available elements - AssertThat(tail.Size(), Equals(2)); - AssertThat(tail[0], Equals(4)); - AssertThat(tail[1], Equals(5)); + Expect(tail.Size()).ToEqual(2); + Expect(tail[0]).ToEqual(4); + Expect(tail[1]).ToEqual(5); auto none = data.Slice(2, 0); // Zero length - AssertThat(none.IsEmpty(), Is().True()); + Expect(none.IsEmpty()).ToBeTrue(); auto end = data.Slice(5, 2); // Offset clamped to size - AssertThat(end.IsEmpty(), Is().True()); + Expect(end.IsEmpty()).ToBeTrue(); }); - it("Can slice views", [&]() + It("Can slice views", []() { TArray data{1, 2, 3, 4, 5}; TView view = data; auto mid = view.Slice(2, 2); // Elements 2 to 4 - AssertThat(mid.Size(), Equals(2)); - AssertThat(mid[0], Equals(3)); - AssertThat(mid[1], Equals(4)); + Expect(mid.Size()).ToEqual(2); + Expect(mid[0]).ToEqual(3); + Expect(mid[1]).ToEqual(4); auto head = view.Slice(0, 3); - AssertThat(head.Size(), Equals(3)); - AssertThat(head[0], Equals(1)); - AssertThat(head[2], Equals(3)); + Expect(head.Size()).ToEqual(3); + Expect(head[0]).ToEqual(1); + Expect(head[2]).ToEqual(3); }); - describe("Iterate", []() + Describe("Iterate", []() { - it("Can iterate empty", [&]() + It("Can iterate empty", []() { TArray data1{}; i32 counter = 0; @@ -932,43 +928,43 @@ go_bandit([]() ++counter; } - AssertThat(counter, Equals(0)); + Expect(counter).ToEqual(0); TArray data2{}; // Without inline capacity counter = 0; for (i32 v : data2) { ++counter; } - AssertThat(counter, Equals(0)); + Expect(counter).ToEqual(0); }); - it("Can iterate non empty", [&]() + It("Can iterate non empty", []() { TArray data1{1, 3, 4}; const i32 mirror[]{1, 3, 4}; i32 counter = 0; for (i32 v : data1) { - AssertThat(v, Equals(mirror[counter])); + Expect(v).ToEqual(mirror[counter]); ++counter; } - AssertThat(counter, Equals(3)); + Expect(counter).ToEqual(3); TArray data2{1, 3, 4}; // Without inline capacity counter = 0; for (i32 v : data2) { - AssertThat(v, Equals(mirror[counter])); + Expect(v).ToEqual(mirror[counter]); ++counter; } - AssertThat(counter, Equals(3)); + Expect(counter).ToEqual(3); }); }); }); - describe("Containers.BitArray", []() + Describe("Containers.BitArray", []() { - it("Can initialize", [&]() + It("Can initialize", []() { BitArray data1{}; BitArray data2(3); @@ -976,96 +972,96 @@ go_bandit([]() BitArray data4(91, true); BitArray data5{false, true, false, true, false, true}; - AssertThat(data1.Size(), Equals(0)); - AssertThat(data1.Capacity(), Equals(0)); - AssertThat(data2.Size(), Equals(3)); - AssertThat(data2.Capacity(), Equals(32)); - AssertThat(data3.Size(), Equals(3)); - AssertThat(data3.Capacity(), Equals(32)); - AssertThat(data4.Size(), Equals(91)); - AssertThat(data4.Capacity(), Equals(96)); - AssertThat(data5.Size(), Equals(6)); - AssertThat(data5.Capacity(), Equals(32)); - - AssertThat(data2[0], Equals(false)); - AssertThat(data2[2], Equals(false)); - AssertThat(data3[0], Equals(true)); - AssertThat(data3[2], Equals(true)); - AssertThat(data4[0], Equals(true)); - AssertThat(data4[90], Equals(true)); - AssertThat(data5[0], Equals(false)); - AssertThat(data5[1], Equals(true)); - AssertThat(data5[2], Equals(false)); - AssertThat(data5[3], Equals(true)); - AssertThat(data5[4], Equals(false)); - AssertThat(data5[5], Equals(true)); + Expect(data1.Size()).ToEqual(0); + Expect(data1.Capacity()).ToEqual(0); + Expect(data2.Size()).ToEqual(3); + Expect(data2.Capacity()).ToEqual(32); + Expect(data3.Size()).ToEqual(3); + Expect(data3.Capacity()).ToEqual(32); + Expect(data4.Size()).ToEqual(91); + Expect(data4.Capacity()).ToEqual(96); + Expect(data5.Size()).ToEqual(6); + Expect(data5.Capacity()).ToEqual(32); + + Expect(data2[0]).ToEqual(false); + Expect(data2[2]).ToEqual(false); + Expect(data3[0]).ToEqual(true); + Expect(data3[2]).ToEqual(true); + Expect(data4[0]).ToEqual(true); + Expect(data4[90]).ToEqual(true); + Expect(data5[0]).ToEqual(false); + Expect(data5[1]).ToEqual(true); + Expect(data5[2]).ToEqual(false); + Expect(data5[3]).ToEqual(true); + Expect(data5[4]).ToEqual(false); + Expect(data5[5]).ToEqual(true); }); - describe("Copy", []() + Describe("Copy", []() { - it("Can copy empty", [&]() + It("Can copy empty", []() { BitArray source1{}; BitArray target1 = source1; // NOLINT - AssertThat(target1.Data(), Equals(nullptr)); - AssertThat(target1.Size(), Equals(0)); - AssertThat(target1.Capacity(), Equals(0)); + Expect(target1.Data()).ToEqual(nullptr); + Expect(target1.Size()).ToEqual(0); + Expect(target1.Capacity()).ToEqual(0); BitArray source2{}; BitArray target2 = source2; // NOLINT - AssertThat(target2.Data(), Equals(nullptr)); - AssertThat(target2.Size(), Equals(0)); - AssertThat(target2.Capacity(), Equals(0)); + Expect(target2.Data()).ToEqual(nullptr); + Expect(target2.Size()).ToEqual(0); + Expect(target2.Capacity()).ToEqual(0); }); - it("Can copy", [&]() + It("Can copy", []() { BitArray source{false, true, false, true, false, true}; BitArray target = source; - AssertThat(source.Size(), Equals(6)); - AssertThat(source.Capacity(), IsGreaterThanOrEqualTo(6)); - AssertThat(target.Size(), Equals(6)); - AssertThat(target.Capacity(), IsGreaterThanOrEqualTo(6)); - AssertThat(target[1], Equals(true)); - AssertThat(target[2], Equals(false)); - AssertThat(target[3], Equals(true)); - AssertThat(source.Data(), !Equals(nullptr)); - AssertThat(target.Data(), !Equals(nullptr)); + Expect(source.Size()).ToEqual(6); + Expect(source.Capacity()).ToBeGreaterOrEqual(6); + Expect(target.Size()).ToEqual(6); + Expect(target.Capacity()).ToBeGreaterOrEqual(6); + Expect(target[1]).ToEqual(true); + Expect(target[2]).ToEqual(false); + Expect(target[3]).ToEqual(true); + Expect(source.Data()).ToNotEqual(nullptr); + Expect(target.Data()).ToNotEqual(nullptr); }); }); - describe("Move", []() + Describe("Move", []() { - it("Can move empty", [&]() + It("Can move empty", []() { BitArray source1{}; BitArray target1 = Move(source1); - AssertThat(target1.Data(), Equals(nullptr)); - AssertThat(target1.Size(), Equals(0)); - AssertThat(target1.Capacity(), Equals(0)); + Expect(target1.Data()).ToEqual(nullptr); + Expect(target1.Size()).ToEqual(0); + Expect(target1.Capacity()).ToEqual(0); BitArray source2{}; BitArray target2 = Move(source2); - AssertThat(target2.Data(), Equals(nullptr)); - AssertThat(target2.Size(), Equals(0)); - AssertThat(target2.Capacity(), Equals(0)); + Expect(target2.Data()).ToEqual(nullptr); + Expect(target2.Size()).ToEqual(0); + Expect(target2.Capacity()).ToEqual(0); }); - it("Can move", [&]() + It("Can move", []() { BitArray source{false, true, false, true, false, true}; u32* sourceData = source.Data(); BitArray target = Move(source); - AssertThat(source.Size(), Equals(0)); - AssertThat(source.Capacity(), Equals(0)); - AssertThat(target.Size(), Equals(6)); - AssertThat(target.Capacity(), IsGreaterThanOrEqualTo(6)); - AssertThat(target[1], Equals(true)); - AssertThat(target[2], Equals(false)); - AssertThat(target[3], Equals(true)); - AssertThat(source.Data(), Equals(nullptr)); - AssertThat(target.Data(), Equals(sourceData)); + Expect(source.Size()).ToEqual(0); + Expect(source.Capacity()).ToEqual(0); + Expect(target.Size()).ToEqual(6); + Expect(target.Capacity()).ToBeGreaterOrEqual(6); + Expect(target[1]).ToEqual(true); + Expect(target[2]).ToEqual(false); + Expect(target[3]).ToEqual(true); + Expect(source.Data()).ToEqual(nullptr); + Expect(target.Data()).ToEqual(sourceData); }); - it("Can bitwise operate", [&]() + It("Can bitwise operate", []() { BitArray a{true, true, false, false}; BitArray b{true, false, true, false}; @@ -1076,65 +1072,65 @@ go_bandit([]() const BitArray negged = ~a; // a & b: only bit 0 is set in both - AssertThat(anded.IsSet(0), Is().True()); - AssertThat(anded.IsSet(1), Is().False()); - AssertThat(anded.IsSet(2), Is().False()); - AssertThat(anded.IsSet(3), Is().False()); + Expect(anded.IsSet(0)).ToBeTrue(); + Expect(anded.IsSet(1)).ToBeFalse(); + Expect(anded.IsSet(2)).ToBeFalse(); + Expect(anded.IsSet(3)).ToBeFalse(); // a | b: all bits set - AssertThat(ored.IsSet(0), Is().True()); - AssertThat(ored.IsSet(1), Is().True()); - AssertThat(ored.IsSet(2), Is().True()); - AssertThat(ored.IsSet(3), Is().False()); + Expect(ored.IsSet(0)).ToBeTrue(); + Expect(ored.IsSet(1)).ToBeTrue(); + Expect(ored.IsSet(2)).ToBeTrue(); + Expect(ored.IsSet(3)).ToBeFalse(); // a ^ b: bits 1 and 2 differ - AssertThat(xored.IsSet(0), Is().False()); - AssertThat(xored.IsSet(1), Is().True()); - AssertThat(xored.IsSet(2), Is().True()); - AssertThat(xored.IsSet(3), Is().False()); + Expect(xored.IsSet(0)).ToBeFalse(); + Expect(xored.IsSet(1)).ToBeTrue(); + Expect(xored.IsSet(2)).ToBeTrue(); + Expect(xored.IsSet(3)).ToBeFalse(); // ~a: all bits flipped - AssertThat(negged.IsSet(0), Is().False()); - AssertThat(negged.IsSet(1), Is().False()); - AssertThat(negged.IsSet(2), Is().True()); - AssertThat(negged.IsSet(3), Is().True()); + Expect(negged.IsSet(0)).ToBeFalse(); + Expect(negged.IsSet(1)).ToBeFalse(); + Expect(negged.IsSet(2)).ToBeTrue(); + Expect(negged.IsSet(3)).ToBeTrue(); // Compound operations BitArray compound = a; compound &= b; - AssertThat(compound.IsSet(0), Is().True()); - AssertThat(compound.IsSet(1), Is().False()); + Expect(compound.IsSet(0)).ToBeTrue(); + Expect(compound.IsSet(1)).ToBeFalse(); compound |= b; - AssertThat(compound.IsSet(2), Is().True()); + Expect(compound.IsSet(2)).ToBeTrue(); compound ^= b; - AssertThat(compound.IsSet(0), Is().False()); - AssertThat(compound.IsSet(2), Is().False()); + Expect(compound.IsSet(0)).ToBeFalse(); + Expect(compound.IsSet(2)).ToBeFalse(); }); - it("Can bitwise operate with different sizes", [&]() + It("Can bitwise operate with different sizes", []() { BitArray small{false}; BitArray big{true, true, true}; const BitArray anded = big & small; - AssertThat(anded.Size(), Equals(1)); - AssertThat(anded.IsSet(0), Is().False()); + Expect(anded.Size()).ToEqual(1); + Expect(anded.IsSet(0)).ToBeFalse(); const BitArray ored = big | small; - AssertThat(ored.Size(), Equals(1)); // Sized to the smallest operand - AssertThat(ored.IsSet(0), Is().True()); + Expect(ored.Size()).ToEqual(1); // Sized to the smallest operand + Expect(ored.IsSet(0)).ToBeTrue(); // Only whole words are operated on. Bits past the smallest word count // keep their value. Bits within a cleared word are cleared with it. BitArray large{false}; large.Resize(40, true); large &= small; // small has a single (zeroed) word - AssertThat(large.Size(), Equals(40)); - AssertThat(large.IsSet(0), Is().False()); - AssertThat(large.IsSet(31), Is().False()); // Same word as bit 0 - AssertThat(large.IsSet(32), Is().True()); // Next word, unaffected - AssertThat(large.IsSet(39), Is().True()); + Expect(large.Size()).ToEqual(40); + Expect(large.IsSet(0)).ToBeFalse(); + Expect(large.IsSet(31)).ToBeFalse(); // Same word as bit 0 + Expect(large.IsSet(32)).ToBeTrue(); // Next word, unaffected + Expect(large.IsSet(39)).ToBeTrue(); }); }); }); -}); +} diff --git a/Tests/Core/Function.spec.cpp b/Tests/Core/Function.spec.cpp index 19aa9768..9edb5fa3 100644 --- a/Tests/Core/Function.spec.cpp +++ b/Tests/Core/Function.spec.cpp @@ -1,12 +1,10 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include #include -using namespace snowhouse; -using namespace bandit; using namespace p; @@ -30,25 +28,25 @@ struct Foo inline bool Foo::called = false; -go_bandit([]() +void RegisterCoreFunctionTests() { - describe("Core.Function", []() + Spec("Core.Function", []() { - it("Can create empty", [&]() + It("Can create empty", []() { TFunction func{}; - AssertThat(func.IsBound(), Equals(false)); - AssertThat(bool(func), Equals(false)); + Expect(func.IsBound()).ToEqual(false); + Expect(bool(func)).ToEqual(false); }); - it("Can create from function", [&]() + It("Can create from function", []() { TFunction func{Foo::StaticFunc}; - AssertThat(func.IsBound(), Equals(true)); + Expect(func.IsBound()).ToEqual(true); }); - it("Can compare functions", [&]() + It("Can compare functions", []() { TFunction func1{Foo::StaticFunc}; TFunction func2{Foo::StaticFunc}; @@ -58,27 +56,27 @@ go_bandit([]() TFunction func5{Foo::OtherStaticFunc}; - AssertThat(func1 == func2, Equals(true)); - AssertThat(func1 == func3, Equals(true)); - AssertThat(func1 == func4, Equals(false)); - // AssertThat(func1 == func5, Equals(false)); + Expect(func1 == func2).ToEqual(true); + Expect(func1 == func3).ToEqual(true); + Expect(func1 == func4).ToEqual(false); + // Expect(func1 == func5).ToEqual(false); }); - it("Can call static functions", [&]() + It("Can call static functions", []() { TFunction func1{Foo::StaticFunc}; TFunction func2{&Foo::StaticFunc}; Foo::called = false; func1(); - AssertThat(Foo::called, Equals(true)); + Expect(Foo::called).ToEqual(true); Foo::called = false; func2(); - AssertThat(Foo::called, Equals(true)); + Expect(Foo::called).ToEqual(true); }); - it("Can call lambda functions", [&]() + It("Can call lambda functions", []() { static bool called; called = false; @@ -88,7 +86,7 @@ go_bandit([]() called = true; }; func(); - AssertThat(called, Equals(true)); + Expect(called).ToEqual(true); }); }); -}); +} diff --git a/Tests/Core/OwnPtr.spec.cpp b/Tests/Core/OwnPtr.spec.cpp index 0eac09a9..8fb2e6c1 100644 --- a/Tests/Core/OwnPtr.spec.cpp +++ b/Tests/Core/OwnPtr.spec.cpp @@ -1,11 +1,9 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include -using namespace snowhouse; -using namespace bandit; using namespace p; @@ -39,147 +37,147 @@ struct MockStruct using PtrBuilder = TestPtrBuilder; bool bCalledNew = false; - static bool bCalledDelete; + inline static bool bCalledDelete = false; }; -go_bandit([]() +void RegisterCoreOwnPtrTests() { - describe("Core.OwnPtr", []() + Spec("Core.OwnPtr", []() { - describe("Owner pointer", []() + Describe("Owner pointer", []() { - it("Can initialize to empty", [&]() + It("Can initialize to empty", []() { TOwnPtr ptr; - AssertThat(ptr.IsValid(), Equals(false)); - AssertThat(ptr.Get(), Equals(nullptr)); + Expect(ptr.IsValid()).ToEqual(false); + Expect(ptr.Get()).ToEqual(nullptr); }); - it("Can instantiate", [&]() + It("Can instantiate", []() { TOwnPtr ptr = MakeOwned(); - AssertThat(ptr.IsValid(), Equals(true)); - AssertThat(ptr.Get(), Is().Not().EqualTo(nullptr)); + Expect(ptr.IsValid()).ToEqual(true); + Expect(ptr.Get()).ToNotEqual(nullptr); }); - it("Owner can release", [&]() + It("Owner can release", []() { TOwnPtr owner = MakeOwned(); - AssertThat(owner.IsValid(), Equals(true)); + Expect(owner.IsValid()).ToEqual(true); owner.Delete(); - AssertThat(owner.IsValid(), Equals(false)); + Expect(owner.IsValid()).ToEqual(false); }); - it("Owner is released when destroyed", [&]() + It("Owner is released when destroyed", []() { TPtr ptr; { TOwnPtr owner = MakeOwned(); ptr = owner; - AssertThat(ptr.IsValid(), Equals(true)); + Expect(ptr.IsValid()).ToEqual(true); } - AssertThat(ptr.IsValid(), Equals(false)); + Expect(ptr.IsValid()).ToEqual(false); }); - describe("Ptr Builder", []() + Describe("Ptr Builder", []() { - it("Calls custom new", [&]() + It("Calls custom new", []() { auto owner = MakeOwned(); - AssertThat(owner->bCalledNew, Equals(true)); + Expect(owner->bCalledNew).ToEqual(true); }); - it("Calls custom delete", [&]() + It("Calls custom delete", []() { MockStruct::bCalledDelete = false; auto owner = MakeOwned(); - AssertThat(MockStruct::bCalledDelete, Equals(false)); + Expect(MockStruct::bCalledDelete).ToEqual(false); owner.Delete(); - AssertThat(MockStruct::bCalledDelete, Equals(true)); + Expect(MockStruct::bCalledDelete).ToEqual(true); }); }); }); - describe("Weak pointer", []() + Describe("Weak pointer", []() { - it("Can initialize to empty", [&]() + It("Can initialize to empty", []() { TPtr ptr; - AssertThat(ptr.IsValid(), Equals(false)); - AssertThat(ptr.Get(), Equals(nullptr)); + Expect(ptr.IsValid()).ToEqual(false); + Expect(ptr.Get()).ToEqual(nullptr); }); - it("Can initialize from owner", [&]() + It("Can initialize from owner", []() { TOwnPtr owner = MakeOwned(); TPtr ptr = owner; - AssertThat(ptr.IsValid(), Equals(true)); - AssertThat(ptr.Get(), Is().Not().EqualTo(nullptr)); + Expect(ptr.IsValid()).ToEqual(true); + Expect(ptr.Get()).ToNotEqual(nullptr); }); - it("Can copy from other weak", [&]() + It("Can copy from other weak", []() { TOwnPtr owner = MakeOwned(); auto* raw = owner.Get(); TPtr ptr = owner; TPtr ptr2 = ptr; - AssertThat(ptr2.IsValid(), Equals(true)); - AssertThat(ptr.Get(), Equals(raw)); - AssertThat(ptr2.Get(), Equals(raw)); + Expect(ptr2.IsValid()).ToEqual(true); + Expect(ptr.Get()).ToEqual(raw); + Expect(ptr2.Get()).ToEqual(raw); }); - it("Can move from other weak", [&]() + It("Can move from other weak", []() { TOwnPtr owner = MakeOwned(); auto* raw = owner.Get(); auto weak = owner.AsPtr(); auto movedWeak = Move(weak); - AssertThat(weak.IsValid(), Equals(false)); - AssertThat(movedWeak.IsValid(), Equals(true)); + Expect(weak.IsValid()).ToEqual(false); + Expect(movedWeak.IsValid()).ToEqual(true); - AssertThat(weak.Get(), Equals(nullptr)); - AssertThat(movedWeak.Get(), Equals(raw)); + Expect(weak.Get()).ToEqual(nullptr); + Expect(movedWeak.Get()).ToEqual(raw); }); - it("Ptr is null after IsValid() == false", [&]() + It("Ptr is null after IsValid() == false", []() { TOwnPtr owner = MakeOwned(); TPtr ptr = owner; owner.Delete(); - AssertThat(ptr.Get(), Is().Not().EqualTo(nullptr)); + Expect(ptr.Get()).ToNotEqual(nullptr); - AssertThat(ptr.IsValid(), Equals(false)); - AssertThat(ptr.Get(), Equals(nullptr)); + Expect(ptr.IsValid()).ToEqual(false); + Expect(ptr.Get()).ToEqual(nullptr); }); }); - describe("Comparisons", []() + Describe("Comparisons", []() { - it("Owner can equal Owner", [&]() + It("Owner can equal Owner", []() { auto owner = MakeOwned(); auto owner2 = MakeOwned(); TOwnPtr ownerEmpty; - AssertThat(owner == owner, Equals(true)); - AssertThat(owner == owner2, Equals(false)); - AssertThat(ownerEmpty == ownerEmpty, Equals(true)); - AssertThat(owner == ownerEmpty, Equals(false)); + Expect(owner == owner).ToEqual(true); + Expect(owner == owner2).ToEqual(false); + Expect(ownerEmpty == ownerEmpty).ToEqual(true); + Expect(owner == ownerEmpty).ToEqual(false); - AssertThat(owner != owner, Equals(false)); - AssertThat(owner != owner2, Equals(true)); - AssertThat(ownerEmpty != ownerEmpty, Equals(false)); - AssertThat(owner != ownerEmpty, Equals(true)); + Expect(owner != owner).ToEqual(false); + Expect(owner != owner2).ToEqual(true); + Expect(ownerEmpty != ownerEmpty).ToEqual(false); + Expect(owner != ownerEmpty).ToEqual(true); }); - it("Owner can equal Weak", [&]() + It("Owner can equal Weak", []() { auto owner = MakeOwned(); auto owner2 = MakeOwned(); @@ -187,18 +185,18 @@ go_bandit([]() TOwnPtr ownerEmpty; TPtr weakEmpty; - AssertThat(owner == weak, Equals(true)); - AssertThat(owner2 == weak, Equals(false)); - AssertThat(ownerEmpty == weak, Equals(false)); - AssertThat(ownerEmpty == weakEmpty, Equals(true)); + Expect(owner == weak).ToEqual(true); + Expect(owner2 == weak).ToEqual(false); + Expect(ownerEmpty == weak).ToEqual(false); + Expect(ownerEmpty == weakEmpty).ToEqual(true); - AssertThat(owner != weak, Equals(false)); - AssertThat(owner2 != weak, Equals(true)); - AssertThat(ownerEmpty != weak, Equals(true)); - AssertThat(ownerEmpty != weakEmpty, Equals(false)); + Expect(owner != weak).ToEqual(false); + Expect(owner2 != weak).ToEqual(true); + Expect(ownerEmpty != weak).ToEqual(true); + Expect(ownerEmpty != weakEmpty).ToEqual(false); }); - it("Weak can equal Weak", [&]() + It("Weak can equal Weak", []() { auto owner = MakeOwned(); auto owner2 = MakeOwned(); @@ -206,18 +204,18 @@ go_bandit([]() auto weak2 = owner2.AsPtr(); TPtr weakEmpty; - AssertThat(weak == weak, Equals(true)); - AssertThat(weak2 == weak, Equals(false)); - AssertThat(weakEmpty == weak, Equals(false)); - AssertThat(weakEmpty == weakEmpty, Equals(true)); + Expect(weak == weak).ToEqual(true); + Expect(weak2 == weak).ToEqual(false); + Expect(weakEmpty == weak).ToEqual(false); + Expect(weakEmpty == weakEmpty).ToEqual(true); - AssertThat(weak != weak, Equals(false)); - AssertThat(weak2 != weak, Equals(true)); - AssertThat(weakEmpty != weak, Equals(true)); - AssertThat(weakEmpty != weakEmpty, Equals(false)); + Expect(weak != weak).ToEqual(false); + Expect(weak2 != weak).ToEqual(true); + Expect(weakEmpty != weak).ToEqual(true); + Expect(weakEmpty != weakEmpty).ToEqual(false); }); - it("Weak can equal Owner", [&]() + It("Weak can equal Owner", []() { auto owner = MakeOwned(); auto owner2 = MakeOwned(); @@ -226,124 +224,122 @@ go_bandit([]() TOwnPtr ownerEmpty; TPtr weakEmpty; - AssertThat(weak == owner, Equals(true)); - AssertThat(weak2 == owner, Equals(false)); - AssertThat(weakEmpty == owner, Equals(false)); - AssertThat(weakEmpty == ownerEmpty, Equals(true)); + Expect(weak == owner).ToEqual(true); + Expect(weak2 == owner).ToEqual(false); + Expect(weakEmpty == owner).ToEqual(false); + Expect(weakEmpty == ownerEmpty).ToEqual(true); - AssertThat(weak != owner, Equals(false)); - AssertThat(weak2 != owner, Equals(true)); - AssertThat(weakEmpty != owner, Equals(true)); - AssertThat(weakEmpty != ownerEmpty, Equals(false)); + Expect(weak != owner).ToEqual(false); + Expect(weak2 != owner).ToEqual(true); + Expect(weakEmpty != owner).ToEqual(true); + Expect(weakEmpty != ownerEmpty).ToEqual(false); }); }); - describe("Counter", []() + Describe("Counter", []() { - it("Adds weaks", [&]() + It("Adds weaks", []() { auto owner = MakeOwned(); const auto* counter = owner.GetCounter(); - AssertThat(counter->weakCount, Equals(0u)); + Expect(counter->weakCount).ToEqual(0u); auto weak = owner.AsPtr(); - AssertThat(counter->weakCount, Equals(1u)); + Expect(counter->weakCount).ToEqual(1u); }); - it("Removes weaks", [&]() + It("Removes weaks", []() { auto owner = MakeOwned(); const auto* counter = owner.GetCounter(); { auto weak = owner.AsPtr(); - AssertThat(counter->weakCount, Equals(1u)); + Expect(counter->weakCount).ToEqual(1u); } - AssertThat(counter->weakCount, Equals(0u)); + Expect(counter->weakCount).ToEqual(0u); }); - it("Removes with owner release", [&]() + It("Removes with owner release", []() { auto owner = MakeOwned(); - AssertThat(owner.GetCounter(), Is().Not().EqualTo(nullptr)); + Expect(owner.GetCounter()).ToNotEqual(nullptr); owner.Delete(); - AssertThat(owner.GetCounter(), Equals(nullptr)); + Expect(owner.GetCounter()).ToEqual(nullptr); }); - it("Removes with no weakCount left", [&]() + It("Removes with no weakCount left", []() { auto owner = MakeOwned(); auto weak = owner.AsPtr(); - AssertThat(weak.GetCounter(), Is().Not().EqualTo(nullptr)); + Expect(weak.GetCounter()).ToNotEqual(nullptr); owner.Delete(); - AssertThat(weak.GetCounter(), Is().Not().EqualTo(nullptr)); + Expect(weak.GetCounter()).ToNotEqual(nullptr); weak.Reset(); - AssertThat(owner.GetCounter(), Equals(nullptr)); + Expect(owner.GetCounter()).ToEqual(nullptr); }); }); - it("Can detect custom PtrBuilders", [&]() + It("Can detect custom PtrBuilders", []() { - AssertThat(p::HasCustomPtrBuilder::value, Equals(false)); - AssertThat(p::HasCustomPtrBuilder::value, Equals(true)); + Expect(p::HasCustomPtrBuilder::value).ToEqual(false); + Expect(p::HasCustomPtrBuilder::value).ToEqual(true); }); - describe("Typeless pointer", []() + Describe("Typeless pointer", []() { - it("Can convert to OwnPtr from TOwnPtr", [&]() + It("Can convert to OwnPtr from TOwnPtr", []() { TOwnPtr typedPtr = MakeOwned(); - AssertThat(typedPtr.IsValid(), Equals(true)); + Expect(typedPtr.IsValid()).ToEqual(true); EmptyStruct* data = typedPtr.Get(); OwnPtr ptr = Move(typedPtr); - AssertThat(typedPtr.IsValid(), Equals(false)); - AssertThat(ptr.IsValid(), Equals(true)); - AssertThat(ptr.Get(), Equals(data)); - AssertThat(ptr.Get(), Equals(data)); + Expect(typedPtr.IsValid()).ToEqual(false); + Expect(ptr.IsValid()).ToEqual(true); + Expect(ptr.Get()).ToEqual(data); + Expect(ptr.Get()).ToEqual(data); }); - it("Can convert to TOwnPtr from OwnPtr", [&]() + It("Can convert to TOwnPtr from OwnPtr", []() { OwnPtr ptr = MakeOwned(); - AssertThat(ptr.IsValid(), Equals(true)); + Expect(ptr.IsValid()).ToEqual(true); auto* data = ptr.Get(); TOwnPtr typedPtr = Move(ptr); - AssertThat(ptr.IsValid(), Equals(false)); - AssertThat(typedPtr.IsValid(), Equals(true)); - AssertThat(typedPtr.Get(), Equals(data)); + Expect(ptr.IsValid()).ToEqual(false); + Expect(typedPtr.IsValid()).ToEqual(true); + Expect(typedPtr.Get()).ToEqual(data); }); - it("Can move", [&]() + It("Can move", []() { OwnPtr ptr1 = MakeOwned(); - AssertThat(ptr1.IsValid(), Equals(true)); - AssertThat(ptr1.GetId(), Equals(GetTypeId())); + Expect(ptr1.IsValid()).ToEqual(true); + Expect(ptr1.GetId()).ToEqual(GetTypeId()); auto* data = ptr1.Get(); OwnPtr ptr2 = Move(ptr1); - AssertThat(ptr1.IsValid(), Equals(false)); - AssertThat(ptr1.Get(), Equals(nullptr)); - AssertThat(ptr1.GetId(), Equals(TypeId::None())); + Expect(ptr1.IsValid()).ToEqual(false); + Expect(ptr1.Get()).ToEqual(nullptr); + Expect(ptr1.GetId()).ToEqual(TypeId::None()); - AssertThat(ptr2.IsValid(), Equals(true)); - AssertThat(ptr2.Get(), Equals(data)); - AssertThat(ptr2.GetId(), Equals(GetTypeId())); + Expect(ptr2.IsValid()).ToEqual(true); + Expect(ptr2.Get()).ToEqual(data); + Expect(ptr2.GetId()).ToEqual(GetTypeId()); }); - it("Cant retrive invalid types", [&]() + It("Cant retrive invalid types", []() { OwnPtr ptr = MakeOwned(); - AssertThat(ptr.Get(), !Equals(nullptr)); - AssertThat(ptr.Get(), Equals(nullptr)); + Expect(ptr.Get()).ToNotEqual(nullptr); + Expect(ptr.Get()).ToEqual(nullptr); }); }); }); -}); - -inline bool MockStruct::bCalledDelete = false; +} diff --git a/Tests/Core/PageBuffer.spec.cpp b/Tests/Core/PageBuffer.spec.cpp index 30349489..f5dc1fa0 100644 --- a/Tests/Core/PageBuffer.spec.cpp +++ b/Tests/Core/PageBuffer.spec.cpp @@ -2,12 +2,10 @@ #include "PipeMemory.h" -#include +#include #include -using namespace snowhouse; -using namespace bandit; using namespace p; @@ -27,52 +25,52 @@ struct Dummy }; -go_bandit([]() +void RegisterCorePageBufferTests() { - describe("ECS.PageBuffer", []() + Spec("ECS.PageBuffer", []() { - it("Can reserve", [&]() + It("Can reserve", []() { TPageBuffer buffer{GetCurrentArena()}; - AssertThat(buffer.GetPagesSize(), Equals(0)); - AssertThat(buffer.Capacity(), Equals(0)); + Expect(buffer.GetPagesSize()).ToEqual(0); + Expect(buffer.Capacity()).ToEqual(0); buffer.Reserve(2); - AssertThat(buffer.GetPagesSize(), Equals(1)); - AssertThat(buffer.Capacity(), Equals(2)); + Expect(buffer.GetPagesSize()).ToEqual(1); + Expect(buffer.Capacity()).ToEqual(2); buffer.Reserve(6); - AssertThat(buffer.GetPagesSize(), Equals(3)); - AssertThat(buffer.Capacity(), Equals(6)); + Expect(buffer.GetPagesSize()).ToEqual(3); + Expect(buffer.Capacity()).ToEqual(6); }); - it("Can shrink", [&]() + It("Can shrink", []() { TPageBuffer buffer{GetCurrentArena()}; buffer.Reserve(7); - AssertThat(buffer.GetPagesSize(), Equals(4)); + Expect(buffer.GetPagesSize()).ToEqual(4); buffer.Shrink(4); - AssertThat(buffer.GetPagesSize(), Equals(2)); - AssertThat(buffer.Capacity(), Equals(4)); + Expect(buffer.GetPagesSize()).ToEqual(2); + Expect(buffer.Capacity()).ToEqual(4); }); - it("Can insert", [&]() + It("Can insert", []() { TPageBuffer buffer{GetCurrentArena()}; buffer.Reserve(4); buffer.Insert(0); - AssertThat(buffer[0].created, Equals(true)); - AssertThat(buffer[0].destroyed, Equals(false)); + Expect(buffer[0].created).ToEqual(true); + Expect(buffer[0].destroyed).ToEqual(false); buffer.Insert(3); - AssertThat(buffer[3].created, Equals(true)); - AssertThat(buffer[3].destroyed, Equals(false)); + Expect(buffer[3].created).ToEqual(true); + Expect(buffer[3].destroyed).ToEqual(false); }); - it("Can remove", [&]() + It("Can remove", []() { TPageBuffer buffer{GetCurrentArena()}; buffer.Reserve(4); @@ -82,30 +80,30 @@ go_bandit([]() buffer.RemoveAt(0); // Temporarily disabled due to GCC only test fail - // AssertThat(buffer[0].destroyed, Equals(true)); + // Expect(buffer[0].destroyed).ToEqual(true); buffer.RemoveAt(3); // Temporarily disabled due to GCC only test fail - // AssertThat(buffer[3].destroyed, Equals(true)); + // Expect(buffer[3].destroyed).ToEqual(true); }); - it("Points to correct page", [&]() + It("Points to correct page", []() { TPageBuffer buffer{GetCurrentArena()}; buffer.Reserve(7); buffer.AssurePage(0); - AssertThat(buffer.AssurePage(0), !Equals(nullptr)); - AssertThat(buffer.AssurePage(1), !Equals(nullptr)); - AssertThat(buffer.AssurePage(2), !Equals(nullptr)); - AssertThat(buffer.AssurePage(5), !Equals(nullptr)); + Expect(buffer.AssurePage(0)).ToNotEqual(nullptr); + Expect(buffer.AssurePage(1)).ToNotEqual(nullptr); + Expect(buffer.AssurePage(2)).ToNotEqual(nullptr); + Expect(buffer.AssurePage(5)).ToNotEqual(nullptr); - AssertThat(buffer.FindPage(0), !Equals(nullptr)); - AssertThat(buffer.FindPage(1), !Equals(nullptr)); - AssertThat(buffer.FindPage(2), !Equals(nullptr)); - AssertThat(buffer.FindPage(5), !Equals(nullptr)); - AssertThat(buffer.FindPage(6), Equals(nullptr)); + Expect(buffer.FindPage(0)).ToNotEqual(nullptr); + Expect(buffer.FindPage(1)).ToNotEqual(nullptr); + Expect(buffer.FindPage(2)).ToNotEqual(nullptr); + Expect(buffer.FindPage(5)).ToNotEqual(nullptr); + Expect(buffer.FindPage(6)).ToEqual(nullptr); }); }); -}); +} diff --git a/Tests/Core/PlatformProcess.spec.cpp b/Tests/Core/PlatformProcess.spec.cpp index 88a051b0..56785554 100644 --- a/Tests/Core/PlatformProcess.spec.cpp +++ b/Tests/Core/PlatformProcess.spec.cpp @@ -3,26 +3,24 @@ #include "Pipe/Core/Log.h" #include "Pipe/Core/Subprocess.h" -#include +#include #include -using namespace snowhouse; -using namespace bandit; using namespace p; -go_bandit([]() +void RegisterCorePlatformProcessTests() { - describe("Core.Subprocess", []() + Spec("Core.Subprocess", []() { - it("Can run process", [&]() + It("Can run process", []() { - AssertThat(p::RunProcess({""}).IsSet(), Equals(false)); + Expect(p::RunProcess({""}).IsSet()).ToEqual(false); -#if defined(_MSC_VER) // Test with a silent command (no stdout) - AssertThat(p::RunProcess({"cmd", "/c", "exit", "0"}).IsSet(), Equals(true)); -#endif + #if defined(_MSC_VER) // Test with a silent command (no stdout) + Expect(p::RunProcess({"cmd", "/c", "exit", "0"}).IsSet()).ToEqual(true); + #endif }); }); -}); +} diff --git a/Tests/Core/Set.spec.cpp b/Tests/Core/Set.spec.cpp index 1fd37138..3ac74001 100644 --- a/Tests/Core/Set.spec.cpp +++ b/Tests/Core/Set.spec.cpp @@ -1,11 +1,9 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include -using namespace snowhouse; -using namespace bandit; using namespace p; template @@ -16,79 +14,79 @@ struct TypeOfSize }; -go_bandit([]() +void RegisterCoreSetTests() { - describe("Core.Set", []() + Spec("Core.Set", []() { - it("Can initialize", [&]() + It("Can initialize", []() { TSet data1{}; TSet data2(u32(3)); TSet data3{5, 4, 3, 2}; - AssertThat(data1.Size(), Equals(0)); - AssertThat(data2.Size(), Equals(0)); - AssertThat(data3.Size(), Equals(4)); + Expect(data1.Size()).ToEqual(0); + Expect(data2.Size()).ToEqual(0); + Expect(data3.Size()).ToEqual(4); - AssertThat(data3[2], Equals(2)); - AssertThat(data3[3], Equals(3)); - AssertThat(data3[4], Equals(4)); - AssertThat(data3[5], Equals(5)); + Expect(data3[2]).ToEqual(2); + Expect(data3[3]).ToEqual(3); + Expect(data3[4]).ToEqual(4); + Expect(data3[5]).ToEqual(5); }); - it("Can copy", [&]() + It("Can copy", []() { TSet data1{6, 5}; TSet data2{data1}; - AssertThat(data1.Size(), Equals(2)); - AssertThat(data2.Size(), Equals(2)); - AssertThat(data2[5], Equals(5)); - AssertThat(data2[6], Equals(6)); + Expect(data1.Size()).ToEqual(2); + Expect(data2.Size()).ToEqual(2); + Expect(data2[5]).ToEqual(5); + Expect(data2[6]).ToEqual(6); TSet data3{6, 5}; TSet data4; data4 = data3; - AssertThat(data3.Size(), Equals(2)); - AssertThat(data4.Size(), Equals(2)); - AssertThat(data4[5], Equals(5)); - AssertThat(data4[6], Equals(6)); + Expect(data3.Size()).ToEqual(2); + Expect(data4.Size()).ToEqual(2); + Expect(data4[5]).ToEqual(5); + Expect(data4[6]).ToEqual(6); }); - it("Can move", [&]() + It("Can move", []() { TSet data1{4, 3}; - AssertThat(data1.Size(), Equals(2)); + Expect(data1.Size()).ToEqual(2); TSet data2{Move(data1)}; - AssertThat(data1.Size(), Equals(0)); - AssertThat(data2.Size(), Equals(2)); + Expect(data1.Size()).ToEqual(0); + Expect(data2.Size()).ToEqual(2); TSet data3{4, 3}; TSet data4; - AssertThat(data3.Size(), Equals(2)); - AssertThat(data4.Size(), Equals(0)); + Expect(data3.Size()).ToEqual(2); + Expect(data4.Size()).ToEqual(0); data4 = Move(data3); - AssertThat(data3.Size(), Equals(0)); - AssertThat(data4.Size(), Equals(2)); - AssertThat(data4[3], Equals(3)); - AssertThat(data4[4], Equals(4)); + Expect(data3.Size()).ToEqual(0); + Expect(data4.Size()).ToEqual(2); + Expect(data4[3]).ToEqual(3); + Expect(data4[4]).ToEqual(4); }); - it("Can access data", [&]() + It("Can access data", []() { TSet data1; TSet data2{1, 5}; - AssertThat(data1.Size(), Equals(0)); - AssertThat(data2.Size(), IsGreaterThanOrEqualTo(2)); + Expect(data1.Size()).ToEqual(0); + Expect(data2.Size()).ToBeGreaterOrEqual(2); - AssertThat(data1.Contains(3), Equals(false)); - AssertThat(data2.Contains(1), Equals(true)); - AssertThat(data2.Contains(5), Equals(true)); - AssertThat(data2.Contains(34), Equals(false)); - AssertThat(data2[1], Equals(1)); - AssertThat(data2[5], Equals(5)); + Expect(data1.Contains(3)).ToEqual(false); + Expect(data2.Contains(1)).ToEqual(true); + Expect(data2.Contains(5)).ToEqual(true); + Expect(data2.Contains(34)).ToEqual(false); + Expect(data2[1]).ToEqual(1); + Expect(data2[5]).ToEqual(5); }); }); -}); +} diff --git a/Tests/Core/SpinLock.spec.cpp b/Tests/Core/SpinLock.spec.cpp index 793fca38..061ed86d 100644 --- a/Tests/Core/SpinLock.spec.cpp +++ b/Tests/Core/SpinLock.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include #include @@ -8,27 +8,25 @@ #include -using namespace snowhouse; -using namespace bandit; using namespace p; -go_bandit([]() +void RegisterCoreSpinLockTests() { - describe("Core.SpinLock", []() + Spec("Core.SpinLock", []() { - describe("SpinLock", [&]() + Describe("SpinLock", []() { - it("Acquires and releases exclusively", [&]() + It("Acquires and releases exclusively", []() { SpinLock lock; ScopedLock guard(lock); - AssertThat(lock.Locked(), Is().True()); - AssertThat(lock.TryLock(), Is().False()); + Expect(lock.Locked()).ToBeTrue(); + Expect(lock.TryLock()).ToBeFalse(); }); - it("Allows serialized writers to increment a counter", [&]() + It("Allows serialized writers to increment a counter", []() { SpinLock lock; i32 counter = 0; @@ -58,37 +56,37 @@ go_bandit([]() thread.join(); } - AssertThat(counter, Is().EqualTo(kThreads * kPerThread)); + Expect(counter).ToEqual(kThreads * kPerThread); }); }); - describe("SharedSpinLock", [&]() + Describe("SharedSpinLock", []() { - it("Exclusive lock excludes a second exclusive lock", [&]() + It("Exclusive lock excludes a second exclusive lock", []() { SharedSpinLock lock; ExclusiveScopedLock writer(lock); - AssertThat(lock.TryLockExclusive(), Is().False()); + Expect(lock.TryLockExclusive()).ToBeFalse(); }); - it("Exclusive lock excludes shared locks", [&]() + It("Exclusive lock excludes shared locks", []() { SharedSpinLock lock; ExclusiveScopedLock writer(lock); - AssertThat(lock.TryLockShared(), Is().False()); + Expect(lock.TryLockShared()).ToBeFalse(); }); - it("Shared lock excludes an exclusive lock", [&]() + It("Shared lock excludes an exclusive lock", []() { SharedSpinLock lock; SharedScopedLock reader(lock); - AssertThat(lock.TryLockExclusive(), Is().False()); + Expect(lock.TryLockExclusive()).ToBeFalse(); }); - it("Allows multiple overlapping shared locks", [&]() + It("Allows multiple overlapping shared locks", []() { SharedSpinLock lock; @@ -97,21 +95,21 @@ go_bandit([]() SharedScopedLock r3(lock); // Readers coexist: shared still acquirable. - AssertThat(lock.TryLockShared(), Is().True()); + Expect(lock.TryLockShared()).ToBeTrue(); lock.UnlockShared(); - AssertThat(lock.TryLockExclusive(), Is().False()); + Expect(lock.TryLockExclusive()).ToBeFalse(); }); - it("Writers exclude each other", [&]() + It("Writers exclude each other", []() { SharedSpinLock lock; ExclusiveScopedLock w1(lock); - AssertThat(lock.TryLockExclusive(), Is().False()); + Expect(lock.TryLockExclusive()).ToBeFalse(); }); - it("Writes under exclusive lock are mutually excluded", [&]() + It("Writes under exclusive lock are mutually excluded", []() { SharedSpinLock lock; i32 counter = 0; @@ -141,10 +139,10 @@ go_bandit([]() thread.join(); } - AssertThat(counter, Is().EqualTo(kThreads * kPerThread)); + Expect(counter).ToEqual(kThreads * kPerThread); }); - it("Shared readers run concurrently without tearing shared state", [&]() + It("Shared readers run concurrently without tearing shared state", []() { SharedSpinLock lock; i32 value = 0; @@ -180,8 +178,8 @@ go_bandit([]() thread.join(); } - AssertThat(reads.load(), Is().EqualTo(kThreads * kIterations)); + Expect(reads.load()).ToEqual(kThreads * kIterations); }); }); }); -}); +} diff --git a/Tests/Core/String.spec.cpp b/Tests/Core/String.spec.cpp index 70c0c500..33926b89 100644 --- a/Tests/Core/String.spec.cpp +++ b/Tests/Core/String.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include #include #include @@ -9,803 +9,803 @@ #include -using namespace snowhouse; -using namespace bandit; using namespace p; // Longer than the inline capacity, forcing heap allocations static const StringView longText = "0123456789ABCDEFGHIJ0123456789ABC"; +// Longer than the inline capacity, for arena allocation tests +static const char* arenaLongText = "This string is long enough to exceed the inline capacity"; -go_bandit([]() + +void RegisterCoreStringTests() { - describe("Strings", []() + Spec("Strings", []() { - describe("String", []() + Describe("String", []() { - describe("Construction", []() + Describe("Construction", []() { - it("Can default construct", [&]() + It("Can default construct", []() { String v{}; - AssertThat(v.size(), Equals(0u)); - AssertThat(v.empty(), Is().True()); - AssertThat(v.length(), Equals(0u)); + Expect(v.size()).ToEqual(0u); + Expect(v.empty()).ToBeTrue(); + Expect(v.length()).ToEqual(0u); // c_str() must always return a valid pointer to a null terminator - AssertThat(v.c_str() != nullptr, Is().True()); - AssertThat(v.c_str()[0], Equals('\0')); - AssertThat(v.data() != nullptr, Is().True()); - AssertThat(v.data()[0], Equals('\0')); + Expect(v.c_str() != nullptr).ToBeTrue(); + Expect(v.c_str()[0]).ToEqual('\0'); + Expect(v.data() != nullptr).ToBeTrue(); + Expect(v.data()[0]).ToEqual('\0'); }); - it("Can construct from literal", [&]() + It("Can construct from literal", []() { String v{"Kiwi"}; - AssertThat(v, Equals("Kiwi")); - AssertThat(v.size(), Equals(4u)); + Expect(v).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4u); }); - it("Can construct from literal with count", [&]() + It("Can construct from literal with count", []() { String v{"KiwiApple", 4}; - AssertThat(v, Equals("Kiwi")); - AssertThat(v.size(), Equals(4u)); + Expect(v).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4u); }); - it("Can construct from count and char", [&]() + It("Can construct from count and char", []() { String v(5, 'x'); - AssertThat(v, Equals("xxxxx")); - AssertThat(v.size(), Equals(5u)); + Expect(v).ToEqual("xxxxx"); + Expect(v.size()).ToEqual(5u); }); - it("Can construct from string view", [&]() + It("Can construct from string view", []() { StringView str{"Kiwi"}; String v{str}; - AssertThat(v, Equals("Kiwi")); - AssertThat(v.size(), Equals(4u)); + Expect(v).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4u); }); - it("Can construct from string view with pos and count", [&]() + It("Can construct from string view with pos and count", []() { StringView str{"KiwiApple"}; String v{str, 4, 5}; - AssertThat(v, Equals("Apple")); + Expect(v).ToEqual("Apple"); }); - it("Can construct from substring", [&]() + It("Can construct from substring", []() { String str{"KiwiApple"}; String v{str, 4}; - AssertThat(v, Equals("Apple")); + Expect(v).ToEqual("Apple"); String v2{str, 4, 3}; - AssertThat(v2, Equals("App")); + Expect(v2).ToEqual("App"); }); - it("Can construct from iterators", [&]() + It("Can construct from iterators", []() { std::string_view sv = "Kiwi"; String v{sv.begin(), sv.end()}; - AssertThat(v, Equals("Kiwi")); + Expect(v).ToEqual("Kiwi"); }); - it("Can construct from initializer list", [&]() + It("Can construct from initializer list", []() { String v{'K', 'i', 'w', 'i'}; - AssertThat(v, Equals("Kiwi")); + Expect(v).ToEqual("Kiwi"); }); - it("Can copy construct", [&]() + It("Can copy construct", []() { String v{"Kiwi"}; String v2{v}; - AssertThat(v2, Equals("Kiwi")); - AssertThat(v, Equals("Kiwi")); + Expect(v2).ToEqual("Kiwi"); + Expect(v).ToEqual("Kiwi"); }); - it("Can move construct", [&]() + It("Can move construct", []() { String v{"Kiwi"}; String v2{Move(v)}; - AssertThat(v2, Equals("Kiwi")); + Expect(v2).ToEqual("Kiwi"); // Moved-from string is valid and empty - AssertThat(v.size(), Equals(0u)); - AssertThat(v.empty(), Is().True()); - AssertThat(v.c_str()[0], Equals('\0')); + Expect(v.size()).ToEqual(0u); + Expect(v.empty()).ToBeTrue(); + Expect(v.c_str()[0]).ToEqual('\0'); }); }); - describe("Assignment", []() + Describe("Assignment", []() { - it("Can assign from literal", [&]() + It("Can assign from literal", []() { String v; v = "Kiwi"; - AssertThat(v, Equals("Kiwi")); + Expect(v).ToEqual("Kiwi"); }); - it("Can copy assign", [&]() + It("Can copy assign", []() { String vKiwi{"Kiwi"}; String vApple{"Apple"}; String vCopy = vKiwi; - AssertThat(vCopy, Equals("Kiwi")); + Expect(vCopy).ToEqual("Kiwi"); vCopy = vApple; - AssertThat(vCopy, Equals("Apple")); - AssertThat(vCopy, Equals(vApple)); + Expect(vCopy).ToEqual("Apple"); + Expect(vCopy).ToEqual(vApple); }); - it("Can move assign", [&]() + It("Can move assign", []() { String vKiwi{"Kiwi"}; String vApple{"Apple"}; String vMove = Move(vKiwi); - AssertThat(vKiwi.size(), Equals(0u)); - AssertThat(vMove, Equals("Kiwi")); + Expect(vKiwi.size()).ToEqual(0u); + Expect(vMove).ToEqual("Kiwi"); vMove = Move(vApple); - AssertThat(vApple.size(), Equals(0u)); - AssertThat(vMove, Equals("Apple")); + Expect(vApple.size()).ToEqual(0u); + Expect(vMove).ToEqual("Apple"); }); - it("Can assign char", [&]() + It("Can assign char", []() { String v; v = 'x'; - AssertThat(v, Equals("x")); + Expect(v).ToEqual("x"); }); - it("Can assign initializer list", [&]() + It("Can assign initializer list", []() { String v; v = {'K', 'i', 'w', 'i'}; - AssertThat(v, Equals("Kiwi")); + Expect(v).ToEqual("Kiwi"); }); - it("Can assign string view", [&]() + It("Can assign string view", []() { String v; StringView sv{"Kiwi"}; v = sv; - AssertThat(v, Equals("Kiwi")); + Expect(v).ToEqual("Kiwi"); }); - it("Can assign", [&]() + It("Can assign", []() { String v; v.assign("Kiwi"); - AssertThat(v, Equals("Kiwi")); + Expect(v).ToEqual("Kiwi"); v.assign("KiwiApple", 4); - AssertThat(v, Equals("Kiwi")); + Expect(v).ToEqual("Kiwi"); v.assign(3, 'x'); - AssertThat(v, Equals("xxx")); + Expect(v).ToEqual("xxx"); String other{"Apple"}; v.assign(other); - AssertThat(v, Equals("Apple")); + Expect(v).ToEqual("Apple"); v.assign(other, 2, 2); - AssertThat(v, Equals("pl")); + Expect(v).ToEqual("pl"); StringView sv{"KiwiApple"}; v.assign(sv, 4, 5); - AssertThat(v, Equals("Apple")); + Expect(v).ToEqual("Apple"); v.assign({'a', 'b', 'c'}); - AssertThat(v, Equals("abc")); + Expect(v).ToEqual("abc"); }); - it("Can self assign", [&]() + It("Can self assign", []() { String v{"Kiwi"}; const String& ref = v; v = ref; - AssertThat(v, Equals("Kiwi")); + Expect(v).ToEqual("Kiwi"); }); - it("Can self assign substrings", [&]() + It("Can self assign substrings", []() { String v{longText}; v.assign(v.c_str() + 10); - AssertThat(v, Equals("ABCDEFGHIJ0123456789ABC")); + Expect(v).ToEqual("ABCDEFGHIJ0123456789ABC"); }); - it("Can self assign substrings with count", [&]() + It("Can self assign substrings with count", []() { String v{longText}; v.assign(v.c_str() + 5, 10); - AssertThat(v, Equals("56789ABCDE")); + Expect(v).ToEqual("56789ABCDE"); }); }); - describe("Element access", []() + Describe("Element access", []() { - it("Can index", [&]() + It("Can index", []() { String v{"Kiwi"}; - AssertThat(v[0], Equals('K')); - AssertThat(v[3], Equals('i')); + Expect(v[0]).ToEqual('K'); + Expect(v[3]).ToEqual('i'); v[0] = 'k'; - AssertThat(v, Equals("kiwi")); + Expect(v).ToEqual("kiwi"); // pos == size() returns reference to null char - AssertThat(v[4], Equals('\0')); + Expect(v[4]).ToEqual('\0'); }); - it("Can access at", [&]() + It("Can access at", []() { String v{"Kiwi"}; - AssertThat(v.at(0), Equals('K')); - AssertThat(v.at(3), Equals('i')); + Expect(v.at(0)).ToEqual('K'); + Expect(v.at(3)).ToEqual('i'); v.at(0) = 'k'; - AssertThat(v, Equals("kiwi")); + Expect(v).ToEqual("kiwi"); }); - it("Can access front and back", [&]() + It("Can access front and back", []() { String v{"Kiwi"}; - AssertThat(v.front(), Equals('K')); - AssertThat(v.back(), Equals('i')); + Expect(v.front()).ToEqual('K'); + Expect(v.back()).ToEqual('i'); v.front() = 'P'; v.back() = 's'; - AssertThat(v, Equals("Piws")); + Expect(v).ToEqual("Piws"); }); - it("Can retrieve data", [&]() + It("Can retrieve data", []() { String v{"Kiwi"}; - AssertThat(v.data(), Equals("Kiwi")); - AssertThat(v.size(), Equals(4u)); - AssertThat(strlen(v.data()), Equals(4u)); + Expect(v.data()).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4u); + Expect(strlen(v.data())).ToEqual(4u); }); - it("Can convert to string view", [&]() + It("Can convert to string view", []() { String v{"Kiwi"}; StringView sv = v; - AssertThat(sv.size(), Equals(4u)); - AssertThat(sv, Equals(StringView{"Kiwi"})); + Expect(sv.size()).ToEqual(4u); + Expect(sv).ToEqual(StringView{"Kiwi"}); StringView wsv{v}; - AssertThat(wsv, Equals(StringView{"Kiwi"})); + Expect(wsv).ToEqual(StringView{"Kiwi"}); }); }); - describe("Iterators", []() + Describe("Iterators", []() { - it("Can iterate", [&]() + It("Can iterate", []() { String v{"Kiwi"}; u32 i = 0; for (char c : v) { - AssertThat(c, Equals("Kiwi"[i])); + Expect(c).ToEqual("Kiwi"[i]); ++i; } - AssertThat(i, Equals(4u)); + Expect(i).ToEqual(4u); }); - it("Can iterate const", [&]() + It("Can iterate const", []() { const String v{"Kiwi"}; u32 i = 0; for (char c : v) { - AssertThat(c, Equals("Kiwi"[i])); + Expect(c).ToEqual("Kiwi"[i]); ++i; } - AssertThat(i, Equals(4u)); + Expect(i).ToEqual(4u); }); - it("Can iterate manually", [&]() + It("Can iterate manually", []() { String v{"Kiwi"}; auto it = v.begin(); auto end = v.end(); - AssertThat(end - it, Equals(4)); - AssertThat(*it, Equals('K')); - AssertThat(it[2], Equals('w')); + Expect(end - it).ToEqual(4); + Expect(*it).ToEqual('K'); + Expect(it[2]).ToEqual('w'); ++it; - AssertThat(*it, Equals('i')); + Expect(*it).ToEqual('i'); it += 2; - AssertThat(*it, Equals('i')); + Expect(*it).ToEqual('i'); --it; - AssertThat(*it, Equals('w')); - AssertThat(it == v.begin() + 2, Is().True()); - AssertThat(it != v.begin(), Is().True()); + Expect(*it).ToEqual('w'); + Expect(it == v.begin() + 2).ToBeTrue(); + Expect(it != v.begin()).ToBeTrue(); }); - it("Can iterate reverse", [&]() + It("Can iterate reverse", []() { String v{"Kiwi"}; u32 i = 0; for (auto rit = v.rbegin(); rit != v.rend(); ++rit) { - AssertThat(*rit, Equals("Kiwi"[3 - i])); + Expect(*rit).ToEqual("Kiwi"[3 - i]); ++i; } - AssertThat(i, Equals(4u)); + Expect(i).ToEqual(4u); }); - it("Can iterate c-variants", [&]() + It("Can iterate c-variants", []() { String v{"Kiwi"}; - AssertThat(*v.cbegin(), Equals('K')); - AssertThat(*(v.cend() - 1), Equals('i')); - AssertThat(*v.crbegin(), Equals('i')); - AssertThat(*(v.crend() - 1), Equals('K')); + Expect(*v.cbegin()).ToEqual('K'); + Expect(*(v.cend() - 1)).ToEqual('i'); + Expect(*v.crbegin()).ToEqual('i'); + Expect(*(v.crend() - 1)).ToEqual('K'); }); - it("Can mutate through iterators", [&]() + It("Can mutate through iterators", []() { String v{"Kiwi"}; std::transform(v.begin(), v.end(), v.begin(), [](char c) { return char(c + 1); }); - AssertThat(v, Equals("Ljxj")); + Expect(v).ToEqual("Ljxj"); }); }); - describe("Capacity", []() + Describe("Capacity", []() { - it("Can query size and length", [&]() + It("Can query size and length", []() { String v{"Kiwi"}; - AssertThat(v.size(), Equals(4u)); - AssertThat(v.length(), Equals(4u)); - AssertThat(v.empty(), Is().False()); + Expect(v.size()).ToEqual(4u); + Expect(v.length()).ToEqual(4u); + Expect(v.empty()).ToBeFalse(); }); - it("Has short string optimization", [&]() + It("Has short string optimization", []() { String v{"Kiwi"}; // Short strings must fit in the internal buffer - AssertThat(v.capacity() >= 15u, Is().True()); - AssertThat(v.capacity() <= 32u, Is().True()); + Expect(v.capacity() >= 15u).ToBeTrue(); + Expect(v.capacity() <= 32u).ToBeTrue(); }); - it("Can reserve", [&]() + It("Can reserve", []() { String v; v.reserve(100); - AssertThat(v.capacity() >= 100u, Is().True()); - AssertThat(v.size(), Equals(0u)); + Expect(v.capacity() >= 100u).ToBeTrue(); + Expect(v.size()).ToEqual(0u); v = "Kiwi"; - AssertThat(v, Equals("Kiwi")); - AssertThat(v.capacity() >= 100u, Is().True()); + Expect(v).ToEqual("Kiwi"); + Expect(v.capacity() >= 100u).ToBeTrue(); }); - it("Can shrink to fit", [&]() + It("Can shrink to fit", []() { String v; v.reserve(100); v = "Kiwi"; v.shrink_to_fit(); - AssertThat(v, Equals("Kiwi")); - AssertThat(v.capacity() >= 4u, Is().True()); - AssertThat(v.capacity() < 100u, Is().True()); + Expect(v).ToEqual("Kiwi"); + Expect(v.capacity() >= 4u).ToBeTrue(); + Expect(v.capacity() < 100u).ToBeTrue(); }); - it("Has max size", [&]() + It("Has max size", []() { String v; // Lengths are stored internally as i32 - AssertThat(v.max_size(), Equals(sizet(Limits::Max() - 1))); + Expect(v.max_size()).ToEqual(sizet(Limits::Max() - 1)); }); }); - describe("Modifiers", []() + Describe("Modifiers", []() { - it("Can clear", [&]() + It("Can clear", []() { String v{"Kiwi"}; v.clear(); - AssertThat(v.empty(), Is().True()); - AssertThat(v.size(), Equals(0u)); - AssertThat(v.c_str()[0], Equals('\0')); + Expect(v.empty()).ToBeTrue(); + Expect(v.size()).ToEqual(0u); + Expect(v.c_str()[0]).ToEqual('\0'); }); - it("Can push and pop back", [&]() + It("Can push and pop back", []() { String v{"Ki"}; v.push_back('w'); v.push_back('i'); - AssertThat(v, Equals("Kiwi")); - AssertThat(v.back(), Equals('i')); + Expect(v).ToEqual("Kiwi"); + Expect(v.back()).ToEqual('i'); v.pop_back(); - AssertThat(v, Equals("Kiw")); + Expect(v).ToEqual("Kiw"); v.pop_back(); v.pop_back(); v.pop_back(); - AssertThat(v, Equals("")); - AssertThat(v.empty(), Is().True()); + Expect(v).ToEqual(""); + Expect(v.empty()).ToBeTrue(); }); - it("Can append", [&]() + It("Can append", []() { String v{"Kiwi"}; v.append("Apple"); - AssertThat(v, Equals("KiwiApple")); + Expect(v).ToEqual("KiwiApple"); v.append("Orange", 3); - AssertThat(v, Equals("KiwiAppleOra")); + Expect(v).ToEqual("KiwiAppleOra"); v.append(3, '-'); - AssertThat(v, Equals("KiwiAppleOra---")); + Expect(v).ToEqual("KiwiAppleOra---"); String other{"End"}; v.append(other); - AssertThat(v, Equals("KiwiAppleOra---End")); + Expect(v).ToEqual("KiwiAppleOra---End"); v.append(other, 1, 2); - AssertThat(v, Equals("KiwiAppleOra---Endnd")); + Expect(v).ToEqual("KiwiAppleOra---Endnd"); StringView sv{"View"}; v.append(sv); - AssertThat(v, Equals("KiwiAppleOra---EndndView")); + Expect(v).ToEqual("KiwiAppleOra---EndndView"); v.append(sv, 2, 2); - AssertThat(v, Equals("KiwiAppleOra---EndndViewew")); + Expect(v).ToEqual("KiwiAppleOra---EndndViewew"); v.append({'!', '?'}); - AssertThat(v, Equals("KiwiAppleOra---EndndViewew!?")); + Expect(v).ToEqual("KiwiAppleOra---EndndViewew!?"); }); - it("Can append with operator+=", [&]() + It("Can append with operator+=", []() { String v{"Kiwi"}; v += "Apple"; - AssertThat(v, Equals("KiwiApple")); + Expect(v).ToEqual("KiwiApple"); v += '!'; - AssertThat(v, Equals("KiwiApple!")); + Expect(v).ToEqual("KiwiApple!"); String other{"End"}; v += other; - AssertThat(v, Equals("KiwiApple!End")); + Expect(v).ToEqual("KiwiApple!End"); v += StringView{"View"}; - AssertThat(v, Equals("KiwiApple!EndView")); + Expect(v).ToEqual("KiwiApple!EndView"); v += {'a', 'b'}; - AssertThat(v, Equals("KiwiApple!EndViewab")); + Expect(v).ToEqual("KiwiApple!EndViewab"); }); - it("Can insert", [&]() + It("Can insert", []() { String v{"KiwiApple"}; v.insert(4, "Orange"); - AssertThat(v, Equals("KiwiOrangeApple")); + Expect(v).ToEqual("KiwiOrangeApple"); v.insert(0, "-"); - AssertThat(v, Equals("-KiwiOrangeApple")); + Expect(v).ToEqual("-KiwiOrangeApple"); v.insert(v.size(), "!"); - AssertThat(v, Equals("-KiwiOrangeApple!")); + Expect(v).ToEqual("-KiwiOrangeApple!"); v.insert(0, 3, '='); - AssertThat(v, Equals("===-KiwiOrangeApple!")); + Expect(v).ToEqual("===-KiwiOrangeApple!"); String other{"XX"}; v.insert(3, other); - AssertThat(v, Equals("===XX-KiwiOrangeApple!")); + Expect(v).ToEqual("===XX-KiwiOrangeApple!"); StringView sv{"YY"}; v.insert(5, sv); - AssertThat(v, Equals("===XXYY-KiwiOrangeApple!")); + Expect(v).ToEqual("===XXYY-KiwiOrangeApple!"); v.insert(0, 2, 'Z'); - AssertThat(v, Equals("ZZ===XXYY-KiwiOrangeApple!")); + Expect(v).ToEqual("ZZ===XXYY-KiwiOrangeApple!"); }); - it("Can insert with iterator", [&]() + It("Can insert with iterator", []() { String v{"Kiwi"}; auto it = v.insert(v.begin() + 2, '-'); - AssertThat(*it, Equals('-')); - AssertThat(v, Equals("Ki-wi")); + Expect(*it).ToEqual('-'); + Expect(v).ToEqual("Ki-wi"); v.insert(v.end(), 3, '!'); - AssertThat(v, Equals("Ki-wi!!!")); + Expect(v).ToEqual("Ki-wi!!!"); String other{"AB"}; v.insert(v.begin(), other.begin(), other.end()); - AssertThat(v, Equals("ABKi-wi!!!")); + Expect(v).ToEqual("ABKi-wi!!!"); v.insert(v.begin() + 2, {'x', 'y'}); - AssertThat(v, Equals("ABxyKi-wi!!!")); + Expect(v).ToEqual("ABxyKi-wi!!!"); }); - it("Can erase", [&]() + It("Can erase", []() { String v{"KiwiApple"}; v.erase(4, 5); - AssertThat(v, Equals("Kiwi")); + Expect(v).ToEqual("Kiwi"); v.erase(2); - AssertThat(v, Equals("Ki")); + Expect(v).ToEqual("Ki"); v.erase(0, 1); - AssertThat(v, Equals("i")); + Expect(v).ToEqual("i"); v.erase(0, 10); - AssertThat(v, Equals("")); + Expect(v).ToEqual(""); }); - it("Can erase with iterator", [&]() + It("Can erase with iterator", []() { String v{"Kiwi"}; auto it = v.erase(v.begin()); - AssertThat(*it, Equals('i')); - AssertThat(v, Equals("iwi")); + Expect(*it).ToEqual('i'); + Expect(v).ToEqual("iwi"); v.erase(v.begin() + 1, v.end()); - AssertThat(v, Equals("i")); + Expect(v).ToEqual("i"); }); - it("Can replace", [&]() + It("Can replace", []() { String v{"KiwiApple"}; v.replace(0, 4, "Orange"); - AssertThat(v, Equals("OrangeApple")); + Expect(v).ToEqual("OrangeApple"); v.replace(0, 6, "X"); - AssertThat(v, Equals("XApple")); + Expect(v).ToEqual("XApple"); v.replace(v.size() - 3, 3, "Z"); - AssertThat(v, Equals("XApZ")); + Expect(v).ToEqual("XApZ"); String other{"Kiwi"}; v.replace(0, 4, other); - AssertThat(v, Equals("Kiwi")); + Expect(v).ToEqual("Kiwi"); StringView sv{"Two"}; v.replace(0, 4, sv); - AssertThat(v, Equals("Two")); + Expect(v).ToEqual("Two"); v.replace(0, 3, 2, 'y'); - AssertThat(v, Equals("yy")); + Expect(v).ToEqual("yy"); }); - it("Can replace with iterators", [&]() + It("Can replace with iterators", []() { String v{"KiwiApple"}; v.replace(v.begin(), v.begin() + 4, "Orange"); - AssertThat(v, Equals("OrangeApple")); + Expect(v).ToEqual("OrangeApple"); }); - it("Can resize", [&]() + It("Can resize", []() { String v{"Kiwi"}; v.resize(2); - AssertThat(v, Equals("Ki")); + Expect(v).ToEqual("Ki"); v.resize(4); - AssertThat(v.size(), Equals(4u)); - AssertThat(v[2], Equals('\0')); - AssertThat(v[3], Equals('\0')); + Expect(v.size()).ToEqual(4u); + Expect(v[2]).ToEqual('\0'); + Expect(v[3]).ToEqual('\0'); v.resize(6, 'x'); - AssertThat(v[4], Equals('x')); - AssertThat(v[5], Equals('x')); - AssertThat(v.size(), Equals(6u)); + Expect(v[4]).ToEqual('x'); + Expect(v[5]).ToEqual('x'); + Expect(v.size()).ToEqual(6u); }); - it("Can swap", [&]() + It("Can swap", []() { String a{"Kiwi"}; String b{"Apple"}; a.swap(b); - AssertThat(a, Equals("Apple")); - AssertThat(b, Equals("Kiwi")); + Expect(a).ToEqual("Apple"); + Expect(b).ToEqual("Kiwi"); }); - it("Can append from self", [&]() + It("Can append from self", []() { String v{longText}; v.append(v.c_str()); - AssertThat(v, Equals(std::string{longText} + std::string{longText})); + Expect(v).ToEqual(std::string{longText} + std::string{longText}); }); - it("Can append self substring", [&]() + It("Can append self substring", []() { String v{longText}; v.append(v.c_str() + 5); - AssertThat(v, Equals(std::string{longText} + std::string{longText.substr(5)})); + Expect(v).ToEqual(std::string{longText} + std::string{longText.substr(5)}); }); - it("Can insert from self", [&]() + It("Can insert from self", []() { String v{longText}; v.insert(0, v.c_str()); - AssertThat(v, Equals(std::string{longText} + std::string{longText})); + Expect(v).ToEqual(std::string{longText} + std::string{longText}); }); - it("Can insert self substring", [&]() + It("Can insert self substring", []() { String v{longText}; v.insert(4, v.c_str() + 5); - AssertThat(v, - Equals(std::string{longText.substr(0, 4)} + std::string{longText.substr(5)} - + std::string{longText.substr(4)})); + Expect(v).ToEqual(std::string{longText.substr(0, 4)} + std::string{longText.substr(5)} + + std::string{longText.substr(4)}); }); - it("Can replace with self", [&]() + It("Can replace with self", []() { String v{longText}; v.replace(0, 4, v.c_str()); - AssertThat(v, Equals(std::string{longText} + std::string{longText.substr(4)})); + Expect(v).ToEqual(std::string{longText} + std::string{longText.substr(4)}); }); - it("Can replace self substring with count", [&]() + It("Can replace self substring with count", []() { String v{longText}; v.replace(5, 10, v.c_str() + 2, 5); - AssertThat(v, Equals(std::string{longText.substr(0, 5)} + "23456" - + std::string{longText.substr(15)})); + Expect(v).ToEqual(std::string{longText.substr(0, 5)} + "23456" + + std::string{longText.substr(15)}); }); }); - describe("Operations", []() + Describe("Operations", []() { - it("Can get substr", [&]() + It("Can get substr", []() { String v{"KiwiApple"}; - AssertThat(v.substr(), Equals("KiwiApple")); - AssertThat(v.substr(4), Equals("Apple")); - AssertThat(v.substr(4, 3), Equals("App")); - AssertThat(v.substr(0, 100), Equals("KiwiApple")); + Expect(v.substr()).ToEqual("KiwiApple"); + Expect(v.substr(4)).ToEqual("Apple"); + Expect(v.substr(4, 3)).ToEqual("App"); + Expect(v.substr(0, 100)).ToEqual("KiwiApple"); }); - it("Can copy out", [&]() + It("Can copy out", []() { String v{"KiwiApple"}; char buffer[16]{}; const auto count = v.copy(buffer, 4, 4); - AssertThat(count, Equals(4u)); - AssertThat(buffer, Equals("Appl")); + Expect(count).ToEqual(4u); + Expect(buffer).ToEqual("Appl"); buffer[count] = '\0'; }); - it("Can compare", [&]() + It("Can compare", []() { String v{"Kiwi"}; String other{"Kiwi"}; String apple{"Apple"}; - AssertThat(v.compare(other), Equals(0)); - AssertThat(v.compare(apple) > 0, Is().True()); - AssertThat(apple.compare(v) < 0, Is().True()); - AssertThat(v.compare("Kiwi"), Equals(0)); - AssertThat(v.compare("Kiwi2") < 0, Is().True()); - AssertThat(v.compare(StringView{"Kiwi"}), Equals(0)); - AssertThat(v.compare(0, 2, String{"Ki"}), Equals(0)); - AssertThat(v.compare(2, 2, String{"wi"}), Equals(0)); + Expect(v.compare(other)).ToEqual(0); + Expect(v.compare(apple) > 0).ToBeTrue(); + Expect(apple.compare(v) < 0).ToBeTrue(); + Expect(v.compare("Kiwi")).ToEqual(0); + Expect(v.compare("Kiwi2") < 0).ToBeTrue(); + Expect(v.compare(StringView{"Kiwi"})).ToEqual(0); + Expect(v.compare(0, 2, String{"Ki"})).ToEqual(0); + Expect(v.compare(2, 2, String{"wi"})).ToEqual(0); }); - it("Can check prefix and suffix", [&]() + It("Can check prefix and suffix", []() { String v{"KiwiApple"}; - AssertThat(v.starts_with("Kiwi"), Is().True()); - AssertThat(v.starts_with('K'), Is().True()); - AssertThat(v.starts_with(StringView{"Ki"}), Is().True()); - AssertThat(v.starts_with("Apple"), Is().False()); - AssertThat(v.ends_with("Apple"), Is().True()); - AssertThat(v.ends_with('e'), Is().True()); - AssertThat(v.ends_with(StringView{"le"}), Is().True()); - AssertThat(v.ends_with("Kiwi"), Is().False()); + Expect(v.starts_with("Kiwi")).ToBeTrue(); + Expect(v.starts_with('K')).ToBeTrue(); + Expect(v.starts_with(StringView{"Ki"})).ToBeTrue(); + Expect(v.starts_with("Apple")).ToBeFalse(); + Expect(v.ends_with("Apple")).ToBeTrue(); + Expect(v.ends_with('e')).ToBeTrue(); + Expect(v.ends_with(StringView{"le"})).ToBeTrue(); + Expect(v.ends_with("Kiwi")).ToBeFalse(); }); - it("Can check contains", [&]() + It("Can check contains", []() { String v{"KiwiApple"}; - AssertThat(v.contains("wiA"), Is().True()); - AssertThat(v.contains('A'), Is().True()); - AssertThat(v.contains(StringView{"zzz"}), Is().False()); - AssertThat(v.contains('z'), Is().False()); + Expect(v.contains("wiA")).ToBeTrue(); + Expect(v.contains('A')).ToBeTrue(); + Expect(v.contains(StringView{"zzz"})).ToBeFalse(); + Expect(v.contains('z')).ToBeFalse(); }); - it("Can find", [&]() + It("Can find", []() { String v{"KiwiKiwi"}; - AssertThat(v.find("Kiwi"), Equals(0u)); - AssertThat(v.find("Kiwi", 1), Equals(4u)); - AssertThat(v.find("Kiwi", 5), Equals(String::npos)); - AssertThat(v.find('i'), Equals(1u)); - AssertThat(v.find('i', 6), Equals(7u)); - AssertThat(v.find('z'), Equals(String::npos)); - AssertThat(v.find(String{"Kiwi"}), Equals(0u)); - AssertThat(v.find(StringView{"Kiwi"}), Equals(0u)); + Expect(v.find("Kiwi")).ToEqual(0u); + Expect(v.find("Kiwi", 1)).ToEqual(4u); + Expect(v.find("Kiwi", 5)).ToEqual(String::npos); + Expect(v.find('i')).ToEqual(1u); + Expect(v.find('i', 6)).ToEqual(7u); + Expect(v.find('z')).ToEqual(String::npos); + Expect(v.find(String{"Kiwi"})).ToEqual(0u); + Expect(v.find(StringView{"Kiwi"})).ToEqual(0u); }); - it("Can rfind", [&]() + It("Can rfind", []() { String v{"KiwiKiwi"}; - AssertThat(v.rfind("Kiwi"), Equals(4u)); - AssertThat(v.rfind("Kiwi", 3), Equals(0u)); - AssertThat(v.rfind('i'), Equals(7u)); - AssertThat(v.rfind('i', 5), Equals(5u)); - AssertThat(v.rfind('z'), Equals(String::npos)); - AssertThat(v.rfind(String{"Kiwi"}), Equals(4u)); - AssertThat(v.rfind(StringView{"Kiwi"}), Equals(4u)); + Expect(v.rfind("Kiwi")).ToEqual(4u); + Expect(v.rfind("Kiwi", 3)).ToEqual(0u); + Expect(v.rfind('i')).ToEqual(7u); + Expect(v.rfind('i', 5)).ToEqual(5u); + Expect(v.rfind('z')).ToEqual(String::npos); + Expect(v.rfind(String{"Kiwi"})).ToEqual(4u); + Expect(v.rfind(StringView{"Kiwi"})).ToEqual(4u); }); - it("Can find first of", [&]() + It("Can find first of", []() { String v{"KiwiApple"}; - AssertThat(v.find_first_of("pl"), Equals(5u)); - AssertThat(v.find_first_of("pl", 6), Equals(6u)); - AssertThat(v.find_first_of('z'), Equals(String::npos)); - AssertThat(v.find_first_of("xyz"), Equals(String::npos)); - AssertThat(v.find_first_of(StringView{"Ap"}), Equals(4u)); + Expect(v.find_first_of("pl")).ToEqual(5u); + Expect(v.find_first_of("pl", 6)).ToEqual(6u); + Expect(v.find_first_of('z')).ToEqual(String::npos); + Expect(v.find_first_of("xyz")).ToEqual(String::npos); + Expect(v.find_first_of(StringView{"Ap"})).ToEqual(4u); }); - it("Can find last of", [&]() + It("Can find last of", []() { String v{"KiwiApple"}; - AssertThat(v.find_last_of("pl"), Equals(7u)); - AssertThat(v.find_last_of("pl", 6), Equals(6u)); - AssertThat(v.find_last_of('z'), Equals(String::npos)); - AssertThat(v.find_last_of(StringView{"Ap"}), Equals(6u)); + Expect(v.find_last_of("pl")).ToEqual(7u); + Expect(v.find_last_of("pl", 6)).ToEqual(6u); + Expect(v.find_last_of('z')).ToEqual(String::npos); + Expect(v.find_last_of(StringView{"Ap"})).ToEqual(6u); }); - it("Can find first not of", [&]() + It("Can find first not of", []() { String v{"aaab"}; - AssertThat(v.find_first_not_of("a"), Equals(3u)); - AssertThat(v.find_first_not_of("ab"), Equals(String::npos)); - AssertThat(v.find_first_not_of('a'), Equals(3u)); - AssertThat(v.find_first_not_of("ab", 3), Equals(String::npos)); + Expect(v.find_first_not_of("a")).ToEqual(3u); + Expect(v.find_first_not_of("ab")).ToEqual(String::npos); + Expect(v.find_first_not_of('a')).ToEqual(3u); + Expect(v.find_first_not_of("ab", 3)).ToEqual(String::npos); }); - it("Can find last not of", [&]() + It("Can find last not of", []() { String v{"baaa"}; - AssertThat(v.find_last_not_of("a"), Equals(0u)); - AssertThat(v.find_last_not_of("ab"), Equals(String::npos)); - AssertThat(v.find_last_not_of('a'), Equals(0u)); - AssertThat(v.find_last_not_of("ab", 0), Equals(String::npos)); + Expect(v.find_last_not_of("a")).ToEqual(0u); + Expect(v.find_last_not_of("ab")).ToEqual(String::npos); + Expect(v.find_last_not_of('a')).ToEqual(0u); + Expect(v.find_last_not_of("ab", 0)).ToEqual(String::npos); }); - it("Has npos", [&]() + It("Has npos", []() { - AssertThat(String::npos, Equals(sizet(-1))); - AssertThat(StringView::npos, Equals(String::npos)); + Expect(String::npos).ToEqual(sizet(-1)); + Expect(StringView::npos).ToEqual(String::npos); }); }); - describe("Operators", []() + Describe("Operators", []() { - it("Can concatenate", [&]() + It("Can concatenate", []() { String a{"Kiwi"}; String b{"Apple"}; - AssertThat(a + b, Equals("KiwiApple")); - AssertThat(a + "X", Equals("KiwiX")); - AssertThat("X" + a, Equals("XKiwi")); - AssertThat(a + '!', Equals("Kiwi!")); - AssertThat('!' + a, Equals("!Kiwi")); - AssertThat(a + StringView{"V"}, Equals("KiwiV")); - AssertThat(StringView{"V"} + a, Equals("VKiwi")); + Expect(a + b).ToEqual("KiwiApple"); + Expect(a + "X").ToEqual("KiwiX"); + Expect("X" + a).ToEqual("XKiwi"); + Expect(a + '!').ToEqual("Kiwi!"); + Expect('!' + a).ToEqual("!Kiwi"); + Expect(a + StringView{"V"}).ToEqual("KiwiV"); + Expect(StringView{"V"} + a).ToEqual("VKiwi"); }); - it("Can chain concatenate", [&]() + It("Can chain concatenate", []() { String a{"Kiwi"}; String result = a + " " + "Apple" + '!'; - AssertThat(result, Equals("Kiwi Apple!")); + Expect(result).ToEqual("Kiwi Apple!"); }); - it("Can compare with other types", [&]() + It("Can compare with other types", []() { String v{"Kiwi"}; - AssertThat(v == String{"Kiwi"}, Is().True()); - AssertThat(v != String{"Apple"}, Is().True()); - AssertThat(v == "Kiwi", Is().True()); - AssertThat(v != "Apple", Is().True()); - AssertThat("Kiwi" == v, Is().True()); - AssertThat("Apple" != v, Is().True()); - AssertThat(v < "Lime", Is().True()); - AssertThat("Lime" > v, Is().True()); - AssertThat(v <= String{"Kiwi"}, Is().True()); - AssertThat(v >= String{"Kiwi"}, Is().True()); - AssertThat(v == StringView{"Kiwi"}, Is().True()); - AssertThat(StringView{"Kiwi"} == v, Is().True()); - AssertThat(v != StringView{"Apple"}, Is().True()); - AssertThat(StringView{"Apple"} != v, Is().True()); - AssertThat(v < StringView{"Lime"}, Is().True()); - AssertThat(StringView{"Lime"} > v, Is().True()); - }); - - it("Can three-way compare", [&]() + Expect(v == String{"Kiwi"}).ToBeTrue(); + Expect(v != String{"Apple"}).ToBeTrue(); + Expect(v == "Kiwi").ToBeTrue(); + Expect(v != "Apple").ToBeTrue(); + Expect("Kiwi" == v).ToBeTrue(); + Expect("Apple" != v).ToBeTrue(); + Expect(v < "Lime").ToBeTrue(); + Expect("Lime" > v).ToBeTrue(); + Expect(v <= String{"Kiwi"}).ToBeTrue(); + Expect(v >= String{"Kiwi"}).ToBeTrue(); + Expect(v == StringView{"Kiwi"}).ToBeTrue(); + Expect(StringView{"Kiwi"} == v).ToBeTrue(); + Expect(v != StringView{"Apple"}).ToBeTrue(); + Expect(StringView{"Apple"} != v).ToBeTrue(); + Expect(v < StringView{"Lime"}).ToBeTrue(); + Expect(StringView{"Lime"} > v).ToBeTrue(); + }); + + It("Can three-way compare", []() { String a{"Kiwi"}; String b{"Lime"}; - AssertThat((a <=> b) < 0, Is().True()); - AssertThat((b <=> a) > 0, Is().True()); - AssertThat((a <=> String{"Kiwi"}) == 0, Is().True()); - AssertThat((a <=> "Kiwi") == 0, Is().True()); + Expect((a <=> b) < 0).ToBeTrue(); + Expect((b <=> a) > 0).ToBeTrue(); + Expect((a <=> String{"Kiwi"}) == 0).ToBeTrue(); + Expect((a <=> "Kiwi") == 0).ToBeTrue(); }); }); - describe("Memory", []() + Describe("Memory", []() { - it("Keeps data valid when growing", [&]() + It("Keeps data valid when growing", []() { String v; for (char c = 'a'; c <= 'z'; ++c) { v.push_back(c); } - AssertThat(v.size(), Equals(26u)); - AssertThat(v, Equals("abcdefghijklmnopqrstuvwxyz")); - AssertThat(v.c_str()[26], Equals('\0')); + Expect(v.size()).ToEqual(26u); + Expect(v).ToEqual("abcdefghijklmnopqrstuvwxyz"); + Expect(v.c_str()[26]).ToEqual('\0'); }); - it("Can reuse capacity", [&]() + It("Can reuse capacity", []() { String v; v.reserve(1000); @@ -815,162 +815,160 @@ go_bandit([]() v.assign("KiwiAppleOrangeBanana"); v.clear(); } - AssertThat(v.capacity(), Equals(cap)); + Expect(v.capacity()).ToEqual(cap); }); - it("Is valid after move assignment", [&]() + It("Is valid after move assignment", []() { String a{"Kiwi"}; String b; b = Move(a); - AssertThat(b, Equals("Kiwi")); + Expect(b).ToEqual("Kiwi"); a = "Reused"; - AssertThat(a, Equals("Reused")); + Expect(a).ToEqual("Reused"); }); }); - describe("Format & Hash", []() + Describe("Format & Hash", []() { - it("Can be formatted", [&]() + It("Can be formatted", []() { String v{"Kiwi"}; - AssertThat(std::format("{}", v), Equals("Kiwi")); - AssertThat(Format("{}-{}", v, 5), Equals("Kiwi-5")); + Expect(std::format("{}", v)).ToEqual("Kiwi"); + Expect(Format("{}-{}", v, 5)).ToEqual("Kiwi-5"); String out; FormatTo(out, "{}!", v); - AssertThat(out, Equals("Kiwi!")); + Expect(out).ToEqual("Kiwi!"); }); - it("Can be hashed", [&]() + It("Can be hashed", []() { String v{"Kiwi"}; - AssertThat(GetHash(v), Equals(GetStringHash("Kiwi"))); - AssertThat(GetHash(StringView{"Kiwi"}), Equals(GetHash(v))); + Expect(GetHash(v)).ToEqual(GetStringHash("Kiwi")); + Expect(GetHash(StringView{"Kiwi"})).ToEqual(GetHash(v)); }); }); - describe("Arena", []() + Describe("Arena", []() { - const char* longText = "This string is long enough to exceed the inline capacity"; - - it("Can default construct on an arena", [&]() + It("Can default construct on an arena", []() { MonoLinearArena arena{Memory::KB * 4}; String v{arena}; - AssertThat(v.empty(), Is().True()); - AssertThat(&v.GetArena(), Equals(static_cast(&arena))); + Expect(v.empty()).ToBeTrue(); + Expect(&v.GetArena()).ToEqual(static_cast(&arena)); // Short strings still use the inline buffer v = "Kiwi"; - AssertThat(v, Equals("Kiwi")); - AssertThat(v.capacity() <= 32u, Is().True()); + Expect(v).ToEqual("Kiwi"); + Expect(v.capacity() <= 32u).ToBeTrue(); }); - it("Can allocate on an arena", [&]() + It("Can allocate on an arena", []() { MonoLinearArena arena{Memory::KB * 4}; - String v{arena, longText}; - AssertThat(v, Equals(longText)); - AssertThat(&v.GetArena(), Equals(static_cast(&arena))); + String v{arena, arenaLongText}; + Expect(v).ToEqual(arenaLongText); + Expect(&v.GetArena()).ToEqual(static_cast(&arena)); // Long strings must allocate on the arena, not the current arena - AssertThat(v.capacity() >= v.size(), Is().True()); + Expect(v.capacity() >= v.size()).ToBeTrue(); }); - it("Can construct with count and char on an arena", [&]() + It("Can construct with count and char on an arena", []() { MonoLinearArena arena{Memory::KB * 4}; String v{arena, 64, 'x'}; - AssertThat(v.size(), Equals(64u)); - AssertThat(&v.GetArena(), Equals(static_cast(&arena))); + Expect(v.size()).ToEqual(64u); + Expect(&v.GetArena()).ToEqual(static_cast(&arena)); }); - it("Can copy into an arena", [&]() + It("Can copy into an arena", []() { MonoLinearArena arena{Memory::KB * 4}; - String original{longText}; + String original{arenaLongText}; String v{arena, original}; - AssertThat(v, Equals(original)); - AssertThat(&v.GetArena(), Equals(static_cast(&arena))); + Expect(v).ToEqual(original); + Expect(&v.GetArena()).ToEqual(static_cast(&arena)); }); - it("Keeps its arena when assigned", [&]() + It("Keeps its arena when assigned", []() { MonoLinearArena arena{Memory::KB * 4}; String v{arena}; - v.assign(longText); + v.assign(arenaLongText); v.append(" with some extra content to force a reallocation"); - AssertThat(&v.GetArena(), Equals(static_cast(&arena))); - AssertThat(v.starts_with("This string"), Is().True()); + Expect(&v.GetArena()).ToEqual(static_cast(&arena)); + Expect(v.starts_with("This string")).ToBeTrue(); }); }); - describe("Strings helpers", []() + Describe("Strings helpers", []() { - it("RemoveFromStart", [&]() + It("RemoveFromStart", []() { String v{"KiwiApple"}; Strings::RemoveFromStart(v, 4); - AssertThat(v, Equals("Apple")); + Expect(v).ToEqual("Apple"); Strings::RemoveFromStart(v, 100); - AssertThat(v.empty(), Is().True()); + Expect(v.empty()).ToBeTrue(); }); - it("RemoveFromEnd", [&]() + It("RemoveFromEnd", []() { String v{"KiwiApple"}; Strings::RemoveFromEnd(v, 5); - AssertThat(v, Equals("Kiwi")); + Expect(v).ToEqual("Kiwi"); Strings::RemoveFromEnd(v, StringView{"wi"}); - AssertThat(v, Equals("Ki")); + Expect(v).ToEqual("Ki"); Strings::RemoveFromEnd(v, 100); - AssertThat(v.empty(), Is().True()); + Expect(v.empty()).ToBeTrue(); }); - it("RemoveCharFromEnd", [&]() + It("RemoveCharFromEnd", []() { String v{"Kiwi!"}; - AssertThat(Strings::RemoveCharFromEnd(v, '!'), Is().True()); - AssertThat(v, Equals("Kiwi")); - AssertThat(Strings::RemoveCharFromEnd(v, '!'), Is().False()); - AssertThat(v, Equals("Kiwi")); + Expect(Strings::RemoveCharFromEnd(v, '!')).ToBeTrue(); + Expect(v).ToEqual("Kiwi"); + Expect(Strings::RemoveCharFromEnd(v, '!')).ToBeFalse(); + Expect(v).ToEqual("Kiwi"); }); - it("ToSentenceCase", [&]() + It("ToSentenceCase", []() { - AssertThat(Strings::ToSentenceCase(""), Equals("")); - AssertThat(Strings::ToSentenceCase("papa"), Equals("Papa")); - AssertThat(Strings::ToSentenceCase("papa "), Equals("Papa ")); - AssertThat(Strings::ToSentenceCase("papa3"), Equals("Papa 3")); - AssertThat(Strings::ToSentenceCase("MisterPotato"), Equals("Mister Potato")); + Expect(Strings::ToSentenceCase("")).ToEqual(""); + Expect(Strings::ToSentenceCase("papa")).ToEqual("Papa"); + Expect(Strings::ToSentenceCase("papa ")).ToEqual("Papa "); + Expect(Strings::ToSentenceCase("papa3")).ToEqual("Papa 3"); + Expect(Strings::ToSentenceCase("MisterPotato")).ToEqual("Mister Potato"); }); - it("Convert u16 to u8", [&]() + It("Convert u16 to u8", []() { TString utf16string{0x41, 0x0448, 0x65e5, 0xd834, 0xdd1e}; TString u = Strings::Convert>(utf16string); - AssertThat(u.size(), Equals(10u)); + Expect(u.size()).ToEqual(10u); }); - it("Convert u8 to u16", [&]() + It("Convert u8 to u16", []() { TString utf8_with_surrogates = "\xe6\x97\xa5\xd1\x88\xf0\x9d\x84\x9e"; TString utf16result = Strings::Convert>(utf8_with_surrogates); - AssertThat(utf16result.size(), Equals(4u)); - AssertThat(utf16result[2] == 0xd834, Is().True()); - AssertThat(utf16result[3] == 0xdd1e, Is().True()); + Expect(utf16result.size()).ToEqual(4u); + Expect(utf16result[2] == 0xd834).ToBeTrue(); + Expect(utf16result[3] == 0xdd1e).ToBeTrue(); }); - it("Convert u32 to u8", [&]() + It("Convert u32 to u8", []() { TString utf32string = {0x448, 0x65E5, 0x10346}; TString utf8result = Strings::Convert>(utf32string); - AssertThat(utf8result.size(), Equals(9u)); + Expect(utf8result.size()).ToEqual(9u); }); - it("Convert u8 to u32", [&]() + It("Convert u8 to u32", []() { TString twochars = "\xe6\x97\xa5\xd1\x88"; TString utf32result = Strings::Convert>(twochars); - AssertThat(utf32result.size(), Equals(2u)); + Expect(utf32result.size()).ToEqual(2u); }); }); }); }); -}); +} diff --git a/Tests/Core/StringView.spec.cpp b/Tests/Core/StringView.spec.cpp index 1e54e70d..4ba52257 100644 --- a/Tests/Core/StringView.spec.cpp +++ b/Tests/Core/StringView.spec.cpp @@ -1,116 +1,114 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include #include -using namespace snowhouse; -using namespace bandit; using namespace p; -go_bandit([]() +void RegisterCoreStringViewTests() { - describe("Strings", []() + Spec("Strings", []() { - describe("StringView", []() + Describe("StringView", []() { - it("Can assign from literal", [&]() + It("Can assign from literal", []() { StringView v{"Kiwi"}; - AssertThat(v, Equals("Kiwi")); - AssertThat(v.size(), Equals(4)); + Expect(v).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4); }); - it("Can assign from string", [&]() + It("Can assign from string", []() { String str{"Kiwi"}; StringView v{str}; - AssertThat(v, Equals("Kiwi")); - AssertThat(v.size(), Equals(4)); + Expect(v).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4); }); - it("Can copy empty", [&]() + It("Can copy empty", []() { StringView str{}; StringView str2{" "}; - AssertThat(str.empty(), Equals(true)); - AssertThat((u8*)str.data(), Equals(nullptr)); - AssertThat(str2.empty(), Equals(false)); - AssertThat((u8*)str2.data(), !Equals(nullptr)); + Expect(str.empty()).ToEqual(true); + Expect((u8*)str.data()).ToEqual(nullptr); + Expect(str2.empty()).ToEqual(false); + Expect((u8*)str2.data()).ToNotEqual(nullptr); str2 = str; - AssertThat(str2.empty(), Equals(true)); - AssertThat((u8*)str2.data(), Equals(nullptr)); + Expect(str2.empty()).ToEqual(true); + Expect((u8*)str2.data()).ToEqual(nullptr); }); - it("Can retrieve string data", [&]() + It("Can retrieve string data", []() { StringView v{"Kiwi"}; - AssertThat(v.data(), Equals("Kiwi")); - AssertThat(v.size(), Equals(4)); + Expect(v.data()).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4); StringView v2{}; - AssertThat((u8*)v2.data(), Equals(nullptr)); - AssertThat(v2.size(), Equals(0)); + Expect((u8*)v2.data()).ToEqual(nullptr); + Expect(v2.size()).ToEqual(0); }); - it("Can compare", [&]() + It("Can compare", []() { StringView vKiwi{"Kiwi"}; StringView vKiwi2{"Kiwi"}; StringView vApple{"Apple"}; - AssertThat(vKiwi, Equals(vKiwi2)); - AssertThat(vKiwi, !Equals(vApple)); + Expect(vKiwi).ToEqual(vKiwi2); + Expect(vKiwi).ToNotEqual(vApple); }); - it("Can copy", [&]() + It("Can copy", []() { StringView vKiwi{"Kiwi"}; StringView vApple{"Apple"}; StringView vCopy = vKiwi; - AssertThat(vCopy, Equals("Kiwi")); - AssertThat(vCopy, Equals(vKiwi)); - AssertThat(vCopy, !Equals(vApple)); + Expect(vCopy).ToEqual("Kiwi"); + Expect(vCopy).ToEqual(vKiwi); + Expect(vCopy).ToNotEqual(vApple); vCopy = vApple; - AssertThat(vCopy, Equals("Apple")); - AssertThat(vCopy, !Equals(vKiwi)); - AssertThat(vCopy, Equals(vApple)); + Expect(vCopy).ToEqual("Apple"); + Expect(vCopy).ToNotEqual(vKiwi); + Expect(vCopy).ToEqual(vApple); }); - it("Can move", [&]() + It("Can move", []() { StringView vKiwi{"Kiwi"}; StringView vApple{"Apple"}; StringView vMove = Move(vKiwi); - AssertThat(vMove, Equals("Kiwi")); + Expect(vMove).ToEqual("Kiwi"); vMove = Move(vApple); - AssertThat(vMove, Equals("Apple")); + Expect(vMove).ToEqual("Apple"); }); - describe("Strings", []() + Describe("Strings", []() { - it("Can Find", [&]() + It("Can Find", []() { StringView v{"Kiwiwi"}; // Find Chars - AssertThat(Strings::Find(v, 'K', FindDir::Front), Equals(0)); - AssertThat(Strings::Find(v, 'K', FindDir::Back), Equals(0)); - AssertThat(Strings::Find(v, 'i', FindDir::Front), Equals(1)); - AssertThat(Strings::Find(v, 'i', FindDir::Back), Equals(5)); + Expect(Strings::Find(v, 'K', FindDir::Front)).ToEqual(0); + Expect(Strings::Find(v, 'K', FindDir::Back)).ToEqual(0); + Expect(Strings::Find(v, 'i', FindDir::Front)).ToEqual(1); + Expect(Strings::Find(v, 'i', FindDir::Back)).ToEqual(5); // Find last chars - AssertThat(Strings::Find(v, 'w', FindDir::Front, true), Equals(0)); // 'K' - AssertThat(Strings::Find(v, 'w', FindDir::Back, true), Equals(5)); // 'i' - AssertThat(Strings::Find(v, 'K', FindDir::Front, true), Equals(1)); // 'i' - AssertThat(Strings::Find(v, 'i', FindDir::Back, true), Equals(4)); // 'w' + Expect(Strings::Find(v, 'w', FindDir::Front, true)).ToEqual(0); // 'K' + Expect(Strings::Find(v, 'w', FindDir::Back, true)).ToEqual(5); // 'i' + Expect(Strings::Find(v, 'K', FindDir::Front, true)).ToEqual(1); // 'i' + Expect(Strings::Find(v, 'i', FindDir::Back, true)).ToEqual(4); // 'w' // Find Sub-strings - AssertThat(Strings::Find(v, "Ki", FindDir::Front), Equals(0)); - AssertThat(Strings::Find(v, "Ki", FindDir::Back), Equals(0)); - AssertThat(Strings::Find(v, "wi", FindDir::Front), Equals(2)); - AssertThat(Strings::Find(v, "wi", FindDir::Back), Equals(4)); + Expect(Strings::Find(v, "Ki", FindDir::Front)).ToEqual(0); + Expect(Strings::Find(v, "Ki", FindDir::Back)).ToEqual(0); + Expect(Strings::Find(v, "wi", FindDir::Front)).ToEqual(2); + Expect(Strings::Find(v, "wi", FindDir::Back)).ToEqual(4); }); }); }); }); -}); +} diff --git a/Tests/Core/Tag.spec.cpp b/Tests/Core/Tag.spec.cpp index 0cb43801..032d8391 100644 --- a/Tests/Core/Tag.spec.cpp +++ b/Tests/Core/Tag.spec.cpp @@ -1,109 +1,107 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include -using namespace snowhouse; -using namespace bandit; using namespace p; -go_bandit([]() +void RegisterCoreTagTests() { - describe("Core.Tag", []() + Spec("Core.Tag", []() { - it("Can copy empty", [&]() + It("Can copy empty", []() { Tag tag{}; Tag tag2{"Ahh"}; - AssertThat(p::GetHash(tag), Equals(0)); - AssertThat(tag.IsNone(), Equals(true)); - AssertThat(p::GetHash(tag2), !Equals(0)); - AssertThat(tag2.IsNone(), Equals(false)); + Expect(p::GetHash(tag)).ToEqual(0); + Expect(tag.IsNone()).ToEqual(true); + Expect(p::GetHash(tag2)).ToNotEqual(0); + Expect(tag2.IsNone()).ToEqual(false); tag2 = tag; - AssertThat(p::GetHash(tag2), Equals(0)); - AssertThat(tag2.IsNone(), Equals(true)); + Expect(p::GetHash(tag2)).ToEqual(0); + Expect(tag2.IsNone()).ToEqual(true); }); - it("Can assign from literal", [&]() + It("Can assign from literal", []() { Tag tag{"Kiwi"}; - AssertThat(tag.AsString(), Equals("Kiwi")); + Expect(tag.AsString()).ToEqual("Kiwi"); }); - it("Can assign from string", [&]() + It("Can assign from string", []() { String str{"Kiwi"}; Tag tag{str}; - AssertThat(tag.AsString(), Equals("Kiwi")); + Expect(tag.AsString()).ToEqual("Kiwi"); }); - it("Can retrieve string data", [&]() + It("Can retrieve string data", []() { Tag tag{"Kiwi"}; - AssertThat(tag.AsString(), Equals("Kiwi")); + Expect(tag.AsString()).ToEqual("Kiwi"); }); - it("Can compare tags", [&]() + It("Can compare tags", []() { Tag tagKiwi{"Kiwi"}; Tag tagKiwi2{"Kiwi"}; Tag tagApple{"Apple"}; - AssertThat(tagKiwi, Equals(tagKiwi2)); - AssertThat(tagKiwi, !Equals(tagApple)); + Expect(tagKiwi).ToEqual(tagKiwi2); + Expect(tagKiwi).ToNotEqual(tagApple); }); - it("Different instances share string allocation", [&]() + It("Different instances share string allocation", []() { Tag tagKiwi{"Kiwi"}; Tag tagKiwi2{"Kiwi"}; Tag tagApple{"Apple"}; - AssertThat(tagKiwi.AsString().data(), Equals(tagKiwi2.AsString().data())); - AssertThat(tagKiwi.AsString().data(), !Equals(tagApple.AsString().data())); + Expect(tagKiwi.AsString().data()).ToEqual(tagKiwi2.AsString().data()); + Expect(tagKiwi.AsString().data()).ToNotEqual(tagApple.AsString().data()); }); - it("Can check invalid/none", [&]() + It("Can check invalid/none", []() { Tag tagValid{"Kiwi"}; Tag tagInvalid{}; - AssertThat(tagValid.IsNone(), Equals(false)); - AssertThat(tagValid, !Equals(Tag::None())); - AssertThat(tagInvalid.IsNone(), Equals(true)); - AssertThat(tagInvalid, Equals(Tag::None())); + Expect(tagValid.IsNone()).ToEqual(false); + Expect(tagValid).ToNotEqual(Tag::None()); + Expect(tagInvalid.IsNone()).ToEqual(true); + Expect(tagInvalid).ToEqual(Tag::None()); }); - it("Contains correct hashes", [&]() + It("Contains correct hashes", []() { Tag tagKiwi{"Kiwi"}; Tag tagKiwi2{"Kiwi"}; - AssertThat(p::GetHash(tagKiwi), Equals(p::GetHash(tagKiwi2))); - AssertThat(tagKiwi.GetStringHash(), Equals(p::GetHash("Kiwi"))); + Expect(p::GetHash(tagKiwi)).ToEqual(p::GetHash(tagKiwi2)); + Expect(tagKiwi.GetStringHash()).ToEqual(p::GetHash("Kiwi")); }); - it("Can copy tag", [&]() + It("Can copy tag", []() { Tag tagKiwi{"Kiwi"}; Tag tagApple{"Apple"}; Tag tagCopy = tagKiwi; - AssertThat(tagCopy.AsString(), Equals("Kiwi")); - AssertThat(tagCopy, Equals(tagKiwi)); - AssertThat(tagCopy, !Equals(tagApple)); + Expect(tagCopy.AsString()).ToEqual("Kiwi"); + Expect(tagCopy).ToEqual(tagKiwi); + Expect(tagCopy).ToNotEqual(tagApple); tagCopy = tagApple; - AssertThat(tagCopy.AsString(), Equals("Apple")); - AssertThat(tagCopy, !Equals(tagKiwi)); - AssertThat(tagCopy, Equals(tagApple)); + Expect(tagCopy.AsString()).ToEqual("Apple"); + Expect(tagCopy).ToNotEqual(tagKiwi); + Expect(tagCopy).ToEqual(tagApple); }); - it("Can move tag", [&]() + It("Can move tag", []() { Tag tagKiwi{"Kiwi"}; Tag tagApple{"Apple"}; Tag tagMove = Move(tagKiwi); - AssertThat(tagKiwi, Equals(Tag::None())); - AssertThat(tagMove.AsString(), Equals("Kiwi")); + Expect(tagKiwi).ToEqual(Tag::None()); + Expect(tagMove.AsString()).ToEqual("Kiwi"); tagMove = Move(tagApple); - AssertThat(tagApple, Equals(Tag::None())); - AssertThat(tagMove.AsString(), Equals("Apple")); + Expect(tagApple).ToEqual(Tag::None()); + Expect(tagMove.AsString()).ToEqual("Apple"); }); }); -}); +} diff --git a/Tests/ECS/Components.spec.cpp b/Tests/ECS/Components.spec.cpp index 58dc705f..90c13698 100644 --- a/Tests/ECS/Components.spec.cpp +++ b/Tests/ECS/Components.spec.cpp @@ -1,28 +1,12 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include -using namespace snowhouse; -using namespace bandit; using namespace p; using namespace std::chrono_literals; -namespace snowhouse -{ - template<> - struct Stringizer - { - static std::string ToString(Id id) - { - std::stringstream stream; - stream << "Id(" << id.value << ")"; - return stream.str(); - } - }; -} // namespace snowhouse - struct EmptyComponent { @@ -60,45 +44,45 @@ struct TestComponent u32 TestComponent::destructed = 0; -go_bandit([]() +void RegisterECSComponentsTests() { - describe("ECS.Components", []() + Spec("ECS.Components", []() { - it("Can add one component", [&]() + It("Can add one component", []() { IdContext ctx; Id id = AddId(ctx); - AssertThat(ctx.Has(id), Is().False()); - AssertThat(ctx.TryGet(id), Equals(nullptr)); - AssertThat(ctx.TryGet(id), Equals(nullptr)); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + Expect(ctx.TryGet(id)).ToEqual(nullptr); ctx.Add(id); - AssertThat(ctx.Has(id), Is().True()); - AssertThat(ctx.TryGet(id), Equals(nullptr)); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); ctx.Add(id); - AssertThat(ctx.Has(id), Is().True()); - AssertThat(ctx.TryGet(id), !Equals(nullptr)); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.TryGet(id)).ToNotEqual(nullptr); }); - it("Can remove one component", [&]() + It("Can remove one component", []() { IdContext ctx; Id id = AddId(ctx); ctx.Add(id); ctx.Remove(id); - AssertThat(ctx.Has(id), Is().False()); - AssertThat(ctx.TryGet(id), Equals(nullptr)); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); NonEmptyComponent::destructed = 0; ctx.Remove(id); - AssertThat(ctx.Has(id), Is().False()); - AssertThat(ctx.TryGet(id), Equals(nullptr)); - AssertThat(NonEmptyComponent::destructed, Equals(1)); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + Expect(NonEmptyComponent::destructed).ToEqual(1); }); - it("Can add many components", [&]() + It("Can add many components", []() { IdContext ctx; TArray ids{3}; @@ -108,12 +92,12 @@ go_bandit([]() for (Id id : ids) { auto* data = ctx.TryGet(id); - AssertThat(data, !Equals(nullptr)); - AssertThat(data->a, Equals(2)); + Expect(data).ToNotEqual(nullptr); + Expect(data->a).ToEqual(2); } }); - it("Can remove many components", [&]() + It("Can remove many components", []() { IdContext ctx; TArray ids{3}; @@ -123,10 +107,10 @@ go_bandit([]() NonEmptyComponent::destructed = 0; TView firstTwo{ids.Data(), ids.Data() + 2}; ctx.Remove(firstTwo); - AssertThat(NonEmptyComponent::destructed, Equals(2)); - AssertThat(ctx.TryGet(ids[0]), Equals(nullptr)); - AssertThat(ctx.TryGet(ids[1]), Equals(nullptr)); - AssertThat(ctx.TryGet(ids[2]), !Equals(nullptr)); + Expect(NonEmptyComponent::destructed).ToEqual(2); + Expect(ctx.TryGet(ids[0])).ToEqual(nullptr); + Expect(ctx.TryGet(ids[1])).ToEqual(nullptr); + Expect(ctx.TryGet(ids[2])).ToNotEqual(nullptr); // Repeat in different order ctx.AddN(ids, NonEmptyComponent{2}); @@ -134,58 +118,58 @@ go_bandit([]() NonEmptyComponent::destructed = 0; TView lastTwo{ids.Data() + 1, ids.Data() + 3}; ctx.Remove(lastTwo); - AssertThat(NonEmptyComponent::destructed, Equals(2)); - AssertThat(ctx.TryGet(ids[0]), !Equals(nullptr)); - AssertThat(ctx.TryGet(ids[1]), Equals(nullptr)); - AssertThat(ctx.TryGet(ids[2]), Equals(nullptr)); + Expect(NonEmptyComponent::destructed).ToEqual(2); + Expect(ctx.TryGet(ids[0])).ToNotEqual(nullptr); + Expect(ctx.TryGet(ids[1])).ToEqual(nullptr); + Expect(ctx.TryGet(ids[2])).ToEqual(nullptr); }); - it("Components are removed after node is deleted", [&]() + It("Components are removed after node is deleted", []() { IdContext ctx; Id id = AddId(ctx); ctx.Add(id); RmId(ctx, id, p::RmIdFlags::Instant); - AssertThat(ctx.IsValid(id), Is().False()); + Expect(ctx.IsValid(id)).ToBeFalse(); - AssertThat(ctx.Has(id), Is().False()); - AssertThat(ctx.TryGet(id), Equals(nullptr)); - AssertThat(ctx.Has(id), Is().False()); - AssertThat(ctx.TryGet(id), Equals(nullptr)); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); }); - it("Components are removed after node is deleted (deferred)", [&]() + It("Components are removed after node is deleted (deferred)", []() { IdContext ctx; Id id = AddId(ctx); ctx.Add(id); RmId(ctx, id); - AssertThat(ctx.IsValid(id), Is().False()); + Expect(ctx.IsValid(id)).ToBeFalse(); - AssertThat(ctx.Has(id), Is().True()); - AssertThat(ctx.TryGet(id), Equals(nullptr)); - AssertThat(ctx.Has(id), Is().True()); - AssertThat(ctx.TryGet(id), !Equals(nullptr)); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.TryGet(id)).ToNotEqual(nullptr); FlushDeferredRemovals(ctx); - AssertThat(ctx.Has(id), Is().False()); - AssertThat(ctx.TryGet(id), Equals(nullptr)); - AssertThat(ctx.Has(id), Is().False()); - AssertThat(ctx.TryGet(id), Equals(nullptr)); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); }); - it("Components keep state when added", [&]() + It("Components keep state when added", []() { IdContext ctx; Id id = AddId(ctx); ctx.AddN(id, NonEmptyComponent{2}); - AssertThat(ctx.TryGet(id), !Equals(nullptr)); - AssertThat(ctx.Get(id).a, Equals(2)); + Expect(ctx.TryGet(id)).ToNotEqual(nullptr); + Expect(ctx.Get(id).a).ToEqual(2); }); - it("Can copy registry", []() + It("Can copy registry", []() { IdContext ctxa; @@ -195,32 +179,32 @@ go_bandit([]() ctxa.AddN(id2, NonEmptyComponent{2}); IdContext ctxb{ctxa}; - AssertThat(ctxb.Has(id), Is().True()); - AssertThat(ctxb.Has(id), Is().True()); - AssertThat(ctxb.TryGet(id), !Equals(nullptr)); + Expect(ctxb.Has(id)).ToBeTrue(); + Expect(ctxb.Has(id)).ToBeTrue(); + Expect(ctxb.TryGet(id)).ToNotEqual(nullptr); // Holds component values - AssertThat(ctxb.Has(id2), Is().True()); - AssertThat(ctxb.Get(id2).a, Equals(2)); + Expect(ctxb.Has(id2)).ToBeTrue(); + Expect(ctxb.Get(id2).a).ToEqual(2); }); - it("Can check components", [&]() + It("Can check components", []() { IdContext ctx; Id id = NoId; - AssertThat(ctx.Has(id), Is().False()); - AssertThat(ctx.Has(id), Is().False()); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.Has(id)).ToBeFalse(); id = AddId(ctx); - AssertThat(ctx.Has(id), Is().False()); - AssertThat(ctx.Has(id), Is().False()); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.Has(id)).ToBeFalse(); ctx.Add(id); - AssertThat(ctx.Has(id), Is().True()); - AssertThat(ctx.Has(id), Is().True()); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.Has(id)).ToBeTrue(); }); - it("Can destroy components on reset", [&]() + It("Can destroy components on reset", []() { NonEmptyComponent::destructed = 0; TestComponent::destructed = 0; @@ -233,53 +217,53 @@ go_bandit([]() ctx.Remove(ids); ctx.Remove(ids[0]); - AssertThat( - NonEmptyComponent::destructed, Equals(4)); // 3 + 1 (passed by value on Add()) - AssertThat(TestComponent::destructed, Equals(2)); // 1 + 1 (passed by value on Add()) + Expect( + NonEmptyComponent::destructed).ToEqual(4); // 3 + 1 (passed by value on Add()) + Expect(TestComponent::destructed).ToEqual(2); // 1 + 1 (passed by value on Add()) NonEmptyComponent::destructed = 0; TestComponent::destructed = 0; ctx.Reset(); - AssertThat(NonEmptyComponent::destructed, Equals(0)); - AssertThat(TestComponent::destructed, Equals(2)); + Expect(NonEmptyComponent::destructed).ToEqual(0); + Expect(TestComponent::destructed).ToEqual(2); }); - it("Components are removed with the entity", [&]() + It("Components are removed with the entity", []() { IdContext ctx; Id id = AddId(ctx); ctx.Add(id); RmId(ctx, id, p::RmIdFlags::Instant); - AssertThat(ctx.IsValid(id), Is().False()); + Expect(ctx.IsValid(id)).ToBeFalse(); - AssertThat(ctx.Has(id), Is().False()); - AssertThat(ctx.TryGet(id), Equals(nullptr)); - AssertThat(ctx.Has(id), Is().False()); - AssertThat(ctx.TryGet(id), Equals(nullptr)); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); }); - it("Components are removed with the entity (deferred)", [&]() + It("Components are removed with the entity (deferred)", []() { IdContext ctx; Id id = AddId(ctx); ctx.Add(id); RmId(ctx, id); - AssertThat(ctx.IsValid(id), Is().False()); + Expect(ctx.IsValid(id)).ToBeFalse(); - AssertThat(ctx.Has(id), Is().True()); - AssertThat(ctx.TryGet(id), Equals(nullptr)); - AssertThat(ctx.Has(id), Is().True()); - AssertThat(ctx.TryGet(id), !Equals(nullptr)); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.TryGet(id)).ToNotEqual(nullptr); FlushDeferredRemovals(ctx); - AssertThat(ctx.Has(id), Is().False()); - AssertThat(ctx.TryGet(id), Equals(nullptr)); - AssertThat(ctx.Has(id), Is().False()); - AssertThat(ctx.TryGet(id), Equals(nullptr)); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); }); - it("Can access components on recicled entities", [&]() + It("Can access components on recicled entities", []() { IdContext ctx; Id id = AddId(ctx); @@ -288,22 +272,22 @@ go_bandit([]() id = AddId(ctx); ctx.Add(id); - AssertThat(ctx.Has(id), Is().False()); - AssertThat(ctx.Has(id), Is().True()); - AssertThat(ctx.TryGet(id), !Equals(nullptr)); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.TryGet(id)).ToNotEqual(nullptr); }); - it("Can access CRemoved", [&]() + It("Can access CRemoved", []() { IdContext ctx; Id id = AddId(ctx); ctx.Add(id); RmId(ctx, id); - AssertThat(ctx.Has(id), Is().True()); - AssertThat(ctx.Has(id), Is().True()); - AssertThat(ctx.Has(id), Is().True()); - AssertThat(ctx.TryGet(id), !Equals(nullptr)); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.TryGet(id)).ToNotEqual(nullptr); }); }); -}); +} diff --git a/Tests/ECS/ECS.spec.cpp b/Tests/ECS/ECS.spec.cpp index 9574dfe3..7437f04a 100644 --- a/Tests/ECS/ECS.spec.cpp +++ b/Tests/ECS/ECS.spec.cpp @@ -1,11 +1,9 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include -using namespace snowhouse; -using namespace bandit; using namespace p; @@ -17,11 +15,11 @@ struct ECSTypeB {}; -go_bandit([]() +void RegisterECSECSsmTests() { - describe("ECS", []() + Spec("ECS", []() { - it("Can copy context", [&]() + It("Can copy context", []() { static IdContext* ctxPtr = nullptr; @@ -32,17 +30,17 @@ go_bandit([]() origin.Add(id); IdContext target{origin}; - AssertThat(origin.IsValid(id), Equals(true)); - AssertThat(origin.Has(id), Equals(true)); - AssertThat(target.IsValid(id), Equals(true)); - AssertThat(target.Has(id), Equals(true)); + Expect(origin.IsValid(id)).ToEqual(true); + Expect(origin.Has(id)).ToEqual(true); + Expect(target.IsValid(id)).ToEqual(true); + Expect(target.Has(id)).ToEqual(true); ctxPtr = ⌖ target.Add(id); - AssertThat(target.Has(id), Equals(true)); + Expect(target.Has(id)).ToEqual(true); }); - it("Can move context", [&]() + It("Can move context", []() { static IdContext* ctxPtr = nullptr; @@ -51,24 +49,24 @@ go_bandit([]() ctxPtr = &origin; origin.Add(id); - AssertThat(origin.Has(id), Equals(true)); + Expect(origin.Has(id)).ToEqual(true); IdContext target{Move(origin)}; - AssertThat(origin.IsValid(id), Equals(false)); + Expect(origin.IsValid(id)).ToEqual(false); - AssertThat(target.IsValid(id), Equals(true)); - AssertThat(target.Has(id), Equals(true)); + Expect(target.IsValid(id)).ToEqual(true); + Expect(target.Has(id)).ToEqual(true); ctxPtr = ⌖ target.Add(id); - AssertThat(target.Has(id), Equals(true)); + Expect(target.Has(id)).ToEqual(true); }); - it("Can assure pool", [&]() + It("Can assure pool", []() { IdContext origin; TPool& pool = origin.AssurePool(); - AssertThat(pool.Size(), Equals(0)); + Expect(pool.Size()).ToEqual(0); }); }); -}); +} diff --git a/Tests/ECS/Filtering.spec.cpp b/Tests/ECS/Filtering.spec.cpp index b8453116..cb255481 100644 --- a/Tests/ECS/Filtering.spec.cpp +++ b/Tests/ECS/Filtering.spec.cpp @@ -1,29 +1,11 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include "bandit/grammar.h" - -#include +#include #include -using namespace snowhouse; -using namespace bandit; using namespace p; -namespace snowhouse -{ - template<> - struct Stringizer - { - static std::string ToString(Id id) - { - std::stringstream stream; - stream << "Id(" << id.value << ")"; - return stream.str(); - } - }; -} // namespace snowhouse - struct TypeA {}; @@ -33,7 +15,7 @@ struct TypeC {}; -go_bandit([]() +namespace { IdContext ctx; Id id1; @@ -41,9 +23,14 @@ go_bandit([]() Id id3; Id id4; Id id5; - describe("ECS.Filtering", [&]() +} // namespace + + +void RegisterECSFilteringTests() +{ + Spec("ECS.Filtering", []() { - before_each([&]() + BeforeEach([]() { ctx = {}; id1 = AddId(ctx); @@ -58,189 +45,189 @@ go_bandit([]() ctx.Add(id5); }); - describe("FindAllIdsWith/FindAllIdsWithAny", [&]() + Describe("FindAllIdsWith/FindAllIdsWithAny", []() { - it("Can get list matching all", [&]() + It("Can get list matching all", []() { TIdScope access{ctx}; TArray typeIds = FindAllIdsWith(access); - AssertThat(typeIds.Contains(id1), Is().True()); - AssertThat(typeIds.Contains(id2), Is().True()); - AssertThat(typeIds.Contains(id3), Is().False()); + Expect(typeIds.Contains(id1)).ToBeTrue(); + Expect(typeIds.Contains(id2)).ToBeTrue(); + Expect(typeIds.Contains(id3)).ToBeFalse(); TArray type2Ids = FindAllIdsWith(access); - AssertThat(type2Ids.Contains(id1), Is().False()); - AssertThat(type2Ids.Contains(id2), Is().True()); - AssertThat(type2Ids.Contains(id3), Is().True()); + Expect(type2Ids.Contains(id1)).ToBeFalse(); + Expect(type2Ids.Contains(id2)).ToBeTrue(); + Expect(type2Ids.Contains(id3)).ToBeTrue(); }); - it("Can get list matching any", [&]() + It("Can get list matching any", []() { TIdScope access{ctx}; TArray typeIds = FindAllIdsWithAny(access); - AssertThat(typeIds.Contains(id1), Is().True()); - AssertThat(typeIds.Contains(id2), Is().True()); - AssertThat(typeIds.Contains(id3), Is().False()); + Expect(typeIds.Contains(id1)).ToBeTrue(); + Expect(typeIds.Contains(id2)).ToBeTrue(); + Expect(typeIds.Contains(id3)).ToBeFalse(); TArray type2Ids = FindAllIdsWithAny(access); - AssertThat(type2Ids.Contains(id1), Is().True()); - AssertThat(type2Ids.Contains(id2), Is().True()); - AssertThat(type2Ids.Contains(id3), Is().True()); + Expect(type2Ids.Contains(id1)).ToBeTrue(); + Expect(type2Ids.Contains(id2)).ToBeTrue(); + Expect(type2Ids.Contains(id3)).ToBeTrue(); }); - it("Doesn't list removed ids", [&]() + It("Doesn't list removed ids", []() { TIdScope access{ctx}; - RmId(ctx, id2, RmIdFlags::Instant); // Remove first in the pool - RmId(ctx, id3, RmIdFlags::Instant); // Remove last in the pool - RmId(ctx, id4, RmIdFlags::Instant); // Remove last in the pool + RmId(ctx, id2, RmIdFlags::Instant); + RmId(ctx, id3, RmIdFlags::Instant); + RmId(ctx, id4, RmIdFlags::Instant); TArray ids = FindAllIdsWith(access); - AssertThat(ids.Contains(NoId), Is().False()); - AssertThat(ids.Size(), Equals(1)); + Expect(ids.Contains(NoId)).ToBeFalse(); + Expect(ids.Size()).ToEqual(1); }); - it("Doesn't list (deferred) removed ids", [&]() + It("Doesn't list (deferred) removed ids", []() { TIdScope access{ctx}; - RmId(ctx, id2); // Remove first in the pool - RmId(ctx, id3); // Remove last in the pool - RmId(ctx, id4); // Remove last in the pool + RmId(ctx, id2); + RmId(ctx, id3); + RmId(ctx, id4); FlushDeferredRemovals(ctx); TArray ids = FindAllIdsWith(access); - AssertThat(ids.Contains(NoId), Is().False()); - AssertThat(ids.Size(), Equals(1)); + Expect(ids.Contains(NoId)).ToBeFalse(); + Expect(ids.Size()).ToEqual(1); }); }); - describe("ExcludeIdsWith", [&]() + Describe("ExcludeIdsWith", []() { - it("Removes ids containing component", [&]() + It("Removes ids containing component", []() { TIdScope access{ctx}; TArray typeIds = FindAllIdsWithAny(access); ExcludeIdsWith(access, typeIds); - AssertThat(typeIds.Contains(id1), Is().True()); - AssertThat(typeIds.Contains(id2), Is().False()); - AssertThat(typeIds.Contains(id3), Is().False()); + Expect(typeIds.Contains(id1)).ToBeTrue(); + Expect(typeIds.Contains(id2)).ToBeFalse(); + Expect(typeIds.Contains(id3)).ToBeFalse(); }); - it("Removes ids not containing component", [&]() + It("Removes ids not containing component", []() { TIdScope access{ctx}; TArray typeIds = FindAllIdsWithAny(access); ExcludeIdsWithout(access, typeIds); - AssertThat(typeIds.Contains(id1), Is().False()); - AssertThat(typeIds.Contains(id2), Is().True()); - AssertThat(typeIds.Contains(id3), Is().False()); + Expect(typeIds.Contains(id1)).ToBeFalse(); + Expect(typeIds.Contains(id2)).ToBeTrue(); + Expect(typeIds.Contains(id3)).ToBeFalse(); }); - it("Removes ids containing multiple component", [&]() + It("Removes ids containing multiple component", []() { TIdScope access{ctx}; TArray typeIds = FindAllIdsWithAny(access); ExcludeIdsWith(access, typeIds); - AssertThat(typeIds.Contains(id1), Is().True()); - AssertThat(typeIds.Contains(id2), Is().False()); - AssertThat(typeIds.Contains(id3), Is().False()); + Expect(typeIds.Contains(id1)).ToBeTrue(); + Expect(typeIds.Contains(id2)).ToBeFalse(); + Expect(typeIds.Contains(id3)).ToBeFalse(); }); }); - describe("FindIdsWith", [&]() + Describe("FindIdsWith", []() { - it("Finds ids containing a component from a list", [&]() + It("Finds ids containing a component from a list", []() { TArray source{id1, id2, id3}; TIdScope access{ctx}; TArray typeIds = FindIdsWith(access, source); - AssertThat(typeIds.Contains(id1), Is().True()); - AssertThat(typeIds.Contains(id2), Is().True()); - AssertThat(typeIds.Contains(id3), Is().False()); + Expect(typeIds.Contains(id1)).ToBeTrue(); + Expect(typeIds.Contains(id2)).ToBeTrue(); + Expect(typeIds.Contains(id3)).ToBeFalse(); }); - it("Finds ids not containing a component from a list", [&]() + It("Finds ids not containing a component from a list", []() { TArray source{id1, id2, id3}; TIdScope access{ctx}; TArray ids = FindIdsWithout(access, source); - AssertThat(ids.Contains(id1), Is().False()); - AssertThat(ids.Contains(id2), Is().False()); - AssertThat(ids.Contains(id3), Is().True()); + Expect(ids.Contains(id1)).ToBeFalse(); + Expect(ids.Contains(id2)).ToBeFalse(); + Expect(ids.Contains(id3)).ToBeTrue(); }); }); - describe("ExtractIdsWith", [&]() + Describe("ExtractIdsWith", []() { - it("Finds and removes ids containing a component from a list", [&]() + It("Finds and removes ids containing a component from a list", []() { TArray source{id1, id2, id3}; TIdScope access{ctx}; TArray ids = ExtractIdsWith(access, source); - AssertThat(ids.Contains(id1), Is().True()); - AssertThat(ids.Contains(id2), Is().True()); - AssertThat(ids.Contains(id3), Is().False()); - AssertThat(source.Contains(id1), Is().False()); - AssertThat(source.Contains(id2), Is().False()); - AssertThat(source.Contains(id3), Is().True()); + Expect(ids.Contains(id1)).ToBeTrue(); + Expect(ids.Contains(id2)).ToBeTrue(); + Expect(ids.Contains(id3)).ToBeFalse(); + Expect(source.Contains(id1)).ToBeFalse(); + Expect(source.Contains(id2)).ToBeFalse(); + Expect(source.Contains(id3)).ToBeTrue(); }); - it("Finds and removes ids not containing a component from a list", [&]() + It("Finds and removes ids not containing a component from a list", []() { TArray source{id1, id2, id3}; TIdScope access{ctx}; TArray ids = ExtractIdsWithout(access, source); - AssertThat(ids.Contains(id1), Is().False()); - AssertThat(ids.Contains(id2), Is().False()); - AssertThat(ids.Contains(id3), Is().True()); - AssertThat(source.Contains(id1), Is().True()); - AssertThat(source.Contains(id2), Is().True()); - AssertThat(source.Contains(id3), Is().False()); + Expect(ids.Contains(id1)).ToBeFalse(); + Expect(ids.Contains(id2)).ToBeFalse(); + Expect(ids.Contains(id3)).ToBeTrue(); + Expect(source.Contains(id1)).ToBeTrue(); + Expect(source.Contains(id2)).ToBeTrue(); + Expect(source.Contains(id3)).ToBeFalse(); }); }); - it("Can filter directly from ECS", [&]() + It("Can filter directly from ECS", []() { TArray ids1 = FindAllIdsWith(ctx); - AssertThat(ids1.Contains(id1), Is().True()); + Expect(ids1.Contains(id1)).ToBeTrue(); TArray ids2 = FindAllIdsWithAny(ctx); - AssertThat(ids2.Contains(id1), Is().True()); + Expect(ids2.Contains(id1)).ToBeTrue(); TArray ids3 = FindAllIdsWithAny(ctx); ExcludeIdsWith(ctx, ids3); - AssertThat(ids3.Contains(id1), Is().True()); + Expect(ids3.Contains(id1)).ToBeTrue(); TArray ids4 = FindAllIdsWithAny(ctx); ExcludeIdsWithout(ctx, ids4); - AssertThat(ids4.Contains(id1), Is().False()); + Expect(ids4.Contains(id1)).ToBeFalse(); }); - it("Can filter CRemoved", [&]() + It("Can filter CRemoved", []() { RmId(ctx, id1); RmId(ctx, id2); RmId(ctx, id3); TArray ids1 = FindAllIdsWith(ctx); - AssertThat(ids1.Contains(id1), Is().True()); + Expect(ids1.Contains(id1)).ToBeTrue(); TArray ids2 = FindAllIdsWith(ctx); - AssertThat(ids2.Contains(id1), Is().True()); - AssertThat(ids2.Contains(id2), Is().True()); - AssertThat(ids2.Contains(id3), Is().True()); - AssertThat(ids2.Size(), Equals(3)); + Expect(ids2.Contains(id1)).ToBeTrue(); + Expect(ids2.Contains(id2)).ToBeTrue(); + Expect(ids2.Contains(id3)).ToBeTrue(); + Expect(ids2.Size()).ToEqual(3); TArray ids3 = FindAllIdsWith(ctx); - AssertThat(ids3.Contains(id1), Is().True()); - AssertThat(ids3.Contains(id2), Is().True()); + Expect(ids3.Contains(id1)).ToBeTrue(); + Expect(ids3.Contains(id2)).ToBeTrue(); }); }); -}); +} diff --git a/Tests/ECS/Hierarchy.spec.cpp b/Tests/ECS/Hierarchy.spec.cpp index 5e0cfe01..5d30eca7 100644 --- a/Tests/ECS/Hierarchy.spec.cpp +++ b/Tests/ECS/Hierarchy.spec.cpp @@ -1,42 +1,29 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include "bandit/grammar.h" -#include +#include #include -using namespace snowhouse; -using namespace bandit; using namespace p; -namespace snowhouse + +namespace { - template<> - struct Stringizer - { - static std::string ToString(Id id) - { - std::stringstream stream; - stream << "Id(" << id.value << ")"; - return stream.str(); - } - }; -} // namespace snowhouse + IdContext ctx; + Id root; + Id child1; + Id child2; + Id child3; + Id grandchild; +} // namespace -go_bandit([]() +void RegisterECSHierarchyTests() { - describe("ECS.Hierarchy", []() + Spec("ECS.Hierarchy", []() { - IdContext ctx; - Id root; - Id child1; - Id child2; - Id child3; - Id grandchild; - - before_each([&]() + BeforeEach([]() { ctx = {}; root = AddId(ctx); @@ -46,284 +33,281 @@ go_bandit([]() grandchild = AddId(ctx); }); - describe("AttachId", [&]() + Describe("AttachId", []() { - it("Creates bidirectional parent-child link for single child", [&]() + It("Creates bidirectional parent-child link for single child", []() { AttachId({ctx}, root, child1); - AssertThat(ctx.Has(root), Is().True()); - AssertThat(ctx.Has(child1), Is().True()); - AssertThat(ctx.Get(root).children.Size(), Equals(1)); - AssertThat(ctx.Get(root).children[0], Equals(child1)); - AssertThat(ctx.Get(child1).parent, Equals(root)); + Expect(ctx.Has(root)).ToBeTrue(); + Expect(ctx.Has(child1)).ToBeTrue(); + Expect(ctx.Get(root).children.Size()).ToEqual(1); + Expect(ctx.Get(root).children[0]).ToEqual(child1); + Expect(ctx.Get(child1).parent).ToEqual(root); }); - it("Appends multiple children to same parent", [&]() + It("Appends multiple children to same parent", []() { AttachId({ctx}, root, {child1, child2, child3}); - AssertThat(ctx.Has(root), Is().True()); - AssertThat(ctx.Has(child1), Is().True()); - AssertThat(ctx.Has(child2), Is().True()); - AssertThat(ctx.Has(child3), Is().True()); - AssertThat(ctx.Get(root).children.Size(), Equals(3)); - AssertThat(ctx.Get(root).children[0], Equals(child1)); - AssertThat(ctx.Get(root).children[1], Equals(child2)); - AssertThat(ctx.Get(root).children[2], Equals(child3)); - AssertThat(ctx.Get(child1).parent, Equals(root)); - AssertThat(ctx.Get(child2).parent, Equals(root)); - AssertThat(ctx.Get(child3).parent, Equals(root)); + Expect(ctx.Has(root)).ToBeTrue(); + Expect(ctx.Has(child1)).ToBeTrue(); + Expect(ctx.Has(child2)).ToBeTrue(); + Expect(ctx.Has(child3)).ToBeTrue(); + Expect(ctx.Get(root).children.Size()).ToEqual(3); + Expect(ctx.Get(root).children[0]).ToEqual(child1); + Expect(ctx.Get(root).children[1]).ToEqual(child2); + Expect(ctx.Get(root).children[2]).ToEqual(child3); + Expect(ctx.Get(child1).parent).ToEqual(root); + Expect(ctx.Get(child2).parent).ToEqual(root); + Expect(ctx.Get(child3).parent).ToEqual(root); }); }); - describe("AttachIdAfter", [&]() + Describe("AttachIdAfter", []() { - it("Inserts child after specified sibling preserving order", [&]() + It("Inserts child after specified sibling preserving order", []() { AttachId({ctx}, root, {child1, child3}); AttachIdAfter({ctx}, root, child2, child1); - AssertThat(ctx.Get(root).children.Size(), Equals(3)); - AssertThat(ctx.Get(root).children.FindIndex(child2), Equals(1)); + Expect(ctx.Get(root).children.Size()).ToEqual(3); + Expect(ctx.Get(root).children.FindIndex(child2)).ToEqual(1); }); }); - describe("TransferIdChildren", [&]() + Describe("TransferIdChildren", []() { - it("Moves children from old parent to new parent", [&]() + It("Moves children from old parent to new parent", []() { Id newRoot = AddId(ctx); AttachId({ctx}, root, {child1, child2}); TransferIdChildren({ctx}, {child1, child2}, newRoot); - AssertThat(ctx.Get(root).children.IsEmpty(), Is().True()); - AssertThat(ctx.Has(newRoot), Is().True()); - AssertThat(ctx.Get(newRoot).children.Size(), Equals(2)); - AssertThat(ctx.Get(child1).parent, Equals(newRoot)); - AssertThat(ctx.Get(child2).parent, Equals(newRoot)); + Expect(ctx.Get(root).children.IsEmpty()).ToBeTrue(); + Expect(ctx.Has(newRoot)).ToBeTrue(); + Expect(ctx.Get(newRoot).children.Size()).ToEqual(2); + Expect(ctx.Get(child1).parent).ToEqual(newRoot); + Expect(ctx.Get(child2).parent).ToEqual(newRoot); }); }); - describe("DetachIdParent", [&]() + Describe("DetachIdParent", []() { - before_each([&]() + BeforeEach([]() { AttachId({ctx}, root, {child1, child2}); }); - it("Retains CChild component when keepComponents is true", [&]() + It("Retains CChild component when keepComponents is true", []() { DetachIdParent({ctx}, child1, true); - AssertThat(ctx.Has(child1), Is().True()); - AssertThat(ctx.Get(child1).parent, Equals(NoId)); - AssertThat(ctx.Get(root).children.Size(), Equals(1)); + Expect(ctx.Has(child1)).ToBeTrue(); + Expect(ctx.Get(child1).parent).ToEqual(NoId); + Expect(ctx.Get(root).children.Size()).ToEqual(1); }); - it("Removes CChild from detached child and removes from parent list", [&]() + It("Removes CChild from detached child and removes from parent list", []() { DetachIdParent({ctx}, child1, false); - AssertThat(ctx.Has(child1), Is().False()); - AssertThat(ctx.Get(root).children.Contains(child1), Is().False()); + Expect(ctx.Has(child1)).ToBeFalse(); + Expect(ctx.Get(root).children.Contains(child1)).ToBeFalse(); }); - it("Removes empty CParent when all children are detached", [&]() + It("Removes empty CParent when all children are detached", []() { DetachIdParent({ctx}, {child1, child2}, false); - AssertThat(ctx.Has(child1), Is().False()); - AssertThat(ctx.Has(child2), Is().False()); - AssertThat(ctx.Has(root), Is().False()); + Expect(ctx.Has(child1)).ToBeFalse(); + Expect(ctx.Has(child2)).ToBeFalse(); + Expect(ctx.Has(root)).ToBeFalse(); }); }); - describe("DetachIdChildren", [&]() + Describe("DetachIdChildren", []() { - before_each([&]() + BeforeEach([]() { AttachId({ctx}, root, {child1, child2}); }); - it("Severes all children but retains CChild when keepComponents is true", [&]() + It("Severes all children but retains CChild when keepComponents is true", []() { DetachIdChildren({ctx}, root, true); - AssertThat(ctx.Has(child1), Is().True()); - AssertThat(ctx.Has(child2), Is().True()); - AssertThat(ctx.Get(child1).parent, Equals(NoId)); - AssertThat(ctx.Get(child2).parent, Equals(NoId)); - AssertThat(ctx.Get(root).children.IsEmpty(), Is().True()); + Expect(ctx.Has(child1)).ToBeTrue(); + Expect(ctx.Has(child2)).ToBeTrue(); + Expect(ctx.Get(child1).parent).ToEqual(NoId); + Expect(ctx.Get(child2).parent).ToEqual(NoId); + Expect(ctx.Get(root).children.IsEmpty()).ToBeTrue(); }); - it("Removes CChild and CParent when keepComponents is false", [&]() + It("Removes CChild and CParent when keepComponents is false", []() { DetachIdChildren({ctx}, root, false); - AssertThat(ctx.Has(child1), Is().False()); - AssertThat(ctx.Has(child2), Is().False()); - AssertThat(ctx.Has(root), Is().False()); + Expect(ctx.Has(child1)).ToBeFalse(); + Expect(ctx.Has(child2)).ToBeFalse(); + Expect(ctx.Has(root)).ToBeFalse(); }); }); - describe("GetIdChildren", [&]() + Describe("GetIdChildren", []() { - before_each([&]() + BeforeEach([]() { AttachId({ctx}, root, {child1, child2}); AttachId({ctx}, child1, grandchild); }); - it("Returns child list for parent entities", [&]() + It("Returns child list for parent entities", []() { const auto* children = GetIdChildren({ctx}, root); - AssertThat(children, !Equals(nullptr)); - AssertThat(children->Size(), Equals(2)); - AssertThat(children->Contains(child1), Is().True()); - AssertThat(children->Contains(child2), Is().True()); + Expect(children).ToNotEqual(nullptr); + Expect(children->Size()).ToEqual(2); + Expect(children->Contains(child1)).ToBeTrue(); + Expect(children->Contains(child2)).ToBeTrue(); }); - it("Combines children from multiple parents into one list", [&]() + It("Combines children from multiple parents into one list", []() { TArray outChildren; GetIdChildren({ctx}, {root, child1}, outChildren); - AssertThat(outChildren.Size(), Equals(3)); - AssertThat(outChildren.Contains(grandchild), Is().True()); + Expect(outChildren.Size()).ToEqual(3); + Expect(outChildren.Contains(grandchild)).ToBeTrue(); }); - it("Returns null for entities without CParent component", [&]() + It("Returns null for entities without CParent component", []() { - AssertThat(GetIdChildren({ctx}, child2), Equals(nullptr)); + Expect(GetIdChildren({ctx}, child2)).ToEqual(nullptr); }); }); - describe("GetAllIdChildren", [&]() + Describe("GetAllIdChildren", []() { - before_each([&]() + BeforeEach([]() { AttachId({ctx}, root, child1); AttachId({ctx}, child1, grandchild); }); - it("Recurses full tree depth to collect all descendents", [&]() + It("Recurses full tree depth to collect all descendents", []() { TArray outChildren; GetAllIdChildren({ctx}, root, outChildren, 10); - AssertThat(outChildren.Size(), Equals(2)); - AssertThat(outChildren.Contains(grandchild), Is().True()); + Expect(outChildren.Size()).ToEqual(2); + Expect(outChildren.Contains(grandchild)).ToBeTrue(); }); - it("Respects depth limit to return only immediate children", [&]() + It("Respects depth limit to return only immediate children", []() { TArray outChildren; GetAllIdChildren({ctx}, root, outChildren, 1); - AssertThat(outChildren.Size(), Equals(1)); - AssertThat(outChildren.Contains(grandchild), Is().False()); + Expect(outChildren.Size()).ToEqual(1); + Expect(outChildren.Contains(grandchild)).ToBeFalse(); }); }); - describe("GetIdParent", [&]() + Describe("GetIdParent", []() { - before_each([&]() + BeforeEach([]() { AttachId({ctx}, root, child1); AttachId({ctx}, child1, grandchild); }); - it("Returns parent Id for child entities", [&]() + It("Returns parent Id for child entities", []() { - AssertThat(GetIdParent({ctx}, child1), Equals(root)); - AssertThat(GetIdParent({ctx}, grandchild), Equals(child1)); + Expect(GetIdParent({ctx}, child1)).ToEqual(root); + Expect(GetIdParent({ctx}, grandchild)).ToEqual(child1); }); - it("Returns unique parents for multiple children", [&]() + It("Returns unique parents for multiple children", []() { TArray outParents; GetIdParent({ctx}, {child1, grandchild}, outParents); - AssertThat(outParents.Size(), Equals(2)); - AssertThat(outParents.Contains(root), Is().True()); - AssertThat(outParents.Contains(child1), Is().True()); + Expect(outParents.Size()).ToEqual(2); + Expect(outParents.Contains(root)).ToBeTrue(); + Expect(outParents.Contains(child1)).ToBeTrue(); }); - it("Returns NoId for root entities without parent", [&]() + It("Returns NoId for root entities without parent", []() { - AssertThat(GetIdParent({ctx}, root), Equals(NoId)); + Expect(GetIdParent({ctx}, root)).ToEqual(NoId); }); - it("Returns NoId for entities without CChild component", [&]() + It("Returns NoId for entities without CChild component", []() { - AssertThat(GetIdParent({ctx}, child2), Equals(NoId)); + Expect(GetIdParent({ctx}, child2)).ToEqual(NoId); }); }); - describe("GetAllIdParents", [&]() + Describe("GetAllIdParents", []() { - before_each([&]() + BeforeEach([]() { AttachId({ctx}, root, child1); AttachId({ctx}, child1, grandchild); }); - it("Traverses full ancestry chain from leaf to root", [&]() + It("Traverses full ancestry chain from leaf to root", []() { TArray outParents; GetAllIdParents({ctx}, grandchild, outParents); - AssertThat(outParents.Size(), Equals(2)); - AssertThat(outParents[0], Equals(child1)); - AssertThat(outParents[1], Equals(root)); + Expect(outParents.Size()).ToEqual(2); + Expect(outParents[0]).ToEqual(child1); + Expect(outParents[1]).ToEqual(root); }); - it("Returns empty when entity has no CChild component", [&]() + It("Returns empty when entity has no CChild component", []() { TArray outParents; GetAllIdParents({ctx}, child2, outParents); - AssertThat(outParents.IsEmpty(), Is().True()); + Expect(outParents.IsEmpty()).ToBeTrue(); }); }); - describe("FindIdParent", [&]() + Describe("FindIdParent", []() { - before_each([&]() + BeforeEach([]() { AttachId({ctx}, root, child1); AttachId({ctx}, child1, grandchild); }); - it("Finds ancestor two levels up matching predicate", [&]() + It("Finds ancestor two levels up matching predicate", []() { - AssertThat(FindIdParent({ctx}, grandchild, + Expect(FindIdParent({ctx}, grandchild, [&](Id id) { return id == root; - }), - Equals(root)); + })).ToEqual(root); }); - it("Finds immediate parent matching predicate", [&]() + It("Finds immediate parent matching predicate", []() { - AssertThat(FindIdParent({ctx}, grandchild, + Expect(FindIdParent({ctx}, grandchild, [&](Id id) { return id == child1; - }), - Equals(child1)); + })).ToEqual(child1); }); - it("Returns NoId when no ancestor matches predicate", [&]() + It("Returns NoId when no ancestor matches predicate", []() { - AssertThat(IsNone(FindIdParent({ctx}, grandchild, + Expect(IsNone(FindIdParent({ctx}, grandchild, [](Id) { return false; - })), - Is().True()); + }))).ToBeTrue(); }); }); - describe("FindIdParents", [&]() + Describe("FindIdParents", []() { - it("Finds nearest matching ancestor for deep entity", [&]() + It("Finds nearest matching ancestor for deep entity", []() { Id intermediate = AddId(ctx); AttachId({ctx}, root, child1); @@ -335,41 +319,41 @@ go_bandit([]() { return true; }); - AssertThat(outParents.Size(), Equals(1)); - AssertThat(outParents.Contains(intermediate), Is().True()); + Expect(outParents.Size()).ToEqual(1); + Expect(outParents.Contains(intermediate)).ToBeTrue(); }); - it("Returns empty when no ancestor matches predicate", [&]() + It("Returns empty when no ancestor matches predicate", []() { TArray outParents; FindIdParents({ctx}, child1, outParents, [](Id) { return false; }); - AssertThat(outParents.IsEmpty(), Is().True()); + Expect(outParents.IsEmpty()).ToBeTrue(); }); }); - describe("GetIdRoots", [&]() + Describe("GetIdRoots", []() { - it("Returns empty when no hierarchy exists", [&]() + It("Returns empty when no hierarchy exists", []() { TArray roots; GetIdRoots({ctx}, roots); - AssertThat(roots.IsEmpty(), Is().True()); + Expect(roots.IsEmpty()).ToBeTrue(); }); - it("Finds root of single-parent hierarchy", [&]() + It("Finds root of single-parent hierarchy", []() { AttachId({ctx}, root, {child1, child2}); TArray roots; GetIdRoots({ctx}, roots); - AssertThat(roots.Size(), Equals(1)); - AssertThat(roots.Contains(root), Is().True()); + Expect(roots.Size()).ToEqual(1); + Expect(roots.Contains(root)).ToBeTrue(); }); - it("Returns multiple roots from independent trees", [&]() + It("Returns multiple roots from independent trees", []() { Id root2 = AddId(ctx); AttachId({ctx}, root, {child1, child2}); @@ -377,41 +361,41 @@ go_bandit([]() TArray roots; GetIdRoots({ctx}, roots); - AssertThat(roots.Size(), Equals(2)); - AssertThat(roots.Contains(root), Is().True()); - AssertThat(roots.Contains(root2), Is().True()); + Expect(roots.Size()).ToEqual(2); + Expect(roots.Contains(root)).ToBeTrue(); + Expect(roots.Contains(root2)).ToBeTrue(); }); - it("Excludes entities that are both parent and child of someone", [&]() + It("Excludes entities that are both parent and child of someone", []() { AttachId({ctx}, root, child1); AttachId({ctx}, child1, grandchild); TArray roots; GetIdRoots({ctx}, roots); - AssertThat(roots.Size(), Equals(1)); - AssertThat(roots.Contains(root), Is().True()); - AssertThat(roots.Contains(child1), Is().False()); + Expect(roots.Size()).ToEqual(1); + Expect(roots.Contains(root)).ToBeTrue(); + Expect(roots.Contains(child1)).ToBeFalse(); }); }); - describe("GetIdParentRoots", [&]() + Describe("GetIdParentRoots", []() { - before_each([&]() + BeforeEach([]() { AttachId({ctx}, root, child1); AttachId({ctx}, child1, grandchild); }); - it("Walks child chain up to root ancestor", [&]() + It("Walks child chain up to root ancestor", []() { TArray roots; GetIdParentRoots({ctx}, grandchild, roots, false); - AssertThat(roots.Size(), Equals(1)); - AssertThat(roots.Contains(root), Is().True()); + Expect(roots.Size()).ToEqual(1); + Expect(roots.Contains(root)).ToBeTrue(); }); - it("Handles children from different trees", [&]() + It("Handles children from different trees", []() { Id root2 = AddId(ctx); Id childOf2 = AddId(ctx); @@ -419,90 +403,90 @@ go_bandit([]() TArray roots; GetIdParentRoots({ctx}, {grandchild, childOf2}, roots, false); - AssertThat(roots.Size(), Equals(2)); - AssertThat(roots.Contains(root), Is().True()); - AssertThat(roots.Contains(root2), Is().True()); + Expect(roots.Size()).ToEqual(2); + Expect(roots.Contains(root)).ToBeTrue(); + Expect(roots.Contains(root2)).ToBeTrue(); }); - it("Considers input entities as roots when considerChildren flag is set", [&]() + It("Considers input entities as roots when considerChildren flag is set", []() { TArray roots; GetIdParentRoots({ctx}, {root, grandchild}, roots, true); - AssertThat(roots.Size(), Equals(1)); - AssertThat(roots.Contains(root), Is().True()); + Expect(roots.Size()).ToEqual(1); + Expect(roots.Contains(root)).ToBeTrue(); }); - it("Returns empty for empty input", [&]() + It("Returns empty for empty input", []() { TArray roots; GetIdParentRoots({ctx}, {}, roots, false); - AssertThat(roots.IsEmpty(), Is().True()); + Expect(roots.IsEmpty()).ToBeTrue(); }); - it("Returns empty for entities with no parent", [&]() + It("Returns empty for entities with no parent", []() { TArray roots; GetIdParentRoots({ctx}, child2, roots, false); - AssertThat(roots.IsEmpty(), Is().True()); + Expect(roots.IsEmpty()).ToBeTrue(); }); }); - describe("FixParentIdLinks", [&]() + Describe("FixParentIdLinks", []() { - before_each([&]() + BeforeEach([]() { AttachId({ctx}, root, child1); }); - it("Returns false when parent-child links are already correct", [&]() + It("Returns false when parent-child links are already correct", []() { - AssertThat(FixParentIdLinks({ctx}, root), Is().False()); + Expect(FixParentIdLinks({ctx}, root)).ToBeFalse(); }); - it("Fixes child->parent reference when it does not match parent's list", [&]() + It("Fixes child->parent reference when it does not match parent's list", []() { ctx.Get(child1).parent = NoId; - AssertThat(FixParentIdLinks({ctx}, root), Is().True()); - AssertThat(ctx.Get(child1).parent, Equals(root)); + Expect(FixParentIdLinks({ctx}, root)).ToBeTrue(); + Expect(ctx.Get(child1).parent).ToEqual(root); }); - it("Adds missing CChild component to orphan children", [&]() + It("Adds missing CChild component to orphan children", []() { ctx.Remove(child1); - AssertThat(ctx.Has(child1), Is().False()); + Expect(ctx.Has(child1)).ToBeFalse(); - AssertThat(FixParentIdLinks({ctx}, root), Is().True()); - AssertThat(ctx.Has(child1), Is().True()); - AssertThat(ctx.Get(child1).parent, Equals(root)); + Expect(FixParentIdLinks({ctx}, root)).ToBeTrue(); + Expect(ctx.Has(child1)).ToBeTrue(); + Expect(ctx.Get(child1).parent).ToEqual(root); }); }); - describe("ValidateParentIdLinks", [&]() + Describe("ValidateParentIdLinks", []() { - before_each([&]() + BeforeEach([]() { AttachId({ctx}, root, child1); }); - it("Returns true when all parent-child links are consistent", [&]() + It("Returns true when all parent-child links are consistent", []() { - AssertThat(ValidateParentIdLinks({ctx}, root), Is().True()); + Expect(ValidateParentIdLinks({ctx}, root)).ToBeTrue(); }); - it("Returns false when child->parent reference is mismatched", [&]() + It("Returns false when child->parent reference is mismatched", []() { ctx.Get(child1).parent = NoId; - AssertThat(ValidateParentIdLinks({ctx}, root), Is().False()); + Expect(ValidateParentIdLinks({ctx}, root)).ToBeFalse(); }); - it("Returns false when CChild component is missing from child", [&]() + It("Returns false when CChild component is missing from child", []() { ctx.Remove(child1); - AssertThat(ValidateParentIdLinks({ctx}, root), Is().False()); + Expect(ValidateParentIdLinks({ctx}, root)).ToBeFalse(); }); }); }); -}); +} diff --git a/Tests/ECS/IdRegistry.spec.cpp b/Tests/ECS/IdRegistry.spec.cpp index 33326f54..b3fe4cac 100644 --- a/Tests/ECS/IdRegistry.spec.cpp +++ b/Tests/ECS/IdRegistry.spec.cpp @@ -1,178 +1,162 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include -using namespace snowhouse; -using namespace bandit; using namespace p; using namespace std::chrono_literals; -namespace snowhouse -{ - template<> - struct Stringizer - { - static std::string ToString(Id id) - { - std::stringstream stream; - stream << "Id(" << id.value << ")"; - return stream.str(); - } - }; -} // namespace snowhouse - -go_bandit([]() +void RegisterECSIdRegistryTests() { - describe("ECS.IdRegistry", []() + Spec("ECS.IdRegistry", []() { - it("Can create one id", [&]() + It("Can create one id", []() { IdRegistry ids; - AssertThat(ids.Size(), Equals(0)); + Expect(ids.Size()).ToEqual(0); Id id = ids.Create(); - AssertThat(id, !Equals(NoId)); - AssertThat(ids.IsValid(id), Is().True()); - AssertThat(ids.Size(), Equals(1)); + Expect(id).ToNotEqual(NoId); + Expect(ids.IsValid(id)).ToBeTrue(); + Expect(ids.Size()).ToEqual(1); }); - it("Can remove one id", [&]() + It("Can remove one id", []() { IdRegistry ids; Id id = ids.Create(); - AssertThat(ids.Size(), Equals(1)); - AssertThat(ids.RemoveInstant(id), Is().True()); - AssertThat(ids.IsValid(id), Is().False()); - AssertThat(ids.Size(), Equals(0)); + Expect(ids.Size()).ToEqual(1); + Expect(ids.RemoveInstant(id)).ToBeTrue(); + Expect(ids.IsValid(id)).ToBeFalse(); + Expect(ids.Size()).ToEqual(0); }); - it("Can create two and remove first", [&]() + It("Can create two and remove first", []() { IdRegistry ids; Id id1 = ids.Create(); ids.Create(); - AssertThat(ids.RemoveInstant(id1), Is().True()); - AssertThat(ids.IsValid(id1), Is().False()); - AssertThat(ids.Size(), Equals(1)); + Expect(ids.RemoveInstant(id1)).ToBeTrue(); + Expect(ids.IsValid(id1)).ToBeFalse(); + Expect(ids.Size()).ToEqual(1); }); - it("Can create two and remove last", [&]() + It("Can create two and remove last", []() { IdRegistry ids; ids.Create(); Id id2 = ids.Create(); - AssertThat(ids.RemoveInstant(id2), Is().True()); - AssertThat(ids.IsValid(id2), Is().False()); - AssertThat(ids.Size(), Equals(1)); + Expect(ids.RemoveInstant(id2)).ToBeTrue(); + Expect(ids.IsValid(id2)).ToBeFalse(); + Expect(ids.Size()).ToEqual(1); }); - it("Can remove one id (deferred)", [&]() + It("Can remove one id (deferred)", []() { IdRegistry ids; Id id = ids.Create(); - AssertThat(ids.Size(), Equals(1)); - AssertThat(ids.Remove(id), Is().True()); - AssertThat(ids.IsValid(id), Is().False()); - AssertThat(ids.Size(), Equals(0)); + Expect(ids.Size()).ToEqual(1); + Expect(ids.Remove(id)).ToBeTrue(); + Expect(ids.IsValid(id)).ToBeFalse(); + Expect(ids.Size()).ToEqual(0); }); - it("Can create two and remove first (deferred)", [&]() + It("Can create two and remove first (deferred)", []() { IdRegistry ids; Id id1 = ids.Create(); ids.Create(); - AssertThat(ids.Remove(id1), Is().True()); - AssertThat(ids.IsValid(id1), Is().False()); - AssertThat(ids.Size(), Equals(1)); + Expect(ids.Remove(id1)).ToBeTrue(); + Expect(ids.IsValid(id1)).ToBeFalse(); + Expect(ids.Size()).ToEqual(1); }); - it("Can create two and remove last (deferred)", [&]() + It("Can create two and remove last (deferred)", []() { IdRegistry ids; ids.Create(); Id id2 = ids.Create(); - AssertThat(ids.Remove(id2), Is().True()); - AssertThat(ids.IsValid(id2), Is().False()); - AssertThat(ids.Size(), Equals(1)); + Expect(ids.Remove(id2)).ToBeTrue(); + Expect(ids.IsValid(id2)).ToBeFalse(); + Expect(ids.Size()).ToEqual(1); }); - it("Removed id index gets reused", [&]() + It("Removed id index gets reused", []() { IdRegistry ids; ids.Create(); Id id = ids.Create(); ids.Create(); - AssertThat(ids.RemoveInstant(id), Is().True()); + Expect(ids.RemoveInstant(id)).ToBeTrue(); Id id2 = ids.Create(); - AssertThat(id2.GetIndex(), Equals(id.GetIndex())); + Expect(id2.GetIndex()).ToEqual(id.GetIndex()); Id id3 = ids.Create(); - AssertThat(id3.GetIndex(), !Equals(id.GetIndex())); + Expect(id3.GetIndex()).ToNotEqual(id.GetIndex()); }); - it("Deferred removed id index doesn't get reused until flushed", [&]() + It("Deferred removed id index doesn't get reused until flushed", []() { IdRegistry ids; ids.Create(); Id id = ids.Create(); ids.Create(); - AssertThat(ids.Remove(id), Is().True()); + Expect(ids.Remove(id)).ToBeTrue(); Id id2 = ids.Create(); - AssertThat(id2.GetIndex(), !Equals(id.GetIndex())); + Expect(id2.GetIndex()).ToNotEqual(id.GetIndex()); ids.FlushDeferredRemovals(); Id id3 = ids.Create(); - AssertThat(id3.GetIndex(), Equals(id.GetIndex())); + Expect(id3.GetIndex()).ToEqual(id.GetIndex()); Id id4 = ids.Create(); - AssertThat(id4.GetIndex(), !Equals(id.GetIndex())); + Expect(id4.GetIndex()).ToNotEqual(id.GetIndex()); }); - it("Can create many ids", [&]() + It("Can create many ids", []() { IdRegistry ids; - AssertThat(ids.Size(), Equals(0)); + Expect(ids.Size()).ToEqual(0); TArray list(3); ids.Create(list); - AssertThat(ids.Size(), Equals(3)); + Expect(ids.Size()).ToEqual(3); for (i32 i = 0; i < list.Size(); ++i) { - AssertThat(list[i].GetIndex(), Equals(i)); - AssertThat(ids.IsValid(list[i]), Is().True()); + Expect(list[i].GetIndex()).ToEqual(i); + Expect(ids.IsValid(list[i])).ToBeTrue(); } }); - it("Can remove many ids", [&]() + It("Can remove many ids", []() { IdRegistry ids; TArray list(3); ids.Create(list); - AssertThat(ids.Size(), Equals(3)); + Expect(ids.Size()).ToEqual(3); - AssertThat(ids.RemoveInstant(list), Is().True()); - AssertThat(ids.Size(), Equals(0)); + Expect(ids.RemoveInstant(list)).ToBeTrue(); + Expect(ids.Size()).ToEqual(0); for (i32 i = 0; i < list.Size(); ++i) { - AssertThat(ids.IsValid(list[i]), Is().False()); + Expect(ids.IsValid(list[i])).ToBeFalse(); } }); - it("Can remove many ids (deferred)", [&]() + It("Can remove many ids (deferred)", []() { IdRegistry ids; TArray list(3); ids.Create(list); - AssertThat(ids.Size(), Equals(3)); + Expect(ids.Size()).ToEqual(3); - AssertThat(ids.Remove(list), Is().True()); - AssertThat(ids.Size(), Equals(0)); + Expect(ids.Remove(list)).ToBeTrue(); + Expect(ids.Size()).ToEqual(0); for (i32 i = 0; i < list.Size(); ++i) { - AssertThat(ids.IsValid(list[i]), Is().False()); + Expect(ids.IsValid(list[i])).ToBeFalse(); } }); }); -}); +} diff --git a/Tests/ECS/IdScopes.spec.cpp b/Tests/ECS/IdScopes.spec.cpp index 232872be..5c355d69 100644 --- a/Tests/ECS/IdScopes.spec.cpp +++ b/Tests/ECS/IdScopes.spec.cpp @@ -1,11 +1,9 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include -using namespace snowhouse; -using namespace bandit; using namespace p; @@ -23,121 +21,121 @@ struct ScopeTypeC }; -go_bandit([]() +void RegisterECSIdScopesTests() { - describe("ECS.IdScopes", []() + Spec("ECS.IdScopes", []() { - describe("Templated", []() + Describe("Templated", []() { - it("Can cache pools", [&]() + It("Can cache pools", []() { IdContext ctx; TIdScope> scope{ctx}; - AssertThat(scope.GetPool(), Equals(ctx.GetPool())); - AssertThat(scope.GetPool(), Equals(ctx.GetPool())); - AssertThat(scope.GetPool(), Equals(ctx.GetPool())); + Expect(scope.GetPool()).ToEqual(ctx.GetPool()); + Expect(scope.GetPool()).ToEqual(ctx.GetPool()); + Expect(scope.GetPool()).ToEqual(ctx.GetPool()); }); - it("Can check if contained", [&]() + It("Can check if contained", []() { IdContext ctx; TPool& pool = ctx.AssurePool(); TIdScope> scope{ctx}; TIdScope scopeConst{ctx}; Id id = NoId; - AssertThat(scope.Has(id), Is().False()); - AssertThat(scopeConst.Has(id), Is().False()); + Expect(scope.Has(id)).ToBeFalse(); + Expect(scopeConst.Has(id)).ToBeFalse(); id = AddId(ctx); - AssertThat(scope.Has(id), Is().False()); - AssertThat(scopeConst.Has(id), Is().False()); + Expect(scope.Has(id)).ToBeFalse(); + Expect(scopeConst.Has(id)).ToBeFalse(); ctx.Add(id); - AssertThat(scope.Has(id), Is().True()); - AssertThat(scopeConst.Has(id), Is().True()); + Expect(scope.Has(id)).ToBeTrue(); + Expect(scopeConst.Has(id)).ToBeTrue(); TIdScope scope2{ctx}; ctx.Add(id); - AssertThat(scope2.Has(id), Is().True()); + Expect(scope2.Has(id)).ToBeTrue(); }); - it("Can initialize superset", [&]() + It("Can initialize superset", []() { IdContext ctx; TPool& typePool = ctx.AssurePool(); TIdScope> scope1{ctx}; TIdScope> superset1{scope1}; - AssertThat(superset1.GetPool(), Equals(&typePool)); + Expect(superset1.GetPool()).ToEqual(&typePool); TIdScope> scope2{ctx}; TIdScope superset2{scope2}; - AssertThat(superset2.GetPool(), Equals(&typePool)); + Expect(superset2.GetPool()).ToEqual(&typePool); TIdScope> scope3{ctx}; TIdScope superset3{scope3}; - AssertThat(superset1.GetPool(), Equals(&typePool)); + Expect(superset1.GetPool()).ToEqual(&typePool); }); - it("Can mark modify", [&]() + It("Can mark modify", []() { IdContext ctx; Id id = AddId(ctx); TIdScope>> scope1{ctx}; - AssertThat(scope1.Has>(id), Is().False()); + Expect(scope1.Has>(id)).ToBeFalse(); scope1.Modify(id); - AssertThat(scope1.Has>(id), Is().True()); - AssertThat(scope1.IsModified(id), Is().True()); + Expect(scope1.Has>(id)).ToBeTrue(); + Expect(scope1.IsModified(id)).ToBeTrue(); scope1.Remove>(id); - AssertThat(scope1.Has>(id), Is().False()); - AssertThat(scope1.IsModified(id), Is().False()); + Expect(scope1.Has>(id)).ToBeFalse(); + Expect(scope1.IsModified(id)).ToBeFalse(); scope1.Modify(id); - AssertThat(scope1.Has>(id), Is().True()); - AssertThat(scope1.IsModified(id), Is().True()); + Expect(scope1.Has>(id)).ToBeTrue(); + Expect(scope1.IsModified(id)).ToBeTrue(); }); - it("Can mark modify automatically", [&]() + It("Can mark modify automatically", []() { IdContext ctx; Id id = AddId(ctx); using MyScope = TIdScope, CMdfd>; MyScope scope{ctx}; - AssertThat(MyScope::WDependencies::Contains>(), Is().True()); - AssertThat(MyScope::WDependencies::Contains>(), Is().False()); - AssertThat(MyScope::RWDependencies::Contains>(), Is().True()); - AssertThat(MyScope::RWDependencies::Contains>(), Is().True()); + Expect(MyScope::WDependencies::Contains>()).ToBeTrue(); + Expect(MyScope::WDependencies::Contains>()).ToBeFalse(); + Expect(MyScope::RWDependencies::Contains>()).ToBeTrue(); + Expect(MyScope::RWDependencies::Contains>()).ToBeTrue(); scope.Add(id); // Type B should be auto modified - AssertThat(scope.IsModified(id), Is().True()); + Expect(scope.IsModified(id)).ToBeTrue(); scope.Add(id); // Type B should not be auto modified - AssertThat(scope.IsModified(id), Is().False()); + Expect(scope.IsModified(id)).ToBeFalse(); scope.ClearPool>(); - AssertThat(scope.IsModified(id), Is().False()); + Expect(scope.IsModified(id)).ToBeFalse(); scope.Has(id); // Has should never mark modify - AssertThat(scope.IsModified(id), Is().False()); + Expect(scope.IsModified(id)).ToBeFalse(); scope.Get(id); - AssertThat(scope.IsModified(id), Is().False()); + Expect(scope.IsModified(id)).ToBeFalse(); scope.Get(id); - AssertThat(scope.IsModified(id), Is().True()); + Expect(scope.IsModified(id)).ToBeTrue(); scope.Add(id); // Type B should not be auto modified - AssertThat(scope.IsModified(id), Is().False()); + Expect(scope.IsModified(id)).ToBeFalse(); scope.ClearPool>(); scope.Remove(id); - AssertThat(scope.Has(id), Is().False()); - AssertThat(scope.IsModified(id), Is().True()); + Expect(scope.Has(id)).ToBeFalse(); + Expect(scope.IsModified(id)).ToBeTrue(); scope.Remove(id); // Type B should not be auto modified - AssertThat(scope.Has(id), Is().False()); - AssertThat(scope.IsModified(id), Is().False()); + Expect(scope.Has(id)).ToBeFalse(); + Expect(scope.IsModified(id)).ToBeFalse(); }); }); }); -}); +} diff --git a/Tests/ECS/Statics.spec.cpp b/Tests/ECS/Statics.spec.cpp index 9199d56b..235ad0e7 100644 --- a/Tests/ECS/Statics.spec.cpp +++ b/Tests/ECS/Statics.spec.cpp @@ -1,11 +1,9 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include -using namespace snowhouse; -using namespace bandit; using namespace p; using namespace std::chrono_literals; @@ -24,72 +22,72 @@ struct StaticTypeThree }; -go_bandit([]() +void RegisterECSStaticsTests() { - describe("ECS.Statics", []() + Spec("ECS.Statics", []() { - it("Can set an static", [&]() + It("Can set an static", []() { IdContext ctx; - AssertThat(ctx.HasStatic(), Equals(false)); + Expect(ctx.HasStatic()).ToEqual(false); auto& var = ctx.SetStatic({4}); - AssertThat(var.i, Equals(4)); - AssertThat(ctx.HasStatic(), Equals(true)); - AssertThat(ctx.HasStatic(), Equals(false)); + Expect(var.i).ToEqual(4); + Expect(ctx.HasStatic()).ToEqual(true); + Expect(ctx.HasStatic()).ToEqual(false); }); - it("Can set two statics", [&]() + It("Can set two statics", []() { IdContext ctx; - AssertThat(ctx.HasStatic(), Equals(false)); - AssertThat(ctx.HasStatic(), Equals(false)); + Expect(ctx.HasStatic()).ToEqual(false); + Expect(ctx.HasStatic()).ToEqual(false); auto& var1 = ctx.SetStatic({4}); auto& var2 = ctx.SetStatic({2}); - AssertThat(var1.i, Equals(4)); - AssertThat(var2.i, Equals(2)); - AssertThat(ctx.HasStatic(), Equals(true)); - AssertThat(ctx.HasStatic(), Equals(true)); + Expect(var1.i).ToEqual(4); + Expect(var2.i).ToEqual(2); + Expect(ctx.HasStatic()).ToEqual(true); + Expect(ctx.HasStatic()).ToEqual(true); }); - it("Can replace an static", [&]() + It("Can replace an static", []() { IdContext ctx; - AssertThat(ctx.HasStatic(), Equals(false)); + Expect(ctx.HasStatic()).ToEqual(false); ctx.SetStatic({4}); ctx.SetStatic({2}); - AssertThat(ctx.GetStatic().i, Equals(2)); - AssertThat(ctx.HasStatic(), Equals(true)); + Expect(ctx.GetStatic().i).ToEqual(2); + Expect(ctx.HasStatic()).ToEqual(true); }); - it("Can get or set an static", [&]() + It("Can get or set an static", []() { IdContext ctx; // Can set - AssertThat(ctx.GetOrSetStatic({4}).i, Equals(4)); + Expect(ctx.GetOrSetStatic({4}).i).ToEqual(4); // Can get - AssertThat(ctx.GetOrSetStatic({10}).i, Equals(4)); + Expect(ctx.GetOrSetStatic({10}).i).ToEqual(4); }); - it("Can remove an static", [&]() + It("Can remove an static", []() { IdContext ctx; ctx.SetStatic(); - AssertThat(ctx.HasStatic(), Equals(true)); - AssertThat(ctx.RemoveStatic(), Is().True()); - AssertThat(ctx.HasStatic(), Equals(false)); + Expect(ctx.HasStatic()).ToEqual(true); + Expect(ctx.RemoveStatic()).ToBeTrue(); + Expect(ctx.HasStatic()).ToEqual(false); - AssertThat(ctx.RemoveStatic(), Is().False()); + Expect(ctx.RemoveStatic()).ToBeFalse(); }); - it("Can get statics", [&]() + It("Can get statics", []() { IdContext ctx; ctx.SetStatic({4}); ctx.SetStatic({2}); - AssertThat(ctx.GetStatic().i, Equals(4)); - AssertThat(ctx.GetStatic().i, Equals(2)); + Expect(ctx.GetStatic().i).ToEqual(4); + Expect(ctx.GetStatic().i).ToEqual(2); ctx.SetStatic({14}); - AssertThat(ctx.GetStatic().i, Equals(14)); + Expect(ctx.GetStatic().i).ToEqual(14); ctx.RemoveStatic(); - AssertThat(ctx.TryGetStatic(), Is().Null()); + Expect(ctx.TryGetStatic()).ToEqual(nullptr); }); }); -}); +} diff --git a/Tests/Files/Paths.spec.cpp b/Tests/Files/Paths.spec.cpp index 17416441..56dd547b 100644 --- a/Tests/Files/Paths.spec.cpp +++ b/Tests/Files/Paths.spec.cpp @@ -1,217 +1,213 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include #include -using namespace snowhouse; -using namespace bandit; +using namespace p; -go_bandit([]() +void RegisterFilesPathsTests() { - describe("Files.Paths", []() + Spec("Files.Paths", []() { - it("Can get root name and path", [&]() + It("Can get root name and path", []() { -#if P_PLATFORM_WINDOWS - AssertThat(p::GetRootPathName("F:\\SomeFolder\\AnotherFolder"), Equals("F:")); - AssertThat(p::GetRootPath("F:\\SomeFolder\\AnotherFolder"), Equals("F:\\")); -#elif P_PLATFORM_LINUX - AssertThat(p::GetRootPathName("/var/SomeFolder/AnotherFolder"), Equals("")); - AssertThat(p::GetRootPath("/var/SomeFolder/AnotherFolder"), Equals("/")); -#endif - AssertThat(p::GetRootPathName("/AnotherFolder"), Equals("")); - AssertThat(p::GetRootPath("/AnotherFolder"), Equals("/")); + #if P_PLATFORM_WINDOWS + Expect(p::GetRootPathName("F:\\SomeFolder\\AnotherFolder")).ToEqual("F:"); + Expect(p::GetRootPath("F:\\SomeFolder\\AnotherFolder")).ToEqual("F:\\"); + #elif P_PLATFORM_LINUX + Expect(p::GetRootPathName("/var/SomeFolder/AnotherFolder")).ToEqual(""); + Expect(p::GetRootPath("/var/SomeFolder/AnotherFolder")).ToEqual("/"); + #endif + Expect(p::GetRootPathName("/AnotherFolder")).ToEqual(""); + Expect(p::GetRootPath("/AnotherFolder")).ToEqual("/"); }); - it("Can get relative path", [&]() + It("Can get relative path", []() { -#if P_PLATFORM_WINDOWS - AssertThat(p::GetRelativePath("F:\\SomeFolder\\AnotherFolder"), - Equals("SomeFolder\\AnotherFolder")); -#endif - AssertThat(p::GetRelativePath("/var/SomeFolder/AnotherFolder"), - Equals("var/SomeFolder/AnotherFolder")); - AssertThat(p::GetRelativePath("/SomeFolder/AnotherFolder"), - Equals("SomeFolder/AnotherFolder")); + #if P_PLATFORM_WINDOWS + Expect(p::GetRelativePath("F:\\SomeFolder\\AnotherFolder")).ToEqual("SomeFolder\\AnotherFolder"); + #endif + Expect(p::GetRelativePath("/var/SomeFolder/AnotherFolder")).ToEqual("var/SomeFolder/AnotherFolder"); + Expect(p::GetRelativePath("/SomeFolder/AnotherFolder")).ToEqual("SomeFolder/AnotherFolder"); }); - it("Can check absolute path", [&]() + It("Can check absolute path", []() { - AssertThat(p::IsAbsolutePath("//host"), Equals(true)); -#if P_PLATFORM_WINDOWS - AssertThat(p::IsAbsolutePath("F:\\SomeFolder\\AnotherFolder"), Equals(true)); -#elif P_PLATFORM_LINUX - AssertThat(p::IsAbsolutePath("/var/SomeFolder/AnotherFolder"), Equals(true)); -#endif - AssertThat(p::IsAbsolutePath("Executable.exe"), Equals(false)); - AssertThat(p::IsAbsolutePath("SomeFolder/AnotherFolder"), Equals(false)); + Expect(p::IsAbsolutePath("//host")).ToEqual(true); + #if P_PLATFORM_WINDOWS + Expect(p::IsAbsolutePath("F:\\SomeFolder\\AnotherFolder")).ToEqual(true); + #elif P_PLATFORM_LINUX + Expect(p::IsAbsolutePath("/var/SomeFolder/AnotherFolder")).ToEqual(true); + #endif + Expect(p::IsAbsolutePath("Executable.exe")).ToEqual(false); + Expect(p::IsAbsolutePath("SomeFolder/AnotherFolder")).ToEqual(false); }); - it("Can check relative path", [&]() + It("Can check relative path", []() { -#if P_PLATFORM_WINDOWS - AssertThat(p::IsRelativePath("F:\\SomeFolder\\AnotherFolder"), Equals(false)); -#elif P_PLATFORM_LINUX - AssertThat(p::IsRelativePath("/var/SomeFolder/AnotherFolder"), Equals(false)); -#endif - AssertThat(p::IsRelativePath("Executable.exe"), Equals(true)); - AssertThat(p::IsRelativePath("SomeFolder/AnotherFolder"), Equals(true)); + #if P_PLATFORM_WINDOWS + Expect(p::IsRelativePath("F:\\SomeFolder\\AnotherFolder")).ToEqual(false); + #elif P_PLATFORM_LINUX + Expect(p::IsRelativePath("/var/SomeFolder/AnotherFolder")).ToEqual(false); + #endif + Expect(p::IsRelativePath("Executable.exe")).ToEqual(true); + Expect(p::IsRelativePath("SomeFolder/AnotherFolder")).ToEqual(true); }); - it("Can get parent path", [&]() + It("Can get parent path", []() { -#if P_PLATFORM_WINDOWS - AssertThat(p::GetParentPath("F:\\SomeFolder\\AnotherFolder"), Equals("F:\\SomeFolder")); -#endif - AssertThat(p::GetParentPath("/var/SomeFolder"), Equals("/var")); - AssertThat(p::GetParentPath("/SomeFolder/AnotherFolder"), Equals("/SomeFolder")); - AssertThat(p::GetParentPath("/SomeFolder/SomeFile.txt"), Equals("/SomeFolder")); + #if P_PLATFORM_WINDOWS + Expect(p::GetParentPath("F:\\SomeFolder\\AnotherFolder")).ToEqual("F:\\SomeFolder"); + #endif + Expect(p::GetParentPath("/var/SomeFolder")).ToEqual("/var"); + Expect(p::GetParentPath("/SomeFolder/AnotherFolder")).ToEqual("/SomeFolder"); + Expect(p::GetParentPath("/SomeFolder/SomeFile.txt")).ToEqual("/SomeFolder"); }); - it("Executable path is not empty", [&]() + It("Executable path is not empty", []() { - AssertThat(p::PlatformPaths::GetExecutablePath(), !Equals("")); + Expect(p::PlatformPaths::GetExecutablePath()).ToNotEqual(""); }); - it("Can get extension", [&]() + It("Can get extension", []() { -#if P_PLATFORM_WINDOWS - AssertThat(p::GetExtension("F:\\SomeFolder\\AnotherFolder.lib"), Equals(".lib")); - AssertThat(p::GetExtension("F:\\AnotherFolder.lib"), Equals(".lib")); - AssertThat(p::GetExtension("F:\\AnotherFolder."), Equals(".")); - AssertThat(p::GetExtension("F:\\AnotherFolder"), Equals("")); - AssertThat(p::GetExtension("F:\\"), Equals("")); -#elif P_PLATFORM_LINUX - AssertThat(p::GetExtension("/var/SomeFolder/AnotherFolder.lib"), Equals(".lib")); - AssertThat(p::GetExtension("/var/AnotherFolder.lib"), Equals(".lib")); - AssertThat(p::GetExtension("/var/AnotherFolder."), Equals(".")); - AssertThat(p::GetExtension("/var/AnotherFolder"), Equals("")); - AssertThat(p::GetExtension("/var/"), Equals("")); -#endif - AssertThat(p::GetExtension("AnotherFolder.lib"), Equals(".lib")); - AssertThat(p::GetExtension("AnotherFolder"), Equals("")); + #if P_PLATFORM_WINDOWS + Expect(p::GetExtension("F:\\SomeFolder\\AnotherFolder.lib")).ToEqual(".lib"); + Expect(p::GetExtension("F:\\AnotherFolder.lib")).ToEqual(".lib"); + Expect(p::GetExtension("F:\\AnotherFolder.")).ToEqual("."); + Expect(p::GetExtension("F:\\AnotherFolder")).ToEqual(""); + Expect(p::GetExtension("F:\\")).ToEqual(""); + #elif P_PLATFORM_LINUX + Expect(p::GetExtension("/var/SomeFolder/AnotherFolder.lib")).ToEqual(".lib"); + Expect(p::GetExtension("/var/AnotherFolder.lib")).ToEqual(".lib"); + Expect(p::GetExtension("/var/AnotherFolder.")).ToEqual("."); + Expect(p::GetExtension("/var/AnotherFolder")).ToEqual(""); + Expect(p::GetExtension("/var/")).ToEqual(""); + #endif + Expect(p::GetExtension("AnotherFolder.lib")).ToEqual(".lib"); + Expect(p::GetExtension("AnotherFolder")).ToEqual(""); }); - it("Can check extension", [&]() + It("Can check extension", []() { -#if P_PLATFORM_WINDOWS - AssertThat(p::HasExtension("F:\\SomeFolder\\AnotherFolder.lib"), Equals(true)); - AssertThat(p::HasExtension("F:\\AnotherFolder.lib"), Equals(true)); - AssertThat(p::HasExtension("F:\\AnotherFolder."), Equals(true)); - AssertThat(p::HasExtension("F:\\AnotherFolder"), Equals(false)); - AssertThat(p::HasExtension("F:\\"), Equals(false)); -#elif P_PLATFORM_LINUX - AssertThat(p::HasExtension("/var/SomeFolder/AnotherFolder.lib"), Equals(true)); - AssertThat(p::HasExtension("/var/AnotherFolder.lib"), Equals(true)); - AssertThat(p::HasExtension("/var/AnotherFolder."), Equals(true)); - AssertThat(p::HasExtension("/var/AnotherFolder"), Equals(false)); - AssertThat(p::HasExtension("/var/"), Equals(false)); -#endif - AssertThat(p::HasExtension("AnotherFolder.lib"), Equals(true)); - AssertThat(p::HasExtension("AnotherFolder"), Equals(false)); + #if P_PLATFORM_WINDOWS + Expect(p::HasExtension("F:\\SomeFolder\\AnotherFolder.lib")).ToEqual(true); + Expect(p::HasExtension("F:\\AnotherFolder.lib")).ToEqual(true); + Expect(p::HasExtension("F:\\AnotherFolder.")).ToEqual(true); + Expect(p::HasExtension("F:\\AnotherFolder")).ToEqual(false); + Expect(p::HasExtension("F:\\")).ToEqual(false); + #elif P_PLATFORM_LINUX + Expect(p::HasExtension("/var/SomeFolder/AnotherFolder.lib")).ToEqual(true); + Expect(p::HasExtension("/var/AnotherFolder.lib")).ToEqual(true); + Expect(p::HasExtension("/var/AnotherFolder.")).ToEqual(true); + Expect(p::HasExtension("/var/AnotherFolder")).ToEqual(false); + Expect(p::HasExtension("/var/")).ToEqual(false); + #endif + Expect(p::HasExtension("AnotherFolder.lib")).ToEqual(true); + Expect(p::HasExtension("AnotherFolder")).ToEqual(false); }); - it("Can replace extension", [&]() + It("Can replace extension", []() { p::String path; -#if P_PLATFORM_WINDOWS + #if P_PLATFORM_WINDOWS path = "F:\\SomeFolder\\AnotherFolder.lib"; p::ReplaceExtension(path, "txt"); - AssertThat(path, Equals(p::String{"F:\\SomeFolder\\AnotherFolder.txt"})); -#elif P_PLATFORM_LINUX + Expect(path).ToEqual("F:\\SomeFolder\\AnotherFolder.txt"); + #elif P_PLATFORM_LINUX path = "/var/SomeFolder/AnotherFolder.lib"; p::ReplaceExtension(path, "txt"); - AssertThat(path, Equals(p::String{"/var/SomeFolder/AnotherFolder.txt"})); -#endif + Expect(path).ToEqual("/var/SomeFolder/AnotherFolder.txt"); + #endif path = "AnotherFolder.lib"; p::ReplaceExtension(path, "txt"); - AssertThat(path, Equals(p::String{"AnotherFolder.txt"})); + Expect(path).ToEqual("AnotherFolder.txt"); path = "AnotherFolder."; p::ReplaceExtension(path, ".txt"); - AssertThat(path, Equals(p::String{"AnotherFolder.txt"})); + Expect(path).ToEqual("AnotherFolder.txt"); path = "AnotherFolder.lib"; p::ReplaceExtension(path, ".txt"); - AssertThat(path, Equals(p::String{"AnotherFolder.txt"})); + Expect(path).ToEqual("AnotherFolder.txt"); path = "AnotherFolder"; p::ReplaceExtension(path, "txt"); - AssertThat(path, Equals(p::String{"AnotherFolder.txt"})); + Expect(path).ToEqual("AnotherFolder.txt"); }); - it("Can get stem", [&]() + It("Can get stem", []() { -#if P_PLATFORM_WINDOWS - AssertThat(p::GetStem("F:\\SomeFolder\\AnotherFolder.lib"), Equals("AnotherFolder")); - AssertThat(p::GetStem("F:\\AnotherFolder.lib"), Equals("AnotherFolder")); - AssertThat(p::GetStem("F:\\AnotherFolder."), Equals("AnotherFolder")); - AssertThat(p::GetStem("F:\\AnotherFolder"), Equals("AnotherFolder")); - AssertThat(p::GetStem("F:\\"), Equals("")); -#elif P_PLATFORM_LINUX - AssertThat(p::GetStem("/var/SomeFolder/AnotherFolder.lib"), Equals("AnotherFolder")); - AssertThat(p::GetStem("/var/AnotherFolder.lib"), Equals("AnotherFolder")); - AssertThat(p::GetStem("/var/AnotherFolder."), Equals("AnotherFolder")); - AssertThat(p::GetStem("/var/AnotherFolder"), Equals("AnotherFolder")); - AssertThat(p::GetStem("/var/"), Equals("")); -#endif - AssertThat(p::GetStem("AnotherFolder.lib"), Equals("AnotherFolder")); - AssertThat(p::GetStem("AnotherFolder"), Equals("AnotherFolder")); - AssertThat(p::GetStem(""), Equals("")); + #if P_PLATFORM_WINDOWS + Expect(p::GetStem("F:\\SomeFolder\\AnotherFolder.lib")).ToEqual("AnotherFolder"); + Expect(p::GetStem("F:\\AnotherFolder.lib")).ToEqual("AnotherFolder"); + Expect(p::GetStem("F:\\AnotherFolder.")).ToEqual("AnotherFolder"); + Expect(p::GetStem("F:\\AnotherFolder")).ToEqual("AnotherFolder"); + Expect(p::GetStem("F:\\")).ToEqual(""); + #elif P_PLATFORM_LINUX + Expect(p::GetStem("/var/SomeFolder/AnotherFolder.lib")).ToEqual("AnotherFolder"); + Expect(p::GetStem("/var/AnotherFolder.lib")).ToEqual("AnotherFolder"); + Expect(p::GetStem("/var/AnotherFolder.")).ToEqual("AnotherFolder"); + Expect(p::GetStem("/var/AnotherFolder")).ToEqual("AnotherFolder"); + Expect(p::GetStem("/var/")).ToEqual(""); + #endif + Expect(p::GetStem("AnotherFolder.lib")).ToEqual("AnotherFolder"); + Expect(p::GetStem("AnotherFolder")).ToEqual("AnotherFolder"); + Expect(p::GetStem("")).ToEqual(""); }); - it("Can check stem", [&]() + It("Can check stem", []() { -#if P_PLATFORM_WINDOWS - AssertThat(p::HasStem("F:\\SomeFolder\\AnotherFolder.lib"), Equals(true)); - AssertThat(p::HasStem("F:\\AnotherFolder.lib"), Equals(true)); - AssertThat(p::HasStem("F:\\AnotherFolder."), Equals(true)); - AssertThat(p::HasStem("F:\\AnotherFolder"), Equals(true)); - AssertThat(p::HasStem("F:\\"), Equals(false)); -#elif P_PLATFORM_LINUX - AssertThat(p::HasStem("/var/SomeFolder/AnotherFolder.lib"), Equals(true)); - AssertThat(p::HasStem("/var/AnotherFolder.lib"), Equals(true)); - AssertThat(p::HasStem("/var/AnotherFolder."), Equals(true)); - AssertThat(p::HasStem("/var/AnotherFolder"), Equals(true)); - AssertThat(p::HasStem("/var/"), Equals(false)); -#endif - AssertThat(p::HasStem("AnotherFolder.lib"), Equals(true)); - AssertThat(p::HasStem("AnotherFolder"), Equals(true)); - AssertThat(p::HasStem(""), Equals(false)); + #if P_PLATFORM_WINDOWS + Expect(p::HasStem("F:\\SomeFolder\\AnotherFolder.lib")).ToEqual(true); + Expect(p::HasStem("F:\\AnotherFolder.lib")).ToEqual(true); + Expect(p::HasStem("F:\\AnotherFolder.")).ToEqual(true); + Expect(p::HasStem("F:\\AnotherFolder")).ToEqual(true); + Expect(p::HasStem("F:\\")).ToEqual(false); + #elif P_PLATFORM_LINUX + Expect(p::HasStem("/var/SomeFolder/AnotherFolder.lib")).ToEqual(true); + Expect(p::HasStem("/var/AnotherFolder.lib")).ToEqual(true); + Expect(p::HasStem("/var/AnotherFolder.")).ToEqual(true); + Expect(p::HasStem("/var/AnotherFolder")).ToEqual(true); + Expect(p::HasStem("/var/")).ToEqual(false); + #endif + Expect(p::HasStem("AnotherFolder.lib")).ToEqual(true); + Expect(p::HasStem("AnotherFolder")).ToEqual(true); + Expect(p::HasStem("")).ToEqual(false); }); - it("Can append to path", [&]() + It("Can append to path", []() { - AssertThat(p::JoinPaths("", ""), Equals(p::StringView{""})); - AssertThat(p::JoinPaths("", "/"), Equals(p::StringView{"/"})); - AssertThat(p::JoinPaths("", "bar"), Equals(p::StringView{"bar"})); - AssertThat(p::JoinPaths("", "/bar"), Equals(p::StringView{"/bar"})); - - AssertThat(p::JoinPaths("/", ""), Equals(p::StringView{"/"})); - AssertThat(p::JoinPaths("/", "/"), Equals(p::StringView{"/"})); - AssertThat(p::JoinPaths("/", "bar"), Equals(p::StringView{"/bar"})); - AssertThat(p::JoinPaths("/", "/bar"), Equals(p::StringView{"/bar"})); - AssertThat(p::JoinPaths("foo", "/"), Equals(p::StringView{"/"})); - - AssertThat(p::JoinPaths("foo", "/bar"), Equals(p::StringView{"/bar"})); - AssertThat(p::JoinPaths("foo/", ""), Equals(p::StringView{"foo/"})); - AssertThat(p::JoinPaths("foo/", "/"), Equals(p::StringView{"/"})); - AssertThat(p::JoinPaths("foo/", "bar"), Equals(p::StringView{"foo/bar"})); - -#if P_PLATFORM_WINDOWS - AssertThat(p::JoinPaths("foo", ""), Equals(p::StringView{"foo\\"})); - AssertThat(p::JoinPaths("foo", "bar"), Equals(p::StringView{"foo\\bar"})); - AssertThat(p::JoinPaths("foo\\", "\\bar"), Equals(p::StringView{"\\bar"})); - AssertThat(p::JoinPaths("c:", "bar"), Equals(p::StringView{"c:bar"})); - AssertThat(p::JoinPaths("\\\\host", "foo"), Equals(p::String{"\\\\host\\foo"})); - AssertThat(p::JoinPaths("\\\\host/", "foo"), Equals(p::String{"\\\\host/foo"})); -#else - AssertThat(p::JoinPaths("foo", ""), Equals(p::StringView{"foo/"})); - AssertThat(p::JoinPaths("foo", "bar"), Equals(p::StringView{"foo/bar"})); - AssertThat(p::JoinPaths("//host", "foo"), Equals(p::StringView{"//host/foo"})); - AssertThat(p::JoinPaths("//host/", "foo"), Equals(p::StringView{"//host/foo"})); -#endif + Expect(p::JoinPaths("", "")).ToEqual(""); + Expect(p::JoinPaths("", "/")).ToEqual("/"); + Expect(p::JoinPaths("", "bar")).ToEqual("bar"); + Expect(p::JoinPaths("", "/bar")).ToEqual("/bar"); + + Expect(p::JoinPaths("/", "")).ToEqual("/"); + Expect(p::JoinPaths("/", "/")).ToEqual("/"); + Expect(p::JoinPaths("/", "bar")).ToEqual("/bar"); + Expect(p::JoinPaths("/", "/bar")).ToEqual("/bar"); + Expect(p::JoinPaths("foo", "/")).ToEqual("/"); + + Expect(p::JoinPaths("foo", "/bar")).ToEqual("/bar"); + Expect(p::JoinPaths("foo/", "")).ToEqual("foo/"); + Expect(p::JoinPaths("foo/", "/")).ToEqual("/"); + Expect(p::JoinPaths("foo/", "bar")).ToEqual("foo/bar"); + + #if P_PLATFORM_WINDOWS + Expect(p::JoinPaths("foo", "")).ToEqual("foo\\"); + Expect(p::JoinPaths("foo", "bar")).ToEqual("foo\\bar"); + Expect(p::JoinPaths("foo\\", "\\bar")).ToEqual("\\bar"); + Expect(p::JoinPaths("c:", "bar")).ToEqual("c:bar"); + Expect(p::JoinPaths("\\\\host", "foo")).ToEqual("\\\\host\\foo"); + Expect(p::JoinPaths("\\\\host/", "foo")).ToEqual("\\\\host/foo"); + #else + Expect(p::JoinPaths("foo", "")).ToEqual("foo/"); + Expect(p::JoinPaths("foo", "bar")).ToEqual("foo/bar"); + Expect(p::JoinPaths("//host", "foo")).ToEqual("//host/foo"); + Expect(p::JoinPaths("//host/", "foo")).ToEqual("//host/foo"); + #endif }); }); -}); +} diff --git a/Tests/Math/Color.spec.cpp b/Tests/Math/Color.spec.cpp index 15acb4ef..95bff8ba 100644 --- a/Tests/Math/Color.spec.cpp +++ b/Tests/Math/Color.spec.cpp @@ -1,176 +1,137 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include -using namespace snowhouse; -using namespace bandit; using namespace p; -namespace snowhouse +void RegisterMathColorTests() { - template<> - struct Stringizer + Spec("Math.Color", []() { - static std::string ToString(u8 a) + Describe("Helpers", []() { - std::stringstream stream; - stream << u32(a); - return stream.str(); - } - }; - template<> - struct Stringizer - { - static std::string ToString(const Color& a) - { - std::stringstream stream; - stream << "Color(" << u32(a.r) << ", " << u32(a.g) << ", " << u32(a.b) << ", " - << u32(a.a) << ")"; - return stream.str(); - } - }; - - template<> - struct Stringizer - { - static std::string ToString(const LinearColor& a) - { - std::stringstream stream; - stream << "LinearColor(" << a.r << ", " << a.g << ", " << a.b << ", " << a.a << ")"; - return stream.str(); - } - }; -} // namespace snowhouse - - -go_bandit([]() -{ - describe("Math.Color", [&]() - { - describe("Helpers", [&]() - { - it("Can make from rgba", [&]() + It("Can make from rgba", []() { auto color = Color::FromRGB(128, 206, 215, 35); - AssertThat(color.r, Equals(128)); - AssertThat(color.g, Equals(206)); - AssertThat(color.b, Equals(215)); - AssertThat(color.a, Equals(35)); + Expect(color.r).ToEqual(128); + Expect(color.g).ToEqual(206); + Expect(color.b).ToEqual(215); + Expect(color.a).ToEqual(35); }); - it("Can make from Hex", [&]() + It("Can make from Hex", []() { auto color = Color::FromHex(0x80ced7); - AssertThat(color.r, Equals(128)); - AssertThat(color.g, Equals(206)); - AssertThat(color.b, Equals(215)); + Expect(color.r).ToEqual(128); + Expect(color.g).ToEqual(206); + Expect(color.b).ToEqual(215); auto colora = Color::FromHexAlpha(0x80ced723); - AssertThat(colora.r, Equals(128)); - AssertThat(colora.g, Equals(206)); - AssertThat(colora.b, Equals(215)); - AssertThat(colora.a, Equals(35)); + Expect(colora.r).ToEqual(128); + Expect(colora.g).ToEqual(206); + Expect(colora.b).ToEqual(215); + Expect(colora.a).ToEqual(35); }); - it("Can make from packed", [&]() + It("Can make from packed", []() { auto argb = Color::FromPackedARGB(0x2380ced7); - AssertThat(argb.r, Equals(128)); - AssertThat(argb.g, Equals(206)); - AssertThat(argb.b, Equals(215)); - AssertThat(argb.a, Equals(35)); + Expect(argb.r).ToEqual(128); + Expect(argb.g).ToEqual(206); + Expect(argb.b).ToEqual(215); + Expect(argb.a).ToEqual(35); auto abgr = Color::FromPackedABGR(0x23d7ce80); - AssertThat(abgr.r, Equals(128)); - AssertThat(abgr.g, Equals(206)); - AssertThat(abgr.b, Equals(215)); - AssertThat(abgr.a, Equals(35)); + Expect(abgr.r).ToEqual(128); + Expect(abgr.g).ToEqual(206); + Expect(abgr.b).ToEqual(215); + Expect(abgr.a).ToEqual(35); auto rgba = Color::FromPackedRGBA(0x80ced723); - AssertThat(rgba.r, Equals(128)); - AssertThat(rgba.g, Equals(206)); - AssertThat(rgba.b, Equals(215)); - AssertThat(rgba.a, Equals(35)); + Expect(rgba.r).ToEqual(128); + Expect(rgba.g).ToEqual(206); + Expect(rgba.b).ToEqual(215); + Expect(rgba.a).ToEqual(35); auto bgra = Color::FromPackedBGRA(0xd7ce8023); - AssertThat(bgra.r, Equals(128)); - AssertThat(bgra.g, Equals(206)); - AssertThat(bgra.b, Equals(215)); - AssertThat(bgra.a, Equals(35)); + Expect(bgra.r).ToEqual(128); + Expect(bgra.g).ToEqual(206); + Expect(bgra.b).ToEqual(215); + Expect(bgra.a).ToEqual(35); }); - it("Can get as packed", [&]() + It("Can get as packed", []() { auto color = Color(128, 206, 215, 35); - AssertThat(color.ToPackedARGB(), Equals(0x2380ced7)); - AssertThat(color.ToPackedABGR(), Equals(0x23d7ce80)); - AssertThat(color.ToPackedRGBA(), Equals(0x80ced723)); - AssertThat(color.ToPackedBGRA(), Equals(0xd7ce8023)); + Expect(color.ToPackedARGB()).ToEqual(0x2380ced7); + Expect(color.ToPackedABGR()).ToEqual(0x23d7ce80); + Expect(color.ToPackedRGBA()).ToEqual(0x80ced723); + Expect(color.ToPackedBGRA()).ToEqual(0xd7ce8023); }); }); - describe("LinearColor", [&]() + Describe("LinearColor", []() { - it("Can Shade", [&]() + It("Can Shade", []() { - AssertThat(LinearColor::White().Shade(1.0f), Equals(LinearColor::Black())); - AssertThat(LinearColor::White().Shade(0.5f), Equals(LinearColor::Gray())); + Expect(LinearColor::White().Shade(1.0f)).ToEqual(LinearColor::Black()); + Expect(LinearColor::White().Shade(0.5f)).ToEqual(LinearColor::Gray()); constexpr LinearColor color{Color::FromHex(0x80ced7)}; - AssertThat(color.Shade(0.5f), Equals(LinearColor{Color::FromHex(0x40676B)})); + Expect(color.Shade(0.5f)).ToEqual(LinearColor{Color::FromHex(0x40676B)}); }); - it("Shade doesn't change alpha", [&]() + It("Shade doesn't change alpha", []() { - AssertThat(LinearColor::White().Translucency(0.5f).Shade(1.0f).a, - EqualsWithDelta(0.5f, 0.01f)); + Expect(std::abs(LinearColor::White().Translucency(0.5f).Shade(1.0f).a - 0.5f)) + .ToBeLessOrEqual(0.01f); }); - it("Can Tint", [&]() + It("Can Tint", []() { - AssertThat(LinearColor::Black().Tint(1.0f), Equals(LinearColor::White())); - AssertThat(LinearColor::Black().Tint(0.5f), Equals(LinearColor::Gray())); - AssertThat(Color::FromHex(0x80ced7).Tint(0.5f), Equals(Color::FromHex(0xbfe6eb))); + Expect(LinearColor::Black().Tint(1.0f)).ToEqual(LinearColor::White()); + Expect(LinearColor::Black().Tint(0.5f)).ToEqual(LinearColor::Gray()); + Expect(Color::FromHex(0x80ced7).Tint(0.5f)).ToEqual(Color::FromHex(0xbfe6eb)); }); - it("Tint doesn't change alpha", [&]() + It("Tint doesn't change alpha", []() { - AssertThat(LinearColor::Black().Translucency(0.5f).Tint(1.0f).a, - EqualsWithDelta(0.5f, 0.01f)); + Expect(std::abs(LinearColor::Black().Translucency(0.5f).Tint(1.0f).a - 0.5f)) + .ToBeLessOrEqual(0.01f); }); }); - describe("Color", [&]() + Describe("Color", []() { - it("Can Shade", [&]() + It("Can Shade", []() { - AssertThat(Color::White().Shade(1.0f), Equals(Color::Black())); - AssertThat(Color::White().Shade(0.5f), Equals(Color::Gray())); - AssertThat(Color::FromHex(0x80ced7).Shade(0.5f), Equals(Color::FromHex(0x40676B))); + Expect(Color::White().Shade(1.0f)).ToEqual(Color::Black()); + Expect(Color::White().Shade(0.5f)).ToEqual(Color::Gray()); + Expect(Color::FromHex(0x80ced7).Shade(0.5f)).ToEqual(Color::FromHex(0x40676B)); }); - it("Shade doesn't change alpha", [&]() + It("Shade doesn't change alpha", []() { - AssertThat(Color::White().Translucency(127).Shade(1.0f).a, Equals(127)); + Expect(Color::White().Translucency(127).Shade(1.0f).a).ToEqual(127); }); - it("Can Tint", [&]() + It("Can Tint", []() { - AssertThat(Color::Black().Tint(1.0f), Equals(Color::White())); - AssertThat(Color::Black().Tint(0.5f), Equals(Color::Gray())); - AssertThat(Color::FromHex(0x80ced7).Tint(0.5f), Equals(Color::FromHex(0xbfe6eb))); + Expect(Color::Black().Tint(1.0f)).ToEqual(Color::White()); + Expect(Color::Black().Tint(0.5f)).ToEqual(Color::Gray()); + Expect(Color::FromHex(0x80ced7).Tint(0.5f)).ToEqual(Color::FromHex(0xbfe6eb)); }); - it("Tint doesn't change alpha", [&]() + It("Tint doesn't change alpha", []() { - AssertThat(Color::Black().Translucency(127).Tint(1.0f).a, Equals(127)); + Expect(Color::Black().Translucency(127).Tint(1.0f).a).ToEqual(127); }); - it("Can convert to linear", [&]() + It("Can convert to linear", []() { - AssertThat(LinearColor{Color::White()}, Equals(LinearColor::White())); - AssertThat(LinearColor{Color::Black()}, Equals(LinearColor::Black())); - AssertThat(LinearColor{Color::Gray()}, Equals(LinearColor::Gray())); + Expect(LinearColor{Color::White()}).ToEqual(LinearColor::White()); + Expect(LinearColor{Color::Black()}).ToEqual(LinearColor::Black()); + Expect(LinearColor{Color::Gray()}).ToEqual(LinearColor::Gray()); }); - }); }); -}); + }); +} diff --git a/Tests/Math/Math.spec.cpp b/Tests/Math/Math.spec.cpp index d6e2661f..02479ce2 100644 --- a/Tests/Math/Math.spec.cpp +++ b/Tests/Math/Math.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include #include #include @@ -9,353 +9,355 @@ #include -using namespace snowhouse; -using namespace bandit; using namespace p; -go_bandit([]() +namespace { - describe("Math.Math", []() + TArray bottomUp{23, 34, 50, 100, 120}; + TArray topDown{120, 100, 50, 34, 23}; +} // namespace + + +void RegisterMathMathTests() +{ + Spec("Math.Math", []() { - describe("Binary Search", []() + Describe("Binary Search", []() { - TArray bottomUp{23, 34, 50, 100, 120}; - TArray topDown{120, 100, 50, 34, 23}; - - it("LowerBound", [&]() + It("LowerBound", [=]() { - AssertThat(bottomUp.LowerBound(34), Equals(1)); - AssertThat(bottomUp.LowerBound(100), Equals(3)); - AssertThat(bottomUp.LowerBound(51), Equals(3)); + Expect(bottomUp.LowerBound(34)).ToEqual(1); + Expect(bottomUp.LowerBound(100)).ToEqual(3); + Expect(bottomUp.LowerBound(51)).ToEqual(3); - AssertThat(topDown.LowerBound(34, TGreater<>()), Equals(3)); - AssertThat(topDown.LowerBound(100, TGreater<>()), Equals(1)); - AssertThat(topDown.LowerBound(51, TGreater<>()), Equals(2)); + Expect(topDown.LowerBound(34, TGreater<>())).ToEqual(3); + Expect(topDown.LowerBound(100, TGreater<>())).ToEqual(1); + Expect(topDown.LowerBound(51, TGreater<>())).ToEqual(2); }); - it("UpperBound", [&]() + It("UpperBound", [=]() { - AssertThat(bottomUp.UpperBound(34), Equals(2)); - AssertThat(bottomUp.UpperBound(100), Equals(4)); + Expect(bottomUp.UpperBound(34)).ToEqual(2); + Expect(bottomUp.UpperBound(100)).ToEqual(4); - AssertThat(topDown.UpperBound(34, TGreater<>()), Equals(4)); - AssertThat(topDown.UpperBound(100, TGreater<>()), Equals(2)); + Expect(topDown.UpperBound(34, TGreater<>())).ToEqual(4); + Expect(topDown.UpperBound(100, TGreater<>())).ToEqual(2); }); - it("Can find equal", [&]() + It("Can find equal", [=]() { - AssertThat(bottomUp.FindSorted(0), Equals(NO_INDEX)); - AssertThat(bottomUp.FindSorted(34), Equals(1)); - AssertThat(bottomUp.FindSorted(33), Equals(NO_INDEX)); - AssertThat(bottomUp.FindSorted(121), Equals(NO_INDEX)); + Expect(bottomUp.FindSorted(0)).ToEqual(NO_INDEX); + Expect(bottomUp.FindSorted(34)).ToEqual(1); + Expect(bottomUp.FindSorted(33)).ToEqual(NO_INDEX); + Expect(bottomUp.FindSorted(121)).ToEqual(NO_INDEX); - AssertThat(topDown.FindSorted(34, TGreater<>()), Equals(3)); + Expect(topDown.FindSorted(34, TGreater<>())).ToEqual(3); }); - describe("FindSortedMax", []() + Describe("FindSortedMax", []() { - describe("Ordered by a < b", []() + Describe("Ordered by a < b", []() { TArray bottomUp{23, 34, 50, 50, 100, 120}; - it("Find first item", [&]() + It("Find first item", [=]() { auto i4 = bottomUp.FindSortedMax(23, false); - AssertThat(i4, Equals(NO_INDEX)); + Expect(i4).ToEqual(NO_INDEX); auto i5 = bottomUp.FindSortedMax(23, true); - AssertThat(i5, Equals(0)); + Expect(i5).ToEqual(0); auto i6 = bottomUp.FindSortedMax(22, true); - AssertThat(i6, Equals(NO_INDEX)); + Expect(i6).ToEqual(NO_INDEX); }); - it("Find any item", [&]() + It("Find any item", [=]() { auto i1 = bottomUp.FindSortedMax(34, true); - AssertThat(i1, Equals(1)); + Expect(i1).ToEqual(1); auto i2 = bottomUp.FindSortedMax(33, true); - AssertThat(i2, Equals(0)); + Expect(i2).ToEqual(0); auto i3 = bottomUp.FindSortedMax(34, false); - AssertThat(i3, Equals(0)); + Expect(i3).ToEqual(0); }); - it("Find last item", [&]() + It("Find last item", [=]() { auto i4 = bottomUp.FindSortedMax(120, false); - AssertThat(i4, Equals(4)); + Expect(i4).ToEqual(4); auto i5 = bottomUp.FindSortedMax(120, true); - AssertThat(i5, Equals(5)); + Expect(i5).ToEqual(5); auto i6 = bottomUp.FindSortedMax(121, true); - AssertThat(i6, Equals(5)); + Expect(i6).ToEqual(5); auto i7 = bottomUp.FindSortedMax(100, false); - AssertThat(i7, Equals(3)); + Expect(i7).ToEqual(3); }); }); - describe("Ordered by a > b", []() + Describe("Ordered by a > b", []() { TArray topDown{120, 100, 50, 50, 34, 23}; - it("Find first item", [&]() + It("Find first item", [=]() { auto i4 = topDown.FindSortedMax(120, true); - AssertThat(i4, Equals(0)); + Expect(i4).ToEqual(0); auto i5 = topDown.FindSortedMax(120, false); - AssertThat(i5, Equals(1)); + Expect(i5).ToEqual(1); auto i6 = topDown.FindSortedMax(121, true); - AssertThat(i6, Equals(0)); + Expect(i6).ToEqual(0); }); - it("Find any item", [&]() + It("Find any item", [=]() { auto i1 = topDown.FindSortedMax(34, true); - AssertThat(i1, Equals(4)); + Expect(i1).ToEqual(4); auto i2 = topDown.FindSortedMax(33, true); - AssertThat(i2, Equals(5)); + Expect(i2).ToEqual(5); auto i3 = topDown.FindSortedMax(34, false); - AssertThat(i3, Equals(5)); + Expect(i3).ToEqual(5); }); - it("Find last item", [&]() + It("Find last item", [=]() { auto i4 = topDown.FindSortedMax(23, false); - AssertThat(i4, Equals(NO_INDEX)); + Expect(i4).ToEqual(NO_INDEX); auto i5 = topDown.FindSortedMax(23, true); - AssertThat(i5, Equals(5)); + Expect(i5).ToEqual(5); auto i6 = topDown.FindSortedMax(22, true); - AssertThat(i6, Equals(NO_INDEX)); + Expect(i6).ToEqual(NO_INDEX); }); }); - describe("All same values", []() + Describe("All same values", []() { TArray allEqual{10, 10, 10}; - it("Doesnt find smaller", [&]() + It("Doesnt find smaller", [=]() { auto i1 = allEqual.FindSortedMax(9, false); - AssertThat(i1, Equals(NO_INDEX)); + Expect(i1).ToEqual(NO_INDEX); auto i2 = allEqual.FindSortedMax(10, false); - AssertThat(i2, Equals(NO_INDEX)); + Expect(i2).ToEqual(NO_INDEX); }); - it("Finds smaller", [&]() + It("Finds smaller", [=]() { auto i1 = allEqual.FindSortedMax(10, true); - AssertThat(i1, Equals(0)); + Expect(i1).ToEqual(0); auto i2 = allEqual.FindSortedMax(11, false); - AssertThat(i2, Equals(0)); + Expect(i2).ToEqual(0); }); }); }); - describe("FindSortedMin", []() + Describe("FindSortedMin", []() { - describe("Ordered by a < b", []() + Describe("Ordered by a < b", []() { TArray bottomUp{23, 34, 50, 50, 100, 120}; - it("Find first item", [&]() + It("Find first item", [=]() { auto i1 = bottomUp.FindSortedMin(23, true); - AssertThat(i1, Equals(0)); + Expect(i1).ToEqual(0); auto i2 = bottomUp.FindSortedMin(20, true); - AssertThat(i2, Equals(0)); + Expect(i2).ToEqual(0); auto i3 = bottomUp.FindSortedMin(23, false); - AssertThat(i3, Equals(1)); + Expect(i3).ToEqual(1); }); - it("Find any item", [&]() + It("Find any item", [=]() { auto i1 = bottomUp.FindSortedMin(33, false); - AssertThat(i1, Equals(1)); + Expect(i1).ToEqual(1); auto i2 = bottomUp.FindSortedMin(34, true); - AssertThat(i2, Equals(1)); + Expect(i2).ToEqual(1); auto i3 = bottomUp.FindSortedMin(34, false); - AssertThat(i3, Equals(2)); + Expect(i3).ToEqual(2); }); - it("Find last item", [&]() + It("Find last item", [=]() { auto i1 = bottomUp.FindSortedMin(100, false); - AssertThat(i1, Equals(5)); + Expect(i1).ToEqual(5); auto i2 = bottomUp.FindSortedMin(120, false); - AssertThat(i2, Equals(NO_INDEX)); + Expect(i2).ToEqual(NO_INDEX); auto i3 = bottomUp.FindSortedMin(120, true); - AssertThat(i3, Equals(5)); + Expect(i3).ToEqual(5); auto i4 = bottomUp.FindSortedMin(121, true); - AssertThat(i4, Equals(NO_INDEX)); + Expect(i4).ToEqual(NO_INDEX); }); }); - describe("Ordered by a > b", [&]() + Describe("Ordered by a > b", []() { TArray topDown{120, 100, 50, 50, 34, 23}; - it("Find first item", [&]() + It("Find first item", [=]() { auto i4 = topDown.FindSortedMin(120, true); - AssertThat(i4, Equals(0)); + Expect(i4).ToEqual(0); auto i5 = topDown.FindSortedMin(120, false); - AssertThat(i5, Equals(NO_INDEX)); + Expect(i5).ToEqual(NO_INDEX); auto i6 = topDown.FindSortedMin(121, true); - AssertThat(i6, Equals(NO_INDEX)); + Expect(i6).ToEqual(NO_INDEX); }); - it("Find any item", [&]() + It("Find any item", [=]() { auto i1 = topDown.FindSortedMin(34, true); - AssertThat(i1, Equals(4)); + Expect(i1).ToEqual(4); auto i2 = topDown.FindSortedMin(33, true); - AssertThat(i2, Equals(4)); + Expect(i2).ToEqual(4); auto i3 = topDown.FindSortedMin(34, false); - AssertThat(i3, Equals(3)); + Expect(i3).ToEqual(3); }); - it("Find last item", [&]() + It("Find last item", [=]() { auto i4 = topDown.FindSortedMin(23, false); - AssertThat(i4, Equals(4)); + Expect(i4).ToEqual(4); auto i5 = topDown.FindSortedMin(23, true); - AssertThat(i5, Equals(5)); + Expect(i5).ToEqual(5); auto i6 = topDown.FindSortedMin(22, true); - AssertThat(i6, Equals(5)); + Expect(i6).ToEqual(5); }); }); - describe("All same values", []() + Describe("All same values", []() { TArray allEqual{10, 10, 10}; - it("Doesnt find bigger", [&]() + It("Doesnt find bigger", [=]() { auto i1 = allEqual.FindSortedMin(11, false); - AssertThat(i1, Equals(NO_INDEX)); + Expect(i1).ToEqual(NO_INDEX); auto i2 = allEqual.FindSortedMin(10, false); - AssertThat(i2, Equals(NO_INDEX)); + Expect(i2).ToEqual(NO_INDEX); }); - it("Finds bigger", [&]() + It("Finds bigger", [=]() { auto i1 = allEqual.FindSortedMin(10, true); - AssertThat(i1, Equals(0)); + Expect(i1).ToEqual(0); auto i2 = allEqual.FindSortedMin(9, false); - AssertThat(i2, Equals(0)); + Expect(i2).ToEqual(0); }); }); }); }); - it("Can check Infinite", [&]() + It("Can check Infinite", [=]() { - AssertThat(IsInf(0.0), Equals(false)); - AssertThat(IsInf(-0.0), Equals(false)); - AssertThat(IsInf(1.0), Equals(false)); - AssertThat(IsInf(-1.0), Equals(false)); + Expect(IsInf(0.0)).ToEqual(false); + Expect(IsInf(-0.0)).ToEqual(false); + Expect(IsInf(1.0)).ToEqual(false); + Expect(IsInf(-1.0)).ToEqual(false); static constexpr double dInfinite = Limits::Infinity(); - AssertThat(IsInf(dInfinite), Equals(true)); - AssertThat(IsInf(-dInfinite), Equals(true)); - AssertThat(IsPosInf(-dInfinite), Equals(false)); - AssertThat(IsNegInf(dInfinite), Equals(false)); - AssertThat(IsInf(Limits::Max()), Equals(false)); - AssertThat(IsInf(Limits::Lowest()), Equals(false)); - AssertThat(IsInf(double(bigNumber)), Equals(false)); + Expect(IsInf(dInfinite)).ToEqual(true); + Expect(IsInf(-dInfinite)).ToEqual(true); + Expect(IsPosInf(-dInfinite)).ToEqual(false); + Expect(IsNegInf(dInfinite)).ToEqual(false); + Expect(IsInf(Limits::Max())).ToEqual(false); + Expect(IsInf(Limits::Lowest())).ToEqual(false); + Expect(IsInf(double(bigNumber))).ToEqual(false); }); - it("Can check NAN", [&]() + It("Can check NAN", [=]() { - AssertThat(IsNAN(0.0), Equals(false)); - AssertThat(IsNAN(Limits::QuietNaN()), Equals(true)); + Expect(IsNAN(0.0)).ToEqual(false); + Expect(IsNAN(Limits::QuietNaN())).ToEqual(true); }); - describe("Roundings", []() + Describe("Roundings", []() { - it("Can Floor", [&]() + It("Can Floor", [=]() { - AssertThat(Floor(0.0), Equals(std::floor(0.0))); - AssertThat(Floor(-0.0), Equals(std::floor(-0.0))); - AssertThat(Floor(4.2), Equals(std::floor(4.2))); - AssertThat(Floor(4.5), Equals(std::floor(4.5))); - AssertThat(Floor(4.7), Equals(std::floor(4.7))); - AssertThat(Floor(5.0), Equals(std::floor(5.0))); - AssertThat(Floor(-4.2), Equals(std::floor(-4.2))); - AssertThat(Floor(-4.7), Equals(std::floor(-4.7))); - AssertThat(Floor(-5.0), Equals(std::floor(-5.0))); - AssertThat(Floor(99999999999999999.0 + 0.5), Equals(99999999999999999.0)); + Expect(Floor(0.0)).ToEqual(std::floor(0.0)); + Expect(Floor(-0.0)).ToEqual(std::floor(-0.0)); + Expect(Floor(4.2)).ToEqual(std::floor(4.2)); + Expect(Floor(4.5)).ToEqual(std::floor(4.5)); + Expect(Floor(4.7)).ToEqual(std::floor(4.7)); + Expect(Floor(5.0)).ToEqual(std::floor(5.0)); + Expect(Floor(-4.2)).ToEqual(std::floor(-4.2)); + Expect(Floor(-4.7)).ToEqual(std::floor(-4.7)); + Expect(Floor(-5.0)).ToEqual(std::floor(-5.0)); + Expect(Floor(99999999999999999.0 + 0.5)).ToEqual(99999999999999999.0); static constexpr double dInfinite = Limits::Infinity(); - AssertThat(Floor(-dInfinite), Equals(std::floor(-dInfinite))); - AssertThat(Floor(dInfinite), Equals(std::floor(dInfinite))); - AssertThat(IsNAN(Floor(Limits::QuietNaN())), Equals(true)); + Expect(Floor(-dInfinite)).ToEqual(std::floor(-dInfinite)); + Expect(Floor(dInfinite)).ToEqual(std::floor(dInfinite)); + Expect(IsNAN(Floor(Limits::QuietNaN()))).ToEqual(true); }); - it("Can Ceil", [&]() + It("Can Ceil", [=]() { - AssertThat(Ceil(0.0), Equals(std::ceil(0.0))); - AssertThat(Ceil(-0.0), Equals(std::ceil(-0.0))); - AssertThat(Ceil(4.2), Equals(std::ceil(4.2))); - AssertThat(Ceil(4.5), Equals(std::ceil(4.5))); - AssertThat(Ceil(4.7), Equals(std::ceil(4.7))); - AssertThat(Ceil(5.0), Equals(std::ceil(5.0))); - AssertThat(Ceil(-4.2), Equals(std::ceil(-4.2))); - AssertThat(Ceil(-4.7), Equals(std::ceil(-4.7))); - AssertThat(Ceil(-5.0), Equals(std::ceil(-5.0))); - AssertThat(Ceil(99999999999999999.0 - 0.5), Equals(99999999999999999.0)); + Expect(Ceil(0.0)).ToEqual(std::ceil(0.0)); + Expect(Ceil(-0.0)).ToEqual(std::ceil(-0.0)); + Expect(Ceil(4.2)).ToEqual(std::ceil(4.2)); + Expect(Ceil(4.5)).ToEqual(std::ceil(4.5)); + Expect(Ceil(4.7)).ToEqual(std::ceil(4.7)); + Expect(Ceil(5.0)).ToEqual(std::ceil(5.0)); + Expect(Ceil(-4.2)).ToEqual(std::ceil(-4.2)); + Expect(Ceil(-4.7)).ToEqual(std::ceil(-4.7)); + Expect(Ceil(-5.0)).ToEqual(std::ceil(-5.0)); + Expect(Ceil(99999999999999999.0 - 0.5)).ToEqual(99999999999999999.0); static constexpr double dInfinite = Limits::Infinity(); - AssertThat(Ceil(-dInfinite), Equals(std::ceil(-dInfinite))); - AssertThat(Ceil(dInfinite), Equals(std::ceil(dInfinite))); - AssertThat(IsNAN(Ceil(Limits::QuietNaN())), Equals(true)); + Expect(Ceil(-dInfinite)).ToEqual(std::ceil(-dInfinite)); + Expect(Ceil(dInfinite)).ToEqual(std::ceil(dInfinite)); + Expect(IsNAN(Ceil(Limits::QuietNaN()))).ToEqual(true); }); - it("Can Round", [&]() + It("Can Round", [=]() { - AssertThat(Round(0.0), Equals(std::round(0.0))); - AssertThat(Round(-0.0), Equals(std::round(-0.0))); - AssertThat(Round(4.2), Equals(std::round(4.2))); - AssertThat(Round(4.5), Equals(std::round(4.5))); - AssertThat(Round(4.7), Equals(std::round(4.7))); - AssertThat(Round(5.0), Equals(std::round(5.0))); - AssertThat(Round(-4.2), Equals(std::round(-4.2))); - AssertThat(Round(-4.7), Equals(std::round(-4.7))); - AssertThat(Round(-5.0), Equals(std::round(-5.0))); - AssertThat(Round(99999999999999999.0 - 0.4), Equals(99999999999999999.0)); + Expect(Round(0.0)).ToEqual(std::round(0.0)); + Expect(Round(-0.0)).ToEqual(std::round(-0.0)); + Expect(Round(4.2)).ToEqual(std::round(4.2)); + Expect(Round(4.5)).ToEqual(std::round(4.5)); + Expect(Round(4.7)).ToEqual(std::round(4.7)); + Expect(Round(5.0)).ToEqual(std::round(5.0)); + Expect(Round(-4.2)).ToEqual(std::round(-4.2)); + Expect(Round(-4.7)).ToEqual(std::round(-4.7)); + Expect(Round(-5.0)).ToEqual(std::round(-5.0)); + Expect(Round(99999999999999999.0 - 0.4)).ToEqual(99999999999999999.0); static constexpr double dInfinite = Limits::Infinity(); - AssertThat(Round(-dInfinite), Equals(std::round(-dInfinite))); - AssertThat(Round(dInfinite), Equals(std::round(dInfinite))); - AssertThat(IsNAN(Round(Limits::QuietNaN())), Equals(true)); + Expect(Round(-dInfinite)).ToEqual(std::round(-dInfinite)); + Expect(Round(dInfinite)).ToEqual(std::round(dInfinite)); + Expect(IsNAN(Round(Limits::QuietNaN()))).ToEqual(true); }); }); }); -}); +} diff --git a/Tests/Math/Vector.spec.cpp b/Tests/Math/Vector.spec.cpp index 4c1fb542..589cd877 100644 --- a/Tests/Math/Vector.spec.cpp +++ b/Tests/Math/Vector.spec.cpp @@ -1,73 +1,71 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include -using namespace snowhouse; -using namespace bandit; using namespace p; -go_bandit([]() +void RegisterMathVectorTests() { - describe("Math.Vector", []() + Spec("Math.Vector", []() { - describe("v2", []() + Describe("v2", []() { - it("Can reflect", [&]() + It("Can reflect", []() { p::v2 v{0.f, 1.f}; p::v2 normal{1.f, 0.f}; p::v2 v2 = v.Reflect(normal); - AssertThat(v2.Equals({0.f, 1.f}), Equals(true)); + Expect(v2.Equals({0.f, 1.f})).ToEqual(true); v = p::v2{0.f, 1.f}; normal = p::v2{0.f, 1.f}; v2 = v.Reflect(normal); - AssertThat(v2.Equals({0.f, -1.f}), Equals(true)); + Expect(v2.Equals({0.f, -1.f})).ToEqual(true); v = p::v2{1.f, 1.f}; normal = p::v2{0.f, 1.f}; v2 = v.Reflect(normal); - AssertThat(v2.Equals({1.f, -1.f}), Equals(true)); + Expect(v2.Equals({1.f, -1.f})).ToEqual(true); v = p::v2{1.f, 1.f}; normal = p::v2{1.f, 0.f}; v2 = v.Reflect(normal); - AssertThat(v2.Equals({-1.f, 1.f}), Equals(true)); + Expect(v2.Equals({-1.f, 1.f})).ToEqual(true); v = p::v2{-1.f, 1.f}; normal = p::v2{-1.f, 0.f}; v2 = v.Reflect(normal); - AssertThat(v2.Equals({1.f, 1.f}), Equals(true)); + Expect(v2.Equals({1.f, 1.f})).ToEqual(true); v = p::v2{-1.f, -1.f}; normal = p::v2{0.f, 1.f}; v2 = v.Reflect(normal); - AssertThat(v2.Equals({-1.f, 1.f}), Equals(true)); + Expect(v2.Equals({-1.f, 1.f})).ToEqual(true); v = p::v2{0.f, 1.f}; normal = p::v2{0.f, 1.f}; v2 = v.Reflect(normal); - AssertThat(v2.Equals({0.f, -1.f}), Equals(true)); + Expect(v2.Equals({0.f, -1.f})).ToEqual(true); v = p::v2{0.f, -1.f}; normal = p::v2{0.f, 1.f}; v2 = v.Reflect(normal); - AssertThat(v2.Equals({0.f, 1.f}), Equals(true)); + Expect(v2.Equals({0.f, 1.f})).ToEqual(true); }); - it("Can convert to angle", [&]() + It("Can convert to angle", []() { float anglea = p::v2{0.f, 1.f}.Angle(); - AssertThat(anglea, Equals(90.f)); + Expect(anglea).ToEqual(90.f); float angleb = p::v2{0.f, -1.f}.Angle(); - AssertThat(angleb, Equals(-90.f)); + Expect(angleb).ToEqual(-90.f); float anglec = p::v2{1.f, 0.f}.Angle(); - AssertThat(anglec, Equals(0.f)); + Expect(anglec).ToEqual(0.f); float angled = p::v2{-1.f, 0.f}.Angle(); - AssertThat(angled, Equals(180.f)); + Expect(angled).ToEqual(180.f); }); - it("Can convert from angle", [&]() + It("Can convert from angle", []() { - AssertThat(p::v2::FromAngle(0.f).Angle(), Equals(0)); - AssertThat(p::v2::FromAngle(90.f).Angle(), Equals(90.f)); + Expect(p::v2::FromAngle(0.f).Angle()).ToEqual(0); + Expect(p::v2::FromAngle(90.f).Angle()).ToEqual(90.f); }); }); }); -}); +} diff --git a/Tests/Memory/BestFitArena.spec.cpp b/Tests/Memory/BestFitArena.spec.cpp index 4f93ef4f..3436c166 100644 --- a/Tests/Memory/BestFitArena.spec.cpp +++ b/Tests/Memory/BestFitArena.spec.cpp @@ -1,11 +1,9 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include -using namespace snowhouse; -using namespace bandit; using namespace p; template @@ -16,31 +14,31 @@ struct TypeOfSize }; -go_bandit([]() +void RegisterMemoryBestFitArenaTests() { - describe("Memory.BestFitArena", []() + Spec("Memory.BestFitArena", []() { - it("Reserves a block on construction", [&]() + It("Reserves a block on construction", []() { BestFitArena arena{1024}; arena.GetStats()->detectLeaks = false; - AssertThat(arena.GetFreeSize(), Equals(1024)); - AssertThat(*arena.GetBlock(), Is().Not().Null()); - AssertThat(arena.GetBlock().size, Is().EqualTo(1024)); + Expect(arena.GetFreeSize()).ToEqual(1024); + Expect(*arena.GetBlock()).ToNotEqual(nullptr); + Expect(arena.GetBlock().size).ToEqual(1024); }); - it("Can allocate", [&]() + It("Can allocate", []() { BestFitArena arena{1024}; arena.GetStats()->detectLeaks = false; void* p = arena.Alloc(4); new (p) TypeOfSize<4>(); - AssertThat(p, Is().Not().Null()); - AssertThat(arena.Contains(p), Is().True()); + Expect(p).ToNotEqual(nullptr); + Expect(arena.Contains(p)).ToBeTrue(); }); - it("Allocates at correct addresses", [&]() + It("Allocates at correct addresses", []() { BestFitArena arena{1024}; arena.GetStats()->detectLeaks = false; @@ -49,14 +47,14 @@ go_bandit([]() void* p = arena.Alloc(4); new (p) TypeOfSize<4>(); - AssertThat(p, Is().EqualTo(blockPtr)); + Expect(p).ToEqual(blockPtr); void* p2 = arena.Alloc(4); new (p2) TypeOfSize<4>(); - AssertThat(p2, Is().EqualTo(blockPtr + 4)); + Expect(p2).ToEqual(blockPtr + 4); }); - it("Detects there is not enough space", [&]() + It("Detects there is not enough space", []() { BestFitArena arena{32}; arena.GetStats()->detectLeaks = false; @@ -64,21 +62,21 @@ go_bandit([]() // 16 bytes void* p = arena.Alloc(20); new (p) TypeOfSize<20>(); - AssertThat(p, Is().Not().Null()); - AssertThat(arena.Contains(p), Is().True()); + Expect(p).ToNotEqual(nullptr); + Expect(arena.Contains(p)).ToBeTrue(); // Another 16 bytes void* p2 = arena.Alloc(6); new (p2) TypeOfSize<6>(); - AssertThat(p2, Is().Not().Null()); - AssertThat(arena.Contains(p2), Is().True()); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.Contains(p2)).ToBeTrue(); // No more space, return null void* p3 = arena.Alloc(8); // 8 bytes - AssertThat(p3, Is().Null()); + Expect(p3).ToEqual(nullptr); }); - it("Allocates with alignment", [&]() + It("Allocates with alignment", []() { BestFitArena arena{1024}; arena.GetStats()->detectLeaks = false; @@ -89,181 +87,181 @@ go_bandit([]() // When padding is not 0 (last ptr is not aligned) void* p = arena.Alloc(4, 8); new (p) TypeOfSize<4>(); - AssertThat(GetAlignmentPadding(p, 8), Is().EqualTo(0)); + Expect(GetAlignmentPadding(p, 8)).ToEqual(0); // When padding is 0 (last ptr is aligned) void* p2 = arena.Alloc(4, 16); new (p2) TypeOfSize<4>(); - AssertThat(GetAlignmentPadding(p2, 16), Is().EqualTo(0)); + Expect(GetAlignmentPadding(p2, 16)).ToEqual(0); // When padding is 0 (last ptr is aligned) void* p3 = arena.Alloc(8, 32); new (p3) TypeOfSize<8>(); - AssertThat(GetAlignmentPadding(p3, 32), Is().EqualTo(0)); + Expect(GetAlignmentPadding(p3, 32)).ToEqual(0); }); - it("Can free", [&]() + It("Can free", []() { BestFitArena arena{64}; arena.GetStats()->detectLeaks = false; void* p = arena.Alloc(32); new (p) TypeOfSize<32>(); - AssertThat(p, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(32)); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(32); arena.Free(p, 32); - AssertThat(arena.GetFreeSize(), Equals(64)); + Expect(arena.GetFreeSize()).ToEqual(64); }); - it("Can free multiple", [&]() + It("Can free multiple", []() { BestFitArena arena{64}; arena.GetStats()->detectLeaks = false; void* p = arena.Alloc(16); new (p) TypeOfSize<16>(); - AssertThat(p, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(48)); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(48); void* p2 = arena.Alloc(16); new (p2) TypeOfSize<16>(); - AssertThat(p2, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(32)); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(32); arena.Free(p2, 16); - AssertThat(arena.GetFreeSize(), Equals(48)); + Expect(arena.GetFreeSize()).ToEqual(48); arena.Free(p, 16); - AssertThat(arena.GetFreeSize(), Equals(64)); + Expect(arena.GetFreeSize()).ToEqual(64); }); - it("Can free in between allocations", [&]() + It("Can free in between allocations", []() { BestFitArena arena{64}; arena.GetStats()->detectLeaks = false; void* p = arena.Alloc(32); new (p) TypeOfSize<32>(); - AssertThat(p, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(32)); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(32); void* p2 = arena.Alloc(30); new (p2) TypeOfSize<30>(); - AssertThat(p2, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(2)); - AssertThat(arena.GetFreeSlots().Size(), Equals(1)); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(2); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); void* p3 = arena.Alloc(2); new (p3) TypeOfSize<2>(); - AssertThat(p3, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(0)); + Expect(p3).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(0); // No space left, no free slots - AssertThat(arena.GetFreeSlots().Size(), Equals(0)); + Expect(arena.GetFreeSlots().Size()).ToEqual(0); arena.Free(p2, 30); - AssertThat(arena.GetFreeSize(), Equals(30)); - AssertThat(arena.GetFreeSlots().Size(), Equals(1)); - AssertThat(arena.GetFreeSlots()[0].start, Equals(p2)); - AssertThat(arena.GetFreeSlots()[0].End(), Equals(p3)); + Expect(arena.GetFreeSize()).ToEqual(30); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + Expect(arena.GetFreeSlots()[0].start).ToEqual(p2); + Expect(arena.GetFreeSlots()[0].End()).ToEqual(p3); }); - it("Can merge previous and next slots on free", [&]() + It("Can merge previous and next slots on free", []() { BestFitArena arena{64}; arena.GetStats()->detectLeaks = false; void* p = arena.Alloc(9); new (p) TypeOfSize<9>(); - AssertThat(p, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(55)); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(55); void* p2 = arena.Alloc(50); new (p2) TypeOfSize<50>(); - AssertThat(p2, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(5)); - AssertThat(arena.GetFreeSlots().Size(), Equals(1)); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(5); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); void* p3 = arena.Alloc(5); new (p3) TypeOfSize<5>(); - AssertThat(p3, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(0)); + Expect(p3).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(0); // No space left, no free slots - AssertThat(arena.GetFreeSlots().Size(), Equals(0)); + Expect(arena.GetFreeSlots().Size()).ToEqual(0); arena.Free(p, 9); - AssertThat(arena.GetFreeSlots().Size(), Equals(1)); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); arena.Free(p3, 5); - AssertThat(arena.GetFreeSlots().Size(), Equals(2)); + Expect(arena.GetFreeSlots().Size()).ToEqual(2); arena.Free(p2, 50); // Slots previous and next are merged - AssertThat(arena.GetFreeSlots().Size(), Equals(1)); - AssertThat(arena.GetFreeSlots()[0].size, Equals(64)); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + Expect(arena.GetFreeSlots()[0].size).ToEqual(64); // Slot contains the entire memory block - AssertThat(arena.GetFreeSlots()[0].start, Equals(arena.GetBlock().data)); - AssertThat(arena.GetFreeSlots()[0].End(), Equals(arena.GetBlock().End())); + Expect(arena.GetFreeSlots()[0].start).ToEqual(arena.GetBlock().data); + Expect(arena.GetFreeSlots()[0].End()).ToEqual(arena.GetBlock().End()); }); - it("Can merge previous slot on free", [&]() + It("Can merge previous slot on free", []() { BestFitArena arena{48}; arena.GetStats()->detectLeaks = false; void* p = arena.Alloc(39); new (p) TypeOfSize<39>(); - AssertThat(p, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(9)); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(9); void* p2 = arena.Alloc(9); new (p2) TypeOfSize<9>(); - AssertThat(p2, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(0)); - AssertThat(arena.GetFreeSlots().Size(), Equals(0)); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(0); + Expect(arena.GetFreeSlots().Size()).ToEqual(0); arena.Free(p, 39); - AssertThat(arena.GetFreeSlots().Size(), Equals(1)); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); arena.Free(p2, 9); // Slot is expanded from the front - AssertThat(arena.GetFreeSlots().Size(), Equals(1)); - AssertThat(arena.GetFreeSlots()[0].size, Equals(48)); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + Expect(arena.GetFreeSlots()[0].size).ToEqual(48); // Slot contains the entire memory block - AssertThat(arena.GetFreeSlots()[0].start, Equals(arena.GetBlock().data)); - AssertThat(arena.GetFreeSlots()[0].End(), Equals(arena.GetBlock().End())); + Expect(arena.GetFreeSlots()[0].start).ToEqual(arena.GetBlock().data); + Expect(arena.GetFreeSlots()[0].End()).ToEqual(arena.GetBlock().End()); }); - it("Can merge next slot on free", [&]() + It("Can merge next slot on free", []() { BestFitArena arena{48}; arena.GetStats()->detectLeaks = false; void* p = arena.Alloc(24); new (p) TypeOfSize<24>(); - AssertThat(p, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(24)); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(24); void* p2 = arena.Alloc(24); new (p2) TypeOfSize<24>(); - AssertThat(p2, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(0)); - AssertThat(arena.GetFreeSlots().Size(), Equals(0)); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(0); + Expect(arena.GetFreeSlots().Size()).ToEqual(0); arena.Free(p2, 24); - AssertThat(arena.GetFreeSlots().Size(), Equals(1)); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); arena.Free(p, 24); // Slot is expanded from the back - AssertThat(arena.GetFreeSlots().Size(), Equals(1)); - AssertThat(arena.GetFreeSlots()[0].size, Equals(48)); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + Expect(arena.GetFreeSlots()[0].size).ToEqual(48); // Slot contains the entire memory block - AssertThat(arena.GetFreeSlots()[0].start, Equals(arena.GetBlock().data)); - AssertThat(arena.GetFreeSlots()[0].End(), Equals(arena.GetBlock().End())); + Expect(arena.GetFreeSlots()[0].start).ToEqual(arena.GetBlock().data); + Expect(arena.GetFreeSlots()[0].End()).ToEqual(arena.GetBlock().End()); }); - it("Ensures a big alignment leaves a gap", [&]() + It("Ensures a big alignment leaves a gap", []() { BestFitArena arena{128}; arena.GetStats()->detectLeaks = false; @@ -271,28 +269,28 @@ go_bandit([]() // We ensure first allocation aligns the block (just for the test) void* p = arena.Alloc(8); new (p) TypeOfSize<8>(); - AssertThat(arena.GetFreeSize(), Equals(120)); + Expect(arena.GetFreeSize()).ToEqual(120); void* p2 = arena.Alloc(8, 64); new (p2) TypeOfSize<8>(); - AssertThat(p2, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(112)); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(112); // Alignment is absolute, so the gap between p and p2 is zero // when the block base lands on a matching 64B boundary. const bool hasGap = p2 > (u8*)p + 8; - AssertThat(arena.GetFreeSlots().Size(), Equals(hasGap ? 2 : 1)); + Expect(arena.GetFreeSlots().Size()).ToEqual(hasGap ? 2 : 1); // Slot contains the rest if the block - AssertThat(arena.GetFreeSlots()[0].start, Equals((u8*)p2 + 8)); - AssertThat(arena.GetFreeSlots()[0].End(), Equals(arena.GetBlock().End())); + Expect(arena.GetFreeSlots()[0].start).ToEqual((u8*)p2 + 8); + Expect(arena.GetFreeSlots()[0].End()).ToEqual(arena.GetBlock().End()); // Slot contains the alignment gap if (hasGap) { - AssertThat(arena.GetFreeSlots()[1].start, Equals((u8*)p + 8)); - AssertThat(arena.GetFreeSlots()[1].End(), Equals(p2)); + Expect(arena.GetFreeSlots()[1].start).ToEqual((u8*)p + 8); + Expect(arena.GetFreeSlots()[1].End()).ToEqual(p2); } }); }); -}); +} diff --git a/Tests/Memory/BigBestFitArena.spec.cpp b/Tests/Memory/BigBestFitArena.spec.cpp index 26abafc6..1f667f36 100644 --- a/Tests/Memory/BigBestFitArena.spec.cpp +++ b/Tests/Memory/BigBestFitArena.spec.cpp @@ -1,11 +1,9 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include -using namespace snowhouse; -using namespace bandit; using namespace p; template @@ -15,31 +13,31 @@ struct TypeOfSize p::u8 data[size]{0}; // Fill data for debugging }; -go_bandit([]() +void RegisterMemoryBigBestFitArenaTests() { - describe("Memory.BigBestFitArena", []() + Spec("Memory.BigBestFitArena", []() { - it("Reserves a block on construction", [&]() + It("Reserves a block on construction", []() { BigBestFitArena arena{1024}; arena.GetStats()->detectLeaks = false; - AssertThat(arena.GetFreeSize(), Equals(1024)); - AssertThat(*arena.GetBlock(), Is().Not().Null()); - AssertThat(arena.GetBlock().size, Is().EqualTo(1024)); + Expect(arena.GetFreeSize()).ToEqual(1024); + Expect(*arena.GetBlock()).ToNotEqual(nullptr); + Expect(arena.GetBlock().size).ToEqual(1024); }); - it("Can allocate", [&]() + It("Can allocate", []() { BigBestFitArena arena{1024}; arena.GetStats()->detectLeaks = false; void* p = arena.Alloc(4); new (p) TypeOfSize<4>(); - AssertThat(p, Is().Not().Null()); - AssertThat(arena.Contains(p), Is().True()); + Expect(p).ToNotEqual(nullptr); + Expect(arena.Contains(p)).ToBeTrue(); }); - it("Allocates at correct addresses", [&]() + It("Allocates at correct addresses", []() { BigBestFitArena arena{1024}; arena.GetStats()->detectLeaks = false; @@ -49,16 +47,16 @@ go_bandit([]() void* p = arena.Alloc(4); new (p) TypeOfSize<4>(); const void* expectedP = blockPtr + p::GetAlignmentPaddingWithHeader(blockPtr, 8, 8); - AssertThat(p, Is().EqualTo(expectedP)); + Expect(p).ToEqual(expectedP); void* p2 = arena.Alloc(4); new (p2) TypeOfSize<4>(); void* expectedP2 = static_cast(p) + 8 + p::GetAlignmentPaddingWithHeader(p, 8, 8); - AssertThat(p2, Is().EqualTo(expectedP2)); + Expect(p2).ToEqual(expectedP2); }); - it("Detects there is not enough space", [&]() + It("Detects there is not enough space", []() { BigBestFitArena arena{32}; arena.GetStats()->detectLeaks = false; @@ -66,21 +64,21 @@ go_bandit([]() // 16 bytes void* p = arena.Alloc(8); new (p) TypeOfSize<8>(); - AssertThat(p, Is().Not().Null()); - AssertThat(arena.Contains(p), Is().True()); + Expect(p).ToNotEqual(nullptr); + Expect(arena.Contains(p)).ToBeTrue(); // Another 16 bytes void* p2 = arena.Alloc(4); new (p2) TypeOfSize<4>(); - AssertThat(p2, Is().Not().Null()); - AssertThat(arena.Contains(p2), Is().True()); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.Contains(p2)).ToBeTrue(); // No more space, return null void* p3 = arena.Alloc(8); // 8 bytes - AssertThat(p3, Is().Null()); + Expect(p3).ToEqual(nullptr); }); - it("Allocates with alignment", [&]() + It("Allocates with alignment", []() { BigBestFitArena arena{1024}; arena.GetStats()->detectLeaks = false; @@ -91,190 +89,187 @@ go_bandit([]() // When padding is not 0 (last ptr is not aligned) void* p = arena.Alloc(4, 8); new (p) TypeOfSize<4>(); - AssertThat(p::GetAlignmentPadding(p, 8), Is().EqualTo(0)); + Expect(p::GetAlignmentPadding(p, 8)).ToEqual(0); // When padding is 0 (last ptr is aligned) void* p2 = arena.Alloc(4, 16); new (p2) TypeOfSize<4>(); - AssertThat(p::GetAlignmentPadding(p2, 16), Is().EqualTo(0)); + Expect(p::GetAlignmentPadding(p2, 16)).ToEqual(0); // When padding is 0 (last ptr is aligned) void* p3 = arena.Alloc(8, 32); new (p3) TypeOfSize<8>(); - AssertThat(p::GetAlignmentPadding(p3, 32), Is().EqualTo(0)); + Expect(p::GetAlignmentPadding(p3, 32)).ToEqual(0); }); - it("Can free", [&]() + It("Can free", []() { BigBestFitArena arena{64}; arena.GetStats()->detectLeaks = false; void* p = arena.Alloc(32); new (p) TypeOfSize<32>(); - AssertThat(p, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(24)); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(24); arena.Free(p, 32); - AssertThat(arena.GetFreeSize(), Equals(64)); + Expect(arena.GetFreeSize()).ToEqual(64); }); - it("Can free multiple", [&]() + It("Can free multiple", []() { BigBestFitArena arena{64}; arena.GetStats()->detectLeaks = false; void* p = arena.Alloc(16); new (p) TypeOfSize<16>(); - AssertThat(p, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(40)); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(40); void* p2 = arena.Alloc(16); new (p2) TypeOfSize<16>(); - AssertThat(p2, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(16)); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(16); arena.Free(p2, 16); - AssertThat(arena.GetFreeSize(), Equals(40)); + Expect(arena.GetFreeSize()).ToEqual(40); arena.Free(p, 16); - AssertThat(arena.GetFreeSize(), Equals(64)); + Expect(arena.GetFreeSize()).ToEqual(64); }); - it("Can free in between allocations", [&]() + It("Can free in between allocations", []() { BigBestFitArena arena{64}; arena.GetStats()->detectLeaks = false; void* p = arena.Alloc(16); new (p) TypeOfSize<16>(); - AssertThat(p, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(40)); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(40); void* p2 = arena.Alloc(16); new (p2) TypeOfSize<16>(); - AssertThat(p2, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(16)); - AssertThat(arena.GetFreeSlots().Size(), Equals(1)); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(16); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); void* p3 = arena.Alloc(8); new (p3) TypeOfSize<8>(); - AssertThat(p3, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(0)); + Expect(p3).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(0); // No space left, no free slots - AssertThat(arena.GetFreeSlots().Size(), Equals(0)); + Expect(arena.GetFreeSlots().Size()).ToEqual(0); arena.Free(p2, 16); - AssertThat(arena.GetFreeSize(), Equals(24)); - AssertThat(arena.GetFreeSlots().Size(), Equals(1)); + Expect(arena.GetFreeSize()).ToEqual(24); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); auto slot = arena.GetFreeSlots()[0]; u8* slotStart = (u8*)arena.GetBlock().data + slot.offset; - AssertThat(slotStart, Equals(static_cast(p2) - 8)); - AssertThat(slotStart + slot.size, Equals(static_cast(p3) - 8)); + Expect(slotStart).ToEqual(static_cast(p2) - 8); + Expect(slotStart + slot.size).ToEqual(static_cast(p3) - 8); }); - it("Can merge previous and next slots on free", [&]() + It("Can merge previous and next slots on free", []() { BigBestFitArena arena{64}; arena.GetStats()->detectLeaks = false; void* p = arena.Alloc(16); new (p) TypeOfSize<16>(); - AssertThat(p, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(40)); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(40); void* p2 = arena.Alloc(16); new (p2) TypeOfSize<16>(); - AssertThat(p2, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(16)); - AssertThat(arena.GetFreeSlots().Size(), Equals(1)); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(16); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); void* p3 = arena.Alloc(8); new (p3) TypeOfSize<8>(); - AssertThat(p3, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(0)); + Expect(p3).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(0); // No space left, no free slots - AssertThat(arena.GetFreeSlots().Size(), Equals(0)); + Expect(arena.GetFreeSlots().Size()).ToEqual(0); arena.Free(p, 16); - AssertThat(arena.GetFreeSlots().Size(), Equals(1)); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); arena.Free(p3, 8); - AssertThat(arena.GetFreeSlots().Size(), Equals(2)); + Expect(arena.GetFreeSlots().Size()).ToEqual(2); arena.Free(p2, 16); // Slots previous and next are merged - AssertThat(arena.GetFreeSlots().Size(), Equals(1)); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); // Slot contains the entire memory block auto slot = arena.GetFreeSlots()[0]; u8* slotStart = (u8*)arena.GetBlock().data + slot.offset; - AssertThat(slotStart, Equals(static_cast(arena.GetBlock().data))); - AssertThat( - slotStart + slot.size, Equals(static_cast(arena.GetBlock().End()))); + Expect(slotStart).ToEqual(static_cast(arena.GetBlock().data)); + Expect(slotStart + slot.size).ToEqual(static_cast(arena.GetBlock().End())); }); - it("Can merge previous slot on free", [&]() + It("Can merge previous slot on free", []() { BigBestFitArena arena{48}; arena.GetStats()->detectLeaks = false; void* p = arena.Alloc(16); new (p) TypeOfSize<16>(); - AssertThat(p, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(24)); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(24); void* p2 = arena.Alloc(16); new (p2) TypeOfSize<16>(); - AssertThat(p2, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(0)); - AssertThat(arena.GetFreeSlots().Size(), Equals(0)); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(0); + Expect(arena.GetFreeSlots().Size()).ToEqual(0); arena.Free(p, 16); - AssertThat(arena.GetFreeSlots().Size(), Equals(1)); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); arena.Free(p2, 16); // Slot is expanded from the front - AssertThat(arena.GetFreeSlots().Size(), Equals(1)); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); // Slot contains the entire memory block auto slot = arena.GetFreeSlots()[0]; u8* slotStart = (u8*)arena.GetBlock().data + slot.offset; - AssertThat(slotStart, Equals(static_cast(arena.GetBlock().data))); - AssertThat( - slotStart + slot.size, Equals(static_cast(arena.GetBlock().End()))); + Expect(slotStart).ToEqual(static_cast(arena.GetBlock().data)); + Expect(slotStart + slot.size).ToEqual(static_cast(arena.GetBlock().End())); }); - it("Can merge next slot on free", [&]() + It("Can merge next slot on free", []() { BigBestFitArena arena{48}; arena.GetStats()->detectLeaks = false; void* p = arena.Alloc(16); new (p) TypeOfSize<16>(); - AssertThat(p, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(24)); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(24); void* p2 = arena.Alloc(16); new (p2) TypeOfSize<16>(); - AssertThat(p2, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(0)); - AssertThat(arena.GetFreeSlots().Size(), Equals(0)); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(0); + Expect(arena.GetFreeSlots().Size()).ToEqual(0); arena.Free(p2, 16); - AssertThat(arena.GetFreeSlots().Size(), Equals(1)); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); arena.Free(p, 16); // Slot is expanded from the back - AssertThat(arena.GetFreeSlots().Size(), Equals(1)); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); // Slot contains the entire memory block auto slot = arena.GetFreeSlots()[0]; u8* slotStart = (u8*)arena.GetBlock().data + slot.offset; - AssertThat(slotStart, Equals(static_cast(arena.GetBlock().data))); - AssertThat( - slotStart + slot.size, Equals(static_cast(arena.GetBlock().End()))); + Expect(slotStart).ToEqual(static_cast(arena.GetBlock().data)); + Expect(slotStart + slot.size).ToEqual(static_cast(arena.GetBlock().End())); }); - it("Ensures a big alignment leaves a gap", [&]() + It("Ensures a big alignment leaves a gap", []() { BigBestFitArena arena{128}; arena.GetStats()->detectLeaks = false; @@ -282,33 +277,32 @@ go_bandit([]() // We ensure first allocation aligns the block (just for the test) void* p = arena.Alloc(8); new (p) TypeOfSize<8>(); - AssertThat(arena.GetFreeSize(), Equals(112)); + Expect(arena.GetFreeSize()).ToEqual(112); void* p2 = arena.Alloc(8, 64); new (p2) TypeOfSize<8>(); - AssertThat(p2, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(96)); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(96); // Alignment is absolute, so the gap between p and p2 is zero // when the block base lands on a matching 64B boundary. const bool hasGap = arena.GetAllocationStart(p2) > arena.GetAllocationEnd(p); - AssertThat(arena.GetFreeSlots().Size(), Equals(hasGap ? 2 : 1)); + Expect(arena.GetFreeSlots().Size()).ToEqual(hasGap ? 2 : 1); // Slot contains the rest if the block auto slot0 = arena.GetFreeSlots()[0]; u8* slot0Start = (u8*)arena.GetBlock().data + slot0.offset; - AssertThat(slot0Start, Equals(arena.GetAllocationEnd(p2))); - AssertThat( - slot0Start + slot0.size, Equals(static_cast(arena.GetBlock().End()))); + Expect(slot0Start).ToEqual(arena.GetAllocationEnd(p2)); + Expect(slot0Start + slot0.size).ToEqual(static_cast(arena.GetBlock().End())); // Slot contains the alignment gap if (hasGap) { auto slot1 = arena.GetFreeSlots()[1]; u8* slot1Start = (u8*)arena.GetBlock().data + slot1.offset; - AssertThat(slot1Start, Equals(arena.GetAllocationEnd(p))); - AssertThat(slot1Start + slot1.size, Equals(arena.GetAllocationStart(p2))); + Expect(slot1Start).ToEqual(arena.GetAllocationEnd(p)); + Expect(slot1Start + slot1.size).ToEqual(arena.GetAllocationStart(p2)); } }); }); -}); +} diff --git a/Tests/Memory/Memory.spec.cpp b/Tests/Memory/Memory.spec.cpp index c0c41835..2a69c6af 100644 --- a/Tests/Memory/Memory.spec.cpp +++ b/Tests/Memory/Memory.spec.cpp @@ -1,11 +1,9 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include -using namespace snowhouse; -using namespace bandit; using namespace p; template @@ -55,39 +53,39 @@ struct MoveType }; -go_bandit([]() +void RegisterMemoryMemoryTests() { - describe("Memory.Operations", []() + Spec("Memory.Operations", []() { - it("Can default construct", [&]() + It("Can default construct", []() { // Check that it inits to 0 bool boolValues[2]{true, true}; // Assign simulated garbage ConstructItems(boolValues, 2); - AssertThat(boolValues[0], Is().EqualTo(false)); - AssertThat(boolValues[1], Is().EqualTo(false)); + Expect(boolValues[0]).ToEqual(false); + Expect(boolValues[1]).ToEqual(false); u8 u8Values[2]{34, 45}; // Assign simulated garbage ConstructItems(u8Values, 2, u8(128)); - AssertThat(u8Values[0], Is().EqualTo(128)); - AssertThat(u8Values[1], Is().EqualTo(128)); + Expect(u8Values[0]).ToEqual(128); + Expect(u8Values[1]).ToEqual(128); u32 u32Values[2]{34, 45}; // Assign simulated garbage ConstructItems(u32Values, 2); - AssertThat(u32Values[0], Is().EqualTo(0)); - AssertThat(u32Values[1], Is().EqualTo(0)); + Expect(u32Values[0]).ToEqual(0); + Expect(u32Values[1]).ToEqual(0); u32* ptrValues[2]{(u32*)1, (u32*)2}; // Assign simulated garbage ConstructItems(ptrValues, 2); - AssertThat(ptrValues[0], Is().EqualTo(nullptr)); - AssertThat(ptrValues[1], Is().EqualTo(nullptr)); + Expect(ptrValues[0]).ToEqual(nullptr); + Expect(ptrValues[1]).ToEqual(nullptr); ConstructedType constructedValues[2]; constructedValues[0].value = 0.234f; // Assign simulated garbage constructedValues[1].value = 0.234f; ConstructItems(constructedValues, 2); - AssertThat(constructedValues[0].value, Is().EqualTo(0.f)); - AssertThat(constructedValues[1].value, Is().EqualTo(0.f)); + Expect(constructedValues[0].value).ToEqual(0.f); + Expect(constructedValues[1].value).ToEqual(0.f); BoolsType boolsValues[2]; boolsValues[0].value1 = false; // Assign simulated garbage @@ -95,156 +93,156 @@ go_bandit([]() boolsValues[1].value1 = false; boolsValues[1].value2 = false; ConstructItems(boolsValues, 2); - AssertThat(boolsValues[0].value1, Is().EqualTo(true)); - AssertThat(boolsValues[0].value2, Is().EqualTo(false)); - AssertThat(boolsValues[1].value1, Is().EqualTo(true)); - AssertThat(boolsValues[1].value2, Is().EqualTo(false)); + Expect(boolsValues[0].value1).ToEqual(true); + Expect(boolsValues[0].value2).ToEqual(false); + Expect(boolsValues[1].value1).ToEqual(true); + Expect(boolsValues[1].value2).ToEqual(false); }); - it("Can value construct", [&]() + It("Can value construct", []() { bool boolValues[2]{false, false}; // Assign simulated garbage ConstructItems(boolValues, 2, true); - AssertThat(boolValues[0], Is().EqualTo(true)); - AssertThat(boolValues[1], Is().EqualTo(true)); + Expect(boolValues[0]).ToEqual(true); + Expect(boolValues[1]).ToEqual(true); u8 u8Values[2]{34, 45}; // Assign simulated garbage ConstructItems(u8Values, 2, u8(128)); - AssertThat(u8Values[0], Is().EqualTo(128)); - AssertThat(u8Values[1], Is().EqualTo(128)); + Expect(u8Values[0]).ToEqual(128); + Expect(u8Values[1]).ToEqual(128); u32 u32Values[2]{34, 45}; // Assign simulated garbage ConstructItems(u32Values, 2, u32(128)); - AssertThat(u32Values[0], Is().EqualTo(128)); - AssertThat(u32Values[1], Is().EqualTo(128)); + Expect(u32Values[0]).ToEqual(128); + Expect(u32Values[1]).ToEqual(128); u32* ptrValues[2]{(u32*)1, (u32*)2}; // Assign simulated garbage ConstructItems(ptrValues, 2, (u32*)32); - AssertThat(ptrValues[0], Is().EqualTo((u32*)32)); - AssertThat(ptrValues[1], Is().EqualTo((u32*)32)); + Expect(ptrValues[0]).ToEqual((u32*)32); + Expect(ptrValues[1]).ToEqual((u32*)32); ConstructedType constructedValues[2]{{0.234f}, {0.234f}}; // Assign simulated garbage ConstructItems(constructedValues, 2, ConstructedType(1.f)); - AssertThat(constructedValues[0].value, Is().EqualTo(1.f)); - AssertThat(constructedValues[1].value, Is().EqualTo(1.f)); + Expect(constructedValues[0].value).ToEqual(1.f); + Expect(constructedValues[1].value).ToEqual(1.f); BoolsType boolsValues[2]{ {.value1 = false, .value2 = true}, {.value1 = false, .value2 = true} - }; // Assign simulated garbage + }; // Assign simulated garbage ConstructItems(boolsValues, 2, BoolsType{.value1 = true, .value2 = true}); - AssertThat(boolsValues[0].value1, Is().EqualTo(true)); - AssertThat(boolsValues[0].value2, Is().EqualTo(true)); - AssertThat(boolsValues[1].value1, Is().EqualTo(true)); - AssertThat(boolsValues[1].value2, Is().EqualTo(true)); + Expect(boolsValues[0].value1).ToEqual(true); + Expect(boolsValues[0].value2).ToEqual(true); + Expect(boolsValues[1].value1).ToEqual(true); + Expect(boolsValues[1].value2).ToEqual(true); }); - it("Can copy construct", [&]() + It("Can copy construct", []() { bool boolValues[2]{false, false}; // Assign simulated garbage bool srcBoolValues[2]{true, false}; CopyConstructItems(boolValues, 2, srcBoolValues); - AssertThat(boolValues[0], Is().EqualTo(true)); - AssertThat(boolValues[1], Is().EqualTo(false)); + Expect(boolValues[0]).ToEqual(true); + Expect(boolValues[1]).ToEqual(false); u8 u8Values[2]{34, 45}; // Assign simulated garbage u8 srcU8Values[2]{128, 129}; CopyConstructItems(u8Values, 2, srcU8Values); - AssertThat(u8Values[0], Is().EqualTo(128)); - AssertThat(u8Values[1], Is().EqualTo(129)); + Expect(u8Values[0]).ToEqual(128); + Expect(u8Values[1]).ToEqual(129); u32 u32Values[2]{34, 45}; // Assign simulated garbage u32 srcU32Values[2]{128, 129}; CopyConstructItems(u32Values, 2, srcU32Values); - AssertThat(u32Values[0], Is().EqualTo(128)); - AssertThat(u32Values[1], Is().EqualTo(129)); + Expect(u32Values[0]).ToEqual(128); + Expect(u32Values[1]).ToEqual(129); u32* ptrValues[2]{(u32*)1, (u32*)2}; // Assign simulated garbage u32* srcPtrValues[2]{(u32*)34, (u32*)23433}; CopyConstructItems(ptrValues, 2, srcPtrValues); - AssertThat(ptrValues[0], Is().EqualTo((u32*)34)); - AssertThat(ptrValues[1], Is().EqualTo((u32*)23433)); + Expect(ptrValues[0]).ToEqual((u32*)34); + Expect(ptrValues[1]).ToEqual((u32*)23433); ConstructedType constructedValues[2]{{0.234f}, {0.234f}}; // Assign simulated garbage ConstructedType srcConstructedValues[2]{{1.f}, {2.f}}; CopyConstructItems(constructedValues, 2, srcConstructedValues); - AssertThat(constructedValues[0].value, Is().EqualTo(1.f)); - AssertThat(constructedValues[1].value, Is().EqualTo(2.f)); + Expect(constructedValues[0].value).ToEqual(1.f); + Expect(constructedValues[1].value).ToEqual(2.f); BoolsType boolsValues[2]{ {false, true}, - {false, true} - }; // Assign simulated garbage + {false, true} + }; // Assign simulated garbage BoolsType srcBoolsValues[2]{ {true, false}, - {false, true } - }; + {false, true } + }; CopyConstructItems(boolsValues, 2, srcBoolsValues); - AssertThat(boolsValues[0].value1, Is().EqualTo(true)); - AssertThat(boolsValues[0].value2, Is().EqualTo(false)); - AssertThat(boolsValues[1].value1, Is().EqualTo(false)); - AssertThat(boolsValues[1].value2, Is().EqualTo(true)); + Expect(boolsValues[0].value1).ToEqual(true); + Expect(boolsValues[0].value2).ToEqual(false); + Expect(boolsValues[1].value1).ToEqual(false); + Expect(boolsValues[1].value2).ToEqual(true); CopyType copyValues[2]{5, 6}; // Assign simulated garbage CopyType srcCopyValues[2]{34, 75}; CopyConstructItems(copyValues, 2, srcCopyValues); - AssertThat(copyValues[0].value, Is().EqualTo(34)); - AssertThat(copyValues[1].value, Is().EqualTo(75)); + Expect(copyValues[0].value).ToEqual(34); + Expect(copyValues[1].value).ToEqual(75); }); - it("Can move construct", [&]() + It("Can move construct", []() { bool boolValues[2]{false, false}; // Assign simulated garbage bool srcBoolValues[2]{true, false}; MoveConstructItems(boolValues, 2, srcBoolValues); - AssertThat(boolValues[0], Is().EqualTo(true)); - AssertThat(boolValues[1], Is().EqualTo(false)); + Expect(boolValues[0]).ToEqual(true); + Expect(boolValues[1]).ToEqual(false); u8 u8Values[2]{34, 45}; // Assign simulated garbage u8 srcU8Values[2]{128, 129}; MoveConstructItems(u8Values, 2, srcU8Values); - AssertThat(u8Values[0], Is().EqualTo(128)); - AssertThat(u8Values[1], Is().EqualTo(129)); + Expect(u8Values[0]).ToEqual(128); + Expect(u8Values[1]).ToEqual(129); u32 u32Values[2]{34, 45}; // Assign simulated garbage u32 srcU32Values[2]{128, 129}; MoveConstructItems(u32Values, 2, srcU32Values); - AssertThat(u32Values[0], Is().EqualTo(128)); - AssertThat(u32Values[1], Is().EqualTo(129)); + Expect(u32Values[0]).ToEqual(128); + Expect(u32Values[1]).ToEqual(129); u32* ptrValues[2]{(u32*)1, (u32*)2}; // Assign simulated garbage u32* srcPtrValues[2]{(u32*)34, (u32*)23433}; MoveConstructItems(ptrValues, 2, srcPtrValues); - AssertThat(ptrValues[0], Is().EqualTo((u32*)34)); - AssertThat(ptrValues[1], Is().EqualTo((u32*)23433)); + Expect(ptrValues[0]).ToEqual((u32*)34); + Expect(ptrValues[1]).ToEqual((u32*)23433); ConstructedType constructedValues[2]{{0.234f}, {0.234f}}; // Assign simulated garbage ConstructedType srcConstructedTypeValues[2]{{1.f}, {2.f}}; MoveConstructItems(constructedValues, 2, srcConstructedTypeValues); - AssertThat(constructedValues[0].value, Is().EqualTo(1.f)); - AssertThat(constructedValues[1].value, Is().EqualTo(2.f)); + Expect(constructedValues[0].value).ToEqual(1.f); + Expect(constructedValues[1].value).ToEqual(2.f); BoolsType boolsValues[2]{ {.value1 = false, .value2 = true}, {.value1 = false, .value2 = true} - }; // Assign simulated garbage + }; // Assign simulated garbage BoolsType srcConstructedType2Values[2]{ {.value1 = true, .value2 = false}, - {.value1 = false, .value2 = true } - }; + {.value1 = false, .value2 = true } + }; MoveConstructItems(boolsValues, 2, srcConstructedType2Values); - AssertThat(boolsValues[0].value1, Is().EqualTo(true)); - AssertThat(boolsValues[0].value2, Is().EqualTo(false)); - AssertThat(boolsValues[1].value1, Is().EqualTo(false)); - AssertThat(boolsValues[1].value2, Is().EqualTo(true)); + Expect(boolsValues[0].value1).ToEqual(true); + Expect(boolsValues[0].value2).ToEqual(false); + Expect(boolsValues[1].value1).ToEqual(false); + Expect(boolsValues[1].value2).ToEqual(true); MoveType moveValues[2]{5, 6}; // Assign simulated garbage MoveType srcMoveValues[2]{34, 75}; MoveConstructItems(moveValues, 2, srcMoveValues); - AssertThat(moveValues[0].value, Is().EqualTo(34)); - AssertThat(moveValues[1].value, Is().EqualTo(75)); - AssertThat(srcMoveValues[0].value, Is().EqualTo(0)); - AssertThat(srcMoveValues[1].value, Is().EqualTo(0)); + Expect(moveValues[0].value).ToEqual(34); + Expect(moveValues[1].value).ToEqual(75); + Expect(srcMoveValues[0].value).ToEqual(0); + Expect(srcMoveValues[1].value).ToEqual(0); }); }); -}); +} diff --git a/Tests/Memory/MemoryStats.spec.cpp b/Tests/Memory/MemoryStats.spec.cpp index 27edffad..2e407aa2 100644 --- a/Tests/Memory/MemoryStats.spec.cpp +++ b/Tests/Memory/MemoryStats.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include #include @@ -8,8 +8,6 @@ #include -using namespace snowhouse; -using namespace bandit; using namespace p; @@ -24,49 +22,49 @@ static const MemoryStatsEvent* LiveFind(const MemoryStats& s, void* ptr) } -go_bandit([]() +void RegisterMemoryMemoryStatsTests() { - describe("Memory.MemoryStats", []() + Spec("Memory.MemoryStats", []() { - describe("Basic", [&]() + Describe("Basic", []() { - it("Starts empty", [&]() + It("Starts empty", []() { MemoryStats s; s.CollectStats(); - AssertThat(s.used, Is().EqualTo(0)); - AssertThat(s.totalAllocated, Is().EqualTo(0)); - AssertThat(LiveCount(s), Is().EqualTo(0)); + Expect(s.used).ToEqual(0); + Expect(s.totalAllocated).ToEqual(0); + Expect(LiveCount(s)).ToEqual(0); }); - it("Tracks a single add", [&]() + It("Tracks a single add", []() { MemoryStats s; s.detectLeaks = false; s.Add((void*)0x1000, 64); s.CollectStats(); - AssertThat(s.used, Is().EqualTo(64)); - AssertThat(s.totalAllocated, Is().EqualTo(64)); - AssertThat(LiveCount(s), Is().EqualTo(1)); - AssertThat(LiveFind(s, (void*)0x1000) != nullptr, Is().True()); - AssertThat(LiveFind(s, (void*)0x1000)->GetSize(), Is().EqualTo(64)); - AssertThat(LiveFind(s, (void*)0x1000)->IsFree(), Is().EqualTo(false)); + Expect(s.used).ToEqual(64); + Expect(s.totalAllocated).ToEqual(64); + Expect(LiveCount(s)).ToEqual(1); + Expect(LiveFind(s, (void*)0x1000) != nullptr).ToBeTrue(); + Expect(LiveFind(s, (void*)0x1000)->GetSize()).ToEqual(64); + Expect(LiveFind(s, (void*)0x1000)->IsFree()).ToEqual(false); }); - it("Tracks add plus free", [&]() + It("Tracks add plus free", []() { MemoryStats s; s.detectLeaks = false; s.Add((void*)0x1000, 64); s.Remove((void*)0x1000, 64); s.CollectStats(); - AssertThat(s.used, Is().EqualTo(0)); - AssertThat(LiveCount(s), Is().EqualTo(0)); + Expect(s.used).ToEqual(0); + Expect(LiveCount(s)).ToEqual(0); // totalAllocated is cumulative alloc bytes ever. - AssertThat(s.totalAllocated, Is().EqualTo(64)); + Expect(s.totalAllocated).ToEqual(64); }); - it("Tracks multiple adds", [&]() + It("Tracks multiple adds", []() { MemoryStats s; s.detectLeaks = false; @@ -74,12 +72,12 @@ go_bandit([]() s.Add((void*)0x2000, 32); s.Add((void*)0x3000, 64); s.CollectStats(); - AssertThat(s.used, Is().EqualTo(16 + 32 + 64)); - AssertThat(s.totalAllocated, Is().EqualTo(16 + 32 + 64)); - AssertThat(LiveCount(s), Is().EqualTo(3)); + Expect(s.used).ToEqual(16 + 32 + 64); + Expect(s.totalAllocated).ToEqual(16 + 32 + 64); + Expect(LiveCount(s)).ToEqual(3); }); - it("Tracks many adds and frees", [&]() + It("Tracks many adds and frees", []() { MemoryStats s; s.detectLeaks = false; @@ -96,12 +94,12 @@ go_bandit([]() } s.CollectStats(); - AssertThat(s.used, Is().EqualTo((N / 2) * 16)); - AssertThat(s.totalAllocated, Is().EqualTo(N * 16)); - AssertThat(LiveCount(s), Is().EqualTo(N / 2)); + Expect(s.used).ToEqual((N / 2) * 16); + Expect(s.totalAllocated).ToEqual(N * 16); + Expect(LiveCount(s)).ToEqual(N / 2); }); - it("Ignores double-free", [&]() + It("Ignores double-free", []() { MemoryStats s; s.detectLeaks = false; @@ -110,21 +108,21 @@ go_bandit([]() s.Remove((void*)0x1000, 64); s.CollectStats(); // The second free matches no live alloc and is ignored. - AssertThat(s.used, Is().EqualTo(0)); - AssertThat(LiveCount(s), Is().EqualTo(0)); + Expect(s.used).ToEqual(0); + Expect(LiveCount(s)).ToEqual(0); }); - it("Ignores free of unknown ptr", [&]() + It("Ignores free of unknown ptr", []() { MemoryStats s; s.detectLeaks = false; s.Remove((void*)0xDEAD, 64); s.CollectStats(); - AssertThat(s.used, Is().EqualTo(0)); - AssertThat(LiveCount(s), Is().EqualTo(0)); + Expect(s.used).ToEqual(0); + Expect(LiveCount(s)).ToEqual(0); }); - it("Records duplicate allocs as UnfreedRealloc", [&]() + It("Records duplicate allocs as UnfreedRealloc", []() { MemoryStats s; s.detectLeaks = false; @@ -133,37 +131,37 @@ go_bandit([]() s.CollectStats(); // Same ptr twice: the second alloc is an error and the // live set is left untouched. - AssertThat(LiveCount(s), Is().EqualTo(1)); - AssertThat(LiveFind(s, (void*)0x1000)->GetSize(), Is().EqualTo(64)); - AssertThat(s.errors.Size(), Is().EqualTo(1)); - AssertThat(s.errors[0].kind == MemoryStatsErrorType::UnfreedRealloc, Is().True()); - AssertThat(s.errors[0].event.GetSize(), Is().EqualTo(128)); - AssertThat(s.used, Is().EqualTo(64)); + Expect(LiveCount(s)).ToEqual(1); + Expect(LiveFind(s, (void*)0x1000)->GetSize()).ToEqual(64); + Expect(s.errors.Size()).ToEqual(1); + Expect(s.errors[0].kind == MemoryStatsErrorType::UnfreedRealloc).ToBeTrue(); + Expect(s.errors[0].event.GetSize()).ToEqual(128); + Expect(s.used).ToEqual(64); }); - it("CheckLeaks always runs when called directly", [&]() + It("CheckLeaks always runs when called directly", []() { MemoryStats s; s.detectLeaks = false; s.Add((void*)0x1000, 64); s.CollectStats(); s.CheckLeaks(); - AssertThat(LiveCount(s), Is().EqualTo(1)); - AssertThat(s.used, Is().EqualTo(64)); + Expect(LiveCount(s)).ToEqual(1); + Expect(s.used).ToEqual(64); }); - it("Always tracks frees (no trackFrees flag)", [&]() + It("Always tracks frees (no trackFrees flag)", []() { MemoryStats s; s.detectLeaks = false; s.Add((void*)0x1000, 64); s.Remove((void*)0x1000, 64); s.CollectStats(); - AssertThat(s.used, Is().EqualTo(0)); - AssertThat(LiveCount(s), Is().EqualTo(0)); + Expect(s.used).ToEqual(0); + Expect(LiveCount(s)).ToEqual(0); }); - it("CheckLeaks with null name does not crash", [&]() + It("CheckLeaks with null name does not crash", []() { { // detectLeaks defaults to true and name defaults to null. @@ -174,7 +172,7 @@ go_bandit([]() } }); - it("live list only keeps unmatched allocs", [&]() + It("live list only keeps unmatched allocs", []() { MemoryStats s; s.detectLeaks = false; @@ -186,17 +184,17 @@ go_bandit([]() s.Remove((void*)0xDEAD, 16); s.CollectStats(); - AssertThat(LiveCount(s), Is().EqualTo(2)); - AssertThat(LiveFind(s, (void*)0x1000)->GetSize(), Is().EqualTo(64)); - AssertThat(LiveFind(s, (void*)0x2000)->GetSize(), Is().EqualTo(32)); + Expect(LiveCount(s)).ToEqual(2); + Expect(LiveFind(s, (void*)0x1000)->GetSize()).ToEqual(64); + Expect(LiveFind(s, (void*)0x2000)->GetSize()).ToEqual(32); // Re-collecting must preserve the live list identically. s.CollectStats(); - AssertThat(LiveCount(s), Is().EqualTo(2)); - AssertThat(s.used, Is().EqualTo(64 + 32)); + Expect(LiveCount(s)).ToEqual(2); + Expect(s.used).ToEqual(64 + 32); }); - it("Alternating instances on one thread", [&]() + It("Alternating instances on one thread", []() { // Exercises thread context reuse when the owner switches. MemoryStats a; @@ -212,28 +210,28 @@ go_bandit([]() a.CollectStats(); b.CollectStats(); - AssertThat(a.used, Is().EqualTo(64 + 16)); - AssertThat(LiveCount(a), Is().EqualTo(2)); - AssertThat(b.used, Is().EqualTo(0)); - AssertThat(LiveCount(b), Is().EqualTo(0)); + Expect(a.used).ToEqual(64 + 16); + Expect(LiveCount(a)).ToEqual(2); + Expect(b.used).ToEqual(0); + Expect(LiveCount(b)).ToEqual(0); }); - it("Add after Reset works", [&]() + It("Add after Reset works", []() { MemoryStats s; s.detectLeaks = false; s.Add((void*)0x1000, 64); s.Reset(); - AssertThat(LiveCount(s), Is().EqualTo(0)); + Expect(LiveCount(s)).ToEqual(0); s.Add((void*)0x2000, 32); s.CollectStats(); - AssertThat(s.used, Is().EqualTo(32)); - AssertThat(s.totalAllocated, Is().EqualTo(32)); - AssertThat(LiveCount(s), Is().EqualTo(1)); + Expect(s.used).ToEqual(32); + Expect(s.totalAllocated).ToEqual(32); + Expect(LiveCount(s)).ToEqual(1); }); - it("Duplicate allocs record UnfreedRealloc and live stays usable", [&]() + It("Duplicate allocs record UnfreedRealloc and live stays usable", []() { MemoryStats s; s.detectLeaks = false; @@ -243,61 +241,61 @@ go_bandit([]() s.Add((void*)0x1000, 64); s.Add((void*)0x1000, 64); s.CollectStats(); - AssertThat(LiveCount(s), Is().EqualTo(1)); - AssertThat(s.errors.Size(), Is().EqualTo(1)); - AssertThat(s.errors[0].kind == MemoryStatsErrorType::UnfreedRealloc, Is().True()); - AssertThat(s.used, Is().EqualTo(64)); + Expect(LiveCount(s)).ToEqual(1); + Expect(s.errors.Size()).ToEqual(1); + Expect(s.errors[0].kind == MemoryStatsErrorType::UnfreedRealloc).ToBeTrue(); + Expect(s.used).ToEqual(64); // Collect 2: freeing the original alloc still works. s.Remove((void*)0x1000, 64); s.CollectStats(); - AssertThat(LiveCount(s), Is().EqualTo(0)); - AssertThat(s.used, Is().EqualTo(0)); + Expect(LiveCount(s)).ToEqual(0); + Expect(s.used).ToEqual(0); }); - it("Free with wrong size records SizeMismatch", [&]() + It("Free with wrong size records SizeMismatch", []() { MemoryStats s; s.detectLeaks = false; s.Add((void*)0x1000, 64); s.Remove((void*)0x1000, 32); // size mismatch s.CollectStats(); - AssertThat(LiveCount(s), Is().EqualTo(1)); - AssertThat(s.errors.Size(), Is().EqualTo(1)); - AssertThat(s.errors[0].kind == MemoryStatsErrorType::SizeMismatch, Is().True()); - AssertThat(s.errors[0].event.GetSize(), Is().EqualTo(32)); - AssertThat(s.used, Is().EqualTo(64)); + Expect(LiveCount(s)).ToEqual(1); + Expect(s.errors.Size()).ToEqual(1); + Expect(s.errors[0].kind == MemoryStatsErrorType::SizeMismatch).ToBeTrue(); + Expect(s.errors[0].event.GetSize()).ToEqual(32); + Expect(s.used).ToEqual(64); // Correcting the size frees the alloc normally. s.Remove((void*)0x1000, 64); s.CollectStats(); - AssertThat(LiveCount(s), Is().EqualTo(0)); - AssertThat(s.used, Is().EqualTo(0)); + Expect(LiveCount(s)).ToEqual(0); + Expect(s.used).ToEqual(0); }); - it("Free of unknown ptr records UnknownFree", [&]() + It("Free of unknown ptr records UnknownFree", []() { MemoryStats s; s.detectLeaks = false; s.Add((void*)0x1000, 64); s.Remove((void*)0xDEAD, 64); s.CollectStats(); - AssertThat(LiveCount(s), Is().EqualTo(1)); - AssertThat(s.errors.Size(), Is().EqualTo(1)); - AssertThat(s.errors[0].kind == MemoryStatsErrorType::UnknownFree, Is().True()); - AssertThat(s.errors[0].event.GetPtr(), Is().EqualTo((u8*)0xDEAD)); - AssertThat(s.used, Is().EqualTo(64)); + Expect(LiveCount(s)).ToEqual(1); + Expect(s.errors.Size()).ToEqual(1); + Expect(s.errors[0].kind == MemoryStatsErrorType::UnknownFree).ToBeTrue(); + Expect(s.errors[0].event.GetPtr()).ToEqual((u8*)0xDEAD); + Expect(s.used).ToEqual(64); }); - it("Ignores null ptr in Remove", [&]() + It("Ignores null ptr in Remove", []() { MemoryStats s; s.Remove(nullptr, 64); s.CollectStats(); - AssertThat(s.used, Is().EqualTo(0)); + Expect(s.used).ToEqual(0); }); - it("Ignores null ptr in Add", [&]() + It("Ignores null ptr in Add", []() { MemoryStats s; s.detectLeaks = false; @@ -305,25 +303,25 @@ go_bandit([]() s.CollectStats(); // Add has no null check (unlike Remove), so the event is // recorded and processed. Add's size is still tracked. - AssertThat(s.used, Is().EqualTo(64)); - AssertThat(LiveCount(s), Is().EqualTo(1)); + Expect(s.used).ToEqual(64); + Expect(LiveCount(s)).ToEqual(1); }); - it("Reset resets state", [&]() + It("Reset resets state", []() { MemoryStats s; s.Add((void*)0x1000, 64); s.Add((void*)0x2000, 32); s.CollectStats(); - AssertThat(s.used, Is().EqualTo(96)); + Expect(s.used).ToEqual(96); s.Reset(); - AssertThat(s.used, Is().EqualTo(0)); - AssertThat(s.totalAllocated, Is().EqualTo(0)); - AssertThat(LiveCount(s), Is().EqualTo(0)); + Expect(s.used).ToEqual(0); + Expect(s.totalAllocated).ToEqual(0); + Expect(LiveCount(s)).ToEqual(0); }); - it("CollectStats is additive", [&]() + It("CollectStats is additive", []() { MemoryStats s; s.detectLeaks = false; @@ -331,26 +329,26 @@ go_bandit([]() s.CollectStats(); s.Add((void*)0x2000, 32); s.CollectStats(); - AssertThat(s.used, Is().EqualTo(96)); - AssertThat(LiveCount(s), Is().EqualTo(2)); + Expect(s.used).ToEqual(96); + Expect(LiveCount(s)).ToEqual(2); }); - it("Re-collecting preserves state", [&]() + It("Re-collecting preserves state", []() { MemoryStats s; s.detectLeaks = false; s.Add((void*)0x1000, 64); s.CollectStats(); s.CollectStats(); - AssertThat(s.used, Is().EqualTo(64)); - AssertThat(LiveCount(s), Is().EqualTo(1)); + Expect(s.used).ToEqual(64); + Expect(LiveCount(s)).ToEqual(1); }); }); - describe("Multiple chunks", [&]() + Describe("Multiple chunks", []() { - it("Spans multiple chunks correctly", [&]() + It("Spans multiple chunks correctly", []() { MemoryStats s; s.detectLeaks = false; @@ -361,12 +359,12 @@ go_bandit([]() s.Add(&buf[i * 8], 8); } s.CollectStats(); - AssertThat(s.used, Is().EqualTo(N * 8)); - AssertThat(s.totalAllocated, Is().EqualTo(N * 8)); - AssertThat(LiveCount(s), Is().EqualTo(N)); + Expect(s.used).ToEqual(N * 8); + Expect(s.totalAllocated).ToEqual(N * 8); + Expect(LiveCount(s)).ToEqual(N); }); - it("Handles add/free across chunks", [&]() + It("Handles add/free across chunks", []() { MemoryStats s; s.detectLeaks = false; @@ -381,12 +379,12 @@ go_bandit([]() s.Remove(&buf[i * 8], 8); } s.CollectStats(); - AssertThat(s.used, Is().EqualTo((N / 2) * 8)); - AssertThat(s.totalAllocated, Is().EqualTo(N * 8)); - AssertThat(LiveCount(s), Is().EqualTo(N / 2)); + Expect(s.used).ToEqual((N / 2) * 8); + Expect(s.totalAllocated).ToEqual(N * 8); + Expect(LiveCount(s)).ToEqual(N / 2); }); - it("Frees chunks between CollectStats calls", [&]() + It("Frees chunks between CollectStats calls", []() { MemoryStats s; s.detectLeaks = false; @@ -397,21 +395,21 @@ go_bandit([]() s.Add(&buf[i * 8], 8); } s.CollectStats(); - AssertThat(LiveCount(s), Is().EqualTo(N)); + Expect(LiveCount(s)).ToEqual(N); for (sizet i = 0; i < N / 2; ++i) { s.Remove(&buf[i * 8], 8); } s.CollectStats(); - AssertThat(LiveCount(s), Is().EqualTo(N / 2)); - AssertThat(s.used, Is().EqualTo((N / 2) * 8)); + Expect(LiveCount(s)).ToEqual(N / 2); + Expect(s.used).ToEqual((N / 2) * 8); }); }); - describe("Multithreading", [&]() + Describe("Multithreading", []() { - it("One thread adds, another collects", [&]() + It("One thread adds, another collects", []() { MemoryStats s; const sizet N = 1000; @@ -445,14 +443,14 @@ go_bandit([]() producer.join(); consumer.join(); - AssertThat(LiveCount(s), Is().EqualTo(N)); - AssertThat(s.used, Is().EqualTo(N * 8)); + Expect(LiveCount(s)).ToEqual(N); + Expect(s.used).ToEqual(N * 8); // Suppress leak warnings at destruction (test buffers are stack). s.Reset(); }); - it("Many threads add, then collects", [&]() + It("Many threads add, then collects", []() { MemoryStats s; const sizet N_PER_THREAD = 1000; @@ -503,15 +501,15 @@ go_bandit([]() } consumer.join(); - AssertThat(LiveCount(s), Is().EqualTo(N)); - AssertThat(s.used, Is().EqualTo(N * 8)); - AssertThat(s.totalAllocated, Is().EqualTo(N * 8)); + Expect(LiveCount(s)).ToEqual(N); + Expect(s.used).ToEqual(N * 8); + Expect(s.totalAllocated).ToEqual(N * 8); // Suppress leak warnings at destruction (test buffers are stack). s.Reset(); }); - it("Many threads add and remove, then collects", [&]() + It("Many threads add and remove, then collects", []() { MemoryStats s; const sizet N_PER_THREAD = 1000; @@ -567,9 +565,9 @@ go_bandit([]() } consumer.join(); - AssertThat(LiveCount(s), Is().EqualTo(N / 2)); - AssertThat(s.used, Is().EqualTo((N / 2) * 8)); - AssertThat(s.totalAllocated, Is().EqualTo(N * 8)); + Expect(LiveCount(s)).ToEqual(N / 2); + Expect(s.used).ToEqual((N / 2) * 8); + Expect(s.totalAllocated).ToEqual(N * 8); // Suppress leak warnings at destruction (test buffers are stack). s.Reset(); @@ -577,9 +575,9 @@ go_bandit([]() }); - describe("Heavy stress", [&]() + Describe("Heavy stress", []() { - it("Many producers, many iterations, no crashes", [&]() + It("Many producers, many iterations, no crashes", []() { MemoryStats s; const sizet N_PER_THREAD = 2000; @@ -635,11 +633,11 @@ go_bandit([]() consumer.join(); // s.used reflects the net remaining live set. - AssertThat(s.used, Is().EqualTo(LiveCount(s) * 8)); + Expect(s.used).ToEqual(LiveCount(s) * 8); // Suppress leak warnings at destruction (test buffers are stack). s.Reset(); }); }); }); -}); \ No newline at end of file +} diff --git a/Tests/Memory/MonoLinearArena.spec.cpp b/Tests/Memory/MonoLinearArena.spec.cpp index bea7e19f..73c1d6bf 100644 --- a/Tests/Memory/MonoLinearArena.spec.cpp +++ b/Tests/Memory/MonoLinearArena.spec.cpp @@ -1,69 +1,67 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include -using namespace snowhouse; -using namespace bandit; using namespace p; -go_bandit([]() +void RegisterMemoryMonoLinearArenaTests() { - describe("Memory.MonoLinearArena", []() + Spec("Memory.MonoLinearArena", []() { - it("Reserves a block on construction", [&]() + It("Reserves a block on construction", []() { MonoLinearArena arena{1024}; - AssertThat(arena.GetAvailableMemory(), Is().EqualTo(1024)); + Expect(arena.GetAvailableMemory()).ToEqual(1024); arena.GetStats()->CollectStats(); - AssertThat(arena.GetStats()->used, Is().EqualTo(0)); + Expect(arena.GetStats()->used).ToEqual(0); }); - it("Can allocate outside the block", [&]() + It("Can allocate outside the block", []() { MonoLinearArena arena{256}; - AssertThat(arena.GetAvailableMemory(), Is().EqualTo(256)); + Expect(arena.GetAvailableMemory()).ToEqual(256); void* p = arena.Alloc(512); - AssertThat(arena.GetAvailableMemory(), Is().EqualTo(256)); + Expect(arena.GetAvailableMemory()).ToEqual(256); arena.Free(p, 512); }); - it("Can free from outside the block", [&]() + It("Can free from outside the block", []() { MonoLinearArena arena{256}; void* p = arena.Alloc(512); - AssertThat(arena.GetAvailableMemory(), Is().EqualTo(256)); + Expect(arena.GetAvailableMemory()).ToEqual(256); arena.Free(p, 512); - AssertThat(arena.GetAvailableMemory(), Is().EqualTo(256)); + Expect(arena.GetAvailableMemory()).ToEqual(256); }); - it("Can free active block", [&]() + It("Can free active block", []() { MonoLinearArena arena{1024}; arena.Release(); TArray blocks; arena.GetBlocks(blocks); - AssertThat(blocks.Size(), Equals(1)); + Expect(blocks.Size()).ToEqual(1); }); - it("Can allocate", [&]() + It("Can allocate", []() { MonoLinearArena arena{1024}; void* p = arena.Alloc(sizeof(float)); - AssertThat(p, Is().Not().Null()); + Expect(p).ToNotEqual(nullptr); arena.GetStats()->CollectStats(); - AssertThat(arena.GetStats()->used, Is().EqualTo(4)); - AssertThat(arena.GetAvailableMemory(), Is().EqualTo(1024)); + Expect(arena.GetStats()->used).ToEqual(4); + Expect(arena.GetAvailableMemory()).ToEqual(1024); arena.Free(p, sizeof(float)); }); - it("Can allocate with alignment", [&]() + It("Can allocate with alignment", []() { MonoLinearArena arena{1024}; @@ -71,43 +69,43 @@ go_bandit([]() // When padding is not 0 (last ptr is not aligned) void* p1 = arena.Alloc(sizeof(float), 8); - AssertThat(p::GetAlignmentPadding(p1, 8), Is().EqualTo(0)); + Expect(p::GetAlignmentPadding(p1, 8)).ToEqual(0); // When padding is 0 (last ptr is aligned) void* p2 = arena.Alloc(sizeof(float), 16); - AssertThat(p::GetAlignmentPadding(p2, 16), Is().EqualTo(0)); + Expect(p::GetAlignmentPadding(p2, 16)).ToEqual(0); arena.Free(p0, sizeof(bool)); arena.Free(p1, sizeof(float)); arena.Free(p2, sizeof(float)); }); - it("Can allocate after release", [&]() + It("Can allocate after release", []() { MonoLinearArena arena{1024}; arena.Release(); void* p = arena.Alloc(sizeof(float)); - AssertThat(p, Is().Not().Null()); + Expect(p).ToNotEqual(nullptr); arena.GetStats()->CollectStats(); - AssertThat(arena.GetStats()->used, Is().EqualTo(4)); + Expect(arena.GetStats()->used).ToEqual(4); // Buffer size will be as small as the type (4 bytes) - AssertThat(arena.GetAvailableMemory(), Is().EqualTo(1024)); + Expect(arena.GetAvailableMemory()).ToEqual(1024); arena.Free(p, sizeof(float)); }); - it("Can free block after Free", [&]() + It("Can free block after Free", []() { MonoLinearArena arena{1024}; void* p = arena.Alloc(256); arena.GetStats()->CollectStats(); - AssertThat(arena.GetStats()->used, Is().EqualTo(256)); + Expect(arena.GetStats()->used).ToEqual(256); arena.Free(p, 256); arena.GetStats()->CollectStats(); - AssertThat(arena.GetStats()->used, Is().EqualTo(0)); + Expect(arena.GetStats()->used).ToEqual(0); }); - it("Allocates at correct addresses", [&]() + It("Allocates at correct addresses", []() { MonoLinearArena arena{1024}; @@ -115,33 +113,33 @@ go_bandit([]() arena.GetBlocks(blocks); void* p1 = arena.Alloc(sizeof(float)); - AssertThat(p1, Is().EqualTo(blocks[0].data)); + Expect(p1).ToEqual(blocks[0].data); void* p2 = arena.Alloc(sizeof(float), alignof(float)); - AssertThat(p2, Is().EqualTo((u8*)blocks[0].data + 4)); + Expect(p2).ToEqual((u8*)blocks[0].data + 4); arena.Free(p1, sizeof(float)); arena.Free(p2, sizeof(float)); }); // Move test to Multi linear - /*it("Allocated new blocks when previous is filled", [&]() { + /*It("Allocated new blocks when previous is filled", []() { MonoLinearArena arena{16}; void* p = arena.Alloc(sizeof(float*)); // 8 bytes arena.Alloc(sizeof(float)); // 4 bytes - AssertThat(arena.GetStats()->used, Is().EqualTo(12)); - AssertThat(arena.GetAvailableMemory(), Is().EqualTo(16)); + Expect(arena.GetStats()->used).ToEqual(12); + Expect(arena.GetAvailableMemory()).ToEqual(16); void* p3 = arena.Alloc(sizeof(float*)); // 8 bytes TArray blocks; arena.GetBlocks(blocks); - AssertThat(blocks.Size(), Equals(2)); - AssertThat(blocks[0], Is().Not().EqualTo(blocks[1])); - AssertThat(p, Is().EqualTo(blocks[0].data)); - AssertThat(p3, Is().EqualTo(blocks[1].data)); + Expect(blocks.Size()).ToEqual(2); + Expect(blocks[0]).ToNotEqual(blocks[1]); + Expect(p).ToEqual(blocks[0].data); + Expect(p3).ToEqual(blocks[1].data); - AssertThat(arena.GetStats()->used, Is().EqualTo(8)); - AssertThat(arena.GetAvailableMemory(), Is().EqualTo(16)); + Expect(arena.GetStats()->used).ToEqual(8); + Expect(arena.GetAvailableMemory()).ToEqual(16); });*/ }); -}); +} diff --git a/Tests/PipeTests/CMakeLists.txt b/Tests/PipeTests/CMakeLists.txt index 85aa890f..951d76a9 100644 --- a/Tests/PipeTests/CMakeLists.txt +++ b/Tests/PipeTests/CMakeLists.txt @@ -4,5 +4,5 @@ pipe_target_define_platform(PipeTestsSelf) pipe_target_enable_CPP20(PipeTestsSelf) pipe_target_disable_rtti(PipeTestsSelf PRIVATE) pipe_target_shared_output_directory(PipeTestsSelf) -target_link_libraries(PipeTestsSelf PUBLIC PipeTestsLib Pipe) +target_link_libraries(PipeTestsSelf PUBLIC PipeTests Pipe) add_test(NAME PipeTestsSelf COMMAND $) diff --git a/Tests/PipeTests/main.cpp b/Tests/PipeTests/main.cpp index 3d7b7c9d..e6ca8cf8 100644 --- a/Tests/PipeTests/main.cpp +++ b/Tests/PipeTests/main.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -// NOTE: PipeNewDelete is deliberately not included here. PipeTestsLib provides the +// NOTE: PipeNewDelete is deliberately not included here. PipeTests provides the // replacement operator new/delete (P_OVERRIDE_NEWDELETE) in its own translation unit; // including it here too would cause duplicate-definition linker errors. diff --git a/Tests/Reflection/MacroReflection.spec.cpp b/Tests/Reflection/MacroReflection.spec.cpp index 8722146c..b6f8c583 100644 --- a/Tests/Reflection/MacroReflection.spec.cpp +++ b/Tests/Reflection/MacroReflection.spec.cpp @@ -1,12 +1,11 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include #include -using namespace snowhouse; -using namespace bandit; +using namespace p; struct TestStruct @@ -21,23 +20,23 @@ struct TestStruct }; -go_bandit([]() +void RegisterReflectionMacroReflectionTests() { - describe("Reflection.Macros", []() + Spec("Reflection.Macros", []() { - it("Can get property names", [&]() + It("Can get property names", []() { p::TypeId testStructType = p::RegisterTypeId(); - AssertThat(p::HasTypeFlags(testStructType, p::TF_Struct), Equals(true)); + Expect(p::HasTypeFlags(testStructType, p::TF_Struct)).ToEqual(true); auto properties = p::GetTypeProperties(testStructType); - AssertThat(properties.Size(), Equals(2)); + Expect(properties.Size()).ToEqual(2); - // AssertThat(properties[0].typeId, Equals(p::GetTypeId>())); - AssertThat(properties[0]->name.Data(), Equals("value0")); - // AssertThat(properties[1].typeId, Equals(p::GetTypeId())); - AssertThat(properties[1]->name.Data(), Equals("value1")); + // Expect(properties[0].typeId).ToEqual(p::GetTypeId>()); + Expect(properties[0]->name.Data()).ToEqual("value0"); + // Expect(properties[1].typeId).ToEqual(p::GetTypeId()); + Expect(properties[1]->name.Data()).ToEqual("value1"); }); }); -}); +} diff --git a/Tests/Reflection/Object.spec.cpp b/Tests/Reflection/Object.spec.cpp index a51e1376..380a382a 100644 --- a/Tests/Reflection/Object.spec.cpp +++ b/Tests/Reflection/Object.spec.cpp @@ -1,11 +1,10 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include -using namespace snowhouse; -using namespace bandit; +using namespace p; class TestObject : public p::Object @@ -23,28 +22,28 @@ class TestObject : public p::Object }; -go_bandit([]() +void RegisterReflectionObjectTests() { - describe("Reflection.Object", []() + Spec("Reflection.Object", []() { - describe("Pointers", []() + Describe("Pointers", []() { - it("Can create object", [&]() + It("Can create object", []() { auto owner = p::MakeOwned(); - AssertThat(owner.Get(), Is().Not().EqualTo(nullptr)); - AssertThat(owner->bConstructed, Equals(true)); + Expect(owner.Get()).ToNotEqual(nullptr); + Expect(owner->bConstructed).ToEqual(true); }); - it("Can create object with owner", [&]() + It("Can create object with owner", []() { auto owner = p::MakeOwned(); auto owner2 = p::MakeOwned(owner); - AssertThat(owner2->bConstructed, Equals(true)); - AssertThat(owner2->GetOwner().Get(), Equals(owner.Get())); + Expect(owner2->bConstructed).ToEqual(true); + Expect(owner2->GetOwner().Get()).ToEqual(owner.Get()); }); }); }); -}); +} diff --git a/Tests/Reflection/Traits.spec.cpp b/Tests/Reflection/Traits.spec.cpp index 8e1b2eeb..33878915 100644 --- a/Tests/Reflection/Traits.spec.cpp +++ b/Tests/Reflection/Traits.spec.cpp @@ -1,13 +1,12 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include #include #include -using namespace snowhouse; -using namespace bandit; +using namespace p; struct TestNotSerializable @@ -40,67 +39,67 @@ namespace p } // namespace p -go_bandit([]() +void RegisterReflectionTraitsTests() { - describe("Reflection.Traits", []() + Spec("Reflection.Traits", []() { - describe("Read/Write properties", []() + Describe("Read/Write properties", []() { - it("Can check for read properties", [&]() + It("Can check for read properties", []() { - AssertThat(p::HasReadProperties(), Is().False()); - AssertThat(p::HasReadProperties(), Is().True()); - AssertThat(p::Readable, Is().False()); - AssertThat(p::Readable, Is().True()); + Expect(p::HasReadProperties()).ToBeFalse(); + Expect(p::HasReadProperties()).ToBeTrue(); + Expect(p::Readable).ToBeFalse(); + Expect(p::Readable).ToBeTrue(); }); - it("Can check for write properties", [&]() + It("Can check for write properties", []() { - AssertThat(p::HasWriteProperties(), Is().False()); - AssertThat(p::HasWriteProperties(), Is().True()); - AssertThat(p::Writable, Is().False()); - AssertThat(p::Writable, Is().True()); + Expect(p::HasWriteProperties()).ToBeFalse(); + Expect(p::HasWriteProperties()).ToBeTrue(); + Expect(p::Writable).ToBeFalse(); + Expect(p::Writable).ToBeTrue(); }); }); - describe("Read/Write external", []() + Describe("Read/Write external", []() { - it("Can check for read properties", [&]() + It("Can check for read properties", []() { - AssertThat(p::Readable, Is().False()); - AssertThat(p::Readable, Is().True()); + Expect(p::Readable).ToBeFalse(); + Expect(p::Readable).ToBeTrue(); }); - it("Can check for write properties", [&]() + It("Can check for write properties", []() { - AssertThat(p::Writable, Is().False()); - AssertThat(p::Writable, Is().True()); + Expect(p::Writable).ToBeFalse(); + Expect(p::Writable).ToBeTrue(); }); }); - describe("Read/Write external in namespace", []() + Describe("Read/Write external in namespace", []() { - it("Can check for read properties", [&]() + It("Can check for read properties", []() { - AssertThat(p::Readable, Is().True()); + Expect(p::Readable).ToBeTrue(); }); - it("Can check for write properties", [&]() + It("Can check for write properties", []() { - AssertThat(p::Writable, Is().True()); + Expect(p::Writable).ToBeTrue(); }); }); - it("Can check super", []() + It("Can check super", []() { - AssertThat(p::HasSuper(), Is().False()); - AssertThat(p::HasSuper(), Is().True()); + Expect(p::HasSuper()).ToBeFalse(); + Expect(p::HasSuper()).ToBeTrue(); }); - it("Can build type on Arrays", []() + It("Can build type on Arrays", []() { - AssertThat(p::CanBuildType>(), Is().True()); - AssertThat(p::HasExternalBuildType>(), Is().True()); + Expect(p::CanBuildType>()).ToBeTrue(); + Expect(p::HasExternalBuildType>()).ToBeTrue(); }); }); -}); +} diff --git a/Tests/Reflection/TypeId.spec.cpp b/Tests/Reflection/TypeId.spec.cpp index 1a71f8c2..aa86f217 100644 --- a/Tests/Reflection/TypeId.spec.cpp +++ b/Tests/Reflection/TypeId.spec.cpp @@ -1,31 +1,29 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include -using namespace snowhouse; -using namespace bandit; using namespace p; struct One {}; -go_bandit([]() +void RegisterReflectionTypeIdTests() { - describe("Reflection.TypeId", []() + Spec("Reflection.TypeId", []() { - it("Ids can be valid and invalid", [&]() + It("Ids can be valid and invalid", []() { static constexpr TypeId id = GetTypeId(); - AssertThat(id.IsValid(), Equals(true)); + Expect(id.IsValid()).ToEqual(true); static constexpr TypeId noId{}; - AssertThat(noId.IsValid(), Equals(false)); + Expect(noId.IsValid()).ToEqual(false); }); - it("Different types don't share an id", [&]() + It("Different types don't share an id", []() { static constexpr TypeId ids[]{ GetTypeId(), GetTypeId(), GetTypeId(), GetTypeId()}; @@ -36,9 +34,9 @@ go_bandit([]() { for (u32 e = i + 1; e < numIds; ++e) { - AssertThat(ids[i], !Equals(ids[e])); + Expect(ids[i]).ToNotEqual(ids[e]); } } }); }); -}); +} diff --git a/Tests/Reflection/TypeName.spec.cpp b/Tests/Reflection/TypeName.spec.cpp index 6b947ffb..93081f25 100644 --- a/Tests/Reflection/TypeName.spec.cpp +++ b/Tests/Reflection/TypeName.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include #include #include @@ -9,8 +9,6 @@ #include -using namespace snowhouse; -using namespace bandit; using namespace p; @@ -27,70 +25,70 @@ namespace Space } // namespace Space -go_bandit([]() +void RegisterReflectionTypeNameTests() { - describe("Reflection.TypeName", []() + Spec("Reflection.TypeName", []() { - it("Can get Platform type names", [&]() + It("Can get Platform type names", []() { - AssertThat(GetTypeName(), Equals("u8")); - AssertThat(GetTypeName(), Equals("u16")); - AssertThat(GetTypeName(), Equals("u32")); - AssertThat(GetTypeName(), Equals("u64")); - AssertThat(GetTypeName(), Equals("i8")); - AssertThat(GetTypeName(), Equals("i16")); - AssertThat(GetTypeName(), Equals("i32")); - AssertThat(GetTypeName(), Equals("i64")); - AssertThat(GetTypeName(), Equals("char")); - AssertThat(GetTypeName(), Equals("StringView")); - AssertThat(GetTypeName(), Equals("String")); + Expect(GetTypeName()).ToEqual("u8"); + Expect(GetTypeName()).ToEqual("u16"); + Expect(GetTypeName()).ToEqual("u32"); + Expect(GetTypeName()).ToEqual("u64"); + Expect(GetTypeName()).ToEqual("i8"); + Expect(GetTypeName()).ToEqual("i16"); + Expect(GetTypeName()).ToEqual("i32"); + Expect(GetTypeName()).ToEqual("i64"); + Expect(GetTypeName()).ToEqual("char"); + Expect(GetTypeName()).ToEqual("StringView"); + Expect(GetTypeName()).ToEqual("String"); }); - it("Can get Native type names", [&]() + It("Can get Native type names", []() { - AssertThat(GetTypeName(), Equals("bool")); - AssertThat(GetTypeName(), Equals("float")); - AssertThat(GetTypeName(), Equals("double")); + Expect(GetTypeName()).ToEqual("bool"); + Expect(GetTypeName()).ToEqual("float"); + Expect(GetTypeName()).ToEqual("double"); }); - it("Can get Class names", [&]() + It("Can get Class names", []() { - AssertThat(GetTypeName(), Equals("AClass")); + Expect(GetTypeName()).ToEqual("AClass"); }); - it("Can get Struct names", [&]() + It("Can get Struct names", []() { - AssertThat(GetTypeName(), Equals("AnStruct")); + Expect(GetTypeName()).ToEqual("AnStruct"); }); - it("Can get names with namespaces", [&]() + It("Can get names with namespaces", []() { - AssertThat(GetTypeName(), Equals("Space::Other")); + Expect(GetTypeName()).ToEqual("Space::Other"); }); - describe("Containers", []() + Describe("Containers", []() { - it("Can get TArray names", [&]() + It("Can get TArray names", []() { - AssertThat(GetTypeName>(), Equals("TArray")); - AssertThat(GetFullTypeName>(), Equals("TArray")); - AssertThat(GetFullTypeName>(false), Equals("TArray")); + Expect(GetTypeName>()).ToEqual("TArray"); + Expect(GetFullTypeName>()).ToEqual("TArray"); + Expect(GetFullTypeName>(false)).ToEqual("TArray"); }); - it("Can get TMap names", [&]() + It("Can get TMap names", []() { auto name = GetTypeName>(); - AssertThat(name, Equals("TMap")); + Expect(name).ToEqual("TMap"); auto fullName = GetFullTypeName>(); - AssertThat(fullName, Equals("TMap")); + Expect(fullName).ToEqual("TMap"); auto namespaceName = GetFullTypeName>(); - AssertThat(namespaceName, Equals("TMap")); + Expect(namespaceName).ToEqual("TMap"); auto noNamespaceName = GetFullTypeName>(false); - AssertThat(noNamespaceName, Equals("TMap")); + Expect(noNamespaceName).ToEqual("TMap"); }); }); }); -}); +} diff --git a/Tests/Serialization/Binary.spec.cpp b/Tests/Serialization/Binary.spec.cpp index 0ab52a0d..826546ef 100644 --- a/Tests/Serialization/Binary.spec.cpp +++ b/Tests/Serialization/Binary.spec.cpp @@ -1,30 +1,28 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include -using namespace snowhouse; -using namespace bandit; using namespace p; -go_bandit([]() +void RegisterSerializationBinaryTests() { - describe("Serialization.Binary", []() + Spec("Serialization.Binary", []() { - describe("Reader", [&]() + Describe("Reader", []() { - it("Can create a reader", [&]() + It("Can create a reader", []() { BinaryFormatReader reader{TArray{}}; - AssertThat(reader.IsValid(), Equals(false)); + Expect(reader.IsValid()).ToEqual(false); BinaryFormatReader reader2{TArray{255}}; - AssertThat(reader2.IsValid(), Equals(true)); + Expect(reader2.IsValid()).ToEqual(true); }); - it("Can read from object value", [&]() + It("Can read from object value", []() { TArray data{255}; BinaryFormatReader reader{data}; @@ -32,23 +30,23 @@ go_bandit([]() ct.BeginObject(); u8 value = 0; ct.Next(value); - AssertThat(value, Equals(255)); + Expect(value).ToEqual(255); }); - it("Can read from array values", [&]() + It("Can read from array values", []() { TArray data{1, 0, 0, 0, 255}; BinaryFormatReader reader{data}; Reader ct = reader; u32 size = 0; ct.BeginArray(size); - AssertThat(size, Equals(1)); + Expect(size).ToEqual(1); u8 value = 0; ct.Next(value); - AssertThat(value, Equals(255)); + Expect(value).ToEqual(255); }); - it("Can iterate arrays", [&]() + It("Can iterate arrays", []() { TArray data{2, 0, 0, 0, // Array size of 2 6, 0, 0, 0, // size 6 @@ -68,15 +66,15 @@ go_bandit([]() { StringView name; ct.Next(name); - AssertThat(name, Equals(expected[i])); + Expect(name).ToEqual(expected[i]); } ct.Leave(); } }); - describe("Types", []() + Describe("Types", []() { - it("Can read bool values", [&]() + It("Can read bool values", []() { TArray data{1, 0}; BinaryFormatReader reader{data}; @@ -84,12 +82,12 @@ go_bandit([]() ct.BeginObject(); bool value = false; ct.Next("a", value); - AssertThat(value, Equals(true)); + Expect(value).ToEqual(true); ct.Next("b", value); - AssertThat(value, Equals(false)); + Expect(value).ToEqual(false); }); - it("Can read i8 values", [&]() + It("Can read i8 values", []() { TArray data{0, 127, 128}; BinaryFormatReader reader{data}; @@ -97,14 +95,14 @@ go_bandit([]() ct.BeginObject(); i8 value = 0; ct.Next("a", value); - AssertThat(value, Equals(0)); + Expect(value).ToEqual(0); ct.Next("b", value); - AssertThat(value, Equals(127)); + Expect(value).ToEqual(127); ct.Next("b", value); - AssertThat(value, Equals(-128)); + Expect(value).ToEqual(-128); }); - it("Can read u8 values", [&]() + It("Can read u8 values", []() { TArray data{0, 255}; BinaryFormatReader reader{data}; @@ -112,12 +110,12 @@ go_bandit([]() ct.BeginObject(); u8 value = 0; ct.Next("a", value); - AssertThat(value, Equals(0)); + Expect(value).ToEqual(0); ct.Next("b", value); - AssertThat(value, Equals(255)); + Expect(value).ToEqual(255); }); - it("Can read i16 values", [&]() + It("Can read i16 values", []() { // Test inbounds and out of bounds values TArray data{0, 0, 0, 128, 255, 127}; @@ -126,14 +124,14 @@ go_bandit([]() ct.BeginObject(); i16 value = 0; ct.Next("a", value); - AssertThat(value, Equals(0)); + Expect(value).ToEqual(0); ct.Next("b", value); - AssertThat(value, Equals(Limits::Lowest())); + Expect(value).ToEqual(Limits::Lowest()); ct.Next("c", value); - AssertThat(value, Equals(Limits::Max())); + Expect(value).ToEqual(Limits::Max()); }); - it("Can read u16 values", [&]() + It("Can read u16 values", []() { // Test inbounds and out of bounds values TArray data{0, 0, 255, 255}; @@ -142,12 +140,12 @@ go_bandit([]() ct.BeginObject(); u16 value = 0; ct.Next("a", value); - AssertThat(value, Equals(0)); + Expect(value).ToEqual(0); ct.Next("b", value); - AssertThat(value, Equals(Limits::Max())); + Expect(value).ToEqual(Limits::Max()); }); - it("Can read i32 values", [&]() + It("Can read i32 values", []() { // Test inbounds and out of bounds values TArray data{0, 0, 0, 0, 0, 0, 0, 128, 255, 255, 255, 127}; @@ -156,14 +154,14 @@ go_bandit([]() ct.BeginObject(); i32 value = 0; ct.Next("a", value); - AssertThat(value, Equals(0)); + Expect(value).ToEqual(0); ct.Next("b", value); - AssertThat(value, Equals(Limits::Lowest())); + Expect(value).ToEqual(Limits::Lowest()); ct.Next("c", value); - AssertThat(value, Equals(Limits::Max())); + Expect(value).ToEqual(Limits::Max()); }); - it("Can read u32 values", [&]() + It("Can read u32 values", []() { // Test inbounds and out of bounds values TArray data{0, 0, 0, 0, 255, 255, 255, 255}; @@ -172,12 +170,12 @@ go_bandit([]() ct.BeginObject(); u32 value = 0; ct.Next("a", value); - AssertThat(value, Equals(0)); + Expect(value).ToEqual(0); ct.Next("b", value); - AssertThat(value, Equals(Limits::Max())); + Expect(value).ToEqual(Limits::Max()); }); - it("Can read i64 values", [&]() + It("Can read i64 values", []() { // Test inbounds and out of bounds values TArray data{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 255, 255, 255, @@ -187,14 +185,14 @@ go_bandit([]() ct.BeginObject(); i64 value = 0; ct.Next("a", value); - AssertThat(value, Equals(0)); + Expect(value).ToEqual(0); ct.Next("b", value); - AssertThat(value, Equals(Limits::Lowest())); + Expect(value).ToEqual(Limits::Lowest()); ct.Next("c", value); - AssertThat(value, Equals(Limits::Max())); + Expect(value).ToEqual(Limits::Max()); }); - it("Can read u64 values", [&]() + It("Can read u64 values", []() { // Test inbounds and out of bounds values TArray data{0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255}; @@ -203,12 +201,12 @@ go_bandit([]() ct.BeginObject(); u64 value = 0; ct.Next("a", value); - AssertThat(value, Equals(0)); + Expect(value).ToEqual(0); ct.Next("b", value); - AssertThat(value, Equals(Limits::Max())); + Expect(value).ToEqual(Limits::Max()); }); - it("Can read float values", [&]() + It("Can read float values", []() { TArray data{51, 51, 179, 191, 0, 0, 96, 64}; BinaryFormatReader reader{data}; @@ -216,12 +214,12 @@ go_bandit([]() ct.BeginObject(); float value = 0.f; ct.Next("a", value); - AssertThat(value, Equals(-1.4f)); + Expect(value).ToEqual(-1.4f); ct.Next("b", value); - AssertThat(value, Equals(3.5f)); + Expect(value).ToEqual(3.5f); }); - it("Can read double values", [&]() + It("Can read double values", []() { TArray data{ 102, 102, 102, 102, 102, 102, 246, 191, 0, 0, 0, 0, 0, 0, 12, 64}; @@ -230,12 +228,12 @@ go_bandit([]() ct.BeginObject(); double value = 0; ct.Next("a", value); - AssertThat(value, Equals(-1.4)); + Expect(value).ToEqual(-1.4); ct.Next("b", value); - AssertThat(value, Equals(3.5)); + Expect(value).ToEqual(3.5); }); - it("Can read StringView values", [&]() + It("Can read StringView values", []() { TArray data{3, 0, 0, 0, 'y', 'e', 's'}; BinaryFormatReader reader{data}; @@ -243,20 +241,20 @@ go_bandit([]() ct.BeginObject(); StringView string; ct.Next("a", string); - AssertThat(string, Equals("yes")); + Expect(string).ToEqual("yes"); }); }); }); - describe("Writer", [&]() + Describe("Writer", []() { - it("Can create a writer", [&]() + It("Can create a writer", []() { BinaryFormatWriter writer{}; - AssertThat(writer.IsValid(), Equals(true)); + Expect(writer.IsValid()).ToEqual(true); }); - it("Can write to object key", [&]() + It("Can write to object key", []() { BinaryFormatWriter writer{}; Writer& ct = writer; @@ -264,10 +262,10 @@ go_bandit([]() ct.Next("name", StringView{"Miguel"}); TArray expected{6, 0, 0, 0, 'M', 'i', 'g', 'u', 'e', 'l'}; - AssertThat(writer.GetData(), Equals(TView{expected})); + Expect(writer.GetData()).ToEqual(TView{expected}); }); - it("Can write arrays", [&]() + It("Can write arrays", []() { BinaryFormatWriter writer{}; Writer& ct = writer; @@ -276,12 +274,12 @@ go_bandit([]() ct.Next(u8(255)); TArray expected{2, 0, 0, 0, 255, 255}; - AssertThat(writer.GetData(), Equals(TView{expected})); + Expect(writer.GetData()).ToEqual(TView{expected}); }); - describe("Types", []() + Describe("Types", []() { - it("Can write bool values", [&]() + It("Can write bool values", []() { BinaryFormatWriter writer{}; Writer ct = writer; @@ -289,10 +287,10 @@ go_bandit([]() ct.Next("a", true); ct.Next("b", false); TArray expected{1, 0}; - AssertThat(writer.GetData(), Equals(TView(expected))); + Expect(writer.GetData()).ToEqual(TView(expected)); }); - it("Can write i8 values", [&]() + It("Can write i8 values", []() { BinaryFormatWriter writer{}; Writer ct = writer; @@ -300,10 +298,10 @@ go_bandit([]() ct.Next("a", i8(127)); ct.Next("b", i8(-128)); TArray expected{127, 128}; - AssertThat(writer.GetData(), Equals(TView(expected))); + Expect(writer.GetData()).ToEqual(TView(expected)); }); - it("Can write u8 values", [&]() + It("Can write u8 values", []() { BinaryFormatWriter writer{}; Writer ct = writer; @@ -311,10 +309,10 @@ go_bandit([]() ct.Next("a", u8(0)); ct.Next("b", u8(255)); TArray expected{0, 255}; - AssertThat(writer.GetData(), Equals(TView(expected))); + Expect(writer.GetData()).ToEqual(TView(expected)); }); - it("Can write i16 values", [&]() + It("Can write i16 values", []() { BinaryFormatWriter writer{}; Writer ct = writer; @@ -322,10 +320,10 @@ go_bandit([]() ct.Next("a", Limits::Max()); ct.Next("b", Limits::Lowest()); TArray expected{255, 127, 0, 128}; - AssertThat(writer.GetData(), Equals(TView(expected))); + Expect(writer.GetData()).ToEqual(TView(expected)); }); - it("Can write u16 values", [&]() + It("Can write u16 values", []() { BinaryFormatWriter writer{}; Writer ct = writer; @@ -333,10 +331,10 @@ go_bandit([]() ct.Next("a", Limits::Max()); ct.Next("b", Limits::Lowest()); TArray expected{255, 255, 0, 0}; - AssertThat(writer.GetData(), Equals(TView(expected))); + Expect(writer.GetData()).ToEqual(TView(expected)); }); - it("Can write i32 values", [&]() + It("Can write i32 values", []() { BinaryFormatWriter writer{}; Writer ct = writer; @@ -344,10 +342,10 @@ go_bandit([]() ct.Next("a", Limits::Max()); ct.Next("b", Limits::Lowest()); TArray expected{255, 255, 255, 127, 0, 0, 0, 128}; - AssertThat(writer.GetData(), Equals(TView(expected))); + Expect(writer.GetData()).ToEqual(TView(expected)); }); - it("Can write u32 values", [&]() + It("Can write u32 values", []() { BinaryFormatWriter writer{}; Writer ct = writer; @@ -355,10 +353,10 @@ go_bandit([]() ct.Next("a", Limits::Max()); ct.Next("b", Limits::Lowest()); TArray expected{255, 255, 255, 255, 0, 0, 0, 0}; - AssertThat(writer.GetData(), Equals(TView(expected))); + Expect(writer.GetData()).ToEqual(TView(expected)); }); - it("Can write i64 values", [&]() + It("Can write i64 values", []() { BinaryFormatWriter writer{}; Writer ct = writer; @@ -367,10 +365,10 @@ go_bandit([]() ct.Next("b", Limits::Lowest()); TArray expected{ 255, 255, 255, 255, 255, 255, 255, 127, 0, 0, 0, 0, 0, 0, 0, 128}; - AssertThat(writer.GetData(), Equals(TView(expected))); + Expect(writer.GetData()).ToEqual(TView(expected)); }); - it("Can write u64 values", [&]() + It("Can write u64 values", []() { BinaryFormatWriter writer{}; Writer ct = writer; @@ -379,10 +377,10 @@ go_bandit([]() ct.Next("b", Limits::Lowest()); TArray expected{ 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}; - AssertThat(writer.GetData(), Equals(TView(expected))); + Expect(writer.GetData()).ToEqual(TView(expected)); }); - it("Can write float values", [&]() + It("Can write float values", []() { BinaryFormatWriter writer{}; Writer ct = writer; @@ -390,10 +388,10 @@ go_bandit([]() ct.Next("a", -1.4f); ct.Next("b", 3.5f); TArray expected{51, 51, 179, 191, 0, 0, 96, 64}; - AssertThat(writer.GetData(), Equals(TView(expected))); + Expect(writer.GetData()).ToEqual(TView(expected)); }); - it("Can write double values", [&]() + It("Can write double values", []() { BinaryFormatWriter writer{}; Writer ct = writer; @@ -402,19 +400,19 @@ go_bandit([]() ct.Next("b", 3.5); TArray expected{ 102, 102, 102, 102, 102, 102, 246, 191, 0, 0, 0, 0, 0, 0, 12, 64}; - AssertThat(writer.GetData(), Equals(TView(expected))); + Expect(writer.GetData()).ToEqual(TView(expected)); }); - it("Can write StringView values", [&]() + It("Can write StringView values", []() { BinaryFormatWriter writer{}; Writer ct = writer; ct.BeginObject(); ct.Next("a", StringView{"yes"}); TArray expected{3, 0, 0, 0, 'y', 'e', 's'}; - AssertThat(writer.GetData(), Equals(TView(expected))); + Expect(writer.GetData()).ToEqual(TView(expected)); }); }); }); }); -}); +} diff --git a/Tests/Serialization/Json.spec.cpp b/Tests/Serialization/Json.spec.cpp index 372fedaf..72a380b6 100644 --- a/Tests/Serialization/Json.spec.cpp +++ b/Tests/Serialization/Json.spec.cpp @@ -1,27 +1,25 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include -using namespace snowhouse; -using namespace bandit; using namespace p; -go_bandit([]() +void RegisterSerializationJsonTests() { - describe("Serialization.Json", []() + Spec("Serialization.Json", []() { - describe("Reader", [&]() + Describe("Reader", []() { - it("Can create a reader", [&]() + It("Can create a reader", []() { JsonFormatReader reader{"{}"}; - AssertThat(reader.IsValid(), Is().True()); + Expect(reader.IsValid()).ToBeTrue(); }); - it("Can read from object value", [&]() + It("Can read from object value", []() { String data{"{\"name\": \"Miguel\"}"}; JsonFormatReader reader{data}; @@ -31,10 +29,10 @@ go_bandit([]() String name; ct.Next("name", name); - AssertThat(name.data(), Equals("Miguel")); + Expect(name.data()).ToEqual("Miguel"); }); - it("Can read from array values", [&]() + It("Can read from array values", []() { String data{"{\"players\": [\"Miguel\", \"Juan\"]}"}; JsonFormatReader reader{data}; @@ -47,16 +45,16 @@ go_bandit([]() ct.BeginArray(size); String name; ct.Next(name); - AssertThat(name.data(), Equals("Miguel")); + Expect(name.data()).ToEqual("Miguel"); ct.Next(name); - AssertThat(name.data(), Equals("Juan")); + Expect(name.data()).ToEqual("Juan"); ct.Leave(); } }); - it("Can iterate arrays", [&]() + It("Can iterate arrays", []() { String data{"{\"players\": [\"Miguel\", \"Juan\"]}"}; JsonFormatReader reader{data}; @@ -72,113 +70,113 @@ go_bandit([]() { StringView name; ct.Next(name); - AssertThat(name, Equals(expected[i])); + Expect(name).ToEqual(expected[i]); } ct.Leave(); } }); - it("Can check types", [&]() + It("Can check types", []() { String data{"{\"players\": [\"Miguel\", \"Juan\"]}"}; JsonFormatReader reader{data}; Reader& ct = reader; - AssertThat(reader.IsObject(), Equals(true)); + Expect(reader.IsObject()).ToEqual(true); ct.BeginObject(); if (ct.EnterNext("players")) { - AssertThat(reader.IsArray(), Equals(true)); + Expect(reader.IsArray()).ToEqual(true); ct.Leave(); } }); - it("Can find multiple keys", [&]() + It("Can find multiple keys", []() { String data{"{\"one\": \"Miguel\", \"other\": \"Juan\"}"}; JsonFormatReader reader{data}; Reader& ct = reader; - AssertThat(reader.IsObject(), Equals(true)); + Expect(reader.IsObject()).ToEqual(true); ct.BeginObject(); StringView name; ct.Next("one", name); - AssertThat(name, Equals("Miguel")); + Expect(name).ToEqual("Miguel"); ct.Next("other", name); - AssertThat(name, Equals("Juan")); + Expect(name).ToEqual("Juan"); }); - it("Can find multiple unordered keys", [&]() + It("Can find multiple unordered keys", []() { String data{"{\"one\": \"Miguel\", \"other\": \"Juan\"}"}; JsonFormatReader reader{data}; Reader& ct = reader; - AssertThat(reader.IsObject(), Equals(true)); + Expect(reader.IsObject()).ToEqual(true); ct.BeginObject(); StringView name; ct.Next("other", name); - AssertThat(name, Equals("Juan")); + Expect(name).ToEqual("Juan"); ct.Next("one", name); - AssertThat(name, Equals("Miguel")); + Expect(name).ToEqual("Miguel"); }); - describe("Types", []() + Describe("Types", []() { - it("Can read bool values", [&]() + It("Can read bool values", []() { JsonFormatReader reader{"{\"alive\": true}"}; Reader& ct = reader; ct.BeginObject(); bool value = false; ct.Next("alive", value); - AssertThat(value, Equals(true)); + Expect(value).ToEqual(true); JsonFormatReader reader2{"{\"alive\": false}"}; ct = reader2; ct.BeginObject(); bool value2 = true; ct.Next("alive", value2); - AssertThat(value2, Equals(false)); + Expect(value2).ToEqual(false); }); - it("Can read i8 values", [&]() + It("Can read i8 values", []() { JsonFormatReader reader{"{\"alive\": -3}"}; Reader& ct = reader; ct.BeginObject(); i8 value = 0; ct.Next("alive", value); - AssertThat(value, Equals(-3)); + Expect(value).ToEqual(-3); JsonFormatReader reader2{"{\"alive\": -1.344}"}; ct = reader2; ct.BeginObject(); i8 value2 = 0; ct.Next("alive", value2); - AssertThat(value2, Equals(-1)); + Expect(value2).ToEqual(-1); }); - it("Can read u8 values", [&]() + It("Can read u8 values", []() { JsonFormatReader reader{"{\"alive\": 3}"}; Reader& ct = reader; ct.BeginObject(); u8 value = 0; ct.Next("alive", value); - AssertThat(value, Equals(3)); + Expect(value).ToEqual(3); JsonFormatReader reader2{"{\"alive\": 1.344}"}; ct = reader2; ct.BeginObject(); u8 value2 = 0; ct.Next("alive", value2); - AssertThat(value2, Equals(1)); + Expect(value2).ToEqual(1); }); - it("Can read i16 values", [&]() + It("Can read i16 values", []() { // Test inbounds and out of bounds values JsonFormatReader reader{ @@ -188,16 +186,16 @@ go_bandit([]() ct.BeginObject(); i16 value = 0; ct.Next("a", value); - AssertThat(value, Equals(Limits::Max())); + Expect(value).ToEqual(Limits::Max()); ct.Next("b", value); - AssertThat(value, Equals(Limits::Lowest())); + Expect(value).ToEqual(Limits::Lowest()); ct.Next("c", value); - AssertThat(value, Equals(Limits::Max())); + Expect(value).ToEqual(Limits::Max()); ct.Next("d", value); - AssertThat(value, Equals(Limits::Lowest())); + Expect(value).ToEqual(Limits::Lowest()); }); - it("Can read u16 values", [&]() + It("Can read u16 values", []() { JsonFormatReader reader{Format("{{\"a\":{},\"b\":{},\"c\":{}}}", Limits::Max(), Limits::Lowest(), -32)}; @@ -205,14 +203,14 @@ go_bandit([]() ct.BeginObject(); u16 value = 0; ct.Next("a", value); - AssertThat(value, Equals(Limits::Max())); + Expect(value).ToEqual(Limits::Max()); ct.Next("b", value); - AssertThat(value, Equals(Limits::Lowest())); + Expect(value).ToEqual(Limits::Lowest()); ct.Next("c", value); - AssertThat(value, Equals(0)); + Expect(value).ToEqual(0); }); - it("Can read i32 values", [&]() + It("Can read i32 values", []() { // Test inbounds and out of bounds values JsonFormatReader reader{ @@ -222,16 +220,16 @@ go_bandit([]() ct.BeginObject(); i32 value = 0; ct.Next("a", value); - AssertThat(value, Equals(Limits::Max())); + Expect(value).ToEqual(Limits::Max()); ct.Next("b", value); - AssertThat(value, Equals(Limits::Lowest())); + Expect(value).ToEqual(Limits::Lowest()); ct.Next("c", value); - AssertThat(value, Equals(Limits::Max())); + Expect(value).ToEqual(Limits::Max()); ct.Next("d", value); - AssertThat(value, Equals(Limits::Lowest())); + Expect(value).ToEqual(Limits::Lowest()); }); - it("Can read u32 values", [&]() + It("Can read u32 values", []() { JsonFormatReader reader{Format("{{\"a\":{},\"b\":{},\"c\":{}}}", Limits::Max(), Limits::Lowest(), -32)}; @@ -239,60 +237,60 @@ go_bandit([]() ct.BeginObject(); u32 value = 0; ct.Next("a", value); - AssertThat(value, Equals(Limits::Max())); + Expect(value).ToEqual(Limits::Max()); ct.Next("b", value); - AssertThat(value, Equals(Limits::Lowest())); + Expect(value).ToEqual(Limits::Lowest()); ct.Next("c", value); - AssertThat(value, Equals(0)); + Expect(value).ToEqual(0); }); - it("Can read float values", [&]() + It("Can read float values", []() { JsonFormatReader reader{"{\"alive\": 0.344}"}; Reader& ct = reader; ct.BeginObject(); float value = 0.f; ct.Next("alive", value); - AssertThat(value, Equals(0.344f)); + Expect(value).ToEqual(0.344f); JsonFormatReader reader2{"{\"alive\": 4}"}; ct = reader2; ct.BeginObject(); float value2 = 0.f; ct.Next("alive", value2); - AssertThat(value2, Equals(4.f)); + Expect(value2).ToEqual(4.f); }); - it("Can read StringView values", [&]() + It("Can read StringView values", []() { JsonFormatReader reader{"{\"alive\": \"yes\"}"}; Reader& ct = reader; ct.BeginObject(); StringView value; ct.Next("alive", value); - AssertThat(value, Equals("yes")); + Expect(value).ToEqual("yes"); }); }); }); - describe("Writer", [&]() + Describe("Writer", []() { - it("Can create a writer", [&]() + It("Can create a writer", []() { JsonFormatWriter writer{}; - AssertThat(writer.IsValid(), Equals(true)); + Expect(writer.IsValid()).ToEqual(true); }); - it("Can write to object key", [&]() + It("Can write to object key", []() { JsonFormatWriter writer{}; Writer& ct = writer; ct.BeginObject(); ct.Next("name", StringView{"Miguel"}); - AssertThat(writer.ToString(false), Equals("{\"name\":\"Miguel\"}")); + Expect(writer.ToString(false)).ToEqual("{\"name\":\"Miguel\"}"); }); - it("Can write arrays", [&]() + It("Can write arrays", []() { JsonFormatWriter writer{}; @@ -309,56 +307,56 @@ go_bandit([]() } ct.Leave(); } - AssertThat(writer.ToString(false), Equals("{\"players\":[\"Miguel\",\"Juan\"]}")); + Expect(writer.ToString(false)).ToEqual("{\"players\":[\"Miguel\",\"Juan\"]}"); }); - it("Can write multiple object keys", [&]() + It("Can write multiple object keys", []() { JsonFormatWriter writer{}; Writer& ct = writer; ct.BeginObject(); ct.Next("one", StringView{"Miguel"}); ct.Next("other", StringView{"Juan"}); - AssertThat( - writer.ToString(false), Equals("{\"one\":\"Miguel\",\"other\":\"Juan\"}")); + Expect( + writer.ToString(false)).ToEqual("{\"one\":\"Miguel\",\"other\":\"Juan\"}"); }); - describe("Types", []() + Describe("Types", []() { - it("Can write bool values", [&]() + It("Can write bool values", []() { JsonFormatWriter writer{}; Writer& ct = writer; ct.BeginObject(); ct.Next("alive", true); - AssertThat(writer.ToString(false), Equals("{\"alive\":true}")); + Expect(writer.ToString(false)).ToEqual("{\"alive\":true}"); JsonFormatWriter writer2{}; ct = writer2; ct.BeginObject(); ct.Next("alive", false); - AssertThat(writer2.ToString(false), Equals("{\"alive\":false}")); + Expect(writer2.ToString(false)).ToEqual("{\"alive\":false}"); }); - it("Can write i8 values", [&]() + It("Can write i8 values", []() { JsonFormatWriter writer{}; Writer ct = writer; ct.BeginObject(); ct.Next("alive", i8(-3)); - AssertThat(writer.ToString(false), Equals("{\"alive\":-3}")); + Expect(writer.ToString(false)).ToEqual("{\"alive\":-3}"); }); - it("Can write u8 values", [&]() + It("Can write u8 values", []() { JsonFormatWriter writer{}; Writer ct = writer; ct.BeginObject(); ct.Next("alive", u8(3)); - AssertThat(writer.ToString(false), Equals("{\"alive\":3}")); + Expect(writer.ToString(false)).ToEqual("{\"alive\":3}"); }); - it("Can write i16 values", [&]() + It("Can write i16 values", []() { JsonFormatWriter writer{}; Writer ct = writer; @@ -366,11 +364,11 @@ go_bandit([]() ct.Next("a", i16(-3000)); ct.Next("b", Limits::Max()); ct.Next("c", Limits::Lowest()); - AssertThat( - writer.ToString(false), Equals("{\"a\":-3000,\"b\":32767,\"c\":-32768}")); + Expect( + writer.ToString(false)).ToEqual("{\"a\":-3000,\"b\":32767,\"c\":-32768}"); }); - it("Can write u16 values", [&]() + It("Can write u16 values", []() { JsonFormatWriter writer{}; Writer ct = writer; @@ -378,58 +376,58 @@ go_bandit([]() ct.Next("a", u16(3000)); ct.Next("b", Limits::Max()); ct.Next("c", Limits::Lowest()); - AssertThat(writer.ToString(false), Equals("{\"a\":3000,\"b\":65535,\"c\":0}")); + Expect(writer.ToString(false)).ToEqual("{\"a\":3000,\"b\":65535,\"c\":0}"); }); - it("Can write u32 values", [&]() + It("Can write u32 values", []() { JsonFormatWriter writer{}; Writer ct = writer; ct.BeginObject(); ct.Next("alive", u32(35533)); - AssertThat(writer.ToString(false), Equals("{\"alive\":35533}")); + Expect(writer.ToString(false)).ToEqual("{\"alive\":35533}"); }); - it("Can write i32 values", [&]() + It("Can write i32 values", []() { JsonFormatWriter writer{}; Writer ct = writer; ct.BeginObject(); i32 value = 0; ct.Next("alive", u32(35533)); - AssertThat(writer.ToString(false), Equals("{\"alive\":35533}")); + Expect(writer.ToString(false)).ToEqual("{\"alive\":35533}"); JsonFormatWriter writer2{}; ct = writer2; ct.BeginObject(); ct.Next("alive", i32(-35533)); - AssertThat(writer2.ToString(false), Equals("{\"alive\":-35533}")); + Expect(writer2.ToString(false)).ToEqual("{\"alive\":-35533}"); }); - it("Can write float values", [&]() + It("Can write float values", []() { JsonFormatWriter writer{}; Writer ct = writer; ct.BeginObject(); ct.Next("alive", 0.344f); - AssertThat(Strings::Contains(writer.ToString(false), "0.344"), Equals(true)); + Expect(Strings::Contains(writer.ToString(false), "0.344")).ToEqual(true); JsonFormatWriter writer2{}; ct = writer2; ct.BeginObject(); ct.Next("alive", 4.f); - AssertThat(writer2.ToString(false), Equals("{\"alive\":4.0}")); + Expect(writer2.ToString(false)).ToEqual("{\"alive\":4.0}"); }); - it("Can write StringView values", [&]() + It("Can write StringView values", []() { JsonFormatWriter writer{}; Writer ct = writer; ct.BeginObject(); ct.Next("alive", StringView{"yes"}); - AssertThat(writer.ToString(false), Equals("{\"alive\":\"yes\"}")); + Expect(writer.ToString(false)).ToEqual("{\"alive\":\"yes\"}"); }); }); }); }); -}); +} diff --git a/Tests/Serialization/Serialization.spec.cpp b/Tests/Serialization/Serialization.spec.cpp index b42b5337..8ee82f23 100644 --- a/Tests/Serialization/Serialization.spec.cpp +++ b/Tests/Serialization/Serialization.spec.cpp @@ -1,11 +1,9 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include -using namespace snowhouse; -using namespace bandit; using namespace p; @@ -91,13 +89,13 @@ struct p::TFlags : public p::DefaultTFlags }; -go_bandit([]() +void RegisterSerializationSerializationTests() { - describe("Serialization", []() + Spec("Serialization", []() { - describe("Serializers in global scope", [&]() + Describe("Serializers in global scope", []() { - it("Can use custom Read()", [&]() + It("Can use custom Read()", []() { SerTypeA val{}; JsonFormatReader reader{"{\"type\": {\"value\": true }}"}; @@ -105,10 +103,10 @@ go_bandit([]() Reader& ct = reader; ct.BeginObject(); ct.Next("type", val); - AssertThat(val.value, Equals(true)); + Expect(val.value).ToEqual(true); }); - it("Can use custom Write()", [&]() + It("Can use custom Write()", []() { SerTypeA val{}; val.value = true; @@ -117,10 +115,10 @@ go_bandit([]() Writer ct = writer; ct.BeginObject(); ct.Next("type", val); - AssertThat(writer.ToString(false), Equals("{\"type\":{\"value\":true}}")); + Expect(writer.ToString(false)).ToEqual("{\"type\":{\"value\":true}}"); }); - it("Can use Serialize() instead of Read()", [&]() + It("Can use Serialize() instead of Read()", []() { SerTypeB val{}; JsonFormatReader reader{"{\"type\": {\"value\": true }}"}; @@ -128,10 +126,10 @@ go_bandit([]() Reader& ct = reader; ct.BeginObject(); ct.Next("type", val); - AssertThat(val.value, Equals(true)); + Expect(val.value).ToEqual(true); }); - it("Can use Serialize() instead of Write()", [&]() + It("Can use Serialize() instead of Write()", []() { SerTypeB val{}; val.value = true; @@ -140,13 +138,13 @@ go_bandit([]() Writer ct = writer; ct.BeginObject(); ct.Next("type", val); - AssertThat(writer.ToString(false), Equals("{\"type\":{\"value\":true}}")); + Expect(writer.ToString(false)).ToEqual("{\"type\":{\"value\":true}}"); }); }); - describe("Serializers as members", [&]() + Describe("Serializers as members", []() { - it("Can use custom Read()", [&]() + It("Can use custom Read()", []() { SerTypeC val{}; JsonFormatReader reader{"{\"type\": {\"value\": true }}"}; @@ -154,10 +152,10 @@ go_bandit([]() Reader& ct = reader; ct.BeginObject(); ct.Next("type", val); - AssertThat(val.value, Equals(true)); + Expect(val.value).ToEqual(true); }); - it("Can use custom Write()", [&]() + It("Can use custom Write()", []() { SerTypeC val{}; val.value = true; @@ -166,10 +164,10 @@ go_bandit([]() Writer ct = writer; ct.BeginObject(); ct.Next("type", val); - AssertThat(writer.ToString(false), Equals("{\"type\":{\"value\":true}}")); + Expect(writer.ToString(false)).ToEqual("{\"type\":{\"value\":true}}"); }); - it("Can use Serialize() instead of Read()", [&]() + It("Can use Serialize() instead of Read()", []() { SerTypeD val{}; JsonFormatReader reader{"{\"type\": {\"value\": true }}"}; @@ -177,10 +175,10 @@ go_bandit([]() Reader& ct = reader; ct.BeginObject(); ct.Next("type", val); - AssertThat(val.value, Equals(true)); + Expect(val.value).ToEqual(true); }); - it("Can use Serialize() instead of Write()", [&]() + It("Can use Serialize() instead of Write()", []() { SerTypeD val{}; val.value = true; @@ -189,8 +187,8 @@ go_bandit([]() Writer ct = writer; ct.BeginObject(); ct.Next("type", val); - AssertThat(writer.ToString(false), Equals("{\"type\":{\"value\":true}}")); + Expect(writer.ToString(false)).ToEqual("{\"type\":{\"value\":true}}"); }); }); }); -}); +} diff --git a/Tests/Time.spec.cpp b/Tests/Time.spec.cpp index a87f68b6..2e717afb 100644 --- a/Tests/Time.spec.cpp +++ b/Tests/Time.spec.cpp @@ -1,33 +1,31 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include -using namespace snowhouse; -using namespace bandit; using namespace p; -go_bandit([]() +void RegisterTimeTests() { - describe("Time.DateTime", []() + Spec("Time.DateTime", []() { - it("Can get day of year", [&]() + It("Can get day of year", []() { DateTime time1{2024, 1, 1}; - AssertThat(time1.GetDayOfYear(), Equals(1)); + Expect(time1.GetDayOfYear()).ToEqual(1); DateTime time11{2024, 1, 30}; - AssertThat(time11.GetDayOfYear(), Equals(30)); + Expect(time11.GetDayOfYear()).ToEqual(30); DateTime time12{2024, 1, 31}; - AssertThat(time12.GetDayOfYear(), Equals(31)); + Expect(time12.GetDayOfYear()).ToEqual(31); DateTime time2{2024, 2, 1}; - AssertThat(time2.GetDayOfYear(), Equals(32)); + Expect(time2.GetDayOfYear()).ToEqual(32); DateTime time3{2024, 3, 1}; - AssertThat(time3.GetDayOfYear(), Equals(60)); + Expect(time3.GetDayOfYear()).ToEqual(60); DateTime time4{2024, 12, 31}; - AssertThat(time4.GetDayOfYear(), Equals(365)); + Expect(time4.GetDayOfYear()).ToEqual(365); }); }); -}); +} diff --git a/Tests/main.cpp b/Tests/main.cpp index 12f6f87d..10761cb7 100644 --- a/Tests/main.cpp +++ b/Tests/main.cpp @@ -1,12 +1,15 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include -// Override as first include +// NOTE: PipeNewDelete is deliberately not included here. PipeTests provides the +// replacement operator new/delete (P_OVERRIDE_NEWDELETE) in its own translation unit; +// including it here too would cause duplicate-definition linker errors. -#include +#include #include #include +#include + // namespace backward //{ @@ -14,13 +17,88 @@ // } // namespace backward +// Forward declarations +void RegisterTimeTests(); +void RegisterCoreFunctionTests(); +void RegisterCoreOwnPtrTests(); +void RegisterCorePageBufferTests(); +void RegisterCorePlatformProcessTests(); +void RegisterCoreSetTests(); +void RegisterCoreSpinLockTests(); +void RegisterCoreStringTests(); +void RegisterCoreStringViewTests(); +void RegisterCoreTagTests(); +void RegisterContainersArraysTests(); +void RegisterECSComponentsTests(); +void RegisterECSECSsmTests(); +void RegisterECSFilteringTests(); +void RegisterECSHierarchyTests(); +void RegisterECSIdRegistryTests(); +void RegisterECSIdScopesTests(); +void RegisterECSStaticsTests(); +void RegisterFilesPathsTests(); +void RegisterMathColorTests(); +void RegisterMathMathTests(); +void RegisterMathVectorTests(); +void RegisterMemoryBestFitArenaTests(); +void RegisterMemoryBigBestFitArenaTests(); +void RegisterMemoryMemoryTests(); +void RegisterMemoryMemoryStatsTests(); +void RegisterMemoryMonoLinearArenaTests(); +void RegisterReflectionMacroReflectionTests(); +void RegisterReflectionObjectTests(); +void RegisterReflectionTraitsTests(); +void RegisterReflectionTypeIdTests(); +void RegisterReflectionTypeNameTests(); +void RegisterSerializationBinaryTests(); +void RegisterSerializationJsonTests(); +void RegisterSerializationSerializationTests(); + + int main(int argc, char* argv[]) { p::Initialize(); // Suppress leak messages from the global HeapArena (used internally by // many subsystems; not all of them free every allocation during tests). p::GetHeapArena().GetStats()->detectLeaks = false; - int result = bandit::run(argc, argv); + + RegisterTimeTests(); + RegisterCoreFunctionTests(); + RegisterCoreOwnPtrTests(); + RegisterCorePageBufferTests(); + RegisterCorePlatformProcessTests(); + RegisterCoreSetTests(); + RegisterCoreSpinLockTests(); + RegisterCoreStringTests(); + RegisterCoreStringViewTests(); + RegisterCoreTagTests(); + RegisterContainersArraysTests(); + RegisterECSComponentsTests(); + RegisterECSECSsmTests(); + RegisterECSFilteringTests(); + RegisterECSHierarchyTests(); + RegisterECSIdRegistryTests(); + RegisterECSIdScopesTests(); + RegisterECSStaticsTests(); + RegisterFilesPathsTests(); + RegisterMathColorTests(); + RegisterMathMathTests(); + RegisterMathVectorTests(); + RegisterMemoryBestFitArenaTests(); + RegisterMemoryBigBestFitArenaTests(); + RegisterMemoryMemoryTests(); + RegisterMemoryMemoryStatsTests(); + RegisterMemoryMonoLinearArenaTests(); + RegisterReflectionMacroReflectionTests(); + RegisterReflectionObjectTests(); + RegisterReflectionTraitsTests(); + RegisterReflectionTypeIdTests(); + RegisterReflectionTypeNameTests(); + RegisterSerializationBinaryTests(); + RegisterSerializationJsonTests(); + RegisterSerializationSerializationTests(); + + int result = p::RunTests(argc, argv); p::Shutdown(); return result; } From ebc9b5a75c1cf2a558e6dd3b0f84f0b93a2138c5 Mon Sep 17 00:00:00 2001 From: muit Date: Fri, 4 Sep 2026 11:19:23 +0200 Subject: [PATCH 12/25] test: file-scope auto-register, PipeTest lib, PipeTests suite, TestSettings filter --- CMakeLists.txt | 20 +- Docs/Plans/2026-09-04-pipe-tests-framework.md | 95 +- .../2026-09-04-pipe-tests-framework-design.md | 22 +- Include/{PipeTests.h => PipeTest.h} | 0 Src/Tests/{PipeTests.cpp => PipeTest.cpp} | 2 +- Tests/CMakeLists.txt | 19 +- Tests/Containers/Arrays.spec.cpp | 1941 +++++++++-------- Tests/Core/Function.spec.cpp | 103 +- Tests/Core/OwnPtr.spec.cpp | 499 ++--- Tests/Core/PageBuffer.spec.cpp | 165 +- Tests/Core/PlatformProcess.spec.cpp | 27 +- Tests/Core/Set.spec.cpp | 153 +- Tests/Core/SpinLock.spec.cpp | 277 +-- Tests/Core/String.spec.cpp | 1681 +++++++------- Tests/Core/StringView.spec.cpp | 183 +- Tests/Core/Tag.spec.cpp | 181 +- Tests/ECS/Components.spec.cpp | 473 ++-- Tests/ECS/ECS.spec.cpp | 111 +- Tests/ECS/Filtering.spec.cpp | 363 +-- Tests/ECS/Hierarchy.spec.cpp | 745 +++---- Tests/ECS/IdRegistry.spec.cpp | 287 +-- Tests/ECS/IdScopes.spec.cpp | 233 +- Tests/ECS/Statics.spec.cpp | 131 +- Tests/Files/Paths.spec.cpp | 407 ++-- Tests/Math/Color.spec.cpp | 245 ++- Tests/Math/Math.spec.cpp | 525 ++--- Tests/Math/Vector.spec.cpp | 117 +- Tests/Memory/BestFitArena.spec.cpp | 501 ++--- Tests/Memory/BigBestFitArena.spec.cpp | 543 ++--- Tests/Memory/Memory.spec.cpp | 387 ++-- Tests/Memory/MemoryStats.spec.cpp | 1025 ++++----- Tests/Memory/MonoLinearArena.spec.cpp | 273 +-- Tests/PipeTests/CMakeLists.txt | 2 +- Tests/PipeTests/PipeTests.spec.cpp | 101 +- Tests/PipeTests/main.cpp | 8 +- Tests/Reflection/MacroReflection.spec.cpp | 35 +- Tests/Reflection/Object.spec.cpp | 47 +- Tests/Reflection/Traits.spec.cpp | 101 +- Tests/Reflection/TypeId.spec.cpp | 51 +- Tests/Reflection/TypeName.spec.cpp | 117 +- Tests/Serialization/Binary.spec.cpp | 727 +++--- Tests/Serialization/Json.spec.cpp | 717 +++--- Tests/Serialization/Serialization.spec.cpp | 197 +- Tests/Time.spec.cpp | 43 +- Tests/main.cpp | 79 +- 45 files changed, 7038 insertions(+), 6921 deletions(-) rename Include/{PipeTests.h => PipeTest.h} (100%) rename Src/Tests/{PipeTests.cpp => PipeTest.cpp} (99%) diff --git a/CMakeLists.txt b/CMakeLists.txt index fee93e1f..6837a989 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,16 +83,16 @@ pipe_target_disable_rtti(Pipe PRIVATE) ################################################################################ -# PipeTests (test framework library, not part of the runtime Pipe library) - -add_library(PipeTests STATIC Src/Tests/PipeTests.cpp) -add_library(Pipe::Tests ALIAS PipeTests) -pipe_target_define_platform(PipeTests) -target_include_directories(PipeTests PUBLIC $) -pipe_target_enable_CPP20(PipeTests) -pipe_target_disable_rtti(PipeTests PRIVATE) -pipe_target_shared_output_directory(PipeTests) -target_link_libraries(PipeTests PUBLIC Pipe) +# PipeTest (test framework library, not part of the runtime Pipe library) + +add_library(PipeTest STATIC Src/Tests/PipeTest.cpp) +add_library(Pipe::Test ALIAS PipeTest) +pipe_target_define_platform(PipeTest) +target_include_directories(PipeTest PUBLIC $) +pipe_target_enable_CPP20(PipeTest) +pipe_target_disable_rtti(PipeTest PRIVATE) +pipe_target_shared_output_directory(PipeTest) +target_link_libraries(PipeTest PUBLIC Pipe) ################################################################################ diff --git a/Docs/Plans/2026-09-04-pipe-tests-framework.md b/Docs/Plans/2026-09-04-pipe-tests-framework.md index 1437cec2..a035e04a 100644 --- a/Docs/Plans/2026-09-04-pipe-tests-framework.md +++ b/Docs/Plans/2026-09-04-pipe-tests-framework.md @@ -4,15 +4,30 @@ **Goal:** Build a Pipe-native test framework (`PipeTests` module) that mirrors Bandit's structure with imgui-style global context, used by both PipeTests and RiftTests. -**Architecture:** A new `PipeTests` module in the Pipe submodule (`Include/PipeTests.h` + `Src/PipeTests.cpp`) built as a **separate CMake library target** (never compiled into the runtime `Pipe` library). Global registration cursor tracks the current test group as functions are called. `Expect(value)` returns a fluent matcher. `p::RunTests(argc, argv)` runs the suite. Existing Bandit tests are NOT migrated and Bandit is NOT removed until the final task. +**Architecture:** A new `PipeTests` module in the Pipe submodule (`Include/PipeTest.h` + `Src/PipeTest.cpp`) built as a **separate CMake library target** (never compiled into the runtime `Pipe` library). Global registration cursor tracks the current test group as functions are called. `Expect(value)` returns a fluent matcher. `p::RunTests(argc, argv)` runs the suite. Existing Bandit tests are NOT migrated and Bandit is NOT removed until the final task. -**Macro-free registration (decision 2026-09-04):** The framework uses NO macros. `Spec`/`Describe`/`It`/`XIt`/`BeforeEach`/`AfterEach` are plain functions in namespace `p`. `Spec` calls live at file scope and auto-register via static init (like `go_bandit`); no manual registration calls needed — `main()` only calls `p::RunTests`. The registry uses a function-local `static` (`GetTestContext()`), so it initializes on first use regardless of translation-unit order. `Spec(fn)` (nameless, go_bandit-style) and `Spec(name, fn)` are both supported; `Spec(fn)` registers into the virtual root describe. +**Macro-free registration (decision 2026-09-04):** The framework uses NO macros. `Spec`/`Describe`/`It`/`XIt`/`BeforeEach`/`AfterEach` are plain functions in namespace `p`. A bare function call is ill-formed at namespace scope, so each spec file wraps its `Spec` call in a TU-local static registrar (the macro-free equivalent of what `go_bandit` expands to); it auto-registers via static init — no manual registration calls needed, `main()` only calls `p::RunTests`. The registry uses a function-local `static` (`GetTestContext()`), so it initializes on first use regardless of translation-unit order. `Spec(fn)` (nameless, go_bandit-style) and `Spec(name, fn)` are both supported; `Spec(fn)` registers into the virtual root describe. + +```cpp +namespace +{ +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("Strings", []() +{ + // ... Describe/It ... +}); +return true; +}(); +} // namespace +``` **Design changes (2026-09-04, post-Task 6):** - `RunTests` split: `RunTests(int argc, char** argv)` parses argv into a `TestSettings` struct (`StringView filter`; `--filter=X`, `--filter X`, or positional) and forwards to `RunTests(const TestSettings&)`, so other systems can run tests programmatically without text args. - Pipe types throughout: `i32` counters, `TFunction` for immediately-invoked callbacks (`Spec`/`Describe`), `TArray`/`String`/`StringView`. Stored bodies/hooks (`It`/`XIt`/`BeforeEach`/`AfterEach`, hook stacks) stay `std::function` (owning) because `TFunction` is a non-owning view and would dangle. - Internals renamed: `TestGroup` → `TestDescribe` (`describes` field), `RegistryState` → `TestContext`, `State()` → `GetTestContext()`, `CurrentGroup()` → `CurrentDescribe()`, `currentGroup` → `currentDescribe`. -- Targets renamed: framework library `PipeTestsLib` → `PipeTests` (alias `Pipe::TestsLib` → `Pipe::Tests`); test executable `PipeTests` → `PipeTesting` (alias `Pipe::Testing`, ctest `PipeTesting`). +- Targets renamed: framework library `PipeTestsLib` → `PipeTest` (alias `Pipe::TestsLib` → `Pipe::Test`); test executable `PipeTests` → `PipeTesting` → `PipeTests` (no alias, ctest `PipeTests`). **Tech Stack:** C++20, CMake 3.26+, no exceptions, no RTTI (`-fno-rtti`). Pipe core types: `StringView`, `String`, `TArray`, `TFunction`, `i32`, `std::function` (stored callbacks only), `p::Format`, `p::Info/Warning/Error`. @@ -49,8 +64,8 @@ Append after the `Pipe` library block in `Extern/Pipe/CMakeLists.txt` (after lin ################################################################################ # PipeTests (test framework library, not part of the runtime Pipe library) -add_library(PipeTests STATIC Src/PipeTests.cpp) -add_library(Pipe::Tests ALIAS PipeTests) +add_library(PipeTest STATIC Src/PipeTest.cpp) +add_library(Pipe::Test ALIAS PipeTest) pipe_target_define_platform(PipeTests) target_include_directories(PipeTests PUBLIC $) pipe_target_enable_CPP20(PipeTests) @@ -59,14 +74,14 @@ pipe_target_shared_output_directory(PipeTests) target_link_libraries(PipeTests PUBLIC Pipe) ``` -Note: `Src/PipeTests.cpp` does not exist yet; CMake will fail until Task 2 creates it. +Note: `Src/PipeTest.cpp` does not exist yet; CMake will fail until Task 2 creates it. -- [ ] **Step 2: Ensure `Src/PipeTests.cpp` is excluded from the `Pipe` library glob** +- [ ] **Step 2: Ensure `Src/PipeTest.cpp` is excluded from the `Pipe` library glob** The `Pipe` library compiles `Src/*.cpp` via `file(GLOB_RECURSE PIPE_SOURCE_FILES CONFIGURE_DEPENDS Src/*.cpp Src/*.c)` (line 65). `PipeTests.cpp` in `Src/` would be globbed into `Pipe`. Since the git repo does not track glob output, verify the exclusion after Task 2 by confirming the `Pipe` target does not include `PipeTests.cpp` (build command in Task 2 will confirm). -**If needed**: remove `Src/PipeTests.cpp` match from the glob by excluding subdirectory — glob includes it. To keep `PipeTests.cpp` out of `Pipe`, place it under a subdirectory instead: put the implementation at `Src/Tests/PipeTests.cpp` (not `Src/PipeTests.cpp`), and point the `PipeTests` target at `Src/Tests/PipeTests.cpp`. The `Pipe` glob `Src/*.cpp` (non-recursive at top level only matches `PipeTests.cpp` if directly in `Src/`; the actual glob is `GLOB_RECURSE ... Src/*.cpp` which is recursive and WILL pick up `Src/Tests/PipeTests.cpp`). +**If needed**: remove `Src/PipeTest.cpp` match from the glob by excluding subdirectory — glob includes it. To keep `PipeTests.cpp` out of `Pipe`, place it under a subdirectory instead: put the implementation at `Src/Tests/PipeTest.cpp` (not `Src/PipeTest.cpp`), and point the `PipeTests` target at `Src/Tests/PipeTest.cpp`. The `Pipe` glob `Src/*.cpp` (non-recursive at top level only matches `PipeTests.cpp` if directly in `Src/`; the actual glob is `GLOB_RECURSE ... Src/*.cpp` which is recursive and WILL pick up `Src/Tests/PipeTest.cpp`). -**Decision (must-follow):** Place the implementation at `Src/Tests/PipeTests.cpp` and exclude the `Src/Tests` directory from the `Pipe` source glob. Modify the `Pipe` glob (line 65) to exclude the `PipeTests` implementation: +**Decision (must-follow):** Place the implementation at `Src/Tests/PipeTest.cpp` and exclude the `Src/Tests` directory from the `Pipe` source glob. Modify the `Pipe` glob (line 65) to exclude the `PipeTests` implementation: ```cmake file(GLOB_RECURSE PIPE_SOURCE_FILES CONFIGURE_DEPENDS Src/*.cpp Src/*.c) @@ -74,10 +89,10 @@ list(FILTER PIPE_SOURCE_FILES EXCLUDE REGEX ".*/Src/Tests/.*") target_sources(Pipe PRIVATE ${PIPE_SOURCE_FILES}) ``` -Then the `PipeTests` target in this task uses `Src/Tests/PipeTests.cpp`: +Then the `PipeTests` target in this task uses `Src/Tests/PipeTest.cpp`: ```cmake -add_library(PipeTests STATIC Src/Tests/PipeTests.cpp) +add_library(PipeTest STATIC Src/Tests/PipeTest.cpp) ``` - [ ] **Step 3: Configure + build (may fail until Task 2 creates the source)** @@ -87,7 +102,7 @@ Run (from `Extern/Pipe`): cmake -S . -B Build cmake --build Build --config Release ``` -Expected: fails only because `Src/Tests/PipeTests.cpp` (and `Include/PipeTests.h`) do not exist yet. This is acceptable mid-plan; the target is created and validated in Task 2. +Expected: fails only because `Src/Tests/PipeTest.cpp` (and `Include/PipeTest.h`) do not exist yet. This is acceptable mid-plan; the target is created and validated in Task 2. - [ ] **Step 4: Commit** @@ -98,10 +113,10 @@ git commit -m "build: add PipeTests library target" --- -### Task 2: `PipeTests.h` public header — registration functions +### Task 2: `PipeTest.h` public header — registration functions **Files:** -- Create: `Extern/Pipe/Include/PipeTests.h` +- Create: `Extern/Pipe/Include/PipeTest.h` **Interfaces:** - Consumes: `Pipe/Core/Log.h` (for error logging), `StringView.h`. @@ -119,7 +134,7 @@ git commit -m "build: add PipeTests library target" - [ ] **Step 1: Declare the registration API** -Create `Extern/Pipe/Include/PipeTests.h`: +Create `Extern/Pipe/Include/PipeTest.h`: ```cpp // Copyright 2015-2026 Piperift. All Rights Reserved. @@ -171,7 +186,7 @@ namespace p - [ ] **Step 2: Commit** ```bash -git add Include/PipeTests.h +git add Include/PipeTest.h git commit -m "feat: declare PipeTests registration API" ``` @@ -180,10 +195,10 @@ git commit -m "feat: declare PipeTests registration API" ### Task 3: `PipeTests.cpp` — registry, cursor, runner (skip + summary) **Files:** -- Create: `Extern/Pipe/Src/Tests/PipeTests.cpp` +- Create: `Extern/Pipe/Src/Tests/PipeTest.cpp` **Interfaces:** -- Consumes: `PipeTests.h`, `Pipe.h`, `Pipe/Core/Log.h`, `PipeStrings.h`, `StringView.h`, `TArray`. +- Consumes: `PipeTest.h`, `Pipe.h`, `Pipe/Core/Log.h`, `PipeStrings.h`, `StringView.h`, `TArray`. - Produces: implementation of `Spec`, `Describe`, `It`, `XIt`, `BeforeEach`, `AfterEach`, `RunTests`. Matcher `Expect` is a separate task (Task 4); until then `It` bodies cannot assert. The runner must support: @@ -205,7 +220,7 @@ The runner must support: #include "PipeNewDelete.h" #endif -#include "PipeTests.h" +#include "PipeTest.h" #include "Pipe.h" #include "Pipe/Core/Log.h" #include "PipeStrings.h" @@ -546,7 +561,7 @@ Expected: target `PipeTests` builds, `Pipe` library does NOT include `PipeTests. - [ ] **Step 5: Commit** ```bash -git add Src/Tests/PipeTests.cpp +git add Src/Tests/PipeTest.cpp git commit -m "feat: add PipeTests registry and runner" ``` @@ -559,7 +574,7 @@ git commit -m "feat: add PipeTests registry and runner" - Create: `Extern/Pipe/Tests/PipeTests/main.cpp` **Interfaces:** -- Consumes: `PipeTests.h`, `Pipe.h`, `p::Expect` (Task 5). To avoid depending on Task 5, implement this task to compile against the header and **defer the actual `Expect` matcher to Task 5**, adding assertions there in step 3. The framework uses no macros (`Spec` at file scope auto-registers); assertions arrive with `Expect` in Task 5. +- Consumes: `PipeTest.h`, `Pipe.h`, `p::Expect` (Task 5). To avoid depending on Task 5, implement this task to compile against the header and **defer the actual `Expect` matcher to Task 5**, adding assertions there in step 3. The framework uses no macros (`Spec` at file scope auto-registers); assertions arrive with `Expect` in Task 5. - Produces: a second test executable `PipeTestsSelf` registered in CTest, proving the framework runs alongside the untouched Bandit suite. @@ -575,7 +590,7 @@ git commit -m "feat: add PipeTests registry and runner" // including it here too would cause duplicate-definition linker errors. #include -#include +#include int main(int argc, char* argv[]) @@ -592,7 +607,7 @@ int main(int argc, char* argv[]) ```cpp // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include @@ -675,16 +690,16 @@ git commit -m "test: add PipeTests self-test suite" ### Task 5: `Expect` fluent matcher + extensible formatter **Files:** -- Modify: `Extern/Pipe/Include/PipeTests.h` -- Modify: `Extern/Pipe/Src/Tests/PipeTests.cpp` +- Modify: `Extern/Pipe/Include/PipeTest.h` +- Modify: `Extern/Pipe/Src/Tests/PipeTest.cpp` **Interfaces:** -- Consumes: `PipeTests.h` registration API (Task 2/3), Pipe `Format`, `StringView`, `Number` concept (`TypeTraits.h`). +- Consumes: `PipeTest.h` registration API (Task 2/3), Pipe `Format`, `StringView`, `Number` concept (`TypeTraits.h`). - Produces: `Expect(value)` matcher returned by `p::Expect(value)` with methods `ToEqual`, `ToNotEqual`, `ToBeLess`, `ToBeLessOrEqual`, `ToBeGreater`, `ToBeGreaterOrEqual`, `ToBeTrue`, `ToBeFalse`, `ToContain`, `ToNotContain`. Failure prints `file:line` + actual/expected via an extensible `ToString`-style hook (`p::TestString`). -- [ ] **Step 1: Add the formatter hook and matcher to `PipeTests.h`** +- [ ] **Step 1: Add the formatter hook and matcher to `PipeTest.h`** -Append to `PipeTests.h`: +Append to `PipeTest.h`: ```cpp // Extensible value-to-string hook for failure messages. @@ -928,7 +943,7 @@ In `RunNested`, before running the body reset the count, after body if `currentT Update `Extern/Pipe/Tests/PipeTests/PipeTests.spec.cpp` to use `Expect`: ```cpp -#include +#include #include using namespace p; @@ -979,7 +994,7 @@ Verify one failure is caught: temporarily add to a test `Expect(1).ToEqual(2);`, - [ ] **Step 5: Commit** ```bash -git add Include/PipeTests.h Src/Tests/PipeTests.cpp Tests/PipeTests +git add Include/PipeTest.h Src/Tests/PipeTest.cpp Tests/PipeTests git commit -m "feat: add Expect fluent matcher" ``` @@ -997,7 +1012,7 @@ git commit -m "feat: add Expect fluent matcher" - Modify: `Tests/CMakeLists.txt` (Rift) and `Tests/*.spec.cpp` (Rift) in `D:\Projects\Piperift\rift` **Interfaces:** -- Consumes: `PipeTests.h`, `p::RunTests` (Tasks 2-5). +- Consumes: `PipeTest.h`, `p::RunTests` (Tasks 2-5). - Produces: Bandit fully removed; both Pipe and Rift suites run on the native framework. ⚠️ **This task is intentionally LAST. Do not start it until Tasks 1-5 are complete and verified.** @@ -1036,7 +1051,7 @@ go_bandit([]() New: ```cpp -#include +#include #include #include @@ -1059,9 +1074,9 @@ Spec("Strings", []() ``` Transform rules (from the spec): -- `#include ` → `#include ` +- `#include ` → `#include ` - `using namespace snowhouse; using namespace bandit;` → remove both; keep `using namespace p;` -- **Top-level:** `go_bandit([](){ describe("G", [](){ ...` → `Spec("G", [](){ ...` at file scope (drop the outer `go_bandit` extra nesting and one `describe` level; the top `Spec("Strings", ...)` replaces go_bandit+first describe and auto-registers via static init — no wrapper function, no `main.cpp` changes). +- **Top-level:** `go_bandit([](){ describe("G", [](){ ...` → `Spec("G", [](){ ...` wrapped in the file-scope static registrar above (drop the outer `go_bandit` extra nesting and one `describe` level; the top `Spec("Strings", ...)` replaces go_bandit+first describe and auto-registers — no `main.cpp` changes). - `describe(` → `Describe(` - `it(` → `It(` (drop the `[&]` → `[]`; lambdas no longer need `&` capture since framework state is global) - `xit(` → `XIt(` @@ -1095,19 +1110,19 @@ Convert every `Extern/Pipe/Tests/**/*.spec.cpp` using the transform rules above. - [ ] **Step 4: Switch PipeTests exe to the new framework** `Extern/Pipe/Tests/CMakeLists.txt`: -- Rename the suite executable `PipeTests` → `PipeTesting` (alias `Pipe::Testing`); link the framework library: `target_link_libraries(PipeTesting PUBLIC Pipe PipeTests)` (framework library is `PipeTests`, alias `Pipe::Tests`) +Keep the suite executable `PipeTests` (no alias); link the framework library: `target_link_libraries(PipeTests PUBLIC Pipe PipeTest)` (framework library is `PipeTest`, alias `Pipe::Test`) - Remove `--reporter=spec` from `add_test(...)`: - `add_test(NAME PipeTesting COMMAND $)` -- Remove the `list(FILTER ...)` exclusion added in Task 4 (restore the plain glob) so all spec files (including migrated ones) build into `PipeTesting`. + `add_test(NAME PipeTests COMMAND $)` +- Remove the `list(FILTER ...)` exclusion added in Task 4 (restore the plain glob) so all spec files (including migrated ones) build into `PipeTests`. `Extern/Pipe/Tests/main.cpp`: replace `int result = bandit::run(argc, argv);` with `int result = p::RunTests(argc, argv);`, and remove `#include `. Specs auto-register at file scope, so `main.cpp` needs no per-file calls. Keep the `p::Initialize`/`p::Shutdown` calls; `PipeNewDelete.h` no longer needs to be included here since `PipeTests` provides the override. -`Extern/Pipe/Tests/PipeTests/CMakeLists.txt`: keep the `PipeTestsSelf` target for framework self-checks, OR fold the self-test spec files into the main `PipeTesting` glob (remove the separate subdirectory). Keep `PipeTestsSelf` as-is for now (harmless), unless the main glob re-includes its files. Since the main glob is `GLOB_RECURSE *.cpp` from `Tests/`, it WILL include `Tests/PipeTests/*.cpp` again → duplicate `main()`. So when restoring the plain glob in step 4, re-apply a filter to EXCLUDE `Tests/PipeTests/` from the main `PipeTesting` exe (keep `PipeTestsSelf` as a separate target): +`Extern/Pipe/Tests/PipeTests/CMakeLists.txt`: keep the `PipeTestsSelf` target for framework self-checks, OR fold the self-test spec files into the main `PipeTests` glob (remove the separate subdirectory). Keep `PipeTestsSelf` as-is for now (harmless), unless the main glob re-includes its files. Since the main glob is `GLOB_RECURSE *.cpp` from `Tests/`, it WILL include `Tests/PipeTests/*.cpp` again → duplicate `main()`. So when restoring the plain glob in step 4, re-apply a filter to EXCLUDE `Tests/PipeTests/` from the main `PipeTests` exe (keep `PipeTestsSelf` as a separate target): ```cmake file(GLOB_RECURSE TESTS_SOURCE_FILES CONFIGURE_DEPENDS *.cpp *.h *.hpp) list(FILTER TESTS_SOURCE_FILES EXCLUDE REGEX ".*/PipeTests/.*") -add_executable(PipeTesting ${TESTS_SOURCE_FILES}) +add_executable(PipeTests ${TESTS_SOURCE_FILES}) ``` Keep `add_subdirectory(PipeTests)` for `PipeTestsSelf`. @@ -1130,7 +1145,7 @@ Expected: `PipeTests` runs all migrated tests with names/locations under the new - [ ] **Step 7: Migrate Rift tests + CMake** In `D:\Projects\Piperift\rift`: -- `Tests/CMakeLists.txt`: `target_link_libraries(RiftTests PUBLIC RiftASTLib Bandit)` → `target_link_libraries(RiftTests PUBLIC RiftASTLib Pipe::Tests)` (the framework library is `PipeTests`, alias `Pipe::Tests`; it is defined unconditionally in `Extern/Pipe/CMakeLists.txt` per Task 1). Rift's `Tests/main.cpp` only swaps `bandit::run` for `p::RunTests` (specs auto-register). +- `Tests/CMakeLists.txt`: `target_link_libraries(RiftTests PUBLIC RiftASTLib Bandit)` → `target_link_libraries(RiftTests PUBLIC RiftASTLib Pipe::Test)` (the framework library is `PipeTest`, alias `Pipe::Test`; it is defined unconditionally in `Extern/Pipe/CMakeLists.txt` per Task 1). Rift's `Tests/main.cpp` only swaps `bandit::run` for `p::RunTests` (specs auto-register). - Convert Rift `Tests/Project.spec.cpp`, `Tests/AST/Statements.spec.cpp`, `Tests/AST/Expressions.spec.cpp`, `Tests/AST/Namespaces.spec.cpp` per the transform rules (uses `before_each`/`after_each` → `BeforeEach`/`AfterEach`, `AssertThat(result, Equals(true))` → `Expect(result).ToBeTrue()`, etc.). Each file holds its `Spec(...)` at file scope; remove `#include ` and `using namespace snowhouse/bandit`. - [ ] **Step 8: Full project build + tests + format** diff --git a/Docs/Specs/2026-09-04-pipe-tests-framework-design.md b/Docs/Specs/2026-09-04-pipe-tests-framework-design.md index eb1678a5..18e702b4 100644 --- a/Docs/Specs/2026-09-04-pipe-tests-framework-design.md +++ b/Docs/Specs/2026-09-04-pipe-tests-framework-design.md @@ -100,15 +100,15 @@ Fluent matcher methods, naming in Pipe CamelCase: ### New files (in the Pipe submodule) -- `Extern/Pipe/Include/PipeTests.h` — public API (global functions, `Expect` matcher, formatter hook). Mostly templates; **no macros**. -- `Extern/Pipe/Src/Tests/PipeTests.cpp` — function-local static `TestContext`, registration functions (`Spec`/`Describe`/`It`/`XIt`/`BeforeEach`/`AfterEach`), `p::RunTests(settings)` + `p::RunTests(int, char**)` argv forwarder. +- `Extern/Pipe/Include/PipeTest.h` — public API (global functions, `Expect` matcher, formatter hook). Mostly templates; **no macros**. +- `Extern/Pipe/Src/Tests/PipeTest.cpp` — function-local static `TestContext`, registration functions (`Spec`/`Describe`/`It`/`XIt`/`BeforeEach`/`AfterEach`), `p::RunTests(settings)` + `p::RunTests(int, char**)` argv forwarder. ### Build — separate target, not into the runtime Pipe lib `Extern/Pipe/CMakeLists.txt`: -- Define `add_library(PipeTests ...)` (alias `Pipe::Tests`) **unconditionally** (alongside `Pipe`, **before** the `PIPE_BUILD_TESTS` gate) so Rift can consume it via the submodule. (The suite executable is `PipeTesting`, alias `Pipe::Testing`, so the `PipeTests` name is free for the framework library.) -- **Exclude `Src/Tests/PipeTests.cpp`** from Pipe's `file(GLOB_RECURSE ... Src/*.cpp)` (currently line 65) so the test framework does **not** ship in the runtime `Pipe` library. Add it only to the `PipeTests` target. +- Define `add_library(PipeTest ...)` (alias `Pipe::Test`) **unconditionally** (alongside `Pipe`, **before** the `PIPE_BUILD_TESTS` gate) so Rift can consume it via the submodule. (The suite executable keeps the `PipeTests` name with no alias.) +- **Exclude `Src/Tests/PipeTest.cpp`** from Pipe's `file(GLOB_RECURSE ... Src/*.cpp)` (currently line 65) so the test framework does **not** ship in the runtime `Pipe` library. Add it only to the `PipeTests` target. - Give `PipeTests` the standard Pipe setup (`pipe_target_define_platform`, `pipe_target_enable_CPP20`, `pipe_target_disable_rtti`, `pipe_target_shared_output_directory`) and link `Pipe`; expose `Include/`. ### Runner @@ -128,15 +128,15 @@ A current-describe cursor in `PipeTests.cpp` (`TestContext::currentDescribe`, ac **Deliberate: do NOT migrate existing tests and do NOT remove third-party libraries until the very end, when the framework is done and self-tested.** Bandit stays linked and coexists throughout development. -1. **Add `PipeTests` module** — header + source; `add_library(PipeTests)`; source-glob exclusion. Build succeeds. +1. **Add `PipeTest` module** — header + source; `add_library(PipeTest)`; source-glob exclusion. Build succeeds. 2. **Self-test the framework with small new tests** (no migration of existing tests): - Create small **new** framework tests in `Extern/Pipe/Tests/PipeTests/` (e.g. `PipeTests.spec.cpp`) written with the new API to validate: `Spec`/`Describe`/`It`/`XIt`/`BeforeEach`/`AfterEach`, all `Expect` matchers, failure reporting, skip counting, `RunTests` exit code, and `Describe`-outside-`Spec` behavior. The spec file holds `Spec(...)` at file scope (auto-registers). - - Wire a **separate small runner** (its own `main.cpp` calling `p::RunTests`) for this smoke target, running **alongside** the existing bandit `PipeTesting` executable. + - Wire a **separate small runner** (its own `main.cpp` calling `p::RunTests`) for this smoke target, running **alongside** the existing bandit `PipeTests` executable. - Verify via `ctest` that **both** the new self-tests and the untouched bandit suite pass. Iterate until the framework is proven. 3. **Final flip (LAST, only when the system is done):** - Migrate existing `*spec.cpp` files file-by-file (transform map below). Each migrated file holds `Spec(...)` at file scope (auto-registers; no wrapper, no `main` changes). - - `Extern/Pipe/Tests/CMakeLists.txt`: rename suite exe to `PipeTesting`, link `Pipe` + `PipeTests`, drop `Bandit`; `main.cpp` calls `p::RunTests(argc, argv)`; drop `--reporter=spec`. - - Migrate Rift `Tests/*.spec.cpp` + `Tests/CMakeLists.txt`: replace `Bandit` with `Pipe::Tests` (the alias); Rift's `Tests/main.cpp` only swaps `bandit::run` for `p::RunTests`. + - `Extern/Pipe/Tests/CMakeLists.txt`: keep suite exe `PipeTests` (no alias), link `Pipe` + `PipeTest`, drop `Bandit`; `main.cpp` calls `p::RunTests(argc, argv)`; drop `--reporter=spec`. + - Migrate Rift `Tests/*.spec.cpp` + `Tests/CMakeLists.txt`: replace `Bandit` with `Pipe::Test` (the alias); Rift's `Tests/main.cpp` only swaps `bandit::run` for `p::RunTests`. - Remove `Bandit`: delete the `Bandit` INTERFACE target from `Extern/Pipe/Extern/CMakeLists.txt`, remove the vendored `Extern/Pipe/Extern/Bandit/` directory, strip remaining bandit includes. - Final full `ctest` + `ClangFormat`/`ClangTidy` pass. @@ -144,8 +144,8 @@ A current-describe cursor in `PipeTests.cpp` (`TestContext::currentDescribe`, ac | Before (bandit) | After (PipeTests) | |-----------------|-------------------| -| `#include ` + `using namespace snowhouse; using namespace bandit;` | `#include ` + `using namespace p;` | -| `go_bandit([](){ describe("G", ...) })` (global scope) | File-scope `Spec("G", [](){ ... })`; auto-registers, no `main` changes | +| `#include ` + `using namespace snowhouse; using namespace bandit;` | `#include ` + `using namespace p;` | +| `go_bandit([](){ describe("G", ...) })` (global scope) | `Spec("G", [](){ ... })` inside a TU-local static registrar (`namespace { const bool autoRegistered = [](){ Spec(...); return true; }(); }`); auto-registers, no `main` changes | | `describe(...)` | `Describe(...)` | | `it(...)` | `It(...)` | | `xit(...)` | `XIt(...)` | @@ -164,7 +164,7 @@ Note: existing test files that `using namespace snowhouse; using namespace bandi - `Bandit` INTERFACE target is defined in `Extern/Pipe/Extern/CMakeLists.txt`, added **unconditionally** (Pipe `CMakeLists.txt` line 47 `add_subdirectory(Extern)` runs before the `PIPE_BUILD_TESTS` gate). - Pipe supplies its own tests executable gated behind `PIPE_BUILD_TESTS` (default `PIPE_IS_PROJECT`, i.e. ON when Pipe is the top project). - Rift builds its **own** `RiftTests` executable in `Tests/CMakeLists.txt`, linking `RiftASTLib` + `Bandit` (imported through Pipe's `Extern`). -- Therefore the `PipeTests` target (alias `Pipe::Tests`) must be defined **unconditionally** in Pipe's `CMakeLists.txt` so Rift can link it. +- Therefore the `PipeTest` target (alias `Pipe::Test`) must be defined **unconditionally** in Pipe's `CMakeLists.txt` so Rift can link it. ## Constraints & style diff --git a/Include/PipeTests.h b/Include/PipeTest.h similarity index 100% rename from Include/PipeTests.h rename to Include/PipeTest.h diff --git a/Src/Tests/PipeTests.cpp b/Src/Tests/PipeTest.cpp similarity index 99% rename from Src/Tests/PipeTests.cpp rename to Src/Tests/PipeTest.cpp index f830055c..bb6d4ce9 100644 --- a/Src/Tests/PipeTests.cpp +++ b/Src/Tests/PipeTest.cpp @@ -10,7 +10,7 @@ #include "Pipe.h" #include "Pipe/Core/Log.h" #include "PipeStrings.h" -#include "PipeTests.h" +#include "PipeTest.h" namespace p diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index cb405df8..f539ba5b 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -3,16 +3,15 @@ file(GLOB_RECURSE TESTS_SOURCE_FILES CONFIGURE_DEPENDS *.cpp *.h *.hpp) list(FILTER TESTS_SOURCE_FILES EXCLUDE REGEX ".*/PipeTests/.*") -add_executable(PipeTesting ${TESTS_SOURCE_FILES}) -add_executable(Pipe::Testing ALIAS PipeTesting) -target_include_directories(PipeTesting PUBLIC .) -pipe_target_enable_CPP20(PipeTesting) -pipe_target_disable_rtti(PipeTesting PRIVATE) -pipe_target_define_platform(PipeTesting) -pipe_target_shared_output_directory(PipeTesting) -target_link_libraries(PipeTesting PUBLIC Pipe PipeTests) -pipe_add_sanitizers(PipeTesting) +add_executable(PipeTests ${TESTS_SOURCE_FILES}) +target_include_directories(PipeTests PUBLIC .) +pipe_target_enable_CPP20(PipeTests) +pipe_target_disable_rtti(PipeTests PRIVATE) +pipe_target_define_platform(PipeTests) +pipe_target_shared_output_directory(PipeTests) +target_link_libraries(PipeTests PUBLIC Pipe PipeTest) +pipe_add_sanitizers(PipeTests) -add_test(NAME PipeTesting COMMAND $) +add_test(NAME PipeTests COMMAND $) add_subdirectory(PipeTests) diff --git a/Tests/Containers/Arrays.spec.cpp b/Tests/Containers/Arrays.spec.cpp index 1223d392..bb821b81 100644 --- a/Tests/Containers/Arrays.spec.cpp +++ b/Tests/Containers/Arrays.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include @@ -41,1096 +41,1101 @@ struct CopyType }; -void RegisterContainersArraysTests() +namespace { - Spec("Containers.Array", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("Containers.Array", []() +{ + It("Can initialize", []() + { + TArray data1{}; + TArray data2(3); + TArray data3{3, 3, 3, 3}; + TArray data4(3, 1); + TArray data5(6, 1); + + Expect(data1.Size()).ToEqual(0); + Expect(data1.Capacity()).ToEqual(0); + Expect(data2.Size()).ToEqual(3); + Expect(data2.Capacity()).ToEqual(5); + Expect(data3.Size()).ToEqual(4); + Expect(data3.Capacity()).ToEqual(5); + Expect(data4.Size()).ToEqual(3); + Expect(data4.Capacity()).ToEqual(5); + Expect(data5.Size()).ToEqual(6); + Expect(data5.Capacity()).ToEqual(6); + + Expect(data2[0]).ToEqual(0); + Expect(data2[2]).ToEqual(0); + Expect(data3[0]).ToEqual(3); + Expect(data3[3]).ToEqual(3); + Expect(data4[0]).ToEqual(1); + Expect(data4[2]).ToEqual(1); + Expect(data5[0]).ToEqual(1); + Expect(data5[5]).ToEqual(1); + }); + + + Describe("Copy", []() { - It("Can initialize", []() + It("Can copy empty", []() { - TArray data1{}; - TArray data2(3); - TArray data3{3, 3, 3, 3}; - TArray data4(3, 1); - TArray data5(6, 1); - - Expect(data1.Size()).ToEqual(0); - Expect(data1.Capacity()).ToEqual(0); - Expect(data2.Size()).ToEqual(3); - Expect(data2.Capacity()).ToEqual(5); - Expect(data3.Size()).ToEqual(4); - Expect(data3.Capacity()).ToEqual(5); - Expect(data4.Size()).ToEqual(3); - Expect(data4.Capacity()).ToEqual(5); - Expect(data5.Size()).ToEqual(6); - Expect(data5.Capacity()).ToEqual(6); - - Expect(data2[0]).ToEqual(0); - Expect(data2[2]).ToEqual(0); - Expect(data3[0]).ToEqual(3); - Expect(data3[3]).ToEqual(3); - Expect(data4[0]).ToEqual(1); - Expect(data4[2]).ToEqual(1); - Expect(data5[0]).ToEqual(1); - Expect(data5[5]).ToEqual(1); + TArray source1{}; + TArray target1 = source1; // NOLINT + Expect(target1.Data()).ToEqual(nullptr); + Expect(target1.Size()).ToEqual(0); + Expect(target1.Capacity()).ToEqual(0); + TArray source2{}; + TArray target2 = source2; // NOLINT + Expect(target2.Data()).ToEqual(nullptr); + Expect(target2.Size()).ToEqual(0); + Expect(target2.Capacity()).ToEqual(0); }); + It("Can copy dynamic to dynamic", []() + { + TArray source{3, 4, 5, 6, 7, 8}; // Not inline buffer + TArray target = source; + Expect(source.Size()).ToEqual(6); + Expect(source.Capacity()).ToEqual(6); + Expect(target.Size()).ToEqual(6); + Expect(target.Capacity()).ToBeGreaterOrEqual(6); + Expect(source[0]).ToEqual(3); + Expect(source[5]).ToEqual(8); + Expect(target[0]).ToEqual(3); + Expect(target[5]).ToEqual(8); + Expect(source.Data()).ToNotEqual(source.GetInlineBuffer()); + Expect(target.Data()).ToNotEqual(target.GetInlineBuffer()); + }); - Describe("Copy", []() + It("Can copy inline to inline", []() { - It("Can copy empty", []() - { - TArray source1{}; - TArray target1 = source1; // NOLINT - Expect(target1.Data()).ToEqual(nullptr); - Expect(target1.Size()).ToEqual(0); - Expect(target1.Capacity()).ToEqual(0); - TArray source2{}; - TArray target2 = source2; // NOLINT - Expect(target2.Data()).ToEqual(nullptr); - Expect(target2.Size()).ToEqual(0); - Expect(target2.Capacity()).ToEqual(0); - }); + TArray source{3, 4, 5, 6}; // Not inline buffer + TArray target; + target = source; + Expect(source.Size()).ToEqual(4); + Expect(source.Capacity()).ToEqual(5); + Expect(target.Size()).ToEqual(4); + Expect(target.Capacity()).ToEqual(5); + Expect(source[0]).ToEqual(3); + Expect(source[3]).ToEqual(6); + Expect(target[0]).ToEqual(3); + Expect(target[3]).ToEqual(6); + Expect(source.Data()).ToEqual(source.GetInlineBuffer()); + Expect(target.Data()).ToEqual(target.GetInlineBuffer()); + TArray target2; // Copy to a different size + target2 = source; + Expect(source.Data()).ToEqual(source.GetInlineBuffer()); + Expect(target2.Data()).ToEqual(target2.GetInlineBuffer()); + }); - It("Can copy dynamic to dynamic", []() - { - TArray source{3, 4, 5, 6, 7, 8}; // Not inline buffer - TArray target = source; - Expect(source.Size()).ToEqual(6); - Expect(source.Capacity()).ToEqual(6); - Expect(target.Size()).ToEqual(6); - Expect(target.Capacity()).ToBeGreaterOrEqual(6); - Expect(source[0]).ToEqual(3); - Expect(source[5]).ToEqual(8); - Expect(target[0]).ToEqual(3); - Expect(target[5]).ToEqual(8); - Expect(source.Data()).ToNotEqual(source.GetInlineBuffer()); - Expect(target.Data()).ToNotEqual(target.GetInlineBuffer()); - }); + It("Can copy dynamic to inline", []() + { + TArray source{3, 4, 5, 6}; // Not inline buffer + TArray target; + target = source; + Expect(source.Size()).ToEqual(4); + Expect(source.Capacity()).ToEqual(4); + Expect(target.Size()).ToEqual(4); + Expect(target.Capacity()).ToEqual(5); + Expect(source[0]).ToEqual(3); + Expect(source[3]).ToEqual(6); + Expect(target[0]).ToEqual(3); + Expect(target[3]).ToEqual(6); + Expect(source.Data()).ToNotEqual(source.GetInlineBuffer()); + Expect(target.Data()).ToEqual(target.GetInlineBuffer()); + }); - It("Can copy inline to inline", []() - { - TArray source{3, 4, 5, 6}; // Not inline buffer - TArray target; - target = source; - Expect(source.Size()).ToEqual(4); - Expect(source.Capacity()).ToEqual(5); - Expect(target.Size()).ToEqual(4); - Expect(target.Capacity()).ToEqual(5); - Expect(source[0]).ToEqual(3); - Expect(source[3]).ToEqual(6); - Expect(target[0]).ToEqual(3); - Expect(target[3]).ToEqual(6); - Expect(source.Data()).ToEqual(source.GetInlineBuffer()); - Expect(target.Data()).ToEqual(target.GetInlineBuffer()); - TArray target2; // Copy to a different size - target2 = source; - Expect(source.Data()).ToEqual(source.GetInlineBuffer()); - Expect(target2.Data()).ToEqual(target2.GetInlineBuffer()); - }); + It("Can copy inline to dynamic", []() + { + TArray source{3, 4, 5, 6}; // Not inline buffer + TArray target; + target = source; + Expect(source.Size()).ToEqual(4); + Expect(source.Capacity()).ToEqual(5); + Expect(target.Size()).ToEqual(4); + Expect(target.Capacity()).ToBeGreaterOrEqual(4); + Expect(source[0]).ToEqual(3); + Expect(source[3]).ToEqual(6); + Expect(target[0]).ToEqual(3); + Expect(target[3]).ToEqual(6); + Expect(source.Data()).ToEqual(source.GetInlineBuffer()); + Expect(target.Data()).ToNotEqual(target.GetInlineBuffer()); + }); + }); - It("Can copy dynamic to inline", []() - { - TArray source{3, 4, 5, 6}; // Not inline buffer - TArray target; - target = source; - Expect(source.Size()).ToEqual(4); - Expect(source.Capacity()).ToEqual(4); - Expect(target.Size()).ToEqual(4); - Expect(target.Capacity()).ToEqual(5); - Expect(source[0]).ToEqual(3); - Expect(source[3]).ToEqual(6); - Expect(target[0]).ToEqual(3); - Expect(target[3]).ToEqual(6); - Expect(source.Data()).ToNotEqual(source.GetInlineBuffer()); - Expect(target.Data()).ToEqual(target.GetInlineBuffer()); - }); + Describe("Move", []() + { + It("Can move empty", []() + { + TArray source1{}; + TArray target1 = Move(source1); + Expect(target1.Data()).ToEqual(nullptr); + Expect(target1.Size()).ToEqual(0); + Expect(target1.Capacity()).ToEqual(0); + TArray source2{}; + TArray target2 = Move(source2); + Expect(target2.Data()).ToEqual(nullptr); + Expect(target2.Size()).ToEqual(0); + Expect(target2.Capacity()).ToEqual(0); + }); - It("Can copy inline to dynamic", []() - { - TArray source{3, 4, 5, 6}; // Not inline buffer - TArray target; - target = source; - Expect(source.Size()).ToEqual(4); - Expect(source.Capacity()).ToEqual(5); - Expect(target.Size()).ToEqual(4); - Expect(target.Capacity()).ToBeGreaterOrEqual(4); - Expect(source[0]).ToEqual(3); - Expect(source[3]).ToEqual(6); - Expect(target[0]).ToEqual(3); - Expect(target[3]).ToEqual(6); - Expect(source.Data()).ToEqual(source.GetInlineBuffer()); - Expect(target.Data()).ToNotEqual(target.GetInlineBuffer()); - }); + It("Can move dynamic to dynamic", []() + { + TArray source{}; // Not inline buffer + source.Add(3); + source.Add(4); + source.Add(5); + source.Add(6); + source.Add(7); + source.Add(8); + MoveType* sourceData = source.Data(); + TArray target = Move(source); + Expect(source.Size()).ToEqual(0); + Expect(source.Capacity()).ToEqual(0); + Expect(target.Size()).ToEqual(6); + Expect(target.Capacity()).ToBeGreaterOrEqual(6); + Expect(target[0].value).ToEqual(3); + Expect(target[5].value).ToEqual(8); + Expect(source.Data()).ToEqual(nullptr); + Expect(target.Data()).ToNotEqual(target.GetInlineBuffer()); + Expect(target.Data()).ToEqual(sourceData); }); - Describe("Move", []() + It("Can move inline to inline", []() { - It("Can move empty", []() - { - TArray source1{}; - TArray target1 = Move(source1); - Expect(target1.Data()).ToEqual(nullptr); - Expect(target1.Size()).ToEqual(0); - Expect(target1.Capacity()).ToEqual(0); - TArray source2{}; - TArray target2 = Move(source2); - Expect(target2.Data()).ToEqual(nullptr); - Expect(target2.Size()).ToEqual(0); - Expect(target2.Capacity()).ToEqual(0); - }); + TArray source{}; // Not inline buffer + source.Add(3); + source.Add(4); + source.Add(5); + source.Add(6); + TArray source2{}; // Not inline buffer + source2.Add(3); + source2.Add(4); + source2.Add(5); + source2.Add(6); + TArray target; + target = Move(source); + Expect(source.Size()).ToEqual(0); + Expect(source.Capacity()).ToEqual(0); + Expect(target.Size()).ToEqual(4); + Expect(target.Capacity()).ToEqual(5); + Expect(target[0].value).ToEqual(3); + Expect(target[3].value).ToEqual(6); + Expect(source.Data()).ToEqual(nullptr); + Expect(target.Data()).ToEqual(target.GetInlineBuffer()); + TArray target2; // Copy to a different size + target2 = Move(source2); + Expect(source2.Data()).ToEqual(nullptr); + Expect(target2.Data()).ToEqual(target2.GetInlineBuffer()); + }); - It("Can move dynamic to dynamic", []() - { - TArray source{}; // Not inline buffer - source.Add(3); - source.Add(4); - source.Add(5); - source.Add(6); - source.Add(7); - source.Add(8); - MoveType* sourceData = source.Data(); - TArray target = Move(source); - Expect(source.Size()).ToEqual(0); - Expect(source.Capacity()).ToEqual(0); - Expect(target.Size()).ToEqual(6); - Expect(target.Capacity()).ToBeGreaterOrEqual(6); - Expect(target[0].value).ToEqual(3); - Expect(target[5].value).ToEqual(8); - Expect(source.Data()).ToEqual(nullptr); - Expect(target.Data()).ToNotEqual(target.GetInlineBuffer()); - Expect(target.Data()).ToEqual(sourceData); - }); + It("Can move dynamic to inline", []() + { + TArray source{}; // Not inline buffer + source.Add(3); + source.Add(4); + source.Add(5); + source.Add(6); + MoveType* sourceData = source.Data(); + TArray target; + target = Move(source); + Expect(source.Size()).ToEqual(0); + Expect(source.Capacity()).ToEqual(0); + Expect(target.Size()).ToEqual(4); + Expect(target.Capacity()).ToEqual(4); + + Expect(target[0].value).ToEqual(3); + Expect(target[3].value).ToEqual(6); + + Expect(source.Data()).ToEqual(nullptr); + Expect(target.Data()).ToNotEqual(target.GetInlineBuffer()); + Expect(target.Data()).ToEqual(sourceData); + }); - It("Can move inline to inline", []() - { - TArray source{}; // Not inline buffer - source.Add(3); - source.Add(4); - source.Add(5); - source.Add(6); - TArray source2{}; // Not inline buffer - source2.Add(3); - source2.Add(4); - source2.Add(5); - source2.Add(6); - TArray target; - target = Move(source); - Expect(source.Size()).ToEqual(0); - Expect(source.Capacity()).ToEqual(0); - Expect(target.Size()).ToEqual(4); - Expect(target.Capacity()).ToEqual(5); - Expect(target[0].value).ToEqual(3); - Expect(target[3].value).ToEqual(6); - Expect(source.Data()).ToEqual(nullptr); - Expect(target.Data()).ToEqual(target.GetInlineBuffer()); - TArray target2; // Copy to a different size - target2 = Move(source2); - Expect(source2.Data()).ToEqual(nullptr); - Expect(target2.Data()).ToEqual(target2.GetInlineBuffer()); - }); + It("Can move inline to dynamic", []() + { + TArray source{}; // Inline buffer + source.Add(3); + source.Add(4); + source.Add(5); + source.Add(6); + TArray target; + target = Move(source); + Expect(source.Size()).ToEqual(0); + Expect(source.Capacity()).ToEqual(0); + Expect(target.Size()).ToEqual(4); + Expect(target.Capacity()).ToBeGreaterOrEqual(4); + + Expect(target[0].value).ToEqual(3); + Expect(target[3].value).ToEqual(6); + + Expect(source.Data()).ToEqual(nullptr); + Expect(target.Data()).ToNotEqual(target.GetInlineBuffer()); + }); + }); - It("Can move dynamic to inline", []() - { - TArray source{}; // Not inline buffer - source.Add(3); - source.Add(4); - source.Add(5); - source.Add(6); - MoveType* sourceData = source.Data(); - TArray target; - target = Move(source); - Expect(source.Size()).ToEqual(0); - Expect(source.Capacity()).ToEqual(0); - Expect(target.Size()).ToEqual(4); - Expect(target.Capacity()).ToEqual(4); - - Expect(target[0].value).ToEqual(3); - Expect(target[3].value).ToEqual(6); - - Expect(source.Data()).ToEqual(nullptr); - Expect(target.Data()).ToNotEqual(target.GetInlineBuffer()); - Expect(target.Data()).ToEqual(sourceData); - }); + It("Can access data", []() + { + TArray data1; + TArray data2{1}; + TArray data3{1}; - It("Can move inline to dynamic", []() - { - TArray source{}; // Inline buffer - source.Add(3); - source.Add(4); - source.Add(5); - source.Add(6); - TArray target; - target = Move(source); - Expect(source.Size()).ToEqual(0); - Expect(source.Capacity()).ToEqual(0); - Expect(target.Size()).ToEqual(4); - Expect(target.Capacity()).ToBeGreaterOrEqual(4); - - Expect(target[0].value).ToEqual(3); - Expect(target[3].value).ToEqual(6); - - Expect(source.Data()).ToEqual(nullptr); - Expect(target.Data()).ToNotEqual(target.GetInlineBuffer()); - }); + Expect(data1.Data()).ToEqual(nullptr); + Expect(data2.Data()).ToNotEqual(nullptr); + Expect(data3.Data()).ToNotEqual(nullptr); + }); + + Describe("Add", []() + { + It("Can add to dynamic", []() + { + TArray data; + data.Reserve(2); // Reserve because we are not testing reallocation here + data.Add(3); + Expect(data.Size()).ToEqual(1); + Expect(data[0]).ToEqual(3); + data.Add(4); + Expect(data.Size()).ToEqual(2); + Expect(data[1]).ToEqual(4); }); - It("Can access data", []() + It("Can add to inline", []() { - TArray data1; - TArray data2{1}; - TArray data3{1}; + TArray data; + data.Add(3); + Expect(data.Size()).ToEqual(1); + Expect(data[0]).ToEqual(3); + data.Add(4); + Expect(data.Size()).ToEqual(2); + Expect(data[1]).ToEqual(4); + }); - Expect(data1.Data()).ToEqual(nullptr); - Expect(data2.Data()).ToNotEqual(nullptr); - Expect(data3.Data()).ToNotEqual(nullptr); + It("Can add to correct buffers", []() + { + TArray data; + data.Add(3); + data.Add(4); + Expect(data.Data()).ToEqual(data.GetInlineBuffer()); + data.Add(5); // Grow here to dynamic buffer + Expect(data.Size()).ToEqual(3); + Expect(data[0]).ToEqual(3); + Expect(data[1]).ToEqual(4); + Expect(data[2]).ToEqual(5); + Expect(data.Data()).ToNotEqual(data.GetInlineBuffer()); }); - Describe("Add", []() + It("Can add value by move", []() { - It("Can add to dynamic", []() - { - TArray data; - data.Reserve(2); // Reserve because we are not testing reallocation here - data.Add(3); - Expect(data.Size()).ToEqual(1); - Expect(data[0]).ToEqual(3); - data.Add(4); - Expect(data.Size()).ToEqual(2); - Expect(data[1]).ToEqual(4); - }); + TArray data; + MoveType tmp{2}; + data.Add(Move(tmp)); + data.Add(MoveType{3}); + Expect(data[0].value).ToEqual(2); + Expect(data[1].value).ToEqual(3); + Expect(tmp.value).ToEqual(0); + }); - It("Can add to inline", []() - { - TArray data; - data.Add(3); - Expect(data.Size()).ToEqual(1); - Expect(data[0]).ToEqual(3); - data.Add(4); - Expect(data.Size()).ToEqual(2); - Expect(data[1]).ToEqual(4); - }); + It("Can add value by copy", []() + { + TArray data; + i32 tmp = 2; + data.Add(tmp); + data.Add(3); + Expect(tmp).ToEqual(2); + Expect(data[0]).ToEqual(2); + Expect(data[1]).ToEqual(3); + }); - It("Can add to correct buffers", []() - { - TArray data; - data.Add(3); - data.Add(4); - Expect(data.Data()).ToEqual(data.GetInlineBuffer()); - data.Add(5); // Grow here to dynamic buffer - Expect(data.Size()).ToEqual(3); - Expect(data[0]).ToEqual(3); - Expect(data[1]).ToEqual(4); - Expect(data[2]).ToEqual(5); - Expect(data.Data()).ToNotEqual(data.GetInlineBuffer()); - }); + It("Can add defaulted", []() + { + TArray data; + data.Add(); + Expect(data[0]).ToEqual(0); + }); + }); - It("Can add value by move", []() - { - TArray data; - MoveType tmp{2}; - data.Add(Move(tmp)); - data.Add(MoveType{3}); - Expect(data[0].value).ToEqual(2); - Expect(data[1].value).ToEqual(3); - Expect(tmp.value).ToEqual(0); - }); + Describe("Append", []() + { + It("Can append defaulted", []() + { + TArray data; + data.Append(2); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(0); + Expect(data[1]).ToEqual(0); + data.Append(0); + Expect(data.Size()).ToEqual(2); + data.Append(2); + Expect(data.Size()).ToEqual(4); + Expect(data[2]).ToEqual(0); + Expect(data[3]).ToEqual(0); + }); - It("Can add value by copy", []() - { - TArray data; - i32 tmp = 2; - data.Add(tmp); - data.Add(3); - Expect(tmp).ToEqual(2); - Expect(data[0]).ToEqual(2); - Expect(data[1]).ToEqual(3); - }); + It("Can append value", []() + { + TArray data; + data.Append(2, 234); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(234); + Expect(data[1]).ToEqual(234); + data.Append(0, 234); + Expect(data.Size()).ToEqual(2); + data.Append(2, 235); + Expect(data.Size()).ToEqual(4); + Expect(data[2]).ToEqual(235); + Expect(data[3]).ToEqual(235); + }); - It("Can add defaulted", []() - { - TArray data; - data.Add(); - Expect(data[0]).ToEqual(0); - }); + It("Can assign multiple values", []() + { + TArray data; + i32 buffer[]{24, 53}; + i32 buffer2[]{74, 51}; + data.Append(buffer, 2); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(24); + Expect(data[1]).ToEqual(53); + data.Append(nullptr, 0); + Expect(data.Size()).ToEqual(2); + data.Append(buffer2, 2); + Expect(data.Size()).ToEqual(4); + Expect(data[2]).ToEqual(74); + Expect(data[3]).ToEqual(51); }); - Describe("Append", []() + It("Can append to dynamic", []() { - It("Can append defaulted", []() - { - TArray data; - data.Append(2); - Expect(data.Size()).ToEqual(2); - Expect(data[0]).ToEqual(0); - Expect(data[1]).ToEqual(0); - data.Append(0); - Expect(data.Size()).ToEqual(2); - data.Append(2); - Expect(data.Size()).ToEqual(4); - Expect(data[2]).ToEqual(0); - Expect(data[3]).ToEqual(0); - }); + TArray data; + data.Reserve(2); // Reserve because we are not testing reallocation here + data.Append(2, 33); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(33); + Expect(data[1]).ToEqual(33); + Expect(data.Data()).ToNotEqual(data.GetInlineBuffer()); + }); - It("Can append value", []() - { - TArray data; - data.Append(2, 234); - Expect(data.Size()).ToEqual(2); - Expect(data[0]).ToEqual(234); - Expect(data[1]).ToEqual(234); - data.Append(0, 234); - Expect(data.Size()).ToEqual(2); - data.Append(2, 235); - Expect(data.Size()).ToEqual(4); - Expect(data[2]).ToEqual(235); - Expect(data[3]).ToEqual(235); - }); + It("Can assign to inline", []() + { + TArray data; + data.Reserve(2); // Reserve because we are not testing reallocation here + data.Append(2, 33); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(33); + Expect(data[1]).ToEqual(33); + Expect(data.Data()).ToEqual(data.GetInlineBuffer()); + }); + }); - It("Can assign multiple values", []() - { - TArray data; - i32 buffer[]{24, 53}; - i32 buffer2[]{74, 51}; - data.Append(buffer, 2); - Expect(data.Size()).ToEqual(2); - Expect(data[0]).ToEqual(24); - Expect(data[1]).ToEqual(53); - data.Append(nullptr, 0); - Expect(data.Size()).ToEqual(2); - data.Append(buffer2, 2); - Expect(data.Size()).ToEqual(4); - Expect(data[2]).ToEqual(74); - Expect(data[3]).ToEqual(51); - }); + Describe("Assign", []() + { + It("Can assign defaulted", []() + { + TArray data; + data.Assign(2); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(0); + Expect(data[1]).ToEqual(0); + data.Assign(0); + Expect(data.Size()).ToEqual(0); + data.Assign(2); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(0); + Expect(data[1]).ToEqual(0); + }); - It("Can append to dynamic", []() - { - TArray data; - data.Reserve(2); // Reserve because we are not testing reallocation here - data.Append(2, 33); - Expect(data.Size()).ToEqual(2); - Expect(data[0]).ToEqual(33); - Expect(data[1]).ToEqual(33); - Expect(data.Data()).ToNotEqual(data.GetInlineBuffer()); - }); + It("Can assign value", []() + { + TArray data; + data.Assign(2, 234); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(234); + Expect(data[1]).ToEqual(234); + data.Assign(0, 234); + Expect(data.Size()).ToEqual(0); + data.Assign(2, 235); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(235); + Expect(data[1]).ToEqual(235); + }); - It("Can assign to inline", []() - { - TArray data; - data.Reserve(2); // Reserve because we are not testing reallocation here - data.Append(2, 33); - Expect(data.Size()).ToEqual(2); - Expect(data[0]).ToEqual(33); - Expect(data[1]).ToEqual(33); - Expect(data.Data()).ToEqual(data.GetInlineBuffer()); - }); + It("Can assign multiple values", []() + { + TArray data; + i32 buffer[]{24, 53}; + i32 buffer2[]{74, 51}; + data.Assign(buffer, 2); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(24); + Expect(data[1]).ToEqual(53); + data.Assign(nullptr, 0); + Expect(data.Size()).ToEqual(0); + data.Assign(buffer2, 2); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(74); + Expect(data[1]).ToEqual(51); }); - Describe("Assign", []() + It("Can assign to dynamic", []() { - It("Can assign defaulted", []() - { - TArray data; - data.Assign(2); - Expect(data.Size()).ToEqual(2); - Expect(data[0]).ToEqual(0); - Expect(data[1]).ToEqual(0); - data.Assign(0); - Expect(data.Size()).ToEqual(0); - data.Assign(2); - Expect(data.Size()).ToEqual(2); - Expect(data[0]).ToEqual(0); - Expect(data[1]).ToEqual(0); - }); + TArray data; + data.Reserve(2); // Reserve because we are not testing reallocation here + data.Assign(2, 33); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(33); + Expect(data[1]).ToEqual(33); + Expect(data.Data()).ToNotEqual(data.GetInlineBuffer()); + }); - It("Can assign value", []() - { - TArray data; - data.Assign(2, 234); - Expect(data.Size()).ToEqual(2); - Expect(data[0]).ToEqual(234); - Expect(data[1]).ToEqual(234); - data.Assign(0, 234); - Expect(data.Size()).ToEqual(0); - data.Assign(2, 235); - Expect(data.Size()).ToEqual(2); - Expect(data[0]).ToEqual(235); - Expect(data[1]).ToEqual(235); - }); + It("Can assign to inline", []() + { + TArray data; + data.Reserve(2); // Reserve because we are not testing reallocation here + data.Assign(2, 33); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(33); + Expect(data[1]).ToEqual(33); + Expect(data.Data()).ToEqual(data.GetInlineBuffer()); + }); + }); - It("Can assign multiple values", []() - { - TArray data; - i32 buffer[]{24, 53}; - i32 buffer2[]{74, 51}; - data.Assign(buffer, 2); - Expect(data.Size()).ToEqual(2); - Expect(data[0]).ToEqual(24); - Expect(data[1]).ToEqual(53); - data.Assign(nullptr, 0); - Expect(data.Size()).ToEqual(0); - data.Assign(buffer2, 2); - Expect(data.Size()).ToEqual(2); - Expect(data[0]).ToEqual(74); - Expect(data[1]).ToEqual(51); - }); + Describe("Insert", []() + { + It("Can insert at empty", []() + { + TArray data; + data.Insert(0, 12); + Expect(data.Size()).ToEqual(1); + Expect(data[0]).ToEqual(12); + + data.Insert(0, 21); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(21); + }); - It("Can assign to dynamic", []() - { - TArray data; - data.Reserve(2); // Reserve because we are not testing reallocation here - data.Assign(2, 33); - Expect(data.Size()).ToEqual(2); - Expect(data[0]).ToEqual(33); - Expect(data[1]).ToEqual(33); - Expect(data.Data()).ToNotEqual(data.GetInlineBuffer()); - }); + It("Can insert at end", []() + { + TArray data{12, 34}; + data.Insert(2, 12); + Expect(data.Size()).ToEqual(3); + Expect(data[2]).ToEqual(12); + }); - It("Can assign to inline", []() - { - TArray data; - data.Reserve(2); // Reserve because we are not testing reallocation here - data.Assign(2, 33); - Expect(data.Size()).ToEqual(2); - Expect(data[0]).ToEqual(33); - Expect(data[1]).ToEqual(33); - Expect(data.Data()).ToEqual(data.GetInlineBuffer()); - }); + It("Can insert to inline", []() + { + TArray data; + data.Insert(0, 12); + data.Insert(0, 21); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(21); }); - Describe("Insert", []() + It("Can insert copied value", []() { - It("Can insert at empty", []() - { - TArray data; - data.Insert(0, 12); - Expect(data.Size()).ToEqual(1); - Expect(data[0]).ToEqual(12); - - data.Insert(0, 21); - Expect(data.Size()).ToEqual(2); - Expect(data[0]).ToEqual(21); - }); + TArray data; + data.Insert(0, 32); // Insert at empty + Expect(data.Size()).ToEqual(1); + Expect(data[0]).ToEqual(32); + + data.Insert(0, 65); // Insert at start + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(65); + Expect(data[1]).ToEqual(32); + + data.Add(85); + data.Insert(1, 27); // Insert in the middle + Expect(data.Size()).ToEqual(4); + Expect(data[1]).ToEqual(27); - It("Can insert at end", []() - { - TArray data{12, 34}; - data.Insert(2, 12); - Expect(data.Size()).ToEqual(3); - Expect(data[2]).ToEqual(12); - }); + data.Insert(4, 43); // Insert in the end + Expect(data.Size()).ToEqual(5); + Expect(data[4]).ToEqual(43); + }); - It("Can insert to inline", []() - { - TArray data; - data.Insert(0, 12); - data.Insert(0, 21); - Expect(data.Size()).ToEqual(2); - Expect(data[0]).ToEqual(21); - }); + It("Can insert many values", []() + { + TArray data; + data.Insert(0, 2, 32); // Insert at empty + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(32); + Expect(data[1]).ToEqual(32); - It("Can insert copied value", []() - { - TArray data; - data.Insert(0, 32); // Insert at empty - Expect(data.Size()).ToEqual(1); - Expect(data[0]).ToEqual(32); - - data.Insert(0, 65); // Insert at start - Expect(data.Size()).ToEqual(2); - Expect(data[0]).ToEqual(65); - Expect(data[1]).ToEqual(32); - - data.Add(85); - data.Insert(1, 27); // Insert in the middle - Expect(data.Size()).ToEqual(4); - Expect(data[1]).ToEqual(27); - - data.Insert(4, 43); // Insert in the end - Expect(data.Size()).ToEqual(5); - Expect(data[4]).ToEqual(43); - }); + data.Insert(0, 2, 5); // Insert at start + Expect(data.Size()).ToEqual(4); + Expect(data[0]).ToEqual(5); + Expect(data[1]).ToEqual(5); + Expect(data[2]).ToEqual(32); + Expect(data[3]).ToEqual(32); - It("Can insert many values", []() - { - TArray data; - data.Insert(0, 2, 32); // Insert at empty - Expect(data.Size()).ToEqual(2); - Expect(data[0]).ToEqual(32); - Expect(data[1]).ToEqual(32); - - data.Insert(0, 2, 5); // Insert at start - Expect(data.Size()).ToEqual(4); - Expect(data[0]).ToEqual(5); - Expect(data[1]).ToEqual(5); - Expect(data[2]).ToEqual(32); - Expect(data[3]).ToEqual(32); - - data.Insert(3, 2, 6); // Insert in the middle - Expect(data.Size()).ToEqual(6); - Expect(data[3]).ToEqual(6); - Expect(data[4]).ToEqual(6); - - data.Insert(6, 2, 9); // Insert in the end - Expect(data.Size()).ToEqual(8); - Expect(data[6]).ToEqual(9); - Expect(data[7]).ToEqual(9); - }); + data.Insert(3, 2, 6); // Insert in the middle + Expect(data.Size()).ToEqual(6); + Expect(data[3]).ToEqual(6); + Expect(data[4]).ToEqual(6); - It("Can insert many values inline", []() - { - TArray data{1, 2}; - data.Insert(1, 3, 9); // More values than trailing elements - Expect(data.Size()).ToEqual(5); - Expect(data[0]).ToEqual(1); - Expect(data[1]).ToEqual(9); - Expect(data[2]).ToEqual(9); - Expect(data[3]).ToEqual(9); - Expect(data[4]).ToEqual(2); - - data.Insert(0, 2, 7); // Fewer values than trailing elements - Expect(data.Size()).ToEqual(7); - Expect(data[0]).ToEqual(7); - Expect(data[1]).ToEqual(7); - Expect(data[2]).ToEqual(1); - Expect(data[3]).ToEqual(9); - Expect(data[4]).ToEqual(9); - Expect(data[5]).ToEqual(9); - Expect(data[6]).ToEqual(2); - - data.Insert(5, 3, 4); // One more value than trailing elements - Expect(data.Size()).ToEqual(10); - Expect(data[5]).ToEqual(4); - Expect(data[6]).ToEqual(4); - Expect(data[7]).ToEqual(4); - Expect(data[8]).ToEqual(9); - Expect(data[9]).ToEqual(2); - - data.Insert(3, 8, 6); // One more value than trailing elements - Expect(data.Size()).ToEqual(18); - Expect(data[3]).ToEqual(6); - Expect(data[10]).ToEqual(6); - Expect(data[11]).ToEqual(9); - Expect(data[17]).ToEqual(2); - }); + data.Insert(6, 2, 9); // Insert in the end + Expect(data.Size()).ToEqual(8); + Expect(data[6]).ToEqual(9); + Expect(data[7]).ToEqual(9); + }); - It("Can insert buffer inline", []() - { - TArray data{1, 2, 3}; - i32 src[]{4, 5, 6, 7}; - data.Insert(1, src, 4); // More values than trailing elements - Expect(data.Size()).ToEqual(7); - Expect(data[0]).ToEqual(1); - Expect(data[1]).ToEqual(4); - Expect(data[4]).ToEqual(7); - Expect(data[5]).ToEqual(2); - Expect(data[6]).ToEqual(3); - }); + It("Can insert many values inline", []() + { + TArray data{1, 2}; + data.Insert(1, 3, 9); // More values than trailing elements + Expect(data.Size()).ToEqual(5); + Expect(data[0]).ToEqual(1); + Expect(data[1]).ToEqual(9); + Expect(data[2]).ToEqual(9); + Expect(data[3]).ToEqual(9); + Expect(data[4]).ToEqual(2); - It("Can insert many non trivial values inline", []() - { - TArray data; - data.Add(CopyType{1}); - data.Add(CopyType{2}); - data.Insert(1, 3, CopyType{9}); // More values than trailing elements - Expect(data.Size()).ToEqual(5); - Expect(data[0].value).ToEqual(1); - Expect(data[1].value).ToEqual(9); - Expect(data[2].value).ToEqual(9); - Expect(data[3].value).ToEqual(9); - Expect(data[4].value).ToEqual(2); - }); + data.Insert(0, 2, 7); // Fewer values than trailing elements + Expect(data.Size()).ToEqual(7); + Expect(data[0]).ToEqual(7); + Expect(data[1]).ToEqual(7); + Expect(data[2]).ToEqual(1); + Expect(data[3]).ToEqual(9); + Expect(data[4]).ToEqual(9); + Expect(data[5]).ToEqual(9); + Expect(data[6]).ToEqual(2); + + data.Insert(5, 3, 4); // One more value than trailing elements + Expect(data.Size()).ToEqual(10); + Expect(data[5]).ToEqual(4); + Expect(data[6]).ToEqual(4); + Expect(data[7]).ToEqual(4); + Expect(data[8]).ToEqual(9); + Expect(data[9]).ToEqual(2); + + data.Insert(3, 8, 6); // One more value than trailing elements + Expect(data.Size()).ToEqual(18); + Expect(data[3]).ToEqual(6); + Expect(data[10]).ToEqual(6); + Expect(data[11]).ToEqual(9); + Expect(data[17]).ToEqual(2); + }); - It("Can insert moved value", []() - { - TArray data; - MoveType tmp{34}; - data.Insert(0, Move(tmp)); // Insert at empty - Expect(data.Size()).ToEqual(1); - Expect(data[0].value).ToEqual(34); - Expect(tmp.value).ToEqual(0); - - MoveType tmp2{4}; - data.Insert(0, Move(tmp2)); // Insert at start - Expect(data.Size()).ToEqual(2); - Expect(data[0].value).ToEqual(4); - Expect(data[1].value).ToEqual(34); - Expect(tmp2.value).ToEqual(0); - - MoveType tmp3{3}; - data.Add(MoveType{85}); - data.Insert(1, Move(tmp3)); // Insert in the middle - Expect(data.Size()).ToEqual(4); - Expect(data[1].value).ToEqual(3); - Expect(tmp3.value).ToEqual(0); - - MoveType tmp4{7}; - data.Insert(4, Move(tmp4)); // Insert in the end - Expect(data.Size()).ToEqual(5); - Expect(data[4].value).ToEqual(7); - Expect(tmp4.value).ToEqual(0); - }); + It("Can insert buffer inline", []() + { + TArray data{1, 2, 3}; + i32 src[]{4, 5, 6, 7}; + data.Insert(1, src, 4); // More values than trailing elements + Expect(data.Size()).ToEqual(7); + Expect(data[0]).ToEqual(1); + Expect(data[1]).ToEqual(4); + Expect(data[4]).ToEqual(7); + Expect(data[5]).ToEqual(2); + Expect(data[6]).ToEqual(3); + }); - It("Can insert buffer", []() - { - TArray data; - i32 src[]{34, 23, 844}; - data.Insert(0, src, 3); // Insert at empty - Expect(data.Size()).ToEqual(3); - Expect(data[0]).ToEqual(34); - Expect(data[1]).ToEqual(23); - Expect(data[2]).ToEqual(844); - - i32 src2[]{2, 71, 21}; - data.Insert(0, src2, 3); // Insert at start - Expect(data.Size()).ToEqual(6); - Expect(data[0]).ToEqual(2); - Expect(data[1]).ToEqual(71); - Expect(data[2]).ToEqual(21); - Expect(data[3]).ToEqual(34); - Expect(data[4]).ToEqual(23); - Expect(data[5]).ToEqual(844); - - i32 src3[]{4, 3, 6}; - data.Insert(3, src3, 3); // Insert in the middle - Expect(data.Size()).ToEqual(9); - Expect(data[3]).ToEqual(4); - Expect(data[4]).ToEqual(3); - Expect(data[5]).ToEqual(6); - - i32 src4[]{7, 2, 3}; - data.Insert(9, src4, 3); // Insert in the end - Expect(data.Size()).ToEqual(12); - Expect(data[9]).ToEqual(7); - Expect(data[10]).ToEqual(2); - Expect(data[11]).ToEqual(3); - }); + It("Can insert many non trivial values inline", []() + { + TArray data; + data.Add(CopyType{1}); + data.Add(CopyType{2}); + data.Insert(1, 3, CopyType{9}); // More values than trailing elements + Expect(data.Size()).ToEqual(5); + Expect(data[0].value).ToEqual(1); + Expect(data[1].value).ToEqual(9); + Expect(data[2].value).ToEqual(9); + Expect(data[3].value).ToEqual(9); + Expect(data[4].value).ToEqual(2); }); - Describe("Remove", []() + It("Can insert moved value", []() { - It("Can remove at index", []() - { - TArray data{1, 2, 3, 4}; + TArray data; + MoveType tmp{34}; + data.Insert(0, Move(tmp)); // Insert at empty + Expect(data.Size()).ToEqual(1); + Expect(data[0].value).ToEqual(34); + Expect(tmp.value).ToEqual(0); + + MoveType tmp2{4}; + data.Insert(0, Move(tmp2)); // Insert at start + Expect(data.Size()).ToEqual(2); + Expect(data[0].value).ToEqual(4); + Expect(data[1].value).ToEqual(34); + Expect(tmp2.value).ToEqual(0); + + MoveType tmp3{3}; + data.Add(MoveType{85}); + data.Insert(1, Move(tmp3)); // Insert in the middle + Expect(data.Size()).ToEqual(4); + Expect(data[1].value).ToEqual(3); + Expect(tmp3.value).ToEqual(0); - // Check invalid inputs - Expect(data.RemoveAt(-1)).ToEqual(false); - Expect(data.RemoveAt(4)).ToEqual(false); + MoveType tmp4{7}; + data.Insert(4, Move(tmp4)); // Insert in the end + Expect(data.Size()).ToEqual(5); + Expect(data[4].value).ToEqual(7); + Expect(tmp4.value).ToEqual(0); + }); - Expect(data.RemoveAt(3)).ToEqual(true); // Remove last - Expect(data).ToEqual(TArray{1, 2, 3}); + It("Can insert buffer", []() + { + TArray data; + i32 src[]{34, 23, 844}; + data.Insert(0, src, 3); // Insert at empty + Expect(data.Size()).ToEqual(3); + Expect(data[0]).ToEqual(34); + Expect(data[1]).ToEqual(23); + Expect(data[2]).ToEqual(844); + + i32 src2[]{2, 71, 21}; + data.Insert(0, src2, 3); // Insert at start + Expect(data.Size()).ToEqual(6); + Expect(data[0]).ToEqual(2); + Expect(data[1]).ToEqual(71); + Expect(data[2]).ToEqual(21); + Expect(data[3]).ToEqual(34); + Expect(data[4]).ToEqual(23); + Expect(data[5]).ToEqual(844); + + i32 src3[]{4, 3, 6}; + data.Insert(3, src3, 3); // Insert in the middle + Expect(data.Size()).ToEqual(9); + Expect(data[3]).ToEqual(4); + Expect(data[4]).ToEqual(3); + Expect(data[5]).ToEqual(6); + + i32 src4[]{7, 2, 3}; + data.Insert(9, src4, 3); // Insert in the end + Expect(data.Size()).ToEqual(12); + Expect(data[9]).ToEqual(7); + Expect(data[10]).ToEqual(2); + Expect(data[11]).ToEqual(3); + }); + }); - Expect(data.RemoveAt(1)).ToEqual(true); // Remove in the middle - Expect(data).ToEqual(TArray{1, 3}); + Describe("Remove", []() + { + It("Can remove at index", []() + { + TArray data{1, 2, 3, 4}; - Expect(data.RemoveAt(0)).ToEqual(true); // remove first - Expect(data).ToEqual(TArray{3}); - }); + // Check invalid inputs + Expect(data.RemoveAt(-1)).ToEqual(false); + Expect(data.RemoveAt(4)).ToEqual(false); - It("Can remove many at index", []() - { - TArray data{1, 2, 3, 4, 5, 6, 7, 8}; + Expect(data.RemoveAt(3)).ToEqual(true); // Remove last + Expect(data).ToEqual(TArray{1, 2, 3}); - // Check invalid inputs - Expect(data.RemoveAt(-1, 2)).ToEqual(false); - Expect(data.RemoveAt(8, 2)).ToEqual(false); - Expect(data.RemoveAt(7, 2)).ToEqual(false); + Expect(data.RemoveAt(1)).ToEqual(true); // Remove in the middle + Expect(data).ToEqual(TArray{1, 3}); - Expect(data.RemoveAt(6, 2)).ToEqual(true); // Remove last - Expect(data).ToEqual(TArray{1, 2, 3, 4, 5, 6}); + Expect(data.RemoveAt(0)).ToEqual(true); // remove first + Expect(data).ToEqual(TArray{3}); + }); - Expect(data.RemoveAt(2, 2)).ToEqual(true); // Remove in the middle - Expect(data).ToEqual(TArray{1, 2, 5, 6}); + It("Can remove many at index", []() + { + TArray data{1, 2, 3, 4, 5, 6, 7, 8}; - Expect(data.RemoveAt(0, 2)).ToEqual(true); // Remove first - Expect(data).ToEqual(TArray{5, 6}); - }); + // Check invalid inputs + Expect(data.RemoveAt(-1, 2)).ToEqual(false); + Expect(data.RemoveAt(8, 2)).ToEqual(false); + Expect(data.RemoveAt(7, 2)).ToEqual(false); - It("Can remove swap at index", []() - { - TArray data{1, 2, 3, 4, 5}; + Expect(data.RemoveAt(6, 2)).ToEqual(true); // Remove last + Expect(data).ToEqual(TArray{1, 2, 3, 4, 5, 6}); - // Check invalid inputs - Expect(data.RemoveAtSwap(-1)).ToEqual(false); - Expect(data.RemoveAtSwap(5)).ToEqual(false); + Expect(data.RemoveAt(2, 2)).ToEqual(true); // Remove in the middle + Expect(data).ToEqual(TArray{1, 2, 5, 6}); - Expect(data.RemoveAtSwap(3)).ToEqual(true); // Remove last - Expect(data).ToEqual(TArray{1, 2, 3, 5}); + Expect(data.RemoveAt(0, 2)).ToEqual(true); // Remove first + Expect(data).ToEqual(TArray{5, 6}); + }); - Expect(data.RemoveAtSwap(1)).ToEqual(true); // Remove swapping - Expect(data).ToEqual(TArray{1, 5, 3}); + It("Can remove swap at index", []() + { + TArray data{1, 2, 3, 4, 5}; - Expect(data.RemoveAtSwap(0)).ToEqual(true); // Remove first - Expect(data).ToEqual(TArray{3, 5}); - }); + // Check invalid inputs + Expect(data.RemoveAtSwap(-1)).ToEqual(false); + Expect(data.RemoveAtSwap(5)).ToEqual(false); - It("Can remove swap many at index", []() - { - TArray data{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + Expect(data.RemoveAtSwap(3)).ToEqual(true); // Remove last + Expect(data).ToEqual(TArray{1, 2, 3, 5}); - // Check invalid inputs - Expect(data.RemoveAtSwap(-1, 2)).ToEqual(false); - Expect(data.RemoveAtSwap(10, 2)).ToEqual(false); - Expect(data.RemoveAtSwap(9, 2)).ToEqual(false); + Expect(data.RemoveAtSwap(1)).ToEqual(true); // Remove swapping + Expect(data).ToEqual(TArray{1, 5, 3}); - Expect(data.RemoveAtSwap(8, 2)).ToEqual(true); // Remove last - Expect(data).ToEqual(TArray{1, 2, 3, 4, 5, 6, 7, 8}); + Expect(data.RemoveAtSwap(0)).ToEqual(true); // Remove first + Expect(data).ToEqual(TArray{3, 5}); + }); - Expect(data.RemoveAtSwap(1, 2)).ToEqual(true); // Removes swapping - Expect(data).ToEqual(TArray{1, 7, 8, 4, 5, 6}); + It("Can remove swap many at index", []() + { + TArray data{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; - Expect(data.RemoveAtSwap(1, 3)).ToEqual(true); // Removes swapping with less left - Expect(data).ToEqual(TArray{1, 5, 6}); + // Check invalid inputs + Expect(data.RemoveAtSwap(-1, 2)).ToEqual(false); + Expect(data.RemoveAtSwap(10, 2)).ToEqual(false); + Expect(data.RemoveAtSwap(9, 2)).ToEqual(false); - Expect(data.RemoveAtSwap(0, 2)).ToEqual(true); // Remove first - Expect(data).ToEqual(TArray{6}); - }); + Expect(data.RemoveAtSwap(8, 2)).ToEqual(true); // Remove last + Expect(data).ToEqual(TArray{1, 2, 3, 4, 5, 6, 7, 8}); - It("Can RemoveLast", []() - { - TArray data{1, 4, 6}; - data.RemoveLast(); - Expect(data.Size()).ToEqual(2); - Expect(data[0]).ToEqual(1); - Expect(data[1]).ToEqual(4); - Expect(data.Capacity()).ToEqual(2); - }); + Expect(data.RemoveAtSwap(1, 2)).ToEqual(true); // Removes swapping + Expect(data).ToEqual(TArray{1, 7, 8, 4, 5, 6}); - It("Can RemoveLast N", []() - { - TArray dataA{1, 4, 6}; - dataA.RemoveLast(2); - Expect(dataA.Size()).ToEqual(1); - Expect(dataA[0]).ToEqual(1); - Expect(dataA.Capacity()).ToEqual(1); - - TArray dataB{1, 4, 6}; - dataB.RemoveLast(3); - Expect(dataB.Size()).ToEqual(0); - Expect(dataB.Capacity()).ToEqual(0); - }); + Expect(data.RemoveAtSwap(1, 3)).ToEqual(true); // Removes swapping with less left + Expect(data).ToEqual(TArray{1, 5, 6}); - It("Can RemoveIf", []() - { - TArray data{1, 4, 5, 6}; + Expect(data.RemoveAtSwap(0, 2)).ToEqual(true); // Remove first + Expect(data).ToEqual(TArray{6}); + }); - Expect(data.Size()).ToEqual(4); + It("Can RemoveLast", []() + { + TArray data{1, 4, 6}; + data.RemoveLast(); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(1); + Expect(data[1]).ToEqual(4); + Expect(data.Capacity()).ToEqual(2); + }); - data.RemoveIf([](i32 v) - { - return v == 1 || v == 6; - }); - Expect(data.Size()).ToEqual(2); - Expect(data[0]).ToEqual(4); - Expect(data[1]).ToEqual(5); - }); + It("Can RemoveLast N", []() + { + TArray dataA{1, 4, 6}; + dataA.RemoveLast(2); + Expect(dataA.Size()).ToEqual(1); + Expect(dataA[0]).ToEqual(1); + Expect(dataA.Capacity()).ToEqual(1); + + TArray dataB{1, 4, 6}; + dataB.RemoveLast(3); + Expect(dataB.Size()).ToEqual(0); + Expect(dataB.Capacity()).ToEqual(0); + }); - It("Can RemoveIfSwap", []() - { - TArray data{1, 4, 5, 6}; + It("Can RemoveIf", []() + { + TArray data{1, 4, 5, 6}; - Expect(data.Size()).ToEqual(4); + Expect(data.Size()).ToEqual(4); - data.RemoveIfSwap([](i32 v) - { - return v == 1 || v == 6; - }); - Expect(data.Size()).ToEqual(2); - Expect(data[0]).ToEqual(5); - Expect(data[1]).ToEqual(4); + data.RemoveIf([](i32 v) + { + return v == 1 || v == 6; }); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(4); + Expect(data[1]).ToEqual(5); }); - It("Can Sort", []() - { - TArray data0{34, 1, 5}; - data0.Sort(); // Default sort is less - Expect(data0[0]).ToEqual(1); - Expect(data0[1]).ToEqual(5); - Expect(data0[2]).ToEqual(34); - - TArray data1{34, 1, 5}; - data1.Sort(TGreater{}); - Expect(data1[0]).ToEqual(34); - Expect(data1[1]).ToEqual(5); - Expect(data1[2]).ToEqual(1); - }); - - It("Can find in AddUniqueSorted", []() + It("Can RemoveIfSwap", []() { - TArray data{1, 5, 5, 34}; + TArray data{1, 4, 5, 6}; - Expect(data.AddUniqueSorted(1)).ToEqual(0); - Expect(data.AddUniqueSorted(5)).ToEqual(1); - Expect(data.AddUniqueSorted(34)).ToEqual(3); Expect(data.Size()).ToEqual(4); + + data.RemoveIfSwap([](i32 v) + { + return v == 1 || v == 6; + }); + Expect(data.Size()).ToEqual(2); + Expect(data[0]).ToEqual(5); + Expect(data[1]).ToEqual(4); }); + }); - It("Can add in AddUniqueSorted", []() - { - TArray data{1, 5, 5, 34}; + It("Can Sort", []() + { + TArray data0{34, 1, 5}; + data0.Sort(); // Default sort is less + Expect(data0[0]).ToEqual(1); + Expect(data0[1]).ToEqual(5); + Expect(data0[2]).ToEqual(34); + + TArray data1{34, 1, 5}; + data1.Sort(TGreater{}); + Expect(data1[0]).ToEqual(34); + Expect(data1[1]).ToEqual(5); + Expect(data1[2]).ToEqual(1); + }); - Expect(data.AddUniqueSorted(2)).ToEqual(1); - Expect(data.Size()).ToEqual(5); + It("Can find in AddUniqueSorted", []() + { + TArray data{1, 5, 5, 34}; - Expect(data.AddUniqueSorted(6)).ToEqual(4); - Expect(data.Size()).ToEqual(6); + Expect(data.AddUniqueSorted(1)).ToEqual(0); + Expect(data.AddUniqueSorted(5)).ToEqual(1); + Expect(data.AddUniqueSorted(34)).ToEqual(3); + Expect(data.Size()).ToEqual(4); + }); - Expect(data.AddUniqueSorted(36)).ToEqual(6); - Expect(data.Size()).ToEqual(7); - }); + It("Can add in AddUniqueSorted", []() + { + TArray data{1, 5, 5, 34}; - It("Can slice", []() - { - TArray data{1, 2, 3, 4, 5}; + Expect(data.AddUniqueSorted(2)).ToEqual(1); + Expect(data.Size()).ToEqual(5); - auto mid = data.Slice(1, 2); // Elements 1 to 3 - Expect(mid.Size()).ToEqual(2); - Expect(mid[0]).ToEqual(2); - Expect(mid[1]).ToEqual(3); + Expect(data.AddUniqueSorted(6)).ToEqual(4); + Expect(data.Size()).ToEqual(6); - auto tail = data.Slice(3, 100); // Clamped to available elements - Expect(tail.Size()).ToEqual(2); - Expect(tail[0]).ToEqual(4); - Expect(tail[1]).ToEqual(5); + Expect(data.AddUniqueSorted(36)).ToEqual(6); + Expect(data.Size()).ToEqual(7); + }); + + It("Can slice", []() + { + TArray data{1, 2, 3, 4, 5}; - auto none = data.Slice(2, 0); // Zero length - Expect(none.IsEmpty()).ToBeTrue(); + auto mid = data.Slice(1, 2); // Elements 1 to 3 + Expect(mid.Size()).ToEqual(2); + Expect(mid[0]).ToEqual(2); + Expect(mid[1]).ToEqual(3); - auto end = data.Slice(5, 2); // Offset clamped to size - Expect(end.IsEmpty()).ToBeTrue(); - }); + auto tail = data.Slice(3, 100); // Clamped to available elements + Expect(tail.Size()).ToEqual(2); + Expect(tail[0]).ToEqual(4); + Expect(tail[1]).ToEqual(5); - It("Can slice views", []() + auto none = data.Slice(2, 0); // Zero length + Expect(none.IsEmpty()).ToBeTrue(); + + auto end = data.Slice(5, 2); // Offset clamped to size + Expect(end.IsEmpty()).ToBeTrue(); + }); + + It("Can slice views", []() + { + TArray data{1, 2, 3, 4, 5}; + TView view = data; + + auto mid = view.Slice(2, 2); // Elements 2 to 4 + Expect(mid.Size()).ToEqual(2); + Expect(mid[0]).ToEqual(3); + Expect(mid[1]).ToEqual(4); + + auto head = view.Slice(0, 3); + Expect(head.Size()).ToEqual(3); + Expect(head[0]).ToEqual(1); + Expect(head[2]).ToEqual(3); + }); + + Describe("Iterate", []() + { + It("Can iterate empty", []() { - TArray data{1, 2, 3, 4, 5}; - TView view = data; - - auto mid = view.Slice(2, 2); // Elements 2 to 4 - Expect(mid.Size()).ToEqual(2); - Expect(mid[0]).ToEqual(3); - Expect(mid[1]).ToEqual(4); - - auto head = view.Slice(0, 3); - Expect(head.Size()).ToEqual(3); - Expect(head[0]).ToEqual(1); - Expect(head[2]).ToEqual(3); + TArray data1{}; + i32 counter = 0; + for (i32 v : data1) + { + ++counter; + } + + Expect(counter).ToEqual(0); + TArray data2{}; // Without inline capacity + counter = 0; + for (i32 v : data2) + { + ++counter; + } + Expect(counter).ToEqual(0); }); - Describe("Iterate", []() + It("Can iterate non empty", []() { - It("Can iterate empty", []() + TArray data1{1, 3, 4}; + const i32 mirror[]{1, 3, 4}; + i32 counter = 0; + for (i32 v : data1) { - TArray data1{}; - i32 counter = 0; - for (i32 v : data1) - { - ++counter; - } - - Expect(counter).ToEqual(0); - TArray data2{}; // Without inline capacity - counter = 0; - for (i32 v : data2) - { - ++counter; - } - Expect(counter).ToEqual(0); - }); - - It("Can iterate non empty", []() + Expect(v).ToEqual(mirror[counter]); + ++counter; + } + Expect(counter).ToEqual(3); + + TArray data2{1, 3, 4}; // Without inline capacity + counter = 0; + for (i32 v : data2) { - TArray data1{1, 3, 4}; - const i32 mirror[]{1, 3, 4}; - i32 counter = 0; - for (i32 v : data1) - { - Expect(v).ToEqual(mirror[counter]); - ++counter; - } - Expect(counter).ToEqual(3); - - TArray data2{1, 3, 4}; // Without inline capacity - counter = 0; - for (i32 v : data2) - { - Expect(v).ToEqual(mirror[counter]); - ++counter; - } - Expect(counter).ToEqual(3); - }); + Expect(v).ToEqual(mirror[counter]); + ++counter; + } + Expect(counter).ToEqual(3); }); }); +}); + +Describe("Containers.BitArray", []() +{ + It("Can initialize", []() + { + BitArray data1{}; + BitArray data2(3); + BitArray data3(3, true); + BitArray data4(91, true); + BitArray data5{false, true, false, true, false, true}; + + Expect(data1.Size()).ToEqual(0); + Expect(data1.Capacity()).ToEqual(0); + Expect(data2.Size()).ToEqual(3); + Expect(data2.Capacity()).ToEqual(32); + Expect(data3.Size()).ToEqual(3); + Expect(data3.Capacity()).ToEqual(32); + Expect(data4.Size()).ToEqual(91); + Expect(data4.Capacity()).ToEqual(96); + Expect(data5.Size()).ToEqual(6); + Expect(data5.Capacity()).ToEqual(32); + + Expect(data2[0]).ToEqual(false); + Expect(data2[2]).ToEqual(false); + Expect(data3[0]).ToEqual(true); + Expect(data3[2]).ToEqual(true); + Expect(data4[0]).ToEqual(true); + Expect(data4[90]).ToEqual(true); + Expect(data5[0]).ToEqual(false); + Expect(data5[1]).ToEqual(true); + Expect(data5[2]).ToEqual(false); + Expect(data5[3]).ToEqual(true); + Expect(data5[4]).ToEqual(false); + Expect(data5[5]).ToEqual(true); + }); - Describe("Containers.BitArray", []() + Describe("Copy", []() { - It("Can initialize", []() + It("Can copy empty", []() { - BitArray data1{}; - BitArray data2(3); - BitArray data3(3, true); - BitArray data4(91, true); - BitArray data5{false, true, false, true, false, true}; - - Expect(data1.Size()).ToEqual(0); - Expect(data1.Capacity()).ToEqual(0); - Expect(data2.Size()).ToEqual(3); - Expect(data2.Capacity()).ToEqual(32); - Expect(data3.Size()).ToEqual(3); - Expect(data3.Capacity()).ToEqual(32); - Expect(data4.Size()).ToEqual(91); - Expect(data4.Capacity()).ToEqual(96); - Expect(data5.Size()).ToEqual(6); - Expect(data5.Capacity()).ToEqual(32); - - Expect(data2[0]).ToEqual(false); - Expect(data2[2]).ToEqual(false); - Expect(data3[0]).ToEqual(true); - Expect(data3[2]).ToEqual(true); - Expect(data4[0]).ToEqual(true); - Expect(data4[90]).ToEqual(true); - Expect(data5[0]).ToEqual(false); - Expect(data5[1]).ToEqual(true); - Expect(data5[2]).ToEqual(false); - Expect(data5[3]).ToEqual(true); - Expect(data5[4]).ToEqual(false); - Expect(data5[5]).ToEqual(true); + BitArray source1{}; + BitArray target1 = source1; // NOLINT + Expect(target1.Data()).ToEqual(nullptr); + Expect(target1.Size()).ToEqual(0); + Expect(target1.Capacity()).ToEqual(0); + BitArray source2{}; + BitArray target2 = source2; // NOLINT + Expect(target2.Data()).ToEqual(nullptr); + Expect(target2.Size()).ToEqual(0); + Expect(target2.Capacity()).ToEqual(0); }); - Describe("Copy", []() + It("Can copy", []() { - It("Can copy empty", []() - { - BitArray source1{}; - BitArray target1 = source1; // NOLINT - Expect(target1.Data()).ToEqual(nullptr); - Expect(target1.Size()).ToEqual(0); - Expect(target1.Capacity()).ToEqual(0); - BitArray source2{}; - BitArray target2 = source2; // NOLINT - Expect(target2.Data()).ToEqual(nullptr); - Expect(target2.Size()).ToEqual(0); - Expect(target2.Capacity()).ToEqual(0); - }); - - It("Can copy", []() - { - BitArray source{false, true, false, true, false, true}; - BitArray target = source; - Expect(source.Size()).ToEqual(6); - Expect(source.Capacity()).ToBeGreaterOrEqual(6); - Expect(target.Size()).ToEqual(6); - Expect(target.Capacity()).ToBeGreaterOrEqual(6); - Expect(target[1]).ToEqual(true); - Expect(target[2]).ToEqual(false); - Expect(target[3]).ToEqual(true); - Expect(source.Data()).ToNotEqual(nullptr); - Expect(target.Data()).ToNotEqual(nullptr); - }); + BitArray source{false, true, false, true, false, true}; + BitArray target = source; + Expect(source.Size()).ToEqual(6); + Expect(source.Capacity()).ToBeGreaterOrEqual(6); + Expect(target.Size()).ToEqual(6); + Expect(target.Capacity()).ToBeGreaterOrEqual(6); + Expect(target[1]).ToEqual(true); + Expect(target[2]).ToEqual(false); + Expect(target[3]).ToEqual(true); + Expect(source.Data()).ToNotEqual(nullptr); + Expect(target.Data()).ToNotEqual(nullptr); }); + }); - Describe("Move", []() + Describe("Move", []() + { + It("Can move empty", []() { - It("Can move empty", []() - { - BitArray source1{}; - BitArray target1 = Move(source1); - Expect(target1.Data()).ToEqual(nullptr); - Expect(target1.Size()).ToEqual(0); - Expect(target1.Capacity()).ToEqual(0); - BitArray source2{}; - BitArray target2 = Move(source2); - Expect(target2.Data()).ToEqual(nullptr); - Expect(target2.Size()).ToEqual(0); - Expect(target2.Capacity()).ToEqual(0); - }); + BitArray source1{}; + BitArray target1 = Move(source1); + Expect(target1.Data()).ToEqual(nullptr); + Expect(target1.Size()).ToEqual(0); + Expect(target1.Capacity()).ToEqual(0); + BitArray source2{}; + BitArray target2 = Move(source2); + Expect(target2.Data()).ToEqual(nullptr); + Expect(target2.Size()).ToEqual(0); + Expect(target2.Capacity()).ToEqual(0); + }); - It("Can move", []() - { - BitArray source{false, true, false, true, false, true}; - u32* sourceData = source.Data(); - BitArray target = Move(source); - Expect(source.Size()).ToEqual(0); - Expect(source.Capacity()).ToEqual(0); - Expect(target.Size()).ToEqual(6); - Expect(target.Capacity()).ToBeGreaterOrEqual(6); - Expect(target[1]).ToEqual(true); - Expect(target[2]).ToEqual(false); - Expect(target[3]).ToEqual(true); - Expect(source.Data()).ToEqual(nullptr); - Expect(target.Data()).ToEqual(sourceData); - }); + It("Can move", []() + { + BitArray source{false, true, false, true, false, true}; + u32* sourceData = source.Data(); + BitArray target = Move(source); + Expect(source.Size()).ToEqual(0); + Expect(source.Capacity()).ToEqual(0); + Expect(target.Size()).ToEqual(6); + Expect(target.Capacity()).ToBeGreaterOrEqual(6); + Expect(target[1]).ToEqual(true); + Expect(target[2]).ToEqual(false); + Expect(target[3]).ToEqual(true); + Expect(source.Data()).ToEqual(nullptr); + Expect(target.Data()).ToEqual(sourceData); + }); - It("Can bitwise operate", []() - { - BitArray a{true, true, false, false}; - BitArray b{true, false, true, false}; - - const BitArray anded = a & b; - const BitArray ored = a | b; - const BitArray xored = a ^ b; - const BitArray negged = ~a; - - // a & b: only bit 0 is set in both - Expect(anded.IsSet(0)).ToBeTrue(); - Expect(anded.IsSet(1)).ToBeFalse(); - Expect(anded.IsSet(2)).ToBeFalse(); - Expect(anded.IsSet(3)).ToBeFalse(); - - // a | b: all bits set - Expect(ored.IsSet(0)).ToBeTrue(); - Expect(ored.IsSet(1)).ToBeTrue(); - Expect(ored.IsSet(2)).ToBeTrue(); - Expect(ored.IsSet(3)).ToBeFalse(); - - // a ^ b: bits 1 and 2 differ - Expect(xored.IsSet(0)).ToBeFalse(); - Expect(xored.IsSet(1)).ToBeTrue(); - Expect(xored.IsSet(2)).ToBeTrue(); - Expect(xored.IsSet(3)).ToBeFalse(); - - // ~a: all bits flipped - Expect(negged.IsSet(0)).ToBeFalse(); - Expect(negged.IsSet(1)).ToBeFalse(); - Expect(negged.IsSet(2)).ToBeTrue(); - Expect(negged.IsSet(3)).ToBeTrue(); - - // Compound operations - BitArray compound = a; - compound &= b; - Expect(compound.IsSet(0)).ToBeTrue(); - Expect(compound.IsSet(1)).ToBeFalse(); - compound |= b; - Expect(compound.IsSet(2)).ToBeTrue(); - compound ^= b; - Expect(compound.IsSet(0)).ToBeFalse(); - Expect(compound.IsSet(2)).ToBeFalse(); - }); + It("Can bitwise operate", []() + { + BitArray a{true, true, false, false}; + BitArray b{true, false, true, false}; + + const BitArray anded = a & b; + const BitArray ored = a | b; + const BitArray xored = a ^ b; + const BitArray negged = ~a; + + // a & b: only bit 0 is set in both + Expect(anded.IsSet(0)).ToBeTrue(); + Expect(anded.IsSet(1)).ToBeFalse(); + Expect(anded.IsSet(2)).ToBeFalse(); + Expect(anded.IsSet(3)).ToBeFalse(); + + // a | b: all bits set + Expect(ored.IsSet(0)).ToBeTrue(); + Expect(ored.IsSet(1)).ToBeTrue(); + Expect(ored.IsSet(2)).ToBeTrue(); + Expect(ored.IsSet(3)).ToBeFalse(); + + // a ^ b: bits 1 and 2 differ + Expect(xored.IsSet(0)).ToBeFalse(); + Expect(xored.IsSet(1)).ToBeTrue(); + Expect(xored.IsSet(2)).ToBeTrue(); + Expect(xored.IsSet(3)).ToBeFalse(); + + // ~a: all bits flipped + Expect(negged.IsSet(0)).ToBeFalse(); + Expect(negged.IsSet(1)).ToBeFalse(); + Expect(negged.IsSet(2)).ToBeTrue(); + Expect(negged.IsSet(3)).ToBeTrue(); + + // Compound operations + BitArray compound = a; + compound &= b; + Expect(compound.IsSet(0)).ToBeTrue(); + Expect(compound.IsSet(1)).ToBeFalse(); + compound |= b; + Expect(compound.IsSet(2)).ToBeTrue(); + compound ^= b; + Expect(compound.IsSet(0)).ToBeFalse(); + Expect(compound.IsSet(2)).ToBeFalse(); + }); - It("Can bitwise operate with different sizes", []() - { - BitArray small{false}; - BitArray big{true, true, true}; - - const BitArray anded = big & small; - Expect(anded.Size()).ToEqual(1); - Expect(anded.IsSet(0)).ToBeFalse(); - - const BitArray ored = big | small; - Expect(ored.Size()).ToEqual(1); // Sized to the smallest operand - Expect(ored.IsSet(0)).ToBeTrue(); - - // Only whole words are operated on. Bits past the smallest word count - // keep their value. Bits within a cleared word are cleared with it. - BitArray large{false}; - large.Resize(40, true); - large &= small; // small has a single (zeroed) word - Expect(large.Size()).ToEqual(40); - Expect(large.IsSet(0)).ToBeFalse(); - Expect(large.IsSet(31)).ToBeFalse(); // Same word as bit 0 - Expect(large.IsSet(32)).ToBeTrue(); // Next word, unaffected - Expect(large.IsSet(39)).ToBeTrue(); - }); + It("Can bitwise operate with different sizes", []() + { + BitArray small{false}; + BitArray big{true, true, true}; + + const BitArray anded = big & small; + Expect(anded.Size()).ToEqual(1); + Expect(anded.IsSet(0)).ToBeFalse(); + + const BitArray ored = big | small; + Expect(ored.Size()).ToEqual(1); // Sized to the smallest operand + Expect(ored.IsSet(0)).ToBeTrue(); + + // Only whole words are operated on. Bits past the smallest word count + // keep their value. Bits within a cleared word are cleared with it. + BitArray large{false}; + large.Resize(40, true); + large &= small; // small has a single (zeroed) word + Expect(large.Size()).ToEqual(40); + Expect(large.IsSet(0)).ToBeFalse(); + Expect(large.IsSet(31)).ToBeFalse(); // Same word as bit 0 + Expect(large.IsSet(32)).ToBeTrue(); // Next word, unaffected + Expect(large.IsSet(39)).ToBeTrue(); }); }); -} +}); +return true; +}(); +} // namespace diff --git a/Tests/Core/Function.spec.cpp b/Tests/Core/Function.spec.cpp index 9edb5fa3..3fde7c4d 100644 --- a/Tests/Core/Function.spec.cpp +++ b/Tests/Core/Function.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include #include @@ -28,65 +28,70 @@ struct Foo inline bool Foo::called = false; -void RegisterCoreFunctionTests() +namespace { - Spec("Core.Function", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("Core.Function", []() +{ + It("Can create empty", []() { - It("Can create empty", []() - { - TFunction func{}; - Expect(func.IsBound()).ToEqual(false); - Expect(bool(func)).ToEqual(false); - }); + TFunction func{}; + Expect(func.IsBound()).ToEqual(false); + Expect(bool(func)).ToEqual(false); + }); - It("Can create from function", []() - { - TFunction func{Foo::StaticFunc}; + It("Can create from function", []() + { + TFunction func{Foo::StaticFunc}; - Expect(func.IsBound()).ToEqual(true); - }); + Expect(func.IsBound()).ToEqual(true); + }); - It("Can compare functions", []() - { - TFunction func1{Foo::StaticFunc}; - TFunction func2{Foo::StaticFunc}; - TFunction func3{&Foo::StaticFunc}; + It("Can compare functions", []() + { + TFunction func1{Foo::StaticFunc}; + TFunction func2{Foo::StaticFunc}; + TFunction func3{&Foo::StaticFunc}; - TFunction func4{}; + TFunction func4{}; - TFunction func5{Foo::OtherStaticFunc}; + TFunction func5{Foo::OtherStaticFunc}; - Expect(func1 == func2).ToEqual(true); - Expect(func1 == func3).ToEqual(true); - Expect(func1 == func4).ToEqual(false); - // Expect(func1 == func5).ToEqual(false); - }); + Expect(func1 == func2).ToEqual(true); + Expect(func1 == func3).ToEqual(true); + Expect(func1 == func4).ToEqual(false); + // Expect(func1 == func5).ToEqual(false); + }); - It("Can call static functions", []() - { - TFunction func1{Foo::StaticFunc}; - TFunction func2{&Foo::StaticFunc}; + It("Can call static functions", []() + { + TFunction func1{Foo::StaticFunc}; + TFunction func2{&Foo::StaticFunc}; - Foo::called = false; - func1(); - Expect(Foo::called).ToEqual(true); + Foo::called = false; + func1(); + Expect(Foo::called).ToEqual(true); - Foo::called = false; - func2(); - Expect(Foo::called).ToEqual(true); - }); + Foo::called = false; + func2(); + Expect(Foo::called).ToEqual(true); + }); + + It("Can call lambda functions", []() + { + static bool called; + called = false; - It("Can call lambda functions", []() + TFunction func = []() { - static bool called; - called = false; - - TFunction func = []() - { - called = true; - }; - func(); - Expect(called).ToEqual(true); - }); + called = true; + }; + func(); + Expect(called).ToEqual(true); }); -} +}); +return true; +}(); +} // namespace diff --git a/Tests/Core/OwnPtr.spec.cpp b/Tests/Core/OwnPtr.spec.cpp index 8fb2e6c1..1b24e7e4 100644 --- a/Tests/Core/OwnPtr.spec.cpp +++ b/Tests/Core/OwnPtr.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include @@ -41,305 +41,310 @@ struct MockStruct }; -void RegisterCoreOwnPtrTests() +namespace { - Spec("Core.OwnPtr", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("Core.OwnPtr", []() +{ + Describe("Owner pointer", []() { - Describe("Owner pointer", []() + It("Can initialize to empty", []() { - It("Can initialize to empty", []() - { - TOwnPtr ptr; - Expect(ptr.IsValid()).ToEqual(false); - Expect(ptr.Get()).ToEqual(nullptr); - }); + TOwnPtr ptr; + Expect(ptr.IsValid()).ToEqual(false); + Expect(ptr.Get()).ToEqual(nullptr); + }); - It("Can instantiate", []() - { - TOwnPtr ptr = MakeOwned(); - Expect(ptr.IsValid()).ToEqual(true); - Expect(ptr.Get()).ToNotEqual(nullptr); - }); + It("Can instantiate", []() + { + TOwnPtr ptr = MakeOwned(); + Expect(ptr.IsValid()).ToEqual(true); + Expect(ptr.Get()).ToNotEqual(nullptr); + }); + + It("Owner can release", []() + { + TOwnPtr owner = MakeOwned(); + Expect(owner.IsValid()).ToEqual(true); + + owner.Delete(); + Expect(owner.IsValid()).ToEqual(false); + }); - It("Owner can release", []() + It("Owner is released when destroyed", []() + { + TPtr ptr; { TOwnPtr owner = MakeOwned(); - Expect(owner.IsValid()).ToEqual(true); - owner.Delete(); - Expect(owner.IsValid()).ToEqual(false); - }); + ptr = owner; + Expect(ptr.IsValid()).ToEqual(true); + } + Expect(ptr.IsValid()).ToEqual(false); + }); - It("Owner is released when destroyed", []() + Describe("Ptr Builder", []() + { + It("Calls custom new", []() { - TPtr ptr; - { - TOwnPtr owner = MakeOwned(); - - ptr = owner; - Expect(ptr.IsValid()).ToEqual(true); - } - Expect(ptr.IsValid()).ToEqual(false); + auto owner = MakeOwned(); + Expect(owner->bCalledNew).ToEqual(true); }); - Describe("Ptr Builder", []() + It("Calls custom delete", []() { - It("Calls custom new", []() - { - auto owner = MakeOwned(); - Expect(owner->bCalledNew).ToEqual(true); - }); - - It("Calls custom delete", []() - { - MockStruct::bCalledDelete = false; - auto owner = MakeOwned(); - Expect(MockStruct::bCalledDelete).ToEqual(false); - owner.Delete(); - Expect(MockStruct::bCalledDelete).ToEqual(true); - }); + MockStruct::bCalledDelete = false; + auto owner = MakeOwned(); + Expect(MockStruct::bCalledDelete).ToEqual(false); + owner.Delete(); + Expect(MockStruct::bCalledDelete).ToEqual(true); }); }); + }); - Describe("Weak pointer", []() + Describe("Weak pointer", []() + { + It("Can initialize to empty", []() { - It("Can initialize to empty", []() - { - TPtr ptr; - Expect(ptr.IsValid()).ToEqual(false); - Expect(ptr.Get()).ToEqual(nullptr); - }); + TPtr ptr; + Expect(ptr.IsValid()).ToEqual(false); + Expect(ptr.Get()).ToEqual(nullptr); + }); - It("Can initialize from owner", []() - { - TOwnPtr owner = MakeOwned(); - TPtr ptr = owner; + It("Can initialize from owner", []() + { + TOwnPtr owner = MakeOwned(); + TPtr ptr = owner; - Expect(ptr.IsValid()).ToEqual(true); - Expect(ptr.Get()).ToNotEqual(nullptr); - }); + Expect(ptr.IsValid()).ToEqual(true); + Expect(ptr.Get()).ToNotEqual(nullptr); + }); - It("Can copy from other weak", []() - { - TOwnPtr owner = MakeOwned(); - auto* raw = owner.Get(); - TPtr ptr = owner; - TPtr ptr2 = ptr; + It("Can copy from other weak", []() + { + TOwnPtr owner = MakeOwned(); + auto* raw = owner.Get(); + TPtr ptr = owner; + TPtr ptr2 = ptr; + + Expect(ptr2.IsValid()).ToEqual(true); + Expect(ptr.Get()).ToEqual(raw); + Expect(ptr2.Get()).ToEqual(raw); + }); - Expect(ptr2.IsValid()).ToEqual(true); - Expect(ptr.Get()).ToEqual(raw); - Expect(ptr2.Get()).ToEqual(raw); - }); + It("Can move from other weak", []() + { + TOwnPtr owner = MakeOwned(); + auto* raw = owner.Get(); + auto weak = owner.AsPtr(); + auto movedWeak = Move(weak); - It("Can move from other weak", []() - { - TOwnPtr owner = MakeOwned(); - auto* raw = owner.Get(); - auto weak = owner.AsPtr(); - auto movedWeak = Move(weak); + Expect(weak.IsValid()).ToEqual(false); + Expect(movedWeak.IsValid()).ToEqual(true); - Expect(weak.IsValid()).ToEqual(false); - Expect(movedWeak.IsValid()).ToEqual(true); + Expect(weak.Get()).ToEqual(nullptr); + Expect(movedWeak.Get()).ToEqual(raw); + }); - Expect(weak.Get()).ToEqual(nullptr); - Expect(movedWeak.Get()).ToEqual(raw); - }); + It("Ptr is null after IsValid() == false", []() + { + TOwnPtr owner = MakeOwned(); + TPtr ptr = owner; + owner.Delete(); - It("Ptr is null after IsValid() == false", []() - { - TOwnPtr owner = MakeOwned(); - TPtr ptr = owner; - owner.Delete(); + Expect(ptr.Get()).ToNotEqual(nullptr); - Expect(ptr.Get()).ToNotEqual(nullptr); + Expect(ptr.IsValid()).ToEqual(false); + Expect(ptr.Get()).ToEqual(nullptr); + }); + }); - Expect(ptr.IsValid()).ToEqual(false); - Expect(ptr.Get()).ToEqual(nullptr); - }); + Describe("Comparisons", []() + { + It("Owner can equal Owner", []() + { + auto owner = MakeOwned(); + auto owner2 = MakeOwned(); + TOwnPtr ownerEmpty; + + Expect(owner == owner).ToEqual(true); + Expect(owner == owner2).ToEqual(false); + Expect(ownerEmpty == ownerEmpty).ToEqual(true); + Expect(owner == ownerEmpty).ToEqual(false); + + Expect(owner != owner).ToEqual(false); + Expect(owner != owner2).ToEqual(true); + Expect(ownerEmpty != ownerEmpty).ToEqual(false); + Expect(owner != ownerEmpty).ToEqual(true); }); - Describe("Comparisons", []() + It("Owner can equal Weak", []() { - It("Owner can equal Owner", []() - { - auto owner = MakeOwned(); - auto owner2 = MakeOwned(); - TOwnPtr ownerEmpty; - - Expect(owner == owner).ToEqual(true); - Expect(owner == owner2).ToEqual(false); - Expect(ownerEmpty == ownerEmpty).ToEqual(true); - Expect(owner == ownerEmpty).ToEqual(false); - - Expect(owner != owner).ToEqual(false); - Expect(owner != owner2).ToEqual(true); - Expect(ownerEmpty != ownerEmpty).ToEqual(false); - Expect(owner != ownerEmpty).ToEqual(true); - }); + auto owner = MakeOwned(); + auto owner2 = MakeOwned(); + auto weak = owner.AsPtr(); + TOwnPtr ownerEmpty; + TPtr weakEmpty; + + Expect(owner == weak).ToEqual(true); + Expect(owner2 == weak).ToEqual(false); + Expect(ownerEmpty == weak).ToEqual(false); + Expect(ownerEmpty == weakEmpty).ToEqual(true); + + Expect(owner != weak).ToEqual(false); + Expect(owner2 != weak).ToEqual(true); + Expect(ownerEmpty != weak).ToEqual(true); + Expect(ownerEmpty != weakEmpty).ToEqual(false); + }); - It("Owner can equal Weak", []() - { - auto owner = MakeOwned(); - auto owner2 = MakeOwned(); - auto weak = owner.AsPtr(); - TOwnPtr ownerEmpty; - TPtr weakEmpty; - - Expect(owner == weak).ToEqual(true); - Expect(owner2 == weak).ToEqual(false); - Expect(ownerEmpty == weak).ToEqual(false); - Expect(ownerEmpty == weakEmpty).ToEqual(true); - - Expect(owner != weak).ToEqual(false); - Expect(owner2 != weak).ToEqual(true); - Expect(ownerEmpty != weak).ToEqual(true); - Expect(ownerEmpty != weakEmpty).ToEqual(false); - }); + It("Weak can equal Weak", []() + { + auto owner = MakeOwned(); + auto owner2 = MakeOwned(); + auto weak = owner.AsPtr(); + auto weak2 = owner2.AsPtr(); + TPtr weakEmpty; + + Expect(weak == weak).ToEqual(true); + Expect(weak2 == weak).ToEqual(false); + Expect(weakEmpty == weak).ToEqual(false); + Expect(weakEmpty == weakEmpty).ToEqual(true); + + Expect(weak != weak).ToEqual(false); + Expect(weak2 != weak).ToEqual(true); + Expect(weakEmpty != weak).ToEqual(true); + Expect(weakEmpty != weakEmpty).ToEqual(false); + }); - It("Weak can equal Weak", []() - { - auto owner = MakeOwned(); - auto owner2 = MakeOwned(); - auto weak = owner.AsPtr(); - auto weak2 = owner2.AsPtr(); - TPtr weakEmpty; - - Expect(weak == weak).ToEqual(true); - Expect(weak2 == weak).ToEqual(false); - Expect(weakEmpty == weak).ToEqual(false); - Expect(weakEmpty == weakEmpty).ToEqual(true); - - Expect(weak != weak).ToEqual(false); - Expect(weak2 != weak).ToEqual(true); - Expect(weakEmpty != weak).ToEqual(true); - Expect(weakEmpty != weakEmpty).ToEqual(false); - }); + It("Weak can equal Owner", []() + { + auto owner = MakeOwned(); + auto owner2 = MakeOwned(); + auto weak = owner.AsPtr(); + auto weak2 = owner2.AsPtr(); + TOwnPtr ownerEmpty; + TPtr weakEmpty; + + Expect(weak == owner).ToEqual(true); + Expect(weak2 == owner).ToEqual(false); + Expect(weakEmpty == owner).ToEqual(false); + Expect(weakEmpty == ownerEmpty).ToEqual(true); + + Expect(weak != owner).ToEqual(false); + Expect(weak2 != owner).ToEqual(true); + Expect(weakEmpty != owner).ToEqual(true); + Expect(weakEmpty != ownerEmpty).ToEqual(false); + }); + }); - It("Weak can equal Owner", []() - { - auto owner = MakeOwned(); - auto owner2 = MakeOwned(); - auto weak = owner.AsPtr(); - auto weak2 = owner2.AsPtr(); - TOwnPtr ownerEmpty; - TPtr weakEmpty; - - Expect(weak == owner).ToEqual(true); - Expect(weak2 == owner).ToEqual(false); - Expect(weakEmpty == owner).ToEqual(false); - Expect(weakEmpty == ownerEmpty).ToEqual(true); - - Expect(weak != owner).ToEqual(false); - Expect(weak2 != owner).ToEqual(true); - Expect(weakEmpty != owner).ToEqual(true); - Expect(weakEmpty != ownerEmpty).ToEqual(false); - }); + Describe("Counter", []() + { + It("Adds weaks", []() + { + auto owner = MakeOwned(); + const auto* counter = owner.GetCounter(); + Expect(counter->weakCount).ToEqual(0u); + + auto weak = owner.AsPtr(); + Expect(counter->weakCount).ToEqual(1u); }); - Describe("Counter", []() + It("Removes weaks", []() { - It("Adds weaks", []() + auto owner = MakeOwned(); + const auto* counter = owner.GetCounter(); { - auto owner = MakeOwned(); - const auto* counter = owner.GetCounter(); - Expect(counter->weakCount).ToEqual(0u); - auto weak = owner.AsPtr(); Expect(counter->weakCount).ToEqual(1u); - }); - - It("Removes weaks", []() - { - auto owner = MakeOwned(); - const auto* counter = owner.GetCounter(); - { - auto weak = owner.AsPtr(); - Expect(counter->weakCount).ToEqual(1u); - } - Expect(counter->weakCount).ToEqual(0u); - }); + } + Expect(counter->weakCount).ToEqual(0u); + }); - It("Removes with owner release", []() - { - auto owner = MakeOwned(); - Expect(owner.GetCounter()).ToNotEqual(nullptr); + It("Removes with owner release", []() + { + auto owner = MakeOwned(); + Expect(owner.GetCounter()).ToNotEqual(nullptr); - owner.Delete(); - Expect(owner.GetCounter()).ToEqual(nullptr); - }); + owner.Delete(); + Expect(owner.GetCounter()).ToEqual(nullptr); + }); - It("Removes with no weakCount left", []() - { - auto owner = MakeOwned(); - auto weak = owner.AsPtr(); - Expect(weak.GetCounter()).ToNotEqual(nullptr); + It("Removes with no weakCount left", []() + { + auto owner = MakeOwned(); + auto weak = owner.AsPtr(); + Expect(weak.GetCounter()).ToNotEqual(nullptr); - owner.Delete(); - Expect(weak.GetCounter()).ToNotEqual(nullptr); + owner.Delete(); + Expect(weak.GetCounter()).ToNotEqual(nullptr); - weak.Reset(); - Expect(owner.GetCounter()).ToEqual(nullptr); - }); + weak.Reset(); + Expect(owner.GetCounter()).ToEqual(nullptr); }); + }); - It("Can detect custom PtrBuilders", []() - { - Expect(p::HasCustomPtrBuilder::value).ToEqual(false); - Expect(p::HasCustomPtrBuilder::value).ToEqual(true); - }); + It("Can detect custom PtrBuilders", []() + { + Expect(p::HasCustomPtrBuilder::value).ToEqual(false); + Expect(p::HasCustomPtrBuilder::value).ToEqual(true); + }); - Describe("Typeless pointer", []() + Describe("Typeless pointer", []() + { + It("Can convert to OwnPtr from TOwnPtr", []() { - It("Can convert to OwnPtr from TOwnPtr", []() - { - TOwnPtr typedPtr = MakeOwned(); - Expect(typedPtr.IsValid()).ToEqual(true); + TOwnPtr typedPtr = MakeOwned(); + Expect(typedPtr.IsValid()).ToEqual(true); - EmptyStruct* data = typedPtr.Get(); - - OwnPtr ptr = Move(typedPtr); - Expect(typedPtr.IsValid()).ToEqual(false); - Expect(ptr.IsValid()).ToEqual(true); - Expect(ptr.Get()).ToEqual(data); - Expect(ptr.Get()).ToEqual(data); - }); + EmptyStruct* data = typedPtr.Get(); - It("Can convert to TOwnPtr from OwnPtr", []() - { - OwnPtr ptr = MakeOwned(); - Expect(ptr.IsValid()).ToEqual(true); - auto* data = ptr.Get(); + OwnPtr ptr = Move(typedPtr); + Expect(typedPtr.IsValid()).ToEqual(false); + Expect(ptr.IsValid()).ToEqual(true); + Expect(ptr.Get()).ToEqual(data); + Expect(ptr.Get()).ToEqual(data); + }); - TOwnPtr typedPtr = Move(ptr); - Expect(ptr.IsValid()).ToEqual(false); - Expect(typedPtr.IsValid()).ToEqual(true); - Expect(typedPtr.Get()).ToEqual(data); - }); + It("Can convert to TOwnPtr from OwnPtr", []() + { + OwnPtr ptr = MakeOwned(); + Expect(ptr.IsValid()).ToEqual(true); + auto* data = ptr.Get(); + + TOwnPtr typedPtr = Move(ptr); + Expect(ptr.IsValid()).ToEqual(false); + Expect(typedPtr.IsValid()).ToEqual(true); + Expect(typedPtr.Get()).ToEqual(data); + }); - It("Can move", []() - { - OwnPtr ptr1 = MakeOwned(); - Expect(ptr1.IsValid()).ToEqual(true); - Expect(ptr1.GetId()).ToEqual(GetTypeId()); - auto* data = ptr1.Get(); - - OwnPtr ptr2 = Move(ptr1); - Expect(ptr1.IsValid()).ToEqual(false); - Expect(ptr1.Get()).ToEqual(nullptr); - Expect(ptr1.GetId()).ToEqual(TypeId::None()); - - Expect(ptr2.IsValid()).ToEqual(true); - Expect(ptr2.Get()).ToEqual(data); - Expect(ptr2.GetId()).ToEqual(GetTypeId()); - }); + It("Can move", []() + { + OwnPtr ptr1 = MakeOwned(); + Expect(ptr1.IsValid()).ToEqual(true); + Expect(ptr1.GetId()).ToEqual(GetTypeId()); + auto* data = ptr1.Get(); + + OwnPtr ptr2 = Move(ptr1); + Expect(ptr1.IsValid()).ToEqual(false); + Expect(ptr1.Get()).ToEqual(nullptr); + Expect(ptr1.GetId()).ToEqual(TypeId::None()); + + Expect(ptr2.IsValid()).ToEqual(true); + Expect(ptr2.Get()).ToEqual(data); + Expect(ptr2.GetId()).ToEqual(GetTypeId()); + }); - It("Cant retrive invalid types", []() - { - OwnPtr ptr = MakeOwned(); - Expect(ptr.Get()).ToNotEqual(nullptr); - Expect(ptr.Get()).ToEqual(nullptr); - }); + It("Cant retrive invalid types", []() + { + OwnPtr ptr = MakeOwned(); + Expect(ptr.Get()).ToNotEqual(nullptr); + Expect(ptr.Get()).ToEqual(nullptr); }); }); -} +}); +return true; +}(); +} // namespace diff --git a/Tests/Core/PageBuffer.spec.cpp b/Tests/Core/PageBuffer.spec.cpp index f5dc1fa0..d388ef04 100644 --- a/Tests/Core/PageBuffer.spec.cpp +++ b/Tests/Core/PageBuffer.spec.cpp @@ -2,7 +2,7 @@ #include "PipeMemory.h" -#include +#include #include @@ -25,85 +25,90 @@ struct Dummy }; -void RegisterCorePageBufferTests() +namespace { - Spec("ECS.PageBuffer", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("ECS.PageBuffer", []() +{ + It("Can reserve", []() + { + TPageBuffer buffer{GetCurrentArena()}; + + Expect(buffer.GetPagesSize()).ToEqual(0); + Expect(buffer.Capacity()).ToEqual(0); + + buffer.Reserve(2); + Expect(buffer.GetPagesSize()).ToEqual(1); + Expect(buffer.Capacity()).ToEqual(2); + + buffer.Reserve(6); + Expect(buffer.GetPagesSize()).ToEqual(3); + Expect(buffer.Capacity()).ToEqual(6); + }); + + It("Can shrink", []() + { + TPageBuffer buffer{GetCurrentArena()}; + buffer.Reserve(7); + Expect(buffer.GetPagesSize()).ToEqual(4); + + buffer.Shrink(4); + Expect(buffer.GetPagesSize()).ToEqual(2); + Expect(buffer.Capacity()).ToEqual(4); + }); + + It("Can insert", []() + { + TPageBuffer buffer{GetCurrentArena()}; + buffer.Reserve(4); + + buffer.Insert(0); + Expect(buffer[0].created).ToEqual(true); + Expect(buffer[0].destroyed).ToEqual(false); + + buffer.Insert(3); + Expect(buffer[3].created).ToEqual(true); + Expect(buffer[3].destroyed).ToEqual(false); + }); + + It("Can remove", []() { - It("Can reserve", []() - { - TPageBuffer buffer{GetCurrentArena()}; - - Expect(buffer.GetPagesSize()).ToEqual(0); - Expect(buffer.Capacity()).ToEqual(0); - - buffer.Reserve(2); - Expect(buffer.GetPagesSize()).ToEqual(1); - Expect(buffer.Capacity()).ToEqual(2); - - buffer.Reserve(6); - Expect(buffer.GetPagesSize()).ToEqual(3); - Expect(buffer.Capacity()).ToEqual(6); - }); - - It("Can shrink", []() - { - TPageBuffer buffer{GetCurrentArena()}; - buffer.Reserve(7); - Expect(buffer.GetPagesSize()).ToEqual(4); - - buffer.Shrink(4); - Expect(buffer.GetPagesSize()).ToEqual(2); - Expect(buffer.Capacity()).ToEqual(4); - }); - - It("Can insert", []() - { - TPageBuffer buffer{GetCurrentArena()}; - buffer.Reserve(4); - - buffer.Insert(0); - Expect(buffer[0].created).ToEqual(true); - Expect(buffer[0].destroyed).ToEqual(false); - - buffer.Insert(3); - Expect(buffer[3].created).ToEqual(true); - Expect(buffer[3].destroyed).ToEqual(false); - }); - - It("Can remove", []() - { - TPageBuffer buffer{GetCurrentArena()}; - buffer.Reserve(4); - - buffer.Insert(0); - buffer.Insert(3); - - buffer.RemoveAt(0); - // Temporarily disabled due to GCC only test fail - // Expect(buffer[0].destroyed).ToEqual(true); - - buffer.RemoveAt(3); - // Temporarily disabled due to GCC only test fail - // Expect(buffer[3].destroyed).ToEqual(true); - }); - - It("Points to correct page", []() - { - TPageBuffer buffer{GetCurrentArena()}; - buffer.Reserve(7); - - buffer.AssurePage(0); - Expect(buffer.AssurePage(0)).ToNotEqual(nullptr); - Expect(buffer.AssurePage(1)).ToNotEqual(nullptr); - Expect(buffer.AssurePage(2)).ToNotEqual(nullptr); - Expect(buffer.AssurePage(5)).ToNotEqual(nullptr); - - - Expect(buffer.FindPage(0)).ToNotEqual(nullptr); - Expect(buffer.FindPage(1)).ToNotEqual(nullptr); - Expect(buffer.FindPage(2)).ToNotEqual(nullptr); - Expect(buffer.FindPage(5)).ToNotEqual(nullptr); - Expect(buffer.FindPage(6)).ToEqual(nullptr); - }); + TPageBuffer buffer{GetCurrentArena()}; + buffer.Reserve(4); + + buffer.Insert(0); + buffer.Insert(3); + + buffer.RemoveAt(0); + // Temporarily disabled due to GCC only test fail + // Expect(buffer[0].destroyed).ToEqual(true); + + buffer.RemoveAt(3); + // Temporarily disabled due to GCC only test fail + // Expect(buffer[3].destroyed).ToEqual(true); + }); + + It("Points to correct page", []() + { + TPageBuffer buffer{GetCurrentArena()}; + buffer.Reserve(7); + + buffer.AssurePage(0); + Expect(buffer.AssurePage(0)).ToNotEqual(nullptr); + Expect(buffer.AssurePage(1)).ToNotEqual(nullptr); + Expect(buffer.AssurePage(2)).ToNotEqual(nullptr); + Expect(buffer.AssurePage(5)).ToNotEqual(nullptr); + + + Expect(buffer.FindPage(0)).ToNotEqual(nullptr); + Expect(buffer.FindPage(1)).ToNotEqual(nullptr); + Expect(buffer.FindPage(2)).ToNotEqual(nullptr); + Expect(buffer.FindPage(5)).ToNotEqual(nullptr); + Expect(buffer.FindPage(6)).ToEqual(nullptr); }); -} +}); +return true; +}(); +} // namespace diff --git a/Tests/Core/PlatformProcess.spec.cpp b/Tests/Core/PlatformProcess.spec.cpp index 56785554..dbc1db56 100644 --- a/Tests/Core/PlatformProcess.spec.cpp +++ b/Tests/Core/PlatformProcess.spec.cpp @@ -3,24 +3,29 @@ #include "Pipe/Core/Log.h" #include "Pipe/Core/Subprocess.h" -#include +#include #include using namespace p; -void RegisterCorePlatformProcessTests() +namespace { - Spec("Core.Subprocess", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("Core.Subprocess", []() +{ + It("Can run process", []() { - It("Can run process", []() - { - Expect(p::RunProcess({""}).IsSet()).ToEqual(false); + Expect(p::RunProcess({""}).IsSet()).ToEqual(false); - #if defined(_MSC_VER) // Test with a silent command (no stdout) - Expect(p::RunProcess({"cmd", "/c", "exit", "0"}).IsSet()).ToEqual(true); - #endif - }); +#if defined(_MSC_VER) // Test with a silent command (no stdout) + Expect(p::RunProcess({"cmd", "/c", "exit", "0"}).IsSet()).ToEqual(true); +#endif }); -} +}); +return true; +}(); +} // namespace diff --git a/Tests/Core/Set.spec.cpp b/Tests/Core/Set.spec.cpp index 3ac74001..c43e2bc8 100644 --- a/Tests/Core/Set.spec.cpp +++ b/Tests/Core/Set.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include @@ -14,79 +14,84 @@ struct TypeOfSize }; -void RegisterCoreSetTests() +namespace { - Spec("Core.Set", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("Core.Set", []() +{ + It("Can initialize", []() + { + TSet data1{}; + TSet data2(u32(3)); + TSet data3{5, 4, 3, 2}; + + Expect(data1.Size()).ToEqual(0); + Expect(data2.Size()).ToEqual(0); + Expect(data3.Size()).ToEqual(4); + + Expect(data3[2]).ToEqual(2); + Expect(data3[3]).ToEqual(3); + Expect(data3[4]).ToEqual(4); + Expect(data3[5]).ToEqual(5); + }); + + It("Can copy", []() + { + TSet data1{6, 5}; + TSet data2{data1}; + Expect(data1.Size()).ToEqual(2); + Expect(data2.Size()).ToEqual(2); + Expect(data2[5]).ToEqual(5); + Expect(data2[6]).ToEqual(6); + + TSet data3{6, 5}; + TSet data4; + data4 = data3; + Expect(data3.Size()).ToEqual(2); + Expect(data4.Size()).ToEqual(2); + Expect(data4[5]).ToEqual(5); + Expect(data4[6]).ToEqual(6); + }); + + It("Can move", []() + { + TSet data1{4, 3}; + Expect(data1.Size()).ToEqual(2); + + TSet data2{Move(data1)}; + Expect(data1.Size()).ToEqual(0); + Expect(data2.Size()).ToEqual(2); + + TSet data3{4, 3}; + TSet data4; + Expect(data3.Size()).ToEqual(2); + Expect(data4.Size()).ToEqual(0); + + data4 = Move(data3); + Expect(data3.Size()).ToEqual(0); + Expect(data4.Size()).ToEqual(2); + Expect(data4[3]).ToEqual(3); + Expect(data4[4]).ToEqual(4); + }); + + It("Can access data", []() { - It("Can initialize", []() - { - TSet data1{}; - TSet data2(u32(3)); - TSet data3{5, 4, 3, 2}; - - Expect(data1.Size()).ToEqual(0); - Expect(data2.Size()).ToEqual(0); - Expect(data3.Size()).ToEqual(4); - - Expect(data3[2]).ToEqual(2); - Expect(data3[3]).ToEqual(3); - Expect(data3[4]).ToEqual(4); - Expect(data3[5]).ToEqual(5); - }); - - It("Can copy", []() - { - TSet data1{6, 5}; - TSet data2{data1}; - Expect(data1.Size()).ToEqual(2); - Expect(data2.Size()).ToEqual(2); - Expect(data2[5]).ToEqual(5); - Expect(data2[6]).ToEqual(6); - - TSet data3{6, 5}; - TSet data4; - data4 = data3; - Expect(data3.Size()).ToEqual(2); - Expect(data4.Size()).ToEqual(2); - Expect(data4[5]).ToEqual(5); - Expect(data4[6]).ToEqual(6); - }); - - It("Can move", []() - { - TSet data1{4, 3}; - Expect(data1.Size()).ToEqual(2); - - TSet data2{Move(data1)}; - Expect(data1.Size()).ToEqual(0); - Expect(data2.Size()).ToEqual(2); - - TSet data3{4, 3}; - TSet data4; - Expect(data3.Size()).ToEqual(2); - Expect(data4.Size()).ToEqual(0); - - data4 = Move(data3); - Expect(data3.Size()).ToEqual(0); - Expect(data4.Size()).ToEqual(2); - Expect(data4[3]).ToEqual(3); - Expect(data4[4]).ToEqual(4); - }); - - It("Can access data", []() - { - TSet data1; - TSet data2{1, 5}; - - Expect(data1.Size()).ToEqual(0); - Expect(data2.Size()).ToBeGreaterOrEqual(2); - - Expect(data1.Contains(3)).ToEqual(false); - Expect(data2.Contains(1)).ToEqual(true); - Expect(data2.Contains(5)).ToEqual(true); - Expect(data2.Contains(34)).ToEqual(false); - Expect(data2[1]).ToEqual(1); - Expect(data2[5]).ToEqual(5); - }); + TSet data1; + TSet data2{1, 5}; + + Expect(data1.Size()).ToEqual(0); + Expect(data2.Size()).ToBeGreaterOrEqual(2); + + Expect(data1.Contains(3)).ToEqual(false); + Expect(data2.Contains(1)).ToEqual(true); + Expect(data2.Contains(5)).ToEqual(true); + Expect(data2.Contains(34)).ToEqual(false); + Expect(data2[1]).ToEqual(1); + Expect(data2[5]).ToEqual(5); }); -} +}); +return true; +}(); +} // namespace diff --git a/Tests/Core/SpinLock.spec.cpp b/Tests/Core/SpinLock.spec.cpp index 061ed86d..02f9f11c 100644 --- a/Tests/Core/SpinLock.spec.cpp +++ b/Tests/Core/SpinLock.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include #include @@ -11,175 +11,180 @@ using namespace p; -void RegisterCoreSpinLockTests() +namespace { - Spec("Core.SpinLock", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("Core.SpinLock", []() +{ + Describe("SpinLock", []() { - Describe("SpinLock", []() + It("Acquires and releases exclusively", []() { - It("Acquires and releases exclusively", []() - { - SpinLock lock; - ScopedLock guard(lock); + SpinLock lock; + ScopedLock guard(lock); - Expect(lock.Locked()).ToBeTrue(); - Expect(lock.TryLock()).ToBeFalse(); - }); + Expect(lock.Locked()).ToBeTrue(); + Expect(lock.TryLock()).ToBeFalse(); + }); - It("Allows serialized writers to increment a counter", []() - { - SpinLock lock; - i32 counter = 0; + It("Allows serialized writers to increment a counter", []() + { + SpinLock lock; + i32 counter = 0; - constexpr i32 kThreads = 4; - constexpr i32 kPerThread = 10'000; + constexpr i32 kThreads = 4; + constexpr i32 kPerThread = 10'000; - std::vector threads; - std::atomic start{false}; - for (i32 t = 0; t < kThreads; ++t) + std::vector threads; + std::atomic start{false}; + for (i32 t = 0; t < kThreads; ++t) + { + threads.emplace_back([&]() { - threads.emplace_back([&]() + while (!start.load(std::memory_order_acquire)) + {} + for (i32 i = 0; i < kPerThread; ++i) { - while (!start.load(std::memory_order_acquire)) - {} - for (i32 i = 0; i < kPerThread; ++i) - { - ScopedLock guard(lock); - ++counter; - } - }); - } - - start.store(true, std::memory_order_release); - for (auto& thread : threads) - { - thread.join(); - } + ScopedLock guard(lock); + ++counter; + } + }); + } + + start.store(true, std::memory_order_release); + for (auto& thread : threads) + { + thread.join(); + } - Expect(counter).ToEqual(kThreads * kPerThread); - }); + Expect(counter).ToEqual(kThreads * kPerThread); }); + }); - Describe("SharedSpinLock", []() + Describe("SharedSpinLock", []() + { + It("Exclusive lock excludes a second exclusive lock", []() { - It("Exclusive lock excludes a second exclusive lock", []() - { - SharedSpinLock lock; - ExclusiveScopedLock writer(lock); + SharedSpinLock lock; + ExclusiveScopedLock writer(lock); - Expect(lock.TryLockExclusive()).ToBeFalse(); - }); + Expect(lock.TryLockExclusive()).ToBeFalse(); + }); - It("Exclusive lock excludes shared locks", []() - { - SharedSpinLock lock; - ExclusiveScopedLock writer(lock); + It("Exclusive lock excludes shared locks", []() + { + SharedSpinLock lock; + ExclusiveScopedLock writer(lock); - Expect(lock.TryLockShared()).ToBeFalse(); - }); + Expect(lock.TryLockShared()).ToBeFalse(); + }); - It("Shared lock excludes an exclusive lock", []() - { - SharedSpinLock lock; - SharedScopedLock reader(lock); + It("Shared lock excludes an exclusive lock", []() + { + SharedSpinLock lock; + SharedScopedLock reader(lock); - Expect(lock.TryLockExclusive()).ToBeFalse(); - }); + Expect(lock.TryLockExclusive()).ToBeFalse(); + }); - It("Allows multiple overlapping shared locks", []() - { - SharedSpinLock lock; + It("Allows multiple overlapping shared locks", []() + { + SharedSpinLock lock; - SharedScopedLock r1(lock); - SharedScopedLock r2(lock); - SharedScopedLock r3(lock); + SharedScopedLock r1(lock); + SharedScopedLock r2(lock); + SharedScopedLock r3(lock); - // Readers coexist: shared still acquirable. - Expect(lock.TryLockShared()).ToBeTrue(); - lock.UnlockShared(); + // Readers coexist: shared still acquirable. + Expect(lock.TryLockShared()).ToBeTrue(); + lock.UnlockShared(); - Expect(lock.TryLockExclusive()).ToBeFalse(); - }); + Expect(lock.TryLockExclusive()).ToBeFalse(); + }); - It("Writers exclude each other", []() - { - SharedSpinLock lock; + It("Writers exclude each other", []() + { + SharedSpinLock lock; - ExclusiveScopedLock w1(lock); - Expect(lock.TryLockExclusive()).ToBeFalse(); - }); + ExclusiveScopedLock w1(lock); + Expect(lock.TryLockExclusive()).ToBeFalse(); + }); - It("Writes under exclusive lock are mutually excluded", []() - { - SharedSpinLock lock; - i32 counter = 0; + It("Writes under exclusive lock are mutually excluded", []() + { + SharedSpinLock lock; + i32 counter = 0; - constexpr i32 kThreads = 4; - constexpr i32 kPerThread = 10'000; + constexpr i32 kThreads = 4; + constexpr i32 kPerThread = 10'000; - std::vector threads; - std::atomic start{false}; - for (i32 t = 0; t < kThreads; ++t) + std::vector threads; + std::atomic start{false}; + for (i32 t = 0; t < kThreads; ++t) + { + threads.emplace_back([&]() { - threads.emplace_back([&]() + while (!start.load(std::memory_order_acquire)) + {} + for (i32 i = 0; i < kPerThread; ++i) { - while (!start.load(std::memory_order_acquire)) - {} - for (i32 i = 0; i < kPerThread; ++i) - { - ExclusiveScopedLock writer(lock); - ++counter; - } - }); - } - - start.store(true, std::memory_order_release); - for (auto& thread : threads) - { - thread.join(); - } + ExclusiveScopedLock writer(lock); + ++counter; + } + }); + } + + start.store(true, std::memory_order_release); + for (auto& thread : threads) + { + thread.join(); + } - Expect(counter).ToEqual(kThreads * kPerThread); - }); + Expect(counter).ToEqual(kThreads * kPerThread); + }); - It("Shared readers run concurrently without tearing shared state", []() + It("Shared readers run concurrently without tearing shared state", []() + { + SharedSpinLock lock; + i32 value = 0; + + constexpr i32 kThreads = 4; + constexpr i32 kIterations = 10'000; + + // Shared-side readers are allowed to overlap, so they must only + // read. This just checks that many threads can take the shared + // side simultaneously without deadlocking or corrupting the lock. + std::vector threads; + std::atomic start{false}; + std::atomic reads{0}; + for (i32 t = 0; t < kThreads; ++t) { - SharedSpinLock lock; - i32 value = 0; - - constexpr i32 kThreads = 4; - constexpr i32 kIterations = 10'000; - - // Shared-side readers are allowed to overlap, so they must only - // read. This just checks that many threads can take the shared - // side simultaneously without deadlocking or corrupting the lock. - std::vector threads; - std::atomic start{false}; - std::atomic reads{0}; - for (i32 t = 0; t < kThreads; ++t) + threads.emplace_back([&]() { - threads.emplace_back([&]() + while (!start.load(std::memory_order_acquire)) + {} + for (i32 i = 0; i < kIterations; ++i) { - while (!start.load(std::memory_order_acquire)) - {} - for (i32 i = 0; i < kIterations; ++i) - { - SharedScopedLock reader(lock); - const i32 v = value; - (void)v; - reads.fetch_add(1, std::memory_order_relaxed); - } - }); - } - - start.store(true, std::memory_order_release); - for (auto& thread : threads) - { - thread.join(); - } + SharedScopedLock reader(lock); + const i32 v = value; + (void)v; + reads.fetch_add(1, std::memory_order_relaxed); + } + }); + } + + start.store(true, std::memory_order_release); + for (auto& thread : threads) + { + thread.join(); + } - Expect(reads.load()).ToEqual(kThreads * kIterations); - }); + Expect(reads.load()).ToEqual(kThreads * kIterations); }); }); -} +}); +return true; +}(); +} // namespace diff --git a/Tests/Core/String.spec.cpp b/Tests/Core/String.spec.cpp index 33926b89..14c53981 100644 --- a/Tests/Core/String.spec.cpp +++ b/Tests/Core/String.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include #include #include @@ -18,957 +18,962 @@ static const StringView longText = "0123456789ABCDEFGHIJ0123456789ABC"; static const char* arenaLongText = "This string is long enough to exceed the inline capacity"; -void RegisterCoreStringTests() +namespace { - Spec("Strings", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("Strings", []() +{ + Describe("String", []() { - Describe("String", []() + Describe("Construction", []() { - Describe("Construction", []() + It("Can default construct", []() { - It("Can default construct", []() - { - String v{}; - Expect(v.size()).ToEqual(0u); - Expect(v.empty()).ToBeTrue(); - Expect(v.length()).ToEqual(0u); - // c_str() must always return a valid pointer to a null terminator - Expect(v.c_str() != nullptr).ToBeTrue(); - Expect(v.c_str()[0]).ToEqual('\0'); - Expect(v.data() != nullptr).ToBeTrue(); - Expect(v.data()[0]).ToEqual('\0'); - }); + String v{}; + Expect(v.size()).ToEqual(0u); + Expect(v.empty()).ToBeTrue(); + Expect(v.length()).ToEqual(0u); + // c_str() must always return a valid pointer to a null terminator + Expect(v.c_str() != nullptr).ToBeTrue(); + Expect(v.c_str()[0]).ToEqual('\0'); + Expect(v.data() != nullptr).ToBeTrue(); + Expect(v.data()[0]).ToEqual('\0'); + }); - It("Can construct from literal", []() - { - String v{"Kiwi"}; - Expect(v).ToEqual("Kiwi"); - Expect(v.size()).ToEqual(4u); - }); + It("Can construct from literal", []() + { + String v{"Kiwi"}; + Expect(v).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4u); + }); - It("Can construct from literal with count", []() - { - String v{"KiwiApple", 4}; - Expect(v).ToEqual("Kiwi"); - Expect(v.size()).ToEqual(4u); - }); + It("Can construct from literal with count", []() + { + String v{"KiwiApple", 4}; + Expect(v).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4u); + }); - It("Can construct from count and char", []() - { - String v(5, 'x'); - Expect(v).ToEqual("xxxxx"); - Expect(v.size()).ToEqual(5u); - }); + It("Can construct from count and char", []() + { + String v(5, 'x'); + Expect(v).ToEqual("xxxxx"); + Expect(v.size()).ToEqual(5u); + }); - It("Can construct from string view", []() - { - StringView str{"Kiwi"}; - String v{str}; - Expect(v).ToEqual("Kiwi"); - Expect(v.size()).ToEqual(4u); - }); + It("Can construct from string view", []() + { + StringView str{"Kiwi"}; + String v{str}; + Expect(v).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4u); + }); - It("Can construct from string view with pos and count", []() - { - StringView str{"KiwiApple"}; - String v{str, 4, 5}; - Expect(v).ToEqual("Apple"); - }); + It("Can construct from string view with pos and count", []() + { + StringView str{"KiwiApple"}; + String v{str, 4, 5}; + Expect(v).ToEqual("Apple"); + }); - It("Can construct from substring", []() - { - String str{"KiwiApple"}; - String v{str, 4}; - Expect(v).ToEqual("Apple"); - String v2{str, 4, 3}; - Expect(v2).ToEqual("App"); - }); + It("Can construct from substring", []() + { + String str{"KiwiApple"}; + String v{str, 4}; + Expect(v).ToEqual("Apple"); + String v2{str, 4, 3}; + Expect(v2).ToEqual("App"); + }); - It("Can construct from iterators", []() - { - std::string_view sv = "Kiwi"; - String v{sv.begin(), sv.end()}; - Expect(v).ToEqual("Kiwi"); - }); + It("Can construct from iterators", []() + { + std::string_view sv = "Kiwi"; + String v{sv.begin(), sv.end()}; + Expect(v).ToEqual("Kiwi"); + }); - It("Can construct from initializer list", []() - { - String v{'K', 'i', 'w', 'i'}; - Expect(v).ToEqual("Kiwi"); - }); + It("Can construct from initializer list", []() + { + String v{'K', 'i', 'w', 'i'}; + Expect(v).ToEqual("Kiwi"); + }); - It("Can copy construct", []() - { - String v{"Kiwi"}; - String v2{v}; - Expect(v2).ToEqual("Kiwi"); - Expect(v).ToEqual("Kiwi"); - }); + It("Can copy construct", []() + { + String v{"Kiwi"}; + String v2{v}; + Expect(v2).ToEqual("Kiwi"); + Expect(v).ToEqual("Kiwi"); + }); - It("Can move construct", []() - { - String v{"Kiwi"}; - String v2{Move(v)}; - Expect(v2).ToEqual("Kiwi"); - // Moved-from string is valid and empty - Expect(v.size()).ToEqual(0u); - Expect(v.empty()).ToBeTrue(); - Expect(v.c_str()[0]).ToEqual('\0'); - }); + It("Can move construct", []() + { + String v{"Kiwi"}; + String v2{Move(v)}; + Expect(v2).ToEqual("Kiwi"); + // Moved-from string is valid and empty + Expect(v.size()).ToEqual(0u); + Expect(v.empty()).ToBeTrue(); + Expect(v.c_str()[0]).ToEqual('\0'); }); + }); - Describe("Assignment", []() + Describe("Assignment", []() + { + It("Can assign from literal", []() { - It("Can assign from literal", []() - { - String v; - v = "Kiwi"; - Expect(v).ToEqual("Kiwi"); - }); + String v; + v = "Kiwi"; + Expect(v).ToEqual("Kiwi"); + }); - It("Can copy assign", []() - { - String vKiwi{"Kiwi"}; - String vApple{"Apple"}; - String vCopy = vKiwi; - Expect(vCopy).ToEqual("Kiwi"); - vCopy = vApple; - Expect(vCopy).ToEqual("Apple"); - Expect(vCopy).ToEqual(vApple); - }); + It("Can copy assign", []() + { + String vKiwi{"Kiwi"}; + String vApple{"Apple"}; + String vCopy = vKiwi; + Expect(vCopy).ToEqual("Kiwi"); + vCopy = vApple; + Expect(vCopy).ToEqual("Apple"); + Expect(vCopy).ToEqual(vApple); + }); - It("Can move assign", []() - { - String vKiwi{"Kiwi"}; - String vApple{"Apple"}; - String vMove = Move(vKiwi); - Expect(vKiwi.size()).ToEqual(0u); - Expect(vMove).ToEqual("Kiwi"); - vMove = Move(vApple); - Expect(vApple.size()).ToEqual(0u); - Expect(vMove).ToEqual("Apple"); - }); + It("Can move assign", []() + { + String vKiwi{"Kiwi"}; + String vApple{"Apple"}; + String vMove = Move(vKiwi); + Expect(vKiwi.size()).ToEqual(0u); + Expect(vMove).ToEqual("Kiwi"); + vMove = Move(vApple); + Expect(vApple.size()).ToEqual(0u); + Expect(vMove).ToEqual("Apple"); + }); - It("Can assign char", []() - { - String v; - v = 'x'; - Expect(v).ToEqual("x"); - }); + It("Can assign char", []() + { + String v; + v = 'x'; + Expect(v).ToEqual("x"); + }); - It("Can assign initializer list", []() - { - String v; - v = {'K', 'i', 'w', 'i'}; - Expect(v).ToEqual("Kiwi"); - }); + It("Can assign initializer list", []() + { + String v; + v = {'K', 'i', 'w', 'i'}; + Expect(v).ToEqual("Kiwi"); + }); - It("Can assign string view", []() - { - String v; - StringView sv{"Kiwi"}; - v = sv; - Expect(v).ToEqual("Kiwi"); - }); + It("Can assign string view", []() + { + String v; + StringView sv{"Kiwi"}; + v = sv; + Expect(v).ToEqual("Kiwi"); + }); - It("Can assign", []() - { - String v; - v.assign("Kiwi"); - Expect(v).ToEqual("Kiwi"); - v.assign("KiwiApple", 4); - Expect(v).ToEqual("Kiwi"); - v.assign(3, 'x'); - Expect(v).ToEqual("xxx"); - String other{"Apple"}; - v.assign(other); - Expect(v).ToEqual("Apple"); - v.assign(other, 2, 2); - Expect(v).ToEqual("pl"); - StringView sv{"KiwiApple"}; - v.assign(sv, 4, 5); - Expect(v).ToEqual("Apple"); - v.assign({'a', 'b', 'c'}); - Expect(v).ToEqual("abc"); - }); + It("Can assign", []() + { + String v; + v.assign("Kiwi"); + Expect(v).ToEqual("Kiwi"); + v.assign("KiwiApple", 4); + Expect(v).ToEqual("Kiwi"); + v.assign(3, 'x'); + Expect(v).ToEqual("xxx"); + String other{"Apple"}; + v.assign(other); + Expect(v).ToEqual("Apple"); + v.assign(other, 2, 2); + Expect(v).ToEqual("pl"); + StringView sv{"KiwiApple"}; + v.assign(sv, 4, 5); + Expect(v).ToEqual("Apple"); + v.assign({'a', 'b', 'c'}); + Expect(v).ToEqual("abc"); + }); - It("Can self assign", []() - { - String v{"Kiwi"}; - const String& ref = v; - v = ref; - Expect(v).ToEqual("Kiwi"); - }); + It("Can self assign", []() + { + String v{"Kiwi"}; + const String& ref = v; + v = ref; + Expect(v).ToEqual("Kiwi"); + }); - It("Can self assign substrings", []() - { - String v{longText}; - v.assign(v.c_str() + 10); - Expect(v).ToEqual("ABCDEFGHIJ0123456789ABC"); - }); + It("Can self assign substrings", []() + { + String v{longText}; + v.assign(v.c_str() + 10); + Expect(v).ToEqual("ABCDEFGHIJ0123456789ABC"); + }); - It("Can self assign substrings with count", []() - { - String v{longText}; - v.assign(v.c_str() + 5, 10); - Expect(v).ToEqual("56789ABCDE"); - }); + It("Can self assign substrings with count", []() + { + String v{longText}; + v.assign(v.c_str() + 5, 10); + Expect(v).ToEqual("56789ABCDE"); }); + }); - Describe("Element access", []() + Describe("Element access", []() + { + It("Can index", []() { - It("Can index", []() - { - String v{"Kiwi"}; - Expect(v[0]).ToEqual('K'); - Expect(v[3]).ToEqual('i'); - v[0] = 'k'; - Expect(v).ToEqual("kiwi"); - // pos == size() returns reference to null char - Expect(v[4]).ToEqual('\0'); - }); + String v{"Kiwi"}; + Expect(v[0]).ToEqual('K'); + Expect(v[3]).ToEqual('i'); + v[0] = 'k'; + Expect(v).ToEqual("kiwi"); + // pos == size() returns reference to null char + Expect(v[4]).ToEqual('\0'); + }); - It("Can access at", []() - { - String v{"Kiwi"}; - Expect(v.at(0)).ToEqual('K'); - Expect(v.at(3)).ToEqual('i'); - v.at(0) = 'k'; - Expect(v).ToEqual("kiwi"); - }); + It("Can access at", []() + { + String v{"Kiwi"}; + Expect(v.at(0)).ToEqual('K'); + Expect(v.at(3)).ToEqual('i'); + v.at(0) = 'k'; + Expect(v).ToEqual("kiwi"); + }); - It("Can access front and back", []() - { - String v{"Kiwi"}; - Expect(v.front()).ToEqual('K'); - Expect(v.back()).ToEqual('i'); - v.front() = 'P'; - v.back() = 's'; - Expect(v).ToEqual("Piws"); - }); + It("Can access front and back", []() + { + String v{"Kiwi"}; + Expect(v.front()).ToEqual('K'); + Expect(v.back()).ToEqual('i'); + v.front() = 'P'; + v.back() = 's'; + Expect(v).ToEqual("Piws"); + }); - It("Can retrieve data", []() - { - String v{"Kiwi"}; - Expect(v.data()).ToEqual("Kiwi"); - Expect(v.size()).ToEqual(4u); - Expect(strlen(v.data())).ToEqual(4u); - }); + It("Can retrieve data", []() + { + String v{"Kiwi"}; + Expect(v.data()).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4u); + Expect(strlen(v.data())).ToEqual(4u); + }); - It("Can convert to string view", []() - { - String v{"Kiwi"}; - StringView sv = v; - Expect(sv.size()).ToEqual(4u); - Expect(sv).ToEqual(StringView{"Kiwi"}); - StringView wsv{v}; - Expect(wsv).ToEqual(StringView{"Kiwi"}); - }); + It("Can convert to string view", []() + { + String v{"Kiwi"}; + StringView sv = v; + Expect(sv.size()).ToEqual(4u); + Expect(sv).ToEqual(StringView{"Kiwi"}); + StringView wsv{v}; + Expect(wsv).ToEqual(StringView{"Kiwi"}); }); + }); - Describe("Iterators", []() + Describe("Iterators", []() + { + It("Can iterate", []() { - It("Can iterate", []() - { - String v{"Kiwi"}; - u32 i = 0; - for (char c : v) - { - Expect(c).ToEqual("Kiwi"[i]); - ++i; - } - Expect(i).ToEqual(4u); - }); + String v{"Kiwi"}; + u32 i = 0; + for (char c : v) + { + Expect(c).ToEqual("Kiwi"[i]); + ++i; + } + Expect(i).ToEqual(4u); + }); - It("Can iterate const", []() - { - const String v{"Kiwi"}; - u32 i = 0; - for (char c : v) - { - Expect(c).ToEqual("Kiwi"[i]); - ++i; - } - Expect(i).ToEqual(4u); - }); + It("Can iterate const", []() + { + const String v{"Kiwi"}; + u32 i = 0; + for (char c : v) + { + Expect(c).ToEqual("Kiwi"[i]); + ++i; + } + Expect(i).ToEqual(4u); + }); - It("Can iterate manually", []() - { - String v{"Kiwi"}; - auto it = v.begin(); - auto end = v.end(); - Expect(end - it).ToEqual(4); - Expect(*it).ToEqual('K'); - Expect(it[2]).ToEqual('w'); - ++it; - Expect(*it).ToEqual('i'); - it += 2; - Expect(*it).ToEqual('i'); - --it; - Expect(*it).ToEqual('w'); - Expect(it == v.begin() + 2).ToBeTrue(); - Expect(it != v.begin()).ToBeTrue(); - }); + It("Can iterate manually", []() + { + String v{"Kiwi"}; + auto it = v.begin(); + auto end = v.end(); + Expect(end - it).ToEqual(4); + Expect(*it).ToEqual('K'); + Expect(it[2]).ToEqual('w'); + ++it; + Expect(*it).ToEqual('i'); + it += 2; + Expect(*it).ToEqual('i'); + --it; + Expect(*it).ToEqual('w'); + Expect(it == v.begin() + 2).ToBeTrue(); + Expect(it != v.begin()).ToBeTrue(); + }); - It("Can iterate reverse", []() - { - String v{"Kiwi"}; - u32 i = 0; - for (auto rit = v.rbegin(); rit != v.rend(); ++rit) - { - Expect(*rit).ToEqual("Kiwi"[3 - i]); - ++i; - } - Expect(i).ToEqual(4u); - }); + It("Can iterate reverse", []() + { + String v{"Kiwi"}; + u32 i = 0; + for (auto rit = v.rbegin(); rit != v.rend(); ++rit) + { + Expect(*rit).ToEqual("Kiwi"[3 - i]); + ++i; + } + Expect(i).ToEqual(4u); + }); - It("Can iterate c-variants", []() - { - String v{"Kiwi"}; - Expect(*v.cbegin()).ToEqual('K'); - Expect(*(v.cend() - 1)).ToEqual('i'); - Expect(*v.crbegin()).ToEqual('i'); - Expect(*(v.crend() - 1)).ToEqual('K'); - }); + It("Can iterate c-variants", []() + { + String v{"Kiwi"}; + Expect(*v.cbegin()).ToEqual('K'); + Expect(*(v.cend() - 1)).ToEqual('i'); + Expect(*v.crbegin()).ToEqual('i'); + Expect(*(v.crend() - 1)).ToEqual('K'); + }); - It("Can mutate through iterators", []() + It("Can mutate through iterators", []() + { + String v{"Kiwi"}; + std::transform(v.begin(), v.end(), v.begin(), [](char c) { - String v{"Kiwi"}; - std::transform(v.begin(), v.end(), v.begin(), [](char c) - { - return char(c + 1); - }); - Expect(v).ToEqual("Ljxj"); + return char(c + 1); }); + Expect(v).ToEqual("Ljxj"); }); + }); - Describe("Capacity", []() + Describe("Capacity", []() + { + It("Can query size and length", []() { - It("Can query size and length", []() - { - String v{"Kiwi"}; - Expect(v.size()).ToEqual(4u); - Expect(v.length()).ToEqual(4u); - Expect(v.empty()).ToBeFalse(); - }); + String v{"Kiwi"}; + Expect(v.size()).ToEqual(4u); + Expect(v.length()).ToEqual(4u); + Expect(v.empty()).ToBeFalse(); + }); - It("Has short string optimization", []() - { - String v{"Kiwi"}; - // Short strings must fit in the internal buffer - Expect(v.capacity() >= 15u).ToBeTrue(); - Expect(v.capacity() <= 32u).ToBeTrue(); - }); + It("Has short string optimization", []() + { + String v{"Kiwi"}; + // Short strings must fit in the internal buffer + Expect(v.capacity() >= 15u).ToBeTrue(); + Expect(v.capacity() <= 32u).ToBeTrue(); + }); - It("Can reserve", []() - { - String v; - v.reserve(100); - Expect(v.capacity() >= 100u).ToBeTrue(); - Expect(v.size()).ToEqual(0u); - v = "Kiwi"; - Expect(v).ToEqual("Kiwi"); - Expect(v.capacity() >= 100u).ToBeTrue(); - }); + It("Can reserve", []() + { + String v; + v.reserve(100); + Expect(v.capacity() >= 100u).ToBeTrue(); + Expect(v.size()).ToEqual(0u); + v = "Kiwi"; + Expect(v).ToEqual("Kiwi"); + Expect(v.capacity() >= 100u).ToBeTrue(); + }); - It("Can shrink to fit", []() - { - String v; - v.reserve(100); - v = "Kiwi"; - v.shrink_to_fit(); - Expect(v).ToEqual("Kiwi"); - Expect(v.capacity() >= 4u).ToBeTrue(); - Expect(v.capacity() < 100u).ToBeTrue(); - }); + It("Can shrink to fit", []() + { + String v; + v.reserve(100); + v = "Kiwi"; + v.shrink_to_fit(); + Expect(v).ToEqual("Kiwi"); + Expect(v.capacity() >= 4u).ToBeTrue(); + Expect(v.capacity() < 100u).ToBeTrue(); + }); - It("Has max size", []() - { - String v; - // Lengths are stored internally as i32 - Expect(v.max_size()).ToEqual(sizet(Limits::Max() - 1)); - }); + It("Has max size", []() + { + String v; + // Lengths are stored internally as i32 + Expect(v.max_size()).ToEqual(sizet(Limits::Max() - 1)); }); + }); - Describe("Modifiers", []() + Describe("Modifiers", []() + { + It("Can clear", []() { - It("Can clear", []() - { - String v{"Kiwi"}; - v.clear(); - Expect(v.empty()).ToBeTrue(); - Expect(v.size()).ToEqual(0u); - Expect(v.c_str()[0]).ToEqual('\0'); - }); + String v{"Kiwi"}; + v.clear(); + Expect(v.empty()).ToBeTrue(); + Expect(v.size()).ToEqual(0u); + Expect(v.c_str()[0]).ToEqual('\0'); + }); - It("Can push and pop back", []() - { - String v{"Ki"}; - v.push_back('w'); - v.push_back('i'); - Expect(v).ToEqual("Kiwi"); - Expect(v.back()).ToEqual('i'); - v.pop_back(); - Expect(v).ToEqual("Kiw"); - v.pop_back(); - v.pop_back(); - v.pop_back(); - Expect(v).ToEqual(""); - Expect(v.empty()).ToBeTrue(); - }); + It("Can push and pop back", []() + { + String v{"Ki"}; + v.push_back('w'); + v.push_back('i'); + Expect(v).ToEqual("Kiwi"); + Expect(v.back()).ToEqual('i'); + v.pop_back(); + Expect(v).ToEqual("Kiw"); + v.pop_back(); + v.pop_back(); + v.pop_back(); + Expect(v).ToEqual(""); + Expect(v.empty()).ToBeTrue(); + }); - It("Can append", []() - { - String v{"Kiwi"}; - v.append("Apple"); - Expect(v).ToEqual("KiwiApple"); - v.append("Orange", 3); - Expect(v).ToEqual("KiwiAppleOra"); - v.append(3, '-'); - Expect(v).ToEqual("KiwiAppleOra---"); - String other{"End"}; - v.append(other); - Expect(v).ToEqual("KiwiAppleOra---End"); - v.append(other, 1, 2); - Expect(v).ToEqual("KiwiAppleOra---Endnd"); - StringView sv{"View"}; - v.append(sv); - Expect(v).ToEqual("KiwiAppleOra---EndndView"); - v.append(sv, 2, 2); - Expect(v).ToEqual("KiwiAppleOra---EndndViewew"); - v.append({'!', '?'}); - Expect(v).ToEqual("KiwiAppleOra---EndndViewew!?"); - }); + It("Can append", []() + { + String v{"Kiwi"}; + v.append("Apple"); + Expect(v).ToEqual("KiwiApple"); + v.append("Orange", 3); + Expect(v).ToEqual("KiwiAppleOra"); + v.append(3, '-'); + Expect(v).ToEqual("KiwiAppleOra---"); + String other{"End"}; + v.append(other); + Expect(v).ToEqual("KiwiAppleOra---End"); + v.append(other, 1, 2); + Expect(v).ToEqual("KiwiAppleOra---Endnd"); + StringView sv{"View"}; + v.append(sv); + Expect(v).ToEqual("KiwiAppleOra---EndndView"); + v.append(sv, 2, 2); + Expect(v).ToEqual("KiwiAppleOra---EndndViewew"); + v.append({'!', '?'}); + Expect(v).ToEqual("KiwiAppleOra---EndndViewew!?"); + }); - It("Can append with operator+=", []() - { - String v{"Kiwi"}; - v += "Apple"; - Expect(v).ToEqual("KiwiApple"); - v += '!'; - Expect(v).ToEqual("KiwiApple!"); - String other{"End"}; - v += other; - Expect(v).ToEqual("KiwiApple!End"); - v += StringView{"View"}; - Expect(v).ToEqual("KiwiApple!EndView"); - v += {'a', 'b'}; - Expect(v).ToEqual("KiwiApple!EndViewab"); - }); + It("Can append with operator+=", []() + { + String v{"Kiwi"}; + v += "Apple"; + Expect(v).ToEqual("KiwiApple"); + v += '!'; + Expect(v).ToEqual("KiwiApple!"); + String other{"End"}; + v += other; + Expect(v).ToEqual("KiwiApple!End"); + v += StringView{"View"}; + Expect(v).ToEqual("KiwiApple!EndView"); + v += {'a', 'b'}; + Expect(v).ToEqual("KiwiApple!EndViewab"); + }); - It("Can insert", []() - { - String v{"KiwiApple"}; - v.insert(4, "Orange"); - Expect(v).ToEqual("KiwiOrangeApple"); - v.insert(0, "-"); - Expect(v).ToEqual("-KiwiOrangeApple"); - v.insert(v.size(), "!"); - Expect(v).ToEqual("-KiwiOrangeApple!"); - v.insert(0, 3, '='); - Expect(v).ToEqual("===-KiwiOrangeApple!"); - String other{"XX"}; - v.insert(3, other); - Expect(v).ToEqual("===XX-KiwiOrangeApple!"); - StringView sv{"YY"}; - v.insert(5, sv); - Expect(v).ToEqual("===XXYY-KiwiOrangeApple!"); - v.insert(0, 2, 'Z'); - Expect(v).ToEqual("ZZ===XXYY-KiwiOrangeApple!"); - }); + It("Can insert", []() + { + String v{"KiwiApple"}; + v.insert(4, "Orange"); + Expect(v).ToEqual("KiwiOrangeApple"); + v.insert(0, "-"); + Expect(v).ToEqual("-KiwiOrangeApple"); + v.insert(v.size(), "!"); + Expect(v).ToEqual("-KiwiOrangeApple!"); + v.insert(0, 3, '='); + Expect(v).ToEqual("===-KiwiOrangeApple!"); + String other{"XX"}; + v.insert(3, other); + Expect(v).ToEqual("===XX-KiwiOrangeApple!"); + StringView sv{"YY"}; + v.insert(5, sv); + Expect(v).ToEqual("===XXYY-KiwiOrangeApple!"); + v.insert(0, 2, 'Z'); + Expect(v).ToEqual("ZZ===XXYY-KiwiOrangeApple!"); + }); - It("Can insert with iterator", []() - { - String v{"Kiwi"}; - auto it = v.insert(v.begin() + 2, '-'); - Expect(*it).ToEqual('-'); - Expect(v).ToEqual("Ki-wi"); - v.insert(v.end(), 3, '!'); - Expect(v).ToEqual("Ki-wi!!!"); - String other{"AB"}; - v.insert(v.begin(), other.begin(), other.end()); - Expect(v).ToEqual("ABKi-wi!!!"); - v.insert(v.begin() + 2, {'x', 'y'}); - Expect(v).ToEqual("ABxyKi-wi!!!"); - }); + It("Can insert with iterator", []() + { + String v{"Kiwi"}; + auto it = v.insert(v.begin() + 2, '-'); + Expect(*it).ToEqual('-'); + Expect(v).ToEqual("Ki-wi"); + v.insert(v.end(), 3, '!'); + Expect(v).ToEqual("Ki-wi!!!"); + String other{"AB"}; + v.insert(v.begin(), other.begin(), other.end()); + Expect(v).ToEqual("ABKi-wi!!!"); + v.insert(v.begin() + 2, {'x', 'y'}); + Expect(v).ToEqual("ABxyKi-wi!!!"); + }); - It("Can erase", []() - { - String v{"KiwiApple"}; - v.erase(4, 5); - Expect(v).ToEqual("Kiwi"); - v.erase(2); - Expect(v).ToEqual("Ki"); - v.erase(0, 1); - Expect(v).ToEqual("i"); - v.erase(0, 10); - Expect(v).ToEqual(""); - }); + It("Can erase", []() + { + String v{"KiwiApple"}; + v.erase(4, 5); + Expect(v).ToEqual("Kiwi"); + v.erase(2); + Expect(v).ToEqual("Ki"); + v.erase(0, 1); + Expect(v).ToEqual("i"); + v.erase(0, 10); + Expect(v).ToEqual(""); + }); - It("Can erase with iterator", []() - { - String v{"Kiwi"}; - auto it = v.erase(v.begin()); - Expect(*it).ToEqual('i'); - Expect(v).ToEqual("iwi"); - v.erase(v.begin() + 1, v.end()); - Expect(v).ToEqual("i"); - }); + It("Can erase with iterator", []() + { + String v{"Kiwi"}; + auto it = v.erase(v.begin()); + Expect(*it).ToEqual('i'); + Expect(v).ToEqual("iwi"); + v.erase(v.begin() + 1, v.end()); + Expect(v).ToEqual("i"); + }); - It("Can replace", []() - { - String v{"KiwiApple"}; - v.replace(0, 4, "Orange"); - Expect(v).ToEqual("OrangeApple"); - v.replace(0, 6, "X"); - Expect(v).ToEqual("XApple"); - v.replace(v.size() - 3, 3, "Z"); - Expect(v).ToEqual("XApZ"); - String other{"Kiwi"}; - v.replace(0, 4, other); - Expect(v).ToEqual("Kiwi"); - StringView sv{"Two"}; - v.replace(0, 4, sv); - Expect(v).ToEqual("Two"); - v.replace(0, 3, 2, 'y'); - Expect(v).ToEqual("yy"); - }); + It("Can replace", []() + { + String v{"KiwiApple"}; + v.replace(0, 4, "Orange"); + Expect(v).ToEqual("OrangeApple"); + v.replace(0, 6, "X"); + Expect(v).ToEqual("XApple"); + v.replace(v.size() - 3, 3, "Z"); + Expect(v).ToEqual("XApZ"); + String other{"Kiwi"}; + v.replace(0, 4, other); + Expect(v).ToEqual("Kiwi"); + StringView sv{"Two"}; + v.replace(0, 4, sv); + Expect(v).ToEqual("Two"); + v.replace(0, 3, 2, 'y'); + Expect(v).ToEqual("yy"); + }); - It("Can replace with iterators", []() - { - String v{"KiwiApple"}; - v.replace(v.begin(), v.begin() + 4, "Orange"); - Expect(v).ToEqual("OrangeApple"); - }); + It("Can replace with iterators", []() + { + String v{"KiwiApple"}; + v.replace(v.begin(), v.begin() + 4, "Orange"); + Expect(v).ToEqual("OrangeApple"); + }); - It("Can resize", []() - { - String v{"Kiwi"}; - v.resize(2); - Expect(v).ToEqual("Ki"); - v.resize(4); - Expect(v.size()).ToEqual(4u); - Expect(v[2]).ToEqual('\0'); - Expect(v[3]).ToEqual('\0'); - v.resize(6, 'x'); - Expect(v[4]).ToEqual('x'); - Expect(v[5]).ToEqual('x'); - Expect(v.size()).ToEqual(6u); - }); + It("Can resize", []() + { + String v{"Kiwi"}; + v.resize(2); + Expect(v).ToEqual("Ki"); + v.resize(4); + Expect(v.size()).ToEqual(4u); + Expect(v[2]).ToEqual('\0'); + Expect(v[3]).ToEqual('\0'); + v.resize(6, 'x'); + Expect(v[4]).ToEqual('x'); + Expect(v[5]).ToEqual('x'); + Expect(v.size()).ToEqual(6u); + }); - It("Can swap", []() - { - String a{"Kiwi"}; - String b{"Apple"}; - a.swap(b); - Expect(a).ToEqual("Apple"); - Expect(b).ToEqual("Kiwi"); - }); + It("Can swap", []() + { + String a{"Kiwi"}; + String b{"Apple"}; + a.swap(b); + Expect(a).ToEqual("Apple"); + Expect(b).ToEqual("Kiwi"); + }); - It("Can append from self", []() - { - String v{longText}; - v.append(v.c_str()); - Expect(v).ToEqual(std::string{longText} + std::string{longText}); - }); + It("Can append from self", []() + { + String v{longText}; + v.append(v.c_str()); + Expect(v).ToEqual(std::string{longText} + std::string{longText}); + }); - It("Can append self substring", []() - { - String v{longText}; - v.append(v.c_str() + 5); - Expect(v).ToEqual(std::string{longText} + std::string{longText.substr(5)}); - }); + It("Can append self substring", []() + { + String v{longText}; + v.append(v.c_str() + 5); + Expect(v).ToEqual(std::string{longText} + std::string{longText.substr(5)}); + }); - It("Can insert from self", []() - { - String v{longText}; - v.insert(0, v.c_str()); - Expect(v).ToEqual(std::string{longText} + std::string{longText}); - }); + It("Can insert from self", []() + { + String v{longText}; + v.insert(0, v.c_str()); + Expect(v).ToEqual(std::string{longText} + std::string{longText}); + }); - It("Can insert self substring", []() - { - String v{longText}; - v.insert(4, v.c_str() + 5); - Expect(v).ToEqual(std::string{longText.substr(0, 4)} + std::string{longText.substr(5)} - + std::string{longText.substr(4)}); - }); + It("Can insert self substring", []() + { + String v{longText}; + v.insert(4, v.c_str() + 5); + Expect(v).ToEqual(std::string{longText.substr(0, 4)} + std::string{longText.substr(5)} + + std::string{longText.substr(4)}); + }); - It("Can replace with self", []() - { - String v{longText}; - v.replace(0, 4, v.c_str()); - Expect(v).ToEqual(std::string{longText} + std::string{longText.substr(4)}); - }); + It("Can replace with self", []() + { + String v{longText}; + v.replace(0, 4, v.c_str()); + Expect(v).ToEqual(std::string{longText} + std::string{longText.substr(4)}); + }); - It("Can replace self substring with count", []() - { - String v{longText}; - v.replace(5, 10, v.c_str() + 2, 5); - Expect(v).ToEqual(std::string{longText.substr(0, 5)} + "23456" - + std::string{longText.substr(15)}); - }); + It("Can replace self substring with count", []() + { + String v{longText}; + v.replace(5, 10, v.c_str() + 2, 5); + Expect(v).ToEqual(std::string{longText.substr(0, 5)} + "23456" + + std::string{longText.substr(15)}); }); + }); - Describe("Operations", []() + Describe("Operations", []() + { + It("Can get substr", []() { - It("Can get substr", []() - { - String v{"KiwiApple"}; - Expect(v.substr()).ToEqual("KiwiApple"); - Expect(v.substr(4)).ToEqual("Apple"); - Expect(v.substr(4, 3)).ToEqual("App"); - Expect(v.substr(0, 100)).ToEqual("KiwiApple"); - }); + String v{"KiwiApple"}; + Expect(v.substr()).ToEqual("KiwiApple"); + Expect(v.substr(4)).ToEqual("Apple"); + Expect(v.substr(4, 3)).ToEqual("App"); + Expect(v.substr(0, 100)).ToEqual("KiwiApple"); + }); - It("Can copy out", []() - { - String v{"KiwiApple"}; - char buffer[16]{}; - const auto count = v.copy(buffer, 4, 4); - Expect(count).ToEqual(4u); - Expect(buffer).ToEqual("Appl"); - buffer[count] = '\0'; - }); + It("Can copy out", []() + { + String v{"KiwiApple"}; + char buffer[16]{}; + const auto count = v.copy(buffer, 4, 4); + Expect(count).ToEqual(4u); + Expect(buffer).ToEqual("Appl"); + buffer[count] = '\0'; + }); - It("Can compare", []() - { - String v{"Kiwi"}; - String other{"Kiwi"}; - String apple{"Apple"}; - Expect(v.compare(other)).ToEqual(0); - Expect(v.compare(apple) > 0).ToBeTrue(); - Expect(apple.compare(v) < 0).ToBeTrue(); - Expect(v.compare("Kiwi")).ToEqual(0); - Expect(v.compare("Kiwi2") < 0).ToBeTrue(); - Expect(v.compare(StringView{"Kiwi"})).ToEqual(0); - Expect(v.compare(0, 2, String{"Ki"})).ToEqual(0); - Expect(v.compare(2, 2, String{"wi"})).ToEqual(0); - }); + It("Can compare", []() + { + String v{"Kiwi"}; + String other{"Kiwi"}; + String apple{"Apple"}; + Expect(v.compare(other)).ToEqual(0); + Expect(v.compare(apple) > 0).ToBeTrue(); + Expect(apple.compare(v) < 0).ToBeTrue(); + Expect(v.compare("Kiwi")).ToEqual(0); + Expect(v.compare("Kiwi2") < 0).ToBeTrue(); + Expect(v.compare(StringView{"Kiwi"})).ToEqual(0); + Expect(v.compare(0, 2, String{"Ki"})).ToEqual(0); + Expect(v.compare(2, 2, String{"wi"})).ToEqual(0); + }); - It("Can check prefix and suffix", []() - { - String v{"KiwiApple"}; - Expect(v.starts_with("Kiwi")).ToBeTrue(); - Expect(v.starts_with('K')).ToBeTrue(); - Expect(v.starts_with(StringView{"Ki"})).ToBeTrue(); - Expect(v.starts_with("Apple")).ToBeFalse(); - Expect(v.ends_with("Apple")).ToBeTrue(); - Expect(v.ends_with('e')).ToBeTrue(); - Expect(v.ends_with(StringView{"le"})).ToBeTrue(); - Expect(v.ends_with("Kiwi")).ToBeFalse(); - }); + It("Can check prefix and suffix", []() + { + String v{"KiwiApple"}; + Expect(v.starts_with("Kiwi")).ToBeTrue(); + Expect(v.starts_with('K')).ToBeTrue(); + Expect(v.starts_with(StringView{"Ki"})).ToBeTrue(); + Expect(v.starts_with("Apple")).ToBeFalse(); + Expect(v.ends_with("Apple")).ToBeTrue(); + Expect(v.ends_with('e')).ToBeTrue(); + Expect(v.ends_with(StringView{"le"})).ToBeTrue(); + Expect(v.ends_with("Kiwi")).ToBeFalse(); + }); - It("Can check contains", []() - { - String v{"KiwiApple"}; - Expect(v.contains("wiA")).ToBeTrue(); - Expect(v.contains('A')).ToBeTrue(); - Expect(v.contains(StringView{"zzz"})).ToBeFalse(); - Expect(v.contains('z')).ToBeFalse(); - }); + It("Can check contains", []() + { + String v{"KiwiApple"}; + Expect(v.contains("wiA")).ToBeTrue(); + Expect(v.contains('A')).ToBeTrue(); + Expect(v.contains(StringView{"zzz"})).ToBeFalse(); + Expect(v.contains('z')).ToBeFalse(); + }); - It("Can find", []() - { - String v{"KiwiKiwi"}; - Expect(v.find("Kiwi")).ToEqual(0u); - Expect(v.find("Kiwi", 1)).ToEqual(4u); - Expect(v.find("Kiwi", 5)).ToEqual(String::npos); - Expect(v.find('i')).ToEqual(1u); - Expect(v.find('i', 6)).ToEqual(7u); - Expect(v.find('z')).ToEqual(String::npos); - Expect(v.find(String{"Kiwi"})).ToEqual(0u); - Expect(v.find(StringView{"Kiwi"})).ToEqual(0u); - }); + It("Can find", []() + { + String v{"KiwiKiwi"}; + Expect(v.find("Kiwi")).ToEqual(0u); + Expect(v.find("Kiwi", 1)).ToEqual(4u); + Expect(v.find("Kiwi", 5)).ToEqual(String::npos); + Expect(v.find('i')).ToEqual(1u); + Expect(v.find('i', 6)).ToEqual(7u); + Expect(v.find('z')).ToEqual(String::npos); + Expect(v.find(String{"Kiwi"})).ToEqual(0u); + Expect(v.find(StringView{"Kiwi"})).ToEqual(0u); + }); - It("Can rfind", []() - { - String v{"KiwiKiwi"}; - Expect(v.rfind("Kiwi")).ToEqual(4u); - Expect(v.rfind("Kiwi", 3)).ToEqual(0u); - Expect(v.rfind('i')).ToEqual(7u); - Expect(v.rfind('i', 5)).ToEqual(5u); - Expect(v.rfind('z')).ToEqual(String::npos); - Expect(v.rfind(String{"Kiwi"})).ToEqual(4u); - Expect(v.rfind(StringView{"Kiwi"})).ToEqual(4u); - }); + It("Can rfind", []() + { + String v{"KiwiKiwi"}; + Expect(v.rfind("Kiwi")).ToEqual(4u); + Expect(v.rfind("Kiwi", 3)).ToEqual(0u); + Expect(v.rfind('i')).ToEqual(7u); + Expect(v.rfind('i', 5)).ToEqual(5u); + Expect(v.rfind('z')).ToEqual(String::npos); + Expect(v.rfind(String{"Kiwi"})).ToEqual(4u); + Expect(v.rfind(StringView{"Kiwi"})).ToEqual(4u); + }); - It("Can find first of", []() - { - String v{"KiwiApple"}; - Expect(v.find_first_of("pl")).ToEqual(5u); - Expect(v.find_first_of("pl", 6)).ToEqual(6u); - Expect(v.find_first_of('z')).ToEqual(String::npos); - Expect(v.find_first_of("xyz")).ToEqual(String::npos); - Expect(v.find_first_of(StringView{"Ap"})).ToEqual(4u); - }); + It("Can find first of", []() + { + String v{"KiwiApple"}; + Expect(v.find_first_of("pl")).ToEqual(5u); + Expect(v.find_first_of("pl", 6)).ToEqual(6u); + Expect(v.find_first_of('z')).ToEqual(String::npos); + Expect(v.find_first_of("xyz")).ToEqual(String::npos); + Expect(v.find_first_of(StringView{"Ap"})).ToEqual(4u); + }); - It("Can find last of", []() - { - String v{"KiwiApple"}; - Expect(v.find_last_of("pl")).ToEqual(7u); - Expect(v.find_last_of("pl", 6)).ToEqual(6u); - Expect(v.find_last_of('z')).ToEqual(String::npos); - Expect(v.find_last_of(StringView{"Ap"})).ToEqual(6u); - }); + It("Can find last of", []() + { + String v{"KiwiApple"}; + Expect(v.find_last_of("pl")).ToEqual(7u); + Expect(v.find_last_of("pl", 6)).ToEqual(6u); + Expect(v.find_last_of('z')).ToEqual(String::npos); + Expect(v.find_last_of(StringView{"Ap"})).ToEqual(6u); + }); - It("Can find first not of", []() - { - String v{"aaab"}; - Expect(v.find_first_not_of("a")).ToEqual(3u); - Expect(v.find_first_not_of("ab")).ToEqual(String::npos); - Expect(v.find_first_not_of('a')).ToEqual(3u); - Expect(v.find_first_not_of("ab", 3)).ToEqual(String::npos); - }); + It("Can find first not of", []() + { + String v{"aaab"}; + Expect(v.find_first_not_of("a")).ToEqual(3u); + Expect(v.find_first_not_of("ab")).ToEqual(String::npos); + Expect(v.find_first_not_of('a')).ToEqual(3u); + Expect(v.find_first_not_of("ab", 3)).ToEqual(String::npos); + }); - It("Can find last not of", []() - { - String v{"baaa"}; - Expect(v.find_last_not_of("a")).ToEqual(0u); - Expect(v.find_last_not_of("ab")).ToEqual(String::npos); - Expect(v.find_last_not_of('a')).ToEqual(0u); - Expect(v.find_last_not_of("ab", 0)).ToEqual(String::npos); - }); + It("Can find last not of", []() + { + String v{"baaa"}; + Expect(v.find_last_not_of("a")).ToEqual(0u); + Expect(v.find_last_not_of("ab")).ToEqual(String::npos); + Expect(v.find_last_not_of('a')).ToEqual(0u); + Expect(v.find_last_not_of("ab", 0)).ToEqual(String::npos); + }); - It("Has npos", []() - { - Expect(String::npos).ToEqual(sizet(-1)); - Expect(StringView::npos).ToEqual(String::npos); - }); + It("Has npos", []() + { + Expect(String::npos).ToEqual(sizet(-1)); + Expect(StringView::npos).ToEqual(String::npos); }); + }); - Describe("Operators", []() + Describe("Operators", []() + { + It("Can concatenate", []() { - It("Can concatenate", []() - { - String a{"Kiwi"}; - String b{"Apple"}; - Expect(a + b).ToEqual("KiwiApple"); - Expect(a + "X").ToEqual("KiwiX"); - Expect("X" + a).ToEqual("XKiwi"); - Expect(a + '!').ToEqual("Kiwi!"); - Expect('!' + a).ToEqual("!Kiwi"); - Expect(a + StringView{"V"}).ToEqual("KiwiV"); - Expect(StringView{"V"} + a).ToEqual("VKiwi"); - }); + String a{"Kiwi"}; + String b{"Apple"}; + Expect(a + b).ToEqual("KiwiApple"); + Expect(a + "X").ToEqual("KiwiX"); + Expect("X" + a).ToEqual("XKiwi"); + Expect(a + '!').ToEqual("Kiwi!"); + Expect('!' + a).ToEqual("!Kiwi"); + Expect(a + StringView{"V"}).ToEqual("KiwiV"); + Expect(StringView{"V"} + a).ToEqual("VKiwi"); + }); - It("Can chain concatenate", []() - { - String a{"Kiwi"}; - String result = a + " " + "Apple" + '!'; - Expect(result).ToEqual("Kiwi Apple!"); - }); + It("Can chain concatenate", []() + { + String a{"Kiwi"}; + String result = a + " " + "Apple" + '!'; + Expect(result).ToEqual("Kiwi Apple!"); + }); - It("Can compare with other types", []() - { - String v{"Kiwi"}; - Expect(v == String{"Kiwi"}).ToBeTrue(); - Expect(v != String{"Apple"}).ToBeTrue(); - Expect(v == "Kiwi").ToBeTrue(); - Expect(v != "Apple").ToBeTrue(); - Expect("Kiwi" == v).ToBeTrue(); - Expect("Apple" != v).ToBeTrue(); - Expect(v < "Lime").ToBeTrue(); - Expect("Lime" > v).ToBeTrue(); - Expect(v <= String{"Kiwi"}).ToBeTrue(); - Expect(v >= String{"Kiwi"}).ToBeTrue(); - Expect(v == StringView{"Kiwi"}).ToBeTrue(); - Expect(StringView{"Kiwi"} == v).ToBeTrue(); - Expect(v != StringView{"Apple"}).ToBeTrue(); - Expect(StringView{"Apple"} != v).ToBeTrue(); - Expect(v < StringView{"Lime"}).ToBeTrue(); - Expect(StringView{"Lime"} > v).ToBeTrue(); - }); + It("Can compare with other types", []() + { + String v{"Kiwi"}; + Expect(v == String{"Kiwi"}).ToBeTrue(); + Expect(v != String{"Apple"}).ToBeTrue(); + Expect(v == "Kiwi").ToBeTrue(); + Expect(v != "Apple").ToBeTrue(); + Expect("Kiwi" == v).ToBeTrue(); + Expect("Apple" != v).ToBeTrue(); + Expect(v < "Lime").ToBeTrue(); + Expect("Lime" > v).ToBeTrue(); + Expect(v <= String{"Kiwi"}).ToBeTrue(); + Expect(v >= String{"Kiwi"}).ToBeTrue(); + Expect(v == StringView{"Kiwi"}).ToBeTrue(); + Expect(StringView{"Kiwi"} == v).ToBeTrue(); + Expect(v != StringView{"Apple"}).ToBeTrue(); + Expect(StringView{"Apple"} != v).ToBeTrue(); + Expect(v < StringView{"Lime"}).ToBeTrue(); + Expect(StringView{"Lime"} > v).ToBeTrue(); + }); - It("Can three-way compare", []() - { - String a{"Kiwi"}; - String b{"Lime"}; - Expect((a <=> b) < 0).ToBeTrue(); - Expect((b <=> a) > 0).ToBeTrue(); - Expect((a <=> String{"Kiwi"}) == 0).ToBeTrue(); - Expect((a <=> "Kiwi") == 0).ToBeTrue(); - }); + It("Can three-way compare", []() + { + String a{"Kiwi"}; + String b{"Lime"}; + Expect((a <=> b) < 0).ToBeTrue(); + Expect((b <=> a) > 0).ToBeTrue(); + Expect((a <=> String{"Kiwi"}) == 0).ToBeTrue(); + Expect((a <=> "Kiwi") == 0).ToBeTrue(); }); + }); - Describe("Memory", []() + Describe("Memory", []() + { + It("Keeps data valid when growing", []() { - It("Keeps data valid when growing", []() - { - String v; - for (char c = 'a'; c <= 'z'; ++c) - { - v.push_back(c); - } - Expect(v.size()).ToEqual(26u); - Expect(v).ToEqual("abcdefghijklmnopqrstuvwxyz"); - Expect(v.c_str()[26]).ToEqual('\0'); - }); + String v; + for (char c = 'a'; c <= 'z'; ++c) + { + v.push_back(c); + } + Expect(v.size()).ToEqual(26u); + Expect(v).ToEqual("abcdefghijklmnopqrstuvwxyz"); + Expect(v.c_str()[26]).ToEqual('\0'); + }); - It("Can reuse capacity", []() + It("Can reuse capacity", []() + { + String v; + v.reserve(1000); + const auto cap = v.capacity(); + for (u32 i = 0; i < 100; ++i) { - String v; - v.reserve(1000); - const auto cap = v.capacity(); - for (u32 i = 0; i < 100; ++i) - { - v.assign("KiwiAppleOrangeBanana"); - v.clear(); - } - Expect(v.capacity()).ToEqual(cap); - }); + v.assign("KiwiAppleOrangeBanana"); + v.clear(); + } + Expect(v.capacity()).ToEqual(cap); + }); - It("Is valid after move assignment", []() - { - String a{"Kiwi"}; - String b; - b = Move(a); - Expect(b).ToEqual("Kiwi"); - a = "Reused"; - Expect(a).ToEqual("Reused"); - }); + It("Is valid after move assignment", []() + { + String a{"Kiwi"}; + String b; + b = Move(a); + Expect(b).ToEqual("Kiwi"); + a = "Reused"; + Expect(a).ToEqual("Reused"); }); + }); - Describe("Format & Hash", []() + Describe("Format & Hash", []() + { + It("Can be formatted", []() { - It("Can be formatted", []() - { - String v{"Kiwi"}; - Expect(std::format("{}", v)).ToEqual("Kiwi"); - Expect(Format("{}-{}", v, 5)).ToEqual("Kiwi-5"); - String out; - FormatTo(out, "{}!", v); - Expect(out).ToEqual("Kiwi!"); - }); + String v{"Kiwi"}; + Expect(std::format("{}", v)).ToEqual("Kiwi"); + Expect(Format("{}-{}", v, 5)).ToEqual("Kiwi-5"); + String out; + FormatTo(out, "{}!", v); + Expect(out).ToEqual("Kiwi!"); + }); - It("Can be hashed", []() - { - String v{"Kiwi"}; - Expect(GetHash(v)).ToEqual(GetStringHash("Kiwi")); - Expect(GetHash(StringView{"Kiwi"})).ToEqual(GetHash(v)); - }); + It("Can be hashed", []() + { + String v{"Kiwi"}; + Expect(GetHash(v)).ToEqual(GetStringHash("Kiwi")); + Expect(GetHash(StringView{"Kiwi"})).ToEqual(GetHash(v)); }); + }); - Describe("Arena", []() + Describe("Arena", []() + { + It("Can default construct on an arena", []() { - It("Can default construct on an arena", []() - { - MonoLinearArena arena{Memory::KB * 4}; - String v{arena}; - Expect(v.empty()).ToBeTrue(); - Expect(&v.GetArena()).ToEqual(static_cast(&arena)); - // Short strings still use the inline buffer - v = "Kiwi"; - Expect(v).ToEqual("Kiwi"); - Expect(v.capacity() <= 32u).ToBeTrue(); - }); + MonoLinearArena arena{Memory::KB * 4}; + String v{arena}; + Expect(v.empty()).ToBeTrue(); + Expect(&v.GetArena()).ToEqual(static_cast(&arena)); + // Short strings still use the inline buffer + v = "Kiwi"; + Expect(v).ToEqual("Kiwi"); + Expect(v.capacity() <= 32u).ToBeTrue(); + }); - It("Can allocate on an arena", []() - { - MonoLinearArena arena{Memory::KB * 4}; - String v{arena, arenaLongText}; - Expect(v).ToEqual(arenaLongText); - Expect(&v.GetArena()).ToEqual(static_cast(&arena)); - // Long strings must allocate on the arena, not the current arena - Expect(v.capacity() >= v.size()).ToBeTrue(); - }); + It("Can allocate on an arena", []() + { + MonoLinearArena arena{Memory::KB * 4}; + String v{arena, arenaLongText}; + Expect(v).ToEqual(arenaLongText); + Expect(&v.GetArena()).ToEqual(static_cast(&arena)); + // Long strings must allocate on the arena, not the current arena + Expect(v.capacity() >= v.size()).ToBeTrue(); + }); - It("Can construct with count and char on an arena", []() - { - MonoLinearArena arena{Memory::KB * 4}; - String v{arena, 64, 'x'}; - Expect(v.size()).ToEqual(64u); - Expect(&v.GetArena()).ToEqual(static_cast(&arena)); - }); + It("Can construct with count and char on an arena", []() + { + MonoLinearArena arena{Memory::KB * 4}; + String v{arena, 64, 'x'}; + Expect(v.size()).ToEqual(64u); + Expect(&v.GetArena()).ToEqual(static_cast(&arena)); + }); - It("Can copy into an arena", []() - { - MonoLinearArena arena{Memory::KB * 4}; - String original{arenaLongText}; - String v{arena, original}; - Expect(v).ToEqual(original); - Expect(&v.GetArena()).ToEqual(static_cast(&arena)); - }); + It("Can copy into an arena", []() + { + MonoLinearArena arena{Memory::KB * 4}; + String original{arenaLongText}; + String v{arena, original}; + Expect(v).ToEqual(original); + Expect(&v.GetArena()).ToEqual(static_cast(&arena)); + }); - It("Keeps its arena when assigned", []() - { - MonoLinearArena arena{Memory::KB * 4}; - String v{arena}; - v.assign(arenaLongText); - v.append(" with some extra content to force a reallocation"); - Expect(&v.GetArena()).ToEqual(static_cast(&arena)); - Expect(v.starts_with("This string")).ToBeTrue(); - }); + It("Keeps its arena when assigned", []() + { + MonoLinearArena arena{Memory::KB * 4}; + String v{arena}; + v.assign(arenaLongText); + v.append(" with some extra content to force a reallocation"); + Expect(&v.GetArena()).ToEqual(static_cast(&arena)); + Expect(v.starts_with("This string")).ToBeTrue(); }); + }); - Describe("Strings helpers", []() + Describe("Strings helpers", []() + { + It("RemoveFromStart", []() { - It("RemoveFromStart", []() - { - String v{"KiwiApple"}; - Strings::RemoveFromStart(v, 4); - Expect(v).ToEqual("Apple"); - Strings::RemoveFromStart(v, 100); - Expect(v.empty()).ToBeTrue(); - }); + String v{"KiwiApple"}; + Strings::RemoveFromStart(v, 4); + Expect(v).ToEqual("Apple"); + Strings::RemoveFromStart(v, 100); + Expect(v.empty()).ToBeTrue(); + }); - It("RemoveFromEnd", []() - { - String v{"KiwiApple"}; - Strings::RemoveFromEnd(v, 5); - Expect(v).ToEqual("Kiwi"); - Strings::RemoveFromEnd(v, StringView{"wi"}); - Expect(v).ToEqual("Ki"); - Strings::RemoveFromEnd(v, 100); - Expect(v.empty()).ToBeTrue(); - }); + It("RemoveFromEnd", []() + { + String v{"KiwiApple"}; + Strings::RemoveFromEnd(v, 5); + Expect(v).ToEqual("Kiwi"); + Strings::RemoveFromEnd(v, StringView{"wi"}); + Expect(v).ToEqual("Ki"); + Strings::RemoveFromEnd(v, 100); + Expect(v.empty()).ToBeTrue(); + }); - It("RemoveCharFromEnd", []() - { - String v{"Kiwi!"}; - Expect(Strings::RemoveCharFromEnd(v, '!')).ToBeTrue(); - Expect(v).ToEqual("Kiwi"); - Expect(Strings::RemoveCharFromEnd(v, '!')).ToBeFalse(); - Expect(v).ToEqual("Kiwi"); - }); + It("RemoveCharFromEnd", []() + { + String v{"Kiwi!"}; + Expect(Strings::RemoveCharFromEnd(v, '!')).ToBeTrue(); + Expect(v).ToEqual("Kiwi"); + Expect(Strings::RemoveCharFromEnd(v, '!')).ToBeFalse(); + Expect(v).ToEqual("Kiwi"); + }); - It("ToSentenceCase", []() - { - Expect(Strings::ToSentenceCase("")).ToEqual(""); - Expect(Strings::ToSentenceCase("papa")).ToEqual("Papa"); - Expect(Strings::ToSentenceCase("papa ")).ToEqual("Papa "); - Expect(Strings::ToSentenceCase("papa3")).ToEqual("Papa 3"); - Expect(Strings::ToSentenceCase("MisterPotato")).ToEqual("Mister Potato"); - }); + It("ToSentenceCase", []() + { + Expect(Strings::ToSentenceCase("")).ToEqual(""); + Expect(Strings::ToSentenceCase("papa")).ToEqual("Papa"); + Expect(Strings::ToSentenceCase("papa ")).ToEqual("Papa "); + Expect(Strings::ToSentenceCase("papa3")).ToEqual("Papa 3"); + Expect(Strings::ToSentenceCase("MisterPotato")).ToEqual("Mister Potato"); + }); - It("Convert u16 to u8", []() - { - TString utf16string{0x41, 0x0448, 0x65e5, 0xd834, 0xdd1e}; - TString u = Strings::Convert>(utf16string); - Expect(u.size()).ToEqual(10u); - }); - It("Convert u8 to u16", []() - { - TString utf8_with_surrogates = "\xe6\x97\xa5\xd1\x88\xf0\x9d\x84\x9e"; - TString utf16result = - Strings::Convert>(utf8_with_surrogates); - Expect(utf16result.size()).ToEqual(4u); - Expect(utf16result[2] == 0xd834).ToBeTrue(); - Expect(utf16result[3] == 0xdd1e).ToBeTrue(); - }); - It("Convert u32 to u8", []() - { - TString utf32string = {0x448, 0x65E5, 0x10346}; - TString utf8result = Strings::Convert>(utf32string); - Expect(utf8result.size()).ToEqual(9u); - }); - It("Convert u8 to u32", []() - { - TString twochars = "\xe6\x97\xa5\xd1\x88"; - TString utf32result = Strings::Convert>(twochars); - Expect(utf32result.size()).ToEqual(2u); - }); + It("Convert u16 to u8", []() + { + TString utf16string{0x41, 0x0448, 0x65e5, 0xd834, 0xdd1e}; + TString u = Strings::Convert>(utf16string); + Expect(u.size()).ToEqual(10u); + }); + It("Convert u8 to u16", []() + { + TString utf8_with_surrogates = "\xe6\x97\xa5\xd1\x88\xf0\x9d\x84\x9e"; + TString utf16result = + Strings::Convert>(utf8_with_surrogates); + Expect(utf16result.size()).ToEqual(4u); + Expect(utf16result[2] == 0xd834).ToBeTrue(); + Expect(utf16result[3] == 0xdd1e).ToBeTrue(); + }); + It("Convert u32 to u8", []() + { + TString utf32string = {0x448, 0x65E5, 0x10346}; + TString utf8result = Strings::Convert>(utf32string); + Expect(utf8result.size()).ToEqual(9u); + }); + It("Convert u8 to u32", []() + { + TString twochars = "\xe6\x97\xa5\xd1\x88"; + TString utf32result = Strings::Convert>(twochars); + Expect(utf32result.size()).ToEqual(2u); }); }); }); -} +}); +return true; +}(); +} // namespace diff --git a/Tests/Core/StringView.spec.cpp b/Tests/Core/StringView.spec.cpp index 4ba52257..cf5e324b 100644 --- a/Tests/Core/StringView.spec.cpp +++ b/Tests/Core/StringView.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include #include @@ -8,107 +8,112 @@ using namespace p; -void RegisterCoreStringViewTests() +namespace { - Spec("Strings", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("Strings", []() +{ + Describe("StringView", []() { - Describe("StringView", []() + It("Can assign from literal", []() { - It("Can assign from literal", []() - { - StringView v{"Kiwi"}; - Expect(v).ToEqual("Kiwi"); - Expect(v.size()).ToEqual(4); - }); + StringView v{"Kiwi"}; + Expect(v).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4); + }); - It("Can assign from string", []() - { - String str{"Kiwi"}; - StringView v{str}; - Expect(v).ToEqual("Kiwi"); - Expect(v.size()).ToEqual(4); - }); + It("Can assign from string", []() + { + String str{"Kiwi"}; + StringView v{str}; + Expect(v).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4); + }); - It("Can copy empty", []() - { - StringView str{}; - StringView str2{" "}; - Expect(str.empty()).ToEqual(true); - Expect((u8*)str.data()).ToEqual(nullptr); - Expect(str2.empty()).ToEqual(false); - Expect((u8*)str2.data()).ToNotEqual(nullptr); - str2 = str; - Expect(str2.empty()).ToEqual(true); - Expect((u8*)str2.data()).ToEqual(nullptr); - }); + It("Can copy empty", []() + { + StringView str{}; + StringView str2{" "}; + Expect(str.empty()).ToEqual(true); + Expect((u8*)str.data()).ToEqual(nullptr); + Expect(str2.empty()).ToEqual(false); + Expect((u8*)str2.data()).ToNotEqual(nullptr); + str2 = str; + Expect(str2.empty()).ToEqual(true); + Expect((u8*)str2.data()).ToEqual(nullptr); + }); - It("Can retrieve string data", []() - { - StringView v{"Kiwi"}; - Expect(v.data()).ToEqual("Kiwi"); - Expect(v.size()).ToEqual(4); - StringView v2{}; - Expect((u8*)v2.data()).ToEqual(nullptr); - Expect(v2.size()).ToEqual(0); - }); + It("Can retrieve string data", []() + { + StringView v{"Kiwi"}; + Expect(v.data()).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4); + StringView v2{}; + Expect((u8*)v2.data()).ToEqual(nullptr); + Expect(v2.size()).ToEqual(0); + }); - It("Can compare", []() - { - StringView vKiwi{"Kiwi"}; - StringView vKiwi2{"Kiwi"}; - StringView vApple{"Apple"}; - Expect(vKiwi).ToEqual(vKiwi2); - Expect(vKiwi).ToNotEqual(vApple); - }); + It("Can compare", []() + { + StringView vKiwi{"Kiwi"}; + StringView vKiwi2{"Kiwi"}; + StringView vApple{"Apple"}; + Expect(vKiwi).ToEqual(vKiwi2); + Expect(vKiwi).ToNotEqual(vApple); + }); - It("Can copy", []() - { - StringView vKiwi{"Kiwi"}; - StringView vApple{"Apple"}; - StringView vCopy = vKiwi; - Expect(vCopy).ToEqual("Kiwi"); - Expect(vCopy).ToEqual(vKiwi); - Expect(vCopy).ToNotEqual(vApple); - vCopy = vApple; - Expect(vCopy).ToEqual("Apple"); - Expect(vCopy).ToNotEqual(vKiwi); - Expect(vCopy).ToEqual(vApple); - }); + It("Can copy", []() + { + StringView vKiwi{"Kiwi"}; + StringView vApple{"Apple"}; + StringView vCopy = vKiwi; + Expect(vCopy).ToEqual("Kiwi"); + Expect(vCopy).ToEqual(vKiwi); + Expect(vCopy).ToNotEqual(vApple); + vCopy = vApple; + Expect(vCopy).ToEqual("Apple"); + Expect(vCopy).ToNotEqual(vKiwi); + Expect(vCopy).ToEqual(vApple); + }); - It("Can move", []() - { - StringView vKiwi{"Kiwi"}; - StringView vApple{"Apple"}; - StringView vMove = Move(vKiwi); - Expect(vMove).ToEqual("Kiwi"); - vMove = Move(vApple); - Expect(vMove).ToEqual("Apple"); - }); + It("Can move", []() + { + StringView vKiwi{"Kiwi"}; + StringView vApple{"Apple"}; + StringView vMove = Move(vKiwi); + Expect(vMove).ToEqual("Kiwi"); + vMove = Move(vApple); + Expect(vMove).ToEqual("Apple"); + }); - Describe("Strings", []() + Describe("Strings", []() + { + It("Can Find", []() { - It("Can Find", []() - { - StringView v{"Kiwiwi"}; + StringView v{"Kiwiwi"}; - // Find Chars - Expect(Strings::Find(v, 'K', FindDir::Front)).ToEqual(0); - Expect(Strings::Find(v, 'K', FindDir::Back)).ToEqual(0); - Expect(Strings::Find(v, 'i', FindDir::Front)).ToEqual(1); - Expect(Strings::Find(v, 'i', FindDir::Back)).ToEqual(5); - // Find last chars - Expect(Strings::Find(v, 'w', FindDir::Front, true)).ToEqual(0); // 'K' - Expect(Strings::Find(v, 'w', FindDir::Back, true)).ToEqual(5); // 'i' - Expect(Strings::Find(v, 'K', FindDir::Front, true)).ToEqual(1); // 'i' - Expect(Strings::Find(v, 'i', FindDir::Back, true)).ToEqual(4); // 'w' + // Find Chars + Expect(Strings::Find(v, 'K', FindDir::Front)).ToEqual(0); + Expect(Strings::Find(v, 'K', FindDir::Back)).ToEqual(0); + Expect(Strings::Find(v, 'i', FindDir::Front)).ToEqual(1); + Expect(Strings::Find(v, 'i', FindDir::Back)).ToEqual(5); + // Find last chars + Expect(Strings::Find(v, 'w', FindDir::Front, true)).ToEqual(0); // 'K' + Expect(Strings::Find(v, 'w', FindDir::Back, true)).ToEqual(5); // 'i' + Expect(Strings::Find(v, 'K', FindDir::Front, true)).ToEqual(1); // 'i' + Expect(Strings::Find(v, 'i', FindDir::Back, true)).ToEqual(4); // 'w' - // Find Sub-strings - Expect(Strings::Find(v, "Ki", FindDir::Front)).ToEqual(0); - Expect(Strings::Find(v, "Ki", FindDir::Back)).ToEqual(0); - Expect(Strings::Find(v, "wi", FindDir::Front)).ToEqual(2); - Expect(Strings::Find(v, "wi", FindDir::Back)).ToEqual(4); - }); + // Find Sub-strings + Expect(Strings::Find(v, "Ki", FindDir::Front)).ToEqual(0); + Expect(Strings::Find(v, "Ki", FindDir::Back)).ToEqual(0); + Expect(Strings::Find(v, "wi", FindDir::Front)).ToEqual(2); + Expect(Strings::Find(v, "wi", FindDir::Back)).ToEqual(4); }); }); }); -} +}); +return true; +}(); +} // namespace diff --git a/Tests/Core/Tag.spec.cpp b/Tests/Core/Tag.spec.cpp index 032d8391..72e4989f 100644 --- a/Tests/Core/Tag.spec.cpp +++ b/Tests/Core/Tag.spec.cpp @@ -1,107 +1,112 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include using namespace p; -void RegisterCoreTagTests() +namespace { - Spec("Core.Tag", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("Core.Tag", []() +{ + It("Can copy empty", []() { - It("Can copy empty", []() - { - Tag tag{}; - Tag tag2{"Ahh"}; - Expect(p::GetHash(tag)).ToEqual(0); - Expect(tag.IsNone()).ToEqual(true); - Expect(p::GetHash(tag2)).ToNotEqual(0); - Expect(tag2.IsNone()).ToEqual(false); - tag2 = tag; - Expect(p::GetHash(tag2)).ToEqual(0); - Expect(tag2.IsNone()).ToEqual(true); - }); - It("Can assign from literal", []() - { - Tag tag{"Kiwi"}; - Expect(tag.AsString()).ToEqual("Kiwi"); - }); + Tag tag{}; + Tag tag2{"Ahh"}; + Expect(p::GetHash(tag)).ToEqual(0); + Expect(tag.IsNone()).ToEqual(true); + Expect(p::GetHash(tag2)).ToNotEqual(0); + Expect(tag2.IsNone()).ToEqual(false); + tag2 = tag; + Expect(p::GetHash(tag2)).ToEqual(0); + Expect(tag2.IsNone()).ToEqual(true); + }); + It("Can assign from literal", []() + { + Tag tag{"Kiwi"}; + Expect(tag.AsString()).ToEqual("Kiwi"); + }); - It("Can assign from string", []() - { - String str{"Kiwi"}; - Tag tag{str}; - Expect(tag.AsString()).ToEqual("Kiwi"); - }); + It("Can assign from string", []() + { + String str{"Kiwi"}; + Tag tag{str}; + Expect(tag.AsString()).ToEqual("Kiwi"); + }); - It("Can retrieve string data", []() - { - Tag tag{"Kiwi"}; - Expect(tag.AsString()).ToEqual("Kiwi"); - }); + It("Can retrieve string data", []() + { + Tag tag{"Kiwi"}; + Expect(tag.AsString()).ToEqual("Kiwi"); + }); - It("Can compare tags", []() - { - Tag tagKiwi{"Kiwi"}; - Tag tagKiwi2{"Kiwi"}; - Tag tagApple{"Apple"}; - Expect(tagKiwi).ToEqual(tagKiwi2); - Expect(tagKiwi).ToNotEqual(tagApple); - }); + It("Can compare tags", []() + { + Tag tagKiwi{"Kiwi"}; + Tag tagKiwi2{"Kiwi"}; + Tag tagApple{"Apple"}; + Expect(tagKiwi).ToEqual(tagKiwi2); + Expect(tagKiwi).ToNotEqual(tagApple); + }); - It("Different instances share string allocation", []() - { - Tag tagKiwi{"Kiwi"}; - Tag tagKiwi2{"Kiwi"}; - Tag tagApple{"Apple"}; - Expect(tagKiwi.AsString().data()).ToEqual(tagKiwi2.AsString().data()); - Expect(tagKiwi.AsString().data()).ToNotEqual(tagApple.AsString().data()); - }); + It("Different instances share string allocation", []() + { + Tag tagKiwi{"Kiwi"}; + Tag tagKiwi2{"Kiwi"}; + Tag tagApple{"Apple"}; + Expect(tagKiwi.AsString().data()).ToEqual(tagKiwi2.AsString().data()); + Expect(tagKiwi.AsString().data()).ToNotEqual(tagApple.AsString().data()); + }); - It("Can check invalid/none", []() - { - Tag tagValid{"Kiwi"}; - Tag tagInvalid{}; - Expect(tagValid.IsNone()).ToEqual(false); - Expect(tagValid).ToNotEqual(Tag::None()); - Expect(tagInvalid.IsNone()).ToEqual(true); - Expect(tagInvalid).ToEqual(Tag::None()); - }); + It("Can check invalid/none", []() + { + Tag tagValid{"Kiwi"}; + Tag tagInvalid{}; + Expect(tagValid.IsNone()).ToEqual(false); + Expect(tagValid).ToNotEqual(Tag::None()); + Expect(tagInvalid.IsNone()).ToEqual(true); + Expect(tagInvalid).ToEqual(Tag::None()); + }); - It("Contains correct hashes", []() - { - Tag tagKiwi{"Kiwi"}; - Tag tagKiwi2{"Kiwi"}; - Expect(p::GetHash(tagKiwi)).ToEqual(p::GetHash(tagKiwi2)); - Expect(tagKiwi.GetStringHash()).ToEqual(p::GetHash("Kiwi")); - }); + It("Contains correct hashes", []() + { + Tag tagKiwi{"Kiwi"}; + Tag tagKiwi2{"Kiwi"}; + Expect(p::GetHash(tagKiwi)).ToEqual(p::GetHash(tagKiwi2)); + Expect(tagKiwi.GetStringHash()).ToEqual(p::GetHash("Kiwi")); + }); - It("Can copy tag", []() - { - Tag tagKiwi{"Kiwi"}; - Tag tagApple{"Apple"}; - Tag tagCopy = tagKiwi; - Expect(tagCopy.AsString()).ToEqual("Kiwi"); - Expect(tagCopy).ToEqual(tagKiwi); - Expect(tagCopy).ToNotEqual(tagApple); - tagCopy = tagApple; - Expect(tagCopy.AsString()).ToEqual("Apple"); - Expect(tagCopy).ToNotEqual(tagKiwi); - Expect(tagCopy).ToEqual(tagApple); - }); + It("Can copy tag", []() + { + Tag tagKiwi{"Kiwi"}; + Tag tagApple{"Apple"}; + Tag tagCopy = tagKiwi; + Expect(tagCopy.AsString()).ToEqual("Kiwi"); + Expect(tagCopy).ToEqual(tagKiwi); + Expect(tagCopy).ToNotEqual(tagApple); + tagCopy = tagApple; + Expect(tagCopy.AsString()).ToEqual("Apple"); + Expect(tagCopy).ToNotEqual(tagKiwi); + Expect(tagCopy).ToEqual(tagApple); + }); - It("Can move tag", []() - { - Tag tagKiwi{"Kiwi"}; - Tag tagApple{"Apple"}; - Tag tagMove = Move(tagKiwi); - Expect(tagKiwi).ToEqual(Tag::None()); - Expect(tagMove.AsString()).ToEqual("Kiwi"); - tagMove = Move(tagApple); - Expect(tagApple).ToEqual(Tag::None()); - Expect(tagMove.AsString()).ToEqual("Apple"); - }); + It("Can move tag", []() + { + Tag tagKiwi{"Kiwi"}; + Tag tagApple{"Apple"}; + Tag tagMove = Move(tagKiwi); + Expect(tagKiwi).ToEqual(Tag::None()); + Expect(tagMove.AsString()).ToEqual("Kiwi"); + tagMove = Move(tagApple); + Expect(tagApple).ToEqual(Tag::None()); + Expect(tagMove.AsString()).ToEqual("Apple"); }); -} +}); +return true; +}(); +} // namespace diff --git a/Tests/ECS/Components.spec.cpp b/Tests/ECS/Components.spec.cpp index 90c13698..f0727f60 100644 --- a/Tests/ECS/Components.spec.cpp +++ b/Tests/ECS/Components.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include @@ -44,250 +44,255 @@ struct TestComponent u32 TestComponent::destructed = 0; -void RegisterECSComponentsTests() +namespace { - Spec("ECS.Components", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("ECS.Components", []() +{ + It("Can add one component", []() { - It("Can add one component", []() - { - IdContext ctx; - Id id = AddId(ctx); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - - ctx.Add(id); - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - - ctx.Add(id); - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.TryGet(id)).ToNotEqual(nullptr); - }); - - It("Can remove one component", []() - { - IdContext ctx; - Id id = AddId(ctx); - ctx.Add(id); - - ctx.Remove(id); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - - NonEmptyComponent::destructed = 0; - ctx.Remove(id); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - Expect(NonEmptyComponent::destructed).ToEqual(1); - }); - - It("Can add many components", []() - { - IdContext ctx; - TArray ids{3}; - AddId(ctx, ids); - ctx.AddN(ids, NonEmptyComponent{2}); - - for (Id id : ids) - { - auto* data = ctx.TryGet(id); - Expect(data).ToNotEqual(nullptr); - Expect(data->a).ToEqual(2); - } - }); - - It("Can remove many components", []() - { - IdContext ctx; - TArray ids{3}; - AddId(ctx, ids); - ctx.AddN(ids, NonEmptyComponent{2}); - - NonEmptyComponent::destructed = 0; - TView firstTwo{ids.Data(), ids.Data() + 2}; - ctx.Remove(firstTwo); - Expect(NonEmptyComponent::destructed).ToEqual(2); - Expect(ctx.TryGet(ids[0])).ToEqual(nullptr); - Expect(ctx.TryGet(ids[1])).ToEqual(nullptr); - Expect(ctx.TryGet(ids[2])).ToNotEqual(nullptr); - - // Repeat in different order - ctx.AddN(ids, NonEmptyComponent{2}); - - NonEmptyComponent::destructed = 0; - TView lastTwo{ids.Data() + 1, ids.Data() + 3}; - ctx.Remove(lastTwo); - Expect(NonEmptyComponent::destructed).ToEqual(2); - Expect(ctx.TryGet(ids[0])).ToNotEqual(nullptr); - Expect(ctx.TryGet(ids[1])).ToEqual(nullptr); - Expect(ctx.TryGet(ids[2])).ToEqual(nullptr); - }); - - It("Components are removed after node is deleted", []() - { - IdContext ctx; - Id id = AddId(ctx); - ctx.Add(id); + IdContext ctx; + Id id = AddId(ctx); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + + ctx.Add(id); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + + ctx.Add(id); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.TryGet(id)).ToNotEqual(nullptr); + }); - RmId(ctx, id, p::RmIdFlags::Instant); - Expect(ctx.IsValid(id)).ToBeFalse(); + It("Can remove one component", []() + { + IdContext ctx; + Id id = AddId(ctx); + ctx.Add(id); + + ctx.Remove(id); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + + NonEmptyComponent::destructed = 0; + ctx.Remove(id); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + Expect(NonEmptyComponent::destructed).ToEqual(1); + }); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - }); + It("Can add many components", []() + { + IdContext ctx; + TArray ids{3}; + AddId(ctx, ids); + ctx.AddN(ids, NonEmptyComponent{2}); - It("Components are removed after node is deleted (deferred)", []() - { - IdContext ctx; - Id id = AddId(ctx); - ctx.Add(id); - - RmId(ctx, id); - Expect(ctx.IsValid(id)).ToBeFalse(); - - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.TryGet(id)).ToNotEqual(nullptr); - - FlushDeferredRemovals(ctx); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - }); - - It("Components keep state when added", []() - { - IdContext ctx; - Id id = AddId(ctx); - ctx.AddN(id, NonEmptyComponent{2}); - Expect(ctx.TryGet(id)).ToNotEqual(nullptr); - Expect(ctx.Get(id).a).ToEqual(2); - }); - - It("Can copy registry", []() + for (Id id : ids) { - IdContext ctxa; + auto* data = ctx.TryGet(id); + Expect(data).ToNotEqual(nullptr); + Expect(data->a).ToEqual(2); + } + }); - Id id = AddId(ctxa); - ctxa.Add(id); - Id id2 = AddId(ctxa); - ctxa.AddN(id2, NonEmptyComponent{2}); + It("Can remove many components", []() + { + IdContext ctx; + TArray ids{3}; + AddId(ctx, ids); + ctx.AddN(ids, NonEmptyComponent{2}); + + NonEmptyComponent::destructed = 0; + TView firstTwo{ids.Data(), ids.Data() + 2}; + ctx.Remove(firstTwo); + Expect(NonEmptyComponent::destructed).ToEqual(2); + Expect(ctx.TryGet(ids[0])).ToEqual(nullptr); + Expect(ctx.TryGet(ids[1])).ToEqual(nullptr); + Expect(ctx.TryGet(ids[2])).ToNotEqual(nullptr); + + // Repeat in different order + ctx.AddN(ids, NonEmptyComponent{2}); + + NonEmptyComponent::destructed = 0; + TView lastTwo{ids.Data() + 1, ids.Data() + 3}; + ctx.Remove(lastTwo); + Expect(NonEmptyComponent::destructed).ToEqual(2); + Expect(ctx.TryGet(ids[0])).ToNotEqual(nullptr); + Expect(ctx.TryGet(ids[1])).ToEqual(nullptr); + Expect(ctx.TryGet(ids[2])).ToEqual(nullptr); + }); - IdContext ctxb{ctxa}; - Expect(ctxb.Has(id)).ToBeTrue(); - Expect(ctxb.Has(id)).ToBeTrue(); - Expect(ctxb.TryGet(id)).ToNotEqual(nullptr); + It("Components are removed after node is deleted", []() + { + IdContext ctx; + Id id = AddId(ctx); + ctx.Add(id); - // Holds component values - Expect(ctxb.Has(id2)).ToBeTrue(); - Expect(ctxb.Get(id2).a).ToEqual(2); - }); + RmId(ctx, id, p::RmIdFlags::Instant); + Expect(ctx.IsValid(id)).ToBeFalse(); - It("Can check components", []() - { - IdContext ctx; - Id id = NoId; - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + }); - id = AddId(ctx); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.Has(id)).ToBeFalse(); + It("Components are removed after node is deleted (deferred)", []() + { + IdContext ctx; + Id id = AddId(ctx); + ctx.Add(id); + + RmId(ctx, id); + Expect(ctx.IsValid(id)).ToBeFalse(); + + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.TryGet(id)).ToNotEqual(nullptr); + + FlushDeferredRemovals(ctx); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + }); - ctx.Add(id); - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.Has(id)).ToBeTrue(); - }); + It("Components keep state when added", []() + { + IdContext ctx; + Id id = AddId(ctx); + ctx.AddN(id, NonEmptyComponent{2}); + Expect(ctx.TryGet(id)).ToNotEqual(nullptr); + Expect(ctx.Get(id).a).ToEqual(2); + }); - It("Can destroy components on reset", []() - { - NonEmptyComponent::destructed = 0; - TestComponent::destructed = 0; - - IdContext ctx; - TArray ids{3}; - AddId(ctx, ids); - ctx.AddN(ids, NonEmptyComponent{2}); - ctx.AddN(ids); - - ctx.Remove(ids); - ctx.Remove(ids[0]); - Expect( - NonEmptyComponent::destructed).ToEqual(4); // 3 + 1 (passed by value on Add()) - Expect(TestComponent::destructed).ToEqual(2); // 1 + 1 (passed by value on Add()) - - NonEmptyComponent::destructed = 0; - TestComponent::destructed = 0; - ctx.Reset(); - - Expect(NonEmptyComponent::destructed).ToEqual(0); - Expect(TestComponent::destructed).ToEqual(2); - }); - - It("Components are removed with the entity", []() - { - IdContext ctx; - Id id = AddId(ctx); - ctx.Add(id); - RmId(ctx, id, p::RmIdFlags::Instant); - Expect(ctx.IsValid(id)).ToBeFalse(); - - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - }); - - It("Components are removed with the entity (deferred)", []() - { - IdContext ctx; - Id id = AddId(ctx); - ctx.Add(id); - RmId(ctx, id); - Expect(ctx.IsValid(id)).ToBeFalse(); - - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.TryGet(id)).ToNotEqual(nullptr); - - FlushDeferredRemovals(ctx); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - }); - - It("Can access components on recicled entities", []() - { - IdContext ctx; - Id id = AddId(ctx); - ctx.Add(id); - RmId(ctx, id); - - id = AddId(ctx); - ctx.Add(id); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.TryGet(id)).ToNotEqual(nullptr); - }); - - It("Can access CRemoved", []() - { - IdContext ctx; - Id id = AddId(ctx); - ctx.Add(id); - RmId(ctx, id); - - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.TryGet(id)).ToNotEqual(nullptr); - }); + It("Can copy registry", []() + { + IdContext ctxa; + + Id id = AddId(ctxa); + ctxa.Add(id); + Id id2 = AddId(ctxa); + ctxa.AddN(id2, NonEmptyComponent{2}); + + IdContext ctxb{ctxa}; + Expect(ctxb.Has(id)).ToBeTrue(); + Expect(ctxb.Has(id)).ToBeTrue(); + Expect(ctxb.TryGet(id)).ToNotEqual(nullptr); + + // Holds component values + Expect(ctxb.Has(id2)).ToBeTrue(); + Expect(ctxb.Get(id2).a).ToEqual(2); + }); + + It("Can check components", []() + { + IdContext ctx; + Id id = NoId; + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.Has(id)).ToBeFalse(); + + id = AddId(ctx); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.Has(id)).ToBeFalse(); + + ctx.Add(id); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.Has(id)).ToBeTrue(); + }); + + It("Can destroy components on reset", []() + { + NonEmptyComponent::destructed = 0; + TestComponent::destructed = 0; + + IdContext ctx; + TArray ids{3}; + AddId(ctx, ids); + ctx.AddN(ids, NonEmptyComponent{2}); + ctx.AddN(ids); + + ctx.Remove(ids); + ctx.Remove(ids[0]); + Expect( + NonEmptyComponent::destructed).ToEqual(4); // 3 + 1 (passed by value on Add()) + Expect(TestComponent::destructed).ToEqual(2); // 1 + 1 (passed by value on Add()) + + NonEmptyComponent::destructed = 0; + TestComponent::destructed = 0; + ctx.Reset(); + + Expect(NonEmptyComponent::destructed).ToEqual(0); + Expect(TestComponent::destructed).ToEqual(2); + }); + + It("Components are removed with the entity", []() + { + IdContext ctx; + Id id = AddId(ctx); + ctx.Add(id); + RmId(ctx, id, p::RmIdFlags::Instant); + Expect(ctx.IsValid(id)).ToBeFalse(); + + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + }); + + It("Components are removed with the entity (deferred)", []() + { + IdContext ctx; + Id id = AddId(ctx); + ctx.Add(id); + RmId(ctx, id); + Expect(ctx.IsValid(id)).ToBeFalse(); + + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.TryGet(id)).ToNotEqual(nullptr); + + FlushDeferredRemovals(ctx); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + }); + + It("Can access components on recicled entities", []() + { + IdContext ctx; + Id id = AddId(ctx); + ctx.Add(id); + RmId(ctx, id); + + id = AddId(ctx); + ctx.Add(id); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.TryGet(id)).ToNotEqual(nullptr); + }); + + It("Can access CRemoved", []() + { + IdContext ctx; + Id id = AddId(ctx); + ctx.Add(id); + RmId(ctx, id); + + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.TryGet(id)).ToNotEqual(nullptr); }); -} +}); +return true; +}(); +} // namespace diff --git a/Tests/ECS/ECS.spec.cpp b/Tests/ECS/ECS.spec.cpp index 7437f04a..40b0faa6 100644 --- a/Tests/ECS/ECS.spec.cpp +++ b/Tests/ECS/ECS.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include @@ -15,58 +15,63 @@ struct ECSTypeB {}; -void RegisterECSECSsmTests() +namespace { - Spec("ECS", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("ECS", []() +{ + It("Can copy context", []() + { + static IdContext* ctxPtr = nullptr; + + IdContext origin; + Id id = AddId(origin); + + ctxPtr = &origin; + origin.Add(id); + + IdContext target{origin}; + Expect(origin.IsValid(id)).ToEqual(true); + Expect(origin.Has(id)).ToEqual(true); + Expect(target.IsValid(id)).ToEqual(true); + Expect(target.Has(id)).ToEqual(true); + + ctxPtr = ⌖ + target.Add(id); + Expect(target.Has(id)).ToEqual(true); + }); + + It("Can move context", []() + { + static IdContext* ctxPtr = nullptr; + + IdContext origin; + Id id = AddId(origin); + + ctxPtr = &origin; + origin.Add(id); + Expect(origin.Has(id)).ToEqual(true); + + IdContext target{Move(origin)}; + Expect(origin.IsValid(id)).ToEqual(false); + + Expect(target.IsValid(id)).ToEqual(true); + Expect(target.Has(id)).ToEqual(true); + + ctxPtr = ⌖ + target.Add(id); + Expect(target.Has(id)).ToEqual(true); + }); + + It("Can assure pool", []() { - It("Can copy context", []() - { - static IdContext* ctxPtr = nullptr; - - IdContext origin; - Id id = AddId(origin); - - ctxPtr = &origin; - origin.Add(id); - - IdContext target{origin}; - Expect(origin.IsValid(id)).ToEqual(true); - Expect(origin.Has(id)).ToEqual(true); - Expect(target.IsValid(id)).ToEqual(true); - Expect(target.Has(id)).ToEqual(true); - - ctxPtr = ⌖ - target.Add(id); - Expect(target.Has(id)).ToEqual(true); - }); - - It("Can move context", []() - { - static IdContext* ctxPtr = nullptr; - - IdContext origin; - Id id = AddId(origin); - - ctxPtr = &origin; - origin.Add(id); - Expect(origin.Has(id)).ToEqual(true); - - IdContext target{Move(origin)}; - Expect(origin.IsValid(id)).ToEqual(false); - - Expect(target.IsValid(id)).ToEqual(true); - Expect(target.Has(id)).ToEqual(true); - - ctxPtr = ⌖ - target.Add(id); - Expect(target.Has(id)).ToEqual(true); - }); - - It("Can assure pool", []() - { - IdContext origin; - TPool& pool = origin.AssurePool(); - Expect(pool.Size()).ToEqual(0); - }); + IdContext origin; + TPool& pool = origin.AssurePool(); + Expect(pool.Size()).ToEqual(0); }); -} +}); +return true; +}(); +} // namespace diff --git a/Tests/ECS/Filtering.spec.cpp b/Tests/ECS/Filtering.spec.cpp index cb255481..b39e50c0 100644 --- a/Tests/ECS/Filtering.spec.cpp +++ b/Tests/ECS/Filtering.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include @@ -26,208 +26,213 @@ namespace } // namespace -void RegisterECSFilteringTests() +namespace +{ +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("ECS.Filtering", []() { - Spec("ECS.Filtering", []() + BeforeEach([]() + { + ctx = {}; + id1 = AddId(ctx); + id2 = AddId(ctx); + id3 = AddId(ctx); + id4 = AddId(ctx); + id5 = AddId(ctx); + ctx.Add(id1); + ctx.Add(id2); + ctx.Add(id3); + ctx.Add(id4); + ctx.Add(id5); + }); + + Describe("FindAllIdsWith/FindAllIdsWithAny", []() { - BeforeEach([]() + It("Can get list matching all", []() { - ctx = {}; - id1 = AddId(ctx); - id2 = AddId(ctx); - id3 = AddId(ctx); - id4 = AddId(ctx); - id5 = AddId(ctx); - ctx.Add(id1); - ctx.Add(id2); - ctx.Add(id3); - ctx.Add(id4); - ctx.Add(id5); + TIdScope access{ctx}; + TArray typeIds = FindAllIdsWith(access); + Expect(typeIds.Contains(id1)).ToBeTrue(); + Expect(typeIds.Contains(id2)).ToBeTrue(); + Expect(typeIds.Contains(id3)).ToBeFalse(); + + TArray type2Ids = FindAllIdsWith(access); + Expect(type2Ids.Contains(id1)).ToBeFalse(); + Expect(type2Ids.Contains(id2)).ToBeTrue(); + Expect(type2Ids.Contains(id3)).ToBeTrue(); }); - Describe("FindAllIdsWith/FindAllIdsWithAny", []() + It("Can get list matching any", []() { - It("Can get list matching all", []() - { - TIdScope access{ctx}; - TArray typeIds = FindAllIdsWith(access); - Expect(typeIds.Contains(id1)).ToBeTrue(); - Expect(typeIds.Contains(id2)).ToBeTrue(); - Expect(typeIds.Contains(id3)).ToBeFalse(); - - TArray type2Ids = FindAllIdsWith(access); - Expect(type2Ids.Contains(id1)).ToBeFalse(); - Expect(type2Ids.Contains(id2)).ToBeTrue(); - Expect(type2Ids.Contains(id3)).ToBeTrue(); - }); - - It("Can get list matching any", []() - { - TIdScope access{ctx}; - TArray typeIds = FindAllIdsWithAny(access); - Expect(typeIds.Contains(id1)).ToBeTrue(); - Expect(typeIds.Contains(id2)).ToBeTrue(); - Expect(typeIds.Contains(id3)).ToBeFalse(); - - TArray type2Ids = FindAllIdsWithAny(access); - Expect(type2Ids.Contains(id1)).ToBeTrue(); - Expect(type2Ids.Contains(id2)).ToBeTrue(); - Expect(type2Ids.Contains(id3)).ToBeTrue(); - }); - - It("Doesn't list removed ids", []() - { - TIdScope access{ctx}; - RmId(ctx, id2, RmIdFlags::Instant); - RmId(ctx, id3, RmIdFlags::Instant); - RmId(ctx, id4, RmIdFlags::Instant); - - TArray ids = FindAllIdsWith(access); - Expect(ids.Contains(NoId)).ToBeFalse(); - Expect(ids.Size()).ToEqual(1); - }); - - It("Doesn't list (deferred) removed ids", []() - { - TIdScope access{ctx}; - RmId(ctx, id2); - RmId(ctx, id3); - RmId(ctx, id4); - - FlushDeferredRemovals(ctx); - - TArray ids = FindAllIdsWith(access); - Expect(ids.Contains(NoId)).ToBeFalse(); - Expect(ids.Size()).ToEqual(1); - }); + TIdScope access{ctx}; + TArray typeIds = FindAllIdsWithAny(access); + Expect(typeIds.Contains(id1)).ToBeTrue(); + Expect(typeIds.Contains(id2)).ToBeTrue(); + Expect(typeIds.Contains(id3)).ToBeFalse(); + + TArray type2Ids = FindAllIdsWithAny(access); + Expect(type2Ids.Contains(id1)).ToBeTrue(); + Expect(type2Ids.Contains(id2)).ToBeTrue(); + Expect(type2Ids.Contains(id3)).ToBeTrue(); }); - Describe("ExcludeIdsWith", []() + It("Doesn't list removed ids", []() { - It("Removes ids containing component", []() - { - TIdScope access{ctx}; - TArray typeIds = FindAllIdsWithAny(access); - - ExcludeIdsWith(access, typeIds); - Expect(typeIds.Contains(id1)).ToBeTrue(); - Expect(typeIds.Contains(id2)).ToBeFalse(); - Expect(typeIds.Contains(id3)).ToBeFalse(); - }); - - It("Removes ids not containing component", []() - { - TIdScope access{ctx}; - TArray typeIds = FindAllIdsWithAny(access); - - ExcludeIdsWithout(access, typeIds); - Expect(typeIds.Contains(id1)).ToBeFalse(); - Expect(typeIds.Contains(id2)).ToBeTrue(); - Expect(typeIds.Contains(id3)).ToBeFalse(); - }); - - It("Removes ids containing multiple component", []() - { - TIdScope access{ctx}; - TArray typeIds = FindAllIdsWithAny(access); - - ExcludeIdsWith(access, typeIds); - Expect(typeIds.Contains(id1)).ToBeTrue(); - Expect(typeIds.Contains(id2)).ToBeFalse(); - Expect(typeIds.Contains(id3)).ToBeFalse(); - }); + TIdScope access{ctx}; + RmId(ctx, id2, RmIdFlags::Instant); + RmId(ctx, id3, RmIdFlags::Instant); + RmId(ctx, id4, RmIdFlags::Instant); + + TArray ids = FindAllIdsWith(access); + Expect(ids.Contains(NoId)).ToBeFalse(); + Expect(ids.Size()).ToEqual(1); }); - Describe("FindIdsWith", []() + It("Doesn't list (deferred) removed ids", []() { - It("Finds ids containing a component from a list", []() - { - TArray source{id1, id2, id3}; - - TIdScope access{ctx}; - TArray typeIds = FindIdsWith(access, source); - Expect(typeIds.Contains(id1)).ToBeTrue(); - Expect(typeIds.Contains(id2)).ToBeTrue(); - Expect(typeIds.Contains(id3)).ToBeFalse(); - }); - - It("Finds ids not containing a component from a list", []() - { - TArray source{id1, id2, id3}; - - TIdScope access{ctx}; - TArray ids = FindIdsWithout(access, source); - Expect(ids.Contains(id1)).ToBeFalse(); - Expect(ids.Contains(id2)).ToBeFalse(); - Expect(ids.Contains(id3)).ToBeTrue(); - }); + TIdScope access{ctx}; + RmId(ctx, id2); + RmId(ctx, id3); + RmId(ctx, id4); + + FlushDeferredRemovals(ctx); + + TArray ids = FindAllIdsWith(access); + Expect(ids.Contains(NoId)).ToBeFalse(); + Expect(ids.Size()).ToEqual(1); }); + }); - Describe("ExtractIdsWith", []() + Describe("ExcludeIdsWith", []() + { + It("Removes ids containing component", []() { - It("Finds and removes ids containing a component from a list", []() - { - TArray source{id1, id2, id3}; - - TIdScope access{ctx}; - TArray ids = ExtractIdsWith(access, source); - Expect(ids.Contains(id1)).ToBeTrue(); - Expect(ids.Contains(id2)).ToBeTrue(); - Expect(ids.Contains(id3)).ToBeFalse(); - Expect(source.Contains(id1)).ToBeFalse(); - Expect(source.Contains(id2)).ToBeFalse(); - Expect(source.Contains(id3)).ToBeTrue(); - }); - - It("Finds and removes ids not containing a component from a list", []() - { - TArray source{id1, id2, id3}; - - TIdScope access{ctx}; - TArray ids = ExtractIdsWithout(access, source); - Expect(ids.Contains(id1)).ToBeFalse(); - Expect(ids.Contains(id2)).ToBeFalse(); - Expect(ids.Contains(id3)).ToBeTrue(); - Expect(source.Contains(id1)).ToBeTrue(); - Expect(source.Contains(id2)).ToBeTrue(); - Expect(source.Contains(id3)).ToBeFalse(); - }); + TIdScope access{ctx}; + TArray typeIds = FindAllIdsWithAny(access); + + ExcludeIdsWith(access, typeIds); + Expect(typeIds.Contains(id1)).ToBeTrue(); + Expect(typeIds.Contains(id2)).ToBeFalse(); + Expect(typeIds.Contains(id3)).ToBeFalse(); }); - It("Can filter directly from ECS", []() + It("Removes ids not containing component", []() { - TArray ids1 = FindAllIdsWith(ctx); - Expect(ids1.Contains(id1)).ToBeTrue(); + TIdScope access{ctx}; + TArray typeIds = FindAllIdsWithAny(access); - TArray ids2 = FindAllIdsWithAny(ctx); - Expect(ids2.Contains(id1)).ToBeTrue(); + ExcludeIdsWithout(access, typeIds); + Expect(typeIds.Contains(id1)).ToBeFalse(); + Expect(typeIds.Contains(id2)).ToBeTrue(); + Expect(typeIds.Contains(id3)).ToBeFalse(); + }); - TArray ids3 = FindAllIdsWithAny(ctx); - ExcludeIdsWith(ctx, ids3); - Expect(ids3.Contains(id1)).ToBeTrue(); + It("Removes ids containing multiple component", []() + { + TIdScope access{ctx}; + TArray typeIds = FindAllIdsWithAny(access); - TArray ids4 = FindAllIdsWithAny(ctx); - ExcludeIdsWithout(ctx, ids4); - Expect(ids4.Contains(id1)).ToBeFalse(); + ExcludeIdsWith(access, typeIds); + Expect(typeIds.Contains(id1)).ToBeTrue(); + Expect(typeIds.Contains(id2)).ToBeFalse(); + Expect(typeIds.Contains(id3)).ToBeFalse(); }); + }); - It("Can filter CRemoved", []() + Describe("FindIdsWith", []() + { + It("Finds ids containing a component from a list", []() { - RmId(ctx, id1); - RmId(ctx, id2); - RmId(ctx, id3); + TArray source{id1, id2, id3}; - TArray ids1 = FindAllIdsWith(ctx); - Expect(ids1.Contains(id1)).ToBeTrue(); - TArray ids2 = FindAllIdsWith(ctx); - Expect(ids2.Contains(id1)).ToBeTrue(); - Expect(ids2.Contains(id2)).ToBeTrue(); - Expect(ids2.Contains(id3)).ToBeTrue(); - Expect(ids2.Size()).ToEqual(3); - - TArray ids3 = FindAllIdsWith(ctx); - Expect(ids3.Contains(id1)).ToBeTrue(); - Expect(ids3.Contains(id2)).ToBeTrue(); + TIdScope access{ctx}; + TArray typeIds = FindIdsWith(access, source); + Expect(typeIds.Contains(id1)).ToBeTrue(); + Expect(typeIds.Contains(id2)).ToBeTrue(); + Expect(typeIds.Contains(id3)).ToBeFalse(); + }); + + It("Finds ids not containing a component from a list", []() + { + TArray source{id1, id2, id3}; + + TIdScope access{ctx}; + TArray ids = FindIdsWithout(access, source); + Expect(ids.Contains(id1)).ToBeFalse(); + Expect(ids.Contains(id2)).ToBeFalse(); + Expect(ids.Contains(id3)).ToBeTrue(); + }); + }); + + Describe("ExtractIdsWith", []() + { + It("Finds and removes ids containing a component from a list", []() + { + TArray source{id1, id2, id3}; + + TIdScope access{ctx}; + TArray ids = ExtractIdsWith(access, source); + Expect(ids.Contains(id1)).ToBeTrue(); + Expect(ids.Contains(id2)).ToBeTrue(); + Expect(ids.Contains(id3)).ToBeFalse(); + Expect(source.Contains(id1)).ToBeFalse(); + Expect(source.Contains(id2)).ToBeFalse(); + Expect(source.Contains(id3)).ToBeTrue(); + }); + + It("Finds and removes ids not containing a component from a list", []() + { + TArray source{id1, id2, id3}; + + TIdScope access{ctx}; + TArray ids = ExtractIdsWithout(access, source); + Expect(ids.Contains(id1)).ToBeFalse(); + Expect(ids.Contains(id2)).ToBeFalse(); + Expect(ids.Contains(id3)).ToBeTrue(); + Expect(source.Contains(id1)).ToBeTrue(); + Expect(source.Contains(id2)).ToBeTrue(); + Expect(source.Contains(id3)).ToBeFalse(); }); }); -} + + It("Can filter directly from ECS", []() + { + TArray ids1 = FindAllIdsWith(ctx); + Expect(ids1.Contains(id1)).ToBeTrue(); + + TArray ids2 = FindAllIdsWithAny(ctx); + Expect(ids2.Contains(id1)).ToBeTrue(); + + TArray ids3 = FindAllIdsWithAny(ctx); + ExcludeIdsWith(ctx, ids3); + Expect(ids3.Contains(id1)).ToBeTrue(); + + TArray ids4 = FindAllIdsWithAny(ctx); + ExcludeIdsWithout(ctx, ids4); + Expect(ids4.Contains(id1)).ToBeFalse(); + }); + + It("Can filter CRemoved", []() + { + RmId(ctx, id1); + RmId(ctx, id2); + RmId(ctx, id3); + + TArray ids1 = FindAllIdsWith(ctx); + Expect(ids1.Contains(id1)).ToBeTrue(); + TArray ids2 = FindAllIdsWith(ctx); + Expect(ids2.Contains(id1)).ToBeTrue(); + Expect(ids2.Contains(id2)).ToBeTrue(); + Expect(ids2.Contains(id3)).ToBeTrue(); + Expect(ids2.Size()).ToEqual(3); + + TArray ids3 = FindAllIdsWith(ctx); + Expect(ids3.Contains(id1)).ToBeTrue(); + Expect(ids3.Contains(id2)).ToBeTrue(); + }); +}); +return true; +}(); +} // namespace diff --git a/Tests/ECS/Hierarchy.spec.cpp b/Tests/ECS/Hierarchy.spec.cpp index 5d30eca7..fb4f6603 100644 --- a/Tests/ECS/Hierarchy.spec.cpp +++ b/Tests/ECS/Hierarchy.spec.cpp @@ -1,7 +1,7 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include @@ -19,474 +19,479 @@ namespace } // namespace -void RegisterECSHierarchyTests() +namespace +{ +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() { - Spec("ECS.Hierarchy", []() +Spec("ECS.Hierarchy", []() +{ + BeforeEach([]() { - BeforeEach([]() - { - ctx = {}; - root = AddId(ctx); - child1 = AddId(ctx); - child2 = AddId(ctx); - child3 = AddId(ctx); - grandchild = AddId(ctx); - }); + ctx = {}; + root = AddId(ctx); + child1 = AddId(ctx); + child2 = AddId(ctx); + child3 = AddId(ctx); + grandchild = AddId(ctx); + }); - Describe("AttachId", []() + Describe("AttachId", []() + { + It("Creates bidirectional parent-child link for single child", []() { - It("Creates bidirectional parent-child link for single child", []() - { - AttachId({ctx}, root, child1); + AttachId({ctx}, root, child1); - Expect(ctx.Has(root)).ToBeTrue(); - Expect(ctx.Has(child1)).ToBeTrue(); - Expect(ctx.Get(root).children.Size()).ToEqual(1); - Expect(ctx.Get(root).children[0]).ToEqual(child1); - Expect(ctx.Get(child1).parent).ToEqual(root); - }); + Expect(ctx.Has(root)).ToBeTrue(); + Expect(ctx.Has(child1)).ToBeTrue(); + Expect(ctx.Get(root).children.Size()).ToEqual(1); + Expect(ctx.Get(root).children[0]).ToEqual(child1); + Expect(ctx.Get(child1).parent).ToEqual(root); + }); - It("Appends multiple children to same parent", []() - { - AttachId({ctx}, root, {child1, child2, child3}); - - Expect(ctx.Has(root)).ToBeTrue(); - Expect(ctx.Has(child1)).ToBeTrue(); - Expect(ctx.Has(child2)).ToBeTrue(); - Expect(ctx.Has(child3)).ToBeTrue(); - Expect(ctx.Get(root).children.Size()).ToEqual(3); - Expect(ctx.Get(root).children[0]).ToEqual(child1); - Expect(ctx.Get(root).children[1]).ToEqual(child2); - Expect(ctx.Get(root).children[2]).ToEqual(child3); - Expect(ctx.Get(child1).parent).ToEqual(root); - Expect(ctx.Get(child2).parent).ToEqual(root); - Expect(ctx.Get(child3).parent).ToEqual(root); - }); + It("Appends multiple children to same parent", []() + { + AttachId({ctx}, root, {child1, child2, child3}); + + Expect(ctx.Has(root)).ToBeTrue(); + Expect(ctx.Has(child1)).ToBeTrue(); + Expect(ctx.Has(child2)).ToBeTrue(); + Expect(ctx.Has(child3)).ToBeTrue(); + Expect(ctx.Get(root).children.Size()).ToEqual(3); + Expect(ctx.Get(root).children[0]).ToEqual(child1); + Expect(ctx.Get(root).children[1]).ToEqual(child2); + Expect(ctx.Get(root).children[2]).ToEqual(child3); + Expect(ctx.Get(child1).parent).ToEqual(root); + Expect(ctx.Get(child2).parent).ToEqual(root); + Expect(ctx.Get(child3).parent).ToEqual(root); }); + }); - Describe("AttachIdAfter", []() + Describe("AttachIdAfter", []() + { + It("Inserts child after specified sibling preserving order", []() { - It("Inserts child after specified sibling preserving order", []() - { - AttachId({ctx}, root, {child1, child3}); - AttachIdAfter({ctx}, root, child2, child1); + AttachId({ctx}, root, {child1, child3}); + AttachIdAfter({ctx}, root, child2, child1); - Expect(ctx.Get(root).children.Size()).ToEqual(3); - Expect(ctx.Get(root).children.FindIndex(child2)).ToEqual(1); - }); + Expect(ctx.Get(root).children.Size()).ToEqual(3); + Expect(ctx.Get(root).children.FindIndex(child2)).ToEqual(1); }); + }); - Describe("TransferIdChildren", []() + Describe("TransferIdChildren", []() + { + It("Moves children from old parent to new parent", []() { - It("Moves children from old parent to new parent", []() - { - Id newRoot = AddId(ctx); - AttachId({ctx}, root, {child1, child2}); - TransferIdChildren({ctx}, {child1, child2}, newRoot); - - Expect(ctx.Get(root).children.IsEmpty()).ToBeTrue(); - Expect(ctx.Has(newRoot)).ToBeTrue(); - Expect(ctx.Get(newRoot).children.Size()).ToEqual(2); - Expect(ctx.Get(child1).parent).ToEqual(newRoot); - Expect(ctx.Get(child2).parent).ToEqual(newRoot); - }); + Id newRoot = AddId(ctx); + AttachId({ctx}, root, {child1, child2}); + TransferIdChildren({ctx}, {child1, child2}, newRoot); + + Expect(ctx.Get(root).children.IsEmpty()).ToBeTrue(); + Expect(ctx.Has(newRoot)).ToBeTrue(); + Expect(ctx.Get(newRoot).children.Size()).ToEqual(2); + Expect(ctx.Get(child1).parent).ToEqual(newRoot); + Expect(ctx.Get(child2).parent).ToEqual(newRoot); }); + }); - Describe("DetachIdParent", []() + Describe("DetachIdParent", []() + { + BeforeEach([]() { - BeforeEach([]() - { - AttachId({ctx}, root, {child1, child2}); - }); + AttachId({ctx}, root, {child1, child2}); + }); - It("Retains CChild component when keepComponents is true", []() - { - DetachIdParent({ctx}, child1, true); + It("Retains CChild component when keepComponents is true", []() + { + DetachIdParent({ctx}, child1, true); - Expect(ctx.Has(child1)).ToBeTrue(); - Expect(ctx.Get(child1).parent).ToEqual(NoId); - Expect(ctx.Get(root).children.Size()).ToEqual(1); - }); + Expect(ctx.Has(child1)).ToBeTrue(); + Expect(ctx.Get(child1).parent).ToEqual(NoId); + Expect(ctx.Get(root).children.Size()).ToEqual(1); + }); - It("Removes CChild from detached child and removes from parent list", []() - { - DetachIdParent({ctx}, child1, false); + It("Removes CChild from detached child and removes from parent list", []() + { + DetachIdParent({ctx}, child1, false); - Expect(ctx.Has(child1)).ToBeFalse(); - Expect(ctx.Get(root).children.Contains(child1)).ToBeFalse(); - }); + Expect(ctx.Has(child1)).ToBeFalse(); + Expect(ctx.Get(root).children.Contains(child1)).ToBeFalse(); + }); - It("Removes empty CParent when all children are detached", []() - { - DetachIdParent({ctx}, {child1, child2}, false); + It("Removes empty CParent when all children are detached", []() + { + DetachIdParent({ctx}, {child1, child2}, false); - Expect(ctx.Has(child1)).ToBeFalse(); - Expect(ctx.Has(child2)).ToBeFalse(); - Expect(ctx.Has(root)).ToBeFalse(); - }); + Expect(ctx.Has(child1)).ToBeFalse(); + Expect(ctx.Has(child2)).ToBeFalse(); + Expect(ctx.Has(root)).ToBeFalse(); }); + }); - Describe("DetachIdChildren", []() + Describe("DetachIdChildren", []() + { + BeforeEach([]() { - BeforeEach([]() - { - AttachId({ctx}, root, {child1, child2}); - }); + AttachId({ctx}, root, {child1, child2}); + }); - It("Severes all children but retains CChild when keepComponents is true", []() - { - DetachIdChildren({ctx}, root, true); + It("Severes all children but retains CChild when keepComponents is true", []() + { + DetachIdChildren({ctx}, root, true); - Expect(ctx.Has(child1)).ToBeTrue(); - Expect(ctx.Has(child2)).ToBeTrue(); - Expect(ctx.Get(child1).parent).ToEqual(NoId); - Expect(ctx.Get(child2).parent).ToEqual(NoId); - Expect(ctx.Get(root).children.IsEmpty()).ToBeTrue(); - }); + Expect(ctx.Has(child1)).ToBeTrue(); + Expect(ctx.Has(child2)).ToBeTrue(); + Expect(ctx.Get(child1).parent).ToEqual(NoId); + Expect(ctx.Get(child2).parent).ToEqual(NoId); + Expect(ctx.Get(root).children.IsEmpty()).ToBeTrue(); + }); - It("Removes CChild and CParent when keepComponents is false", []() - { - DetachIdChildren({ctx}, root, false); + It("Removes CChild and CParent when keepComponents is false", []() + { + DetachIdChildren({ctx}, root, false); - Expect(ctx.Has(child1)).ToBeFalse(); - Expect(ctx.Has(child2)).ToBeFalse(); - Expect(ctx.Has(root)).ToBeFalse(); - }); + Expect(ctx.Has(child1)).ToBeFalse(); + Expect(ctx.Has(child2)).ToBeFalse(); + Expect(ctx.Has(root)).ToBeFalse(); }); + }); - Describe("GetIdChildren", []() + Describe("GetIdChildren", []() + { + BeforeEach([]() { - BeforeEach([]() - { - AttachId({ctx}, root, {child1, child2}); - AttachId({ctx}, child1, grandchild); - }); + AttachId({ctx}, root, {child1, child2}); + AttachId({ctx}, child1, grandchild); + }); - It("Returns child list for parent entities", []() - { - const auto* children = GetIdChildren({ctx}, root); - Expect(children).ToNotEqual(nullptr); - Expect(children->Size()).ToEqual(2); - Expect(children->Contains(child1)).ToBeTrue(); - Expect(children->Contains(child2)).ToBeTrue(); - }); + It("Returns child list for parent entities", []() + { + const auto* children = GetIdChildren({ctx}, root); + Expect(children).ToNotEqual(nullptr); + Expect(children->Size()).ToEqual(2); + Expect(children->Contains(child1)).ToBeTrue(); + Expect(children->Contains(child2)).ToBeTrue(); + }); - It("Combines children from multiple parents into one list", []() - { - TArray outChildren; - GetIdChildren({ctx}, {root, child1}, outChildren); - Expect(outChildren.Size()).ToEqual(3); - Expect(outChildren.Contains(grandchild)).ToBeTrue(); - }); + It("Combines children from multiple parents into one list", []() + { + TArray outChildren; + GetIdChildren({ctx}, {root, child1}, outChildren); + Expect(outChildren.Size()).ToEqual(3); + Expect(outChildren.Contains(grandchild)).ToBeTrue(); + }); - It("Returns null for entities without CParent component", []() - { - Expect(GetIdChildren({ctx}, child2)).ToEqual(nullptr); - }); + It("Returns null for entities without CParent component", []() + { + Expect(GetIdChildren({ctx}, child2)).ToEqual(nullptr); }); + }); - Describe("GetAllIdChildren", []() + Describe("GetAllIdChildren", []() + { + BeforeEach([]() { - BeforeEach([]() - { - AttachId({ctx}, root, child1); - AttachId({ctx}, child1, grandchild); - }); + AttachId({ctx}, root, child1); + AttachId({ctx}, child1, grandchild); + }); - It("Recurses full tree depth to collect all descendents", []() - { - TArray outChildren; - GetAllIdChildren({ctx}, root, outChildren, 10); - Expect(outChildren.Size()).ToEqual(2); - Expect(outChildren.Contains(grandchild)).ToBeTrue(); - }); + It("Recurses full tree depth to collect all descendents", []() + { + TArray outChildren; + GetAllIdChildren({ctx}, root, outChildren, 10); + Expect(outChildren.Size()).ToEqual(2); + Expect(outChildren.Contains(grandchild)).ToBeTrue(); + }); - It("Respects depth limit to return only immediate children", []() - { - TArray outChildren; - GetAllIdChildren({ctx}, root, outChildren, 1); - Expect(outChildren.Size()).ToEqual(1); - Expect(outChildren.Contains(grandchild)).ToBeFalse(); - }); + It("Respects depth limit to return only immediate children", []() + { + TArray outChildren; + GetAllIdChildren({ctx}, root, outChildren, 1); + Expect(outChildren.Size()).ToEqual(1); + Expect(outChildren.Contains(grandchild)).ToBeFalse(); }); + }); - Describe("GetIdParent", []() + Describe("GetIdParent", []() + { + BeforeEach([]() { - BeforeEach([]() - { - AttachId({ctx}, root, child1); - AttachId({ctx}, child1, grandchild); - }); + AttachId({ctx}, root, child1); + AttachId({ctx}, child1, grandchild); + }); - It("Returns parent Id for child entities", []() - { - Expect(GetIdParent({ctx}, child1)).ToEqual(root); - Expect(GetIdParent({ctx}, grandchild)).ToEqual(child1); - }); + It("Returns parent Id for child entities", []() + { + Expect(GetIdParent({ctx}, child1)).ToEqual(root); + Expect(GetIdParent({ctx}, grandchild)).ToEqual(child1); + }); - It("Returns unique parents for multiple children", []() - { - TArray outParents; - GetIdParent({ctx}, {child1, grandchild}, outParents); - Expect(outParents.Size()).ToEqual(2); - Expect(outParents.Contains(root)).ToBeTrue(); - Expect(outParents.Contains(child1)).ToBeTrue(); - }); + It("Returns unique parents for multiple children", []() + { + TArray outParents; + GetIdParent({ctx}, {child1, grandchild}, outParents); + Expect(outParents.Size()).ToEqual(2); + Expect(outParents.Contains(root)).ToBeTrue(); + Expect(outParents.Contains(child1)).ToBeTrue(); + }); - It("Returns NoId for root entities without parent", []() - { - Expect(GetIdParent({ctx}, root)).ToEqual(NoId); - }); + It("Returns NoId for root entities without parent", []() + { + Expect(GetIdParent({ctx}, root)).ToEqual(NoId); + }); - It("Returns NoId for entities without CChild component", []() - { - Expect(GetIdParent({ctx}, child2)).ToEqual(NoId); - }); + It("Returns NoId for entities without CChild component", []() + { + Expect(GetIdParent({ctx}, child2)).ToEqual(NoId); }); + }); - Describe("GetAllIdParents", []() + Describe("GetAllIdParents", []() + { + BeforeEach([]() { - BeforeEach([]() - { - AttachId({ctx}, root, child1); - AttachId({ctx}, child1, grandchild); - }); + AttachId({ctx}, root, child1); + AttachId({ctx}, child1, grandchild); + }); - It("Traverses full ancestry chain from leaf to root", []() - { - TArray outParents; - GetAllIdParents({ctx}, grandchild, outParents); - Expect(outParents.Size()).ToEqual(2); - Expect(outParents[0]).ToEqual(child1); - Expect(outParents[1]).ToEqual(root); - }); + It("Traverses full ancestry chain from leaf to root", []() + { + TArray outParents; + GetAllIdParents({ctx}, grandchild, outParents); + Expect(outParents.Size()).ToEqual(2); + Expect(outParents[0]).ToEqual(child1); + Expect(outParents[1]).ToEqual(root); + }); - It("Returns empty when entity has no CChild component", []() - { - TArray outParents; - GetAllIdParents({ctx}, child2, outParents); - Expect(outParents.IsEmpty()).ToBeTrue(); - }); + It("Returns empty when entity has no CChild component", []() + { + TArray outParents; + GetAllIdParents({ctx}, child2, outParents); + Expect(outParents.IsEmpty()).ToBeTrue(); }); + }); - Describe("FindIdParent", []() + Describe("FindIdParent", []() + { + BeforeEach([]() { - BeforeEach([]() - { - AttachId({ctx}, root, child1); - AttachId({ctx}, child1, grandchild); - }); + AttachId({ctx}, root, child1); + AttachId({ctx}, child1, grandchild); + }); - It("Finds ancestor two levels up matching predicate", []() + It("Finds ancestor two levels up matching predicate", []() + { + Expect(FindIdParent({ctx}, grandchild, + [&](Id id) { - Expect(FindIdParent({ctx}, grandchild, - [&](Id id) - { - return id == root; - })).ToEqual(root); - }); + return id == root; + })).ToEqual(root); + }); - It("Finds immediate parent matching predicate", []() + It("Finds immediate parent matching predicate", []() + { + Expect(FindIdParent({ctx}, grandchild, + [&](Id id) { - Expect(FindIdParent({ctx}, grandchild, - [&](Id id) - { - return id == child1; - })).ToEqual(child1); - }); + return id == child1; + })).ToEqual(child1); + }); - It("Returns NoId when no ancestor matches predicate", []() + It("Returns NoId when no ancestor matches predicate", []() + { + Expect(IsNone(FindIdParent({ctx}, grandchild, + [](Id) { - Expect(IsNone(FindIdParent({ctx}, grandchild, - [](Id) - { - return false; - }))).ToBeTrue(); - }); + return false; + }))).ToBeTrue(); }); + }); - Describe("FindIdParents", []() + Describe("FindIdParents", []() + { + It("Finds nearest matching ancestor for deep entity", []() { - It("Finds nearest matching ancestor for deep entity", []() - { - Id intermediate = AddId(ctx); - AttachId({ctx}, root, child1); - AttachId({ctx}, child1, intermediate); - AttachId({ctx}, intermediate, grandchild); - - TArray outParents; - FindIdParents({ctx}, grandchild, outParents, [](Id) - { - return true; - }); - Expect(outParents.Size()).ToEqual(1); - Expect(outParents.Contains(intermediate)).ToBeTrue(); - }); + Id intermediate = AddId(ctx); + AttachId({ctx}, root, child1); + AttachId({ctx}, child1, intermediate); + AttachId({ctx}, intermediate, grandchild); - It("Returns empty when no ancestor matches predicate", []() + TArray outParents; + FindIdParents({ctx}, grandchild, outParents, [](Id) { - TArray outParents; - FindIdParents({ctx}, child1, outParents, [](Id) - { - return false; - }); - Expect(outParents.IsEmpty()).ToBeTrue(); + return true; }); + Expect(outParents.Size()).ToEqual(1); + Expect(outParents.Contains(intermediate)).ToBeTrue(); }); - Describe("GetIdRoots", []() + It("Returns empty when no ancestor matches predicate", []() { - It("Returns empty when no hierarchy exists", []() + TArray outParents; + FindIdParents({ctx}, child1, outParents, [](Id) { - TArray roots; - GetIdRoots({ctx}, roots); - Expect(roots.IsEmpty()).ToBeTrue(); + return false; }); + Expect(outParents.IsEmpty()).ToBeTrue(); + }); + }); - It("Finds root of single-parent hierarchy", []() - { - AttachId({ctx}, root, {child1, child2}); + Describe("GetIdRoots", []() + { + It("Returns empty when no hierarchy exists", []() + { + TArray roots; + GetIdRoots({ctx}, roots); + Expect(roots.IsEmpty()).ToBeTrue(); + }); - TArray roots; - GetIdRoots({ctx}, roots); - Expect(roots.Size()).ToEqual(1); - Expect(roots.Contains(root)).ToBeTrue(); - }); + It("Finds root of single-parent hierarchy", []() + { + AttachId({ctx}, root, {child1, child2}); - It("Returns multiple roots from independent trees", []() - { - Id root2 = AddId(ctx); - AttachId({ctx}, root, {child1, child2}); - AttachId({ctx}, root2, child3); - - TArray roots; - GetIdRoots({ctx}, roots); - Expect(roots.Size()).ToEqual(2); - Expect(roots.Contains(root)).ToBeTrue(); - Expect(roots.Contains(root2)).ToBeTrue(); - }); + TArray roots; + GetIdRoots({ctx}, roots); + Expect(roots.Size()).ToEqual(1); + Expect(roots.Contains(root)).ToBeTrue(); + }); - It("Excludes entities that are both parent and child of someone", []() - { - AttachId({ctx}, root, child1); - AttachId({ctx}, child1, grandchild); - - TArray roots; - GetIdRoots({ctx}, roots); - Expect(roots.Size()).ToEqual(1); - Expect(roots.Contains(root)).ToBeTrue(); - Expect(roots.Contains(child1)).ToBeFalse(); - }); + It("Returns multiple roots from independent trees", []() + { + Id root2 = AddId(ctx); + AttachId({ctx}, root, {child1, child2}); + AttachId({ctx}, root2, child3); + + TArray roots; + GetIdRoots({ctx}, roots); + Expect(roots.Size()).ToEqual(2); + Expect(roots.Contains(root)).ToBeTrue(); + Expect(roots.Contains(root2)).ToBeTrue(); }); - Describe("GetIdParentRoots", []() + It("Excludes entities that are both parent and child of someone", []() { - BeforeEach([]() - { - AttachId({ctx}, root, child1); - AttachId({ctx}, child1, grandchild); - }); + AttachId({ctx}, root, child1); + AttachId({ctx}, child1, grandchild); + + TArray roots; + GetIdRoots({ctx}, roots); + Expect(roots.Size()).ToEqual(1); + Expect(roots.Contains(root)).ToBeTrue(); + Expect(roots.Contains(child1)).ToBeFalse(); + }); + }); - It("Walks child chain up to root ancestor", []() - { - TArray roots; - GetIdParentRoots({ctx}, grandchild, roots, false); - Expect(roots.Size()).ToEqual(1); - Expect(roots.Contains(root)).ToBeTrue(); - }); + Describe("GetIdParentRoots", []() + { + BeforeEach([]() + { + AttachId({ctx}, root, child1); + AttachId({ctx}, child1, grandchild); + }); - It("Handles children from different trees", []() - { - Id root2 = AddId(ctx); - Id childOf2 = AddId(ctx); - AttachId({ctx}, root2, childOf2); - - TArray roots; - GetIdParentRoots({ctx}, {grandchild, childOf2}, roots, false); - Expect(roots.Size()).ToEqual(2); - Expect(roots.Contains(root)).ToBeTrue(); - Expect(roots.Contains(root2)).ToBeTrue(); - }); + It("Walks child chain up to root ancestor", []() + { + TArray roots; + GetIdParentRoots({ctx}, grandchild, roots, false); + Expect(roots.Size()).ToEqual(1); + Expect(roots.Contains(root)).ToBeTrue(); + }); - It("Considers input entities as roots when considerChildren flag is set", []() - { - TArray roots; - GetIdParentRoots({ctx}, {root, grandchild}, roots, true); - Expect(roots.Size()).ToEqual(1); - Expect(roots.Contains(root)).ToBeTrue(); - }); + It("Handles children from different trees", []() + { + Id root2 = AddId(ctx); + Id childOf2 = AddId(ctx); + AttachId({ctx}, root2, childOf2); + + TArray roots; + GetIdParentRoots({ctx}, {grandchild, childOf2}, roots, false); + Expect(roots.Size()).ToEqual(2); + Expect(roots.Contains(root)).ToBeTrue(); + Expect(roots.Contains(root2)).ToBeTrue(); + }); - It("Returns empty for empty input", []() - { - TArray roots; - GetIdParentRoots({ctx}, {}, roots, false); - Expect(roots.IsEmpty()).ToBeTrue(); - }); + It("Considers input entities as roots when considerChildren flag is set", []() + { + TArray roots; + GetIdParentRoots({ctx}, {root, grandchild}, roots, true); + Expect(roots.Size()).ToEqual(1); + Expect(roots.Contains(root)).ToBeTrue(); + }); - It("Returns empty for entities with no parent", []() - { - TArray roots; - GetIdParentRoots({ctx}, child2, roots, false); - Expect(roots.IsEmpty()).ToBeTrue(); - }); + It("Returns empty for empty input", []() + { + TArray roots; + GetIdParentRoots({ctx}, {}, roots, false); + Expect(roots.IsEmpty()).ToBeTrue(); }); - Describe("FixParentIdLinks", []() + It("Returns empty for entities with no parent", []() { - BeforeEach([]() - { - AttachId({ctx}, root, child1); - }); + TArray roots; + GetIdParentRoots({ctx}, child2, roots, false); + Expect(roots.IsEmpty()).ToBeTrue(); + }); + }); - It("Returns false when parent-child links are already correct", []() - { - Expect(FixParentIdLinks({ctx}, root)).ToBeFalse(); - }); + Describe("FixParentIdLinks", []() + { + BeforeEach([]() + { + AttachId({ctx}, root, child1); + }); - It("Fixes child->parent reference when it does not match parent's list", []() - { - ctx.Get(child1).parent = NoId; + It("Returns false when parent-child links are already correct", []() + { + Expect(FixParentIdLinks({ctx}, root)).ToBeFalse(); + }); - Expect(FixParentIdLinks({ctx}, root)).ToBeTrue(); - Expect(ctx.Get(child1).parent).ToEqual(root); - }); + It("Fixes child->parent reference when it does not match parent's list", []() + { + ctx.Get(child1).parent = NoId; - It("Adds missing CChild component to orphan children", []() - { - ctx.Remove(child1); - Expect(ctx.Has(child1)).ToBeFalse(); + Expect(FixParentIdLinks({ctx}, root)).ToBeTrue(); + Expect(ctx.Get(child1).parent).ToEqual(root); + }); - Expect(FixParentIdLinks({ctx}, root)).ToBeTrue(); - Expect(ctx.Has(child1)).ToBeTrue(); - Expect(ctx.Get(child1).parent).ToEqual(root); - }); + It("Adds missing CChild component to orphan children", []() + { + ctx.Remove(child1); + Expect(ctx.Has(child1)).ToBeFalse(); + + Expect(FixParentIdLinks({ctx}, root)).ToBeTrue(); + Expect(ctx.Has(child1)).ToBeTrue(); + Expect(ctx.Get(child1).parent).ToEqual(root); }); + }); - Describe("ValidateParentIdLinks", []() + Describe("ValidateParentIdLinks", []() + { + BeforeEach([]() { - BeforeEach([]() - { - AttachId({ctx}, root, child1); - }); + AttachId({ctx}, root, child1); + }); - It("Returns true when all parent-child links are consistent", []() - { - Expect(ValidateParentIdLinks({ctx}, root)).ToBeTrue(); - }); + It("Returns true when all parent-child links are consistent", []() + { + Expect(ValidateParentIdLinks({ctx}, root)).ToBeTrue(); + }); - It("Returns false when child->parent reference is mismatched", []() - { - ctx.Get(child1).parent = NoId; + It("Returns false when child->parent reference is mismatched", []() + { + ctx.Get(child1).parent = NoId; - Expect(ValidateParentIdLinks({ctx}, root)).ToBeFalse(); - }); + Expect(ValidateParentIdLinks({ctx}, root)).ToBeFalse(); + }); - It("Returns false when CChild component is missing from child", []() - { - ctx.Remove(child1); + It("Returns false when CChild component is missing from child", []() + { + ctx.Remove(child1); - Expect(ValidateParentIdLinks({ctx}, root)).ToBeFalse(); - }); + Expect(ValidateParentIdLinks({ctx}, root)).ToBeFalse(); }); }); -} +}); +return true; +}(); +} // namespace diff --git a/Tests/ECS/IdRegistry.spec.cpp b/Tests/ECS/IdRegistry.spec.cpp index b3fe4cac..4c67e91b 100644 --- a/Tests/ECS/IdRegistry.spec.cpp +++ b/Tests/ECS/IdRegistry.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include @@ -8,155 +8,160 @@ using namespace p; using namespace std::chrono_literals; -void RegisterECSIdRegistryTests() +namespace { - Spec("ECS.IdRegistry", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("ECS.IdRegistry", []() +{ + It("Can create one id", []() { - It("Can create one id", []() - { - IdRegistry ids; - Expect(ids.Size()).ToEqual(0); - Id id = ids.Create(); - Expect(id).ToNotEqual(NoId); - Expect(ids.IsValid(id)).ToBeTrue(); - Expect(ids.Size()).ToEqual(1); - }); - - It("Can remove one id", []() - { - IdRegistry ids; - Id id = ids.Create(); - Expect(ids.Size()).ToEqual(1); - Expect(ids.RemoveInstant(id)).ToBeTrue(); - Expect(ids.IsValid(id)).ToBeFalse(); - Expect(ids.Size()).ToEqual(0); - }); - - It("Can create two and remove first", []() - { - IdRegistry ids; - Id id1 = ids.Create(); - ids.Create(); - Expect(ids.RemoveInstant(id1)).ToBeTrue(); - Expect(ids.IsValid(id1)).ToBeFalse(); - Expect(ids.Size()).ToEqual(1); - }); - - It("Can create two and remove last", []() - { - IdRegistry ids; - ids.Create(); - Id id2 = ids.Create(); - Expect(ids.RemoveInstant(id2)).ToBeTrue(); - Expect(ids.IsValid(id2)).ToBeFalse(); - Expect(ids.Size()).ToEqual(1); - }); - - It("Can remove one id (deferred)", []() - { - IdRegistry ids; - Id id = ids.Create(); - Expect(ids.Size()).ToEqual(1); - Expect(ids.Remove(id)).ToBeTrue(); - Expect(ids.IsValid(id)).ToBeFalse(); - Expect(ids.Size()).ToEqual(0); - }); - - It("Can create two and remove first (deferred)", []() - { - IdRegistry ids; - Id id1 = ids.Create(); - ids.Create(); - Expect(ids.Remove(id1)).ToBeTrue(); - Expect(ids.IsValid(id1)).ToBeFalse(); - Expect(ids.Size()).ToEqual(1); - }); - - It("Can create two and remove last (deferred)", []() - { - IdRegistry ids; - ids.Create(); - Id id2 = ids.Create(); - Expect(ids.Remove(id2)).ToBeTrue(); - Expect(ids.IsValid(id2)).ToBeFalse(); - Expect(ids.Size()).ToEqual(1); - }); - - It("Removed id index gets reused", []() - { - IdRegistry ids; - ids.Create(); - Id id = ids.Create(); - ids.Create(); - Expect(ids.RemoveInstant(id)).ToBeTrue(); - Id id2 = ids.Create(); - Expect(id2.GetIndex()).ToEqual(id.GetIndex()); - Id id3 = ids.Create(); - Expect(id3.GetIndex()).ToNotEqual(id.GetIndex()); - }); - - It("Deferred removed id index doesn't get reused until flushed", []() - { - IdRegistry ids; - ids.Create(); - Id id = ids.Create(); - ids.Create(); - Expect(ids.Remove(id)).ToBeTrue(); - Id id2 = ids.Create(); - Expect(id2.GetIndex()).ToNotEqual(id.GetIndex()); - ids.FlushDeferredRemovals(); - Id id3 = ids.Create(); - Expect(id3.GetIndex()).ToEqual(id.GetIndex()); - Id id4 = ids.Create(); - Expect(id4.GetIndex()).ToNotEqual(id.GetIndex()); - }); - - It("Can create many ids", []() + IdRegistry ids; + Expect(ids.Size()).ToEqual(0); + Id id = ids.Create(); + Expect(id).ToNotEqual(NoId); + Expect(ids.IsValid(id)).ToBeTrue(); + Expect(ids.Size()).ToEqual(1); + }); + + It("Can remove one id", []() + { + IdRegistry ids; + Id id = ids.Create(); + Expect(ids.Size()).ToEqual(1); + Expect(ids.RemoveInstant(id)).ToBeTrue(); + Expect(ids.IsValid(id)).ToBeFalse(); + Expect(ids.Size()).ToEqual(0); + }); + + It("Can create two and remove first", []() + { + IdRegistry ids; + Id id1 = ids.Create(); + ids.Create(); + Expect(ids.RemoveInstant(id1)).ToBeTrue(); + Expect(ids.IsValid(id1)).ToBeFalse(); + Expect(ids.Size()).ToEqual(1); + }); + + It("Can create two and remove last", []() + { + IdRegistry ids; + ids.Create(); + Id id2 = ids.Create(); + Expect(ids.RemoveInstant(id2)).ToBeTrue(); + Expect(ids.IsValid(id2)).ToBeFalse(); + Expect(ids.Size()).ToEqual(1); + }); + + It("Can remove one id (deferred)", []() + { + IdRegistry ids; + Id id = ids.Create(); + Expect(ids.Size()).ToEqual(1); + Expect(ids.Remove(id)).ToBeTrue(); + Expect(ids.IsValid(id)).ToBeFalse(); + Expect(ids.Size()).ToEqual(0); + }); + + It("Can create two and remove first (deferred)", []() + { + IdRegistry ids; + Id id1 = ids.Create(); + ids.Create(); + Expect(ids.Remove(id1)).ToBeTrue(); + Expect(ids.IsValid(id1)).ToBeFalse(); + Expect(ids.Size()).ToEqual(1); + }); + + It("Can create two and remove last (deferred)", []() + { + IdRegistry ids; + ids.Create(); + Id id2 = ids.Create(); + Expect(ids.Remove(id2)).ToBeTrue(); + Expect(ids.IsValid(id2)).ToBeFalse(); + Expect(ids.Size()).ToEqual(1); + }); + + It("Removed id index gets reused", []() + { + IdRegistry ids; + ids.Create(); + Id id = ids.Create(); + ids.Create(); + Expect(ids.RemoveInstant(id)).ToBeTrue(); + Id id2 = ids.Create(); + Expect(id2.GetIndex()).ToEqual(id.GetIndex()); + Id id3 = ids.Create(); + Expect(id3.GetIndex()).ToNotEqual(id.GetIndex()); + }); + + It("Deferred removed id index doesn't get reused until flushed", []() + { + IdRegistry ids; + ids.Create(); + Id id = ids.Create(); + ids.Create(); + Expect(ids.Remove(id)).ToBeTrue(); + Id id2 = ids.Create(); + Expect(id2.GetIndex()).ToNotEqual(id.GetIndex()); + ids.FlushDeferredRemovals(); + Id id3 = ids.Create(); + Expect(id3.GetIndex()).ToEqual(id.GetIndex()); + Id id4 = ids.Create(); + Expect(id4.GetIndex()).ToNotEqual(id.GetIndex()); + }); + + It("Can create many ids", []() + { + IdRegistry ids; + Expect(ids.Size()).ToEqual(0); + + TArray list(3); + ids.Create(list); + + Expect(ids.Size()).ToEqual(3); + for (i32 i = 0; i < list.Size(); ++i) { - IdRegistry ids; - Expect(ids.Size()).ToEqual(0); + Expect(list[i].GetIndex()).ToEqual(i); + Expect(ids.IsValid(list[i])).ToBeTrue(); + } + }); - TArray list(3); - ids.Create(list); + It("Can remove many ids", []() + { + IdRegistry ids; + TArray list(3); + ids.Create(list); + Expect(ids.Size()).ToEqual(3); - Expect(ids.Size()).ToEqual(3); - for (i32 i = 0; i < list.Size(); ++i) - { - Expect(list[i].GetIndex()).ToEqual(i); - Expect(ids.IsValid(list[i])).ToBeTrue(); - } - }); + Expect(ids.RemoveInstant(list)).ToBeTrue(); + Expect(ids.Size()).ToEqual(0); - It("Can remove many ids", []() + for (i32 i = 0; i < list.Size(); ++i) { - IdRegistry ids; - TArray list(3); - ids.Create(list); - Expect(ids.Size()).ToEqual(3); + Expect(ids.IsValid(list[i])).ToBeFalse(); + } + }); - Expect(ids.RemoveInstant(list)).ToBeTrue(); - Expect(ids.Size()).ToEqual(0); + It("Can remove many ids (deferred)", []() + { + IdRegistry ids; + TArray list(3); + ids.Create(list); + Expect(ids.Size()).ToEqual(3); - for (i32 i = 0; i < list.Size(); ++i) - { - Expect(ids.IsValid(list[i])).ToBeFalse(); - } - }); + Expect(ids.Remove(list)).ToBeTrue(); + Expect(ids.Size()).ToEqual(0); - It("Can remove many ids (deferred)", []() + for (i32 i = 0; i < list.Size(); ++i) { - IdRegistry ids; - TArray list(3); - ids.Create(list); - Expect(ids.Size()).ToEqual(3); - - Expect(ids.Remove(list)).ToBeTrue(); - Expect(ids.Size()).ToEqual(0); - - for (i32 i = 0; i < list.Size(); ++i) - { - Expect(ids.IsValid(list[i])).ToBeFalse(); - } - }); + Expect(ids.IsValid(list[i])).ToBeFalse(); + } }); -} +}); +return true; +}(); +} // namespace diff --git a/Tests/ECS/IdScopes.spec.cpp b/Tests/ECS/IdScopes.spec.cpp index 5c355d69..fdf28661 100644 --- a/Tests/ECS/IdScopes.spec.cpp +++ b/Tests/ECS/IdScopes.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include @@ -21,121 +21,126 @@ struct ScopeTypeC }; -void RegisterECSIdScopesTests() +namespace { - Spec("ECS.IdScopes", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("ECS.IdScopes", []() +{ + Describe("Templated", []() { - Describe("Templated", []() + It("Can cache pools", []() + { + IdContext ctx; + TIdScope> scope{ctx}; + + Expect(scope.GetPool()).ToEqual(ctx.GetPool()); + Expect(scope.GetPool()).ToEqual(ctx.GetPool()); + Expect(scope.GetPool()).ToEqual(ctx.GetPool()); + }); + + It("Can check if contained", []() + { + IdContext ctx; + TPool& pool = ctx.AssurePool(); + TIdScope> scope{ctx}; + TIdScope scopeConst{ctx}; + Id id = NoId; + Expect(scope.Has(id)).ToBeFalse(); + Expect(scopeConst.Has(id)).ToBeFalse(); + + id = AddId(ctx); + Expect(scope.Has(id)).ToBeFalse(); + Expect(scopeConst.Has(id)).ToBeFalse(); + + ctx.Add(id); + Expect(scope.Has(id)).ToBeTrue(); + Expect(scopeConst.Has(id)).ToBeTrue(); + + TIdScope scope2{ctx}; + ctx.Add(id); + Expect(scope2.Has(id)).ToBeTrue(); + }); + + It("Can initialize superset", []() + { + IdContext ctx; + TPool& typePool = ctx.AssurePool(); + + TIdScope> scope1{ctx}; + TIdScope> superset1{scope1}; + Expect(superset1.GetPool()).ToEqual(&typePool); + + TIdScope> scope2{ctx}; + TIdScope superset2{scope2}; + Expect(superset2.GetPool()).ToEqual(&typePool); + + TIdScope> scope3{ctx}; + TIdScope superset3{scope3}; + Expect(superset1.GetPool()).ToEqual(&typePool); + }); + + It("Can mark modify", []() + { + IdContext ctx; + Id id = AddId(ctx); + TIdScope>> scope1{ctx}; + Expect(scope1.Has>(id)).ToBeFalse(); + scope1.Modify(id); + Expect(scope1.Has>(id)).ToBeTrue(); + Expect(scope1.IsModified(id)).ToBeTrue(); + + scope1.Remove>(id); + Expect(scope1.Has>(id)).ToBeFalse(); + Expect(scope1.IsModified(id)).ToBeFalse(); + + scope1.Modify(id); + Expect(scope1.Has>(id)).ToBeTrue(); + Expect(scope1.IsModified(id)).ToBeTrue(); + }); + + It("Can mark modify automatically", []() { - It("Can cache pools", []() - { - IdContext ctx; - TIdScope> scope{ctx}; - - Expect(scope.GetPool()).ToEqual(ctx.GetPool()); - Expect(scope.GetPool()).ToEqual(ctx.GetPool()); - Expect(scope.GetPool()).ToEqual(ctx.GetPool()); - }); - - It("Can check if contained", []() - { - IdContext ctx; - TPool& pool = ctx.AssurePool(); - TIdScope> scope{ctx}; - TIdScope scopeConst{ctx}; - Id id = NoId; - Expect(scope.Has(id)).ToBeFalse(); - Expect(scopeConst.Has(id)).ToBeFalse(); - - id = AddId(ctx); - Expect(scope.Has(id)).ToBeFalse(); - Expect(scopeConst.Has(id)).ToBeFalse(); - - ctx.Add(id); - Expect(scope.Has(id)).ToBeTrue(); - Expect(scopeConst.Has(id)).ToBeTrue(); - - TIdScope scope2{ctx}; - ctx.Add(id); - Expect(scope2.Has(id)).ToBeTrue(); - }); - - It("Can initialize superset", []() - { - IdContext ctx; - TPool& typePool = ctx.AssurePool(); - - TIdScope> scope1{ctx}; - TIdScope> superset1{scope1}; - Expect(superset1.GetPool()).ToEqual(&typePool); - - TIdScope> scope2{ctx}; - TIdScope superset2{scope2}; - Expect(superset2.GetPool()).ToEqual(&typePool); - - TIdScope> scope3{ctx}; - TIdScope superset3{scope3}; - Expect(superset1.GetPool()).ToEqual(&typePool); - }); - - It("Can mark modify", []() - { - IdContext ctx; - Id id = AddId(ctx); - TIdScope>> scope1{ctx}; - Expect(scope1.Has>(id)).ToBeFalse(); - scope1.Modify(id); - Expect(scope1.Has>(id)).ToBeTrue(); - Expect(scope1.IsModified(id)).ToBeTrue(); - - scope1.Remove>(id); - Expect(scope1.Has>(id)).ToBeFalse(); - Expect(scope1.IsModified(id)).ToBeFalse(); - - scope1.Modify(id); - Expect(scope1.Has>(id)).ToBeTrue(); - Expect(scope1.IsModified(id)).ToBeTrue(); - }); - - It("Can mark modify automatically", []() - { - IdContext ctx; - Id id = AddId(ctx); - using MyScope = TIdScope, CMdfd>; - MyScope scope{ctx}; - Expect(MyScope::WDependencies::Contains>()).ToBeTrue(); - Expect(MyScope::WDependencies::Contains>()).ToBeFalse(); - Expect(MyScope::RWDependencies::Contains>()).ToBeTrue(); - Expect(MyScope::RWDependencies::Contains>()).ToBeTrue(); - - scope.Add(id); // Type B should be auto modified - Expect(scope.IsModified(id)).ToBeTrue(); - scope.Add(id); // Type B should not be auto modified - Expect(scope.IsModified(id)).ToBeFalse(); - - scope.ClearPool>(); - Expect(scope.IsModified(id)).ToBeFalse(); - - scope.Has(id); // Has should never mark modify - Expect(scope.IsModified(id)).ToBeFalse(); - - scope.Get(id); - Expect(scope.IsModified(id)).ToBeFalse(); - scope.Get(id); - Expect(scope.IsModified(id)).ToBeTrue(); - scope.Add(id); // Type B should not be auto modified - Expect(scope.IsModified(id)).ToBeFalse(); - - scope.ClearPool>(); - - scope.Remove(id); - Expect(scope.Has(id)).ToBeFalse(); - Expect(scope.IsModified(id)).ToBeTrue(); - - scope.Remove(id); // Type B should not be auto modified - Expect(scope.Has(id)).ToBeFalse(); - Expect(scope.IsModified(id)).ToBeFalse(); - }); + IdContext ctx; + Id id = AddId(ctx); + using MyScope = TIdScope, CMdfd>; + MyScope scope{ctx}; + Expect(MyScope::WDependencies::Contains>()).ToBeTrue(); + Expect(MyScope::WDependencies::Contains>()).ToBeFalse(); + Expect(MyScope::RWDependencies::Contains>()).ToBeTrue(); + Expect(MyScope::RWDependencies::Contains>()).ToBeTrue(); + + scope.Add(id); // Type B should be auto modified + Expect(scope.IsModified(id)).ToBeTrue(); + scope.Add(id); // Type B should not be auto modified + Expect(scope.IsModified(id)).ToBeFalse(); + + scope.ClearPool>(); + Expect(scope.IsModified(id)).ToBeFalse(); + + scope.Has(id); // Has should never mark modify + Expect(scope.IsModified(id)).ToBeFalse(); + + scope.Get(id); + Expect(scope.IsModified(id)).ToBeFalse(); + scope.Get(id); + Expect(scope.IsModified(id)).ToBeTrue(); + scope.Add(id); // Type B should not be auto modified + Expect(scope.IsModified(id)).ToBeFalse(); + + scope.ClearPool>(); + + scope.Remove(id); + Expect(scope.Has(id)).ToBeFalse(); + Expect(scope.IsModified(id)).ToBeTrue(); + + scope.Remove(id); // Type B should not be auto modified + Expect(scope.Has(id)).ToBeFalse(); + Expect(scope.IsModified(id)).ToBeFalse(); }); }); -} +}); +return true; +}(); +} // namespace diff --git a/Tests/ECS/Statics.spec.cpp b/Tests/ECS/Statics.spec.cpp index 235ad0e7..ebe655b9 100644 --- a/Tests/ECS/Statics.spec.cpp +++ b/Tests/ECS/Statics.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include @@ -22,72 +22,77 @@ struct StaticTypeThree }; -void RegisterECSStaticsTests() +namespace { - Spec("ECS.Statics", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("ECS.Statics", []() +{ + It("Can set an static", []() + { + IdContext ctx; + Expect(ctx.HasStatic()).ToEqual(false); + auto& var = ctx.SetStatic({4}); + Expect(var.i).ToEqual(4); + Expect(ctx.HasStatic()).ToEqual(true); + Expect(ctx.HasStatic()).ToEqual(false); + }); + It("Can set two statics", []() + { + IdContext ctx; + Expect(ctx.HasStatic()).ToEqual(false); + Expect(ctx.HasStatic()).ToEqual(false); + auto& var1 = ctx.SetStatic({4}); + auto& var2 = ctx.SetStatic({2}); + Expect(var1.i).ToEqual(4); + Expect(var2.i).ToEqual(2); + Expect(ctx.HasStatic()).ToEqual(true); + Expect(ctx.HasStatic()).ToEqual(true); + }); + It("Can replace an static", []() { - It("Can set an static", []() - { - IdContext ctx; - Expect(ctx.HasStatic()).ToEqual(false); - auto& var = ctx.SetStatic({4}); - Expect(var.i).ToEqual(4); - Expect(ctx.HasStatic()).ToEqual(true); - Expect(ctx.HasStatic()).ToEqual(false); - }); - It("Can set two statics", []() - { - IdContext ctx; - Expect(ctx.HasStatic()).ToEqual(false); - Expect(ctx.HasStatic()).ToEqual(false); - auto& var1 = ctx.SetStatic({4}); - auto& var2 = ctx.SetStatic({2}); - Expect(var1.i).ToEqual(4); - Expect(var2.i).ToEqual(2); - Expect(ctx.HasStatic()).ToEqual(true); - Expect(ctx.HasStatic()).ToEqual(true); - }); - It("Can replace an static", []() - { - IdContext ctx; - Expect(ctx.HasStatic()).ToEqual(false); - ctx.SetStatic({4}); - ctx.SetStatic({2}); - Expect(ctx.GetStatic().i).ToEqual(2); - Expect(ctx.HasStatic()).ToEqual(true); - }); - It("Can get or set an static", []() - { - IdContext ctx; - // Can set - Expect(ctx.GetOrSetStatic({4}).i).ToEqual(4); - // Can get - Expect(ctx.GetOrSetStatic({10}).i).ToEqual(4); - }); - It("Can remove an static", []() - { - IdContext ctx; - ctx.SetStatic(); - Expect(ctx.HasStatic()).ToEqual(true); - Expect(ctx.RemoveStatic()).ToBeTrue(); - Expect(ctx.HasStatic()).ToEqual(false); + IdContext ctx; + Expect(ctx.HasStatic()).ToEqual(false); + ctx.SetStatic({4}); + ctx.SetStatic({2}); + Expect(ctx.GetStatic().i).ToEqual(2); + Expect(ctx.HasStatic()).ToEqual(true); + }); + It("Can get or set an static", []() + { + IdContext ctx; + // Can set + Expect(ctx.GetOrSetStatic({4}).i).ToEqual(4); + // Can get + Expect(ctx.GetOrSetStatic({10}).i).ToEqual(4); + }); + It("Can remove an static", []() + { + IdContext ctx; + ctx.SetStatic(); + Expect(ctx.HasStatic()).ToEqual(true); + Expect(ctx.RemoveStatic()).ToBeTrue(); + Expect(ctx.HasStatic()).ToEqual(false); - Expect(ctx.RemoveStatic()).ToBeFalse(); - }); + Expect(ctx.RemoveStatic()).ToBeFalse(); + }); - It("Can get statics", []() - { - IdContext ctx; - ctx.SetStatic({4}); - ctx.SetStatic({2}); - Expect(ctx.GetStatic().i).ToEqual(4); - Expect(ctx.GetStatic().i).ToEqual(2); + It("Can get statics", []() + { + IdContext ctx; + ctx.SetStatic({4}); + ctx.SetStatic({2}); + Expect(ctx.GetStatic().i).ToEqual(4); + Expect(ctx.GetStatic().i).ToEqual(2); - ctx.SetStatic({14}); - Expect(ctx.GetStatic().i).ToEqual(14); + ctx.SetStatic({14}); + Expect(ctx.GetStatic().i).ToEqual(14); - ctx.RemoveStatic(); - Expect(ctx.TryGetStatic()).ToEqual(nullptr); - }); + ctx.RemoveStatic(); + Expect(ctx.TryGetStatic()).ToEqual(nullptr); }); -} +}); +return true; +}(); +} // namespace diff --git a/Tests/Files/Paths.spec.cpp b/Tests/Files/Paths.spec.cpp index 56dd547b..c3d7466e 100644 --- a/Tests/Files/Paths.spec.cpp +++ b/Tests/Files/Paths.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include #include @@ -8,206 +8,211 @@ using namespace p; -void RegisterFilesPathsTests() +namespace { - Spec("Files.Paths", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("Files.Paths", []() +{ + It("Can get root name and path", []() + { +#if P_PLATFORM_WINDOWS + Expect(p::GetRootPathName("F:\\SomeFolder\\AnotherFolder")).ToEqual("F:"); + Expect(p::GetRootPath("F:\\SomeFolder\\AnotherFolder")).ToEqual("F:\\"); +#elif P_PLATFORM_LINUX + Expect(p::GetRootPathName("/var/SomeFolder/AnotherFolder")).ToEqual(""); + Expect(p::GetRootPath("/var/SomeFolder/AnotherFolder")).ToEqual("/"); +#endif + Expect(p::GetRootPathName("/AnotherFolder")).ToEqual(""); + Expect(p::GetRootPath("/AnotherFolder")).ToEqual("/"); + }); + + It("Can get relative path", []() + { +#if P_PLATFORM_WINDOWS + Expect(p::GetRelativePath("F:\\SomeFolder\\AnotherFolder")).ToEqual("SomeFolder\\AnotherFolder"); +#endif + Expect(p::GetRelativePath("/var/SomeFolder/AnotherFolder")).ToEqual("var/SomeFolder/AnotherFolder"); + Expect(p::GetRelativePath("/SomeFolder/AnotherFolder")).ToEqual("SomeFolder/AnotherFolder"); + }); + + It("Can check absolute path", []() + { + Expect(p::IsAbsolutePath("//host")).ToEqual(true); +#if P_PLATFORM_WINDOWS + Expect(p::IsAbsolutePath("F:\\SomeFolder\\AnotherFolder")).ToEqual(true); +#elif P_PLATFORM_LINUX + Expect(p::IsAbsolutePath("/var/SomeFolder/AnotherFolder")).ToEqual(true); +#endif + Expect(p::IsAbsolutePath("Executable.exe")).ToEqual(false); + Expect(p::IsAbsolutePath("SomeFolder/AnotherFolder")).ToEqual(false); + }); + + It("Can check relative path", []() + { +#if P_PLATFORM_WINDOWS + Expect(p::IsRelativePath("F:\\SomeFolder\\AnotherFolder")).ToEqual(false); +#elif P_PLATFORM_LINUX + Expect(p::IsRelativePath("/var/SomeFolder/AnotherFolder")).ToEqual(false); +#endif + Expect(p::IsRelativePath("Executable.exe")).ToEqual(true); + Expect(p::IsRelativePath("SomeFolder/AnotherFolder")).ToEqual(true); + }); + + It("Can get parent path", []() + { +#if P_PLATFORM_WINDOWS + Expect(p::GetParentPath("F:\\SomeFolder\\AnotherFolder")).ToEqual("F:\\SomeFolder"); +#endif + Expect(p::GetParentPath("/var/SomeFolder")).ToEqual("/var"); + Expect(p::GetParentPath("/SomeFolder/AnotherFolder")).ToEqual("/SomeFolder"); + Expect(p::GetParentPath("/SomeFolder/SomeFile.txt")).ToEqual("/SomeFolder"); + }); + + It("Executable path is not empty", []() + { + Expect(p::PlatformPaths::GetExecutablePath()).ToNotEqual(""); + }); + + It("Can get extension", []() + { +#if P_PLATFORM_WINDOWS + Expect(p::GetExtension("F:\\SomeFolder\\AnotherFolder.lib")).ToEqual(".lib"); + Expect(p::GetExtension("F:\\AnotherFolder.lib")).ToEqual(".lib"); + Expect(p::GetExtension("F:\\AnotherFolder.")).ToEqual("."); + Expect(p::GetExtension("F:\\AnotherFolder")).ToEqual(""); + Expect(p::GetExtension("F:\\")).ToEqual(""); +#elif P_PLATFORM_LINUX + Expect(p::GetExtension("/var/SomeFolder/AnotherFolder.lib")).ToEqual(".lib"); + Expect(p::GetExtension("/var/AnotherFolder.lib")).ToEqual(".lib"); + Expect(p::GetExtension("/var/AnotherFolder.")).ToEqual("."); + Expect(p::GetExtension("/var/AnotherFolder")).ToEqual(""); + Expect(p::GetExtension("/var/")).ToEqual(""); +#endif + Expect(p::GetExtension("AnotherFolder.lib")).ToEqual(".lib"); + Expect(p::GetExtension("AnotherFolder")).ToEqual(""); + }); + + It("Can check extension", []() + { +#if P_PLATFORM_WINDOWS + Expect(p::HasExtension("F:\\SomeFolder\\AnotherFolder.lib")).ToEqual(true); + Expect(p::HasExtension("F:\\AnotherFolder.lib")).ToEqual(true); + Expect(p::HasExtension("F:\\AnotherFolder.")).ToEqual(true); + Expect(p::HasExtension("F:\\AnotherFolder")).ToEqual(false); + Expect(p::HasExtension("F:\\")).ToEqual(false); +#elif P_PLATFORM_LINUX + Expect(p::HasExtension("/var/SomeFolder/AnotherFolder.lib")).ToEqual(true); + Expect(p::HasExtension("/var/AnotherFolder.lib")).ToEqual(true); + Expect(p::HasExtension("/var/AnotherFolder.")).ToEqual(true); + Expect(p::HasExtension("/var/AnotherFolder")).ToEqual(false); + Expect(p::HasExtension("/var/")).ToEqual(false); +#endif + Expect(p::HasExtension("AnotherFolder.lib")).ToEqual(true); + Expect(p::HasExtension("AnotherFolder")).ToEqual(false); + }); + + It("Can replace extension", []() + { + p::String path; +#if P_PLATFORM_WINDOWS + path = "F:\\SomeFolder\\AnotherFolder.lib"; + p::ReplaceExtension(path, "txt"); + Expect(path).ToEqual("F:\\SomeFolder\\AnotherFolder.txt"); +#elif P_PLATFORM_LINUX + path = "/var/SomeFolder/AnotherFolder.lib"; + p::ReplaceExtension(path, "txt"); + Expect(path).ToEqual("/var/SomeFolder/AnotherFolder.txt"); +#endif + path = "AnotherFolder.lib"; + p::ReplaceExtension(path, "txt"); + Expect(path).ToEqual("AnotherFolder.txt"); + path = "AnotherFolder."; + p::ReplaceExtension(path, ".txt"); + Expect(path).ToEqual("AnotherFolder.txt"); + path = "AnotherFolder.lib"; + p::ReplaceExtension(path, ".txt"); + Expect(path).ToEqual("AnotherFolder.txt"); + path = "AnotherFolder"; + p::ReplaceExtension(path, "txt"); + Expect(path).ToEqual("AnotherFolder.txt"); + }); + + It("Can get stem", []() + { +#if P_PLATFORM_WINDOWS + Expect(p::GetStem("F:\\SomeFolder\\AnotherFolder.lib")).ToEqual("AnotherFolder"); + Expect(p::GetStem("F:\\AnotherFolder.lib")).ToEqual("AnotherFolder"); + Expect(p::GetStem("F:\\AnotherFolder.")).ToEqual("AnotherFolder"); + Expect(p::GetStem("F:\\AnotherFolder")).ToEqual("AnotherFolder"); + Expect(p::GetStem("F:\\")).ToEqual(""); +#elif P_PLATFORM_LINUX + Expect(p::GetStem("/var/SomeFolder/AnotherFolder.lib")).ToEqual("AnotherFolder"); + Expect(p::GetStem("/var/AnotherFolder.lib")).ToEqual("AnotherFolder"); + Expect(p::GetStem("/var/AnotherFolder.")).ToEqual("AnotherFolder"); + Expect(p::GetStem("/var/AnotherFolder")).ToEqual("AnotherFolder"); + Expect(p::GetStem("/var/")).ToEqual(""); +#endif + Expect(p::GetStem("AnotherFolder.lib")).ToEqual("AnotherFolder"); + Expect(p::GetStem("AnotherFolder")).ToEqual("AnotherFolder"); + Expect(p::GetStem("")).ToEqual(""); + }); + + It("Can check stem", []() + { +#if P_PLATFORM_WINDOWS + Expect(p::HasStem("F:\\SomeFolder\\AnotherFolder.lib")).ToEqual(true); + Expect(p::HasStem("F:\\AnotherFolder.lib")).ToEqual(true); + Expect(p::HasStem("F:\\AnotherFolder.")).ToEqual(true); + Expect(p::HasStem("F:\\AnotherFolder")).ToEqual(true); + Expect(p::HasStem("F:\\")).ToEqual(false); +#elif P_PLATFORM_LINUX + Expect(p::HasStem("/var/SomeFolder/AnotherFolder.lib")).ToEqual(true); + Expect(p::HasStem("/var/AnotherFolder.lib")).ToEqual(true); + Expect(p::HasStem("/var/AnotherFolder.")).ToEqual(true); + Expect(p::HasStem("/var/AnotherFolder")).ToEqual(true); + Expect(p::HasStem("/var/")).ToEqual(false); +#endif + Expect(p::HasStem("AnotherFolder.lib")).ToEqual(true); + Expect(p::HasStem("AnotherFolder")).ToEqual(true); + Expect(p::HasStem("")).ToEqual(false); + }); + + + It("Can append to path", []() { - It("Can get root name and path", []() - { - #if P_PLATFORM_WINDOWS - Expect(p::GetRootPathName("F:\\SomeFolder\\AnotherFolder")).ToEqual("F:"); - Expect(p::GetRootPath("F:\\SomeFolder\\AnotherFolder")).ToEqual("F:\\"); - #elif P_PLATFORM_LINUX - Expect(p::GetRootPathName("/var/SomeFolder/AnotherFolder")).ToEqual(""); - Expect(p::GetRootPath("/var/SomeFolder/AnotherFolder")).ToEqual("/"); - #endif - Expect(p::GetRootPathName("/AnotherFolder")).ToEqual(""); - Expect(p::GetRootPath("/AnotherFolder")).ToEqual("/"); - }); - - It("Can get relative path", []() - { - #if P_PLATFORM_WINDOWS - Expect(p::GetRelativePath("F:\\SomeFolder\\AnotherFolder")).ToEqual("SomeFolder\\AnotherFolder"); - #endif - Expect(p::GetRelativePath("/var/SomeFolder/AnotherFolder")).ToEqual("var/SomeFolder/AnotherFolder"); - Expect(p::GetRelativePath("/SomeFolder/AnotherFolder")).ToEqual("SomeFolder/AnotherFolder"); - }); - - It("Can check absolute path", []() - { - Expect(p::IsAbsolutePath("//host")).ToEqual(true); - #if P_PLATFORM_WINDOWS - Expect(p::IsAbsolutePath("F:\\SomeFolder\\AnotherFolder")).ToEqual(true); - #elif P_PLATFORM_LINUX - Expect(p::IsAbsolutePath("/var/SomeFolder/AnotherFolder")).ToEqual(true); - #endif - Expect(p::IsAbsolutePath("Executable.exe")).ToEqual(false); - Expect(p::IsAbsolutePath("SomeFolder/AnotherFolder")).ToEqual(false); - }); - - It("Can check relative path", []() - { - #if P_PLATFORM_WINDOWS - Expect(p::IsRelativePath("F:\\SomeFolder\\AnotherFolder")).ToEqual(false); - #elif P_PLATFORM_LINUX - Expect(p::IsRelativePath("/var/SomeFolder/AnotherFolder")).ToEqual(false); - #endif - Expect(p::IsRelativePath("Executable.exe")).ToEqual(true); - Expect(p::IsRelativePath("SomeFolder/AnotherFolder")).ToEqual(true); - }); - - It("Can get parent path", []() - { - #if P_PLATFORM_WINDOWS - Expect(p::GetParentPath("F:\\SomeFolder\\AnotherFolder")).ToEqual("F:\\SomeFolder"); - #endif - Expect(p::GetParentPath("/var/SomeFolder")).ToEqual("/var"); - Expect(p::GetParentPath("/SomeFolder/AnotherFolder")).ToEqual("/SomeFolder"); - Expect(p::GetParentPath("/SomeFolder/SomeFile.txt")).ToEqual("/SomeFolder"); - }); - - It("Executable path is not empty", []() - { - Expect(p::PlatformPaths::GetExecutablePath()).ToNotEqual(""); - }); - - It("Can get extension", []() - { - #if P_PLATFORM_WINDOWS - Expect(p::GetExtension("F:\\SomeFolder\\AnotherFolder.lib")).ToEqual(".lib"); - Expect(p::GetExtension("F:\\AnotherFolder.lib")).ToEqual(".lib"); - Expect(p::GetExtension("F:\\AnotherFolder.")).ToEqual("."); - Expect(p::GetExtension("F:\\AnotherFolder")).ToEqual(""); - Expect(p::GetExtension("F:\\")).ToEqual(""); - #elif P_PLATFORM_LINUX - Expect(p::GetExtension("/var/SomeFolder/AnotherFolder.lib")).ToEqual(".lib"); - Expect(p::GetExtension("/var/AnotherFolder.lib")).ToEqual(".lib"); - Expect(p::GetExtension("/var/AnotherFolder.")).ToEqual("."); - Expect(p::GetExtension("/var/AnotherFolder")).ToEqual(""); - Expect(p::GetExtension("/var/")).ToEqual(""); - #endif - Expect(p::GetExtension("AnotherFolder.lib")).ToEqual(".lib"); - Expect(p::GetExtension("AnotherFolder")).ToEqual(""); - }); - - It("Can check extension", []() - { - #if P_PLATFORM_WINDOWS - Expect(p::HasExtension("F:\\SomeFolder\\AnotherFolder.lib")).ToEqual(true); - Expect(p::HasExtension("F:\\AnotherFolder.lib")).ToEqual(true); - Expect(p::HasExtension("F:\\AnotherFolder.")).ToEqual(true); - Expect(p::HasExtension("F:\\AnotherFolder")).ToEqual(false); - Expect(p::HasExtension("F:\\")).ToEqual(false); - #elif P_PLATFORM_LINUX - Expect(p::HasExtension("/var/SomeFolder/AnotherFolder.lib")).ToEqual(true); - Expect(p::HasExtension("/var/AnotherFolder.lib")).ToEqual(true); - Expect(p::HasExtension("/var/AnotherFolder.")).ToEqual(true); - Expect(p::HasExtension("/var/AnotherFolder")).ToEqual(false); - Expect(p::HasExtension("/var/")).ToEqual(false); - #endif - Expect(p::HasExtension("AnotherFolder.lib")).ToEqual(true); - Expect(p::HasExtension("AnotherFolder")).ToEqual(false); - }); - - It("Can replace extension", []() - { - p::String path; - #if P_PLATFORM_WINDOWS - path = "F:\\SomeFolder\\AnotherFolder.lib"; - p::ReplaceExtension(path, "txt"); - Expect(path).ToEqual("F:\\SomeFolder\\AnotherFolder.txt"); - #elif P_PLATFORM_LINUX - path = "/var/SomeFolder/AnotherFolder.lib"; - p::ReplaceExtension(path, "txt"); - Expect(path).ToEqual("/var/SomeFolder/AnotherFolder.txt"); - #endif - path = "AnotherFolder.lib"; - p::ReplaceExtension(path, "txt"); - Expect(path).ToEqual("AnotherFolder.txt"); - path = "AnotherFolder."; - p::ReplaceExtension(path, ".txt"); - Expect(path).ToEqual("AnotherFolder.txt"); - path = "AnotherFolder.lib"; - p::ReplaceExtension(path, ".txt"); - Expect(path).ToEqual("AnotherFolder.txt"); - path = "AnotherFolder"; - p::ReplaceExtension(path, "txt"); - Expect(path).ToEqual("AnotherFolder.txt"); - }); - - It("Can get stem", []() - { - #if P_PLATFORM_WINDOWS - Expect(p::GetStem("F:\\SomeFolder\\AnotherFolder.lib")).ToEqual("AnotherFolder"); - Expect(p::GetStem("F:\\AnotherFolder.lib")).ToEqual("AnotherFolder"); - Expect(p::GetStem("F:\\AnotherFolder.")).ToEqual("AnotherFolder"); - Expect(p::GetStem("F:\\AnotherFolder")).ToEqual("AnotherFolder"); - Expect(p::GetStem("F:\\")).ToEqual(""); - #elif P_PLATFORM_LINUX - Expect(p::GetStem("/var/SomeFolder/AnotherFolder.lib")).ToEqual("AnotherFolder"); - Expect(p::GetStem("/var/AnotherFolder.lib")).ToEqual("AnotherFolder"); - Expect(p::GetStem("/var/AnotherFolder.")).ToEqual("AnotherFolder"); - Expect(p::GetStem("/var/AnotherFolder")).ToEqual("AnotherFolder"); - Expect(p::GetStem("/var/")).ToEqual(""); - #endif - Expect(p::GetStem("AnotherFolder.lib")).ToEqual("AnotherFolder"); - Expect(p::GetStem("AnotherFolder")).ToEqual("AnotherFolder"); - Expect(p::GetStem("")).ToEqual(""); - }); - - It("Can check stem", []() - { - #if P_PLATFORM_WINDOWS - Expect(p::HasStem("F:\\SomeFolder\\AnotherFolder.lib")).ToEqual(true); - Expect(p::HasStem("F:\\AnotherFolder.lib")).ToEqual(true); - Expect(p::HasStem("F:\\AnotherFolder.")).ToEqual(true); - Expect(p::HasStem("F:\\AnotherFolder")).ToEqual(true); - Expect(p::HasStem("F:\\")).ToEqual(false); - #elif P_PLATFORM_LINUX - Expect(p::HasStem("/var/SomeFolder/AnotherFolder.lib")).ToEqual(true); - Expect(p::HasStem("/var/AnotherFolder.lib")).ToEqual(true); - Expect(p::HasStem("/var/AnotherFolder.")).ToEqual(true); - Expect(p::HasStem("/var/AnotherFolder")).ToEqual(true); - Expect(p::HasStem("/var/")).ToEqual(false); - #endif - Expect(p::HasStem("AnotherFolder.lib")).ToEqual(true); - Expect(p::HasStem("AnotherFolder")).ToEqual(true); - Expect(p::HasStem("")).ToEqual(false); - }); - - - It("Can append to path", []() - { - Expect(p::JoinPaths("", "")).ToEqual(""); - Expect(p::JoinPaths("", "/")).ToEqual("/"); - Expect(p::JoinPaths("", "bar")).ToEqual("bar"); - Expect(p::JoinPaths("", "/bar")).ToEqual("/bar"); - - Expect(p::JoinPaths("/", "")).ToEqual("/"); - Expect(p::JoinPaths("/", "/")).ToEqual("/"); - Expect(p::JoinPaths("/", "bar")).ToEqual("/bar"); - Expect(p::JoinPaths("/", "/bar")).ToEqual("/bar"); - Expect(p::JoinPaths("foo", "/")).ToEqual("/"); - - Expect(p::JoinPaths("foo", "/bar")).ToEqual("/bar"); - Expect(p::JoinPaths("foo/", "")).ToEqual("foo/"); - Expect(p::JoinPaths("foo/", "/")).ToEqual("/"); - Expect(p::JoinPaths("foo/", "bar")).ToEqual("foo/bar"); - - #if P_PLATFORM_WINDOWS - Expect(p::JoinPaths("foo", "")).ToEqual("foo\\"); - Expect(p::JoinPaths("foo", "bar")).ToEqual("foo\\bar"); - Expect(p::JoinPaths("foo\\", "\\bar")).ToEqual("\\bar"); - Expect(p::JoinPaths("c:", "bar")).ToEqual("c:bar"); - Expect(p::JoinPaths("\\\\host", "foo")).ToEqual("\\\\host\\foo"); - Expect(p::JoinPaths("\\\\host/", "foo")).ToEqual("\\\\host/foo"); - #else - Expect(p::JoinPaths("foo", "")).ToEqual("foo/"); - Expect(p::JoinPaths("foo", "bar")).ToEqual("foo/bar"); - Expect(p::JoinPaths("//host", "foo")).ToEqual("//host/foo"); - Expect(p::JoinPaths("//host/", "foo")).ToEqual("//host/foo"); - #endif - }); + Expect(p::JoinPaths("", "")).ToEqual(""); + Expect(p::JoinPaths("", "/")).ToEqual("/"); + Expect(p::JoinPaths("", "bar")).ToEqual("bar"); + Expect(p::JoinPaths("", "/bar")).ToEqual("/bar"); + + Expect(p::JoinPaths("/", "")).ToEqual("/"); + Expect(p::JoinPaths("/", "/")).ToEqual("/"); + Expect(p::JoinPaths("/", "bar")).ToEqual("/bar"); + Expect(p::JoinPaths("/", "/bar")).ToEqual("/bar"); + Expect(p::JoinPaths("foo", "/")).ToEqual("/"); + + Expect(p::JoinPaths("foo", "/bar")).ToEqual("/bar"); + Expect(p::JoinPaths("foo/", "")).ToEqual("foo/"); + Expect(p::JoinPaths("foo/", "/")).ToEqual("/"); + Expect(p::JoinPaths("foo/", "bar")).ToEqual("foo/bar"); + +#if P_PLATFORM_WINDOWS + Expect(p::JoinPaths("foo", "")).ToEqual("foo\\"); + Expect(p::JoinPaths("foo", "bar")).ToEqual("foo\\bar"); + Expect(p::JoinPaths("foo\\", "\\bar")).ToEqual("\\bar"); + Expect(p::JoinPaths("c:", "bar")).ToEqual("c:bar"); + Expect(p::JoinPaths("\\\\host", "foo")).ToEqual("\\\\host\\foo"); + Expect(p::JoinPaths("\\\\host/", "foo")).ToEqual("\\\\host/foo"); +#else + Expect(p::JoinPaths("foo", "")).ToEqual("foo/"); + Expect(p::JoinPaths("foo", "bar")).ToEqual("foo/bar"); + Expect(p::JoinPaths("//host", "foo")).ToEqual("//host/foo"); + Expect(p::JoinPaths("//host/", "foo")).ToEqual("//host/foo"); +#endif }); -} +}); +return true; +}(); +} // namespace diff --git a/Tests/Math/Color.spec.cpp b/Tests/Math/Color.spec.cpp index 95bff8ba..fc0cb3d0 100644 --- a/Tests/Math/Color.spec.cpp +++ b/Tests/Math/Color.spec.cpp @@ -1,137 +1,142 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include using namespace p; -void RegisterMathColorTests() +namespace { - Spec("Math.Color", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("Math.Color", []() +{ + Describe("Helpers", []() { - Describe("Helpers", []() + It("Can make from rgba", []() { - It("Can make from rgba", []() - { - auto color = Color::FromRGB(128, 206, 215, 35); - Expect(color.r).ToEqual(128); - Expect(color.g).ToEqual(206); - Expect(color.b).ToEqual(215); - Expect(color.a).ToEqual(35); - }); - It("Can make from Hex", []() - { - auto color = Color::FromHex(0x80ced7); - Expect(color.r).ToEqual(128); - Expect(color.g).ToEqual(206); - Expect(color.b).ToEqual(215); - - auto colora = Color::FromHexAlpha(0x80ced723); - Expect(colora.r).ToEqual(128); - Expect(colora.g).ToEqual(206); - Expect(colora.b).ToEqual(215); - Expect(colora.a).ToEqual(35); - }); - - It("Can make from packed", []() - { - auto argb = Color::FromPackedARGB(0x2380ced7); - Expect(argb.r).ToEqual(128); - Expect(argb.g).ToEqual(206); - Expect(argb.b).ToEqual(215); - Expect(argb.a).ToEqual(35); - - auto abgr = Color::FromPackedABGR(0x23d7ce80); - Expect(abgr.r).ToEqual(128); - Expect(abgr.g).ToEqual(206); - Expect(abgr.b).ToEqual(215); - Expect(abgr.a).ToEqual(35); - - auto rgba = Color::FromPackedRGBA(0x80ced723); - Expect(rgba.r).ToEqual(128); - Expect(rgba.g).ToEqual(206); - Expect(rgba.b).ToEqual(215); - Expect(rgba.a).ToEqual(35); - - auto bgra = Color::FromPackedBGRA(0xd7ce8023); - Expect(bgra.r).ToEqual(128); - Expect(bgra.g).ToEqual(206); - Expect(bgra.b).ToEqual(215); - Expect(bgra.a).ToEqual(35); - }); - - It("Can get as packed", []() - { - auto color = Color(128, 206, 215, 35); - Expect(color.ToPackedARGB()).ToEqual(0x2380ced7); - Expect(color.ToPackedABGR()).ToEqual(0x23d7ce80); - Expect(color.ToPackedRGBA()).ToEqual(0x80ced723); - Expect(color.ToPackedBGRA()).ToEqual(0xd7ce8023); - }); + auto color = Color::FromRGB(128, 206, 215, 35); + Expect(color.r).ToEqual(128); + Expect(color.g).ToEqual(206); + Expect(color.b).ToEqual(215); + Expect(color.a).ToEqual(35); }); - Describe("LinearColor", []() + It("Can make from Hex", []() { - It("Can Shade", []() - { - Expect(LinearColor::White().Shade(1.0f)).ToEqual(LinearColor::Black()); - Expect(LinearColor::White().Shade(0.5f)).ToEqual(LinearColor::Gray()); - constexpr LinearColor color{Color::FromHex(0x80ced7)}; - Expect(color.Shade(0.5f)).ToEqual(LinearColor{Color::FromHex(0x40676B)}); - }); - - It("Shade doesn't change alpha", []() - { - Expect(std::abs(LinearColor::White().Translucency(0.5f).Shade(1.0f).a - 0.5f)) - .ToBeLessOrEqual(0.01f); - }); - - It("Can Tint", []() - { - Expect(LinearColor::Black().Tint(1.0f)).ToEqual(LinearColor::White()); - Expect(LinearColor::Black().Tint(0.5f)).ToEqual(LinearColor::Gray()); - Expect(Color::FromHex(0x80ced7).Tint(0.5f)).ToEqual(Color::FromHex(0xbfe6eb)); - }); - - It("Tint doesn't change alpha", []() - { - Expect(std::abs(LinearColor::Black().Translucency(0.5f).Tint(1.0f).a - 0.5f)) - .ToBeLessOrEqual(0.01f); - }); + auto color = Color::FromHex(0x80ced7); + Expect(color.r).ToEqual(128); + Expect(color.g).ToEqual(206); + Expect(color.b).ToEqual(215); + + auto colora = Color::FromHexAlpha(0x80ced723); + Expect(colora.r).ToEqual(128); + Expect(colora.g).ToEqual(206); + Expect(colora.b).ToEqual(215); + Expect(colora.a).ToEqual(35); }); - Describe("Color", []() + + It("Can make from packed", []() { - It("Can Shade", []() - { - Expect(Color::White().Shade(1.0f)).ToEqual(Color::Black()); - Expect(Color::White().Shade(0.5f)).ToEqual(Color::Gray()); - Expect(Color::FromHex(0x80ced7).Shade(0.5f)).ToEqual(Color::FromHex(0x40676B)); - }); - - It("Shade doesn't change alpha", []() - { - Expect(Color::White().Translucency(127).Shade(1.0f).a).ToEqual(127); - }); - - It("Can Tint", []() - { - Expect(Color::Black().Tint(1.0f)).ToEqual(Color::White()); - Expect(Color::Black().Tint(0.5f)).ToEqual(Color::Gray()); - Expect(Color::FromHex(0x80ced7).Tint(0.5f)).ToEqual(Color::FromHex(0xbfe6eb)); - }); - - It("Tint doesn't change alpha", []() - { - Expect(Color::Black().Translucency(127).Tint(1.0f).a).ToEqual(127); - }); - - It("Can convert to linear", []() - { - Expect(LinearColor{Color::White()}).ToEqual(LinearColor::White()); - Expect(LinearColor{Color::Black()}).ToEqual(LinearColor::Black()); - Expect(LinearColor{Color::Gray()}).ToEqual(LinearColor::Gray()); - }); + auto argb = Color::FromPackedARGB(0x2380ced7); + Expect(argb.r).ToEqual(128); + Expect(argb.g).ToEqual(206); + Expect(argb.b).ToEqual(215); + Expect(argb.a).ToEqual(35); + + auto abgr = Color::FromPackedABGR(0x23d7ce80); + Expect(abgr.r).ToEqual(128); + Expect(abgr.g).ToEqual(206); + Expect(abgr.b).ToEqual(215); + Expect(abgr.a).ToEqual(35); + + auto rgba = Color::FromPackedRGBA(0x80ced723); + Expect(rgba.r).ToEqual(128); + Expect(rgba.g).ToEqual(206); + Expect(rgba.b).ToEqual(215); + Expect(rgba.a).ToEqual(35); + + auto bgra = Color::FromPackedBGRA(0xd7ce8023); + Expect(bgra.r).ToEqual(128); + Expect(bgra.g).ToEqual(206); + Expect(bgra.b).ToEqual(215); + Expect(bgra.a).ToEqual(35); + }); + + It("Can get as packed", []() + { + auto color = Color(128, 206, 215, 35); + Expect(color.ToPackedARGB()).ToEqual(0x2380ced7); + Expect(color.ToPackedABGR()).ToEqual(0x23d7ce80); + Expect(color.ToPackedRGBA()).ToEqual(0x80ced723); + Expect(color.ToPackedBGRA()).ToEqual(0xd7ce8023); + }); }); + Describe("LinearColor", []() + { + It("Can Shade", []() + { + Expect(LinearColor::White().Shade(1.0f)).ToEqual(LinearColor::Black()); + Expect(LinearColor::White().Shade(0.5f)).ToEqual(LinearColor::Gray()); + constexpr LinearColor color{Color::FromHex(0x80ced7)}; + Expect(color.Shade(0.5f)).ToEqual(LinearColor{Color::FromHex(0x40676B)}); + }); + + It("Shade doesn't change alpha", []() + { + Expect(std::abs(LinearColor::White().Translucency(0.5f).Shade(1.0f).a - 0.5f)) + .ToBeLessOrEqual(0.01f); + }); + + It("Can Tint", []() + { + Expect(LinearColor::Black().Tint(1.0f)).ToEqual(LinearColor::White()); + Expect(LinearColor::Black().Tint(0.5f)).ToEqual(LinearColor::Gray()); + Expect(Color::FromHex(0x80ced7).Tint(0.5f)).ToEqual(Color::FromHex(0xbfe6eb)); + }); + + It("Tint doesn't change alpha", []() + { + Expect(std::abs(LinearColor::Black().Translucency(0.5f).Tint(1.0f).a - 0.5f)) + .ToBeLessOrEqual(0.01f); + }); }); -} + Describe("Color", []() + { + It("Can Shade", []() + { + Expect(Color::White().Shade(1.0f)).ToEqual(Color::Black()); + Expect(Color::White().Shade(0.5f)).ToEqual(Color::Gray()); + Expect(Color::FromHex(0x80ced7).Shade(0.5f)).ToEqual(Color::FromHex(0x40676B)); + }); + + It("Shade doesn't change alpha", []() + { + Expect(Color::White().Translucency(127).Shade(1.0f).a).ToEqual(127); + }); + + It("Can Tint", []() + { + Expect(Color::Black().Tint(1.0f)).ToEqual(Color::White()); + Expect(Color::Black().Tint(0.5f)).ToEqual(Color::Gray()); + Expect(Color::FromHex(0x80ced7).Tint(0.5f)).ToEqual(Color::FromHex(0xbfe6eb)); + }); + + It("Tint doesn't change alpha", []() + { + Expect(Color::Black().Translucency(127).Tint(1.0f).a).ToEqual(127); + }); + + It("Can convert to linear", []() + { + Expect(LinearColor{Color::White()}).ToEqual(LinearColor::White()); + Expect(LinearColor{Color::Black()}).ToEqual(LinearColor::Black()); + Expect(LinearColor{Color::Gray()}).ToEqual(LinearColor::Gray()); + }); +}); +}); +return true; +}(); +} // namespace diff --git a/Tests/Math/Math.spec.cpp b/Tests/Math/Math.spec.cpp index 02479ce2..49d6f95d 100644 --- a/Tests/Math/Math.spec.cpp +++ b/Tests/Math/Math.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include #include #include @@ -19,345 +19,350 @@ namespace } // namespace -void RegisterMathMathTests() +namespace +{ +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("Math.Math", []() { - Spec("Math.Math", []() + Describe("Binary Search", []() { - Describe("Binary Search", []() + It("LowerBound", [=]() { - It("LowerBound", [=]() - { - Expect(bottomUp.LowerBound(34)).ToEqual(1); - Expect(bottomUp.LowerBound(100)).ToEqual(3); - Expect(bottomUp.LowerBound(51)).ToEqual(3); + Expect(bottomUp.LowerBound(34)).ToEqual(1); + Expect(bottomUp.LowerBound(100)).ToEqual(3); + Expect(bottomUp.LowerBound(51)).ToEqual(3); - Expect(topDown.LowerBound(34, TGreater<>())).ToEqual(3); - Expect(topDown.LowerBound(100, TGreater<>())).ToEqual(1); - Expect(topDown.LowerBound(51, TGreater<>())).ToEqual(2); - }); + Expect(topDown.LowerBound(34, TGreater<>())).ToEqual(3); + Expect(topDown.LowerBound(100, TGreater<>())).ToEqual(1); + Expect(topDown.LowerBound(51, TGreater<>())).ToEqual(2); + }); - It("UpperBound", [=]() - { - Expect(bottomUp.UpperBound(34)).ToEqual(2); - Expect(bottomUp.UpperBound(100)).ToEqual(4); + It("UpperBound", [=]() + { + Expect(bottomUp.UpperBound(34)).ToEqual(2); + Expect(bottomUp.UpperBound(100)).ToEqual(4); - Expect(topDown.UpperBound(34, TGreater<>())).ToEqual(4); - Expect(topDown.UpperBound(100, TGreater<>())).ToEqual(2); - }); + Expect(topDown.UpperBound(34, TGreater<>())).ToEqual(4); + Expect(topDown.UpperBound(100, TGreater<>())).ToEqual(2); + }); - It("Can find equal", [=]() - { - Expect(bottomUp.FindSorted(0)).ToEqual(NO_INDEX); - Expect(bottomUp.FindSorted(34)).ToEqual(1); - Expect(bottomUp.FindSorted(33)).ToEqual(NO_INDEX); - Expect(bottomUp.FindSorted(121)).ToEqual(NO_INDEX); + It("Can find equal", [=]() + { + Expect(bottomUp.FindSorted(0)).ToEqual(NO_INDEX); + Expect(bottomUp.FindSorted(34)).ToEqual(1); + Expect(bottomUp.FindSorted(33)).ToEqual(NO_INDEX); + Expect(bottomUp.FindSorted(121)).ToEqual(NO_INDEX); - Expect(topDown.FindSorted(34, TGreater<>())).ToEqual(3); - }); + Expect(topDown.FindSorted(34, TGreater<>())).ToEqual(3); + }); - Describe("FindSortedMax", []() + Describe("FindSortedMax", []() + { + Describe("Ordered by a < b", []() { - Describe("Ordered by a < b", []() - { - TArray bottomUp{23, 34, 50, 50, 100, 120}; + TArray bottomUp{23, 34, 50, 50, 100, 120}; - It("Find first item", [=]() - { - auto i4 = bottomUp.FindSortedMax(23, false); - Expect(i4).ToEqual(NO_INDEX); + It("Find first item", [=]() + { + auto i4 = bottomUp.FindSortedMax(23, false); + Expect(i4).ToEqual(NO_INDEX); - auto i5 = bottomUp.FindSortedMax(23, true); - Expect(i5).ToEqual(0); + auto i5 = bottomUp.FindSortedMax(23, true); + Expect(i5).ToEqual(0); - auto i6 = bottomUp.FindSortedMax(22, true); - Expect(i6).ToEqual(NO_INDEX); - }); + auto i6 = bottomUp.FindSortedMax(22, true); + Expect(i6).ToEqual(NO_INDEX); + }); - It("Find any item", [=]() - { - auto i1 = bottomUp.FindSortedMax(34, true); - Expect(i1).ToEqual(1); + It("Find any item", [=]() + { + auto i1 = bottomUp.FindSortedMax(34, true); + Expect(i1).ToEqual(1); - auto i2 = bottomUp.FindSortedMax(33, true); - Expect(i2).ToEqual(0); + auto i2 = bottomUp.FindSortedMax(33, true); + Expect(i2).ToEqual(0); - auto i3 = bottomUp.FindSortedMax(34, false); - Expect(i3).ToEqual(0); - }); + auto i3 = bottomUp.FindSortedMax(34, false); + Expect(i3).ToEqual(0); + }); - It("Find last item", [=]() - { - auto i4 = bottomUp.FindSortedMax(120, false); - Expect(i4).ToEqual(4); + It("Find last item", [=]() + { + auto i4 = bottomUp.FindSortedMax(120, false); + Expect(i4).ToEqual(4); - auto i5 = bottomUp.FindSortedMax(120, true); - Expect(i5).ToEqual(5); + auto i5 = bottomUp.FindSortedMax(120, true); + Expect(i5).ToEqual(5); - auto i6 = bottomUp.FindSortedMax(121, true); - Expect(i6).ToEqual(5); + auto i6 = bottomUp.FindSortedMax(121, true); + Expect(i6).ToEqual(5); - auto i7 = bottomUp.FindSortedMax(100, false); - Expect(i7).ToEqual(3); - }); + auto i7 = bottomUp.FindSortedMax(100, false); + Expect(i7).ToEqual(3); }); + }); - Describe("Ordered by a > b", []() - { - TArray topDown{120, 100, 50, 50, 34, 23}; + Describe("Ordered by a > b", []() + { + TArray topDown{120, 100, 50, 50, 34, 23}; - It("Find first item", [=]() - { - auto i4 = topDown.FindSortedMax(120, true); - Expect(i4).ToEqual(0); + It("Find first item", [=]() + { + auto i4 = topDown.FindSortedMax(120, true); + Expect(i4).ToEqual(0); - auto i5 = topDown.FindSortedMax(120, false); - Expect(i5).ToEqual(1); + auto i5 = topDown.FindSortedMax(120, false); + Expect(i5).ToEqual(1); - auto i6 = topDown.FindSortedMax(121, true); - Expect(i6).ToEqual(0); - }); + auto i6 = topDown.FindSortedMax(121, true); + Expect(i6).ToEqual(0); + }); - It("Find any item", [=]() - { - auto i1 = topDown.FindSortedMax(34, true); - Expect(i1).ToEqual(4); + It("Find any item", [=]() + { + auto i1 = topDown.FindSortedMax(34, true); + Expect(i1).ToEqual(4); - auto i2 = topDown.FindSortedMax(33, true); - Expect(i2).ToEqual(5); + auto i2 = topDown.FindSortedMax(33, true); + Expect(i2).ToEqual(5); - auto i3 = topDown.FindSortedMax(34, false); - Expect(i3).ToEqual(5); - }); + auto i3 = topDown.FindSortedMax(34, false); + Expect(i3).ToEqual(5); + }); - It("Find last item", [=]() - { - auto i4 = topDown.FindSortedMax(23, false); - Expect(i4).ToEqual(NO_INDEX); + It("Find last item", [=]() + { + auto i4 = topDown.FindSortedMax(23, false); + Expect(i4).ToEqual(NO_INDEX); - auto i5 = topDown.FindSortedMax(23, true); - Expect(i5).ToEqual(5); + auto i5 = topDown.FindSortedMax(23, true); + Expect(i5).ToEqual(5); - auto i6 = topDown.FindSortedMax(22, true); - Expect(i6).ToEqual(NO_INDEX); - }); + auto i6 = topDown.FindSortedMax(22, true); + Expect(i6).ToEqual(NO_INDEX); }); + }); - Describe("All same values", []() - { - TArray allEqual{10, 10, 10}; + Describe("All same values", []() + { + TArray allEqual{10, 10, 10}; - It("Doesnt find smaller", [=]() - { - auto i1 = allEqual.FindSortedMax(9, false); - Expect(i1).ToEqual(NO_INDEX); + It("Doesnt find smaller", [=]() + { + auto i1 = allEqual.FindSortedMax(9, false); + Expect(i1).ToEqual(NO_INDEX); - auto i2 = allEqual.FindSortedMax(10, false); - Expect(i2).ToEqual(NO_INDEX); - }); + auto i2 = allEqual.FindSortedMax(10, false); + Expect(i2).ToEqual(NO_INDEX); + }); - It("Finds smaller", [=]() - { - auto i1 = allEqual.FindSortedMax(10, true); - Expect(i1).ToEqual(0); + It("Finds smaller", [=]() + { + auto i1 = allEqual.FindSortedMax(10, true); + Expect(i1).ToEqual(0); - auto i2 = allEqual.FindSortedMax(11, false); - Expect(i2).ToEqual(0); - }); + auto i2 = allEqual.FindSortedMax(11, false); + Expect(i2).ToEqual(0); }); }); - Describe("FindSortedMin", []() + }); + Describe("FindSortedMin", []() + { + Describe("Ordered by a < b", []() { - Describe("Ordered by a < b", []() - { - TArray bottomUp{23, 34, 50, 50, 100, 120}; + TArray bottomUp{23, 34, 50, 50, 100, 120}; - It("Find first item", [=]() - { - auto i1 = bottomUp.FindSortedMin(23, true); - Expect(i1).ToEqual(0); + It("Find first item", [=]() + { + auto i1 = bottomUp.FindSortedMin(23, true); + Expect(i1).ToEqual(0); - auto i2 = bottomUp.FindSortedMin(20, true); - Expect(i2).ToEqual(0); + auto i2 = bottomUp.FindSortedMin(20, true); + Expect(i2).ToEqual(0); - auto i3 = bottomUp.FindSortedMin(23, false); - Expect(i3).ToEqual(1); - }); + auto i3 = bottomUp.FindSortedMin(23, false); + Expect(i3).ToEqual(1); + }); - It("Find any item", [=]() - { - auto i1 = bottomUp.FindSortedMin(33, false); - Expect(i1).ToEqual(1); + It("Find any item", [=]() + { + auto i1 = bottomUp.FindSortedMin(33, false); + Expect(i1).ToEqual(1); - auto i2 = bottomUp.FindSortedMin(34, true); - Expect(i2).ToEqual(1); + auto i2 = bottomUp.FindSortedMin(34, true); + Expect(i2).ToEqual(1); - auto i3 = bottomUp.FindSortedMin(34, false); - Expect(i3).ToEqual(2); - }); + auto i3 = bottomUp.FindSortedMin(34, false); + Expect(i3).ToEqual(2); + }); - It("Find last item", [=]() - { - auto i1 = bottomUp.FindSortedMin(100, false); - Expect(i1).ToEqual(5); + It("Find last item", [=]() + { + auto i1 = bottomUp.FindSortedMin(100, false); + Expect(i1).ToEqual(5); - auto i2 = bottomUp.FindSortedMin(120, false); - Expect(i2).ToEqual(NO_INDEX); + auto i2 = bottomUp.FindSortedMin(120, false); + Expect(i2).ToEqual(NO_INDEX); - auto i3 = bottomUp.FindSortedMin(120, true); - Expect(i3).ToEqual(5); + auto i3 = bottomUp.FindSortedMin(120, true); + Expect(i3).ToEqual(5); - auto i4 = bottomUp.FindSortedMin(121, true); - Expect(i4).ToEqual(NO_INDEX); - }); + auto i4 = bottomUp.FindSortedMin(121, true); + Expect(i4).ToEqual(NO_INDEX); }); + }); - Describe("Ordered by a > b", []() - { - TArray topDown{120, 100, 50, 50, 34, 23}; + Describe("Ordered by a > b", []() + { + TArray topDown{120, 100, 50, 50, 34, 23}; - It("Find first item", [=]() - { - auto i4 = topDown.FindSortedMin(120, true); - Expect(i4).ToEqual(0); + It("Find first item", [=]() + { + auto i4 = topDown.FindSortedMin(120, true); + Expect(i4).ToEqual(0); - auto i5 = topDown.FindSortedMin(120, false); - Expect(i5).ToEqual(NO_INDEX); + auto i5 = topDown.FindSortedMin(120, false); + Expect(i5).ToEqual(NO_INDEX); - auto i6 = topDown.FindSortedMin(121, true); - Expect(i6).ToEqual(NO_INDEX); - }); + auto i6 = topDown.FindSortedMin(121, true); + Expect(i6).ToEqual(NO_INDEX); + }); - It("Find any item", [=]() - { - auto i1 = topDown.FindSortedMin(34, true); - Expect(i1).ToEqual(4); + It("Find any item", [=]() + { + auto i1 = topDown.FindSortedMin(34, true); + Expect(i1).ToEqual(4); - auto i2 = topDown.FindSortedMin(33, true); - Expect(i2).ToEqual(4); + auto i2 = topDown.FindSortedMin(33, true); + Expect(i2).ToEqual(4); - auto i3 = topDown.FindSortedMin(34, false); - Expect(i3).ToEqual(3); - }); + auto i3 = topDown.FindSortedMin(34, false); + Expect(i3).ToEqual(3); + }); - It("Find last item", [=]() - { - auto i4 = topDown.FindSortedMin(23, false); - Expect(i4).ToEqual(4); + It("Find last item", [=]() + { + auto i4 = topDown.FindSortedMin(23, false); + Expect(i4).ToEqual(4); - auto i5 = topDown.FindSortedMin(23, true); - Expect(i5).ToEqual(5); + auto i5 = topDown.FindSortedMin(23, true); + Expect(i5).ToEqual(5); - auto i6 = topDown.FindSortedMin(22, true); - Expect(i6).ToEqual(5); - }); + auto i6 = topDown.FindSortedMin(22, true); + Expect(i6).ToEqual(5); }); + }); - Describe("All same values", []() - { - TArray allEqual{10, 10, 10}; + Describe("All same values", []() + { + TArray allEqual{10, 10, 10}; - It("Doesnt find bigger", [=]() - { - auto i1 = allEqual.FindSortedMin(11, false); - Expect(i1).ToEqual(NO_INDEX); + It("Doesnt find bigger", [=]() + { + auto i1 = allEqual.FindSortedMin(11, false); + Expect(i1).ToEqual(NO_INDEX); - auto i2 = allEqual.FindSortedMin(10, false); - Expect(i2).ToEqual(NO_INDEX); - }); + auto i2 = allEqual.FindSortedMin(10, false); + Expect(i2).ToEqual(NO_INDEX); + }); - It("Finds bigger", [=]() - { - auto i1 = allEqual.FindSortedMin(10, true); - Expect(i1).ToEqual(0); + It("Finds bigger", [=]() + { + auto i1 = allEqual.FindSortedMin(10, true); + Expect(i1).ToEqual(0); - auto i2 = allEqual.FindSortedMin(9, false); - Expect(i2).ToEqual(0); - }); + auto i2 = allEqual.FindSortedMin(9, false); + Expect(i2).ToEqual(0); }); }); }); + }); + + It("Can check Infinite", [=]() + { + Expect(IsInf(0.0)).ToEqual(false); + Expect(IsInf(-0.0)).ToEqual(false); + Expect(IsInf(1.0)).ToEqual(false); + Expect(IsInf(-1.0)).ToEqual(false); + + static constexpr double dInfinite = Limits::Infinity(); + Expect(IsInf(dInfinite)).ToEqual(true); + Expect(IsInf(-dInfinite)).ToEqual(true); + Expect(IsPosInf(-dInfinite)).ToEqual(false); + Expect(IsNegInf(dInfinite)).ToEqual(false); + Expect(IsInf(Limits::Max())).ToEqual(false); + Expect(IsInf(Limits::Lowest())).ToEqual(false); + Expect(IsInf(double(bigNumber))).ToEqual(false); + }); + + It("Can check NAN", [=]() + { + Expect(IsNAN(0.0)).ToEqual(false); + Expect(IsNAN(Limits::QuietNaN())).ToEqual(true); + }); - It("Can check Infinite", [=]() + Describe("Roundings", []() + { + It("Can Floor", [=]() { - Expect(IsInf(0.0)).ToEqual(false); - Expect(IsInf(-0.0)).ToEqual(false); - Expect(IsInf(1.0)).ToEqual(false); - Expect(IsInf(-1.0)).ToEqual(false); + Expect(Floor(0.0)).ToEqual(std::floor(0.0)); + Expect(Floor(-0.0)).ToEqual(std::floor(-0.0)); + Expect(Floor(4.2)).ToEqual(std::floor(4.2)); + Expect(Floor(4.5)).ToEqual(std::floor(4.5)); + Expect(Floor(4.7)).ToEqual(std::floor(4.7)); + Expect(Floor(5.0)).ToEqual(std::floor(5.0)); + Expect(Floor(-4.2)).ToEqual(std::floor(-4.2)); + Expect(Floor(-4.7)).ToEqual(std::floor(-4.7)); + Expect(Floor(-5.0)).ToEqual(std::floor(-5.0)); + Expect(Floor(99999999999999999.0 + 0.5)).ToEqual(99999999999999999.0); static constexpr double dInfinite = Limits::Infinity(); - Expect(IsInf(dInfinite)).ToEqual(true); - Expect(IsInf(-dInfinite)).ToEqual(true); - Expect(IsPosInf(-dInfinite)).ToEqual(false); - Expect(IsNegInf(dInfinite)).ToEqual(false); - Expect(IsInf(Limits::Max())).ToEqual(false); - Expect(IsInf(Limits::Lowest())).ToEqual(false); - Expect(IsInf(double(bigNumber))).ToEqual(false); + Expect(Floor(-dInfinite)).ToEqual(std::floor(-dInfinite)); + Expect(Floor(dInfinite)).ToEqual(std::floor(dInfinite)); + Expect(IsNAN(Floor(Limits::QuietNaN()))).ToEqual(true); }); - - It("Can check NAN", [=]() + It("Can Ceil", [=]() { - Expect(IsNAN(0.0)).ToEqual(false); - Expect(IsNAN(Limits::QuietNaN())).ToEqual(true); + Expect(Ceil(0.0)).ToEqual(std::ceil(0.0)); + Expect(Ceil(-0.0)).ToEqual(std::ceil(-0.0)); + Expect(Ceil(4.2)).ToEqual(std::ceil(4.2)); + Expect(Ceil(4.5)).ToEqual(std::ceil(4.5)); + Expect(Ceil(4.7)).ToEqual(std::ceil(4.7)); + Expect(Ceil(5.0)).ToEqual(std::ceil(5.0)); + Expect(Ceil(-4.2)).ToEqual(std::ceil(-4.2)); + Expect(Ceil(-4.7)).ToEqual(std::ceil(-4.7)); + Expect(Ceil(-5.0)).ToEqual(std::ceil(-5.0)); + Expect(Ceil(99999999999999999.0 - 0.5)).ToEqual(99999999999999999.0); + + static constexpr double dInfinite = Limits::Infinity(); + Expect(Ceil(-dInfinite)).ToEqual(std::ceil(-dInfinite)); + Expect(Ceil(dInfinite)).ToEqual(std::ceil(dInfinite)); + Expect(IsNAN(Ceil(Limits::QuietNaN()))).ToEqual(true); }); - Describe("Roundings", []() + It("Can Round", [=]() { - It("Can Floor", [=]() - { - Expect(Floor(0.0)).ToEqual(std::floor(0.0)); - Expect(Floor(-0.0)).ToEqual(std::floor(-0.0)); - Expect(Floor(4.2)).ToEqual(std::floor(4.2)); - Expect(Floor(4.5)).ToEqual(std::floor(4.5)); - Expect(Floor(4.7)).ToEqual(std::floor(4.7)); - Expect(Floor(5.0)).ToEqual(std::floor(5.0)); - Expect(Floor(-4.2)).ToEqual(std::floor(-4.2)); - Expect(Floor(-4.7)).ToEqual(std::floor(-4.7)); - Expect(Floor(-5.0)).ToEqual(std::floor(-5.0)); - Expect(Floor(99999999999999999.0 + 0.5)).ToEqual(99999999999999999.0); - - static constexpr double dInfinite = Limits::Infinity(); - Expect(Floor(-dInfinite)).ToEqual(std::floor(-dInfinite)); - Expect(Floor(dInfinite)).ToEqual(std::floor(dInfinite)); - Expect(IsNAN(Floor(Limits::QuietNaN()))).ToEqual(true); - }); - It("Can Ceil", [=]() - { - Expect(Ceil(0.0)).ToEqual(std::ceil(0.0)); - Expect(Ceil(-0.0)).ToEqual(std::ceil(-0.0)); - Expect(Ceil(4.2)).ToEqual(std::ceil(4.2)); - Expect(Ceil(4.5)).ToEqual(std::ceil(4.5)); - Expect(Ceil(4.7)).ToEqual(std::ceil(4.7)); - Expect(Ceil(5.0)).ToEqual(std::ceil(5.0)); - Expect(Ceil(-4.2)).ToEqual(std::ceil(-4.2)); - Expect(Ceil(-4.7)).ToEqual(std::ceil(-4.7)); - Expect(Ceil(-5.0)).ToEqual(std::ceil(-5.0)); - Expect(Ceil(99999999999999999.0 - 0.5)).ToEqual(99999999999999999.0); - - static constexpr double dInfinite = Limits::Infinity(); - Expect(Ceil(-dInfinite)).ToEqual(std::ceil(-dInfinite)); - Expect(Ceil(dInfinite)).ToEqual(std::ceil(dInfinite)); - Expect(IsNAN(Ceil(Limits::QuietNaN()))).ToEqual(true); - }); + Expect(Round(0.0)).ToEqual(std::round(0.0)); + Expect(Round(-0.0)).ToEqual(std::round(-0.0)); + Expect(Round(4.2)).ToEqual(std::round(4.2)); + Expect(Round(4.5)).ToEqual(std::round(4.5)); + Expect(Round(4.7)).ToEqual(std::round(4.7)); + Expect(Round(5.0)).ToEqual(std::round(5.0)); + Expect(Round(-4.2)).ToEqual(std::round(-4.2)); + Expect(Round(-4.7)).ToEqual(std::round(-4.7)); + Expect(Round(-5.0)).ToEqual(std::round(-5.0)); + Expect(Round(99999999999999999.0 - 0.4)).ToEqual(99999999999999999.0); - It("Can Round", [=]() - { - Expect(Round(0.0)).ToEqual(std::round(0.0)); - Expect(Round(-0.0)).ToEqual(std::round(-0.0)); - Expect(Round(4.2)).ToEqual(std::round(4.2)); - Expect(Round(4.5)).ToEqual(std::round(4.5)); - Expect(Round(4.7)).ToEqual(std::round(4.7)); - Expect(Round(5.0)).ToEqual(std::round(5.0)); - Expect(Round(-4.2)).ToEqual(std::round(-4.2)); - Expect(Round(-4.7)).ToEqual(std::round(-4.7)); - Expect(Round(-5.0)).ToEqual(std::round(-5.0)); - Expect(Round(99999999999999999.0 - 0.4)).ToEqual(99999999999999999.0); - - static constexpr double dInfinite = Limits::Infinity(); - Expect(Round(-dInfinite)).ToEqual(std::round(-dInfinite)); - Expect(Round(dInfinite)).ToEqual(std::round(dInfinite)); - Expect(IsNAN(Round(Limits::QuietNaN()))).ToEqual(true); - }); + static constexpr double dInfinite = Limits::Infinity(); + Expect(Round(-dInfinite)).ToEqual(std::round(-dInfinite)); + Expect(Round(dInfinite)).ToEqual(std::round(dInfinite)); + Expect(IsNAN(Round(Limits::QuietNaN()))).ToEqual(true); }); }); -} +}); +return true; +}(); +} // namespace diff --git a/Tests/Math/Vector.spec.cpp b/Tests/Math/Vector.spec.cpp index 589cd877..82728fae 100644 --- a/Tests/Math/Vector.spec.cpp +++ b/Tests/Math/Vector.spec.cpp @@ -1,71 +1,76 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include using namespace p; -void RegisterMathVectorTests() +namespace { - Spec("Math.Vector", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("Math.Vector", []() +{ + Describe("v2", []() { - Describe("v2", []() + It("Can reflect", []() { - It("Can reflect", []() - { - p::v2 v{0.f, 1.f}; - p::v2 normal{1.f, 0.f}; - p::v2 v2 = v.Reflect(normal); - Expect(v2.Equals({0.f, 1.f})).ToEqual(true); - v = p::v2{0.f, 1.f}; - normal = p::v2{0.f, 1.f}; - v2 = v.Reflect(normal); - Expect(v2.Equals({0.f, -1.f})).ToEqual(true); - v = p::v2{1.f, 1.f}; - normal = p::v2{0.f, 1.f}; - v2 = v.Reflect(normal); - Expect(v2.Equals({1.f, -1.f})).ToEqual(true); - v = p::v2{1.f, 1.f}; - normal = p::v2{1.f, 0.f}; - v2 = v.Reflect(normal); - Expect(v2.Equals({-1.f, 1.f})).ToEqual(true); - v = p::v2{-1.f, 1.f}; - normal = p::v2{-1.f, 0.f}; - v2 = v.Reflect(normal); - Expect(v2.Equals({1.f, 1.f})).ToEqual(true); - v = p::v2{-1.f, -1.f}; - normal = p::v2{0.f, 1.f}; - v2 = v.Reflect(normal); - Expect(v2.Equals({-1.f, 1.f})).ToEqual(true); - v = p::v2{0.f, 1.f}; - normal = p::v2{0.f, 1.f}; - v2 = v.Reflect(normal); - Expect(v2.Equals({0.f, -1.f})).ToEqual(true); - v = p::v2{0.f, -1.f}; - normal = p::v2{0.f, 1.f}; - v2 = v.Reflect(normal); - Expect(v2.Equals({0.f, 1.f})).ToEqual(true); - }); + p::v2 v{0.f, 1.f}; + p::v2 normal{1.f, 0.f}; + p::v2 v2 = v.Reflect(normal); + Expect(v2.Equals({0.f, 1.f})).ToEqual(true); + v = p::v2{0.f, 1.f}; + normal = p::v2{0.f, 1.f}; + v2 = v.Reflect(normal); + Expect(v2.Equals({0.f, -1.f})).ToEqual(true); + v = p::v2{1.f, 1.f}; + normal = p::v2{0.f, 1.f}; + v2 = v.Reflect(normal); + Expect(v2.Equals({1.f, -1.f})).ToEqual(true); + v = p::v2{1.f, 1.f}; + normal = p::v2{1.f, 0.f}; + v2 = v.Reflect(normal); + Expect(v2.Equals({-1.f, 1.f})).ToEqual(true); + v = p::v2{-1.f, 1.f}; + normal = p::v2{-1.f, 0.f}; + v2 = v.Reflect(normal); + Expect(v2.Equals({1.f, 1.f})).ToEqual(true); + v = p::v2{-1.f, -1.f}; + normal = p::v2{0.f, 1.f}; + v2 = v.Reflect(normal); + Expect(v2.Equals({-1.f, 1.f})).ToEqual(true); + v = p::v2{0.f, 1.f}; + normal = p::v2{0.f, 1.f}; + v2 = v.Reflect(normal); + Expect(v2.Equals({0.f, -1.f})).ToEqual(true); + v = p::v2{0.f, -1.f}; + normal = p::v2{0.f, 1.f}; + v2 = v.Reflect(normal); + Expect(v2.Equals({0.f, 1.f})).ToEqual(true); + }); - It("Can convert to angle", []() - { - float anglea = p::v2{0.f, 1.f}.Angle(); - Expect(anglea).ToEqual(90.f); - float angleb = p::v2{0.f, -1.f}.Angle(); - Expect(angleb).ToEqual(-90.f); - float anglec = p::v2{1.f, 0.f}.Angle(); - Expect(anglec).ToEqual(0.f); - float angled = p::v2{-1.f, 0.f}.Angle(); - Expect(angled).ToEqual(180.f); - }); + It("Can convert to angle", []() + { + float anglea = p::v2{0.f, 1.f}.Angle(); + Expect(anglea).ToEqual(90.f); + float angleb = p::v2{0.f, -1.f}.Angle(); + Expect(angleb).ToEqual(-90.f); + float anglec = p::v2{1.f, 0.f}.Angle(); + Expect(anglec).ToEqual(0.f); + float angled = p::v2{-1.f, 0.f}.Angle(); + Expect(angled).ToEqual(180.f); + }); - It("Can convert from angle", []() - { - Expect(p::v2::FromAngle(0.f).Angle()).ToEqual(0); - Expect(p::v2::FromAngle(90.f).Angle()).ToEqual(90.f); - }); + It("Can convert from angle", []() + { + Expect(p::v2::FromAngle(0.f).Angle()).ToEqual(0); + Expect(p::v2::FromAngle(90.f).Angle()).ToEqual(90.f); }); }); -} +}); +return true; +}(); +} // namespace diff --git a/Tests/Memory/BestFitArena.spec.cpp b/Tests/Memory/BestFitArena.spec.cpp index 3436c166..dd402df3 100644 --- a/Tests/Memory/BestFitArena.spec.cpp +++ b/Tests/Memory/BestFitArena.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include @@ -14,283 +14,288 @@ struct TypeOfSize }; -void RegisterMemoryBestFitArenaTests() +namespace { - Spec("Memory.BestFitArena", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("Memory.BestFitArena", []() +{ + It("Reserves a block on construction", []() { - It("Reserves a block on construction", []() - { - BestFitArena arena{1024}; - arena.GetStats()->detectLeaks = false; - Expect(arena.GetFreeSize()).ToEqual(1024); - Expect(*arena.GetBlock()).ToNotEqual(nullptr); - Expect(arena.GetBlock().size).ToEqual(1024); - }); - - It("Can allocate", []() - { - BestFitArena arena{1024}; - arena.GetStats()->detectLeaks = false; - - void* p = arena.Alloc(4); - new (p) TypeOfSize<4>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.Contains(p)).ToBeTrue(); - }); - - It("Allocates at correct addresses", []() - { - BestFitArena arena{1024}; - arena.GetStats()->detectLeaks = false; - - const auto* blockPtr = static_cast(*arena.GetBlock()); - - void* p = arena.Alloc(4); - new (p) TypeOfSize<4>(); - Expect(p).ToEqual(blockPtr); - - void* p2 = arena.Alloc(4); - new (p2) TypeOfSize<4>(); - Expect(p2).ToEqual(blockPtr + 4); - }); - - It("Detects there is not enough space", []() - { - BestFitArena arena{32}; - arena.GetStats()->detectLeaks = false; - - // 16 bytes - void* p = arena.Alloc(20); - new (p) TypeOfSize<20>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.Contains(p)).ToBeTrue(); - - // Another 16 bytes - void* p2 = arena.Alloc(6); - new (p2) TypeOfSize<6>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.Contains(p2)).ToBeTrue(); - - // No more space, return null - void* p3 = arena.Alloc(8); // 8 bytes - Expect(p3).ToEqual(nullptr); - }); - - It("Allocates with alignment", []() - { - BestFitArena arena{1024}; - arena.GetStats()->detectLeaks = false; - - void* b = arena.Alloc(1); - new (b) TypeOfSize<1>(); - - // When padding is not 0 (last ptr is not aligned) - void* p = arena.Alloc(4, 8); - new (p) TypeOfSize<4>(); - Expect(GetAlignmentPadding(p, 8)).ToEqual(0); - - // When padding is 0 (last ptr is aligned) - void* p2 = arena.Alloc(4, 16); - new (p2) TypeOfSize<4>(); - Expect(GetAlignmentPadding(p2, 16)).ToEqual(0); - - // When padding is 0 (last ptr is aligned) - void* p3 = arena.Alloc(8, 32); - new (p3) TypeOfSize<8>(); - Expect(GetAlignmentPadding(p3, 32)).ToEqual(0); - }); - - It("Can free", []() - { - BestFitArena arena{64}; - arena.GetStats()->detectLeaks = false; + BestFitArena arena{1024}; + arena.GetStats()->detectLeaks = false; + Expect(arena.GetFreeSize()).ToEqual(1024); + Expect(*arena.GetBlock()).ToNotEqual(nullptr); + Expect(arena.GetBlock().size).ToEqual(1024); + }); - void* p = arena.Alloc(32); - new (p) TypeOfSize<32>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(32); + It("Can allocate", []() + { + BestFitArena arena{1024}; + arena.GetStats()->detectLeaks = false; - arena.Free(p, 32); - Expect(arena.GetFreeSize()).ToEqual(64); - }); + void* p = arena.Alloc(4); + new (p) TypeOfSize<4>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.Contains(p)).ToBeTrue(); + }); - It("Can free multiple", []() - { - BestFitArena arena{64}; - arena.GetStats()->detectLeaks = false; + It("Allocates at correct addresses", []() + { + BestFitArena arena{1024}; + arena.GetStats()->detectLeaks = false; - void* p = arena.Alloc(16); - new (p) TypeOfSize<16>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(48); + const auto* blockPtr = static_cast(*arena.GetBlock()); - void* p2 = arena.Alloc(16); - new (p2) TypeOfSize<16>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(32); + void* p = arena.Alloc(4); + new (p) TypeOfSize<4>(); + Expect(p).ToEqual(blockPtr); - arena.Free(p2, 16); - Expect(arena.GetFreeSize()).ToEqual(48); + void* p2 = arena.Alloc(4); + new (p2) TypeOfSize<4>(); + Expect(p2).ToEqual(blockPtr + 4); + }); - arena.Free(p, 16); - Expect(arena.GetFreeSize()).ToEqual(64); - }); + It("Detects there is not enough space", []() + { + BestFitArena arena{32}; + arena.GetStats()->detectLeaks = false; + + // 16 bytes + void* p = arena.Alloc(20); + new (p) TypeOfSize<20>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.Contains(p)).ToBeTrue(); + + // Another 16 bytes + void* p2 = arena.Alloc(6); + new (p2) TypeOfSize<6>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.Contains(p2)).ToBeTrue(); + + // No more space, return null + void* p3 = arena.Alloc(8); // 8 bytes + Expect(p3).ToEqual(nullptr); + }); - It("Can free in between allocations", []() - { - BestFitArena arena{64}; - arena.GetStats()->detectLeaks = false; - - void* p = arena.Alloc(32); - new (p) TypeOfSize<32>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(32); - - void* p2 = arena.Alloc(30); - new (p2) TypeOfSize<30>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(2); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - - void* p3 = arena.Alloc(2); - new (p3) TypeOfSize<2>(); - Expect(p3).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(0); - // No space left, no free slots - Expect(arena.GetFreeSlots().Size()).ToEqual(0); - - arena.Free(p2, 30); - Expect(arena.GetFreeSize()).ToEqual(30); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - Expect(arena.GetFreeSlots()[0].start).ToEqual(p2); - Expect(arena.GetFreeSlots()[0].End()).ToEqual(p3); - }); - - It("Can merge previous and next slots on free", []() - { - BestFitArena arena{64}; - arena.GetStats()->detectLeaks = false; + It("Allocates with alignment", []() + { + BestFitArena arena{1024}; + arena.GetStats()->detectLeaks = false; + + void* b = arena.Alloc(1); + new (b) TypeOfSize<1>(); + + // When padding is not 0 (last ptr is not aligned) + void* p = arena.Alloc(4, 8); + new (p) TypeOfSize<4>(); + Expect(GetAlignmentPadding(p, 8)).ToEqual(0); + + // When padding is 0 (last ptr is aligned) + void* p2 = arena.Alloc(4, 16); + new (p2) TypeOfSize<4>(); + Expect(GetAlignmentPadding(p2, 16)).ToEqual(0); + + // When padding is 0 (last ptr is aligned) + void* p3 = arena.Alloc(8, 32); + new (p3) TypeOfSize<8>(); + Expect(GetAlignmentPadding(p3, 32)).ToEqual(0); + }); - void* p = arena.Alloc(9); - new (p) TypeOfSize<9>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(55); + It("Can free", []() + { + BestFitArena arena{64}; + arena.GetStats()->detectLeaks = false; - void* p2 = arena.Alloc(50); - new (p2) TypeOfSize<50>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(5); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); + void* p = arena.Alloc(32); + new (p) TypeOfSize<32>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(32); - void* p3 = arena.Alloc(5); - new (p3) TypeOfSize<5>(); - Expect(p3).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(0); + arena.Free(p, 32); + Expect(arena.GetFreeSize()).ToEqual(64); + }); - // No space left, no free slots - Expect(arena.GetFreeSlots().Size()).ToEqual(0); + It("Can free multiple", []() + { + BestFitArena arena{64}; + arena.GetStats()->detectLeaks = false; - arena.Free(p, 9); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); + void* p = arena.Alloc(16); + new (p) TypeOfSize<16>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(48); - arena.Free(p3, 5); - Expect(arena.GetFreeSlots().Size()).ToEqual(2); + void* p2 = arena.Alloc(16); + new (p2) TypeOfSize<16>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(32); - arena.Free(p2, 50); // Slots previous and next are merged - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - Expect(arena.GetFreeSlots()[0].size).ToEqual(64); + arena.Free(p2, 16); + Expect(arena.GetFreeSize()).ToEqual(48); - // Slot contains the entire memory block - Expect(arena.GetFreeSlots()[0].start).ToEqual(arena.GetBlock().data); - Expect(arena.GetFreeSlots()[0].End()).ToEqual(arena.GetBlock().End()); - }); + arena.Free(p, 16); + Expect(arena.GetFreeSize()).ToEqual(64); + }); - It("Can merge previous slot on free", []() - { - BestFitArena arena{48}; - arena.GetStats()->detectLeaks = false; + It("Can free in between allocations", []() + { + BestFitArena arena{64}; + arena.GetStats()->detectLeaks = false; + + void* p = arena.Alloc(32); + new (p) TypeOfSize<32>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(32); + + void* p2 = arena.Alloc(30); + new (p2) TypeOfSize<30>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(2); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + void* p3 = arena.Alloc(2); + new (p3) TypeOfSize<2>(); + Expect(p3).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(0); + // No space left, no free slots + Expect(arena.GetFreeSlots().Size()).ToEqual(0); + + arena.Free(p2, 30); + Expect(arena.GetFreeSize()).ToEqual(30); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + Expect(arena.GetFreeSlots()[0].start).ToEqual(p2); + Expect(arena.GetFreeSlots()[0].End()).ToEqual(p3); + }); - void* p = arena.Alloc(39); - new (p) TypeOfSize<39>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(9); + It("Can merge previous and next slots on free", []() + { + BestFitArena arena{64}; + arena.GetStats()->detectLeaks = false; - void* p2 = arena.Alloc(9); - new (p2) TypeOfSize<9>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(0); - Expect(arena.GetFreeSlots().Size()).ToEqual(0); + void* p = arena.Alloc(9); + new (p) TypeOfSize<9>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(55); - arena.Free(p, 39); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); + void* p2 = arena.Alloc(50); + new (p2) TypeOfSize<50>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(5); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); - arena.Free(p2, 9); // Slot is expanded from the front - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - Expect(arena.GetFreeSlots()[0].size).ToEqual(48); + void* p3 = arena.Alloc(5); + new (p3) TypeOfSize<5>(); + Expect(p3).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(0); - // Slot contains the entire memory block - Expect(arena.GetFreeSlots()[0].start).ToEqual(arena.GetBlock().data); - Expect(arena.GetFreeSlots()[0].End()).ToEqual(arena.GetBlock().End()); - }); + // No space left, no free slots + Expect(arena.GetFreeSlots().Size()).ToEqual(0); - It("Can merge next slot on free", []() - { - BestFitArena arena{48}; - arena.GetStats()->detectLeaks = false; + arena.Free(p, 9); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); - void* p = arena.Alloc(24); - new (p) TypeOfSize<24>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(24); + arena.Free(p3, 5); + Expect(arena.GetFreeSlots().Size()).ToEqual(2); - void* p2 = arena.Alloc(24); - new (p2) TypeOfSize<24>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(0); - Expect(arena.GetFreeSlots().Size()).ToEqual(0); + arena.Free(p2, 50); // Slots previous and next are merged + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + Expect(arena.GetFreeSlots()[0].size).ToEqual(64); - arena.Free(p2, 24); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); + // Slot contains the entire memory block + Expect(arena.GetFreeSlots()[0].start).ToEqual(arena.GetBlock().data); + Expect(arena.GetFreeSlots()[0].End()).ToEqual(arena.GetBlock().End()); + }); - arena.Free(p, 24); // Slot is expanded from the back - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - Expect(arena.GetFreeSlots()[0].size).ToEqual(48); + It("Can merge previous slot on free", []() + { + BestFitArena arena{48}; + arena.GetStats()->detectLeaks = false; + + void* p = arena.Alloc(39); + new (p) TypeOfSize<39>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(9); + + void* p2 = arena.Alloc(9); + new (p2) TypeOfSize<9>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(0); + Expect(arena.GetFreeSlots().Size()).ToEqual(0); + + arena.Free(p, 39); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + arena.Free(p2, 9); // Slot is expanded from the front + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + Expect(arena.GetFreeSlots()[0].size).ToEqual(48); + + // Slot contains the entire memory block + Expect(arena.GetFreeSlots()[0].start).ToEqual(arena.GetBlock().data); + Expect(arena.GetFreeSlots()[0].End()).ToEqual(arena.GetBlock().End()); + }); - // Slot contains the entire memory block - Expect(arena.GetFreeSlots()[0].start).ToEqual(arena.GetBlock().data); - Expect(arena.GetFreeSlots()[0].End()).ToEqual(arena.GetBlock().End()); - }); + It("Can merge next slot on free", []() + { + BestFitArena arena{48}; + arena.GetStats()->detectLeaks = false; + + void* p = arena.Alloc(24); + new (p) TypeOfSize<24>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(24); + + void* p2 = arena.Alloc(24); + new (p2) TypeOfSize<24>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(0); + Expect(arena.GetFreeSlots().Size()).ToEqual(0); + + arena.Free(p2, 24); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + arena.Free(p, 24); // Slot is expanded from the back + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + Expect(arena.GetFreeSlots()[0].size).ToEqual(48); + + // Slot contains the entire memory block + Expect(arena.GetFreeSlots()[0].start).ToEqual(arena.GetBlock().data); + Expect(arena.GetFreeSlots()[0].End()).ToEqual(arena.GetBlock().End()); + }); - It("Ensures a big alignment leaves a gap", []() + It("Ensures a big alignment leaves a gap", []() + { + BestFitArena arena{128}; + arena.GetStats()->detectLeaks = false; + + // We ensure first allocation aligns the block (just for the test) + void* p = arena.Alloc(8); + new (p) TypeOfSize<8>(); + Expect(arena.GetFreeSize()).ToEqual(120); + + void* p2 = arena.Alloc(8, 64); + new (p2) TypeOfSize<8>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(112); + + // Alignment is absolute, so the gap between p and p2 is zero + // when the block base lands on a matching 64B boundary. + const bool hasGap = p2 > (u8*)p + 8; + Expect(arena.GetFreeSlots().Size()).ToEqual(hasGap ? 2 : 1); + + // Slot contains the rest if the block + Expect(arena.GetFreeSlots()[0].start).ToEqual((u8*)p2 + 8); + Expect(arena.GetFreeSlots()[0].End()).ToEqual(arena.GetBlock().End()); + + // Slot contains the alignment gap + if (hasGap) { - BestFitArena arena{128}; - arena.GetStats()->detectLeaks = false; - - // We ensure first allocation aligns the block (just for the test) - void* p = arena.Alloc(8); - new (p) TypeOfSize<8>(); - Expect(arena.GetFreeSize()).ToEqual(120); - - void* p2 = arena.Alloc(8, 64); - new (p2) TypeOfSize<8>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(112); - - // Alignment is absolute, so the gap between p and p2 is zero - // when the block base lands on a matching 64B boundary. - const bool hasGap = p2 > (u8*)p + 8; - Expect(arena.GetFreeSlots().Size()).ToEqual(hasGap ? 2 : 1); - - // Slot contains the rest if the block - Expect(arena.GetFreeSlots()[0].start).ToEqual((u8*)p2 + 8); - Expect(arena.GetFreeSlots()[0].End()).ToEqual(arena.GetBlock().End()); - - // Slot contains the alignment gap - if (hasGap) - { - Expect(arena.GetFreeSlots()[1].start).ToEqual((u8*)p + 8); - Expect(arena.GetFreeSlots()[1].End()).ToEqual(p2); - } - }); + Expect(arena.GetFreeSlots()[1].start).ToEqual((u8*)p + 8); + Expect(arena.GetFreeSlots()[1].End()).ToEqual(p2); + } }); -} +}); +return true; +}(); +} // namespace diff --git a/Tests/Memory/BigBestFitArena.spec.cpp b/Tests/Memory/BigBestFitArena.spec.cpp index 1f667f36..3b5180ec 100644 --- a/Tests/Memory/BigBestFitArena.spec.cpp +++ b/Tests/Memory/BigBestFitArena.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include @@ -13,296 +13,301 @@ struct TypeOfSize p::u8 data[size]{0}; // Fill data for debugging }; -void RegisterMemoryBigBestFitArenaTests() +namespace { - Spec("Memory.BigBestFitArena", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("Memory.BigBestFitArena", []() +{ + It("Reserves a block on construction", []() { - It("Reserves a block on construction", []() - { - BigBestFitArena arena{1024}; - arena.GetStats()->detectLeaks = false; - Expect(arena.GetFreeSize()).ToEqual(1024); - Expect(*arena.GetBlock()).ToNotEqual(nullptr); - Expect(arena.GetBlock().size).ToEqual(1024); - }); - - It("Can allocate", []() - { - BigBestFitArena arena{1024}; - arena.GetStats()->detectLeaks = false; - - void* p = arena.Alloc(4); - new (p) TypeOfSize<4>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.Contains(p)).ToBeTrue(); - }); - - It("Allocates at correct addresses", []() - { - BigBestFitArena arena{1024}; - arena.GetStats()->detectLeaks = false; - - const auto* blockPtr = static_cast(*arena.GetBlock()); - - void* p = arena.Alloc(4); - new (p) TypeOfSize<4>(); - const void* expectedP = blockPtr + p::GetAlignmentPaddingWithHeader(blockPtr, 8, 8); - Expect(p).ToEqual(expectedP); - - void* p2 = arena.Alloc(4); - new (p2) TypeOfSize<4>(); - void* expectedP2 = - static_cast(p) + 8 + p::GetAlignmentPaddingWithHeader(p, 8, 8); - Expect(p2).ToEqual(expectedP2); - }); - - It("Detects there is not enough space", []() - { - BigBestFitArena arena{32}; - arena.GetStats()->detectLeaks = false; - - // 16 bytes - void* p = arena.Alloc(8); - new (p) TypeOfSize<8>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.Contains(p)).ToBeTrue(); - - // Another 16 bytes - void* p2 = arena.Alloc(4); - new (p2) TypeOfSize<4>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.Contains(p2)).ToBeTrue(); - - // No more space, return null - void* p3 = arena.Alloc(8); // 8 bytes - Expect(p3).ToEqual(nullptr); - }); - - It("Allocates with alignment", []() - { - BigBestFitArena arena{1024}; - arena.GetStats()->detectLeaks = false; - - void* b = arena.Alloc(1); - new (b) TypeOfSize<1>(); + BigBestFitArena arena{1024}; + arena.GetStats()->detectLeaks = false; + Expect(arena.GetFreeSize()).ToEqual(1024); + Expect(*arena.GetBlock()).ToNotEqual(nullptr); + Expect(arena.GetBlock().size).ToEqual(1024); + }); - // When padding is not 0 (last ptr is not aligned) - void* p = arena.Alloc(4, 8); - new (p) TypeOfSize<4>(); - Expect(p::GetAlignmentPadding(p, 8)).ToEqual(0); + It("Can allocate", []() + { + BigBestFitArena arena{1024}; + arena.GetStats()->detectLeaks = false; - // When padding is 0 (last ptr is aligned) - void* p2 = arena.Alloc(4, 16); - new (p2) TypeOfSize<4>(); - Expect(p::GetAlignmentPadding(p2, 16)).ToEqual(0); + void* p = arena.Alloc(4); + new (p) TypeOfSize<4>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.Contains(p)).ToBeTrue(); + }); - // When padding is 0 (last ptr is aligned) - void* p3 = arena.Alloc(8, 32); - new (p3) TypeOfSize<8>(); - Expect(p::GetAlignmentPadding(p3, 32)).ToEqual(0); - }); + It("Allocates at correct addresses", []() + { + BigBestFitArena arena{1024}; + arena.GetStats()->detectLeaks = false; - It("Can free", []() - { - BigBestFitArena arena{64}; - arena.GetStats()->detectLeaks = false; + const auto* blockPtr = static_cast(*arena.GetBlock()); - void* p = arena.Alloc(32); - new (p) TypeOfSize<32>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(24); + void* p = arena.Alloc(4); + new (p) TypeOfSize<4>(); + const void* expectedP = blockPtr + p::GetAlignmentPaddingWithHeader(blockPtr, 8, 8); + Expect(p).ToEqual(expectedP); - arena.Free(p, 32); - Expect(arena.GetFreeSize()).ToEqual(64); - }); + void* p2 = arena.Alloc(4); + new (p2) TypeOfSize<4>(); + void* expectedP2 = + static_cast(p) + 8 + p::GetAlignmentPaddingWithHeader(p, 8, 8); + Expect(p2).ToEqual(expectedP2); + }); - It("Can free multiple", []() - { - BigBestFitArena arena{64}; - arena.GetStats()->detectLeaks = false; + It("Detects there is not enough space", []() + { + BigBestFitArena arena{32}; + arena.GetStats()->detectLeaks = false; + + // 16 bytes + void* p = arena.Alloc(8); + new (p) TypeOfSize<8>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.Contains(p)).ToBeTrue(); + + // Another 16 bytes + void* p2 = arena.Alloc(4); + new (p2) TypeOfSize<4>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.Contains(p2)).ToBeTrue(); + + // No more space, return null + void* p3 = arena.Alloc(8); // 8 bytes + Expect(p3).ToEqual(nullptr); + }); - void* p = arena.Alloc(16); - new (p) TypeOfSize<16>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(40); + It("Allocates with alignment", []() + { + BigBestFitArena arena{1024}; + arena.GetStats()->detectLeaks = false; + + void* b = arena.Alloc(1); + new (b) TypeOfSize<1>(); + + // When padding is not 0 (last ptr is not aligned) + void* p = arena.Alloc(4, 8); + new (p) TypeOfSize<4>(); + Expect(p::GetAlignmentPadding(p, 8)).ToEqual(0); + + // When padding is 0 (last ptr is aligned) + void* p2 = arena.Alloc(4, 16); + new (p2) TypeOfSize<4>(); + Expect(p::GetAlignmentPadding(p2, 16)).ToEqual(0); + + // When padding is 0 (last ptr is aligned) + void* p3 = arena.Alloc(8, 32); + new (p3) TypeOfSize<8>(); + Expect(p::GetAlignmentPadding(p3, 32)).ToEqual(0); + }); - void* p2 = arena.Alloc(16); - new (p2) TypeOfSize<16>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(16); + It("Can free", []() + { + BigBestFitArena arena{64}; + arena.GetStats()->detectLeaks = false; - arena.Free(p2, 16); - Expect(arena.GetFreeSize()).ToEqual(40); + void* p = arena.Alloc(32); + new (p) TypeOfSize<32>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(24); - arena.Free(p, 16); - Expect(arena.GetFreeSize()).ToEqual(64); - }); + arena.Free(p, 32); + Expect(arena.GetFreeSize()).ToEqual(64); + }); - It("Can free in between allocations", []() - { - BigBestFitArena arena{64}; - arena.GetStats()->detectLeaks = false; - - void* p = arena.Alloc(16); - new (p) TypeOfSize<16>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(40); - - void* p2 = arena.Alloc(16); - new (p2) TypeOfSize<16>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(16); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - - void* p3 = arena.Alloc(8); - new (p3) TypeOfSize<8>(); - Expect(p3).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(0); - // No space left, no free slots - Expect(arena.GetFreeSlots().Size()).ToEqual(0); - - arena.Free(p2, 16); - Expect(arena.GetFreeSize()).ToEqual(24); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - - auto slot = arena.GetFreeSlots()[0]; - u8* slotStart = (u8*)arena.GetBlock().data + slot.offset; - Expect(slotStart).ToEqual(static_cast(p2) - 8); - Expect(slotStart + slot.size).ToEqual(static_cast(p3) - 8); - }); - - It("Can merge previous and next slots on free", []() - { - BigBestFitArena arena{64}; - arena.GetStats()->detectLeaks = false; + It("Can free multiple", []() + { + BigBestFitArena arena{64}; + arena.GetStats()->detectLeaks = false; - void* p = arena.Alloc(16); - new (p) TypeOfSize<16>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(40); + void* p = arena.Alloc(16); + new (p) TypeOfSize<16>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(40); - void* p2 = arena.Alloc(16); - new (p2) TypeOfSize<16>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(16); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); + void* p2 = arena.Alloc(16); + new (p2) TypeOfSize<16>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(16); - void* p3 = arena.Alloc(8); - new (p3) TypeOfSize<8>(); - Expect(p3).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(0); + arena.Free(p2, 16); + Expect(arena.GetFreeSize()).ToEqual(40); - // No space left, no free slots - Expect(arena.GetFreeSlots().Size()).ToEqual(0); + arena.Free(p, 16); + Expect(arena.GetFreeSize()).ToEqual(64); + }); - arena.Free(p, 16); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); + It("Can free in between allocations", []() + { + BigBestFitArena arena{64}; + arena.GetStats()->detectLeaks = false; + + void* p = arena.Alloc(16); + new (p) TypeOfSize<16>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(40); + + void* p2 = arena.Alloc(16); + new (p2) TypeOfSize<16>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(16); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + void* p3 = arena.Alloc(8); + new (p3) TypeOfSize<8>(); + Expect(p3).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(0); + // No space left, no free slots + Expect(arena.GetFreeSlots().Size()).ToEqual(0); + + arena.Free(p2, 16); + Expect(arena.GetFreeSize()).ToEqual(24); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + auto slot = arena.GetFreeSlots()[0]; + u8* slotStart = (u8*)arena.GetBlock().data + slot.offset; + Expect(slotStart).ToEqual(static_cast(p2) - 8); + Expect(slotStart + slot.size).ToEqual(static_cast(p3) - 8); + }); - arena.Free(p3, 8); - Expect(arena.GetFreeSlots().Size()).ToEqual(2); + It("Can merge previous and next slots on free", []() + { + BigBestFitArena arena{64}; + arena.GetStats()->detectLeaks = false; + + void* p = arena.Alloc(16); + new (p) TypeOfSize<16>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(40); + + void* p2 = arena.Alloc(16); + new (p2) TypeOfSize<16>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(16); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + void* p3 = arena.Alloc(8); + new (p3) TypeOfSize<8>(); + Expect(p3).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(0); + + // No space left, no free slots + Expect(arena.GetFreeSlots().Size()).ToEqual(0); + + arena.Free(p, 16); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + arena.Free(p3, 8); + Expect(arena.GetFreeSlots().Size()).ToEqual(2); + + arena.Free(p2, 16); // Slots previous and next are merged + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + // Slot contains the entire memory block + auto slot = arena.GetFreeSlots()[0]; + u8* slotStart = (u8*)arena.GetBlock().data + slot.offset; + Expect(slotStart).ToEqual(static_cast(arena.GetBlock().data)); + Expect(slotStart + slot.size).ToEqual(static_cast(arena.GetBlock().End())); + }); - arena.Free(p2, 16); // Slots previous and next are merged - Expect(arena.GetFreeSlots().Size()).ToEqual(1); + It("Can merge previous slot on free", []() + { + BigBestFitArena arena{48}; + arena.GetStats()->detectLeaks = false; + + void* p = arena.Alloc(16); + new (p) TypeOfSize<16>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(24); + + void* p2 = arena.Alloc(16); + new (p2) TypeOfSize<16>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(0); + Expect(arena.GetFreeSlots().Size()).ToEqual(0); + + arena.Free(p, 16); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + arena.Free(p2, 16); // Slot is expanded from the front + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + // Slot contains the entire memory block + auto slot = arena.GetFreeSlots()[0]; + u8* slotStart = (u8*)arena.GetBlock().data + slot.offset; + Expect(slotStart).ToEqual(static_cast(arena.GetBlock().data)); + Expect(slotStart + slot.size).ToEqual(static_cast(arena.GetBlock().End())); + }); - // Slot contains the entire memory block - auto slot = arena.GetFreeSlots()[0]; - u8* slotStart = (u8*)arena.GetBlock().data + slot.offset; - Expect(slotStart).ToEqual(static_cast(arena.GetBlock().data)); - Expect(slotStart + slot.size).ToEqual(static_cast(arena.GetBlock().End())); - }); + It("Can merge next slot on free", []() + { + BigBestFitArena arena{48}; + arena.GetStats()->detectLeaks = false; + + void* p = arena.Alloc(16); + new (p) TypeOfSize<16>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(24); + + void* p2 = arena.Alloc(16); + new (p2) TypeOfSize<16>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(0); + Expect(arena.GetFreeSlots().Size()).ToEqual(0); + + arena.Free(p2, 16); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + arena.Free(p, 16); // Slot is expanded from the back + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + // Slot contains the entire memory block + auto slot = arena.GetFreeSlots()[0]; + u8* slotStart = (u8*)arena.GetBlock().data + slot.offset; + Expect(slotStart).ToEqual(static_cast(arena.GetBlock().data)); + Expect(slotStart + slot.size).ToEqual(static_cast(arena.GetBlock().End())); + }); - It("Can merge previous slot on free", []() - { - BigBestFitArena arena{48}; - arena.GetStats()->detectLeaks = false; - - void* p = arena.Alloc(16); - new (p) TypeOfSize<16>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(24); - - void* p2 = arena.Alloc(16); - new (p2) TypeOfSize<16>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(0); - Expect(arena.GetFreeSlots().Size()).ToEqual(0); - - arena.Free(p, 16); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - - arena.Free(p2, 16); // Slot is expanded from the front - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - - // Slot contains the entire memory block - auto slot = arena.GetFreeSlots()[0]; - u8* slotStart = (u8*)arena.GetBlock().data + slot.offset; - Expect(slotStart).ToEqual(static_cast(arena.GetBlock().data)); - Expect(slotStart + slot.size).ToEqual(static_cast(arena.GetBlock().End())); - }); - - It("Can merge next slot on free", []() - { - BigBestFitArena arena{48}; - arena.GetStats()->detectLeaks = false; - - void* p = arena.Alloc(16); - new (p) TypeOfSize<16>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(24); - - void* p2 = arena.Alloc(16); - new (p2) TypeOfSize<16>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(0); - Expect(arena.GetFreeSlots().Size()).ToEqual(0); - - arena.Free(p2, 16); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - - arena.Free(p, 16); // Slot is expanded from the back - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - - // Slot contains the entire memory block - auto slot = arena.GetFreeSlots()[0]; - u8* slotStart = (u8*)arena.GetBlock().data + slot.offset; - Expect(slotStart).ToEqual(static_cast(arena.GetBlock().data)); - Expect(slotStart + slot.size).ToEqual(static_cast(arena.GetBlock().End())); - }); - - It("Ensures a big alignment leaves a gap", []() + It("Ensures a big alignment leaves a gap", []() + { + BigBestFitArena arena{128}; + arena.GetStats()->detectLeaks = false; + + // We ensure first allocation aligns the block (just for the test) + void* p = arena.Alloc(8); + new (p) TypeOfSize<8>(); + Expect(arena.GetFreeSize()).ToEqual(112); + + void* p2 = arena.Alloc(8, 64); + new (p2) TypeOfSize<8>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(96); + + // Alignment is absolute, so the gap between p and p2 is zero + // when the block base lands on a matching 64B boundary. + const bool hasGap = arena.GetAllocationStart(p2) > arena.GetAllocationEnd(p); + Expect(arena.GetFreeSlots().Size()).ToEqual(hasGap ? 2 : 1); + + // Slot contains the rest if the block + auto slot0 = arena.GetFreeSlots()[0]; + u8* slot0Start = (u8*)arena.GetBlock().data + slot0.offset; + Expect(slot0Start).ToEqual(arena.GetAllocationEnd(p2)); + Expect(slot0Start + slot0.size).ToEqual(static_cast(arena.GetBlock().End())); + + // Slot contains the alignment gap + if (hasGap) { - BigBestFitArena arena{128}; - arena.GetStats()->detectLeaks = false; - - // We ensure first allocation aligns the block (just for the test) - void* p = arena.Alloc(8); - new (p) TypeOfSize<8>(); - Expect(arena.GetFreeSize()).ToEqual(112); - - void* p2 = arena.Alloc(8, 64); - new (p2) TypeOfSize<8>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(96); - - // Alignment is absolute, so the gap between p and p2 is zero - // when the block base lands on a matching 64B boundary. - const bool hasGap = arena.GetAllocationStart(p2) > arena.GetAllocationEnd(p); - Expect(arena.GetFreeSlots().Size()).ToEqual(hasGap ? 2 : 1); - - // Slot contains the rest if the block - auto slot0 = arena.GetFreeSlots()[0]; - u8* slot0Start = (u8*)arena.GetBlock().data + slot0.offset; - Expect(slot0Start).ToEqual(arena.GetAllocationEnd(p2)); - Expect(slot0Start + slot0.size).ToEqual(static_cast(arena.GetBlock().End())); - - // Slot contains the alignment gap - if (hasGap) - { - auto slot1 = arena.GetFreeSlots()[1]; - u8* slot1Start = (u8*)arena.GetBlock().data + slot1.offset; - Expect(slot1Start).ToEqual(arena.GetAllocationEnd(p)); - Expect(slot1Start + slot1.size).ToEqual(arena.GetAllocationStart(p2)); - } - }); + auto slot1 = arena.GetFreeSlots()[1]; + u8* slot1Start = (u8*)arena.GetBlock().data + slot1.offset; + Expect(slot1Start).ToEqual(arena.GetAllocationEnd(p)); + Expect(slot1Start + slot1.size).ToEqual(arena.GetAllocationStart(p2)); + } }); -} +}); +return true; +}(); +} // namespace diff --git a/Tests/Memory/Memory.spec.cpp b/Tests/Memory/Memory.spec.cpp index 2a69c6af..bdef3340 100644 --- a/Tests/Memory/Memory.spec.cpp +++ b/Tests/Memory/Memory.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include @@ -53,196 +53,201 @@ struct MoveType }; -void RegisterMemoryMemoryTests() +namespace { - Spec("Memory.Operations", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("Memory.Operations", []() +{ + It("Can default construct", []() + { + // Check that it inits to 0 + bool boolValues[2]{true, true}; // Assign simulated garbage + ConstructItems(boolValues, 2); + Expect(boolValues[0]).ToEqual(false); + Expect(boolValues[1]).ToEqual(false); + + u8 u8Values[2]{34, 45}; // Assign simulated garbage + ConstructItems(u8Values, 2, u8(128)); + Expect(u8Values[0]).ToEqual(128); + Expect(u8Values[1]).ToEqual(128); + + u32 u32Values[2]{34, 45}; // Assign simulated garbage + ConstructItems(u32Values, 2); + Expect(u32Values[0]).ToEqual(0); + Expect(u32Values[1]).ToEqual(0); + + u32* ptrValues[2]{(u32*)1, (u32*)2}; // Assign simulated garbage + ConstructItems(ptrValues, 2); + Expect(ptrValues[0]).ToEqual(nullptr); + Expect(ptrValues[1]).ToEqual(nullptr); + + ConstructedType constructedValues[2]; + constructedValues[0].value = 0.234f; // Assign simulated garbage + constructedValues[1].value = 0.234f; + ConstructItems(constructedValues, 2); + Expect(constructedValues[0].value).ToEqual(0.f); + Expect(constructedValues[1].value).ToEqual(0.f); + + BoolsType boolsValues[2]; + boolsValues[0].value1 = false; // Assign simulated garbage + boolsValues[0].value2 = true; + boolsValues[1].value1 = false; + boolsValues[1].value2 = false; + ConstructItems(boolsValues, 2); + Expect(boolsValues[0].value1).ToEqual(true); + Expect(boolsValues[0].value2).ToEqual(false); + Expect(boolsValues[1].value1).ToEqual(true); + Expect(boolsValues[1].value2).ToEqual(false); + }); + + It("Can value construct", []() + { + bool boolValues[2]{false, false}; // Assign simulated garbage + ConstructItems(boolValues, 2, true); + Expect(boolValues[0]).ToEqual(true); + Expect(boolValues[1]).ToEqual(true); + + u8 u8Values[2]{34, 45}; // Assign simulated garbage + ConstructItems(u8Values, 2, u8(128)); + Expect(u8Values[0]).ToEqual(128); + Expect(u8Values[1]).ToEqual(128); + + u32 u32Values[2]{34, 45}; // Assign simulated garbage + ConstructItems(u32Values, 2, u32(128)); + Expect(u32Values[0]).ToEqual(128); + Expect(u32Values[1]).ToEqual(128); + + u32* ptrValues[2]{(u32*)1, (u32*)2}; // Assign simulated garbage + ConstructItems(ptrValues, 2, (u32*)32); + Expect(ptrValues[0]).ToEqual((u32*)32); + Expect(ptrValues[1]).ToEqual((u32*)32); + + ConstructedType constructedValues[2]{{0.234f}, {0.234f}}; // Assign simulated garbage + ConstructItems(constructedValues, 2, ConstructedType(1.f)); + Expect(constructedValues[0].value).ToEqual(1.f); + Expect(constructedValues[1].value).ToEqual(1.f); + + BoolsType boolsValues[2]{ + {.value1 = false, .value2 = true}, + {.value1 = false, .value2 = true} + }; // Assign simulated garbage + ConstructItems(boolsValues, 2, BoolsType{.value1 = true, .value2 = true}); + Expect(boolsValues[0].value1).ToEqual(true); + Expect(boolsValues[0].value2).ToEqual(true); + Expect(boolsValues[1].value1).ToEqual(true); + Expect(boolsValues[1].value2).ToEqual(true); + }); + + It("Can copy construct", []() + { + bool boolValues[2]{false, false}; // Assign simulated garbage + bool srcBoolValues[2]{true, false}; + CopyConstructItems(boolValues, 2, srcBoolValues); + Expect(boolValues[0]).ToEqual(true); + Expect(boolValues[1]).ToEqual(false); + + u8 u8Values[2]{34, 45}; // Assign simulated garbage + u8 srcU8Values[2]{128, 129}; + CopyConstructItems(u8Values, 2, srcU8Values); + Expect(u8Values[0]).ToEqual(128); + Expect(u8Values[1]).ToEqual(129); + + u32 u32Values[2]{34, 45}; // Assign simulated garbage + u32 srcU32Values[2]{128, 129}; + CopyConstructItems(u32Values, 2, srcU32Values); + Expect(u32Values[0]).ToEqual(128); + Expect(u32Values[1]).ToEqual(129); + + u32* ptrValues[2]{(u32*)1, (u32*)2}; // Assign simulated garbage + u32* srcPtrValues[2]{(u32*)34, (u32*)23433}; + CopyConstructItems(ptrValues, 2, srcPtrValues); + Expect(ptrValues[0]).ToEqual((u32*)34); + Expect(ptrValues[1]).ToEqual((u32*)23433); + + ConstructedType constructedValues[2]{{0.234f}, {0.234f}}; // Assign simulated garbage + ConstructedType srcConstructedValues[2]{{1.f}, {2.f}}; + CopyConstructItems(constructedValues, 2, srcConstructedValues); + Expect(constructedValues[0].value).ToEqual(1.f); + Expect(constructedValues[1].value).ToEqual(2.f); + + BoolsType boolsValues[2]{ + {false, true}, + {false, true} + }; // Assign simulated garbage + BoolsType srcBoolsValues[2]{ + {true, false}, + {false, true } + }; + CopyConstructItems(boolsValues, 2, srcBoolsValues); + Expect(boolsValues[0].value1).ToEqual(true); + Expect(boolsValues[0].value2).ToEqual(false); + Expect(boolsValues[1].value1).ToEqual(false); + Expect(boolsValues[1].value2).ToEqual(true); + + CopyType copyValues[2]{5, 6}; // Assign simulated garbage + CopyType srcCopyValues[2]{34, 75}; + CopyConstructItems(copyValues, 2, srcCopyValues); + Expect(copyValues[0].value).ToEqual(34); + Expect(copyValues[1].value).ToEqual(75); + }); + + It("Can move construct", []() { - It("Can default construct", []() - { - // Check that it inits to 0 - bool boolValues[2]{true, true}; // Assign simulated garbage - ConstructItems(boolValues, 2); - Expect(boolValues[0]).ToEqual(false); - Expect(boolValues[1]).ToEqual(false); - - u8 u8Values[2]{34, 45}; // Assign simulated garbage - ConstructItems(u8Values, 2, u8(128)); - Expect(u8Values[0]).ToEqual(128); - Expect(u8Values[1]).ToEqual(128); - - u32 u32Values[2]{34, 45}; // Assign simulated garbage - ConstructItems(u32Values, 2); - Expect(u32Values[0]).ToEqual(0); - Expect(u32Values[1]).ToEqual(0); - - u32* ptrValues[2]{(u32*)1, (u32*)2}; // Assign simulated garbage - ConstructItems(ptrValues, 2); - Expect(ptrValues[0]).ToEqual(nullptr); - Expect(ptrValues[1]).ToEqual(nullptr); - - ConstructedType constructedValues[2]; - constructedValues[0].value = 0.234f; // Assign simulated garbage - constructedValues[1].value = 0.234f; - ConstructItems(constructedValues, 2); - Expect(constructedValues[0].value).ToEqual(0.f); - Expect(constructedValues[1].value).ToEqual(0.f); - - BoolsType boolsValues[2]; - boolsValues[0].value1 = false; // Assign simulated garbage - boolsValues[0].value2 = true; - boolsValues[1].value1 = false; - boolsValues[1].value2 = false; - ConstructItems(boolsValues, 2); - Expect(boolsValues[0].value1).ToEqual(true); - Expect(boolsValues[0].value2).ToEqual(false); - Expect(boolsValues[1].value1).ToEqual(true); - Expect(boolsValues[1].value2).ToEqual(false); - }); - - It("Can value construct", []() - { - bool boolValues[2]{false, false}; // Assign simulated garbage - ConstructItems(boolValues, 2, true); - Expect(boolValues[0]).ToEqual(true); - Expect(boolValues[1]).ToEqual(true); - - u8 u8Values[2]{34, 45}; // Assign simulated garbage - ConstructItems(u8Values, 2, u8(128)); - Expect(u8Values[0]).ToEqual(128); - Expect(u8Values[1]).ToEqual(128); - - u32 u32Values[2]{34, 45}; // Assign simulated garbage - ConstructItems(u32Values, 2, u32(128)); - Expect(u32Values[0]).ToEqual(128); - Expect(u32Values[1]).ToEqual(128); - - u32* ptrValues[2]{(u32*)1, (u32*)2}; // Assign simulated garbage - ConstructItems(ptrValues, 2, (u32*)32); - Expect(ptrValues[0]).ToEqual((u32*)32); - Expect(ptrValues[1]).ToEqual((u32*)32); - - ConstructedType constructedValues[2]{{0.234f}, {0.234f}}; // Assign simulated garbage - ConstructItems(constructedValues, 2, ConstructedType(1.f)); - Expect(constructedValues[0].value).ToEqual(1.f); - Expect(constructedValues[1].value).ToEqual(1.f); - - BoolsType boolsValues[2]{ - {.value1 = false, .value2 = true}, - {.value1 = false, .value2 = true} - }; // Assign simulated garbage - ConstructItems(boolsValues, 2, BoolsType{.value1 = true, .value2 = true}); - Expect(boolsValues[0].value1).ToEqual(true); - Expect(boolsValues[0].value2).ToEqual(true); - Expect(boolsValues[1].value1).ToEqual(true); - Expect(boolsValues[1].value2).ToEqual(true); - }); - - It("Can copy construct", []() - { - bool boolValues[2]{false, false}; // Assign simulated garbage - bool srcBoolValues[2]{true, false}; - CopyConstructItems(boolValues, 2, srcBoolValues); - Expect(boolValues[0]).ToEqual(true); - Expect(boolValues[1]).ToEqual(false); - - u8 u8Values[2]{34, 45}; // Assign simulated garbage - u8 srcU8Values[2]{128, 129}; - CopyConstructItems(u8Values, 2, srcU8Values); - Expect(u8Values[0]).ToEqual(128); - Expect(u8Values[1]).ToEqual(129); - - u32 u32Values[2]{34, 45}; // Assign simulated garbage - u32 srcU32Values[2]{128, 129}; - CopyConstructItems(u32Values, 2, srcU32Values); - Expect(u32Values[0]).ToEqual(128); - Expect(u32Values[1]).ToEqual(129); - - u32* ptrValues[2]{(u32*)1, (u32*)2}; // Assign simulated garbage - u32* srcPtrValues[2]{(u32*)34, (u32*)23433}; - CopyConstructItems(ptrValues, 2, srcPtrValues); - Expect(ptrValues[0]).ToEqual((u32*)34); - Expect(ptrValues[1]).ToEqual((u32*)23433); - - ConstructedType constructedValues[2]{{0.234f}, {0.234f}}; // Assign simulated garbage - ConstructedType srcConstructedValues[2]{{1.f}, {2.f}}; - CopyConstructItems(constructedValues, 2, srcConstructedValues); - Expect(constructedValues[0].value).ToEqual(1.f); - Expect(constructedValues[1].value).ToEqual(2.f); - - BoolsType boolsValues[2]{ - {false, true}, - {false, true} - }; // Assign simulated garbage - BoolsType srcBoolsValues[2]{ - {true, false}, - {false, true } - }; - CopyConstructItems(boolsValues, 2, srcBoolsValues); - Expect(boolsValues[0].value1).ToEqual(true); - Expect(boolsValues[0].value2).ToEqual(false); - Expect(boolsValues[1].value1).ToEqual(false); - Expect(boolsValues[1].value2).ToEqual(true); - - CopyType copyValues[2]{5, 6}; // Assign simulated garbage - CopyType srcCopyValues[2]{34, 75}; - CopyConstructItems(copyValues, 2, srcCopyValues); - Expect(copyValues[0].value).ToEqual(34); - Expect(copyValues[1].value).ToEqual(75); - }); - - It("Can move construct", []() - { - bool boolValues[2]{false, false}; // Assign simulated garbage - bool srcBoolValues[2]{true, false}; - MoveConstructItems(boolValues, 2, srcBoolValues); - Expect(boolValues[0]).ToEqual(true); - Expect(boolValues[1]).ToEqual(false); - - u8 u8Values[2]{34, 45}; // Assign simulated garbage - u8 srcU8Values[2]{128, 129}; - MoveConstructItems(u8Values, 2, srcU8Values); - Expect(u8Values[0]).ToEqual(128); - Expect(u8Values[1]).ToEqual(129); - - u32 u32Values[2]{34, 45}; // Assign simulated garbage - u32 srcU32Values[2]{128, 129}; - MoveConstructItems(u32Values, 2, srcU32Values); - Expect(u32Values[0]).ToEqual(128); - Expect(u32Values[1]).ToEqual(129); - - u32* ptrValues[2]{(u32*)1, (u32*)2}; // Assign simulated garbage - u32* srcPtrValues[2]{(u32*)34, (u32*)23433}; - MoveConstructItems(ptrValues, 2, srcPtrValues); - Expect(ptrValues[0]).ToEqual((u32*)34); - Expect(ptrValues[1]).ToEqual((u32*)23433); - - ConstructedType constructedValues[2]{{0.234f}, {0.234f}}; // Assign simulated garbage - ConstructedType srcConstructedTypeValues[2]{{1.f}, {2.f}}; - MoveConstructItems(constructedValues, 2, srcConstructedTypeValues); - Expect(constructedValues[0].value).ToEqual(1.f); - Expect(constructedValues[1].value).ToEqual(2.f); - - BoolsType boolsValues[2]{ - {.value1 = false, .value2 = true}, - {.value1 = false, .value2 = true} - }; // Assign simulated garbage - BoolsType srcConstructedType2Values[2]{ - {.value1 = true, .value2 = false}, - {.value1 = false, .value2 = true } - }; - MoveConstructItems(boolsValues, 2, srcConstructedType2Values); - Expect(boolsValues[0].value1).ToEqual(true); - Expect(boolsValues[0].value2).ToEqual(false); - Expect(boolsValues[1].value1).ToEqual(false); - Expect(boolsValues[1].value2).ToEqual(true); - - MoveType moveValues[2]{5, 6}; // Assign simulated garbage - MoveType srcMoveValues[2]{34, 75}; - MoveConstructItems(moveValues, 2, srcMoveValues); - Expect(moveValues[0].value).ToEqual(34); - Expect(moveValues[1].value).ToEqual(75); - Expect(srcMoveValues[0].value).ToEqual(0); - Expect(srcMoveValues[1].value).ToEqual(0); - }); + bool boolValues[2]{false, false}; // Assign simulated garbage + bool srcBoolValues[2]{true, false}; + MoveConstructItems(boolValues, 2, srcBoolValues); + Expect(boolValues[0]).ToEqual(true); + Expect(boolValues[1]).ToEqual(false); + + u8 u8Values[2]{34, 45}; // Assign simulated garbage + u8 srcU8Values[2]{128, 129}; + MoveConstructItems(u8Values, 2, srcU8Values); + Expect(u8Values[0]).ToEqual(128); + Expect(u8Values[1]).ToEqual(129); + + u32 u32Values[2]{34, 45}; // Assign simulated garbage + u32 srcU32Values[2]{128, 129}; + MoveConstructItems(u32Values, 2, srcU32Values); + Expect(u32Values[0]).ToEqual(128); + Expect(u32Values[1]).ToEqual(129); + + u32* ptrValues[2]{(u32*)1, (u32*)2}; // Assign simulated garbage + u32* srcPtrValues[2]{(u32*)34, (u32*)23433}; + MoveConstructItems(ptrValues, 2, srcPtrValues); + Expect(ptrValues[0]).ToEqual((u32*)34); + Expect(ptrValues[1]).ToEqual((u32*)23433); + + ConstructedType constructedValues[2]{{0.234f}, {0.234f}}; // Assign simulated garbage + ConstructedType srcConstructedTypeValues[2]{{1.f}, {2.f}}; + MoveConstructItems(constructedValues, 2, srcConstructedTypeValues); + Expect(constructedValues[0].value).ToEqual(1.f); + Expect(constructedValues[1].value).ToEqual(2.f); + + BoolsType boolsValues[2]{ + {.value1 = false, .value2 = true}, + {.value1 = false, .value2 = true} + }; // Assign simulated garbage + BoolsType srcConstructedType2Values[2]{ + {.value1 = true, .value2 = false}, + {.value1 = false, .value2 = true } + }; + MoveConstructItems(boolsValues, 2, srcConstructedType2Values); + Expect(boolsValues[0].value1).ToEqual(true); + Expect(boolsValues[0].value2).ToEqual(false); + Expect(boolsValues[1].value1).ToEqual(false); + Expect(boolsValues[1].value2).ToEqual(true); + + MoveType moveValues[2]{5, 6}; // Assign simulated garbage + MoveType srcMoveValues[2]{34, 75}; + MoveConstructItems(moveValues, 2, srcMoveValues); + Expect(moveValues[0].value).ToEqual(34); + Expect(moveValues[1].value).ToEqual(75); + Expect(srcMoveValues[0].value).ToEqual(0); + Expect(srcMoveValues[1].value).ToEqual(0); }); -} +}); +return true; +}(); +} // namespace diff --git a/Tests/Memory/MemoryStats.spec.cpp b/Tests/Memory/MemoryStats.spec.cpp index 2e407aa2..68469bbc 100644 --- a/Tests/Memory/MemoryStats.spec.cpp +++ b/Tests/Memory/MemoryStats.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include #include @@ -22,622 +22,627 @@ static const MemoryStatsEvent* LiveFind(const MemoryStats& s, void* ptr) } -void RegisterMemoryMemoryStatsTests() +namespace { - Spec("Memory.MemoryStats", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("Memory.MemoryStats", []() +{ + Describe("Basic", []() { - Describe("Basic", []() + It("Starts empty", []() { - It("Starts empty", []() - { - MemoryStats s; - s.CollectStats(); - Expect(s.used).ToEqual(0); - Expect(s.totalAllocated).ToEqual(0); - Expect(LiveCount(s)).ToEqual(0); - }); + MemoryStats s; + s.CollectStats(); + Expect(s.used).ToEqual(0); + Expect(s.totalAllocated).ToEqual(0); + Expect(LiveCount(s)).ToEqual(0); + }); - It("Tracks a single add", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 64); - s.CollectStats(); - Expect(s.used).ToEqual(64); - Expect(s.totalAllocated).ToEqual(64); - Expect(LiveCount(s)).ToEqual(1); - Expect(LiveFind(s, (void*)0x1000) != nullptr).ToBeTrue(); - Expect(LiveFind(s, (void*)0x1000)->GetSize()).ToEqual(64); - Expect(LiveFind(s, (void*)0x1000)->IsFree()).ToEqual(false); - }); + It("Tracks a single add", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 64); + s.CollectStats(); + Expect(s.used).ToEqual(64); + Expect(s.totalAllocated).ToEqual(64); + Expect(LiveCount(s)).ToEqual(1); + Expect(LiveFind(s, (void*)0x1000) != nullptr).ToBeTrue(); + Expect(LiveFind(s, (void*)0x1000)->GetSize()).ToEqual(64); + Expect(LiveFind(s, (void*)0x1000)->IsFree()).ToEqual(false); + }); - It("Tracks add plus free", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 64); - s.Remove((void*)0x1000, 64); - s.CollectStats(); - Expect(s.used).ToEqual(0); - Expect(LiveCount(s)).ToEqual(0); - // totalAllocated is cumulative alloc bytes ever. - Expect(s.totalAllocated).ToEqual(64); - }); + It("Tracks add plus free", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 64); + s.Remove((void*)0x1000, 64); + s.CollectStats(); + Expect(s.used).ToEqual(0); + Expect(LiveCount(s)).ToEqual(0); + // totalAllocated is cumulative alloc bytes ever. + Expect(s.totalAllocated).ToEqual(64); + }); - It("Tracks multiple adds", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 16); - s.Add((void*)0x2000, 32); - s.Add((void*)0x3000, 64); - s.CollectStats(); - Expect(s.used).ToEqual(16 + 32 + 64); - Expect(s.totalAllocated).ToEqual(16 + 32 + 64); - Expect(LiveCount(s)).ToEqual(3); - }); + It("Tracks multiple adds", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 16); + s.Add((void*)0x2000, 32); + s.Add((void*)0x3000, 64); + s.CollectStats(); + Expect(s.used).ToEqual(16 + 32 + 64); + Expect(s.totalAllocated).ToEqual(16 + 32 + 64); + Expect(LiveCount(s)).ToEqual(3); + }); + + It("Tracks many adds and frees", []() + { + MemoryStats s; + s.detectLeaks = false; + const sizet N = 100; + TArray buf(N * 16); - It("Tracks many adds and frees", []() + for (sizet i = 0; i < N; ++i) { - MemoryStats s; - s.detectLeaks = false; - const sizet N = 100; - TArray buf(N * 16); + s.Add(&buf[i * 16], 16); + } + for (sizet i = 0; i < N; i += 2) + { + s.Remove(&buf[i * 16], 16); + } + s.CollectStats(); - for (sizet i = 0; i < N; ++i) - { - s.Add(&buf[i * 16], 16); - } - for (sizet i = 0; i < N; i += 2) - { - s.Remove(&buf[i * 16], 16); - } - s.CollectStats(); + Expect(s.used).ToEqual((N / 2) * 16); + Expect(s.totalAllocated).ToEqual(N * 16); + Expect(LiveCount(s)).ToEqual(N / 2); + }); - Expect(s.used).ToEqual((N / 2) * 16); - Expect(s.totalAllocated).ToEqual(N * 16); - Expect(LiveCount(s)).ToEqual(N / 2); - }); + It("Ignores double-free", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 64); + s.Remove((void*)0x1000, 64); + s.Remove((void*)0x1000, 64); + s.CollectStats(); + // The second free matches no live alloc and is ignored. + Expect(s.used).ToEqual(0); + Expect(LiveCount(s)).ToEqual(0); + }); - It("Ignores double-free", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 64); - s.Remove((void*)0x1000, 64); - s.Remove((void*)0x1000, 64); - s.CollectStats(); - // The second free matches no live alloc and is ignored. - Expect(s.used).ToEqual(0); - Expect(LiveCount(s)).ToEqual(0); - }); + It("Ignores free of unknown ptr", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Remove((void*)0xDEAD, 64); + s.CollectStats(); + Expect(s.used).ToEqual(0); + Expect(LiveCount(s)).ToEqual(0); + }); - It("Ignores free of unknown ptr", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Remove((void*)0xDEAD, 64); - s.CollectStats(); - Expect(s.used).ToEqual(0); - Expect(LiveCount(s)).ToEqual(0); - }); + It("Records duplicate allocs as UnfreedRealloc", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 64); + s.Add((void*)0x1000, 128); + s.CollectStats(); + // Same ptr twice: the second alloc is an error and the + // live set is left untouched. + Expect(LiveCount(s)).ToEqual(1); + Expect(LiveFind(s, (void*)0x1000)->GetSize()).ToEqual(64); + Expect(s.errors.Size()).ToEqual(1); + Expect(s.errors[0].kind == MemoryStatsErrorType::UnfreedRealloc).ToBeTrue(); + Expect(s.errors[0].event.GetSize()).ToEqual(128); + Expect(s.used).ToEqual(64); + }); - It("Records duplicate allocs as UnfreedRealloc", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 64); - s.Add((void*)0x1000, 128); - s.CollectStats(); - // Same ptr twice: the second alloc is an error and the - // live set is left untouched. - Expect(LiveCount(s)).ToEqual(1); - Expect(LiveFind(s, (void*)0x1000)->GetSize()).ToEqual(64); - Expect(s.errors.Size()).ToEqual(1); - Expect(s.errors[0].kind == MemoryStatsErrorType::UnfreedRealloc).ToBeTrue(); - Expect(s.errors[0].event.GetSize()).ToEqual(128); - Expect(s.used).ToEqual(64); - }); + It("CheckLeaks always runs when called directly", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 64); + s.CollectStats(); + s.CheckLeaks(); + Expect(LiveCount(s)).ToEqual(1); + Expect(s.used).ToEqual(64); + }); - It("CheckLeaks always runs when called directly", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 64); - s.CollectStats(); - s.CheckLeaks(); - Expect(LiveCount(s)).ToEqual(1); - Expect(s.used).ToEqual(64); - }); + It("Always tracks frees (no trackFrees flag)", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 64); + s.Remove((void*)0x1000, 64); + s.CollectStats(); + Expect(s.used).ToEqual(0); + Expect(LiveCount(s)).ToEqual(0); + }); - It("Always tracks frees (no trackFrees flag)", []() + It("CheckLeaks with null name does not crash", []() + { { + // detectLeaks defaults to true and name defaults to null. MemoryStats s; - s.detectLeaks = false; s.Add((void*)0x1000, 64); - s.Remove((void*)0x1000, 64); s.CollectStats(); - Expect(s.used).ToEqual(0); - Expect(LiveCount(s)).ToEqual(0); - }); + // Destructor runs CheckLeaks with leaks and a null name. + } + }); - It("CheckLeaks with null name does not crash", []() - { - { - // detectLeaks defaults to true and name defaults to null. - MemoryStats s; - s.Add((void*)0x1000, 64); - s.CollectStats(); - // Destructor runs CheckLeaks with leaks and a null name. - } - }); + It("live list only keeps unmatched allocs", []() + { + MemoryStats s; + s.detectLeaks = false; + // allocs: 2 live, 1 matched. frees: 2 (one matches, one stray). + s.Add((void*)0x1000, 64); + s.Add((void*)0x2000, 32); + s.Add((void*)0x3000, 16); + s.Remove((void*)0x3000, 16); + s.Remove((void*)0xDEAD, 16); + s.CollectStats(); + + Expect(LiveCount(s)).ToEqual(2); + Expect(LiveFind(s, (void*)0x1000)->GetSize()).ToEqual(64); + Expect(LiveFind(s, (void*)0x2000)->GetSize()).ToEqual(32); + + // Re-collecting must preserve the live list identically. + s.CollectStats(); + Expect(LiveCount(s)).ToEqual(2); + Expect(s.used).ToEqual(64 + 32); + }); - It("live list only keeps unmatched allocs", []() - { - MemoryStats s; - s.detectLeaks = false; - // allocs: 2 live, 1 matched. frees: 2 (one matches, one stray). - s.Add((void*)0x1000, 64); - s.Add((void*)0x2000, 32); - s.Add((void*)0x3000, 16); - s.Remove((void*)0x3000, 16); - s.Remove((void*)0xDEAD, 16); - s.CollectStats(); + It("Alternating instances on one thread", []() + { + // Exercises thread context reuse when the owner switches. + MemoryStats a; + MemoryStats b; + a.detectLeaks = false; + b.detectLeaks = false; + + a.Add((void*)0x1000, 64); + b.Add((void*)0x2000, 32); + a.Add((void*)0x3000, 16); + b.Remove((void*)0x2000, 32); + + a.CollectStats(); + b.CollectStats(); + + Expect(a.used).ToEqual(64 + 16); + Expect(LiveCount(a)).ToEqual(2); + Expect(b.used).ToEqual(0); + Expect(LiveCount(b)).ToEqual(0); + }); - Expect(LiveCount(s)).ToEqual(2); - Expect(LiveFind(s, (void*)0x1000)->GetSize()).ToEqual(64); - Expect(LiveFind(s, (void*)0x2000)->GetSize()).ToEqual(32); + It("Add after Reset works", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 64); + s.Reset(); + Expect(LiveCount(s)).ToEqual(0); + + s.Add((void*)0x2000, 32); + s.CollectStats(); + Expect(s.used).ToEqual(32); + Expect(s.totalAllocated).ToEqual(32); + Expect(LiveCount(s)).ToEqual(1); + }); - // Re-collecting must preserve the live list identically. - s.CollectStats(); - Expect(LiveCount(s)).ToEqual(2); - Expect(s.used).ToEqual(64 + 32); - }); + It("Duplicate allocs record UnfreedRealloc and live stays usable", []() + { + MemoryStats s; + s.detectLeaks = false; + + // Collect 1: two allocs sharing the same ptr. The second is + // an UnfreedRealloc error; the live set keeps only the first. + s.Add((void*)0x1000, 64); + s.Add((void*)0x1000, 64); + s.CollectStats(); + Expect(LiveCount(s)).ToEqual(1); + Expect(s.errors.Size()).ToEqual(1); + Expect(s.errors[0].kind == MemoryStatsErrorType::UnfreedRealloc).ToBeTrue(); + Expect(s.used).ToEqual(64); + + // Collect 2: freeing the original alloc still works. + s.Remove((void*)0x1000, 64); + s.CollectStats(); + Expect(LiveCount(s)).ToEqual(0); + Expect(s.used).ToEqual(0); + }); - It("Alternating instances on one thread", []() - { - // Exercises thread context reuse when the owner switches. - MemoryStats a; - MemoryStats b; - a.detectLeaks = false; - b.detectLeaks = false; - - a.Add((void*)0x1000, 64); - b.Add((void*)0x2000, 32); - a.Add((void*)0x3000, 16); - b.Remove((void*)0x2000, 32); - - a.CollectStats(); - b.CollectStats(); - - Expect(a.used).ToEqual(64 + 16); - Expect(LiveCount(a)).ToEqual(2); - Expect(b.used).ToEqual(0); - Expect(LiveCount(b)).ToEqual(0); - }); + It("Free with wrong size records SizeMismatch", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 64); + s.Remove((void*)0x1000, 32); // size mismatch + s.CollectStats(); + Expect(LiveCount(s)).ToEqual(1); + Expect(s.errors.Size()).ToEqual(1); + Expect(s.errors[0].kind == MemoryStatsErrorType::SizeMismatch).ToBeTrue(); + Expect(s.errors[0].event.GetSize()).ToEqual(32); + Expect(s.used).ToEqual(64); + + // Correcting the size frees the alloc normally. + s.Remove((void*)0x1000, 64); + s.CollectStats(); + Expect(LiveCount(s)).ToEqual(0); + Expect(s.used).ToEqual(0); + }); - It("Add after Reset works", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 64); - s.Reset(); - Expect(LiveCount(s)).ToEqual(0); + It("Free of unknown ptr records UnknownFree", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 64); + s.Remove((void*)0xDEAD, 64); + s.CollectStats(); + Expect(LiveCount(s)).ToEqual(1); + Expect(s.errors.Size()).ToEqual(1); + Expect(s.errors[0].kind == MemoryStatsErrorType::UnknownFree).ToBeTrue(); + Expect(s.errors[0].event.GetPtr()).ToEqual((u8*)0xDEAD); + Expect(s.used).ToEqual(64); + }); - s.Add((void*)0x2000, 32); - s.CollectStats(); - Expect(s.used).ToEqual(32); - Expect(s.totalAllocated).ToEqual(32); - Expect(LiveCount(s)).ToEqual(1); - }); + It("Ignores null ptr in Remove", []() + { + MemoryStats s; + s.Remove(nullptr, 64); + s.CollectStats(); + Expect(s.used).ToEqual(0); + }); - It("Duplicate allocs record UnfreedRealloc and live stays usable", []() - { - MemoryStats s; - s.detectLeaks = false; + It("Ignores null ptr in Add", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add(nullptr, 64); + s.CollectStats(); + // Add has no null check (unlike Remove), so the event is + // recorded and processed. Add's size is still tracked. + Expect(s.used).ToEqual(64); + Expect(LiveCount(s)).ToEqual(1); + }); - // Collect 1: two allocs sharing the same ptr. The second is - // an UnfreedRealloc error; the live set keeps only the first. - s.Add((void*)0x1000, 64); - s.Add((void*)0x1000, 64); - s.CollectStats(); - Expect(LiveCount(s)).ToEqual(1); - Expect(s.errors.Size()).ToEqual(1); - Expect(s.errors[0].kind == MemoryStatsErrorType::UnfreedRealloc).ToBeTrue(); - Expect(s.used).ToEqual(64); + It("Reset resets state", []() + { + MemoryStats s; + s.Add((void*)0x1000, 64); + s.Add((void*)0x2000, 32); + s.CollectStats(); + Expect(s.used).ToEqual(96); + + s.Reset(); + Expect(s.used).ToEqual(0); + Expect(s.totalAllocated).ToEqual(0); + Expect(LiveCount(s)).ToEqual(0); + }); - // Collect 2: freeing the original alloc still works. - s.Remove((void*)0x1000, 64); - s.CollectStats(); - Expect(LiveCount(s)).ToEqual(0); - Expect(s.used).ToEqual(0); - }); + It("CollectStats is additive", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 64); + s.CollectStats(); + s.Add((void*)0x2000, 32); + s.CollectStats(); + Expect(s.used).ToEqual(96); + Expect(LiveCount(s)).ToEqual(2); + }); - It("Free with wrong size records SizeMismatch", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 64); - s.Remove((void*)0x1000, 32); // size mismatch - s.CollectStats(); - Expect(LiveCount(s)).ToEqual(1); - Expect(s.errors.Size()).ToEqual(1); - Expect(s.errors[0].kind == MemoryStatsErrorType::SizeMismatch).ToBeTrue(); - Expect(s.errors[0].event.GetSize()).ToEqual(32); - Expect(s.used).ToEqual(64); - - // Correcting the size frees the alloc normally. - s.Remove((void*)0x1000, 64); - s.CollectStats(); - Expect(LiveCount(s)).ToEqual(0); - Expect(s.used).ToEqual(0); - }); + It("Re-collecting preserves state", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 64); + s.CollectStats(); + s.CollectStats(); + Expect(s.used).ToEqual(64); + Expect(LiveCount(s)).ToEqual(1); + }); + }); - It("Free of unknown ptr records UnknownFree", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 64); - s.Remove((void*)0xDEAD, 64); - s.CollectStats(); - Expect(LiveCount(s)).ToEqual(1); - Expect(s.errors.Size()).ToEqual(1); - Expect(s.errors[0].kind == MemoryStatsErrorType::UnknownFree).ToBeTrue(); - Expect(s.errors[0].event.GetPtr()).ToEqual((u8*)0xDEAD); - Expect(s.used).ToEqual(64); - }); - It("Ignores null ptr in Remove", []() + Describe("Multiple chunks", []() + { + It("Spans multiple chunks correctly", []() + { + MemoryStats s; + s.detectLeaks = false; + const sizet N = 10000; + TArray buf(N * 8); + for (sizet i = 0; i < N; ++i) { - MemoryStats s; - s.Remove(nullptr, 64); - s.CollectStats(); - Expect(s.used).ToEqual(0); - }); + s.Add(&buf[i * 8], 8); + } + s.CollectStats(); + Expect(s.used).ToEqual(N * 8); + Expect(s.totalAllocated).ToEqual(N * 8); + Expect(LiveCount(s)).ToEqual(N); + }); - It("Ignores null ptr in Add", []() + It("Handles add/free across chunks", []() + { + MemoryStats s; + s.detectLeaks = false; + const sizet N = 5000; + TArray buf(N * 8); + for (sizet i = 0; i < N; ++i) { - MemoryStats s; - s.detectLeaks = false; - s.Add(nullptr, 64); - s.CollectStats(); - // Add has no null check (unlike Remove), so the event is - // recorded and processed. Add's size is still tracked. - Expect(s.used).ToEqual(64); - Expect(LiveCount(s)).ToEqual(1); - }); - - It("Reset resets state", []() + s.Add(&buf[i * 8], 8); + } + for (sizet i = 0; i < N / 2; ++i) { - MemoryStats s; - s.Add((void*)0x1000, 64); - s.Add((void*)0x2000, 32); - s.CollectStats(); - Expect(s.used).ToEqual(96); - - s.Reset(); - Expect(s.used).ToEqual(0); - Expect(s.totalAllocated).ToEqual(0); - Expect(LiveCount(s)).ToEqual(0); - }); + s.Remove(&buf[i * 8], 8); + } + s.CollectStats(); + Expect(s.used).ToEqual((N / 2) * 8); + Expect(s.totalAllocated).ToEqual(N * 8); + Expect(LiveCount(s)).ToEqual(N / 2); + }); - It("CollectStats is additive", []() + It("Frees chunks between CollectStats calls", []() + { + MemoryStats s; + s.detectLeaks = false; + const sizet N = 10000; + TArray buf(N * 8); + for (sizet i = 0; i < N; ++i) { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 64); - s.CollectStats(); - s.Add((void*)0x2000, 32); - s.CollectStats(); - Expect(s.used).ToEqual(96); - Expect(LiveCount(s)).ToEqual(2); - }); - - It("Re-collecting preserves state", []() + s.Add(&buf[i * 8], 8); + } + s.CollectStats(); + Expect(LiveCount(s)).ToEqual(N); + for (sizet i = 0; i < N / 2; ++i) { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 64); - s.CollectStats(); - s.CollectStats(); - Expect(s.used).ToEqual(64); - Expect(LiveCount(s)).ToEqual(1); - }); + s.Remove(&buf[i * 8], 8); + } + s.CollectStats(); + Expect(LiveCount(s)).ToEqual(N / 2); + Expect(s.used).ToEqual((N / 2) * 8); }); + }); - Describe("Multiple chunks", []() + Describe("Multithreading", []() + { + It("One thread adds, another collects", []() { - It("Spans multiple chunks correctly", []() - { - MemoryStats s; - s.detectLeaks = false; - const sizet N = 10000; - TArray buf(N * 8); - for (sizet i = 0; i < N; ++i) - { - s.Add(&buf[i * 8], 8); - } - s.CollectStats(); - Expect(s.used).ToEqual(N * 8); - Expect(s.totalAllocated).ToEqual(N * 8); - Expect(LiveCount(s)).ToEqual(N); - }); + MemoryStats s; + const sizet N = 1000; + TArray buf(N * 8); + std::atomic start{false}; + std::atomic producerDone{false}; - It("Handles add/free across chunks", []() + std::thread producer([&]() { - MemoryStats s; - s.detectLeaks = false; - const sizet N = 5000; - TArray buf(N * 8); + while (!start.load(std::memory_order_acquire)) + {} for (sizet i = 0; i < N; ++i) { s.Add(&buf[i * 8], 8); } - for (sizet i = 0; i < N / 2; ++i) - { - s.Remove(&buf[i * 8], 8); - } - s.CollectStats(); - Expect(s.used).ToEqual((N / 2) * 8); - Expect(s.totalAllocated).ToEqual(N * 8); - Expect(LiveCount(s)).ToEqual(N / 2); + producerDone.store(true, std::memory_order_release); }); - It("Frees chunks between CollectStats calls", []() + std::thread consumer([&]() { - MemoryStats s; - s.detectLeaks = false; - const sizet N = 10000; - TArray buf(N * 8); - for (sizet i = 0; i < N; ++i) - { - s.Add(&buf[i * 8], 8); - } - s.CollectStats(); - Expect(LiveCount(s)).ToEqual(N); - for (sizet i = 0; i < N / 2; ++i) + while (!start.load(std::memory_order_acquire)) + {} + while (!producerDone.load(std::memory_order_acquire) || LiveCount(s) < N) { - s.Remove(&buf[i * 8], 8); + s.CollectStats(); + std::this_thread::yield(); } - s.CollectStats(); - Expect(LiveCount(s)).ToEqual(N / 2); - Expect(s.used).ToEqual((N / 2) * 8); }); - }); - - - Describe("Multithreading", []() - { - It("One thread adds, another collects", []() - { - MemoryStats s; - const sizet N = 1000; - TArray buf(N * 8); - std::atomic start{false}; - std::atomic producerDone{false}; - std::thread producer([&]() - { - while (!start.load(std::memory_order_acquire)) - {} - for (sizet i = 0; i < N; ++i) - { - s.Add(&buf[i * 8], 8); - } - producerDone.store(true, std::memory_order_release); - }); + start.store(true, std::memory_order_release); + producer.join(); + consumer.join(); - std::thread consumer([&]() - { - while (!start.load(std::memory_order_acquire)) - {} - while (!producerDone.load(std::memory_order_acquire) || LiveCount(s) < N) - { - s.CollectStats(); - std::this_thread::yield(); - } - }); - - start.store(true, std::memory_order_release); - producer.join(); - consumer.join(); + Expect(LiveCount(s)).ToEqual(N); + Expect(s.used).ToEqual(N * 8); - Expect(LiveCount(s)).ToEqual(N); - Expect(s.used).ToEqual(N * 8); + // Suppress leak warnings at destruction (test buffers are stack). + s.Reset(); + }); - // Suppress leak warnings at destruction (test buffers are stack). - s.Reset(); - }); + It("Many threads add, then collects", []() + { + MemoryStats s; + const sizet N_PER_THREAD = 1000; + const sizet NUM_THREADS = 4; + const sizet N = N_PER_THREAD * NUM_THREADS; - It("Many threads add, then collects", []() + TArray, 0> buffers; + for (sizet t = 0; t < NUM_THREADS; ++t) { - MemoryStats s; - const sizet N_PER_THREAD = 1000; - const sizet NUM_THREADS = 4; - const sizet N = N_PER_THREAD * NUM_THREADS; + TArray buf(N_PER_THREAD * 8); + buffers.Add(Move(buf)); + } - TArray, 0> buffers; - for (sizet t = 0; t < NUM_THREADS; ++t) - { - TArray buf(N_PER_THREAD * 8); - buffers.Add(Move(buf)); - } - - std::atomic start{false}; - std::atomic producersDone{0}; - std::vector producers; - - for (sizet t = 0; t < NUM_THREADS; ++t) - { - producers.emplace_back([&, t]() - { - while (!start.load(std::memory_order_acquire)) - {} - for (sizet i = 0; i < N_PER_THREAD; ++i) - { - s.Add(&buffers[t][i * 8], 8); - } - producersDone.fetch_add(1, std::memory_order_release); - }); - } + std::atomic start{false}; + std::atomic producersDone{0}; + std::vector producers; - std::thread consumer([&]() + for (sizet t = 0; t < NUM_THREADS; ++t) + { + producers.emplace_back([&, t]() { while (!start.load(std::memory_order_acquire)) {} - while (producersDone.load(std::memory_order_acquire) < NUM_THREADS) + for (sizet i = 0; i < N_PER_THREAD; ++i) { - s.CollectStats(); - std::this_thread::yield(); + s.Add(&buffers[t][i * 8], 8); } - s.CollectStats(); + producersDone.fetch_add(1, std::memory_order_release); }); + } - start.store(true, std::memory_order_release); - for (auto& t : producers) + std::thread consumer([&]() + { + while (!start.load(std::memory_order_acquire)) + {} + while (producersDone.load(std::memory_order_acquire) < NUM_THREADS) { - t.join(); + s.CollectStats(); + std::this_thread::yield(); } - consumer.join(); - - Expect(LiveCount(s)).ToEqual(N); - Expect(s.used).ToEqual(N * 8); - Expect(s.totalAllocated).ToEqual(N * 8); - - // Suppress leak warnings at destruction (test buffers are stack). - s.Reset(); + s.CollectStats(); }); - It("Many threads add and remove, then collects", []() + start.store(true, std::memory_order_release); + for (auto& t : producers) { - MemoryStats s; - const sizet N_PER_THREAD = 1000; - const sizet NUM_THREADS = 4; - const sizet N = N_PER_THREAD * NUM_THREADS; + t.join(); + } + consumer.join(); - TArray, 0> buffers; - for (sizet t = 0; t < NUM_THREADS; ++t) - { - TArray buf(N_PER_THREAD * 8); - buffers.Add(Move(buf)); - } + Expect(LiveCount(s)).ToEqual(N); + Expect(s.used).ToEqual(N * 8); + Expect(s.totalAllocated).ToEqual(N * 8); - std::atomic start{false}; - std::atomic producersDone{0}; - std::vector producers; + // Suppress leak warnings at destruction (test buffers are stack). + s.Reset(); + }); - for (sizet t = 0; t < NUM_THREADS; ++t) - { - producers.emplace_back([&, t]() - { - while (!start.load(std::memory_order_acquire)) - {} - for (sizet i = 0; i < N_PER_THREAD; ++i) - { - s.Add(&buffers[t][i * 8], 8); - } - // Free the first half. - for (sizet i = 0; i < N_PER_THREAD / 2; ++i) - { - s.Remove(&buffers[t][i * 8], 8); - } - producersDone.fetch_add(1, std::memory_order_release); - }); - } + It("Many threads add and remove, then collects", []() + { + MemoryStats s; + const sizet N_PER_THREAD = 1000; + const sizet NUM_THREADS = 4; + const sizet N = N_PER_THREAD * NUM_THREADS; + + TArray, 0> buffers; + for (sizet t = 0; t < NUM_THREADS; ++t) + { + TArray buf(N_PER_THREAD * 8); + buffers.Add(Move(buf)); + } - std::thread consumer([&]() + std::atomic start{false}; + std::atomic producersDone{0}; + std::vector producers; + + for (sizet t = 0; t < NUM_THREADS; ++t) + { + producers.emplace_back([&, t]() { while (!start.load(std::memory_order_acquire)) {} - while (producersDone.load(std::memory_order_acquire) < NUM_THREADS) + for (sizet i = 0; i < N_PER_THREAD; ++i) { - s.CollectStats(); - std::this_thread::yield(); + s.Add(&buffers[t][i * 8], 8); } - s.CollectStats(); + // Free the first half. + for (sizet i = 0; i < N_PER_THREAD / 2; ++i) + { + s.Remove(&buffers[t][i * 8], 8); + } + producersDone.fetch_add(1, std::memory_order_release); }); + } - start.store(true, std::memory_order_release); - for (auto& t : producers) + std::thread consumer([&]() + { + while (!start.load(std::memory_order_acquire)) + {} + while (producersDone.load(std::memory_order_acquire) < NUM_THREADS) { - t.join(); + s.CollectStats(); + std::this_thread::yield(); } - consumer.join(); + s.CollectStats(); + }); - Expect(LiveCount(s)).ToEqual(N / 2); - Expect(s.used).ToEqual((N / 2) * 8); - Expect(s.totalAllocated).ToEqual(N * 8); + start.store(true, std::memory_order_release); + for (auto& t : producers) + { + t.join(); + } + consumer.join(); - // Suppress leak warnings at destruction (test buffers are stack). - s.Reset(); - }); + Expect(LiveCount(s)).ToEqual(N / 2); + Expect(s.used).ToEqual((N / 2) * 8); + Expect(s.totalAllocated).ToEqual(N * 8); + + // Suppress leak warnings at destruction (test buffers are stack). + s.Reset(); }); + }); - Describe("Heavy stress", []() + Describe("Heavy stress", []() + { + It("Many producers, many iterations, no crashes", []() { - It("Many producers, many iterations, no crashes", []() - { - MemoryStats s; - const sizet N_PER_THREAD = 2000; - const sizet NUM_THREADS = 4; - const sizet N = N_PER_THREAD * NUM_THREADS; + MemoryStats s; + const sizet N_PER_THREAD = 2000; + const sizet NUM_THREADS = 4; + const sizet N = N_PER_THREAD * NUM_THREADS; - TArray, 0> buffers; - for (sizet t = 0; t < NUM_THREADS; ++t) - { - TArray buf(N_PER_THREAD * 8); - buffers.Add(Move(buf)); - } + TArray, 0> buffers; + for (sizet t = 0; t < NUM_THREADS; ++t) + { + TArray buf(N_PER_THREAD * 8); + buffers.Add(Move(buf)); + } - std::atomic start{false}; - std::atomic producersDone{0}; - std::vector producers; + std::atomic start{false}; + std::atomic producersDone{0}; + std::vector producers; - for (sizet t = 0; t < NUM_THREADS; ++t) - { - producers.emplace_back([&, t]() - { - while (!start.load(std::memory_order_acquire)) - {} - for (sizet i = 0; i < N_PER_THREAD; ++i) - { - s.Add(&buffers[t][i * 8], 8); - if (i > 0 && i % 3 == 0) - { - s.Remove(&buffers[t][(i - 1) * 8], 8); - } - } - producersDone.fetch_add(1, std::memory_order_release); - }); - } - - std::thread consumer([&]() + for (sizet t = 0; t < NUM_THREADS; ++t) + { + producers.emplace_back([&, t]() { while (!start.load(std::memory_order_acquire)) {} - while (producersDone.load(std::memory_order_acquire) < NUM_THREADS) + for (sizet i = 0; i < N_PER_THREAD; ++i) { - s.CollectStats(); - std::this_thread::yield(); + s.Add(&buffers[t][i * 8], 8); + if (i > 0 && i % 3 == 0) + { + s.Remove(&buffers[t][(i - 1) * 8], 8); + } } - s.CollectStats(); + producersDone.fetch_add(1, std::memory_order_release); }); + } - start.store(true, std::memory_order_release); - for (auto& t : producers) + std::thread consumer([&]() + { + while (!start.load(std::memory_order_acquire)) + {} + while (producersDone.load(std::memory_order_acquire) < NUM_THREADS) { - t.join(); + s.CollectStats(); + std::this_thread::yield(); } - consumer.join(); + s.CollectStats(); + }); - // s.used reflects the net remaining live set. - Expect(s.used).ToEqual(LiveCount(s) * 8); + start.store(true, std::memory_order_release); + for (auto& t : producers) + { + t.join(); + } + consumer.join(); - // Suppress leak warnings at destruction (test buffers are stack). - s.Reset(); - }); + // s.used reflects the net remaining live set. + Expect(s.used).ToEqual(LiveCount(s) * 8); + + // Suppress leak warnings at destruction (test buffers are stack). + s.Reset(); }); }); -} +}); +return true; +}(); +} // namespace diff --git a/Tests/Memory/MonoLinearArena.spec.cpp b/Tests/Memory/MonoLinearArena.spec.cpp index 73c1d6bf..b891b5f9 100644 --- a/Tests/Memory/MonoLinearArena.spec.cpp +++ b/Tests/Memory/MonoLinearArena.spec.cpp @@ -1,145 +1,150 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include using namespace p; -void RegisterMemoryMonoLinearArenaTests() +namespace { - Spec("Memory.MonoLinearArena", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("Memory.MonoLinearArena", []() +{ + It("Reserves a block on construction", []() + { + MonoLinearArena arena{1024}; + + Expect(arena.GetAvailableMemory()).ToEqual(1024); + arena.GetStats()->CollectStats(); + Expect(arena.GetStats()->used).ToEqual(0); + }); + + It("Can allocate outside the block", []() + { + MonoLinearArena arena{256}; + + Expect(arena.GetAvailableMemory()).ToEqual(256); + void* p = arena.Alloc(512); + Expect(arena.GetAvailableMemory()).ToEqual(256); + arena.Free(p, 512); + }); + + It("Can free from outside the block", []() + { + MonoLinearArena arena{256}; + + void* p = arena.Alloc(512); + Expect(arena.GetAvailableMemory()).ToEqual(256); + arena.Free(p, 512); + Expect(arena.GetAvailableMemory()).ToEqual(256); + }); + + It("Can free active block", []() + { + MonoLinearArena arena{1024}; + arena.Release(); + + TArray blocks; + arena.GetBlocks(blocks); + Expect(blocks.Size()).ToEqual(1); + }); + + It("Can allocate", []() + { + MonoLinearArena arena{1024}; + void* p = arena.Alloc(sizeof(float)); + Expect(p).ToNotEqual(nullptr); + arena.GetStats()->CollectStats(); + Expect(arena.GetStats()->used).ToEqual(4); + Expect(arena.GetAvailableMemory()).ToEqual(1024); + arena.Free(p, sizeof(float)); + }); + + It("Can allocate with alignment", []() + { + MonoLinearArena arena{1024}; + + void* p0 = arena.Alloc(sizeof(bool)); + + // When padding is not 0 (last ptr is not aligned) + void* p1 = arena.Alloc(sizeof(float), 8); + Expect(p::GetAlignmentPadding(p1, 8)).ToEqual(0); + + // When padding is 0 (last ptr is aligned) + void* p2 = arena.Alloc(sizeof(float), 16); + Expect(p::GetAlignmentPadding(p2, 16)).ToEqual(0); + + arena.Free(p0, sizeof(bool)); + arena.Free(p1, sizeof(float)); + arena.Free(p2, sizeof(float)); + }); + + It("Can allocate after release", []() { - It("Reserves a block on construction", []() - { - MonoLinearArena arena{1024}; - - Expect(arena.GetAvailableMemory()).ToEqual(1024); - arena.GetStats()->CollectStats(); - Expect(arena.GetStats()->used).ToEqual(0); - }); - - It("Can allocate outside the block", []() - { - MonoLinearArena arena{256}; - - Expect(arena.GetAvailableMemory()).ToEqual(256); - void* p = arena.Alloc(512); - Expect(arena.GetAvailableMemory()).ToEqual(256); - arena.Free(p, 512); - }); - - It("Can free from outside the block", []() - { - MonoLinearArena arena{256}; - - void* p = arena.Alloc(512); - Expect(arena.GetAvailableMemory()).ToEqual(256); - arena.Free(p, 512); - Expect(arena.GetAvailableMemory()).ToEqual(256); - }); - - It("Can free active block", []() - { - MonoLinearArena arena{1024}; - arena.Release(); - - TArray blocks; - arena.GetBlocks(blocks); - Expect(blocks.Size()).ToEqual(1); - }); - - It("Can allocate", []() - { - MonoLinearArena arena{1024}; - void* p = arena.Alloc(sizeof(float)); - Expect(p).ToNotEqual(nullptr); - arena.GetStats()->CollectStats(); - Expect(arena.GetStats()->used).ToEqual(4); - Expect(arena.GetAvailableMemory()).ToEqual(1024); - arena.Free(p, sizeof(float)); - }); - - It("Can allocate with alignment", []() - { - MonoLinearArena arena{1024}; - - void* p0 = arena.Alloc(sizeof(bool)); - - // When padding is not 0 (last ptr is not aligned) - void* p1 = arena.Alloc(sizeof(float), 8); - Expect(p::GetAlignmentPadding(p1, 8)).ToEqual(0); - - // When padding is 0 (last ptr is aligned) - void* p2 = arena.Alloc(sizeof(float), 16); - Expect(p::GetAlignmentPadding(p2, 16)).ToEqual(0); - - arena.Free(p0, sizeof(bool)); - arena.Free(p1, sizeof(float)); - arena.Free(p2, sizeof(float)); - }); - - It("Can allocate after release", []() - { - MonoLinearArena arena{1024}; - arena.Release(); - void* p = arena.Alloc(sizeof(float)); - Expect(p).ToNotEqual(nullptr); - arena.GetStats()->CollectStats(); - Expect(arena.GetStats()->used).ToEqual(4); - // Buffer size will be as small as the type (4 bytes) - Expect(arena.GetAvailableMemory()).ToEqual(1024); - - arena.Free(p, sizeof(float)); - }); - - It("Can free block after Free", []() - { - MonoLinearArena arena{1024}; - void* p = arena.Alloc(256); - arena.GetStats()->CollectStats(); - Expect(arena.GetStats()->used).ToEqual(256); - arena.Free(p, 256); - arena.GetStats()->CollectStats(); - Expect(arena.GetStats()->used).ToEqual(0); - }); - - It("Allocates at correct addresses", []() - { - MonoLinearArena arena{1024}; - - TArray blocks; - arena.GetBlocks(blocks); - - void* p1 = arena.Alloc(sizeof(float)); - Expect(p1).ToEqual(blocks[0].data); - void* p2 = arena.Alloc(sizeof(float), alignof(float)); - Expect(p2).ToEqual((u8*)blocks[0].data + 4); - - arena.Free(p1, sizeof(float)); - arena.Free(p2, sizeof(float)); - }); - - // Move test to Multi linear - /*It("Allocated new blocks when previous is filled", []() { - MonoLinearArena arena{16}; - - void* p = arena.Alloc(sizeof(float*)); // 8 bytes - arena.Alloc(sizeof(float)); // 4 bytes - Expect(arena.GetStats()->used).ToEqual(12); - Expect(arena.GetAvailableMemory()).ToEqual(16); - - void* p3 = arena.Alloc(sizeof(float*)); // 8 bytes - TArray blocks; - arena.GetBlocks(blocks); - Expect(blocks.Size()).ToEqual(2); - Expect(blocks[0]).ToNotEqual(blocks[1]); - Expect(p).ToEqual(blocks[0].data); - Expect(p3).ToEqual(blocks[1].data); - - Expect(arena.GetStats()->used).ToEqual(8); - Expect(arena.GetAvailableMemory()).ToEqual(16); - });*/ + MonoLinearArena arena{1024}; + arena.Release(); + void* p = arena.Alloc(sizeof(float)); + Expect(p).ToNotEqual(nullptr); + arena.GetStats()->CollectStats(); + Expect(arena.GetStats()->used).ToEqual(4); + // Buffer size will be as small as the type (4 bytes) + Expect(arena.GetAvailableMemory()).ToEqual(1024); + + arena.Free(p, sizeof(float)); }); -} + + It("Can free block after Free", []() + { + MonoLinearArena arena{1024}; + void* p = arena.Alloc(256); + arena.GetStats()->CollectStats(); + Expect(arena.GetStats()->used).ToEqual(256); + arena.Free(p, 256); + arena.GetStats()->CollectStats(); + Expect(arena.GetStats()->used).ToEqual(0); + }); + + It("Allocates at correct addresses", []() + { + MonoLinearArena arena{1024}; + + TArray blocks; + arena.GetBlocks(blocks); + + void* p1 = arena.Alloc(sizeof(float)); + Expect(p1).ToEqual(blocks[0].data); + void* p2 = arena.Alloc(sizeof(float), alignof(float)); + Expect(p2).ToEqual((u8*)blocks[0].data + 4); + + arena.Free(p1, sizeof(float)); + arena.Free(p2, sizeof(float)); + }); + + // Move test to Multi linear + /*It("Allocated new blocks when previous is filled", []() { + MonoLinearArena arena{16}; + + void* p = arena.Alloc(sizeof(float*)); // 8 bytes + arena.Alloc(sizeof(float)); // 4 bytes + Expect(arena.GetStats()->used).ToEqual(12); + Expect(arena.GetAvailableMemory()).ToEqual(16); + + void* p3 = arena.Alloc(sizeof(float*)); // 8 bytes + TArray blocks; + arena.GetBlocks(blocks); + Expect(blocks.Size()).ToEqual(2); + Expect(blocks[0]).ToNotEqual(blocks[1]); + Expect(p).ToEqual(blocks[0].data); + Expect(p3).ToEqual(blocks[1].data); + + Expect(arena.GetStats()->used).ToEqual(8); + Expect(arena.GetAvailableMemory()).ToEqual(16); + });*/ +}); +return true; +}(); +} // namespace diff --git a/Tests/PipeTests/CMakeLists.txt b/Tests/PipeTests/CMakeLists.txt index 951d76a9..3f1ef591 100644 --- a/Tests/PipeTests/CMakeLists.txt +++ b/Tests/PipeTests/CMakeLists.txt @@ -4,5 +4,5 @@ pipe_target_define_platform(PipeTestsSelf) pipe_target_enable_CPP20(PipeTestsSelf) pipe_target_disable_rtti(PipeTestsSelf PRIVATE) pipe_target_shared_output_directory(PipeTestsSelf) -target_link_libraries(PipeTestsSelf PUBLIC PipeTests Pipe) +target_link_libraries(PipeTestsSelf PUBLIC PipeTest Pipe) add_test(NAME PipeTestsSelf COMMAND $) diff --git a/Tests/PipeTests/PipeTests.spec.cpp b/Tests/PipeTests/PipeTests.spec.cpp index ce19f3ec..526aa28f 100644 --- a/Tests/PipeTests/PipeTests.spec.cpp +++ b/Tests/PipeTests/PipeTests.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include #include @@ -12,62 +12,67 @@ static int afterEachCount = 0; static int topTestResult = 0; -void RegisterPipeTests() +namespace { - Spec("PipeTests", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("PipeTests", []() +{ + BeforeEach([]() + { + ++beforeEachCount; + }); + AfterEach([]() { - BeforeEach([]() + ++afterEachCount; + }); + + Describe("Basics", []() + { + It("Registers and runs", []() { - ++beforeEachCount; + topTestResult = 42; }); - AfterEach([]() + XIt("Is skipped", []() { - ++afterEachCount; + topTestResult = -1; }); + }); - Describe("Basics", []() + Describe("Expect", []() + { + It("ToEqual / ToNotEqual", []() { - It("Registers and runs", []() - { - topTestResult = 42; - }); - XIt("Is skipped", []() - { - topTestResult = -1; - }); + int value = 4; + Expect(value).ToEqual(4); + Expect(value).ToNotEqual(5); }); - - Describe("Expect", []() + It("Relational", []() + { + int value = 4; + Expect(value).ToBeLess(5); + Expect(value).ToBeLessOrEqual(4); + Expect(value).ToBeGreater(3); + Expect(value).ToBeGreaterOrEqual(4); + }); + It("Booleans", []() + { + bool flag = true; + Expect(flag).ToBeTrue(); + Expect(!flag).ToBeFalse(); + }); + It("Strings", []() + { + Expect("acidic").ToContain("acid"); + Expect(String{"hello"}).ToNotContain("world"); + }); + It("Equals int", []() { - It("ToEqual / ToNotEqual", []() - { - int value = 4; - Expect(value).ToEqual(4); - Expect(value).ToNotEqual(5); - }); - It("Relational", []() - { - int value = 4; - Expect(value).ToBeLess(5); - Expect(value).ToBeLessOrEqual(4); - Expect(value).ToBeGreater(3); - Expect(value).ToBeGreaterOrEqual(4); - }); - It("Booleans", []() - { - bool flag = true; - Expect(flag).ToBeTrue(); - Expect(!flag).ToBeFalse(); - }); - It("Strings", []() - { - Expect("acidic").ToContain("acid"); - Expect(String{"hello"}).ToNotContain("world"); - }); - It("Equals int", []() - { - Expect(4).ToEqual(4); - }); + Expect(4).ToEqual(4); }); }); -} \ No newline at end of file +}); +return true; +}(); +} // namespace diff --git a/Tests/PipeTests/main.cpp b/Tests/PipeTests/main.cpp index e6ca8cf8..c3e8713e 100644 --- a/Tests/PipeTests/main.cpp +++ b/Tests/PipeTests/main.cpp @@ -1,20 +1,16 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -// NOTE: PipeNewDelete is deliberately not included here. PipeTests provides the +// NOTE: PipeNewDelete is deliberately not included here. PipeTest provides the // replacement operator new/delete (P_OVERRIDE_NEWDELETE) in its own translation unit; // including it here too would cause duplicate-definition linker errors. #include -#include - - -void RegisterPipeTests(); +#include int main(int argc, char* argv[]) { p::Initialize(); - RegisterPipeTests(); int result = p::RunTests(argc, argv); p::Shutdown(); return result; diff --git a/Tests/Reflection/MacroReflection.spec.cpp b/Tests/Reflection/MacroReflection.spec.cpp index b6f8c583..76591804 100644 --- a/Tests/Reflection/MacroReflection.spec.cpp +++ b/Tests/Reflection/MacroReflection.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include #include @@ -20,23 +20,28 @@ struct TestStruct }; -void RegisterReflectionMacroReflectionTests() +namespace { - Spec("Reflection.Macros", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("Reflection.Macros", []() +{ + It("Can get property names", []() { - It("Can get property names", []() - { - p::TypeId testStructType = p::RegisterTypeId(); + p::TypeId testStructType = p::RegisterTypeId(); - Expect(p::HasTypeFlags(testStructType, p::TF_Struct)).ToEqual(true); + Expect(p::HasTypeFlags(testStructType, p::TF_Struct)).ToEqual(true); - auto properties = p::GetTypeProperties(testStructType); - Expect(properties.Size()).ToEqual(2); + auto properties = p::GetTypeProperties(testStructType); + Expect(properties.Size()).ToEqual(2); - // Expect(properties[0].typeId).ToEqual(p::GetTypeId>()); - Expect(properties[0]->name.Data()).ToEqual("value0"); - // Expect(properties[1].typeId).ToEqual(p::GetTypeId()); - Expect(properties[1]->name.Data()).ToEqual("value1"); - }); + // Expect(properties[0].typeId).ToEqual(p::GetTypeId>()); + Expect(properties[0]->name.Data()).ToEqual("value0"); + // Expect(properties[1].typeId).ToEqual(p::GetTypeId()); + Expect(properties[1]->name.Data()).ToEqual("value1"); }); -} +}); +return true; +}(); +} // namespace diff --git a/Tests/Reflection/Object.spec.cpp b/Tests/Reflection/Object.spec.cpp index 380a382a..b893c741 100644 --- a/Tests/Reflection/Object.spec.cpp +++ b/Tests/Reflection/Object.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include @@ -22,28 +22,33 @@ class TestObject : public p::Object }; -void RegisterReflectionObjectTests() +namespace { - Spec("Reflection.Object", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("Reflection.Object", []() +{ + Describe("Pointers", []() { - Describe("Pointers", []() + It("Can create object", []() { - It("Can create object", []() - { - auto owner = p::MakeOwned(); - - Expect(owner.Get()).ToNotEqual(nullptr); - Expect(owner->bConstructed).ToEqual(true); - }); - - It("Can create object with owner", []() - { - auto owner = p::MakeOwned(); - auto owner2 = p::MakeOwned(owner); - - Expect(owner2->bConstructed).ToEqual(true); - Expect(owner2->GetOwner().Get()).ToEqual(owner.Get()); - }); + auto owner = p::MakeOwned(); + + Expect(owner.Get()).ToNotEqual(nullptr); + Expect(owner->bConstructed).ToEqual(true); + }); + + It("Can create object with owner", []() + { + auto owner = p::MakeOwned(); + auto owner2 = p::MakeOwned(owner); + + Expect(owner2->bConstructed).ToEqual(true); + Expect(owner2->GetOwner().Get()).ToEqual(owner.Get()); }); }); -} +}); +return true; +}(); +} // namespace diff --git a/Tests/Reflection/Traits.spec.cpp b/Tests/Reflection/Traits.spec.cpp index 33878915..60d8d4d1 100644 --- a/Tests/Reflection/Traits.spec.cpp +++ b/Tests/Reflection/Traits.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include #include #include @@ -39,67 +39,72 @@ namespace p } // namespace p -void RegisterReflectionTraitsTests() +namespace { - Spec("Reflection.Traits", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("Reflection.Traits", []() +{ + Describe("Read/Write properties", []() { - Describe("Read/Write properties", []() + It("Can check for read properties", []() { - It("Can check for read properties", []() - { - Expect(p::HasReadProperties()).ToBeFalse(); - Expect(p::HasReadProperties()).ToBeTrue(); - Expect(p::Readable).ToBeFalse(); - Expect(p::Readable).ToBeTrue(); - }); - - It("Can check for write properties", []() - { - Expect(p::HasWriteProperties()).ToBeFalse(); - Expect(p::HasWriteProperties()).ToBeTrue(); - Expect(p::Writable).ToBeFalse(); - Expect(p::Writable).ToBeTrue(); - }); + Expect(p::HasReadProperties()).ToBeFalse(); + Expect(p::HasReadProperties()).ToBeTrue(); + Expect(p::Readable).ToBeFalse(); + Expect(p::Readable).ToBeTrue(); }); - Describe("Read/Write external", []() + It("Can check for write properties", []() { - It("Can check for read properties", []() - { - Expect(p::Readable).ToBeFalse(); - Expect(p::Readable).ToBeTrue(); - }); - - It("Can check for write properties", []() - { - Expect(p::Writable).ToBeFalse(); - Expect(p::Writable).ToBeTrue(); - }); + Expect(p::HasWriteProperties()).ToBeFalse(); + Expect(p::HasWriteProperties()).ToBeTrue(); + Expect(p::Writable).ToBeFalse(); + Expect(p::Writable).ToBeTrue(); }); + }); - Describe("Read/Write external in namespace", []() + Describe("Read/Write external", []() + { + It("Can check for read properties", []() { - It("Can check for read properties", []() - { - Expect(p::Readable).ToBeTrue(); - }); - - It("Can check for write properties", []() - { - Expect(p::Writable).ToBeTrue(); - }); + Expect(p::Readable).ToBeFalse(); + Expect(p::Readable).ToBeTrue(); }); - It("Can check super", []() + It("Can check for write properties", []() { - Expect(p::HasSuper()).ToBeFalse(); - Expect(p::HasSuper()).ToBeTrue(); + Expect(p::Writable).ToBeFalse(); + Expect(p::Writable).ToBeTrue(); }); + }); - It("Can build type on Arrays", []() + Describe("Read/Write external in namespace", []() + { + It("Can check for read properties", []() { - Expect(p::CanBuildType>()).ToBeTrue(); - Expect(p::HasExternalBuildType>()).ToBeTrue(); + Expect(p::Readable).ToBeTrue(); }); + + It("Can check for write properties", []() + { + Expect(p::Writable).ToBeTrue(); + }); + }); + + It("Can check super", []() + { + Expect(p::HasSuper()).ToBeFalse(); + Expect(p::HasSuper()).ToBeTrue(); + }); + + It("Can build type on Arrays", []() + { + Expect(p::CanBuildType>()).ToBeTrue(); + Expect(p::HasExternalBuildType>()).ToBeTrue(); }); -} +}); +return true; +}(); +} // namespace diff --git a/Tests/Reflection/TypeId.spec.cpp b/Tests/Reflection/TypeId.spec.cpp index aa86f217..e3dde3b6 100644 --- a/Tests/Reflection/TypeId.spec.cpp +++ b/Tests/Reflection/TypeId.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include @@ -10,33 +10,38 @@ struct One {}; -void RegisterReflectionTypeIdTests() +namespace { - Spec("Reflection.TypeId", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("Reflection.TypeId", []() +{ + It("Ids can be valid and invalid", []() { - It("Ids can be valid and invalid", []() - { - static constexpr TypeId id = GetTypeId(); - Expect(id.IsValid()).ToEqual(true); + static constexpr TypeId id = GetTypeId(); + Expect(id.IsValid()).ToEqual(true); - static constexpr TypeId noId{}; - Expect(noId.IsValid()).ToEqual(false); - }); + static constexpr TypeId noId{}; + Expect(noId.IsValid()).ToEqual(false); + }); - It("Different types don't share an id", []() - { - static constexpr TypeId ids[]{ - GetTypeId(), GetTypeId(), GetTypeId(), GetTypeId()}; - static constexpr u32 numIds = sizeof(ids) / sizeof(TypeId); + It("Different types don't share an id", []() + { + static constexpr TypeId ids[]{ + GetTypeId(), GetTypeId(), GetTypeId(), GetTypeId()}; + static constexpr u32 numIds = sizeof(ids) / sizeof(TypeId); - // Check that no id matches the other - for (u32 i = 0; i < numIds; ++i) + // Check that no id matches the other + for (u32 i = 0; i < numIds; ++i) + { + for (u32 e = i + 1; e < numIds; ++e) { - for (u32 e = i + 1; e < numIds; ++e) - { - Expect(ids[i]).ToNotEqual(ids[e]); - } + Expect(ids[i]).ToNotEqual(ids[e]); } - }); + } }); -} +}); +return true; +}(); +} // namespace diff --git a/Tests/Reflection/TypeName.spec.cpp b/Tests/Reflection/TypeName.spec.cpp index 93081f25..27545896 100644 --- a/Tests/Reflection/TypeName.spec.cpp +++ b/Tests/Reflection/TypeName.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include #include #include @@ -25,70 +25,75 @@ namespace Space } // namespace Space -void RegisterReflectionTypeNameTests() +namespace { - Spec("Reflection.TypeName", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("Reflection.TypeName", []() +{ + It("Can get Platform type names", []() { - It("Can get Platform type names", []() - { - Expect(GetTypeName()).ToEqual("u8"); - Expect(GetTypeName()).ToEqual("u16"); - Expect(GetTypeName()).ToEqual("u32"); - Expect(GetTypeName()).ToEqual("u64"); - Expect(GetTypeName()).ToEqual("i8"); - Expect(GetTypeName()).ToEqual("i16"); - Expect(GetTypeName()).ToEqual("i32"); - Expect(GetTypeName()).ToEqual("i64"); - Expect(GetTypeName()).ToEqual("char"); - Expect(GetTypeName()).ToEqual("StringView"); - Expect(GetTypeName()).ToEqual("String"); - }); + Expect(GetTypeName()).ToEqual("u8"); + Expect(GetTypeName()).ToEqual("u16"); + Expect(GetTypeName()).ToEqual("u32"); + Expect(GetTypeName()).ToEqual("u64"); + Expect(GetTypeName()).ToEqual("i8"); + Expect(GetTypeName()).ToEqual("i16"); + Expect(GetTypeName()).ToEqual("i32"); + Expect(GetTypeName()).ToEqual("i64"); + Expect(GetTypeName()).ToEqual("char"); + Expect(GetTypeName()).ToEqual("StringView"); + Expect(GetTypeName()).ToEqual("String"); + }); - It("Can get Native type names", []() - { - Expect(GetTypeName()).ToEqual("bool"); - Expect(GetTypeName()).ToEqual("float"); - Expect(GetTypeName()).ToEqual("double"); - }); + It("Can get Native type names", []() + { + Expect(GetTypeName()).ToEqual("bool"); + Expect(GetTypeName()).ToEqual("float"); + Expect(GetTypeName()).ToEqual("double"); + }); - It("Can get Class names", []() - { - Expect(GetTypeName()).ToEqual("AClass"); - }); + It("Can get Class names", []() + { + Expect(GetTypeName()).ToEqual("AClass"); + }); - It("Can get Struct names", []() - { - Expect(GetTypeName()).ToEqual("AnStruct"); - }); + It("Can get Struct names", []() + { + Expect(GetTypeName()).ToEqual("AnStruct"); + }); + + It("Can get names with namespaces", []() + { + Expect(GetTypeName()).ToEqual("Space::Other"); + }); - It("Can get names with namespaces", []() + Describe("Containers", []() + { + It("Can get TArray names", []() { - Expect(GetTypeName()).ToEqual("Space::Other"); + Expect(GetTypeName>()).ToEqual("TArray"); + Expect(GetFullTypeName>()).ToEqual("TArray"); + Expect(GetFullTypeName>(false)).ToEqual("TArray"); }); - Describe("Containers", []() + It("Can get TMap names", []() { - It("Can get TArray names", []() - { - Expect(GetTypeName>()).ToEqual("TArray"); - Expect(GetFullTypeName>()).ToEqual("TArray"); - Expect(GetFullTypeName>(false)).ToEqual("TArray"); - }); - - It("Can get TMap names", []() - { - auto name = GetTypeName>(); - Expect(name).ToEqual("TMap"); - - auto fullName = GetFullTypeName>(); - Expect(fullName).ToEqual("TMap"); - - - auto namespaceName = GetFullTypeName>(); - Expect(namespaceName).ToEqual("TMap"); - auto noNamespaceName = GetFullTypeName>(false); - Expect(noNamespaceName).ToEqual("TMap"); - }); + auto name = GetTypeName>(); + Expect(name).ToEqual("TMap"); + + auto fullName = GetFullTypeName>(); + Expect(fullName).ToEqual("TMap"); + + + auto namespaceName = GetFullTypeName>(); + Expect(namespaceName).ToEqual("TMap"); + auto noNamespaceName = GetFullTypeName>(false); + Expect(noNamespaceName).ToEqual("TMap"); }); }); -} +}); +return true; +}(); +} // namespace diff --git a/Tests/Serialization/Binary.spec.cpp b/Tests/Serialization/Binary.spec.cpp index 826546ef..477e1dd8 100644 --- a/Tests/Serialization/Binary.spec.cpp +++ b/Tests/Serialization/Binary.spec.cpp @@ -1,418 +1,423 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include using namespace p; -void RegisterSerializationBinaryTests() +namespace { - Spec("Serialization.Binary", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("Serialization.Binary", []() +{ + Describe("Reader", []() { - Describe("Reader", []() + It("Can create a reader", []() + { + BinaryFormatReader reader{TArray{}}; + Expect(reader.IsValid()).ToEqual(false); + + BinaryFormatReader reader2{TArray{255}}; + Expect(reader2.IsValid()).ToEqual(true); + }); + + It("Can read from object value", []() + { + TArray data{255}; + BinaryFormatReader reader{data}; + Reader ct = reader; + ct.BeginObject(); + u8 value = 0; + ct.Next(value); + Expect(value).ToEqual(255); + }); + + It("Can read from array values", []() + { + TArray data{1, 0, 0, 0, 255}; + BinaryFormatReader reader{data}; + Reader ct = reader; + u32 size = 0; + ct.BeginArray(size); + Expect(size).ToEqual(1); + u8 value = 0; + ct.Next(value); + Expect(value).ToEqual(255); + }); + + It("Can iterate arrays", []() { - It("Can create a reader", []() + TArray data{2, 0, 0, 0, // Array size of 2 + 6, 0, 0, 0, // size 6 + 'M', 'i', 'g', 'u', 'e', 'l', // + 4, 0, 0, 0, // size 4 + 'J', 'u', 'a', 'n'}; + BinaryFormatReader reader{data}; + + Reader& ct = reader; + ct.BeginObject(); + if (ct.EnterNext("players")) { - BinaryFormatReader reader{TArray{}}; - Expect(reader.IsValid()).ToEqual(false); + static const StringView expected[]{"Miguel", "Juan"}; + u32 size; + ct.BeginArray(size); + for (u32 i = 0; i < size; ++i) + { + StringView name; + ct.Next(name); + Expect(name).ToEqual(expected[i]); + } + ct.Leave(); + } + }); - BinaryFormatReader reader2{TArray{255}}; - Expect(reader2.IsValid()).ToEqual(true); + Describe("Types", []() + { + It("Can read bool values", []() + { + TArray data{1, 0}; + BinaryFormatReader reader{data}; + Reader& ct = reader; + ct.BeginObject(); + bool value = false; + ct.Next("a", value); + Expect(value).ToEqual(true); + ct.Next("b", value); + Expect(value).ToEqual(false); }); - It("Can read from object value", []() + It("Can read i8 values", []() { - TArray data{255}; + TArray data{0, 127, 128}; BinaryFormatReader reader{data}; - Reader ct = reader; + Reader& ct = reader; + ct.BeginObject(); + i8 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(0); + ct.Next("b", value); + Expect(value).ToEqual(127); + ct.Next("b", value); + Expect(value).ToEqual(-128); + }); + + It("Can read u8 values", []() + { + TArray data{0, 255}; + BinaryFormatReader reader{data}; + Reader& ct = reader; ct.BeginObject(); u8 value = 0; - ct.Next(value); + ct.Next("a", value); + Expect(value).ToEqual(0); + ct.Next("b", value); Expect(value).ToEqual(255); }); - It("Can read from array values", []() + It("Can read i16 values", []() { - TArray data{1, 0, 0, 0, 255}; + // Test inbounds and out of bounds values + TArray data{0, 0, 0, 128, 255, 127}; BinaryFormatReader reader{data}; Reader ct = reader; - u32 size = 0; - ct.BeginArray(size); - Expect(size).ToEqual(1); - u8 value = 0; - ct.Next(value); - Expect(value).ToEqual(255); + ct.BeginObject(); + i16 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(0); + ct.Next("b", value); + Expect(value).ToEqual(Limits::Lowest()); + ct.Next("c", value); + Expect(value).ToEqual(Limits::Max()); }); - It("Can iterate arrays", []() + It("Can read u16 values", []() { - TArray data{2, 0, 0, 0, // Array size of 2 - 6, 0, 0, 0, // size 6 - 'M', 'i', 'g', 'u', 'e', 'l', // - 4, 0, 0, 0, // size 4 - 'J', 'u', 'a', 'n'}; + // Test inbounds and out of bounds values + TArray data{0, 0, 255, 255}; BinaryFormatReader reader{data}; + Reader ct = reader; + ct.BeginObject(); + u16 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(0); + ct.Next("b", value); + Expect(value).ToEqual(Limits::Max()); + }); - Reader& ct = reader; + It("Can read i32 values", []() + { + // Test inbounds and out of bounds values + TArray data{0, 0, 0, 0, 0, 0, 0, 128, 255, 255, 255, 127}; + BinaryFormatReader reader{data}; + Reader ct = reader; ct.BeginObject(); - if (ct.EnterNext("players")) - { - static const StringView expected[]{"Miguel", "Juan"}; - u32 size; - ct.BeginArray(size); - for (u32 i = 0; i < size; ++i) - { - StringView name; - ct.Next(name); - Expect(name).ToEqual(expected[i]); - } - ct.Leave(); - } + i32 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(0); + ct.Next("b", value); + Expect(value).ToEqual(Limits::Lowest()); + ct.Next("c", value); + Expect(value).ToEqual(Limits::Max()); }); - Describe("Types", []() + It("Can read u32 values", []() { - It("Can read bool values", []() - { - TArray data{1, 0}; - BinaryFormatReader reader{data}; - Reader& ct = reader; - ct.BeginObject(); - bool value = false; - ct.Next("a", value); - Expect(value).ToEqual(true); - ct.Next("b", value); - Expect(value).ToEqual(false); - }); - - It("Can read i8 values", []() - { - TArray data{0, 127, 128}; - BinaryFormatReader reader{data}; - Reader& ct = reader; - ct.BeginObject(); - i8 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(0); - ct.Next("b", value); - Expect(value).ToEqual(127); - ct.Next("b", value); - Expect(value).ToEqual(-128); - }); - - It("Can read u8 values", []() - { - TArray data{0, 255}; - BinaryFormatReader reader{data}; - Reader& ct = reader; - ct.BeginObject(); - u8 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(0); - ct.Next("b", value); - Expect(value).ToEqual(255); - }); - - It("Can read i16 values", []() - { - // Test inbounds and out of bounds values - TArray data{0, 0, 0, 128, 255, 127}; - BinaryFormatReader reader{data}; - Reader ct = reader; - ct.BeginObject(); - i16 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(0); - ct.Next("b", value); - Expect(value).ToEqual(Limits::Lowest()); - ct.Next("c", value); - Expect(value).ToEqual(Limits::Max()); - }); - - It("Can read u16 values", []() - { - // Test inbounds and out of bounds values - TArray data{0, 0, 255, 255}; - BinaryFormatReader reader{data}; - Reader ct = reader; - ct.BeginObject(); - u16 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(0); - ct.Next("b", value); - Expect(value).ToEqual(Limits::Max()); - }); - - It("Can read i32 values", []() - { - // Test inbounds and out of bounds values - TArray data{0, 0, 0, 0, 0, 0, 0, 128, 255, 255, 255, 127}; - BinaryFormatReader reader{data}; - Reader ct = reader; - ct.BeginObject(); - i32 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(0); - ct.Next("b", value); - Expect(value).ToEqual(Limits::Lowest()); - ct.Next("c", value); - Expect(value).ToEqual(Limits::Max()); - }); - - It("Can read u32 values", []() - { - // Test inbounds and out of bounds values - TArray data{0, 0, 0, 0, 255, 255, 255, 255}; - BinaryFormatReader reader{data}; - Reader ct = reader; - ct.BeginObject(); - u32 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(0); - ct.Next("b", value); - Expect(value).ToEqual(Limits::Max()); - }); - - It("Can read i64 values", []() - { - // Test inbounds and out of bounds values - TArray data{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 255, 255, 255, - 255, 255, 255, 255, 127}; - BinaryFormatReader reader{data}; - Reader ct = reader; - ct.BeginObject(); - i64 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(0); - ct.Next("b", value); - Expect(value).ToEqual(Limits::Lowest()); - ct.Next("c", value); - Expect(value).ToEqual(Limits::Max()); - }); - - It("Can read u64 values", []() - { - // Test inbounds and out of bounds values - TArray data{0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255}; - BinaryFormatReader reader{data}; - Reader ct = reader; - ct.BeginObject(); - u64 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(0); - ct.Next("b", value); - Expect(value).ToEqual(Limits::Max()); - }); - - It("Can read float values", []() - { - TArray data{51, 51, 179, 191, 0, 0, 96, 64}; - BinaryFormatReader reader{data}; - Reader ct = reader; - ct.BeginObject(); - float value = 0.f; - ct.Next("a", value); - Expect(value).ToEqual(-1.4f); - ct.Next("b", value); - Expect(value).ToEqual(3.5f); - }); - - It("Can read double values", []() - { - TArray data{ - 102, 102, 102, 102, 102, 102, 246, 191, 0, 0, 0, 0, 0, 0, 12, 64}; - BinaryFormatReader reader{data}; - Reader ct = reader; - ct.BeginObject(); - double value = 0; - ct.Next("a", value); - Expect(value).ToEqual(-1.4); - ct.Next("b", value); - Expect(value).ToEqual(3.5); - }); - - It("Can read StringView values", []() - { - TArray data{3, 0, 0, 0, 'y', 'e', 's'}; - BinaryFormatReader reader{data}; - Reader ct = reader; - ct.BeginObject(); - StringView string; - ct.Next("a", string); - Expect(string).ToEqual("yes"); - }); + // Test inbounds and out of bounds values + TArray data{0, 0, 0, 0, 255, 255, 255, 255}; + BinaryFormatReader reader{data}; + Reader ct = reader; + ct.BeginObject(); + u32 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(0); + ct.Next("b", value); + Expect(value).ToEqual(Limits::Max()); + }); + + It("Can read i64 values", []() + { + // Test inbounds and out of bounds values + TArray data{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 255, 255, 255, + 255, 255, 255, 255, 127}; + BinaryFormatReader reader{data}; + Reader ct = reader; + ct.BeginObject(); + i64 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(0); + ct.Next("b", value); + Expect(value).ToEqual(Limits::Lowest()); + ct.Next("c", value); + Expect(value).ToEqual(Limits::Max()); }); + + It("Can read u64 values", []() + { + // Test inbounds and out of bounds values + TArray data{0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255}; + BinaryFormatReader reader{data}; + Reader ct = reader; + ct.BeginObject(); + u64 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(0); + ct.Next("b", value); + Expect(value).ToEqual(Limits::Max()); + }); + + It("Can read float values", []() + { + TArray data{51, 51, 179, 191, 0, 0, 96, 64}; + BinaryFormatReader reader{data}; + Reader ct = reader; + ct.BeginObject(); + float value = 0.f; + ct.Next("a", value); + Expect(value).ToEqual(-1.4f); + ct.Next("b", value); + Expect(value).ToEqual(3.5f); + }); + + It("Can read double values", []() + { + TArray data{ + 102, 102, 102, 102, 102, 102, 246, 191, 0, 0, 0, 0, 0, 0, 12, 64}; + BinaryFormatReader reader{data}; + Reader ct = reader; + ct.BeginObject(); + double value = 0; + ct.Next("a", value); + Expect(value).ToEqual(-1.4); + ct.Next("b", value); + Expect(value).ToEqual(3.5); + }); + + It("Can read StringView values", []() + { + TArray data{3, 0, 0, 0, 'y', 'e', 's'}; + BinaryFormatReader reader{data}; + Reader ct = reader; + ct.BeginObject(); + StringView string; + ct.Next("a", string); + Expect(string).ToEqual("yes"); + }); + }); + }); + + Describe("Writer", []() + { + It("Can create a writer", []() + { + BinaryFormatWriter writer{}; + Expect(writer.IsValid()).ToEqual(true); + }); + + It("Can write to object key", []() + { + BinaryFormatWriter writer{}; + Writer& ct = writer; + ct.BeginObject(); + ct.Next("name", StringView{"Miguel"}); + + TArray expected{6, 0, 0, 0, 'M', 'i', 'g', 'u', 'e', 'l'}; + Expect(writer.GetData()).ToEqual(TView{expected}); }); - Describe("Writer", []() + It("Can write arrays", []() { - It("Can create a writer", []() + BinaryFormatWriter writer{}; + Writer& ct = writer; + ct.BeginArray(2); + ct.Next(u8(255)); + ct.Next(u8(255)); + + TArray expected{2, 0, 0, 0, 255, 255}; + Expect(writer.GetData()).ToEqual(TView{expected}); + }); + + Describe("Types", []() + { + It("Can write bool values", []() { BinaryFormatWriter writer{}; - Expect(writer.IsValid()).ToEqual(true); + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", true); + ct.Next("b", false); + TArray expected{1, 0}; + Expect(writer.GetData()).ToEqual(TView(expected)); }); - It("Can write to object key", []() + It("Can write i8 values", []() { BinaryFormatWriter writer{}; - Writer& ct = writer; + Writer ct = writer; ct.BeginObject(); - ct.Next("name", StringView{"Miguel"}); + ct.Next("a", i8(127)); + ct.Next("b", i8(-128)); + TArray expected{127, 128}; + Expect(writer.GetData()).ToEqual(TView(expected)); + }); - TArray expected{6, 0, 0, 0, 'M', 'i', 'g', 'u', 'e', 'l'}; - Expect(writer.GetData()).ToEqual(TView{expected}); + It("Can write u8 values", []() + { + BinaryFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", u8(0)); + ct.Next("b", u8(255)); + TArray expected{0, 255}; + Expect(writer.GetData()).ToEqual(TView(expected)); }); - It("Can write arrays", []() + It("Can write i16 values", []() { BinaryFormatWriter writer{}; - Writer& ct = writer; - ct.BeginArray(2); - ct.Next(u8(255)); - ct.Next(u8(255)); + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", Limits::Max()); + ct.Next("b", Limits::Lowest()); + TArray expected{255, 127, 0, 128}; + Expect(writer.GetData()).ToEqual(TView(expected)); + }); - TArray expected{2, 0, 0, 0, 255, 255}; - Expect(writer.GetData()).ToEqual(TView{expected}); + It("Can write u16 values", []() + { + BinaryFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", Limits::Max()); + ct.Next("b", Limits::Lowest()); + TArray expected{255, 255, 0, 0}; + Expect(writer.GetData()).ToEqual(TView(expected)); }); - Describe("Types", []() + It("Can write i32 values", []() { - It("Can write bool values", []() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", true); - ct.Next("b", false); - TArray expected{1, 0}; - Expect(writer.GetData()).ToEqual(TView(expected)); - }); - - It("Can write i8 values", []() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", i8(127)); - ct.Next("b", i8(-128)); - TArray expected{127, 128}; - Expect(writer.GetData()).ToEqual(TView(expected)); - }); - - It("Can write u8 values", []() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", u8(0)); - ct.Next("b", u8(255)); - TArray expected{0, 255}; - Expect(writer.GetData()).ToEqual(TView(expected)); - }); - - It("Can write i16 values", []() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", Limits::Max()); - ct.Next("b", Limits::Lowest()); - TArray expected{255, 127, 0, 128}; - Expect(writer.GetData()).ToEqual(TView(expected)); - }); - - It("Can write u16 values", []() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", Limits::Max()); - ct.Next("b", Limits::Lowest()); - TArray expected{255, 255, 0, 0}; - Expect(writer.GetData()).ToEqual(TView(expected)); - }); - - It("Can write i32 values", []() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", Limits::Max()); - ct.Next("b", Limits::Lowest()); - TArray expected{255, 255, 255, 127, 0, 0, 0, 128}; - Expect(writer.GetData()).ToEqual(TView(expected)); - }); - - It("Can write u32 values", []() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", Limits::Max()); - ct.Next("b", Limits::Lowest()); - TArray expected{255, 255, 255, 255, 0, 0, 0, 0}; - Expect(writer.GetData()).ToEqual(TView(expected)); - }); - - It("Can write i64 values", []() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", Limits::Max()); - ct.Next("b", Limits::Lowest()); - TArray expected{ - 255, 255, 255, 255, 255, 255, 255, 127, 0, 0, 0, 0, 0, 0, 0, 128}; - Expect(writer.GetData()).ToEqual(TView(expected)); - }); - - It("Can write u64 values", []() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", Limits::Max()); - ct.Next("b", Limits::Lowest()); - TArray expected{ - 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}; - Expect(writer.GetData()).ToEqual(TView(expected)); - }); - - It("Can write float values", []() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", -1.4f); - ct.Next("b", 3.5f); - TArray expected{51, 51, 179, 191, 0, 0, 96, 64}; - Expect(writer.GetData()).ToEqual(TView(expected)); - }); - - It("Can write double values", []() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", -1.4); - ct.Next("b", 3.5); - TArray expected{ - 102, 102, 102, 102, 102, 102, 246, 191, 0, 0, 0, 0, 0, 0, 12, 64}; - Expect(writer.GetData()).ToEqual(TView(expected)); - }); - - It("Can write StringView values", []() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", StringView{"yes"}); - TArray expected{3, 0, 0, 0, 'y', 'e', 's'}; - Expect(writer.GetData()).ToEqual(TView(expected)); - }); + BinaryFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", Limits::Max()); + ct.Next("b", Limits::Lowest()); + TArray expected{255, 255, 255, 127, 0, 0, 0, 128}; + Expect(writer.GetData()).ToEqual(TView(expected)); + }); + + It("Can write u32 values", []() + { + BinaryFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", Limits::Max()); + ct.Next("b", Limits::Lowest()); + TArray expected{255, 255, 255, 255, 0, 0, 0, 0}; + Expect(writer.GetData()).ToEqual(TView(expected)); + }); + + It("Can write i64 values", []() + { + BinaryFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", Limits::Max()); + ct.Next("b", Limits::Lowest()); + TArray expected{ + 255, 255, 255, 255, 255, 255, 255, 127, 0, 0, 0, 0, 0, 0, 0, 128}; + Expect(writer.GetData()).ToEqual(TView(expected)); + }); + + It("Can write u64 values", []() + { + BinaryFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", Limits::Max()); + ct.Next("b", Limits::Lowest()); + TArray expected{ + 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}; + Expect(writer.GetData()).ToEqual(TView(expected)); + }); + + It("Can write float values", []() + { + BinaryFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", -1.4f); + ct.Next("b", 3.5f); + TArray expected{51, 51, 179, 191, 0, 0, 96, 64}; + Expect(writer.GetData()).ToEqual(TView(expected)); + }); + + It("Can write double values", []() + { + BinaryFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", -1.4); + ct.Next("b", 3.5); + TArray expected{ + 102, 102, 102, 102, 102, 102, 246, 191, 0, 0, 0, 0, 0, 0, 12, 64}; + Expect(writer.GetData()).ToEqual(TView(expected)); + }); + + It("Can write StringView values", []() + { + BinaryFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", StringView{"yes"}); + TArray expected{3, 0, 0, 0, 'y', 'e', 's'}; + Expect(writer.GetData()).ToEqual(TView(expected)); }); }); }); -} +}); +return true; +}(); +} // namespace diff --git a/Tests/Serialization/Json.spec.cpp b/Tests/Serialization/Json.spec.cpp index 72a380b6..b56d6e17 100644 --- a/Tests/Serialization/Json.spec.cpp +++ b/Tests/Serialization/Json.spec.cpp @@ -1,433 +1,438 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include using namespace p; -void RegisterSerializationJsonTests() +namespace { - Spec("Serialization.Json", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("Serialization.Json", []() +{ + Describe("Reader", []() { - Describe("Reader", []() + It("Can create a reader", []() + { + JsonFormatReader reader{"{}"}; + Expect(reader.IsValid()).ToBeTrue(); + }); + + It("Can read from object value", []() { - It("Can create a reader", []() + String data{"{\"name\": \"Miguel\"}"}; + JsonFormatReader reader{data}; + + Reader& ct = reader; + ct.BeginObject(); + String name; + ct.Next("name", name); + + Expect(name.data()).ToEqual("Miguel"); + }); + + It("Can read from array values", []() + { + String data{"{\"players\": [\"Miguel\", \"Juan\"]}"}; + JsonFormatReader reader{data}; + + Reader& ct = reader; + ct.BeginObject(); + if (ct.EnterNext("players")) { - JsonFormatReader reader{"{}"}; - Expect(reader.IsValid()).ToBeTrue(); - }); + u32 size; + ct.BeginArray(size); + String name; + ct.Next(name); + Expect(name.data()).ToEqual("Miguel"); + + ct.Next(name); + Expect(name.data()).ToEqual("Juan"); + + ct.Leave(); + } + }); - It("Can read from object value", []() + It("Can iterate arrays", []() + { + String data{"{\"players\": [\"Miguel\", \"Juan\"]}"}; + JsonFormatReader reader{data}; + + Reader& ct = reader; + ct.BeginObject(); + if (ct.EnterNext("players")) { - String data{"{\"name\": \"Miguel\"}"}; - JsonFormatReader reader{data}; + static const StringView expected[]{"Miguel", "Juan"}; + u32 size; + ct.BeginArray(size); + for (u32 i = 0; i < size; ++i) + { + StringView name; + ct.Next(name); + Expect(name).ToEqual(expected[i]); + } + ct.Leave(); + } + }); + + It("Can check types", []() + { + String data{"{\"players\": [\"Miguel\", \"Juan\"]}"}; + JsonFormatReader reader{data}; + + Reader& ct = reader; + Expect(reader.IsObject()).ToEqual(true); + ct.BeginObject(); + if (ct.EnterNext("players")) + { + Expect(reader.IsArray()).ToEqual(true); + ct.Leave(); + } + }); + + It("Can find multiple keys", []() + { + String data{"{\"one\": \"Miguel\", \"other\": \"Juan\"}"}; + JsonFormatReader reader{data}; + + Reader& ct = reader; + Expect(reader.IsObject()).ToEqual(true); + ct.BeginObject(); + StringView name; + ct.Next("one", name); + Expect(name).ToEqual("Miguel"); + + ct.Next("other", name); + Expect(name).ToEqual("Juan"); + }); + + It("Can find multiple unordered keys", []() + { + String data{"{\"one\": \"Miguel\", \"other\": \"Juan\"}"}; + JsonFormatReader reader{data}; + + Reader& ct = reader; + Expect(reader.IsObject()).ToEqual(true); + ct.BeginObject(); + StringView name; + ct.Next("other", name); + Expect(name).ToEqual("Juan"); + + ct.Next("one", name); + Expect(name).ToEqual("Miguel"); + }); + Describe("Types", []() + { + It("Can read bool values", []() + { + JsonFormatReader reader{"{\"alive\": true}"}; Reader& ct = reader; ct.BeginObject(); - String name; - ct.Next("name", name); + bool value = false; + ct.Next("alive", value); + Expect(value).ToEqual(true); - Expect(name.data()).ToEqual("Miguel"); + JsonFormatReader reader2{"{\"alive\": false}"}; + ct = reader2; + ct.BeginObject(); + bool value2 = true; + ct.Next("alive", value2); + Expect(value2).ToEqual(false); }); - It("Can read from array values", []() + It("Can read i8 values", []() { - String data{"{\"players\": [\"Miguel\", \"Juan\"]}"}; - JsonFormatReader reader{data}; - + JsonFormatReader reader{"{\"alive\": -3}"}; Reader& ct = reader; ct.BeginObject(); - if (ct.EnterNext("players")) - { - u32 size; - ct.BeginArray(size); - String name; - ct.Next(name); - Expect(name.data()).ToEqual("Miguel"); + i8 value = 0; + ct.Next("alive", value); + Expect(value).ToEqual(-3); - ct.Next(name); - Expect(name.data()).ToEqual("Juan"); - - ct.Leave(); - } + JsonFormatReader reader2{"{\"alive\": -1.344}"}; + ct = reader2; + ct.BeginObject(); + i8 value2 = 0; + ct.Next("alive", value2); + Expect(value2).ToEqual(-1); }); - It("Can iterate arrays", []() + It("Can read u8 values", []() { - String data{"{\"players\": [\"Miguel\", \"Juan\"]}"}; - JsonFormatReader reader{data}; - + JsonFormatReader reader{"{\"alive\": 3}"}; Reader& ct = reader; ct.BeginObject(); - if (ct.EnterNext("players")) - { - static const StringView expected[]{"Miguel", "Juan"}; - u32 size; - ct.BeginArray(size); - for (u32 i = 0; i < size; ++i) - { - StringView name; - ct.Next(name); - Expect(name).ToEqual(expected[i]); - } - ct.Leave(); - } + u8 value = 0; + ct.Next("alive", value); + Expect(value).ToEqual(3); + + JsonFormatReader reader2{"{\"alive\": 1.344}"}; + ct = reader2; + ct.BeginObject(); + u8 value2 = 0; + ct.Next("alive", value2); + Expect(value2).ToEqual(1); }); - It("Can check types", []() + It("Can read i16 values", []() { - String data{"{\"players\": [\"Miguel\", \"Juan\"]}"}; - JsonFormatReader reader{data}; + // Test inbounds and out of bounds values + JsonFormatReader reader{ + Format("{{\"a\":{},\"b\":{},\"c\":{},\"d\":{}}}", Limits::Max(), + Limits::Lowest(), Limits::Max(), Limits::Lowest())}; + Reader ct = reader; + ct.BeginObject(); + i16 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(Limits::Max()); + ct.Next("b", value); + Expect(value).ToEqual(Limits::Lowest()); + ct.Next("c", value); + Expect(value).ToEqual(Limits::Max()); + ct.Next("d", value); + Expect(value).ToEqual(Limits::Lowest()); + }); - Reader& ct = reader; - Expect(reader.IsObject()).ToEqual(true); + It("Can read u16 values", []() + { + JsonFormatReader reader{Format("{{\"a\":{},\"b\":{},\"c\":{}}}", + Limits::Max(), Limits::Lowest(), -32)}; + Reader ct = reader; ct.BeginObject(); - if (ct.EnterNext("players")) - { - Expect(reader.IsArray()).ToEqual(true); - ct.Leave(); - } + u16 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(Limits::Max()); + ct.Next("b", value); + Expect(value).ToEqual(Limits::Lowest()); + ct.Next("c", value); + Expect(value).ToEqual(0); }); - It("Can find multiple keys", []() + It("Can read i32 values", []() { - String data{"{\"one\": \"Miguel\", \"other\": \"Juan\"}"}; - JsonFormatReader reader{data}; + // Test inbounds and out of bounds values + JsonFormatReader reader{ + Format("{{\"a\":{},\"b\":{},\"c\":{},\"d\":{}}}", Limits::Max(), + Limits::Lowest(), Limits::Max(), Limits::Lowest())}; + Reader ct = reader; + ct.BeginObject(); + i32 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(Limits::Max()); + ct.Next("b", value); + Expect(value).ToEqual(Limits::Lowest()); + ct.Next("c", value); + Expect(value).ToEqual(Limits::Max()); + ct.Next("d", value); + Expect(value).ToEqual(Limits::Lowest()); + }); + It("Can read u32 values", []() + { + JsonFormatReader reader{Format("{{\"a\":{},\"b\":{},\"c\":{}}}", + Limits::Max(), Limits::Lowest(), -32)}; + Reader ct = reader; + ct.BeginObject(); + u32 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(Limits::Max()); + ct.Next("b", value); + Expect(value).ToEqual(Limits::Lowest()); + ct.Next("c", value); + Expect(value).ToEqual(0); + }); + + It("Can read float values", []() + { + JsonFormatReader reader{"{\"alive\": 0.344}"}; Reader& ct = reader; - Expect(reader.IsObject()).ToEqual(true); ct.BeginObject(); - StringView name; - ct.Next("one", name); - Expect(name).ToEqual("Miguel"); + float value = 0.f; + ct.Next("alive", value); + Expect(value).ToEqual(0.344f); - ct.Next("other", name); - Expect(name).ToEqual("Juan"); + JsonFormatReader reader2{"{\"alive\": 4}"}; + ct = reader2; + ct.BeginObject(); + float value2 = 0.f; + ct.Next("alive", value2); + Expect(value2).ToEqual(4.f); }); - It("Can find multiple unordered keys", []() + It("Can read StringView values", []() { - String data{"{\"one\": \"Miguel\", \"other\": \"Juan\"}"}; - JsonFormatReader reader{data}; - + JsonFormatReader reader{"{\"alive\": \"yes\"}"}; Reader& ct = reader; - Expect(reader.IsObject()).ToEqual(true); ct.BeginObject(); - StringView name; - ct.Next("other", name); - Expect(name).ToEqual("Juan"); - - ct.Next("one", name); - Expect(name).ToEqual("Miguel"); + StringView value; + ct.Next("alive", value); + Expect(value).ToEqual("yes"); }); + }); + }); + + Describe("Writer", []() + { + It("Can create a writer", []() + { + JsonFormatWriter writer{}; + Expect(writer.IsValid()).ToEqual(true); + }); - Describe("Types", []() + It("Can write to object key", []() + { + JsonFormatWriter writer{}; + Writer& ct = writer; + ct.BeginObject(); + ct.Next("name", StringView{"Miguel"}); + Expect(writer.ToString(false)).ToEqual("{\"name\":\"Miguel\"}"); + }); + + It("Can write arrays", []() + { + JsonFormatWriter writer{}; + + Writer& ct = writer; + ct.BeginObject(); + if (ct.EnterNext("players")) { - It("Can read bool values", []() - { - JsonFormatReader reader{"{\"alive\": true}"}; - Reader& ct = reader; - ct.BeginObject(); - bool value = false; - ct.Next("alive", value); - Expect(value).ToEqual(true); - - JsonFormatReader reader2{"{\"alive\": false}"}; - ct = reader2; - ct.BeginObject(); - bool value2 = true; - ct.Next("alive", value2); - Expect(value2).ToEqual(false); - }); - - It("Can read i8 values", []() - { - JsonFormatReader reader{"{\"alive\": -3}"}; - Reader& ct = reader; - ct.BeginObject(); - i8 value = 0; - ct.Next("alive", value); - Expect(value).ToEqual(-3); - - JsonFormatReader reader2{"{\"alive\": -1.344}"}; - ct = reader2; - ct.BeginObject(); - i8 value2 = 0; - ct.Next("alive", value2); - Expect(value2).ToEqual(-1); - }); - - It("Can read u8 values", []() - { - JsonFormatReader reader{"{\"alive\": 3}"}; - Reader& ct = reader; - ct.BeginObject(); - u8 value = 0; - ct.Next("alive", value); - Expect(value).ToEqual(3); - - JsonFormatReader reader2{"{\"alive\": 1.344}"}; - ct = reader2; - ct.BeginObject(); - u8 value2 = 0; - ct.Next("alive", value2); - Expect(value2).ToEqual(1); - }); - - It("Can read i16 values", []() - { - // Test inbounds and out of bounds values - JsonFormatReader reader{ - Format("{{\"a\":{},\"b\":{},\"c\":{},\"d\":{}}}", Limits::Max(), - Limits::Lowest(), Limits::Max(), Limits::Lowest())}; - Reader ct = reader; - ct.BeginObject(); - i16 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(Limits::Max()); - ct.Next("b", value); - Expect(value).ToEqual(Limits::Lowest()); - ct.Next("c", value); - Expect(value).ToEqual(Limits::Max()); - ct.Next("d", value); - Expect(value).ToEqual(Limits::Lowest()); - }); - - It("Can read u16 values", []() - { - JsonFormatReader reader{Format("{{\"a\":{},\"b\":{},\"c\":{}}}", - Limits::Max(), Limits::Lowest(), -32)}; - Reader ct = reader; - ct.BeginObject(); - u16 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(Limits::Max()); - ct.Next("b", value); - Expect(value).ToEqual(Limits::Lowest()); - ct.Next("c", value); - Expect(value).ToEqual(0); - }); - - It("Can read i32 values", []() - { - // Test inbounds and out of bounds values - JsonFormatReader reader{ - Format("{{\"a\":{},\"b\":{},\"c\":{},\"d\":{}}}", Limits::Max(), - Limits::Lowest(), Limits::Max(), Limits::Lowest())}; - Reader ct = reader; - ct.BeginObject(); - i32 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(Limits::Max()); - ct.Next("b", value); - Expect(value).ToEqual(Limits::Lowest()); - ct.Next("c", value); - Expect(value).ToEqual(Limits::Max()); - ct.Next("d", value); - Expect(value).ToEqual(Limits::Lowest()); - }); - - It("Can read u32 values", []() - { - JsonFormatReader reader{Format("{{\"a\":{},\"b\":{},\"c\":{}}}", - Limits::Max(), Limits::Lowest(), -32)}; - Reader ct = reader; - ct.BeginObject(); - u32 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(Limits::Max()); - ct.Next("b", value); - Expect(value).ToEqual(Limits::Lowest()); - ct.Next("c", value); - Expect(value).ToEqual(0); - }); - - It("Can read float values", []() + static const StringView expected[]{"Miguel", "Juan"}; + u32 size = 2; + ct.BeginArray(size); + for (u32 i = 0; i < size; ++i) { - JsonFormatReader reader{"{\"alive\": 0.344}"}; - Reader& ct = reader; - ct.BeginObject(); - float value = 0.f; - ct.Next("alive", value); - Expect(value).ToEqual(0.344f); - - JsonFormatReader reader2{"{\"alive\": 4}"}; - ct = reader2; - ct.BeginObject(); - float value2 = 0.f; - ct.Next("alive", value2); - Expect(value2).ToEqual(4.f); - }); - - It("Can read StringView values", []() - { - JsonFormatReader reader{"{\"alive\": \"yes\"}"}; - Reader& ct = reader; - ct.BeginObject(); - StringView value; - ct.Next("alive", value); - Expect(value).ToEqual("yes"); - }); - }); + ct.Next(expected[i]); + } + ct.Leave(); + } + Expect(writer.ToString(false)).ToEqual("{\"players\":[\"Miguel\",\"Juan\"]}"); }); - Describe("Writer", []() + It("Can write multiple object keys", []() { - It("Can create a writer", []() + JsonFormatWriter writer{}; + Writer& ct = writer; + ct.BeginObject(); + ct.Next("one", StringView{"Miguel"}); + ct.Next("other", StringView{"Juan"}); + Expect( + writer.ToString(false)).ToEqual("{\"one\":\"Miguel\",\"other\":\"Juan\"}"); + }); + + Describe("Types", []() + { + It("Can write bool values", []() { JsonFormatWriter writer{}; - Expect(writer.IsValid()).ToEqual(true); + Writer& ct = writer; + ct.BeginObject(); + ct.Next("alive", true); + Expect(writer.ToString(false)).ToEqual("{\"alive\":true}"); + + JsonFormatWriter writer2{}; + ct = writer2; + ct.BeginObject(); + ct.Next("alive", false); + Expect(writer2.ToString(false)).ToEqual("{\"alive\":false}"); }); - It("Can write to object key", []() + It("Can write i8 values", []() { JsonFormatWriter writer{}; - Writer& ct = writer; + Writer ct = writer; ct.BeginObject(); - ct.Next("name", StringView{"Miguel"}); - Expect(writer.ToString(false)).ToEqual("{\"name\":\"Miguel\"}"); + ct.Next("alive", i8(-3)); + Expect(writer.ToString(false)).ToEqual("{\"alive\":-3}"); }); - It("Can write arrays", []() + It("Can write u8 values", []() { JsonFormatWriter writer{}; - - Writer& ct = writer; + Writer ct = writer; ct.BeginObject(); - if (ct.EnterNext("players")) - { - static const StringView expected[]{"Miguel", "Juan"}; - u32 size = 2; - ct.BeginArray(size); - for (u32 i = 0; i < size; ++i) - { - ct.Next(expected[i]); - } - ct.Leave(); - } - Expect(writer.ToString(false)).ToEqual("{\"players\":[\"Miguel\",\"Juan\"]}"); + ct.Next("alive", u8(3)); + Expect(writer.ToString(false)).ToEqual("{\"alive\":3}"); }); - It("Can write multiple object keys", []() + It("Can write i16 values", []() { JsonFormatWriter writer{}; - Writer& ct = writer; + Writer ct = writer; ct.BeginObject(); - ct.Next("one", StringView{"Miguel"}); - ct.Next("other", StringView{"Juan"}); + ct.Next("a", i16(-3000)); + ct.Next("b", Limits::Max()); + ct.Next("c", Limits::Lowest()); Expect( - writer.ToString(false)).ToEqual("{\"one\":\"Miguel\",\"other\":\"Juan\"}"); + writer.ToString(false)).ToEqual("{\"a\":-3000,\"b\":32767,\"c\":-32768}"); }); - Describe("Types", []() + It("Can write u16 values", []() { - It("Can write bool values", []() - { - JsonFormatWriter writer{}; - Writer& ct = writer; - ct.BeginObject(); - ct.Next("alive", true); - Expect(writer.ToString(false)).ToEqual("{\"alive\":true}"); - - JsonFormatWriter writer2{}; - ct = writer2; - ct.BeginObject(); - ct.Next("alive", false); - Expect(writer2.ToString(false)).ToEqual("{\"alive\":false}"); - }); - - It("Can write i8 values", []() - { - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("alive", i8(-3)); - Expect(writer.ToString(false)).ToEqual("{\"alive\":-3}"); - }); - - It("Can write u8 values", []() - { - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("alive", u8(3)); - Expect(writer.ToString(false)).ToEqual("{\"alive\":3}"); - }); - - It("Can write i16 values", []() - { - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", i16(-3000)); - ct.Next("b", Limits::Max()); - ct.Next("c", Limits::Lowest()); - Expect( - writer.ToString(false)).ToEqual("{\"a\":-3000,\"b\":32767,\"c\":-32768}"); - }); - - It("Can write u16 values", []() - { - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", u16(3000)); - ct.Next("b", Limits::Max()); - ct.Next("c", Limits::Lowest()); - Expect(writer.ToString(false)).ToEqual("{\"a\":3000,\"b\":65535,\"c\":0}"); - }); - - It("Can write u32 values", []() - { - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("alive", u32(35533)); - Expect(writer.ToString(false)).ToEqual("{\"alive\":35533}"); - }); - - It("Can write i32 values", []() - { - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - i32 value = 0; - ct.Next("alive", u32(35533)); - Expect(writer.ToString(false)).ToEqual("{\"alive\":35533}"); - - JsonFormatWriter writer2{}; - ct = writer2; - ct.BeginObject(); - ct.Next("alive", i32(-35533)); - Expect(writer2.ToString(false)).ToEqual("{\"alive\":-35533}"); - }); - - It("Can write float values", []() - { - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("alive", 0.344f); - Expect(Strings::Contains(writer.ToString(false), "0.344")).ToEqual(true); - - JsonFormatWriter writer2{}; - ct = writer2; - ct.BeginObject(); - ct.Next("alive", 4.f); - Expect(writer2.ToString(false)).ToEqual("{\"alive\":4.0}"); - }); - - It("Can write StringView values", []() - { - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("alive", StringView{"yes"}); - Expect(writer.ToString(false)).ToEqual("{\"alive\":\"yes\"}"); - }); + JsonFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", u16(3000)); + ct.Next("b", Limits::Max()); + ct.Next("c", Limits::Lowest()); + Expect(writer.ToString(false)).ToEqual("{\"a\":3000,\"b\":65535,\"c\":0}"); + }); + + It("Can write u32 values", []() + { + JsonFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("alive", u32(35533)); + Expect(writer.ToString(false)).ToEqual("{\"alive\":35533}"); + }); + + It("Can write i32 values", []() + { + JsonFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + i32 value = 0; + ct.Next("alive", u32(35533)); + Expect(writer.ToString(false)).ToEqual("{\"alive\":35533}"); + + JsonFormatWriter writer2{}; + ct = writer2; + ct.BeginObject(); + ct.Next("alive", i32(-35533)); + Expect(writer2.ToString(false)).ToEqual("{\"alive\":-35533}"); + }); + + It("Can write float values", []() + { + JsonFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("alive", 0.344f); + Expect(Strings::Contains(writer.ToString(false), "0.344")).ToEqual(true); + + JsonFormatWriter writer2{}; + ct = writer2; + ct.BeginObject(); + ct.Next("alive", 4.f); + Expect(writer2.ToString(false)).ToEqual("{\"alive\":4.0}"); + }); + + It("Can write StringView values", []() + { + JsonFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("alive", StringView{"yes"}); + Expect(writer.ToString(false)).ToEqual("{\"alive\":\"yes\"}"); }); }); }); -} +}); +return true; +}(); +} // namespace diff --git a/Tests/Serialization/Serialization.spec.cpp b/Tests/Serialization/Serialization.spec.cpp index 8ee82f23..ddf11a1b 100644 --- a/Tests/Serialization/Serialization.spec.cpp +++ b/Tests/Serialization/Serialization.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include @@ -89,106 +89,111 @@ struct p::TFlags : public p::DefaultTFlags }; -void RegisterSerializationSerializationTests() +namespace { - Spec("Serialization", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("Serialization", []() +{ + Describe("Serializers in global scope", []() { - Describe("Serializers in global scope", []() + It("Can use custom Read()", []() + { + SerTypeA val{}; + JsonFormatReader reader{"{\"type\": {\"value\": true }}"}; + + Reader& ct = reader; + ct.BeginObject(); + ct.Next("type", val); + Expect(val.value).ToEqual(true); + }); + + It("Can use custom Write()", []() + { + SerTypeA val{}; + val.value = true; + + JsonFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("type", val); + Expect(writer.ToString(false)).ToEqual("{\"type\":{\"value\":true}}"); + }); + + It("Can use Serialize() instead of Read()", []() { - It("Can use custom Read()", []() - { - SerTypeA val{}; - JsonFormatReader reader{"{\"type\": {\"value\": true }}"}; - - Reader& ct = reader; - ct.BeginObject(); - ct.Next("type", val); - Expect(val.value).ToEqual(true); - }); - - It("Can use custom Write()", []() - { - SerTypeA val{}; - val.value = true; - - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("type", val); - Expect(writer.ToString(false)).ToEqual("{\"type\":{\"value\":true}}"); - }); - - It("Can use Serialize() instead of Read()", []() - { - SerTypeB val{}; - JsonFormatReader reader{"{\"type\": {\"value\": true }}"}; - - Reader& ct = reader; - ct.BeginObject(); - ct.Next("type", val); - Expect(val.value).ToEqual(true); - }); - - It("Can use Serialize() instead of Write()", []() - { - SerTypeB val{}; - val.value = true; - - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("type", val); - Expect(writer.ToString(false)).ToEqual("{\"type\":{\"value\":true}}"); - }); + SerTypeB val{}; + JsonFormatReader reader{"{\"type\": {\"value\": true }}"}; + + Reader& ct = reader; + ct.BeginObject(); + ct.Next("type", val); + Expect(val.value).ToEqual(true); }); - Describe("Serializers as members", []() + It("Can use Serialize() instead of Write()", []() { - It("Can use custom Read()", []() - { - SerTypeC val{}; - JsonFormatReader reader{"{\"type\": {\"value\": true }}"}; - - Reader& ct = reader; - ct.BeginObject(); - ct.Next("type", val); - Expect(val.value).ToEqual(true); - }); - - It("Can use custom Write()", []() - { - SerTypeC val{}; - val.value = true; - - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("type", val); - Expect(writer.ToString(false)).ToEqual("{\"type\":{\"value\":true}}"); - }); - - It("Can use Serialize() instead of Read()", []() - { - SerTypeD val{}; - JsonFormatReader reader{"{\"type\": {\"value\": true }}"}; - - Reader& ct = reader; - ct.BeginObject(); - ct.Next("type", val); - Expect(val.value).ToEqual(true); - }); - - It("Can use Serialize() instead of Write()", []() - { - SerTypeD val{}; - val.value = true; - - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("type", val); - Expect(writer.ToString(false)).ToEqual("{\"type\":{\"value\":true}}"); - }); + SerTypeB val{}; + val.value = true; + + JsonFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("type", val); + Expect(writer.ToString(false)).ToEqual("{\"type\":{\"value\":true}}"); }); }); -} + + Describe("Serializers as members", []() + { + It("Can use custom Read()", []() + { + SerTypeC val{}; + JsonFormatReader reader{"{\"type\": {\"value\": true }}"}; + + Reader& ct = reader; + ct.BeginObject(); + ct.Next("type", val); + Expect(val.value).ToEqual(true); + }); + + It("Can use custom Write()", []() + { + SerTypeC val{}; + val.value = true; + + JsonFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("type", val); + Expect(writer.ToString(false)).ToEqual("{\"type\":{\"value\":true}}"); + }); + + It("Can use Serialize() instead of Read()", []() + { + SerTypeD val{}; + JsonFormatReader reader{"{\"type\": {\"value\": true }}"}; + + Reader& ct = reader; + ct.BeginObject(); + ct.Next("type", val); + Expect(val.value).ToEqual(true); + }); + + It("Can use Serialize() instead of Write()", []() + { + SerTypeD val{}; + val.value = true; + + JsonFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("type", val); + Expect(writer.ToString(false)).ToEqual("{\"type\":{\"value\":true}}"); + }); + }); +}); +return true; +}(); +} // namespace diff --git a/Tests/Time.spec.cpp b/Tests/Time.spec.cpp index 2e717afb..703900f3 100644 --- a/Tests/Time.spec.cpp +++ b/Tests/Time.spec.cpp @@ -1,31 +1,36 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include using namespace p; -void RegisterTimeTests() +namespace { - Spec("Time.DateTime", []() +// Auto-registers via static init (macro-free go_bandit equivalent). +const bool autoRegistered = []() +{ +Spec("Time.DateTime", []() +{ + It("Can get day of year", []() { - It("Can get day of year", []() - { - DateTime time1{2024, 1, 1}; - Expect(time1.GetDayOfYear()).ToEqual(1); - DateTime time11{2024, 1, 30}; - Expect(time11.GetDayOfYear()).ToEqual(30); - DateTime time12{2024, 1, 31}; - Expect(time12.GetDayOfYear()).ToEqual(31); + DateTime time1{2024, 1, 1}; + Expect(time1.GetDayOfYear()).ToEqual(1); + DateTime time11{2024, 1, 30}; + Expect(time11.GetDayOfYear()).ToEqual(30); + DateTime time12{2024, 1, 31}; + Expect(time12.GetDayOfYear()).ToEqual(31); - DateTime time2{2024, 2, 1}; - Expect(time2.GetDayOfYear()).ToEqual(32); - DateTime time3{2024, 3, 1}; - Expect(time3.GetDayOfYear()).ToEqual(60); - DateTime time4{2024, 12, 31}; - Expect(time4.GetDayOfYear()).ToEqual(365); - }); + DateTime time2{2024, 2, 1}; + Expect(time2.GetDayOfYear()).ToEqual(32); + DateTime time3{2024, 3, 1}; + Expect(time3.GetDayOfYear()).ToEqual(60); + DateTime time4{2024, 12, 31}; + Expect(time4.GetDayOfYear()).ToEqual(365); }); -} +}); +return true; +}(); +} // namespace diff --git a/Tests/main.cpp b/Tests/main.cpp index 10761cb7..d5220744 100644 --- a/Tests/main.cpp +++ b/Tests/main.cpp @@ -1,10 +1,10 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -// NOTE: PipeNewDelete is deliberately not included here. PipeTests provides the +// NOTE: PipeNewDelete is deliberately not included here. PipeTest provides the // replacement operator new/delete (P_OVERRIDE_NEWDELETE) in its own translation unit; // including it here too would cause duplicate-definition linker errors. -#include +#include #include #include @@ -17,44 +17,6 @@ // } // namespace backward -// Forward declarations -void RegisterTimeTests(); -void RegisterCoreFunctionTests(); -void RegisterCoreOwnPtrTests(); -void RegisterCorePageBufferTests(); -void RegisterCorePlatformProcessTests(); -void RegisterCoreSetTests(); -void RegisterCoreSpinLockTests(); -void RegisterCoreStringTests(); -void RegisterCoreStringViewTests(); -void RegisterCoreTagTests(); -void RegisterContainersArraysTests(); -void RegisterECSComponentsTests(); -void RegisterECSECSsmTests(); -void RegisterECSFilteringTests(); -void RegisterECSHierarchyTests(); -void RegisterECSIdRegistryTests(); -void RegisterECSIdScopesTests(); -void RegisterECSStaticsTests(); -void RegisterFilesPathsTests(); -void RegisterMathColorTests(); -void RegisterMathMathTests(); -void RegisterMathVectorTests(); -void RegisterMemoryBestFitArenaTests(); -void RegisterMemoryBigBestFitArenaTests(); -void RegisterMemoryMemoryTests(); -void RegisterMemoryMemoryStatsTests(); -void RegisterMemoryMonoLinearArenaTests(); -void RegisterReflectionMacroReflectionTests(); -void RegisterReflectionObjectTests(); -void RegisterReflectionTraitsTests(); -void RegisterReflectionTypeIdTests(); -void RegisterReflectionTypeNameTests(); -void RegisterSerializationBinaryTests(); -void RegisterSerializationJsonTests(); -void RegisterSerializationSerializationTests(); - - int main(int argc, char* argv[]) { p::Initialize(); @@ -62,42 +24,7 @@ int main(int argc, char* argv[]) // many subsystems; not all of them free every allocation during tests). p::GetHeapArena().GetStats()->detectLeaks = false; - RegisterTimeTests(); - RegisterCoreFunctionTests(); - RegisterCoreOwnPtrTests(); - RegisterCorePageBufferTests(); - RegisterCorePlatformProcessTests(); - RegisterCoreSetTests(); - RegisterCoreSpinLockTests(); - RegisterCoreStringTests(); - RegisterCoreStringViewTests(); - RegisterCoreTagTests(); - RegisterContainersArraysTests(); - RegisterECSComponentsTests(); - RegisterECSECSsmTests(); - RegisterECSFilteringTests(); - RegisterECSHierarchyTests(); - RegisterECSIdRegistryTests(); - RegisterECSIdScopesTests(); - RegisterECSStaticsTests(); - RegisterFilesPathsTests(); - RegisterMathColorTests(); - RegisterMathMathTests(); - RegisterMathVectorTests(); - RegisterMemoryBestFitArenaTests(); - RegisterMemoryBigBestFitArenaTests(); - RegisterMemoryMemoryTests(); - RegisterMemoryMemoryStatsTests(); - RegisterMemoryMonoLinearArenaTests(); - RegisterReflectionMacroReflectionTests(); - RegisterReflectionObjectTests(); - RegisterReflectionTraitsTests(); - RegisterReflectionTypeIdTests(); - RegisterReflectionTypeNameTests(); - RegisterSerializationBinaryTests(); - RegisterSerializationJsonTests(); - RegisterSerializationSerializationTests(); - + // Specs auto-register at file scope via static init; just run them. int result = p::RunTests(argc, argv); p::Shutdown(); return result; From 7ced1197b6d2129afaac7247cfcd9edf8852933b Mon Sep 17 00:00:00 2001 From: muit Date: Fri, 4 Sep 2026 12:52:54 +0200 Subject: [PATCH 13/25] test: clean up spec files, remove PipeTestsSelf, add CLI features - Remove autoRegistered wrappers from all 35 Pipe spec files - Delete Tests/PipeTests/ (moved to Tests root as PipeTest.spec.cpp) - Add --list/-l, -f shorthand, --no-color, ANSI colored output - Add PipeTime.spec.cpp for time tests - Fix Arrays.spec.cpp orphaned Describe at file scope - Fix Hierarchy.spec.cpp TFunction lambda signature --- Include/PipeTest.h | 64 +- Src/Tests/PipeTest.cpp | 137 +- Tests/CMakeLists.txt | 3 - Tests/Containers/Arrays.spec.cpp | 12 +- Tests/Core/Function.spec.cpp | 96 +- Tests/Core/OwnPtr.spec.cpp | 494 +++-- Tests/Core/PageBuffer.spec.cpp | 134 +- Tests/Core/PlatformProcess.spec.cpp | 18 +- Tests/Core/Set.spec.cpp | 146 +- Tests/Core/SpinLock.spec.cpp | 272 ++- Tests/Core/String.spec.cpp | 1676 ++++++++--------- Tests/Core/StringView.spec.cpp | 178 +- Tests/Core/Tag.spec.cpp | 174 +- Tests/ECS/Components.spec.cpp | 466 +++-- Tests/ECS/ECS.spec.cpp | 104 +- Tests/ECS/Filtering.spec.cpp | 356 ++-- Tests/ECS/Hierarchy.spec.cpp | 738 ++++---- Tests/ECS/IdRegistry.spec.cpp | 280 ++- Tests/ECS/IdScopes.spec.cpp | 228 ++- Tests/ECS/Statics.spec.cpp | 124 +- Tests/Files/Paths.spec.cpp | 324 ++-- Tests/Math/Color.spec.cpp | 238 ++- Tests/Math/Math.spec.cpp | 520 +++-- Tests/Math/Vector.spec.cpp | 112 +- Tests/Memory/BestFitArena.spec.cpp | 496 +++-- Tests/Memory/BigBestFitArena.spec.cpp | 538 +++--- Tests/Memory/Memory.spec.cpp | 364 ++-- Tests/Memory/MemoryStats.spec.cpp | 1020 +++++----- Tests/Memory/MonoLinearArena.spec.cpp | 266 ++- .../PipeTests.spec.cpp => PipeTest.spec.cpp} | 51 +- Tests/PipeTests/CMakeLists.txt | 8 - Tests/PipeTests/main.cpp | 17 - Tests/PipeTime.spec.cpp | 28 + Tests/Reflection/MacroReflection.spec.cpp | 30 +- Tests/Reflection/Object.spec.cpp | 42 +- Tests/Reflection/Traits.spec.cpp | 96 +- Tests/Reflection/TypeId.spec.cpp | 46 +- Tests/Reflection/TypeName.spec.cpp | 110 +- Tests/Serialization/Binary.spec.cpp | 722 ++++--- Tests/Serialization/Json.spec.cpp | 712 ++++--- Tests/Serialization/Serialization.spec.cpp | 190 +- Tests/Time.spec.cpp | 36 - 42 files changed, 5722 insertions(+), 5944 deletions(-) rename Tests/{PipeTests/PipeTests.spec.cpp => PipeTest.spec.cpp} (60%) delete mode 100644 Tests/PipeTests/CMakeLists.txt delete mode 100644 Tests/PipeTests/main.cpp create mode 100644 Tests/PipeTime.spec.cpp delete mode 100644 Tests/Time.spec.cpp diff --git a/Include/PipeTest.h b/Include/PipeTest.h index 3c9d12b2..cdd1d43a 100644 --- a/Include/PipeTest.h +++ b/Include/PipeTest.h @@ -3,11 +3,12 @@ #pragma once #include "Pipe/Core/Function.h" +#include "Pipe/Core/Macros.h" #include "Pipe/Core/StringView.h" #include "PipeStrings.h" -#include #include +#include #include #include #include @@ -30,17 +31,18 @@ namespace p { // Detects whether a type can be rendered via std::format. template - concept FormattableType = requires(const T& value) - { - std::formatter, Char>{}; - }; + concept FormattableType = + requires(const T& value) { std::formatter, Char>{}; }; } // namespace details + void RegisterSpec(StringView name, TFunction fn); + void RegisterSpec(TFunction fn); + // Self-registering top-level. Spec(name, fn) opens a first describe named `name`. // fn runs immediately during registration, so TFunction (non-owning) is safe. - void Spec(StringView name, TFunction fn); - // Nameless top-level (like go_bandit); use Describe inside fn. - void Spec(TFunction fn); + // Macro handles static-init registration at file scope. +#define Spec(...) \ + static const bool P_CAT(_pipeSpecReg_, __COUNTER__) = (::p::RegisterSpec(__VA_ARGS__), true); // Nested describe. Only valid inside a Spec; otherwise logs an error and ignores. void Describe(StringView name, TFunction fn); @@ -54,11 +56,12 @@ namespace p // Teardown hook attached to the current describe. void AfterEach(std::function fn); - // Settings for a test run. Empty filter runs everything; otherwise only tests - // whose full name contains the filter substring run. + // Settings for a test run. struct TestSettings { - StringView filter; + StringView filter; // Empty runs all; otherwise substring match on full name. + bool listOnly = false; // List test names without running. + bool useColor = true; // Colorized output. }; int RunTests(const TestSettings& settings); @@ -168,9 +171,7 @@ namespace p class ExpectValue { public: - ExpectValue(const Actual& value, const std::source_location& loc) - : value(value) - , loc(loc) + ExpectValue(const Actual& value, const std::source_location& loc) : value(value), loc(loc) {} template @@ -178,8 +179,8 @@ namespace p { if (!details::ValuesEqual::Eval(value, expected)) { - details::Fail(loc, Format( - "Expected {} to equal {}", TestString(value), TestString(expected))); + details::Fail(loc, + Format("Expected {} to equal {}", TestString(value), TestString(expected))); } } @@ -188,8 +189,8 @@ namespace p { if (details::ValuesEqual::Eval(value, expected)) { - details::Fail(loc, Format( - "Expected {} to not equal {}", TestString(value), TestString(expected))); + details::Fail(loc, + Format("Expected {} to not equal {}", TestString(value), TestString(expected))); } } @@ -197,8 +198,8 @@ namespace p { if (!(value < other)) { - details::Fail(loc, Format( - "Expected {} to be less than {}", TestString(value), TestString(other))); + details::Fail(loc, + Format("Expected {} to be less than {}", TestString(value), TestString(other))); } } @@ -206,8 +207,8 @@ namespace p { if (!(value <= other)) { - details::Fail(loc, Format( - "Expected {} to be less or equal to {}", TestString(value), TestString(other))); + details::Fail(loc, Format("Expected {} to be less or equal to {}", + TestString(value), TestString(other))); } } @@ -215,8 +216,8 @@ namespace p { if (!(value > other)) { - details::Fail(loc, Format( - "Expected {} to be greater than {}", TestString(value), TestString(other))); + details::Fail(loc, Format("Expected {} to be greater than {}", TestString(value), + TestString(other))); } } @@ -224,8 +225,8 @@ namespace p { if (!(value >= other)) { - details::Fail(loc, Format( - "Expected {} to be greater or equal to {}", TestString(value), TestString(other))); + details::Fail(loc, Format("Expected {} to be greater or equal to {}", + TestString(value), TestString(other))); } } @@ -250,8 +251,8 @@ namespace p StringView view{value}; if (Strings::Find(view, sub) == StringView::npos) { - details::Fail(loc, Format( - "Expected {} to contain {}", TestString(value), TestString(sub))); + details::Fail( + loc, Format("Expected {} to contain {}", TestString(value), TestString(sub))); } } @@ -260,8 +261,8 @@ namespace p StringView view{value}; if (Strings::Find(view, sub) != StringView::npos) { - details::Fail(loc, Format( - "Expected {} to not contain {}", TestString(value), TestString(sub))); + details::Fail(loc, + Format("Expected {} to not contain {}", TestString(value), TestString(sub))); } } @@ -272,7 +273,8 @@ namespace p // Returns a matcher bound to the caller's source location for reporting. template - ExpectValue Expect(const T& value, const std::source_location loc = std::source_location::current()) + ExpectValue Expect( + const T& value, const std::source_location loc = std::source_location::current()) { return ExpectValue(value, loc); } diff --git a/Src/Tests/PipeTest.cpp b/Src/Tests/PipeTest.cpp index bb6d4ce9..48fbc32b 100644 --- a/Src/Tests/PipeTest.cpp +++ b/Src/Tests/PipeTest.cpp @@ -41,11 +41,11 @@ namespace p TestDescribe root{"", {}, {}, {}, {}}; // Pointer into `root.describes` for the currently-adding describe. - TestDescribe* currentDescribe = nullptr; - i32 failedTests = 0; - i32 runTests = 0; - i32 skippedTests = 0; - i32 currentTestFailureCount = 0; + TestDescribe* currentDescribe = nullptr; + i32 failedTests = 0; + i32 runTests = 0; + i32 skippedTests = 0; + i32 currentTestFailureCount = 0; }; // Function-local static: initialized on first use regardless of the @@ -68,13 +68,13 @@ namespace p { void Fail(const std::source_location& loc, StringView message) { - Error("PipeTests: {}:{}: {}", loc.file_name(), loc.line(), message); + Error("PipeTest: {}:{}: {}", loc.file_name(), loc.line(), message); ++GetTestContext().currentTestFailureCount; } } // namespace details - void Spec(StringView name, TFunction fn) + void RegisterSpec(StringView name, TFunction fn) { TestContext& context = GetTestContext(); TestDescribe describe; @@ -88,9 +88,9 @@ namespace p context.currentDescribe = nullptr; } - void Spec(TFunction fn) + void RegisterSpec(TFunction fn) { - TestContext& context = GetTestContext(); + TestContext& context = GetTestContext(); context.currentDescribe = &context.root; fn(); context.currentDescribe = nullptr; @@ -101,12 +101,12 @@ namespace p TestDescribe*& current = CurrentDescribe(); if (!current) { - Error("PipeTests: Describe('{}') called outside a Spec. Ignoring.", name); + Error("PipeTest: Describe('{}') called outside a Spec. Ignoring.", name); return; } TestDescribe describe; - describe.name = String{name}; + describe.name = String{name}; current->describes.Add(Move(describe)); TestDescribe* prevDescribe = current; current = ¤t->describes.Last(); @@ -119,7 +119,7 @@ namespace p TestDescribe*& current = CurrentDescribe(); if (!current) { - Error("PipeTests: It('{}') called outside a Spec. Ignoring.", name); + Error("PipeTest: It('{}') called outside a Spec. Ignoring.", name); return; } TestCase test; @@ -134,7 +134,7 @@ namespace p TestDescribe*& current = CurrentDescribe(); if (!current) { - Error("PipeTests: XIt('{}') called outside a Spec. Ignoring.", name); + Error("PipeTest: XIt('{}') called outside a Spec. Ignoring.", name); return; } TestCase test; @@ -149,7 +149,7 @@ namespace p TestDescribe*& current = CurrentDescribe(); if (!current) { - Error("PipeTests: BeforeEach called outside a Spec. Ignoring."); + Error("PipeTest: BeforeEach called outside a Spec. Ignoring."); return; } current->beforeEach = fn; @@ -160,7 +160,7 @@ namespace p TestDescribe*& current = CurrentDescribe(); if (!current) { - Error("PipeTests: AfterEach called outside a Spec. Ignoring."); + Error("PipeTest: AfterEach called outside a Spec. Ignoring."); return; } current->afterEach = fn; @@ -169,9 +169,16 @@ namespace p namespace { + // ANSI color codes (used when useColor is true). + const char* kColorReset = "\033[0m"; + const char* kColorGreen = "\033[32m"; + const char* kColorRed = "\033[31m"; + const char* kColorYellow = "\033[33m"; + const char* kColorCyan = "\033[36m"; + const char* kColorDim = "\033[90m"; + static String FullName(const TestDescribe& describe, const TestCase& test) { - // Build "SpecName.SubDescribe.TestName" for reporting. Root has empty name. String result; if (!describe.name.empty()) { @@ -187,8 +194,31 @@ namespace p return filter.empty() || Strings::Contains(fullName, filter); } + static void ListNested(const TestDescribe& describe, StringView filter) + { + for (const TestDescribe& sub : describe.describes) + { + ListNested(sub, filter); + } + for (const TestCase& test : describe.tests) + { + String full = FullName(describe, test); + if (MatchesFilter(full, filter)) + { + if (test.skip) + { + Info(" {}[SKIP]{} {}", kColorYellow, kColorReset, full); + } + else + { + Info(" {}{}{}", kColorDim, full, kColorReset); + } + } + } + } + static void RunNested(TestDescribe& describe, TArray>& beforeHooks, - TArray>& afterHooks, StringView filter) + TArray>& afterHooks, StringView filter, bool useColor) { TestContext& context = GetTestContext(); if (describe.beforeEach) @@ -202,7 +232,7 @@ namespace p for (TestDescribe& sub : describe.describes) { - RunNested(sub, beforeHooks, afterHooks, filter); + RunNested(sub, beforeHooks, afterHooks, filter, useColor); } for (TestCase& test : describe.tests) @@ -224,7 +254,7 @@ namespace p } context.currentTestFailureCount = 0; - bool passed = true; + bool passed = true; try { test.body(); @@ -232,7 +262,7 @@ namespace p catch (...) { passed = false; - Error("PipeTests: test failed by exception: {}", FullName(describe, test)); + Error("PipeTest: test failed by exception: {}", FullName(describe, test)); } passed = passed && (context.currentTestFailureCount == 0); @@ -241,14 +271,30 @@ namespace p afterHooks[i - 1](); } + String name = FullName(describe, test); if (passed) { - Info(" [PASS] {}", FullName(describe, test)); + if (useColor) + { + Info(" {}[PASS]{} {}{}{}{}", kColorGreen, kColorReset, kColorDim, name, + kColorReset, kColorReset); + } + else + { + Info(" [PASS] {}", name); + } } else { ++context.failedTests; - Error(" [FAIL] {}", FullName(describe, test)); + if (useColor) + { + Error(" {}[FAIL]{} {}", kColorRed, kColorReset, name); + } + else + { + Error(" [FAIL] {}", name); + } } } @@ -271,13 +317,40 @@ namespace p context.failedTests = 0; context.skippedTests = 0; - Info("PipeTests: {} describe(s) registered.", context.root.describes.Size()); + const char* cr = settings.useColor ? kColorReset : ""; + const char* cb = settings.useColor ? kColorCyan : ""; + + Info("{}{}describe(s) registered.{}", cb, context.root.describes.Size(), cr); + + // --list: print test names and exit. + if (settings.listOnly) + { + for (TestDescribe& spec : context.root.describes) + { + String specName = spec.name.empty() ? String{"(unnamed)"} : String{spec.name}; + Info("{}", specName); + ListNested(spec, settings.filter); + } + return 0; + } + TArray> beforeHooks; TArray> afterHooks; - RunNested(context.root, beforeHooks, afterHooks, settings.filter); + RunNested(context.root, beforeHooks, afterHooks, settings.filter, settings.useColor); - Info("PipeTests complete: {} run, {} passed, {} failed, {} skipped.", context.runTests, - context.runTests - context.failedTests, context.failedTests, context.skippedTests); + i32 passed = context.runTests - context.failedTests; + if (settings.useColor) + { + Info("{}PipeTest{}: {}{} run{}, {}{}{} passed{}, {}{}{} failed{}, {}{} skipped{}", cb, + kColorReset, context.runTests, cr, cr, passed > 0 ? kColorGreen : "", passed, + cr, context.failedTests > 0 ? kColorRed : "", context.failedTests, cr, + context.skippedTests > 0 ? kColorYellow : "", context.skippedTests, cr); + } + else + { + Info("PipeTest: {} run, {} passed, {} failed, {} skipped.", context.runTests, passed, + context.failedTests, context.skippedTests); + } return context.failedTests == 0 ? 0 : 1; } @@ -292,16 +365,24 @@ namespace p { settings.filter = Strings::RemoveFromStart(arg, StringView{"--filter="}); } - else if (Strings::Equals(arg, StringView{"--filter"})) + else if (Strings::Equals(arg, StringView{"--filter"}) || Strings::Equals(arg, StringView{"-f"})) { if (i + 1 < argc) { settings.filter = StringView{argv[++i]}; } } + else if (Strings::Equals(arg, StringView{"--list"}) || Strings::Equals(arg, StringView{"-l"})) + { + settings.listOnly = true; + } + else if (Strings::Equals(arg, StringView{"--no-color"})) + { + settings.useColor = false; + } else if (Strings::StartsWith(arg, StringView{"--"})) { - Warning("PipeTests: unknown argument '{}'. Ignoring.", arg); + Warning("PipeTest: unknown argument '{}'. Ignoring.", arg); } else if (settings.filter.empty()) { diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index f539ba5b..343194f5 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -1,7 +1,6 @@ # Copyright 2015-2023 Piperift - All rights reserved file(GLOB_RECURSE TESTS_SOURCE_FILES CONFIGURE_DEPENDS *.cpp *.h *.hpp) -list(FILTER TESTS_SOURCE_FILES EXCLUDE REGEX ".*/PipeTests/.*") add_executable(PipeTests ${TESTS_SOURCE_FILES}) target_include_directories(PipeTests PUBLIC .) @@ -13,5 +12,3 @@ target_link_libraries(PipeTests PUBLIC Pipe PipeTest) pipe_add_sanitizers(PipeTests) add_test(NAME PipeTests COMMAND $) - -add_subdirectory(PipeTests) diff --git a/Tests/Containers/Arrays.spec.cpp b/Tests/Containers/Arrays.spec.cpp index bb821b81..43524093 100644 --- a/Tests/Containers/Arrays.spec.cpp +++ b/Tests/Containers/Arrays.spec.cpp @@ -1,7 +1,7 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include +#include using namespace p; @@ -41,11 +41,6 @@ struct CopyType }; -namespace -{ -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() -{ Spec("Containers.Array", []() { It("Can initialize", []() @@ -965,7 +960,7 @@ Spec("Containers.Array", []() }); }); -Describe("Containers.BitArray", []() +Spec("Containers.BitArray", []() { It("Can initialize", []() { @@ -1136,6 +1131,3 @@ Describe("Containers.BitArray", []() }); }); }); -return true; -}(); -} // namespace diff --git a/Tests/Core/Function.spec.cpp b/Tests/Core/Function.spec.cpp index 3fde7c4d..88154fd8 100644 --- a/Tests/Core/Function.spec.cpp +++ b/Tests/Core/Function.spec.cpp @@ -28,70 +28,62 @@ struct Foo inline bool Foo::called = false; -namespace +Spec("Core.Function", []() { -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() +It("Can create empty", []() { -Spec("Core.Function", []() + TFunction func{}; + Expect(func.IsBound()).ToEqual(false); + Expect(bool(func)).ToEqual(false); +}); + +It("Can create from function", []() { - It("Can create empty", []() - { - TFunction func{}; - Expect(func.IsBound()).ToEqual(false); - Expect(bool(func)).ToEqual(false); - }); + TFunction func{Foo::StaticFunc}; - It("Can create from function", []() - { - TFunction func{Foo::StaticFunc}; + Expect(func.IsBound()).ToEqual(true); +}); - Expect(func.IsBound()).ToEqual(true); - }); +It("Can compare functions", []() +{ + TFunction func1{Foo::StaticFunc}; + TFunction func2{Foo::StaticFunc}; + TFunction func3{&Foo::StaticFunc}; - It("Can compare functions", []() - { - TFunction func1{Foo::StaticFunc}; - TFunction func2{Foo::StaticFunc}; - TFunction func3{&Foo::StaticFunc}; + TFunction func4{}; - TFunction func4{}; + TFunction func5{Foo::OtherStaticFunc}; - TFunction func5{Foo::OtherStaticFunc}; + Expect(func1 == func2).ToEqual(true); + Expect(func1 == func3).ToEqual(true); + Expect(func1 == func4).ToEqual(false); + // Expect(func1 == func5).ToEqual(false); +}); - Expect(func1 == func2).ToEqual(true); - Expect(func1 == func3).ToEqual(true); - Expect(func1 == func4).ToEqual(false); - // Expect(func1 == func5).ToEqual(false); - }); +It("Can call static functions", []() +{ + TFunction func1{Foo::StaticFunc}; + TFunction func2{&Foo::StaticFunc}; - It("Can call static functions", []() - { - TFunction func1{Foo::StaticFunc}; - TFunction func2{&Foo::StaticFunc}; + Foo::called = false; + func1(); + Expect(Foo::called).ToEqual(true); - Foo::called = false; - func1(); - Expect(Foo::called).ToEqual(true); + Foo::called = false; + func2(); + Expect(Foo::called).ToEqual(true); +}); - Foo::called = false; - func2(); - Expect(Foo::called).ToEqual(true); - }); +It("Can call lambda functions", []() +{ + static bool called; + called = false; - It("Can call lambda functions", []() + TFunction func = []() { - static bool called; - called = false; - - TFunction func = []() - { - called = true; - }; - func(); - Expect(called).ToEqual(true); - }); + called = true; + }; + func(); + Expect(called).ToEqual(true); +}); }); -return true; -}(); -} // namespace diff --git a/Tests/Core/OwnPtr.spec.cpp b/Tests/Core/OwnPtr.spec.cpp index 1b24e7e4..3aea1bf0 100644 --- a/Tests/Core/OwnPtr.spec.cpp +++ b/Tests/Core/OwnPtr.spec.cpp @@ -41,310 +41,302 @@ struct MockStruct }; -namespace -{ -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() -{ Spec("Core.OwnPtr", []() { - Describe("Owner pointer", []() +Describe("Owner pointer", []() +{ + It("Can initialize to empty", []() { - It("Can initialize to empty", []() - { - TOwnPtr ptr; - Expect(ptr.IsValid()).ToEqual(false); - Expect(ptr.Get()).ToEqual(nullptr); - }); + TOwnPtr ptr; + Expect(ptr.IsValid()).ToEqual(false); + Expect(ptr.Get()).ToEqual(nullptr); + }); - It("Can instantiate", []() - { - TOwnPtr ptr = MakeOwned(); - Expect(ptr.IsValid()).ToEqual(true); - Expect(ptr.Get()).ToNotEqual(nullptr); - }); + It("Can instantiate", []() + { + TOwnPtr ptr = MakeOwned(); + Expect(ptr.IsValid()).ToEqual(true); + Expect(ptr.Get()).ToNotEqual(nullptr); + }); - It("Owner can release", []() + It("Owner can release", []() + { + TOwnPtr owner = MakeOwned(); + Expect(owner.IsValid()).ToEqual(true); + + owner.Delete(); + Expect(owner.IsValid()).ToEqual(false); + }); + + It("Owner is released when destroyed", []() + { + TPtr ptr; { TOwnPtr owner = MakeOwned(); - Expect(owner.IsValid()).ToEqual(true); - owner.Delete(); - Expect(owner.IsValid()).ToEqual(false); - }); + ptr = owner; + Expect(ptr.IsValid()).ToEqual(true); + } + Expect(ptr.IsValid()).ToEqual(false); + }); - It("Owner is released when destroyed", []() + Describe("Ptr Builder", []() + { + It("Calls custom new", []() { - TPtr ptr; - { - TOwnPtr owner = MakeOwned(); - - ptr = owner; - Expect(ptr.IsValid()).ToEqual(true); - } - Expect(ptr.IsValid()).ToEqual(false); + auto owner = MakeOwned(); + Expect(owner->bCalledNew).ToEqual(true); }); - Describe("Ptr Builder", []() + It("Calls custom delete", []() { - It("Calls custom new", []() - { - auto owner = MakeOwned(); - Expect(owner->bCalledNew).ToEqual(true); - }); - - It("Calls custom delete", []() - { - MockStruct::bCalledDelete = false; - auto owner = MakeOwned(); - Expect(MockStruct::bCalledDelete).ToEqual(false); - owner.Delete(); - Expect(MockStruct::bCalledDelete).ToEqual(true); - }); + MockStruct::bCalledDelete = false; + auto owner = MakeOwned(); + Expect(MockStruct::bCalledDelete).ToEqual(false); + owner.Delete(); + Expect(MockStruct::bCalledDelete).ToEqual(true); }); }); +}); - Describe("Weak pointer", []() +Describe("Weak pointer", []() +{ + It("Can initialize to empty", []() { - It("Can initialize to empty", []() - { - TPtr ptr; - Expect(ptr.IsValid()).ToEqual(false); - Expect(ptr.Get()).ToEqual(nullptr); - }); + TPtr ptr; + Expect(ptr.IsValid()).ToEqual(false); + Expect(ptr.Get()).ToEqual(nullptr); + }); - It("Can initialize from owner", []() - { - TOwnPtr owner = MakeOwned(); - TPtr ptr = owner; + It("Can initialize from owner", []() + { + TOwnPtr owner = MakeOwned(); + TPtr ptr = owner; - Expect(ptr.IsValid()).ToEqual(true); - Expect(ptr.Get()).ToNotEqual(nullptr); - }); + Expect(ptr.IsValid()).ToEqual(true); + Expect(ptr.Get()).ToNotEqual(nullptr); + }); - It("Can copy from other weak", []() - { - TOwnPtr owner = MakeOwned(); - auto* raw = owner.Get(); - TPtr ptr = owner; - TPtr ptr2 = ptr; + It("Can copy from other weak", []() + { + TOwnPtr owner = MakeOwned(); + auto* raw = owner.Get(); + TPtr ptr = owner; + TPtr ptr2 = ptr; + + Expect(ptr2.IsValid()).ToEqual(true); + Expect(ptr.Get()).ToEqual(raw); + Expect(ptr2.Get()).ToEqual(raw); + }); - Expect(ptr2.IsValid()).ToEqual(true); - Expect(ptr.Get()).ToEqual(raw); - Expect(ptr2.Get()).ToEqual(raw); - }); + It("Can move from other weak", []() + { + TOwnPtr owner = MakeOwned(); + auto* raw = owner.Get(); + auto weak = owner.AsPtr(); + auto movedWeak = Move(weak); - It("Can move from other weak", []() - { - TOwnPtr owner = MakeOwned(); - auto* raw = owner.Get(); - auto weak = owner.AsPtr(); - auto movedWeak = Move(weak); + Expect(weak.IsValid()).ToEqual(false); + Expect(movedWeak.IsValid()).ToEqual(true); - Expect(weak.IsValid()).ToEqual(false); - Expect(movedWeak.IsValid()).ToEqual(true); + Expect(weak.Get()).ToEqual(nullptr); + Expect(movedWeak.Get()).ToEqual(raw); + }); - Expect(weak.Get()).ToEqual(nullptr); - Expect(movedWeak.Get()).ToEqual(raw); - }); + It("Ptr is null after IsValid() == false", []() + { + TOwnPtr owner = MakeOwned(); + TPtr ptr = owner; + owner.Delete(); - It("Ptr is null after IsValid() == false", []() - { - TOwnPtr owner = MakeOwned(); - TPtr ptr = owner; - owner.Delete(); + Expect(ptr.Get()).ToNotEqual(nullptr); - Expect(ptr.Get()).ToNotEqual(nullptr); + Expect(ptr.IsValid()).ToEqual(false); + Expect(ptr.Get()).ToEqual(nullptr); + }); +}); - Expect(ptr.IsValid()).ToEqual(false); - Expect(ptr.Get()).ToEqual(nullptr); - }); +Describe("Comparisons", []() +{ + It("Owner can equal Owner", []() + { + auto owner = MakeOwned(); + auto owner2 = MakeOwned(); + TOwnPtr ownerEmpty; + + Expect(owner == owner).ToEqual(true); + Expect(owner == owner2).ToEqual(false); + Expect(ownerEmpty == ownerEmpty).ToEqual(true); + Expect(owner == ownerEmpty).ToEqual(false); + + Expect(owner != owner).ToEqual(false); + Expect(owner != owner2).ToEqual(true); + Expect(ownerEmpty != ownerEmpty).ToEqual(false); + Expect(owner != ownerEmpty).ToEqual(true); }); - Describe("Comparisons", []() + It("Owner can equal Weak", []() { - It("Owner can equal Owner", []() - { - auto owner = MakeOwned(); - auto owner2 = MakeOwned(); - TOwnPtr ownerEmpty; - - Expect(owner == owner).ToEqual(true); - Expect(owner == owner2).ToEqual(false); - Expect(ownerEmpty == ownerEmpty).ToEqual(true); - Expect(owner == ownerEmpty).ToEqual(false); - - Expect(owner != owner).ToEqual(false); - Expect(owner != owner2).ToEqual(true); - Expect(ownerEmpty != ownerEmpty).ToEqual(false); - Expect(owner != ownerEmpty).ToEqual(true); - }); + auto owner = MakeOwned(); + auto owner2 = MakeOwned(); + auto weak = owner.AsPtr(); + TOwnPtr ownerEmpty; + TPtr weakEmpty; + + Expect(owner == weak).ToEqual(true); + Expect(owner2 == weak).ToEqual(false); + Expect(ownerEmpty == weak).ToEqual(false); + Expect(ownerEmpty == weakEmpty).ToEqual(true); + + Expect(owner != weak).ToEqual(false); + Expect(owner2 != weak).ToEqual(true); + Expect(ownerEmpty != weak).ToEqual(true); + Expect(ownerEmpty != weakEmpty).ToEqual(false); + }); - It("Owner can equal Weak", []() - { - auto owner = MakeOwned(); - auto owner2 = MakeOwned(); - auto weak = owner.AsPtr(); - TOwnPtr ownerEmpty; - TPtr weakEmpty; - - Expect(owner == weak).ToEqual(true); - Expect(owner2 == weak).ToEqual(false); - Expect(ownerEmpty == weak).ToEqual(false); - Expect(ownerEmpty == weakEmpty).ToEqual(true); - - Expect(owner != weak).ToEqual(false); - Expect(owner2 != weak).ToEqual(true); - Expect(ownerEmpty != weak).ToEqual(true); - Expect(ownerEmpty != weakEmpty).ToEqual(false); - }); + It("Weak can equal Weak", []() + { + auto owner = MakeOwned(); + auto owner2 = MakeOwned(); + auto weak = owner.AsPtr(); + auto weak2 = owner2.AsPtr(); + TPtr weakEmpty; + + Expect(weak == weak).ToEqual(true); + Expect(weak2 == weak).ToEqual(false); + Expect(weakEmpty == weak).ToEqual(false); + Expect(weakEmpty == weakEmpty).ToEqual(true); + + Expect(weak != weak).ToEqual(false); + Expect(weak2 != weak).ToEqual(true); + Expect(weakEmpty != weak).ToEqual(true); + Expect(weakEmpty != weakEmpty).ToEqual(false); + }); - It("Weak can equal Weak", []() - { - auto owner = MakeOwned(); - auto owner2 = MakeOwned(); - auto weak = owner.AsPtr(); - auto weak2 = owner2.AsPtr(); - TPtr weakEmpty; - - Expect(weak == weak).ToEqual(true); - Expect(weak2 == weak).ToEqual(false); - Expect(weakEmpty == weak).ToEqual(false); - Expect(weakEmpty == weakEmpty).ToEqual(true); - - Expect(weak != weak).ToEqual(false); - Expect(weak2 != weak).ToEqual(true); - Expect(weakEmpty != weak).ToEqual(true); - Expect(weakEmpty != weakEmpty).ToEqual(false); - }); + It("Weak can equal Owner", []() + { + auto owner = MakeOwned(); + auto owner2 = MakeOwned(); + auto weak = owner.AsPtr(); + auto weak2 = owner2.AsPtr(); + TOwnPtr ownerEmpty; + TPtr weakEmpty; + + Expect(weak == owner).ToEqual(true); + Expect(weak2 == owner).ToEqual(false); + Expect(weakEmpty == owner).ToEqual(false); + Expect(weakEmpty == ownerEmpty).ToEqual(true); + + Expect(weak != owner).ToEqual(false); + Expect(weak2 != owner).ToEqual(true); + Expect(weakEmpty != owner).ToEqual(true); + Expect(weakEmpty != ownerEmpty).ToEqual(false); + }); +}); - It("Weak can equal Owner", []() - { - auto owner = MakeOwned(); - auto owner2 = MakeOwned(); - auto weak = owner.AsPtr(); - auto weak2 = owner2.AsPtr(); - TOwnPtr ownerEmpty; - TPtr weakEmpty; - - Expect(weak == owner).ToEqual(true); - Expect(weak2 == owner).ToEqual(false); - Expect(weakEmpty == owner).ToEqual(false); - Expect(weakEmpty == ownerEmpty).ToEqual(true); - - Expect(weak != owner).ToEqual(false); - Expect(weak2 != owner).ToEqual(true); - Expect(weakEmpty != owner).ToEqual(true); - Expect(weakEmpty != ownerEmpty).ToEqual(false); - }); +Describe("Counter", []() +{ + It("Adds weaks", []() + { + auto owner = MakeOwned(); + const auto* counter = owner.GetCounter(); + Expect(counter->weakCount).ToEqual(0u); + + auto weak = owner.AsPtr(); + Expect(counter->weakCount).ToEqual(1u); }); - Describe("Counter", []() + It("Removes weaks", []() { - It("Adds weaks", []() + auto owner = MakeOwned(); + const auto* counter = owner.GetCounter(); { - auto owner = MakeOwned(); - const auto* counter = owner.GetCounter(); - Expect(counter->weakCount).ToEqual(0u); - auto weak = owner.AsPtr(); Expect(counter->weakCount).ToEqual(1u); - }); - - It("Removes weaks", []() - { - auto owner = MakeOwned(); - const auto* counter = owner.GetCounter(); - { - auto weak = owner.AsPtr(); - Expect(counter->weakCount).ToEqual(1u); - } - Expect(counter->weakCount).ToEqual(0u); - }); + } + Expect(counter->weakCount).ToEqual(0u); + }); - It("Removes with owner release", []() - { - auto owner = MakeOwned(); - Expect(owner.GetCounter()).ToNotEqual(nullptr); + It("Removes with owner release", []() + { + auto owner = MakeOwned(); + Expect(owner.GetCounter()).ToNotEqual(nullptr); - owner.Delete(); - Expect(owner.GetCounter()).ToEqual(nullptr); - }); + owner.Delete(); + Expect(owner.GetCounter()).ToEqual(nullptr); + }); - It("Removes with no weakCount left", []() - { - auto owner = MakeOwned(); - auto weak = owner.AsPtr(); - Expect(weak.GetCounter()).ToNotEqual(nullptr); + It("Removes with no weakCount left", []() + { + auto owner = MakeOwned(); + auto weak = owner.AsPtr(); + Expect(weak.GetCounter()).ToNotEqual(nullptr); - owner.Delete(); - Expect(weak.GetCounter()).ToNotEqual(nullptr); + owner.Delete(); + Expect(weak.GetCounter()).ToNotEqual(nullptr); - weak.Reset(); - Expect(owner.GetCounter()).ToEqual(nullptr); - }); + weak.Reset(); + Expect(owner.GetCounter()).ToEqual(nullptr); }); +}); - It("Can detect custom PtrBuilders", []() - { - Expect(p::HasCustomPtrBuilder::value).ToEqual(false); - Expect(p::HasCustomPtrBuilder::value).ToEqual(true); - }); +It("Can detect custom PtrBuilders", []() +{ + Expect(p::HasCustomPtrBuilder::value).ToEqual(false); + Expect(p::HasCustomPtrBuilder::value).ToEqual(true); +}); - Describe("Typeless pointer", []() +Describe("Typeless pointer", []() +{ + It("Can convert to OwnPtr from TOwnPtr", []() { - It("Can convert to OwnPtr from TOwnPtr", []() - { - TOwnPtr typedPtr = MakeOwned(); - Expect(typedPtr.IsValid()).ToEqual(true); + TOwnPtr typedPtr = MakeOwned(); + Expect(typedPtr.IsValid()).ToEqual(true); - EmptyStruct* data = typedPtr.Get(); + EmptyStruct* data = typedPtr.Get(); - OwnPtr ptr = Move(typedPtr); - Expect(typedPtr.IsValid()).ToEqual(false); - Expect(ptr.IsValid()).ToEqual(true); - Expect(ptr.Get()).ToEqual(data); - Expect(ptr.Get()).ToEqual(data); - }); - - It("Can convert to TOwnPtr from OwnPtr", []() - { - OwnPtr ptr = MakeOwned(); - Expect(ptr.IsValid()).ToEqual(true); - auto* data = ptr.Get(); + OwnPtr ptr = Move(typedPtr); + Expect(typedPtr.IsValid()).ToEqual(false); + Expect(ptr.IsValid()).ToEqual(true); + Expect(ptr.Get()).ToEqual(data); + Expect(ptr.Get()).ToEqual(data); + }); - TOwnPtr typedPtr = Move(ptr); - Expect(ptr.IsValid()).ToEqual(false); - Expect(typedPtr.IsValid()).ToEqual(true); - Expect(typedPtr.Get()).ToEqual(data); - }); + It("Can convert to TOwnPtr from OwnPtr", []() + { + OwnPtr ptr = MakeOwned(); + Expect(ptr.IsValid()).ToEqual(true); + auto* data = ptr.Get(); + + TOwnPtr typedPtr = Move(ptr); + Expect(ptr.IsValid()).ToEqual(false); + Expect(typedPtr.IsValid()).ToEqual(true); + Expect(typedPtr.Get()).ToEqual(data); + }); - It("Can move", []() - { - OwnPtr ptr1 = MakeOwned(); - Expect(ptr1.IsValid()).ToEqual(true); - Expect(ptr1.GetId()).ToEqual(GetTypeId()); - auto* data = ptr1.Get(); - - OwnPtr ptr2 = Move(ptr1); - Expect(ptr1.IsValid()).ToEqual(false); - Expect(ptr1.Get()).ToEqual(nullptr); - Expect(ptr1.GetId()).ToEqual(TypeId::None()); - - Expect(ptr2.IsValid()).ToEqual(true); - Expect(ptr2.Get()).ToEqual(data); - Expect(ptr2.GetId()).ToEqual(GetTypeId()); - }); + It("Can move", []() + { + OwnPtr ptr1 = MakeOwned(); + Expect(ptr1.IsValid()).ToEqual(true); + Expect(ptr1.GetId()).ToEqual(GetTypeId()); + auto* data = ptr1.Get(); + + OwnPtr ptr2 = Move(ptr1); + Expect(ptr1.IsValid()).ToEqual(false); + Expect(ptr1.Get()).ToEqual(nullptr); + Expect(ptr1.GetId()).ToEqual(TypeId::None()); + + Expect(ptr2.IsValid()).ToEqual(true); + Expect(ptr2.Get()).ToEqual(data); + Expect(ptr2.GetId()).ToEqual(GetTypeId()); + }); - It("Cant retrive invalid types", []() - { - OwnPtr ptr = MakeOwned(); - Expect(ptr.Get()).ToNotEqual(nullptr); - Expect(ptr.Get()).ToEqual(nullptr); - }); + It("Cant retrive invalid types", []() + { + OwnPtr ptr = MakeOwned(); + Expect(ptr.Get()).ToNotEqual(nullptr); + Expect(ptr.Get()).ToEqual(nullptr); }); }); -return true; -}(); -} // namespace +}); diff --git a/Tests/Core/PageBuffer.spec.cpp b/Tests/Core/PageBuffer.spec.cpp index d388ef04..71a6a9e7 100644 --- a/Tests/Core/PageBuffer.spec.cpp +++ b/Tests/Core/PageBuffer.spec.cpp @@ -25,90 +25,82 @@ struct Dummy }; -namespace -{ -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() -{ Spec("ECS.PageBuffer", []() { - It("Can reserve", []() - { - TPageBuffer buffer{GetCurrentArena()}; +It("Can reserve", []() +{ + TPageBuffer buffer{GetCurrentArena()}; - Expect(buffer.GetPagesSize()).ToEqual(0); - Expect(buffer.Capacity()).ToEqual(0); + Expect(buffer.GetPagesSize()).ToEqual(0); + Expect(buffer.Capacity()).ToEqual(0); - buffer.Reserve(2); - Expect(buffer.GetPagesSize()).ToEqual(1); - Expect(buffer.Capacity()).ToEqual(2); + buffer.Reserve(2); + Expect(buffer.GetPagesSize()).ToEqual(1); + Expect(buffer.Capacity()).ToEqual(2); - buffer.Reserve(6); - Expect(buffer.GetPagesSize()).ToEqual(3); - Expect(buffer.Capacity()).ToEqual(6); - }); + buffer.Reserve(6); + Expect(buffer.GetPagesSize()).ToEqual(3); + Expect(buffer.Capacity()).ToEqual(6); +}); - It("Can shrink", []() - { - TPageBuffer buffer{GetCurrentArena()}; - buffer.Reserve(7); - Expect(buffer.GetPagesSize()).ToEqual(4); +It("Can shrink", []() +{ + TPageBuffer buffer{GetCurrentArena()}; + buffer.Reserve(7); + Expect(buffer.GetPagesSize()).ToEqual(4); - buffer.Shrink(4); - Expect(buffer.GetPagesSize()).ToEqual(2); - Expect(buffer.Capacity()).ToEqual(4); - }); + buffer.Shrink(4); + Expect(buffer.GetPagesSize()).ToEqual(2); + Expect(buffer.Capacity()).ToEqual(4); +}); - It("Can insert", []() - { - TPageBuffer buffer{GetCurrentArena()}; - buffer.Reserve(4); +It("Can insert", []() +{ + TPageBuffer buffer{GetCurrentArena()}; + buffer.Reserve(4); - buffer.Insert(0); - Expect(buffer[0].created).ToEqual(true); - Expect(buffer[0].destroyed).ToEqual(false); + buffer.Insert(0); + Expect(buffer[0].created).ToEqual(true); + Expect(buffer[0].destroyed).ToEqual(false); - buffer.Insert(3); - Expect(buffer[3].created).ToEqual(true); - Expect(buffer[3].destroyed).ToEqual(false); - }); + buffer.Insert(3); + Expect(buffer[3].created).ToEqual(true); + Expect(buffer[3].destroyed).ToEqual(false); +}); - It("Can remove", []() - { - TPageBuffer buffer{GetCurrentArena()}; - buffer.Reserve(4); +It("Can remove", []() +{ + TPageBuffer buffer{GetCurrentArena()}; + buffer.Reserve(4); - buffer.Insert(0); - buffer.Insert(3); + buffer.Insert(0); + buffer.Insert(3); - buffer.RemoveAt(0); - // Temporarily disabled due to GCC only test fail - // Expect(buffer[0].destroyed).ToEqual(true); + buffer.RemoveAt(0); + // Temporarily disabled due to GCC only test fail + // Expect(buffer[0].destroyed).ToEqual(true); - buffer.RemoveAt(3); - // Temporarily disabled due to GCC only test fail - // Expect(buffer[3].destroyed).ToEqual(true); - }); + buffer.RemoveAt(3); + // Temporarily disabled due to GCC only test fail + // Expect(buffer[3].destroyed).ToEqual(true); +}); - It("Points to correct page", []() - { - TPageBuffer buffer{GetCurrentArena()}; - buffer.Reserve(7); - - buffer.AssurePage(0); - Expect(buffer.AssurePage(0)).ToNotEqual(nullptr); - Expect(buffer.AssurePage(1)).ToNotEqual(nullptr); - Expect(buffer.AssurePage(2)).ToNotEqual(nullptr); - Expect(buffer.AssurePage(5)).ToNotEqual(nullptr); - - - Expect(buffer.FindPage(0)).ToNotEqual(nullptr); - Expect(buffer.FindPage(1)).ToNotEqual(nullptr); - Expect(buffer.FindPage(2)).ToNotEqual(nullptr); - Expect(buffer.FindPage(5)).ToNotEqual(nullptr); - Expect(buffer.FindPage(6)).ToEqual(nullptr); - }); +It("Points to correct page", []() +{ + TPageBuffer buffer{GetCurrentArena()}; + buffer.Reserve(7); + + buffer.AssurePage(0); + Expect(buffer.AssurePage(0)).ToNotEqual(nullptr); + Expect(buffer.AssurePage(1)).ToNotEqual(nullptr); + Expect(buffer.AssurePage(2)).ToNotEqual(nullptr); + Expect(buffer.AssurePage(5)).ToNotEqual(nullptr); + + + Expect(buffer.FindPage(0)).ToNotEqual(nullptr); + Expect(buffer.FindPage(1)).ToNotEqual(nullptr); + Expect(buffer.FindPage(2)).ToNotEqual(nullptr); + Expect(buffer.FindPage(5)).ToNotEqual(nullptr); + Expect(buffer.FindPage(6)).ToEqual(nullptr); +}); }); -return true; -}(); -} // namespace diff --git a/Tests/Core/PlatformProcess.spec.cpp b/Tests/Core/PlatformProcess.spec.cpp index dbc1db56..60de5581 100644 --- a/Tests/Core/PlatformProcess.spec.cpp +++ b/Tests/Core/PlatformProcess.spec.cpp @@ -10,22 +10,14 @@ using namespace p; -namespace -{ -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() -{ Spec("Core.Subprocess", []() { - It("Can run process", []() - { - Expect(p::RunProcess({""}).IsSet()).ToEqual(false); +It("Can run process", []() +{ + Expect(p::RunProcess({""}).IsSet()).ToEqual(false); #if defined(_MSC_VER) // Test with a silent command (no stdout) - Expect(p::RunProcess({"cmd", "/c", "exit", "0"}).IsSet()).ToEqual(true); + Expect(p::RunProcess({"cmd", "/c", "exit", "0"}).IsSet()).ToEqual(true); #endif - }); }); -return true; -}(); -} // namespace +}); diff --git a/Tests/Core/Set.spec.cpp b/Tests/Core/Set.spec.cpp index c43e2bc8..4c96d06a 100644 --- a/Tests/Core/Set.spec.cpp +++ b/Tests/Core/Set.spec.cpp @@ -14,84 +14,76 @@ struct TypeOfSize }; -namespace +Spec("Core.Set", []() { -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() +It("Can initialize", []() { -Spec("Core.Set", []() + TSet data1{}; + TSet data2(u32(3)); + TSet data3{5, 4, 3, 2}; + + Expect(data1.Size()).ToEqual(0); + Expect(data2.Size()).ToEqual(0); + Expect(data3.Size()).ToEqual(4); + + Expect(data3[2]).ToEqual(2); + Expect(data3[3]).ToEqual(3); + Expect(data3[4]).ToEqual(4); + Expect(data3[5]).ToEqual(5); +}); + +It("Can copy", []() { - It("Can initialize", []() - { - TSet data1{}; - TSet data2(u32(3)); - TSet data3{5, 4, 3, 2}; - - Expect(data1.Size()).ToEqual(0); - Expect(data2.Size()).ToEqual(0); - Expect(data3.Size()).ToEqual(4); - - Expect(data3[2]).ToEqual(2); - Expect(data3[3]).ToEqual(3); - Expect(data3[4]).ToEqual(4); - Expect(data3[5]).ToEqual(5); - }); - - It("Can copy", []() - { - TSet data1{6, 5}; - TSet data2{data1}; - Expect(data1.Size()).ToEqual(2); - Expect(data2.Size()).ToEqual(2); - Expect(data2[5]).ToEqual(5); - Expect(data2[6]).ToEqual(6); - - TSet data3{6, 5}; - TSet data4; - data4 = data3; - Expect(data3.Size()).ToEqual(2); - Expect(data4.Size()).ToEqual(2); - Expect(data4[5]).ToEqual(5); - Expect(data4[6]).ToEqual(6); - }); - - It("Can move", []() - { - TSet data1{4, 3}; - Expect(data1.Size()).ToEqual(2); - - TSet data2{Move(data1)}; - Expect(data1.Size()).ToEqual(0); - Expect(data2.Size()).ToEqual(2); - - TSet data3{4, 3}; - TSet data4; - Expect(data3.Size()).ToEqual(2); - Expect(data4.Size()).ToEqual(0); - - data4 = Move(data3); - Expect(data3.Size()).ToEqual(0); - Expect(data4.Size()).ToEqual(2); - Expect(data4[3]).ToEqual(3); - Expect(data4[4]).ToEqual(4); - }); - - It("Can access data", []() - { - TSet data1; - TSet data2{1, 5}; - - Expect(data1.Size()).ToEqual(0); - Expect(data2.Size()).ToBeGreaterOrEqual(2); - - Expect(data1.Contains(3)).ToEqual(false); - Expect(data2.Contains(1)).ToEqual(true); - Expect(data2.Contains(5)).ToEqual(true); - Expect(data2.Contains(34)).ToEqual(false); - Expect(data2[1]).ToEqual(1); - Expect(data2[5]).ToEqual(5); - }); + TSet data1{6, 5}; + TSet data2{data1}; + Expect(data1.Size()).ToEqual(2); + Expect(data2.Size()).ToEqual(2); + Expect(data2[5]).ToEqual(5); + Expect(data2[6]).ToEqual(6); + + TSet data3{6, 5}; + TSet data4; + data4 = data3; + Expect(data3.Size()).ToEqual(2); + Expect(data4.Size()).ToEqual(2); + Expect(data4[5]).ToEqual(5); + Expect(data4[6]).ToEqual(6); +}); + +It("Can move", []() +{ + TSet data1{4, 3}; + Expect(data1.Size()).ToEqual(2); + + TSet data2{Move(data1)}; + Expect(data1.Size()).ToEqual(0); + Expect(data2.Size()).ToEqual(2); + + TSet data3{4, 3}; + TSet data4; + Expect(data3.Size()).ToEqual(2); + Expect(data4.Size()).ToEqual(0); + + data4 = Move(data3); + Expect(data3.Size()).ToEqual(0); + Expect(data4.Size()).ToEqual(2); + Expect(data4[3]).ToEqual(3); + Expect(data4[4]).ToEqual(4); +}); + +It("Can access data", []() +{ + TSet data1; + TSet data2{1, 5}; + + Expect(data1.Size()).ToEqual(0); + Expect(data2.Size()).ToBeGreaterOrEqual(2); + + Expect(data1.Contains(3)).ToEqual(false); + Expect(data2.Contains(1)).ToEqual(true); + Expect(data2.Contains(5)).ToEqual(true); + Expect(data2.Contains(34)).ToEqual(false); + Expect(data2[1]).ToEqual(1); + Expect(data2[5]).ToEqual(5); +}); }); -return true; -}(); -} // namespace diff --git a/Tests/Core/SpinLock.spec.cpp b/Tests/Core/SpinLock.spec.cpp index 02f9f11c..6701ac4f 100644 --- a/Tests/Core/SpinLock.spec.cpp +++ b/Tests/Core/SpinLock.spec.cpp @@ -11,180 +11,172 @@ using namespace p; -namespace -{ -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() -{ Spec("Core.SpinLock", []() { - Describe("SpinLock", []() +Describe("SpinLock", []() +{ + It("Acquires and releases exclusively", []() { - It("Acquires and releases exclusively", []() - { - SpinLock lock; - ScopedLock guard(lock); + SpinLock lock; + ScopedLock guard(lock); - Expect(lock.Locked()).ToBeTrue(); - Expect(lock.TryLock()).ToBeFalse(); - }); + Expect(lock.Locked()).ToBeTrue(); + Expect(lock.TryLock()).ToBeFalse(); + }); - It("Allows serialized writers to increment a counter", []() - { - SpinLock lock; - i32 counter = 0; + It("Allows serialized writers to increment a counter", []() + { + SpinLock lock; + i32 counter = 0; - constexpr i32 kThreads = 4; - constexpr i32 kPerThread = 10'000; + constexpr i32 kThreads = 4; + constexpr i32 kPerThread = 10'000; - std::vector threads; - std::atomic start{false}; - for (i32 t = 0; t < kThreads; ++t) + std::vector threads; + std::atomic start{false}; + for (i32 t = 0; t < kThreads; ++t) + { + threads.emplace_back([&]() { - threads.emplace_back([&]() + while (!start.load(std::memory_order_acquire)) + {} + for (i32 i = 0; i < kPerThread; ++i) { - while (!start.load(std::memory_order_acquire)) - {} - for (i32 i = 0; i < kPerThread; ++i) - { - ScopedLock guard(lock); - ++counter; - } - }); - } - - start.store(true, std::memory_order_release); - for (auto& thread : threads) - { - thread.join(); - } + ScopedLock guard(lock); + ++counter; + } + }); + } + + start.store(true, std::memory_order_release); + for (auto& thread : threads) + { + thread.join(); + } - Expect(counter).ToEqual(kThreads * kPerThread); - }); + Expect(counter).ToEqual(kThreads * kPerThread); }); +}); - Describe("SharedSpinLock", []() +Describe("SharedSpinLock", []() +{ + It("Exclusive lock excludes a second exclusive lock", []() { - It("Exclusive lock excludes a second exclusive lock", []() - { - SharedSpinLock lock; - ExclusiveScopedLock writer(lock); + SharedSpinLock lock; + ExclusiveScopedLock writer(lock); - Expect(lock.TryLockExclusive()).ToBeFalse(); - }); + Expect(lock.TryLockExclusive()).ToBeFalse(); + }); - It("Exclusive lock excludes shared locks", []() - { - SharedSpinLock lock; - ExclusiveScopedLock writer(lock); + It("Exclusive lock excludes shared locks", []() + { + SharedSpinLock lock; + ExclusiveScopedLock writer(lock); - Expect(lock.TryLockShared()).ToBeFalse(); - }); + Expect(lock.TryLockShared()).ToBeFalse(); + }); - It("Shared lock excludes an exclusive lock", []() - { - SharedSpinLock lock; - SharedScopedLock reader(lock); + It("Shared lock excludes an exclusive lock", []() + { + SharedSpinLock lock; + SharedScopedLock reader(lock); - Expect(lock.TryLockExclusive()).ToBeFalse(); - }); + Expect(lock.TryLockExclusive()).ToBeFalse(); + }); - It("Allows multiple overlapping shared locks", []() - { - SharedSpinLock lock; + It("Allows multiple overlapping shared locks", []() + { + SharedSpinLock lock; - SharedScopedLock r1(lock); - SharedScopedLock r2(lock); - SharedScopedLock r3(lock); + SharedScopedLock r1(lock); + SharedScopedLock r2(lock); + SharedScopedLock r3(lock); - // Readers coexist: shared still acquirable. - Expect(lock.TryLockShared()).ToBeTrue(); - lock.UnlockShared(); + // Readers coexist: shared still acquirable. + Expect(lock.TryLockShared()).ToBeTrue(); + lock.UnlockShared(); - Expect(lock.TryLockExclusive()).ToBeFalse(); - }); + Expect(lock.TryLockExclusive()).ToBeFalse(); + }); - It("Writers exclude each other", []() - { - SharedSpinLock lock; + It("Writers exclude each other", []() + { + SharedSpinLock lock; - ExclusiveScopedLock w1(lock); - Expect(lock.TryLockExclusive()).ToBeFalse(); - }); + ExclusiveScopedLock w1(lock); + Expect(lock.TryLockExclusive()).ToBeFalse(); + }); - It("Writes under exclusive lock are mutually excluded", []() - { - SharedSpinLock lock; - i32 counter = 0; + It("Writes under exclusive lock are mutually excluded", []() + { + SharedSpinLock lock; + i32 counter = 0; - constexpr i32 kThreads = 4; - constexpr i32 kPerThread = 10'000; + constexpr i32 kThreads = 4; + constexpr i32 kPerThread = 10'000; - std::vector threads; - std::atomic start{false}; - for (i32 t = 0; t < kThreads; ++t) + std::vector threads; + std::atomic start{false}; + for (i32 t = 0; t < kThreads; ++t) + { + threads.emplace_back([&]() { - threads.emplace_back([&]() + while (!start.load(std::memory_order_acquire)) + {} + for (i32 i = 0; i < kPerThread; ++i) { - while (!start.load(std::memory_order_acquire)) - {} - for (i32 i = 0; i < kPerThread; ++i) - { - ExclusiveScopedLock writer(lock); - ++counter; - } - }); - } - - start.store(true, std::memory_order_release); - for (auto& thread : threads) - { - thread.join(); - } + ExclusiveScopedLock writer(lock); + ++counter; + } + }); + } + + start.store(true, std::memory_order_release); + for (auto& thread : threads) + { + thread.join(); + } - Expect(counter).ToEqual(kThreads * kPerThread); - }); + Expect(counter).ToEqual(kThreads * kPerThread); + }); - It("Shared readers run concurrently without tearing shared state", []() + It("Shared readers run concurrently without tearing shared state", []() + { + SharedSpinLock lock; + i32 value = 0; + + constexpr i32 kThreads = 4; + constexpr i32 kIterations = 10'000; + + // Shared-side readers are allowed to overlap, so they must only + // read. This just checks that many threads can take the shared + // side simultaneously without deadlocking or corrupting the lock. + std::vector threads; + std::atomic start{false}; + std::atomic reads{0}; + for (i32 t = 0; t < kThreads; ++t) { - SharedSpinLock lock; - i32 value = 0; - - constexpr i32 kThreads = 4; - constexpr i32 kIterations = 10'000; - - // Shared-side readers are allowed to overlap, so they must only - // read. This just checks that many threads can take the shared - // side simultaneously without deadlocking or corrupting the lock. - std::vector threads; - std::atomic start{false}; - std::atomic reads{0}; - for (i32 t = 0; t < kThreads; ++t) + threads.emplace_back([&]() { - threads.emplace_back([&]() + while (!start.load(std::memory_order_acquire)) + {} + for (i32 i = 0; i < kIterations; ++i) { - while (!start.load(std::memory_order_acquire)) - {} - for (i32 i = 0; i < kIterations; ++i) - { - SharedScopedLock reader(lock); - const i32 v = value; - (void)v; - reads.fetch_add(1, std::memory_order_relaxed); - } - }); - } - - start.store(true, std::memory_order_release); - for (auto& thread : threads) - { - thread.join(); - } + SharedScopedLock reader(lock); + const i32 v = value; + (void)v; + reads.fetch_add(1, std::memory_order_relaxed); + } + }); + } + + start.store(true, std::memory_order_release); + for (auto& thread : threads) + { + thread.join(); + } - Expect(reads.load()).ToEqual(kThreads * kIterations); - }); + Expect(reads.load()).ToEqual(kThreads * kIterations); }); }); -return true; -}(); -} // namespace +}); diff --git a/Tests/Core/String.spec.cpp b/Tests/Core/String.spec.cpp index 14c53981..807f3b3c 100644 --- a/Tests/Core/String.spec.cpp +++ b/Tests/Core/String.spec.cpp @@ -18,962 +18,954 @@ static const StringView longText = "0123456789ABCDEFGHIJ0123456789ABC"; static const char* arenaLongText = "This string is long enough to exceed the inline capacity"; -namespace -{ -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() -{ Spec("Strings", []() { - Describe("String", []() +Describe("String", []() +{ + Describe("Construction", []() { - Describe("Construction", []() + It("Can default construct", []() { - It("Can default construct", []() - { - String v{}; - Expect(v.size()).ToEqual(0u); - Expect(v.empty()).ToBeTrue(); - Expect(v.length()).ToEqual(0u); - // c_str() must always return a valid pointer to a null terminator - Expect(v.c_str() != nullptr).ToBeTrue(); - Expect(v.c_str()[0]).ToEqual('\0'); - Expect(v.data() != nullptr).ToBeTrue(); - Expect(v.data()[0]).ToEqual('\0'); - }); + String v{}; + Expect(v.size()).ToEqual(0u); + Expect(v.empty()).ToBeTrue(); + Expect(v.length()).ToEqual(0u); + // c_str() must always return a valid pointer to a null terminator + Expect(v.c_str() != nullptr).ToBeTrue(); + Expect(v.c_str()[0]).ToEqual('\0'); + Expect(v.data() != nullptr).ToBeTrue(); + Expect(v.data()[0]).ToEqual('\0'); + }); - It("Can construct from literal", []() - { - String v{"Kiwi"}; - Expect(v).ToEqual("Kiwi"); - Expect(v.size()).ToEqual(4u); - }); + It("Can construct from literal", []() + { + String v{"Kiwi"}; + Expect(v).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4u); + }); - It("Can construct from literal with count", []() - { - String v{"KiwiApple", 4}; - Expect(v).ToEqual("Kiwi"); - Expect(v.size()).ToEqual(4u); - }); + It("Can construct from literal with count", []() + { + String v{"KiwiApple", 4}; + Expect(v).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4u); + }); - It("Can construct from count and char", []() - { - String v(5, 'x'); - Expect(v).ToEqual("xxxxx"); - Expect(v.size()).ToEqual(5u); - }); + It("Can construct from count and char", []() + { + String v(5, 'x'); + Expect(v).ToEqual("xxxxx"); + Expect(v.size()).ToEqual(5u); + }); - It("Can construct from string view", []() - { - StringView str{"Kiwi"}; - String v{str}; - Expect(v).ToEqual("Kiwi"); - Expect(v.size()).ToEqual(4u); - }); + It("Can construct from string view", []() + { + StringView str{"Kiwi"}; + String v{str}; + Expect(v).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4u); + }); - It("Can construct from string view with pos and count", []() - { - StringView str{"KiwiApple"}; - String v{str, 4, 5}; - Expect(v).ToEqual("Apple"); - }); + It("Can construct from string view with pos and count", []() + { + StringView str{"KiwiApple"}; + String v{str, 4, 5}; + Expect(v).ToEqual("Apple"); + }); - It("Can construct from substring", []() - { - String str{"KiwiApple"}; - String v{str, 4}; - Expect(v).ToEqual("Apple"); - String v2{str, 4, 3}; - Expect(v2).ToEqual("App"); - }); + It("Can construct from substring", []() + { + String str{"KiwiApple"}; + String v{str, 4}; + Expect(v).ToEqual("Apple"); + String v2{str, 4, 3}; + Expect(v2).ToEqual("App"); + }); - It("Can construct from iterators", []() - { - std::string_view sv = "Kiwi"; - String v{sv.begin(), sv.end()}; - Expect(v).ToEqual("Kiwi"); - }); + It("Can construct from iterators", []() + { + std::string_view sv = "Kiwi"; + String v{sv.begin(), sv.end()}; + Expect(v).ToEqual("Kiwi"); + }); - It("Can construct from initializer list", []() - { - String v{'K', 'i', 'w', 'i'}; - Expect(v).ToEqual("Kiwi"); - }); + It("Can construct from initializer list", []() + { + String v{'K', 'i', 'w', 'i'}; + Expect(v).ToEqual("Kiwi"); + }); - It("Can copy construct", []() - { - String v{"Kiwi"}; - String v2{v}; - Expect(v2).ToEqual("Kiwi"); - Expect(v).ToEqual("Kiwi"); - }); + It("Can copy construct", []() + { + String v{"Kiwi"}; + String v2{v}; + Expect(v2).ToEqual("Kiwi"); + Expect(v).ToEqual("Kiwi"); + }); - It("Can move construct", []() - { - String v{"Kiwi"}; - String v2{Move(v)}; - Expect(v2).ToEqual("Kiwi"); - // Moved-from string is valid and empty - Expect(v.size()).ToEqual(0u); - Expect(v.empty()).ToBeTrue(); - Expect(v.c_str()[0]).ToEqual('\0'); - }); + It("Can move construct", []() + { + String v{"Kiwi"}; + String v2{Move(v)}; + Expect(v2).ToEqual("Kiwi"); + // Moved-from string is valid and empty + Expect(v.size()).ToEqual(0u); + Expect(v.empty()).ToBeTrue(); + Expect(v.c_str()[0]).ToEqual('\0'); }); + }); - Describe("Assignment", []() + Describe("Assignment", []() + { + It("Can assign from literal", []() { - It("Can assign from literal", []() - { - String v; - v = "Kiwi"; - Expect(v).ToEqual("Kiwi"); - }); + String v; + v = "Kiwi"; + Expect(v).ToEqual("Kiwi"); + }); - It("Can copy assign", []() - { - String vKiwi{"Kiwi"}; - String vApple{"Apple"}; - String vCopy = vKiwi; - Expect(vCopy).ToEqual("Kiwi"); - vCopy = vApple; - Expect(vCopy).ToEqual("Apple"); - Expect(vCopy).ToEqual(vApple); - }); + It("Can copy assign", []() + { + String vKiwi{"Kiwi"}; + String vApple{"Apple"}; + String vCopy = vKiwi; + Expect(vCopy).ToEqual("Kiwi"); + vCopy = vApple; + Expect(vCopy).ToEqual("Apple"); + Expect(vCopy).ToEqual(vApple); + }); - It("Can move assign", []() - { - String vKiwi{"Kiwi"}; - String vApple{"Apple"}; - String vMove = Move(vKiwi); - Expect(vKiwi.size()).ToEqual(0u); - Expect(vMove).ToEqual("Kiwi"); - vMove = Move(vApple); - Expect(vApple.size()).ToEqual(0u); - Expect(vMove).ToEqual("Apple"); - }); + It("Can move assign", []() + { + String vKiwi{"Kiwi"}; + String vApple{"Apple"}; + String vMove = Move(vKiwi); + Expect(vKiwi.size()).ToEqual(0u); + Expect(vMove).ToEqual("Kiwi"); + vMove = Move(vApple); + Expect(vApple.size()).ToEqual(0u); + Expect(vMove).ToEqual("Apple"); + }); - It("Can assign char", []() - { - String v; - v = 'x'; - Expect(v).ToEqual("x"); - }); + It("Can assign char", []() + { + String v; + v = 'x'; + Expect(v).ToEqual("x"); + }); - It("Can assign initializer list", []() - { - String v; - v = {'K', 'i', 'w', 'i'}; - Expect(v).ToEqual("Kiwi"); - }); + It("Can assign initializer list", []() + { + String v; + v = {'K', 'i', 'w', 'i'}; + Expect(v).ToEqual("Kiwi"); + }); - It("Can assign string view", []() - { - String v; - StringView sv{"Kiwi"}; - v = sv; - Expect(v).ToEqual("Kiwi"); - }); + It("Can assign string view", []() + { + String v; + StringView sv{"Kiwi"}; + v = sv; + Expect(v).ToEqual("Kiwi"); + }); - It("Can assign", []() - { - String v; - v.assign("Kiwi"); - Expect(v).ToEqual("Kiwi"); - v.assign("KiwiApple", 4); - Expect(v).ToEqual("Kiwi"); - v.assign(3, 'x'); - Expect(v).ToEqual("xxx"); - String other{"Apple"}; - v.assign(other); - Expect(v).ToEqual("Apple"); - v.assign(other, 2, 2); - Expect(v).ToEqual("pl"); - StringView sv{"KiwiApple"}; - v.assign(sv, 4, 5); - Expect(v).ToEqual("Apple"); - v.assign({'a', 'b', 'c'}); - Expect(v).ToEqual("abc"); - }); + It("Can assign", []() + { + String v; + v.assign("Kiwi"); + Expect(v).ToEqual("Kiwi"); + v.assign("KiwiApple", 4); + Expect(v).ToEqual("Kiwi"); + v.assign(3, 'x'); + Expect(v).ToEqual("xxx"); + String other{"Apple"}; + v.assign(other); + Expect(v).ToEqual("Apple"); + v.assign(other, 2, 2); + Expect(v).ToEqual("pl"); + StringView sv{"KiwiApple"}; + v.assign(sv, 4, 5); + Expect(v).ToEqual("Apple"); + v.assign({'a', 'b', 'c'}); + Expect(v).ToEqual("abc"); + }); - It("Can self assign", []() - { - String v{"Kiwi"}; - const String& ref = v; - v = ref; - Expect(v).ToEqual("Kiwi"); - }); + It("Can self assign", []() + { + String v{"Kiwi"}; + const String& ref = v; + v = ref; + Expect(v).ToEqual("Kiwi"); + }); - It("Can self assign substrings", []() - { - String v{longText}; - v.assign(v.c_str() + 10); - Expect(v).ToEqual("ABCDEFGHIJ0123456789ABC"); - }); + It("Can self assign substrings", []() + { + String v{longText}; + v.assign(v.c_str() + 10); + Expect(v).ToEqual("ABCDEFGHIJ0123456789ABC"); + }); - It("Can self assign substrings with count", []() - { - String v{longText}; - v.assign(v.c_str() + 5, 10); - Expect(v).ToEqual("56789ABCDE"); - }); + It("Can self assign substrings with count", []() + { + String v{longText}; + v.assign(v.c_str() + 5, 10); + Expect(v).ToEqual("56789ABCDE"); }); + }); - Describe("Element access", []() + Describe("Element access", []() + { + It("Can index", []() { - It("Can index", []() - { - String v{"Kiwi"}; - Expect(v[0]).ToEqual('K'); - Expect(v[3]).ToEqual('i'); - v[0] = 'k'; - Expect(v).ToEqual("kiwi"); - // pos == size() returns reference to null char - Expect(v[4]).ToEqual('\0'); - }); + String v{"Kiwi"}; + Expect(v[0]).ToEqual('K'); + Expect(v[3]).ToEqual('i'); + v[0] = 'k'; + Expect(v).ToEqual("kiwi"); + // pos == size() returns reference to null char + Expect(v[4]).ToEqual('\0'); + }); - It("Can access at", []() - { - String v{"Kiwi"}; - Expect(v.at(0)).ToEqual('K'); - Expect(v.at(3)).ToEqual('i'); - v.at(0) = 'k'; - Expect(v).ToEqual("kiwi"); - }); + It("Can access at", []() + { + String v{"Kiwi"}; + Expect(v.at(0)).ToEqual('K'); + Expect(v.at(3)).ToEqual('i'); + v.at(0) = 'k'; + Expect(v).ToEqual("kiwi"); + }); - It("Can access front and back", []() - { - String v{"Kiwi"}; - Expect(v.front()).ToEqual('K'); - Expect(v.back()).ToEqual('i'); - v.front() = 'P'; - v.back() = 's'; - Expect(v).ToEqual("Piws"); - }); + It("Can access front and back", []() + { + String v{"Kiwi"}; + Expect(v.front()).ToEqual('K'); + Expect(v.back()).ToEqual('i'); + v.front() = 'P'; + v.back() = 's'; + Expect(v).ToEqual("Piws"); + }); - It("Can retrieve data", []() - { - String v{"Kiwi"}; - Expect(v.data()).ToEqual("Kiwi"); - Expect(v.size()).ToEqual(4u); - Expect(strlen(v.data())).ToEqual(4u); - }); + It("Can retrieve data", []() + { + String v{"Kiwi"}; + Expect(v.data()).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4u); + Expect(strlen(v.data())).ToEqual(4u); + }); - It("Can convert to string view", []() - { - String v{"Kiwi"}; - StringView sv = v; - Expect(sv.size()).ToEqual(4u); - Expect(sv).ToEqual(StringView{"Kiwi"}); - StringView wsv{v}; - Expect(wsv).ToEqual(StringView{"Kiwi"}); - }); + It("Can convert to string view", []() + { + String v{"Kiwi"}; + StringView sv = v; + Expect(sv.size()).ToEqual(4u); + Expect(sv).ToEqual(StringView{"Kiwi"}); + StringView wsv{v}; + Expect(wsv).ToEqual(StringView{"Kiwi"}); }); + }); - Describe("Iterators", []() + Describe("Iterators", []() + { + It("Can iterate", []() { - It("Can iterate", []() - { - String v{"Kiwi"}; - u32 i = 0; - for (char c : v) - { - Expect(c).ToEqual("Kiwi"[i]); - ++i; - } - Expect(i).ToEqual(4u); - }); + String v{"Kiwi"}; + u32 i = 0; + for (char c : v) + { + Expect(c).ToEqual("Kiwi"[i]); + ++i; + } + Expect(i).ToEqual(4u); + }); - It("Can iterate const", []() - { - const String v{"Kiwi"}; - u32 i = 0; - for (char c : v) - { - Expect(c).ToEqual("Kiwi"[i]); - ++i; - } - Expect(i).ToEqual(4u); - }); + It("Can iterate const", []() + { + const String v{"Kiwi"}; + u32 i = 0; + for (char c : v) + { + Expect(c).ToEqual("Kiwi"[i]); + ++i; + } + Expect(i).ToEqual(4u); + }); - It("Can iterate manually", []() - { - String v{"Kiwi"}; - auto it = v.begin(); - auto end = v.end(); - Expect(end - it).ToEqual(4); - Expect(*it).ToEqual('K'); - Expect(it[2]).ToEqual('w'); - ++it; - Expect(*it).ToEqual('i'); - it += 2; - Expect(*it).ToEqual('i'); - --it; - Expect(*it).ToEqual('w'); - Expect(it == v.begin() + 2).ToBeTrue(); - Expect(it != v.begin()).ToBeTrue(); - }); + It("Can iterate manually", []() + { + String v{"Kiwi"}; + auto it = v.begin(); + auto end = v.end(); + Expect(end - it).ToEqual(4); + Expect(*it).ToEqual('K'); + Expect(it[2]).ToEqual('w'); + ++it; + Expect(*it).ToEqual('i'); + it += 2; + Expect(*it).ToEqual('i'); + --it; + Expect(*it).ToEqual('w'); + Expect(it == v.begin() + 2).ToBeTrue(); + Expect(it != v.begin()).ToBeTrue(); + }); - It("Can iterate reverse", []() - { - String v{"Kiwi"}; - u32 i = 0; - for (auto rit = v.rbegin(); rit != v.rend(); ++rit) - { - Expect(*rit).ToEqual("Kiwi"[3 - i]); - ++i; - } - Expect(i).ToEqual(4u); - }); + It("Can iterate reverse", []() + { + String v{"Kiwi"}; + u32 i = 0; + for (auto rit = v.rbegin(); rit != v.rend(); ++rit) + { + Expect(*rit).ToEqual("Kiwi"[3 - i]); + ++i; + } + Expect(i).ToEqual(4u); + }); - It("Can iterate c-variants", []() - { - String v{"Kiwi"}; - Expect(*v.cbegin()).ToEqual('K'); - Expect(*(v.cend() - 1)).ToEqual('i'); - Expect(*v.crbegin()).ToEqual('i'); - Expect(*(v.crend() - 1)).ToEqual('K'); - }); + It("Can iterate c-variants", []() + { + String v{"Kiwi"}; + Expect(*v.cbegin()).ToEqual('K'); + Expect(*(v.cend() - 1)).ToEqual('i'); + Expect(*v.crbegin()).ToEqual('i'); + Expect(*(v.crend() - 1)).ToEqual('K'); + }); - It("Can mutate through iterators", []() + It("Can mutate through iterators", []() + { + String v{"Kiwi"}; + std::transform(v.begin(), v.end(), v.begin(), [](char c) { - String v{"Kiwi"}; - std::transform(v.begin(), v.end(), v.begin(), [](char c) - { - return char(c + 1); - }); - Expect(v).ToEqual("Ljxj"); + return char(c + 1); }); + Expect(v).ToEqual("Ljxj"); }); + }); - Describe("Capacity", []() + Describe("Capacity", []() + { + It("Can query size and length", []() { - It("Can query size and length", []() - { - String v{"Kiwi"}; - Expect(v.size()).ToEqual(4u); - Expect(v.length()).ToEqual(4u); - Expect(v.empty()).ToBeFalse(); - }); + String v{"Kiwi"}; + Expect(v.size()).ToEqual(4u); + Expect(v.length()).ToEqual(4u); + Expect(v.empty()).ToBeFalse(); + }); - It("Has short string optimization", []() - { - String v{"Kiwi"}; - // Short strings must fit in the internal buffer - Expect(v.capacity() >= 15u).ToBeTrue(); - Expect(v.capacity() <= 32u).ToBeTrue(); - }); + It("Has short string optimization", []() + { + String v{"Kiwi"}; + // Short strings must fit in the internal buffer + Expect(v.capacity() >= 15u).ToBeTrue(); + Expect(v.capacity() <= 32u).ToBeTrue(); + }); - It("Can reserve", []() - { - String v; - v.reserve(100); - Expect(v.capacity() >= 100u).ToBeTrue(); - Expect(v.size()).ToEqual(0u); - v = "Kiwi"; - Expect(v).ToEqual("Kiwi"); - Expect(v.capacity() >= 100u).ToBeTrue(); - }); + It("Can reserve", []() + { + String v; + v.reserve(100); + Expect(v.capacity() >= 100u).ToBeTrue(); + Expect(v.size()).ToEqual(0u); + v = "Kiwi"; + Expect(v).ToEqual("Kiwi"); + Expect(v.capacity() >= 100u).ToBeTrue(); + }); - It("Can shrink to fit", []() - { - String v; - v.reserve(100); - v = "Kiwi"; - v.shrink_to_fit(); - Expect(v).ToEqual("Kiwi"); - Expect(v.capacity() >= 4u).ToBeTrue(); - Expect(v.capacity() < 100u).ToBeTrue(); - }); + It("Can shrink to fit", []() + { + String v; + v.reserve(100); + v = "Kiwi"; + v.shrink_to_fit(); + Expect(v).ToEqual("Kiwi"); + Expect(v.capacity() >= 4u).ToBeTrue(); + Expect(v.capacity() < 100u).ToBeTrue(); + }); - It("Has max size", []() - { - String v; - // Lengths are stored internally as i32 - Expect(v.max_size()).ToEqual(sizet(Limits::Max() - 1)); - }); + It("Has max size", []() + { + String v; + // Lengths are stored internally as i32 + Expect(v.max_size()).ToEqual(sizet(Limits::Max() - 1)); }); + }); - Describe("Modifiers", []() + Describe("Modifiers", []() + { + It("Can clear", []() { - It("Can clear", []() - { - String v{"Kiwi"}; - v.clear(); - Expect(v.empty()).ToBeTrue(); - Expect(v.size()).ToEqual(0u); - Expect(v.c_str()[0]).ToEqual('\0'); - }); + String v{"Kiwi"}; + v.clear(); + Expect(v.empty()).ToBeTrue(); + Expect(v.size()).ToEqual(0u); + Expect(v.c_str()[0]).ToEqual('\0'); + }); - It("Can push and pop back", []() - { - String v{"Ki"}; - v.push_back('w'); - v.push_back('i'); - Expect(v).ToEqual("Kiwi"); - Expect(v.back()).ToEqual('i'); - v.pop_back(); - Expect(v).ToEqual("Kiw"); - v.pop_back(); - v.pop_back(); - v.pop_back(); - Expect(v).ToEqual(""); - Expect(v.empty()).ToBeTrue(); - }); + It("Can push and pop back", []() + { + String v{"Ki"}; + v.push_back('w'); + v.push_back('i'); + Expect(v).ToEqual("Kiwi"); + Expect(v.back()).ToEqual('i'); + v.pop_back(); + Expect(v).ToEqual("Kiw"); + v.pop_back(); + v.pop_back(); + v.pop_back(); + Expect(v).ToEqual(""); + Expect(v.empty()).ToBeTrue(); + }); - It("Can append", []() - { - String v{"Kiwi"}; - v.append("Apple"); - Expect(v).ToEqual("KiwiApple"); - v.append("Orange", 3); - Expect(v).ToEqual("KiwiAppleOra"); - v.append(3, '-'); - Expect(v).ToEqual("KiwiAppleOra---"); - String other{"End"}; - v.append(other); - Expect(v).ToEqual("KiwiAppleOra---End"); - v.append(other, 1, 2); - Expect(v).ToEqual("KiwiAppleOra---Endnd"); - StringView sv{"View"}; - v.append(sv); - Expect(v).ToEqual("KiwiAppleOra---EndndView"); - v.append(sv, 2, 2); - Expect(v).ToEqual("KiwiAppleOra---EndndViewew"); - v.append({'!', '?'}); - Expect(v).ToEqual("KiwiAppleOra---EndndViewew!?"); - }); + It("Can append", []() + { + String v{"Kiwi"}; + v.append("Apple"); + Expect(v).ToEqual("KiwiApple"); + v.append("Orange", 3); + Expect(v).ToEqual("KiwiAppleOra"); + v.append(3, '-'); + Expect(v).ToEqual("KiwiAppleOra---"); + String other{"End"}; + v.append(other); + Expect(v).ToEqual("KiwiAppleOra---End"); + v.append(other, 1, 2); + Expect(v).ToEqual("KiwiAppleOra---Endnd"); + StringView sv{"View"}; + v.append(sv); + Expect(v).ToEqual("KiwiAppleOra---EndndView"); + v.append(sv, 2, 2); + Expect(v).ToEqual("KiwiAppleOra---EndndViewew"); + v.append({'!', '?'}); + Expect(v).ToEqual("KiwiAppleOra---EndndViewew!?"); + }); - It("Can append with operator+=", []() - { - String v{"Kiwi"}; - v += "Apple"; - Expect(v).ToEqual("KiwiApple"); - v += '!'; - Expect(v).ToEqual("KiwiApple!"); - String other{"End"}; - v += other; - Expect(v).ToEqual("KiwiApple!End"); - v += StringView{"View"}; - Expect(v).ToEqual("KiwiApple!EndView"); - v += {'a', 'b'}; - Expect(v).ToEqual("KiwiApple!EndViewab"); - }); + It("Can append with operator+=", []() + { + String v{"Kiwi"}; + v += "Apple"; + Expect(v).ToEqual("KiwiApple"); + v += '!'; + Expect(v).ToEqual("KiwiApple!"); + String other{"End"}; + v += other; + Expect(v).ToEqual("KiwiApple!End"); + v += StringView{"View"}; + Expect(v).ToEqual("KiwiApple!EndView"); + v += {'a', 'b'}; + Expect(v).ToEqual("KiwiApple!EndViewab"); + }); - It("Can insert", []() - { - String v{"KiwiApple"}; - v.insert(4, "Orange"); - Expect(v).ToEqual("KiwiOrangeApple"); - v.insert(0, "-"); - Expect(v).ToEqual("-KiwiOrangeApple"); - v.insert(v.size(), "!"); - Expect(v).ToEqual("-KiwiOrangeApple!"); - v.insert(0, 3, '='); - Expect(v).ToEqual("===-KiwiOrangeApple!"); - String other{"XX"}; - v.insert(3, other); - Expect(v).ToEqual("===XX-KiwiOrangeApple!"); - StringView sv{"YY"}; - v.insert(5, sv); - Expect(v).ToEqual("===XXYY-KiwiOrangeApple!"); - v.insert(0, 2, 'Z'); - Expect(v).ToEqual("ZZ===XXYY-KiwiOrangeApple!"); - }); + It("Can insert", []() + { + String v{"KiwiApple"}; + v.insert(4, "Orange"); + Expect(v).ToEqual("KiwiOrangeApple"); + v.insert(0, "-"); + Expect(v).ToEqual("-KiwiOrangeApple"); + v.insert(v.size(), "!"); + Expect(v).ToEqual("-KiwiOrangeApple!"); + v.insert(0, 3, '='); + Expect(v).ToEqual("===-KiwiOrangeApple!"); + String other{"XX"}; + v.insert(3, other); + Expect(v).ToEqual("===XX-KiwiOrangeApple!"); + StringView sv{"YY"}; + v.insert(5, sv); + Expect(v).ToEqual("===XXYY-KiwiOrangeApple!"); + v.insert(0, 2, 'Z'); + Expect(v).ToEqual("ZZ===XXYY-KiwiOrangeApple!"); + }); - It("Can insert with iterator", []() - { - String v{"Kiwi"}; - auto it = v.insert(v.begin() + 2, '-'); - Expect(*it).ToEqual('-'); - Expect(v).ToEqual("Ki-wi"); - v.insert(v.end(), 3, '!'); - Expect(v).ToEqual("Ki-wi!!!"); - String other{"AB"}; - v.insert(v.begin(), other.begin(), other.end()); - Expect(v).ToEqual("ABKi-wi!!!"); - v.insert(v.begin() + 2, {'x', 'y'}); - Expect(v).ToEqual("ABxyKi-wi!!!"); - }); + It("Can insert with iterator", []() + { + String v{"Kiwi"}; + auto it = v.insert(v.begin() + 2, '-'); + Expect(*it).ToEqual('-'); + Expect(v).ToEqual("Ki-wi"); + v.insert(v.end(), 3, '!'); + Expect(v).ToEqual("Ki-wi!!!"); + String other{"AB"}; + v.insert(v.begin(), other.begin(), other.end()); + Expect(v).ToEqual("ABKi-wi!!!"); + v.insert(v.begin() + 2, {'x', 'y'}); + Expect(v).ToEqual("ABxyKi-wi!!!"); + }); - It("Can erase", []() - { - String v{"KiwiApple"}; - v.erase(4, 5); - Expect(v).ToEqual("Kiwi"); - v.erase(2); - Expect(v).ToEqual("Ki"); - v.erase(0, 1); - Expect(v).ToEqual("i"); - v.erase(0, 10); - Expect(v).ToEqual(""); - }); + It("Can erase", []() + { + String v{"KiwiApple"}; + v.erase(4, 5); + Expect(v).ToEqual("Kiwi"); + v.erase(2); + Expect(v).ToEqual("Ki"); + v.erase(0, 1); + Expect(v).ToEqual("i"); + v.erase(0, 10); + Expect(v).ToEqual(""); + }); - It("Can erase with iterator", []() - { - String v{"Kiwi"}; - auto it = v.erase(v.begin()); - Expect(*it).ToEqual('i'); - Expect(v).ToEqual("iwi"); - v.erase(v.begin() + 1, v.end()); - Expect(v).ToEqual("i"); - }); + It("Can erase with iterator", []() + { + String v{"Kiwi"}; + auto it = v.erase(v.begin()); + Expect(*it).ToEqual('i'); + Expect(v).ToEqual("iwi"); + v.erase(v.begin() + 1, v.end()); + Expect(v).ToEqual("i"); + }); - It("Can replace", []() - { - String v{"KiwiApple"}; - v.replace(0, 4, "Orange"); - Expect(v).ToEqual("OrangeApple"); - v.replace(0, 6, "X"); - Expect(v).ToEqual("XApple"); - v.replace(v.size() - 3, 3, "Z"); - Expect(v).ToEqual("XApZ"); - String other{"Kiwi"}; - v.replace(0, 4, other); - Expect(v).ToEqual("Kiwi"); - StringView sv{"Two"}; - v.replace(0, 4, sv); - Expect(v).ToEqual("Two"); - v.replace(0, 3, 2, 'y'); - Expect(v).ToEqual("yy"); - }); + It("Can replace", []() + { + String v{"KiwiApple"}; + v.replace(0, 4, "Orange"); + Expect(v).ToEqual("OrangeApple"); + v.replace(0, 6, "X"); + Expect(v).ToEqual("XApple"); + v.replace(v.size() - 3, 3, "Z"); + Expect(v).ToEqual("XApZ"); + String other{"Kiwi"}; + v.replace(0, 4, other); + Expect(v).ToEqual("Kiwi"); + StringView sv{"Two"}; + v.replace(0, 4, sv); + Expect(v).ToEqual("Two"); + v.replace(0, 3, 2, 'y'); + Expect(v).ToEqual("yy"); + }); - It("Can replace with iterators", []() - { - String v{"KiwiApple"}; - v.replace(v.begin(), v.begin() + 4, "Orange"); - Expect(v).ToEqual("OrangeApple"); - }); + It("Can replace with iterators", []() + { + String v{"KiwiApple"}; + v.replace(v.begin(), v.begin() + 4, "Orange"); + Expect(v).ToEqual("OrangeApple"); + }); - It("Can resize", []() - { - String v{"Kiwi"}; - v.resize(2); - Expect(v).ToEqual("Ki"); - v.resize(4); - Expect(v.size()).ToEqual(4u); - Expect(v[2]).ToEqual('\0'); - Expect(v[3]).ToEqual('\0'); - v.resize(6, 'x'); - Expect(v[4]).ToEqual('x'); - Expect(v[5]).ToEqual('x'); - Expect(v.size()).ToEqual(6u); - }); + It("Can resize", []() + { + String v{"Kiwi"}; + v.resize(2); + Expect(v).ToEqual("Ki"); + v.resize(4); + Expect(v.size()).ToEqual(4u); + Expect(v[2]).ToEqual('\0'); + Expect(v[3]).ToEqual('\0'); + v.resize(6, 'x'); + Expect(v[4]).ToEqual('x'); + Expect(v[5]).ToEqual('x'); + Expect(v.size()).ToEqual(6u); + }); - It("Can swap", []() - { - String a{"Kiwi"}; - String b{"Apple"}; - a.swap(b); - Expect(a).ToEqual("Apple"); - Expect(b).ToEqual("Kiwi"); - }); + It("Can swap", []() + { + String a{"Kiwi"}; + String b{"Apple"}; + a.swap(b); + Expect(a).ToEqual("Apple"); + Expect(b).ToEqual("Kiwi"); + }); - It("Can append from self", []() - { - String v{longText}; - v.append(v.c_str()); - Expect(v).ToEqual(std::string{longText} + std::string{longText}); - }); + It("Can append from self", []() + { + String v{longText}; + v.append(v.c_str()); + Expect(v).ToEqual(std::string{longText} + std::string{longText}); + }); - It("Can append self substring", []() - { - String v{longText}; - v.append(v.c_str() + 5); - Expect(v).ToEqual(std::string{longText} + std::string{longText.substr(5)}); - }); + It("Can append self substring", []() + { + String v{longText}; + v.append(v.c_str() + 5); + Expect(v).ToEqual(std::string{longText} + std::string{longText.substr(5)}); + }); - It("Can insert from self", []() - { - String v{longText}; - v.insert(0, v.c_str()); - Expect(v).ToEqual(std::string{longText} + std::string{longText}); - }); + It("Can insert from self", []() + { + String v{longText}; + v.insert(0, v.c_str()); + Expect(v).ToEqual(std::string{longText} + std::string{longText}); + }); - It("Can insert self substring", []() - { - String v{longText}; - v.insert(4, v.c_str() + 5); - Expect(v).ToEqual(std::string{longText.substr(0, 4)} + std::string{longText.substr(5)} - + std::string{longText.substr(4)}); - }); + It("Can insert self substring", []() + { + String v{longText}; + v.insert(4, v.c_str() + 5); + Expect(v).ToEqual(std::string{longText.substr(0, 4)} + std::string{longText.substr(5)} + + std::string{longText.substr(4)}); + }); - It("Can replace with self", []() - { - String v{longText}; - v.replace(0, 4, v.c_str()); - Expect(v).ToEqual(std::string{longText} + std::string{longText.substr(4)}); - }); + It("Can replace with self", []() + { + String v{longText}; + v.replace(0, 4, v.c_str()); + Expect(v).ToEqual(std::string{longText} + std::string{longText.substr(4)}); + }); - It("Can replace self substring with count", []() - { - String v{longText}; - v.replace(5, 10, v.c_str() + 2, 5); - Expect(v).ToEqual(std::string{longText.substr(0, 5)} + "23456" - + std::string{longText.substr(15)}); - }); + It("Can replace self substring with count", []() + { + String v{longText}; + v.replace(5, 10, v.c_str() + 2, 5); + Expect(v).ToEqual(std::string{longText.substr(0, 5)} + "23456" + + std::string{longText.substr(15)}); }); + }); - Describe("Operations", []() + Describe("Operations", []() + { + It("Can get substr", []() { - It("Can get substr", []() - { - String v{"KiwiApple"}; - Expect(v.substr()).ToEqual("KiwiApple"); - Expect(v.substr(4)).ToEqual("Apple"); - Expect(v.substr(4, 3)).ToEqual("App"); - Expect(v.substr(0, 100)).ToEqual("KiwiApple"); - }); + String v{"KiwiApple"}; + Expect(v.substr()).ToEqual("KiwiApple"); + Expect(v.substr(4)).ToEqual("Apple"); + Expect(v.substr(4, 3)).ToEqual("App"); + Expect(v.substr(0, 100)).ToEqual("KiwiApple"); + }); - It("Can copy out", []() - { - String v{"KiwiApple"}; - char buffer[16]{}; - const auto count = v.copy(buffer, 4, 4); - Expect(count).ToEqual(4u); - Expect(buffer).ToEqual("Appl"); - buffer[count] = '\0'; - }); + It("Can copy out", []() + { + String v{"KiwiApple"}; + char buffer[16]{}; + const auto count = v.copy(buffer, 4, 4); + Expect(count).ToEqual(4u); + Expect(buffer).ToEqual("Appl"); + buffer[count] = '\0'; + }); - It("Can compare", []() - { - String v{"Kiwi"}; - String other{"Kiwi"}; - String apple{"Apple"}; - Expect(v.compare(other)).ToEqual(0); - Expect(v.compare(apple) > 0).ToBeTrue(); - Expect(apple.compare(v) < 0).ToBeTrue(); - Expect(v.compare("Kiwi")).ToEqual(0); - Expect(v.compare("Kiwi2") < 0).ToBeTrue(); - Expect(v.compare(StringView{"Kiwi"})).ToEqual(0); - Expect(v.compare(0, 2, String{"Ki"})).ToEqual(0); - Expect(v.compare(2, 2, String{"wi"})).ToEqual(0); - }); + It("Can compare", []() + { + String v{"Kiwi"}; + String other{"Kiwi"}; + String apple{"Apple"}; + Expect(v.compare(other)).ToEqual(0); + Expect(v.compare(apple) > 0).ToBeTrue(); + Expect(apple.compare(v) < 0).ToBeTrue(); + Expect(v.compare("Kiwi")).ToEqual(0); + Expect(v.compare("Kiwi2") < 0).ToBeTrue(); + Expect(v.compare(StringView{"Kiwi"})).ToEqual(0); + Expect(v.compare(0, 2, String{"Ki"})).ToEqual(0); + Expect(v.compare(2, 2, String{"wi"})).ToEqual(0); + }); - It("Can check prefix and suffix", []() - { - String v{"KiwiApple"}; - Expect(v.starts_with("Kiwi")).ToBeTrue(); - Expect(v.starts_with('K')).ToBeTrue(); - Expect(v.starts_with(StringView{"Ki"})).ToBeTrue(); - Expect(v.starts_with("Apple")).ToBeFalse(); - Expect(v.ends_with("Apple")).ToBeTrue(); - Expect(v.ends_with('e')).ToBeTrue(); - Expect(v.ends_with(StringView{"le"})).ToBeTrue(); - Expect(v.ends_with("Kiwi")).ToBeFalse(); - }); + It("Can check prefix and suffix", []() + { + String v{"KiwiApple"}; + Expect(v.starts_with("Kiwi")).ToBeTrue(); + Expect(v.starts_with('K')).ToBeTrue(); + Expect(v.starts_with(StringView{"Ki"})).ToBeTrue(); + Expect(v.starts_with("Apple")).ToBeFalse(); + Expect(v.ends_with("Apple")).ToBeTrue(); + Expect(v.ends_with('e')).ToBeTrue(); + Expect(v.ends_with(StringView{"le"})).ToBeTrue(); + Expect(v.ends_with("Kiwi")).ToBeFalse(); + }); - It("Can check contains", []() - { - String v{"KiwiApple"}; - Expect(v.contains("wiA")).ToBeTrue(); - Expect(v.contains('A')).ToBeTrue(); - Expect(v.contains(StringView{"zzz"})).ToBeFalse(); - Expect(v.contains('z')).ToBeFalse(); - }); + It("Can check contains", []() + { + String v{"KiwiApple"}; + Expect(v.contains("wiA")).ToBeTrue(); + Expect(v.contains('A')).ToBeTrue(); + Expect(v.contains(StringView{"zzz"})).ToBeFalse(); + Expect(v.contains('z')).ToBeFalse(); + }); - It("Can find", []() - { - String v{"KiwiKiwi"}; - Expect(v.find("Kiwi")).ToEqual(0u); - Expect(v.find("Kiwi", 1)).ToEqual(4u); - Expect(v.find("Kiwi", 5)).ToEqual(String::npos); - Expect(v.find('i')).ToEqual(1u); - Expect(v.find('i', 6)).ToEqual(7u); - Expect(v.find('z')).ToEqual(String::npos); - Expect(v.find(String{"Kiwi"})).ToEqual(0u); - Expect(v.find(StringView{"Kiwi"})).ToEqual(0u); - }); + It("Can find", []() + { + String v{"KiwiKiwi"}; + Expect(v.find("Kiwi")).ToEqual(0u); + Expect(v.find("Kiwi", 1)).ToEqual(4u); + Expect(v.find("Kiwi", 5)).ToEqual(String::npos); + Expect(v.find('i')).ToEqual(1u); + Expect(v.find('i', 6)).ToEqual(7u); + Expect(v.find('z')).ToEqual(String::npos); + Expect(v.find(String{"Kiwi"})).ToEqual(0u); + Expect(v.find(StringView{"Kiwi"})).ToEqual(0u); + }); - It("Can rfind", []() - { - String v{"KiwiKiwi"}; - Expect(v.rfind("Kiwi")).ToEqual(4u); - Expect(v.rfind("Kiwi", 3)).ToEqual(0u); - Expect(v.rfind('i')).ToEqual(7u); - Expect(v.rfind('i', 5)).ToEqual(5u); - Expect(v.rfind('z')).ToEqual(String::npos); - Expect(v.rfind(String{"Kiwi"})).ToEqual(4u); - Expect(v.rfind(StringView{"Kiwi"})).ToEqual(4u); - }); + It("Can rfind", []() + { + String v{"KiwiKiwi"}; + Expect(v.rfind("Kiwi")).ToEqual(4u); + Expect(v.rfind("Kiwi", 3)).ToEqual(0u); + Expect(v.rfind('i')).ToEqual(7u); + Expect(v.rfind('i', 5)).ToEqual(5u); + Expect(v.rfind('z')).ToEqual(String::npos); + Expect(v.rfind(String{"Kiwi"})).ToEqual(4u); + Expect(v.rfind(StringView{"Kiwi"})).ToEqual(4u); + }); - It("Can find first of", []() - { - String v{"KiwiApple"}; - Expect(v.find_first_of("pl")).ToEqual(5u); - Expect(v.find_first_of("pl", 6)).ToEqual(6u); - Expect(v.find_first_of('z')).ToEqual(String::npos); - Expect(v.find_first_of("xyz")).ToEqual(String::npos); - Expect(v.find_first_of(StringView{"Ap"})).ToEqual(4u); - }); + It("Can find first of", []() + { + String v{"KiwiApple"}; + Expect(v.find_first_of("pl")).ToEqual(5u); + Expect(v.find_first_of("pl", 6)).ToEqual(6u); + Expect(v.find_first_of('z')).ToEqual(String::npos); + Expect(v.find_first_of("xyz")).ToEqual(String::npos); + Expect(v.find_first_of(StringView{"Ap"})).ToEqual(4u); + }); - It("Can find last of", []() - { - String v{"KiwiApple"}; - Expect(v.find_last_of("pl")).ToEqual(7u); - Expect(v.find_last_of("pl", 6)).ToEqual(6u); - Expect(v.find_last_of('z')).ToEqual(String::npos); - Expect(v.find_last_of(StringView{"Ap"})).ToEqual(6u); - }); + It("Can find last of", []() + { + String v{"KiwiApple"}; + Expect(v.find_last_of("pl")).ToEqual(7u); + Expect(v.find_last_of("pl", 6)).ToEqual(6u); + Expect(v.find_last_of('z')).ToEqual(String::npos); + Expect(v.find_last_of(StringView{"Ap"})).ToEqual(6u); + }); - It("Can find first not of", []() - { - String v{"aaab"}; - Expect(v.find_first_not_of("a")).ToEqual(3u); - Expect(v.find_first_not_of("ab")).ToEqual(String::npos); - Expect(v.find_first_not_of('a')).ToEqual(3u); - Expect(v.find_first_not_of("ab", 3)).ToEqual(String::npos); - }); + It("Can find first not of", []() + { + String v{"aaab"}; + Expect(v.find_first_not_of("a")).ToEqual(3u); + Expect(v.find_first_not_of("ab")).ToEqual(String::npos); + Expect(v.find_first_not_of('a')).ToEqual(3u); + Expect(v.find_first_not_of("ab", 3)).ToEqual(String::npos); + }); - It("Can find last not of", []() - { - String v{"baaa"}; - Expect(v.find_last_not_of("a")).ToEqual(0u); - Expect(v.find_last_not_of("ab")).ToEqual(String::npos); - Expect(v.find_last_not_of('a')).ToEqual(0u); - Expect(v.find_last_not_of("ab", 0)).ToEqual(String::npos); - }); + It("Can find last not of", []() + { + String v{"baaa"}; + Expect(v.find_last_not_of("a")).ToEqual(0u); + Expect(v.find_last_not_of("ab")).ToEqual(String::npos); + Expect(v.find_last_not_of('a')).ToEqual(0u); + Expect(v.find_last_not_of("ab", 0)).ToEqual(String::npos); + }); - It("Has npos", []() - { - Expect(String::npos).ToEqual(sizet(-1)); - Expect(StringView::npos).ToEqual(String::npos); - }); + It("Has npos", []() + { + Expect(String::npos).ToEqual(sizet(-1)); + Expect(StringView::npos).ToEqual(String::npos); }); + }); - Describe("Operators", []() + Describe("Operators", []() + { + It("Can concatenate", []() { - It("Can concatenate", []() - { - String a{"Kiwi"}; - String b{"Apple"}; - Expect(a + b).ToEqual("KiwiApple"); - Expect(a + "X").ToEqual("KiwiX"); - Expect("X" + a).ToEqual("XKiwi"); - Expect(a + '!').ToEqual("Kiwi!"); - Expect('!' + a).ToEqual("!Kiwi"); - Expect(a + StringView{"V"}).ToEqual("KiwiV"); - Expect(StringView{"V"} + a).ToEqual("VKiwi"); - }); + String a{"Kiwi"}; + String b{"Apple"}; + Expect(a + b).ToEqual("KiwiApple"); + Expect(a + "X").ToEqual("KiwiX"); + Expect("X" + a).ToEqual("XKiwi"); + Expect(a + '!').ToEqual("Kiwi!"); + Expect('!' + a).ToEqual("!Kiwi"); + Expect(a + StringView{"V"}).ToEqual("KiwiV"); + Expect(StringView{"V"} + a).ToEqual("VKiwi"); + }); - It("Can chain concatenate", []() - { - String a{"Kiwi"}; - String result = a + " " + "Apple" + '!'; - Expect(result).ToEqual("Kiwi Apple!"); - }); + It("Can chain concatenate", []() + { + String a{"Kiwi"}; + String result = a + " " + "Apple" + '!'; + Expect(result).ToEqual("Kiwi Apple!"); + }); - It("Can compare with other types", []() - { - String v{"Kiwi"}; - Expect(v == String{"Kiwi"}).ToBeTrue(); - Expect(v != String{"Apple"}).ToBeTrue(); - Expect(v == "Kiwi").ToBeTrue(); - Expect(v != "Apple").ToBeTrue(); - Expect("Kiwi" == v).ToBeTrue(); - Expect("Apple" != v).ToBeTrue(); - Expect(v < "Lime").ToBeTrue(); - Expect("Lime" > v).ToBeTrue(); - Expect(v <= String{"Kiwi"}).ToBeTrue(); - Expect(v >= String{"Kiwi"}).ToBeTrue(); - Expect(v == StringView{"Kiwi"}).ToBeTrue(); - Expect(StringView{"Kiwi"} == v).ToBeTrue(); - Expect(v != StringView{"Apple"}).ToBeTrue(); - Expect(StringView{"Apple"} != v).ToBeTrue(); - Expect(v < StringView{"Lime"}).ToBeTrue(); - Expect(StringView{"Lime"} > v).ToBeTrue(); - }); + It("Can compare with other types", []() + { + String v{"Kiwi"}; + Expect(v == String{"Kiwi"}).ToBeTrue(); + Expect(v != String{"Apple"}).ToBeTrue(); + Expect(v == "Kiwi").ToBeTrue(); + Expect(v != "Apple").ToBeTrue(); + Expect("Kiwi" == v).ToBeTrue(); + Expect("Apple" != v).ToBeTrue(); + Expect(v < "Lime").ToBeTrue(); + Expect("Lime" > v).ToBeTrue(); + Expect(v <= String{"Kiwi"}).ToBeTrue(); + Expect(v >= String{"Kiwi"}).ToBeTrue(); + Expect(v == StringView{"Kiwi"}).ToBeTrue(); + Expect(StringView{"Kiwi"} == v).ToBeTrue(); + Expect(v != StringView{"Apple"}).ToBeTrue(); + Expect(StringView{"Apple"} != v).ToBeTrue(); + Expect(v < StringView{"Lime"}).ToBeTrue(); + Expect(StringView{"Lime"} > v).ToBeTrue(); + }); - It("Can three-way compare", []() - { - String a{"Kiwi"}; - String b{"Lime"}; - Expect((a <=> b) < 0).ToBeTrue(); - Expect((b <=> a) > 0).ToBeTrue(); - Expect((a <=> String{"Kiwi"}) == 0).ToBeTrue(); - Expect((a <=> "Kiwi") == 0).ToBeTrue(); - }); + It("Can three-way compare", []() + { + String a{"Kiwi"}; + String b{"Lime"}; + Expect((a <=> b) < 0).ToBeTrue(); + Expect((b <=> a) > 0).ToBeTrue(); + Expect((a <=> String{"Kiwi"}) == 0).ToBeTrue(); + Expect((a <=> "Kiwi") == 0).ToBeTrue(); }); + }); - Describe("Memory", []() + Describe("Memory", []() + { + It("Keeps data valid when growing", []() { - It("Keeps data valid when growing", []() - { - String v; - for (char c = 'a'; c <= 'z'; ++c) - { - v.push_back(c); - } - Expect(v.size()).ToEqual(26u); - Expect(v).ToEqual("abcdefghijklmnopqrstuvwxyz"); - Expect(v.c_str()[26]).ToEqual('\0'); - }); + String v; + for (char c = 'a'; c <= 'z'; ++c) + { + v.push_back(c); + } + Expect(v.size()).ToEqual(26u); + Expect(v).ToEqual("abcdefghijklmnopqrstuvwxyz"); + Expect(v.c_str()[26]).ToEqual('\0'); + }); - It("Can reuse capacity", []() + It("Can reuse capacity", []() + { + String v; + v.reserve(1000); + const auto cap = v.capacity(); + for (u32 i = 0; i < 100; ++i) { - String v; - v.reserve(1000); - const auto cap = v.capacity(); - for (u32 i = 0; i < 100; ++i) - { - v.assign("KiwiAppleOrangeBanana"); - v.clear(); - } - Expect(v.capacity()).ToEqual(cap); - }); + v.assign("KiwiAppleOrangeBanana"); + v.clear(); + } + Expect(v.capacity()).ToEqual(cap); + }); - It("Is valid after move assignment", []() - { - String a{"Kiwi"}; - String b; - b = Move(a); - Expect(b).ToEqual("Kiwi"); - a = "Reused"; - Expect(a).ToEqual("Reused"); - }); + It("Is valid after move assignment", []() + { + String a{"Kiwi"}; + String b; + b = Move(a); + Expect(b).ToEqual("Kiwi"); + a = "Reused"; + Expect(a).ToEqual("Reused"); }); + }); - Describe("Format & Hash", []() + Describe("Format & Hash", []() + { + It("Can be formatted", []() { - It("Can be formatted", []() - { - String v{"Kiwi"}; - Expect(std::format("{}", v)).ToEqual("Kiwi"); - Expect(Format("{}-{}", v, 5)).ToEqual("Kiwi-5"); - String out; - FormatTo(out, "{}!", v); - Expect(out).ToEqual("Kiwi!"); - }); + String v{"Kiwi"}; + Expect(std::format("{}", v)).ToEqual("Kiwi"); + Expect(Format("{}-{}", v, 5)).ToEqual("Kiwi-5"); + String out; + FormatTo(out, "{}!", v); + Expect(out).ToEqual("Kiwi!"); + }); - It("Can be hashed", []() - { - String v{"Kiwi"}; - Expect(GetHash(v)).ToEqual(GetStringHash("Kiwi")); - Expect(GetHash(StringView{"Kiwi"})).ToEqual(GetHash(v)); - }); + It("Can be hashed", []() + { + String v{"Kiwi"}; + Expect(GetHash(v)).ToEqual(GetStringHash("Kiwi")); + Expect(GetHash(StringView{"Kiwi"})).ToEqual(GetHash(v)); }); + }); - Describe("Arena", []() + Describe("Arena", []() + { + It("Can default construct on an arena", []() { - It("Can default construct on an arena", []() - { - MonoLinearArena arena{Memory::KB * 4}; - String v{arena}; - Expect(v.empty()).ToBeTrue(); - Expect(&v.GetArena()).ToEqual(static_cast(&arena)); - // Short strings still use the inline buffer - v = "Kiwi"; - Expect(v).ToEqual("Kiwi"); - Expect(v.capacity() <= 32u).ToBeTrue(); - }); + MonoLinearArena arena{Memory::KB * 4}; + String v{arena}; + Expect(v.empty()).ToBeTrue(); + Expect(&v.GetArena()).ToEqual(static_cast(&arena)); + // Short strings still use the inline buffer + v = "Kiwi"; + Expect(v).ToEqual("Kiwi"); + Expect(v.capacity() <= 32u).ToBeTrue(); + }); - It("Can allocate on an arena", []() - { - MonoLinearArena arena{Memory::KB * 4}; - String v{arena, arenaLongText}; - Expect(v).ToEqual(arenaLongText); - Expect(&v.GetArena()).ToEqual(static_cast(&arena)); - // Long strings must allocate on the arena, not the current arena - Expect(v.capacity() >= v.size()).ToBeTrue(); - }); + It("Can allocate on an arena", []() + { + MonoLinearArena arena{Memory::KB * 4}; + String v{arena, arenaLongText}; + Expect(v).ToEqual(arenaLongText); + Expect(&v.GetArena()).ToEqual(static_cast(&arena)); + // Long strings must allocate on the arena, not the current arena + Expect(v.capacity() >= v.size()).ToBeTrue(); + }); - It("Can construct with count and char on an arena", []() - { - MonoLinearArena arena{Memory::KB * 4}; - String v{arena, 64, 'x'}; - Expect(v.size()).ToEqual(64u); - Expect(&v.GetArena()).ToEqual(static_cast(&arena)); - }); + It("Can construct with count and char on an arena", []() + { + MonoLinearArena arena{Memory::KB * 4}; + String v{arena, 64, 'x'}; + Expect(v.size()).ToEqual(64u); + Expect(&v.GetArena()).ToEqual(static_cast(&arena)); + }); - It("Can copy into an arena", []() - { - MonoLinearArena arena{Memory::KB * 4}; - String original{arenaLongText}; - String v{arena, original}; - Expect(v).ToEqual(original); - Expect(&v.GetArena()).ToEqual(static_cast(&arena)); - }); + It("Can copy into an arena", []() + { + MonoLinearArena arena{Memory::KB * 4}; + String original{arenaLongText}; + String v{arena, original}; + Expect(v).ToEqual(original); + Expect(&v.GetArena()).ToEqual(static_cast(&arena)); + }); - It("Keeps its arena when assigned", []() - { - MonoLinearArena arena{Memory::KB * 4}; - String v{arena}; - v.assign(arenaLongText); - v.append(" with some extra content to force a reallocation"); - Expect(&v.GetArena()).ToEqual(static_cast(&arena)); - Expect(v.starts_with("This string")).ToBeTrue(); - }); + It("Keeps its arena when assigned", []() + { + MonoLinearArena arena{Memory::KB * 4}; + String v{arena}; + v.assign(arenaLongText); + v.append(" with some extra content to force a reallocation"); + Expect(&v.GetArena()).ToEqual(static_cast(&arena)); + Expect(v.starts_with("This string")).ToBeTrue(); }); + }); - Describe("Strings helpers", []() + Describe("Strings helpers", []() + { + It("RemoveFromStart", []() { - It("RemoveFromStart", []() - { - String v{"KiwiApple"}; - Strings::RemoveFromStart(v, 4); - Expect(v).ToEqual("Apple"); - Strings::RemoveFromStart(v, 100); - Expect(v.empty()).ToBeTrue(); - }); + String v{"KiwiApple"}; + Strings::RemoveFromStart(v, 4); + Expect(v).ToEqual("Apple"); + Strings::RemoveFromStart(v, 100); + Expect(v.empty()).ToBeTrue(); + }); - It("RemoveFromEnd", []() - { - String v{"KiwiApple"}; - Strings::RemoveFromEnd(v, 5); - Expect(v).ToEqual("Kiwi"); - Strings::RemoveFromEnd(v, StringView{"wi"}); - Expect(v).ToEqual("Ki"); - Strings::RemoveFromEnd(v, 100); - Expect(v.empty()).ToBeTrue(); - }); + It("RemoveFromEnd", []() + { + String v{"KiwiApple"}; + Strings::RemoveFromEnd(v, 5); + Expect(v).ToEqual("Kiwi"); + Strings::RemoveFromEnd(v, StringView{"wi"}); + Expect(v).ToEqual("Ki"); + Strings::RemoveFromEnd(v, 100); + Expect(v.empty()).ToBeTrue(); + }); - It("RemoveCharFromEnd", []() - { - String v{"Kiwi!"}; - Expect(Strings::RemoveCharFromEnd(v, '!')).ToBeTrue(); - Expect(v).ToEqual("Kiwi"); - Expect(Strings::RemoveCharFromEnd(v, '!')).ToBeFalse(); - Expect(v).ToEqual("Kiwi"); - }); + It("RemoveCharFromEnd", []() + { + String v{"Kiwi!"}; + Expect(Strings::RemoveCharFromEnd(v, '!')).ToBeTrue(); + Expect(v).ToEqual("Kiwi"); + Expect(Strings::RemoveCharFromEnd(v, '!')).ToBeFalse(); + Expect(v).ToEqual("Kiwi"); + }); - It("ToSentenceCase", []() - { - Expect(Strings::ToSentenceCase("")).ToEqual(""); - Expect(Strings::ToSentenceCase("papa")).ToEqual("Papa"); - Expect(Strings::ToSentenceCase("papa ")).ToEqual("Papa "); - Expect(Strings::ToSentenceCase("papa3")).ToEqual("Papa 3"); - Expect(Strings::ToSentenceCase("MisterPotato")).ToEqual("Mister Potato"); - }); + It("ToSentenceCase", []() + { + Expect(Strings::ToSentenceCase("")).ToEqual(""); + Expect(Strings::ToSentenceCase("papa")).ToEqual("Papa"); + Expect(Strings::ToSentenceCase("papa ")).ToEqual("Papa "); + Expect(Strings::ToSentenceCase("papa3")).ToEqual("Papa 3"); + Expect(Strings::ToSentenceCase("MisterPotato")).ToEqual("Mister Potato"); + }); - It("Convert u16 to u8", []() - { - TString utf16string{0x41, 0x0448, 0x65e5, 0xd834, 0xdd1e}; - TString u = Strings::Convert>(utf16string); - Expect(u.size()).ToEqual(10u); - }); - It("Convert u8 to u16", []() - { - TString utf8_with_surrogates = "\xe6\x97\xa5\xd1\x88\xf0\x9d\x84\x9e"; - TString utf16result = - Strings::Convert>(utf8_with_surrogates); - Expect(utf16result.size()).ToEqual(4u); - Expect(utf16result[2] == 0xd834).ToBeTrue(); - Expect(utf16result[3] == 0xdd1e).ToBeTrue(); - }); - It("Convert u32 to u8", []() - { - TString utf32string = {0x448, 0x65E5, 0x10346}; - TString utf8result = Strings::Convert>(utf32string); - Expect(utf8result.size()).ToEqual(9u); - }); - It("Convert u8 to u32", []() - { - TString twochars = "\xe6\x97\xa5\xd1\x88"; - TString utf32result = Strings::Convert>(twochars); - Expect(utf32result.size()).ToEqual(2u); - }); + It("Convert u16 to u8", []() + { + TString utf16string{0x41, 0x0448, 0x65e5, 0xd834, 0xdd1e}; + TString u = Strings::Convert>(utf16string); + Expect(u.size()).ToEqual(10u); + }); + It("Convert u8 to u16", []() + { + TString utf8_with_surrogates = "\xe6\x97\xa5\xd1\x88\xf0\x9d\x84\x9e"; + TString utf16result = + Strings::Convert>(utf8_with_surrogates); + Expect(utf16result.size()).ToEqual(4u); + Expect(utf16result[2] == 0xd834).ToBeTrue(); + Expect(utf16result[3] == 0xdd1e).ToBeTrue(); + }); + It("Convert u32 to u8", []() + { + TString utf32string = {0x448, 0x65E5, 0x10346}; + TString utf8result = Strings::Convert>(utf32string); + Expect(utf8result.size()).ToEqual(9u); + }); + It("Convert u8 to u32", []() + { + TString twochars = "\xe6\x97\xa5\xd1\x88"; + TString utf32result = Strings::Convert>(twochars); + Expect(utf32result.size()).ToEqual(2u); }); }); }); -return true; -}(); -} // namespace +}); diff --git a/Tests/Core/StringView.spec.cpp b/Tests/Core/StringView.spec.cpp index cf5e324b..5137d182 100644 --- a/Tests/Core/StringView.spec.cpp +++ b/Tests/Core/StringView.spec.cpp @@ -8,112 +8,104 @@ using namespace p; -namespace -{ -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() -{ Spec("Strings", []() { - Describe("StringView", []() +Describe("StringView", []() +{ + It("Can assign from literal", []() { - It("Can assign from literal", []() - { - StringView v{"Kiwi"}; - Expect(v).ToEqual("Kiwi"); - Expect(v.size()).ToEqual(4); - }); + StringView v{"Kiwi"}; + Expect(v).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4); + }); - It("Can assign from string", []() - { - String str{"Kiwi"}; - StringView v{str}; - Expect(v).ToEqual("Kiwi"); - Expect(v.size()).ToEqual(4); - }); + It("Can assign from string", []() + { + String str{"Kiwi"}; + StringView v{str}; + Expect(v).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4); + }); - It("Can copy empty", []() - { - StringView str{}; - StringView str2{" "}; - Expect(str.empty()).ToEqual(true); - Expect((u8*)str.data()).ToEqual(nullptr); - Expect(str2.empty()).ToEqual(false); - Expect((u8*)str2.data()).ToNotEqual(nullptr); - str2 = str; - Expect(str2.empty()).ToEqual(true); - Expect((u8*)str2.data()).ToEqual(nullptr); - }); + It("Can copy empty", []() + { + StringView str{}; + StringView str2{" "}; + Expect(str.empty()).ToEqual(true); + Expect((u8*)str.data()).ToEqual(nullptr); + Expect(str2.empty()).ToEqual(false); + Expect((u8*)str2.data()).ToNotEqual(nullptr); + str2 = str; + Expect(str2.empty()).ToEqual(true); + Expect((u8*)str2.data()).ToEqual(nullptr); + }); - It("Can retrieve string data", []() - { - StringView v{"Kiwi"}; - Expect(v.data()).ToEqual("Kiwi"); - Expect(v.size()).ToEqual(4); - StringView v2{}; - Expect((u8*)v2.data()).ToEqual(nullptr); - Expect(v2.size()).ToEqual(0); - }); + It("Can retrieve string data", []() + { + StringView v{"Kiwi"}; + Expect(v.data()).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4); + StringView v2{}; + Expect((u8*)v2.data()).ToEqual(nullptr); + Expect(v2.size()).ToEqual(0); + }); - It("Can compare", []() - { - StringView vKiwi{"Kiwi"}; - StringView vKiwi2{"Kiwi"}; - StringView vApple{"Apple"}; - Expect(vKiwi).ToEqual(vKiwi2); - Expect(vKiwi).ToNotEqual(vApple); - }); + It("Can compare", []() + { + StringView vKiwi{"Kiwi"}; + StringView vKiwi2{"Kiwi"}; + StringView vApple{"Apple"}; + Expect(vKiwi).ToEqual(vKiwi2); + Expect(vKiwi).ToNotEqual(vApple); + }); - It("Can copy", []() - { - StringView vKiwi{"Kiwi"}; - StringView vApple{"Apple"}; - StringView vCopy = vKiwi; - Expect(vCopy).ToEqual("Kiwi"); - Expect(vCopy).ToEqual(vKiwi); - Expect(vCopy).ToNotEqual(vApple); - vCopy = vApple; - Expect(vCopy).ToEqual("Apple"); - Expect(vCopy).ToNotEqual(vKiwi); - Expect(vCopy).ToEqual(vApple); - }); + It("Can copy", []() + { + StringView vKiwi{"Kiwi"}; + StringView vApple{"Apple"}; + StringView vCopy = vKiwi; + Expect(vCopy).ToEqual("Kiwi"); + Expect(vCopy).ToEqual(vKiwi); + Expect(vCopy).ToNotEqual(vApple); + vCopy = vApple; + Expect(vCopy).ToEqual("Apple"); + Expect(vCopy).ToNotEqual(vKiwi); + Expect(vCopy).ToEqual(vApple); + }); - It("Can move", []() - { - StringView vKiwi{"Kiwi"}; - StringView vApple{"Apple"}; - StringView vMove = Move(vKiwi); - Expect(vMove).ToEqual("Kiwi"); - vMove = Move(vApple); - Expect(vMove).ToEqual("Apple"); - }); + It("Can move", []() + { + StringView vKiwi{"Kiwi"}; + StringView vApple{"Apple"}; + StringView vMove = Move(vKiwi); + Expect(vMove).ToEqual("Kiwi"); + vMove = Move(vApple); + Expect(vMove).ToEqual("Apple"); + }); - Describe("Strings", []() + Describe("Strings", []() + { + It("Can Find", []() { - It("Can Find", []() - { - StringView v{"Kiwiwi"}; + StringView v{"Kiwiwi"}; - // Find Chars - Expect(Strings::Find(v, 'K', FindDir::Front)).ToEqual(0); - Expect(Strings::Find(v, 'K', FindDir::Back)).ToEqual(0); - Expect(Strings::Find(v, 'i', FindDir::Front)).ToEqual(1); - Expect(Strings::Find(v, 'i', FindDir::Back)).ToEqual(5); - // Find last chars - Expect(Strings::Find(v, 'w', FindDir::Front, true)).ToEqual(0); // 'K' - Expect(Strings::Find(v, 'w', FindDir::Back, true)).ToEqual(5); // 'i' - Expect(Strings::Find(v, 'K', FindDir::Front, true)).ToEqual(1); // 'i' - Expect(Strings::Find(v, 'i', FindDir::Back, true)).ToEqual(4); // 'w' + // Find Chars + Expect(Strings::Find(v, 'K', FindDir::Front)).ToEqual(0); + Expect(Strings::Find(v, 'K', FindDir::Back)).ToEqual(0); + Expect(Strings::Find(v, 'i', FindDir::Front)).ToEqual(1); + Expect(Strings::Find(v, 'i', FindDir::Back)).ToEqual(5); + // Find last chars + Expect(Strings::Find(v, 'w', FindDir::Front, true)).ToEqual(0); // 'K' + Expect(Strings::Find(v, 'w', FindDir::Back, true)).ToEqual(5); // 'i' + Expect(Strings::Find(v, 'K', FindDir::Front, true)).ToEqual(1); // 'i' + Expect(Strings::Find(v, 'i', FindDir::Back, true)).ToEqual(4); // 'w' - // Find Sub-strings - Expect(Strings::Find(v, "Ki", FindDir::Front)).ToEqual(0); - Expect(Strings::Find(v, "Ki", FindDir::Back)).ToEqual(0); - Expect(Strings::Find(v, "wi", FindDir::Front)).ToEqual(2); - Expect(Strings::Find(v, "wi", FindDir::Back)).ToEqual(4); - }); + // Find Sub-strings + Expect(Strings::Find(v, "Ki", FindDir::Front)).ToEqual(0); + Expect(Strings::Find(v, "Ki", FindDir::Back)).ToEqual(0); + Expect(Strings::Find(v, "wi", FindDir::Front)).ToEqual(2); + Expect(Strings::Find(v, "wi", FindDir::Back)).ToEqual(4); }); }); }); -return true; -}(); -} // namespace +}); diff --git a/Tests/Core/Tag.spec.cpp b/Tests/Core/Tag.spec.cpp index 72e4989f..b1f8bfff 100644 --- a/Tests/Core/Tag.spec.cpp +++ b/Tests/Core/Tag.spec.cpp @@ -7,106 +7,98 @@ using namespace p; -namespace +Spec("Core.Tag", []() { -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() +It("Can copy empty", []() { -Spec("Core.Tag", []() + Tag tag{}; + Tag tag2{"Ahh"}; + Expect(p::GetHash(tag)).ToEqual(0); + Expect(tag.IsNone()).ToEqual(true); + Expect(p::GetHash(tag2)).ToNotEqual(0); + Expect(tag2.IsNone()).ToEqual(false); + tag2 = tag; + Expect(p::GetHash(tag2)).ToEqual(0); + Expect(tag2.IsNone()).ToEqual(true); +}); +It("Can assign from literal", []() { - It("Can copy empty", []() - { - Tag tag{}; - Tag tag2{"Ahh"}; - Expect(p::GetHash(tag)).ToEqual(0); - Expect(tag.IsNone()).ToEqual(true); - Expect(p::GetHash(tag2)).ToNotEqual(0); - Expect(tag2.IsNone()).ToEqual(false); - tag2 = tag; - Expect(p::GetHash(tag2)).ToEqual(0); - Expect(tag2.IsNone()).ToEqual(true); - }); - It("Can assign from literal", []() - { - Tag tag{"Kiwi"}; - Expect(tag.AsString()).ToEqual("Kiwi"); - }); + Tag tag{"Kiwi"}; + Expect(tag.AsString()).ToEqual("Kiwi"); +}); - It("Can assign from string", []() - { - String str{"Kiwi"}; - Tag tag{str}; - Expect(tag.AsString()).ToEqual("Kiwi"); - }); +It("Can assign from string", []() +{ + String str{"Kiwi"}; + Tag tag{str}; + Expect(tag.AsString()).ToEqual("Kiwi"); +}); - It("Can retrieve string data", []() - { - Tag tag{"Kiwi"}; - Expect(tag.AsString()).ToEqual("Kiwi"); - }); +It("Can retrieve string data", []() +{ + Tag tag{"Kiwi"}; + Expect(tag.AsString()).ToEqual("Kiwi"); +}); - It("Can compare tags", []() - { - Tag tagKiwi{"Kiwi"}; - Tag tagKiwi2{"Kiwi"}; - Tag tagApple{"Apple"}; - Expect(tagKiwi).ToEqual(tagKiwi2); - Expect(tagKiwi).ToNotEqual(tagApple); - }); +It("Can compare tags", []() +{ + Tag tagKiwi{"Kiwi"}; + Tag tagKiwi2{"Kiwi"}; + Tag tagApple{"Apple"}; + Expect(tagKiwi).ToEqual(tagKiwi2); + Expect(tagKiwi).ToNotEqual(tagApple); +}); - It("Different instances share string allocation", []() - { - Tag tagKiwi{"Kiwi"}; - Tag tagKiwi2{"Kiwi"}; - Tag tagApple{"Apple"}; - Expect(tagKiwi.AsString().data()).ToEqual(tagKiwi2.AsString().data()); - Expect(tagKiwi.AsString().data()).ToNotEqual(tagApple.AsString().data()); - }); +It("Different instances share string allocation", []() +{ + Tag tagKiwi{"Kiwi"}; + Tag tagKiwi2{"Kiwi"}; + Tag tagApple{"Apple"}; + Expect(tagKiwi.AsString().data()).ToEqual(tagKiwi2.AsString().data()); + Expect(tagKiwi.AsString().data()).ToNotEqual(tagApple.AsString().data()); +}); - It("Can check invalid/none", []() - { - Tag tagValid{"Kiwi"}; - Tag tagInvalid{}; - Expect(tagValid.IsNone()).ToEqual(false); - Expect(tagValid).ToNotEqual(Tag::None()); - Expect(tagInvalid.IsNone()).ToEqual(true); - Expect(tagInvalid).ToEqual(Tag::None()); - }); +It("Can check invalid/none", []() +{ + Tag tagValid{"Kiwi"}; + Tag tagInvalid{}; + Expect(tagValid.IsNone()).ToEqual(false); + Expect(tagValid).ToNotEqual(Tag::None()); + Expect(tagInvalid.IsNone()).ToEqual(true); + Expect(tagInvalid).ToEqual(Tag::None()); +}); - It("Contains correct hashes", []() - { - Tag tagKiwi{"Kiwi"}; - Tag tagKiwi2{"Kiwi"}; - Expect(p::GetHash(tagKiwi)).ToEqual(p::GetHash(tagKiwi2)); - Expect(tagKiwi.GetStringHash()).ToEqual(p::GetHash("Kiwi")); - }); +It("Contains correct hashes", []() +{ + Tag tagKiwi{"Kiwi"}; + Tag tagKiwi2{"Kiwi"}; + Expect(p::GetHash(tagKiwi)).ToEqual(p::GetHash(tagKiwi2)); + Expect(tagKiwi.GetStringHash()).ToEqual(p::GetHash("Kiwi")); +}); - It("Can copy tag", []() - { - Tag tagKiwi{"Kiwi"}; - Tag tagApple{"Apple"}; - Tag tagCopy = tagKiwi; - Expect(tagCopy.AsString()).ToEqual("Kiwi"); - Expect(tagCopy).ToEqual(tagKiwi); - Expect(tagCopy).ToNotEqual(tagApple); - tagCopy = tagApple; - Expect(tagCopy.AsString()).ToEqual("Apple"); - Expect(tagCopy).ToNotEqual(tagKiwi); - Expect(tagCopy).ToEqual(tagApple); - }); +It("Can copy tag", []() +{ + Tag tagKiwi{"Kiwi"}; + Tag tagApple{"Apple"}; + Tag tagCopy = tagKiwi; + Expect(tagCopy.AsString()).ToEqual("Kiwi"); + Expect(tagCopy).ToEqual(tagKiwi); + Expect(tagCopy).ToNotEqual(tagApple); + tagCopy = tagApple; + Expect(tagCopy.AsString()).ToEqual("Apple"); + Expect(tagCopy).ToNotEqual(tagKiwi); + Expect(tagCopy).ToEqual(tagApple); +}); - It("Can move tag", []() - { - Tag tagKiwi{"Kiwi"}; - Tag tagApple{"Apple"}; - Tag tagMove = Move(tagKiwi); - Expect(tagKiwi).ToEqual(Tag::None()); - Expect(tagMove.AsString()).ToEqual("Kiwi"); - tagMove = Move(tagApple); - Expect(tagApple).ToEqual(Tag::None()); - Expect(tagMove.AsString()).ToEqual("Apple"); - }); +It("Can move tag", []() +{ + Tag tagKiwi{"Kiwi"}; + Tag tagApple{"Apple"}; + Tag tagMove = Move(tagKiwi); + Expect(tagKiwi).ToEqual(Tag::None()); + Expect(tagMove.AsString()).ToEqual("Kiwi"); + tagMove = Move(tagApple); + Expect(tagApple).ToEqual(Tag::None()); + Expect(tagMove.AsString()).ToEqual("Apple"); +}); }); -return true; -}(); -} // namespace diff --git a/Tests/ECS/Components.spec.cpp b/Tests/ECS/Components.spec.cpp index f0727f60..0998e656 100644 --- a/Tests/ECS/Components.spec.cpp +++ b/Tests/ECS/Components.spec.cpp @@ -44,255 +44,247 @@ struct TestComponent u32 TestComponent::destructed = 0; -namespace +Spec("ECS.Components", []() { -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() +It("Can add one component", []() { -Spec("ECS.Components", []() + IdContext ctx; + Id id = AddId(ctx); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + + ctx.Add(id); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + + ctx.Add(id); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.TryGet(id)).ToNotEqual(nullptr); +}); + +It("Can remove one component", []() { - It("Can add one component", []() - { - IdContext ctx; - Id id = AddId(ctx); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - - ctx.Add(id); - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - - ctx.Add(id); - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.TryGet(id)).ToNotEqual(nullptr); - }); - - It("Can remove one component", []() - { - IdContext ctx; - Id id = AddId(ctx); - ctx.Add(id); - - ctx.Remove(id); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - - NonEmptyComponent::destructed = 0; - ctx.Remove(id); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - Expect(NonEmptyComponent::destructed).ToEqual(1); - }); - - It("Can add many components", []() - { - IdContext ctx; - TArray ids{3}; - AddId(ctx, ids); - ctx.AddN(ids, NonEmptyComponent{2}); - - for (Id id : ids) - { - auto* data = ctx.TryGet(id); - Expect(data).ToNotEqual(nullptr); - Expect(data->a).ToEqual(2); - } - }); - - It("Can remove many components", []() - { - IdContext ctx; - TArray ids{3}; - AddId(ctx, ids); - ctx.AddN(ids, NonEmptyComponent{2}); - - NonEmptyComponent::destructed = 0; - TView firstTwo{ids.Data(), ids.Data() + 2}; - ctx.Remove(firstTwo); - Expect(NonEmptyComponent::destructed).ToEqual(2); - Expect(ctx.TryGet(ids[0])).ToEqual(nullptr); - Expect(ctx.TryGet(ids[1])).ToEqual(nullptr); - Expect(ctx.TryGet(ids[2])).ToNotEqual(nullptr); - - // Repeat in different order - ctx.AddN(ids, NonEmptyComponent{2}); - - NonEmptyComponent::destructed = 0; - TView lastTwo{ids.Data() + 1, ids.Data() + 3}; - ctx.Remove(lastTwo); - Expect(NonEmptyComponent::destructed).ToEqual(2); - Expect(ctx.TryGet(ids[0])).ToNotEqual(nullptr); - Expect(ctx.TryGet(ids[1])).ToEqual(nullptr); - Expect(ctx.TryGet(ids[2])).ToEqual(nullptr); - }); - - It("Components are removed after node is deleted", []() + IdContext ctx; + Id id = AddId(ctx); + ctx.Add(id); + + ctx.Remove(id); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + + NonEmptyComponent::destructed = 0; + ctx.Remove(id); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + Expect(NonEmptyComponent::destructed).ToEqual(1); +}); + +It("Can add many components", []() +{ + IdContext ctx; + TArray ids{3}; + AddId(ctx, ids); + ctx.AddN(ids, NonEmptyComponent{2}); + + for (Id id : ids) { - IdContext ctx; - Id id = AddId(ctx); - ctx.Add(id); + auto* data = ctx.TryGet(id); + Expect(data).ToNotEqual(nullptr); + Expect(data->a).ToEqual(2); + } +}); - RmId(ctx, id, p::RmIdFlags::Instant); - Expect(ctx.IsValid(id)).ToBeFalse(); +It("Can remove many components", []() +{ + IdContext ctx; + TArray ids{3}; + AddId(ctx, ids); + ctx.AddN(ids, NonEmptyComponent{2}); + + NonEmptyComponent::destructed = 0; + TView firstTwo{ids.Data(), ids.Data() + 2}; + ctx.Remove(firstTwo); + Expect(NonEmptyComponent::destructed).ToEqual(2); + Expect(ctx.TryGet(ids[0])).ToEqual(nullptr); + Expect(ctx.TryGet(ids[1])).ToEqual(nullptr); + Expect(ctx.TryGet(ids[2])).ToNotEqual(nullptr); + + // Repeat in different order + ctx.AddN(ids, NonEmptyComponent{2}); + + NonEmptyComponent::destructed = 0; + TView lastTwo{ids.Data() + 1, ids.Data() + 3}; + ctx.Remove(lastTwo); + Expect(NonEmptyComponent::destructed).ToEqual(2); + Expect(ctx.TryGet(ids[0])).ToNotEqual(nullptr); + Expect(ctx.TryGet(ids[1])).ToEqual(nullptr); + Expect(ctx.TryGet(ids[2])).ToEqual(nullptr); +}); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - }); +It("Components are removed after node is deleted", []() +{ + IdContext ctx; + Id id = AddId(ctx); + ctx.Add(id); - It("Components are removed after node is deleted (deferred)", []() - { - IdContext ctx; - Id id = AddId(ctx); - ctx.Add(id); - - RmId(ctx, id); - Expect(ctx.IsValid(id)).ToBeFalse(); - - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.TryGet(id)).ToNotEqual(nullptr); - - FlushDeferredRemovals(ctx); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - }); - - It("Components keep state when added", []() - { - IdContext ctx; - Id id = AddId(ctx); - ctx.AddN(id, NonEmptyComponent{2}); - Expect(ctx.TryGet(id)).ToNotEqual(nullptr); - Expect(ctx.Get(id).a).ToEqual(2); - }); - - It("Can copy registry", []() - { - IdContext ctxa; + RmId(ctx, id, p::RmIdFlags::Instant); + Expect(ctx.IsValid(id)).ToBeFalse(); - Id id = AddId(ctxa); - ctxa.Add(id); - Id id2 = AddId(ctxa); - ctxa.AddN(id2, NonEmptyComponent{2}); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); +}); - IdContext ctxb{ctxa}; - Expect(ctxb.Has(id)).ToBeTrue(); - Expect(ctxb.Has(id)).ToBeTrue(); - Expect(ctxb.TryGet(id)).ToNotEqual(nullptr); +It("Components are removed after node is deleted (deferred)", []() +{ + IdContext ctx; + Id id = AddId(ctx); + ctx.Add(id); + + RmId(ctx, id); + Expect(ctx.IsValid(id)).ToBeFalse(); + + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.TryGet(id)).ToNotEqual(nullptr); + + FlushDeferredRemovals(ctx); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); +}); - // Holds component values - Expect(ctxb.Has(id2)).ToBeTrue(); - Expect(ctxb.Get(id2).a).ToEqual(2); - }); +It("Components keep state when added", []() +{ + IdContext ctx; + Id id = AddId(ctx); + ctx.AddN(id, NonEmptyComponent{2}); + Expect(ctx.TryGet(id)).ToNotEqual(nullptr); + Expect(ctx.Get(id).a).ToEqual(2); +}); - It("Can check components", []() - { - IdContext ctx; - Id id = NoId; - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.Has(id)).ToBeFalse(); +It("Can copy registry", []() +{ + IdContext ctxa; - id = AddId(ctx); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.Has(id)).ToBeFalse(); + Id id = AddId(ctxa); + ctxa.Add(id); + Id id2 = AddId(ctxa); + ctxa.AddN(id2, NonEmptyComponent{2}); - ctx.Add(id); - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.Has(id)).ToBeTrue(); - }); + IdContext ctxb{ctxa}; + Expect(ctxb.Has(id)).ToBeTrue(); + Expect(ctxb.Has(id)).ToBeTrue(); + Expect(ctxb.TryGet(id)).ToNotEqual(nullptr); - It("Can destroy components on reset", []() - { - NonEmptyComponent::destructed = 0; - TestComponent::destructed = 0; - - IdContext ctx; - TArray ids{3}; - AddId(ctx, ids); - ctx.AddN(ids, NonEmptyComponent{2}); - ctx.AddN(ids); - - ctx.Remove(ids); - ctx.Remove(ids[0]); - Expect( - NonEmptyComponent::destructed).ToEqual(4); // 3 + 1 (passed by value on Add()) - Expect(TestComponent::destructed).ToEqual(2); // 1 + 1 (passed by value on Add()) - - NonEmptyComponent::destructed = 0; - TestComponent::destructed = 0; - ctx.Reset(); - - Expect(NonEmptyComponent::destructed).ToEqual(0); - Expect(TestComponent::destructed).ToEqual(2); - }); - - It("Components are removed with the entity", []() - { - IdContext ctx; - Id id = AddId(ctx); - ctx.Add(id); - RmId(ctx, id, p::RmIdFlags::Instant); - Expect(ctx.IsValid(id)).ToBeFalse(); - - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - }); - - It("Components are removed with the entity (deferred)", []() - { - IdContext ctx; - Id id = AddId(ctx); - ctx.Add(id); - RmId(ctx, id); - Expect(ctx.IsValid(id)).ToBeFalse(); - - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.TryGet(id)).ToNotEqual(nullptr); - - FlushDeferredRemovals(ctx); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - }); - - It("Can access components on recicled entities", []() - { - IdContext ctx; - Id id = AddId(ctx); - ctx.Add(id); - RmId(ctx, id); - - id = AddId(ctx); - ctx.Add(id); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.TryGet(id)).ToNotEqual(nullptr); - }); - - It("Can access CRemoved", []() - { - IdContext ctx; - Id id = AddId(ctx); - ctx.Add(id); - RmId(ctx, id); - - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.TryGet(id)).ToNotEqual(nullptr); - }); + // Holds component values + Expect(ctxb.Has(id2)).ToBeTrue(); + Expect(ctxb.Get(id2).a).ToEqual(2); +}); + +It("Can check components", []() +{ + IdContext ctx; + Id id = NoId; + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.Has(id)).ToBeFalse(); + + id = AddId(ctx); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.Has(id)).ToBeFalse(); + + ctx.Add(id); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.Has(id)).ToBeTrue(); +}); + +It("Can destroy components on reset", []() +{ + NonEmptyComponent::destructed = 0; + TestComponent::destructed = 0; + + IdContext ctx; + TArray ids{3}; + AddId(ctx, ids); + ctx.AddN(ids, NonEmptyComponent{2}); + ctx.AddN(ids); + + ctx.Remove(ids); + ctx.Remove(ids[0]); + Expect( + NonEmptyComponent::destructed).ToEqual(4); // 3 + 1 (passed by value on Add()) + Expect(TestComponent::destructed).ToEqual(2); // 1 + 1 (passed by value on Add()) + + NonEmptyComponent::destructed = 0; + TestComponent::destructed = 0; + ctx.Reset(); + + Expect(NonEmptyComponent::destructed).ToEqual(0); + Expect(TestComponent::destructed).ToEqual(2); +}); + +It("Components are removed with the entity", []() +{ + IdContext ctx; + Id id = AddId(ctx); + ctx.Add(id); + RmId(ctx, id, p::RmIdFlags::Instant); + Expect(ctx.IsValid(id)).ToBeFalse(); + + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); +}); + +It("Components are removed with the entity (deferred)", []() +{ + IdContext ctx; + Id id = AddId(ctx); + ctx.Add(id); + RmId(ctx, id); + Expect(ctx.IsValid(id)).ToBeFalse(); + + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.TryGet(id)).ToNotEqual(nullptr); + + FlushDeferredRemovals(ctx); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); +}); + +It("Can access components on recicled entities", []() +{ + IdContext ctx; + Id id = AddId(ctx); + ctx.Add(id); + RmId(ctx, id); + + id = AddId(ctx); + ctx.Add(id); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.TryGet(id)).ToNotEqual(nullptr); +}); + +It("Can access CRemoved", []() +{ + IdContext ctx; + Id id = AddId(ctx); + ctx.Add(id); + RmId(ctx, id); + + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.TryGet(id)).ToNotEqual(nullptr); +}); }); -return true; -}(); -} // namespace diff --git a/Tests/ECS/ECS.spec.cpp b/Tests/ECS/ECS.spec.cpp index 40b0faa6..231d9e61 100644 --- a/Tests/ECS/ECS.spec.cpp +++ b/Tests/ECS/ECS.spec.cpp @@ -15,63 +15,55 @@ struct ECSTypeB {}; -namespace +Spec("ECS", []() { -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() +It("Can copy context", []() { -Spec("ECS", []() + static IdContext* ctxPtr = nullptr; + + IdContext origin; + Id id = AddId(origin); + + ctxPtr = &origin; + origin.Add(id); + + IdContext target{origin}; + Expect(origin.IsValid(id)).ToEqual(true); + Expect(origin.Has(id)).ToEqual(true); + Expect(target.IsValid(id)).ToEqual(true); + Expect(target.Has(id)).ToEqual(true); + + ctxPtr = ⌖ + target.Add(id); + Expect(target.Has(id)).ToEqual(true); +}); + +It("Can move context", []() { - It("Can copy context", []() - { - static IdContext* ctxPtr = nullptr; - - IdContext origin; - Id id = AddId(origin); - - ctxPtr = &origin; - origin.Add(id); - - IdContext target{origin}; - Expect(origin.IsValid(id)).ToEqual(true); - Expect(origin.Has(id)).ToEqual(true); - Expect(target.IsValid(id)).ToEqual(true); - Expect(target.Has(id)).ToEqual(true); - - ctxPtr = ⌖ - target.Add(id); - Expect(target.Has(id)).ToEqual(true); - }); - - It("Can move context", []() - { - static IdContext* ctxPtr = nullptr; - - IdContext origin; - Id id = AddId(origin); - - ctxPtr = &origin; - origin.Add(id); - Expect(origin.Has(id)).ToEqual(true); - - IdContext target{Move(origin)}; - Expect(origin.IsValid(id)).ToEqual(false); - - Expect(target.IsValid(id)).ToEqual(true); - Expect(target.Has(id)).ToEqual(true); - - ctxPtr = ⌖ - target.Add(id); - Expect(target.Has(id)).ToEqual(true); - }); - - It("Can assure pool", []() - { - IdContext origin; - TPool& pool = origin.AssurePool(); - Expect(pool.Size()).ToEqual(0); - }); + static IdContext* ctxPtr = nullptr; + + IdContext origin; + Id id = AddId(origin); + + ctxPtr = &origin; + origin.Add(id); + Expect(origin.Has(id)).ToEqual(true); + + IdContext target{Move(origin)}; + Expect(origin.IsValid(id)).ToEqual(false); + + Expect(target.IsValid(id)).ToEqual(true); + Expect(target.Has(id)).ToEqual(true); + + ctxPtr = ⌖ + target.Add(id); + Expect(target.Has(id)).ToEqual(true); +}); + +It("Can assure pool", []() +{ + IdContext origin; + TPool& pool = origin.AssurePool(); + Expect(pool.Size()).ToEqual(0); +}); }); -return true; -}(); -} // namespace diff --git a/Tests/ECS/Filtering.spec.cpp b/Tests/ECS/Filtering.spec.cpp index b39e50c0..f23ffefa 100644 --- a/Tests/ECS/Filtering.spec.cpp +++ b/Tests/ECS/Filtering.spec.cpp @@ -26,213 +26,205 @@ namespace } // namespace -namespace +Spec("ECS.Filtering", []() { -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() +BeforeEach([]() { -Spec("ECS.Filtering", []() + ctx = {}; + id1 = AddId(ctx); + id2 = AddId(ctx); + id3 = AddId(ctx); + id4 = AddId(ctx); + id5 = AddId(ctx); + ctx.Add(id1); + ctx.Add(id2); + ctx.Add(id3); + ctx.Add(id4); + ctx.Add(id5); +}); + +Describe("FindAllIdsWith/FindAllIdsWithAny", []() { - BeforeEach([]() + It("Can get list matching all", []() { - ctx = {}; - id1 = AddId(ctx); - id2 = AddId(ctx); - id3 = AddId(ctx); - id4 = AddId(ctx); - id5 = AddId(ctx); - ctx.Add(id1); - ctx.Add(id2); - ctx.Add(id3); - ctx.Add(id4); - ctx.Add(id5); + TIdScope access{ctx}; + TArray typeIds = FindAllIdsWith(access); + Expect(typeIds.Contains(id1)).ToBeTrue(); + Expect(typeIds.Contains(id2)).ToBeTrue(); + Expect(typeIds.Contains(id3)).ToBeFalse(); + + TArray type2Ids = FindAllIdsWith(access); + Expect(type2Ids.Contains(id1)).ToBeFalse(); + Expect(type2Ids.Contains(id2)).ToBeTrue(); + Expect(type2Ids.Contains(id3)).ToBeTrue(); }); - Describe("FindAllIdsWith/FindAllIdsWithAny", []() + It("Can get list matching any", []() { - It("Can get list matching all", []() - { - TIdScope access{ctx}; - TArray typeIds = FindAllIdsWith(access); - Expect(typeIds.Contains(id1)).ToBeTrue(); - Expect(typeIds.Contains(id2)).ToBeTrue(); - Expect(typeIds.Contains(id3)).ToBeFalse(); - - TArray type2Ids = FindAllIdsWith(access); - Expect(type2Ids.Contains(id1)).ToBeFalse(); - Expect(type2Ids.Contains(id2)).ToBeTrue(); - Expect(type2Ids.Contains(id3)).ToBeTrue(); - }); - - It("Can get list matching any", []() - { - TIdScope access{ctx}; - TArray typeIds = FindAllIdsWithAny(access); - Expect(typeIds.Contains(id1)).ToBeTrue(); - Expect(typeIds.Contains(id2)).ToBeTrue(); - Expect(typeIds.Contains(id3)).ToBeFalse(); - - TArray type2Ids = FindAllIdsWithAny(access); - Expect(type2Ids.Contains(id1)).ToBeTrue(); - Expect(type2Ids.Contains(id2)).ToBeTrue(); - Expect(type2Ids.Contains(id3)).ToBeTrue(); - }); - - It("Doesn't list removed ids", []() - { - TIdScope access{ctx}; - RmId(ctx, id2, RmIdFlags::Instant); - RmId(ctx, id3, RmIdFlags::Instant); - RmId(ctx, id4, RmIdFlags::Instant); - - TArray ids = FindAllIdsWith(access); - Expect(ids.Contains(NoId)).ToBeFalse(); - Expect(ids.Size()).ToEqual(1); - }); - - It("Doesn't list (deferred) removed ids", []() - { - TIdScope access{ctx}; - RmId(ctx, id2); - RmId(ctx, id3); - RmId(ctx, id4); - - FlushDeferredRemovals(ctx); - - TArray ids = FindAllIdsWith(access); - Expect(ids.Contains(NoId)).ToBeFalse(); - Expect(ids.Size()).ToEqual(1); - }); + TIdScope access{ctx}; + TArray typeIds = FindAllIdsWithAny(access); + Expect(typeIds.Contains(id1)).ToBeTrue(); + Expect(typeIds.Contains(id2)).ToBeTrue(); + Expect(typeIds.Contains(id3)).ToBeFalse(); + + TArray type2Ids = FindAllIdsWithAny(access); + Expect(type2Ids.Contains(id1)).ToBeTrue(); + Expect(type2Ids.Contains(id2)).ToBeTrue(); + Expect(type2Ids.Contains(id3)).ToBeTrue(); }); - Describe("ExcludeIdsWith", []() + It("Doesn't list removed ids", []() { - It("Removes ids containing component", []() - { - TIdScope access{ctx}; - TArray typeIds = FindAllIdsWithAny(access); - - ExcludeIdsWith(access, typeIds); - Expect(typeIds.Contains(id1)).ToBeTrue(); - Expect(typeIds.Contains(id2)).ToBeFalse(); - Expect(typeIds.Contains(id3)).ToBeFalse(); - }); - - It("Removes ids not containing component", []() - { - TIdScope access{ctx}; - TArray typeIds = FindAllIdsWithAny(access); - - ExcludeIdsWithout(access, typeIds); - Expect(typeIds.Contains(id1)).ToBeFalse(); - Expect(typeIds.Contains(id2)).ToBeTrue(); - Expect(typeIds.Contains(id3)).ToBeFalse(); - }); - - It("Removes ids containing multiple component", []() - { - TIdScope access{ctx}; - TArray typeIds = FindAllIdsWithAny(access); - - ExcludeIdsWith(access, typeIds); - Expect(typeIds.Contains(id1)).ToBeTrue(); - Expect(typeIds.Contains(id2)).ToBeFalse(); - Expect(typeIds.Contains(id3)).ToBeFalse(); - }); + TIdScope access{ctx}; + RmId(ctx, id2, RmIdFlags::Instant); + RmId(ctx, id3, RmIdFlags::Instant); + RmId(ctx, id4, RmIdFlags::Instant); + + TArray ids = FindAllIdsWith(access); + Expect(ids.Contains(NoId)).ToBeFalse(); + Expect(ids.Size()).ToEqual(1); }); - Describe("FindIdsWith", []() + It("Doesn't list (deferred) removed ids", []() { - It("Finds ids containing a component from a list", []() - { - TArray source{id1, id2, id3}; - - TIdScope access{ctx}; - TArray typeIds = FindIdsWith(access, source); - Expect(typeIds.Contains(id1)).ToBeTrue(); - Expect(typeIds.Contains(id2)).ToBeTrue(); - Expect(typeIds.Contains(id3)).ToBeFalse(); - }); - - It("Finds ids not containing a component from a list", []() - { - TArray source{id1, id2, id3}; - - TIdScope access{ctx}; - TArray ids = FindIdsWithout(access, source); - Expect(ids.Contains(id1)).ToBeFalse(); - Expect(ids.Contains(id2)).ToBeFalse(); - Expect(ids.Contains(id3)).ToBeTrue(); - }); + TIdScope access{ctx}; + RmId(ctx, id2); + RmId(ctx, id3); + RmId(ctx, id4); + + FlushDeferredRemovals(ctx); + + TArray ids = FindAllIdsWith(access); + Expect(ids.Contains(NoId)).ToBeFalse(); + Expect(ids.Size()).ToEqual(1); }); +}); - Describe("ExtractIdsWith", []() +Describe("ExcludeIdsWith", []() +{ + It("Removes ids containing component", []() { - It("Finds and removes ids containing a component from a list", []() - { - TArray source{id1, id2, id3}; - - TIdScope access{ctx}; - TArray ids = ExtractIdsWith(access, source); - Expect(ids.Contains(id1)).ToBeTrue(); - Expect(ids.Contains(id2)).ToBeTrue(); - Expect(ids.Contains(id3)).ToBeFalse(); - Expect(source.Contains(id1)).ToBeFalse(); - Expect(source.Contains(id2)).ToBeFalse(); - Expect(source.Contains(id3)).ToBeTrue(); - }); - - It("Finds and removes ids not containing a component from a list", []() - { - TArray source{id1, id2, id3}; - - TIdScope access{ctx}; - TArray ids = ExtractIdsWithout(access, source); - Expect(ids.Contains(id1)).ToBeFalse(); - Expect(ids.Contains(id2)).ToBeFalse(); - Expect(ids.Contains(id3)).ToBeTrue(); - Expect(source.Contains(id1)).ToBeTrue(); - Expect(source.Contains(id2)).ToBeTrue(); - Expect(source.Contains(id3)).ToBeFalse(); - }); + TIdScope access{ctx}; + TArray typeIds = FindAllIdsWithAny(access); + + ExcludeIdsWith(access, typeIds); + Expect(typeIds.Contains(id1)).ToBeTrue(); + Expect(typeIds.Contains(id2)).ToBeFalse(); + Expect(typeIds.Contains(id3)).ToBeFalse(); }); - It("Can filter directly from ECS", []() + It("Removes ids not containing component", []() { - TArray ids1 = FindAllIdsWith(ctx); - Expect(ids1.Contains(id1)).ToBeTrue(); + TIdScope access{ctx}; + TArray typeIds = FindAllIdsWithAny(access); - TArray ids2 = FindAllIdsWithAny(ctx); - Expect(ids2.Contains(id1)).ToBeTrue(); + ExcludeIdsWithout(access, typeIds); + Expect(typeIds.Contains(id1)).ToBeFalse(); + Expect(typeIds.Contains(id2)).ToBeTrue(); + Expect(typeIds.Contains(id3)).ToBeFalse(); + }); - TArray ids3 = FindAllIdsWithAny(ctx); - ExcludeIdsWith(ctx, ids3); - Expect(ids3.Contains(id1)).ToBeTrue(); + It("Removes ids containing multiple component", []() + { + TIdScope access{ctx}; + TArray typeIds = FindAllIdsWithAny(access); - TArray ids4 = FindAllIdsWithAny(ctx); - ExcludeIdsWithout(ctx, ids4); - Expect(ids4.Contains(id1)).ToBeFalse(); + ExcludeIdsWith(access, typeIds); + Expect(typeIds.Contains(id1)).ToBeTrue(); + Expect(typeIds.Contains(id2)).ToBeFalse(); + Expect(typeIds.Contains(id3)).ToBeFalse(); }); +}); - It("Can filter CRemoved", []() +Describe("FindIdsWith", []() +{ + It("Finds ids containing a component from a list", []() { - RmId(ctx, id1); - RmId(ctx, id2); - RmId(ctx, id3); + TArray source{id1, id2, id3}; - TArray ids1 = FindAllIdsWith(ctx); - Expect(ids1.Contains(id1)).ToBeTrue(); - TArray ids2 = FindAllIdsWith(ctx); - Expect(ids2.Contains(id1)).ToBeTrue(); - Expect(ids2.Contains(id2)).ToBeTrue(); - Expect(ids2.Contains(id3)).ToBeTrue(); - Expect(ids2.Size()).ToEqual(3); - - TArray ids3 = FindAllIdsWith(ctx); - Expect(ids3.Contains(id1)).ToBeTrue(); - Expect(ids3.Contains(id2)).ToBeTrue(); + TIdScope access{ctx}; + TArray typeIds = FindIdsWith(access, source); + Expect(typeIds.Contains(id1)).ToBeTrue(); + Expect(typeIds.Contains(id2)).ToBeTrue(); + Expect(typeIds.Contains(id3)).ToBeFalse(); + }); + + It("Finds ids not containing a component from a list", []() + { + TArray source{id1, id2, id3}; + + TIdScope access{ctx}; + TArray ids = FindIdsWithout(access, source); + Expect(ids.Contains(id1)).ToBeFalse(); + Expect(ids.Contains(id2)).ToBeFalse(); + Expect(ids.Contains(id3)).ToBeTrue(); }); }); -return true; -}(); -} // namespace + +Describe("ExtractIdsWith", []() +{ + It("Finds and removes ids containing a component from a list", []() + { + TArray source{id1, id2, id3}; + + TIdScope access{ctx}; + TArray ids = ExtractIdsWith(access, source); + Expect(ids.Contains(id1)).ToBeTrue(); + Expect(ids.Contains(id2)).ToBeTrue(); + Expect(ids.Contains(id3)).ToBeFalse(); + Expect(source.Contains(id1)).ToBeFalse(); + Expect(source.Contains(id2)).ToBeFalse(); + Expect(source.Contains(id3)).ToBeTrue(); + }); + + It("Finds and removes ids not containing a component from a list", []() + { + TArray source{id1, id2, id3}; + + TIdScope access{ctx}; + TArray ids = ExtractIdsWithout(access, source); + Expect(ids.Contains(id1)).ToBeFalse(); + Expect(ids.Contains(id2)).ToBeFalse(); + Expect(ids.Contains(id3)).ToBeTrue(); + Expect(source.Contains(id1)).ToBeTrue(); + Expect(source.Contains(id2)).ToBeTrue(); + Expect(source.Contains(id3)).ToBeFalse(); + }); +}); + +It("Can filter directly from ECS", []() +{ + TArray ids1 = FindAllIdsWith(ctx); + Expect(ids1.Contains(id1)).ToBeTrue(); + + TArray ids2 = FindAllIdsWithAny(ctx); + Expect(ids2.Contains(id1)).ToBeTrue(); + + TArray ids3 = FindAllIdsWithAny(ctx); + ExcludeIdsWith(ctx, ids3); + Expect(ids3.Contains(id1)).ToBeTrue(); + + TArray ids4 = FindAllIdsWithAny(ctx); + ExcludeIdsWithout(ctx, ids4); + Expect(ids4.Contains(id1)).ToBeFalse(); +}); + +It("Can filter CRemoved", []() +{ + RmId(ctx, id1); + RmId(ctx, id2); + RmId(ctx, id3); + + TArray ids1 = FindAllIdsWith(ctx); + Expect(ids1.Contains(id1)).ToBeTrue(); + TArray ids2 = FindAllIdsWith(ctx); + Expect(ids2.Contains(id1)).ToBeTrue(); + Expect(ids2.Contains(id2)).ToBeTrue(); + Expect(ids2.Contains(id3)).ToBeTrue(); + Expect(ids2.Size()).ToEqual(3); + + TArray ids3 = FindAllIdsWith(ctx); + Expect(ids3.Contains(id1)).ToBeTrue(); + Expect(ids3.Contains(id2)).ToBeTrue(); +}); +}); diff --git a/Tests/ECS/Hierarchy.spec.cpp b/Tests/ECS/Hierarchy.spec.cpp index fb4f6603..032bd68b 100644 --- a/Tests/ECS/Hierarchy.spec.cpp +++ b/Tests/ECS/Hierarchy.spec.cpp @@ -19,479 +19,471 @@ namespace } // namespace -namespace +Spec("ECS.Hierarchy", []() { -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() +BeforeEach([]() { -Spec("ECS.Hierarchy", []() + ctx = {}; + root = AddId(ctx); + child1 = AddId(ctx); + child2 = AddId(ctx); + child3 = AddId(ctx); + grandchild = AddId(ctx); +}); + +Describe("AttachId", []() { - BeforeEach([]() + It("Creates bidirectional parent-child link for single child", []() { - ctx = {}; - root = AddId(ctx); - child1 = AddId(ctx); - child2 = AddId(ctx); - child3 = AddId(ctx); - grandchild = AddId(ctx); + AttachId({ctx}, root, child1); + + Expect(ctx.Has(root)).ToBeTrue(); + Expect(ctx.Has(child1)).ToBeTrue(); + Expect(ctx.Get(root).children.Size()).ToEqual(1); + Expect(ctx.Get(root).children[0]).ToEqual(child1); + Expect(ctx.Get(child1).parent).ToEqual(root); }); - Describe("AttachId", []() + It("Appends multiple children to same parent", []() { - It("Creates bidirectional parent-child link for single child", []() - { - AttachId({ctx}, root, child1); - - Expect(ctx.Has(root)).ToBeTrue(); - Expect(ctx.Has(child1)).ToBeTrue(); - Expect(ctx.Get(root).children.Size()).ToEqual(1); - Expect(ctx.Get(root).children[0]).ToEqual(child1); - Expect(ctx.Get(child1).parent).ToEqual(root); - }); - - It("Appends multiple children to same parent", []() - { - AttachId({ctx}, root, {child1, child2, child3}); - - Expect(ctx.Has(root)).ToBeTrue(); - Expect(ctx.Has(child1)).ToBeTrue(); - Expect(ctx.Has(child2)).ToBeTrue(); - Expect(ctx.Has(child3)).ToBeTrue(); - Expect(ctx.Get(root).children.Size()).ToEqual(3); - Expect(ctx.Get(root).children[0]).ToEqual(child1); - Expect(ctx.Get(root).children[1]).ToEqual(child2); - Expect(ctx.Get(root).children[2]).ToEqual(child3); - Expect(ctx.Get(child1).parent).ToEqual(root); - Expect(ctx.Get(child2).parent).ToEqual(root); - Expect(ctx.Get(child3).parent).ToEqual(root); - }); + AttachId({ctx}, root, {child1, child2, child3}); + + Expect(ctx.Has(root)).ToBeTrue(); + Expect(ctx.Has(child1)).ToBeTrue(); + Expect(ctx.Has(child2)).ToBeTrue(); + Expect(ctx.Has(child3)).ToBeTrue(); + Expect(ctx.Get(root).children.Size()).ToEqual(3); + Expect(ctx.Get(root).children[0]).ToEqual(child1); + Expect(ctx.Get(root).children[1]).ToEqual(child2); + Expect(ctx.Get(root).children[2]).ToEqual(child3); + Expect(ctx.Get(child1).parent).ToEqual(root); + Expect(ctx.Get(child2).parent).ToEqual(root); + Expect(ctx.Get(child3).parent).ToEqual(root); }); +}); - Describe("AttachIdAfter", []() +Describe("AttachIdAfter", []() +{ + It("Inserts child after specified sibling preserving order", []() { - It("Inserts child after specified sibling preserving order", []() - { - AttachId({ctx}, root, {child1, child3}); - AttachIdAfter({ctx}, root, child2, child1); + AttachId({ctx}, root, {child1, child3}); + AttachIdAfter({ctx}, root, child2, child1); - Expect(ctx.Get(root).children.Size()).ToEqual(3); - Expect(ctx.Get(root).children.FindIndex(child2)).ToEqual(1); - }); + Expect(ctx.Get(root).children.Size()).ToEqual(3); + Expect(ctx.Get(root).children.FindIndex(child2)).ToEqual(1); }); +}); - Describe("TransferIdChildren", []() +Describe("TransferIdChildren", []() +{ + It("Moves children from old parent to new parent", []() { - It("Moves children from old parent to new parent", []() - { - Id newRoot = AddId(ctx); - AttachId({ctx}, root, {child1, child2}); - TransferIdChildren({ctx}, {child1, child2}, newRoot); - - Expect(ctx.Get(root).children.IsEmpty()).ToBeTrue(); - Expect(ctx.Has(newRoot)).ToBeTrue(); - Expect(ctx.Get(newRoot).children.Size()).ToEqual(2); - Expect(ctx.Get(child1).parent).ToEqual(newRoot); - Expect(ctx.Get(child2).parent).ToEqual(newRoot); - }); + Id newRoot = AddId(ctx); + AttachId({ctx}, root, {child1, child2}); + TransferIdChildren({ctx}, {child1, child2}, newRoot); + + Expect(ctx.Get(root).children.IsEmpty()).ToBeTrue(); + Expect(ctx.Has(newRoot)).ToBeTrue(); + Expect(ctx.Get(newRoot).children.Size()).ToEqual(2); + Expect(ctx.Get(child1).parent).ToEqual(newRoot); + Expect(ctx.Get(child2).parent).ToEqual(newRoot); }); +}); - Describe("DetachIdParent", []() +Describe("DetachIdParent", []() +{ + BeforeEach([]() { - BeforeEach([]() - { - AttachId({ctx}, root, {child1, child2}); - }); + AttachId({ctx}, root, {child1, child2}); + }); - It("Retains CChild component when keepComponents is true", []() - { - DetachIdParent({ctx}, child1, true); + It("Retains CChild component when keepComponents is true", []() + { + DetachIdParent({ctx}, child1, true); - Expect(ctx.Has(child1)).ToBeTrue(); - Expect(ctx.Get(child1).parent).ToEqual(NoId); - Expect(ctx.Get(root).children.Size()).ToEqual(1); - }); + Expect(ctx.Has(child1)).ToBeTrue(); + Expect(ctx.Get(child1).parent).ToEqual(NoId); + Expect(ctx.Get(root).children.Size()).ToEqual(1); + }); - It("Removes CChild from detached child and removes from parent list", []() - { - DetachIdParent({ctx}, child1, false); + It("Removes CChild from detached child and removes from parent list", []() + { + DetachIdParent({ctx}, child1, false); - Expect(ctx.Has(child1)).ToBeFalse(); - Expect(ctx.Get(root).children.Contains(child1)).ToBeFalse(); - }); + Expect(ctx.Has(child1)).ToBeFalse(); + Expect(ctx.Get(root).children.Contains(child1)).ToBeFalse(); + }); - It("Removes empty CParent when all children are detached", []() - { - DetachIdParent({ctx}, {child1, child2}, false); + It("Removes empty CParent when all children are detached", []() + { + DetachIdParent({ctx}, {child1, child2}, false); - Expect(ctx.Has(child1)).ToBeFalse(); - Expect(ctx.Has(child2)).ToBeFalse(); - Expect(ctx.Has(root)).ToBeFalse(); - }); + Expect(ctx.Has(child1)).ToBeFalse(); + Expect(ctx.Has(child2)).ToBeFalse(); + Expect(ctx.Has(root)).ToBeFalse(); }); +}); - Describe("DetachIdChildren", []() +Describe("DetachIdChildren", []() +{ + BeforeEach([]() { - BeforeEach([]() - { - AttachId({ctx}, root, {child1, child2}); - }); + AttachId({ctx}, root, {child1, child2}); + }); - It("Severes all children but retains CChild when keepComponents is true", []() - { - DetachIdChildren({ctx}, root, true); + It("Severes all children but retains CChild when keepComponents is true", []() + { + DetachIdChildren({ctx}, root, true); - Expect(ctx.Has(child1)).ToBeTrue(); - Expect(ctx.Has(child2)).ToBeTrue(); - Expect(ctx.Get(child1).parent).ToEqual(NoId); - Expect(ctx.Get(child2).parent).ToEqual(NoId); - Expect(ctx.Get(root).children.IsEmpty()).ToBeTrue(); - }); + Expect(ctx.Has(child1)).ToBeTrue(); + Expect(ctx.Has(child2)).ToBeTrue(); + Expect(ctx.Get(child1).parent).ToEqual(NoId); + Expect(ctx.Get(child2).parent).ToEqual(NoId); + Expect(ctx.Get(root).children.IsEmpty()).ToBeTrue(); + }); - It("Removes CChild and CParent when keepComponents is false", []() - { - DetachIdChildren({ctx}, root, false); + It("Removes CChild and CParent when keepComponents is false", []() + { + DetachIdChildren({ctx}, root, false); - Expect(ctx.Has(child1)).ToBeFalse(); - Expect(ctx.Has(child2)).ToBeFalse(); - Expect(ctx.Has(root)).ToBeFalse(); - }); + Expect(ctx.Has(child1)).ToBeFalse(); + Expect(ctx.Has(child2)).ToBeFalse(); + Expect(ctx.Has(root)).ToBeFalse(); }); +}); - Describe("GetIdChildren", []() +Describe("GetIdChildren", []() +{ + BeforeEach([]() { - BeforeEach([]() - { - AttachId({ctx}, root, {child1, child2}); - AttachId({ctx}, child1, grandchild); - }); + AttachId({ctx}, root, {child1, child2}); + AttachId({ctx}, child1, grandchild); + }); - It("Returns child list for parent entities", []() - { - const auto* children = GetIdChildren({ctx}, root); - Expect(children).ToNotEqual(nullptr); - Expect(children->Size()).ToEqual(2); - Expect(children->Contains(child1)).ToBeTrue(); - Expect(children->Contains(child2)).ToBeTrue(); - }); + It("Returns child list for parent entities", []() + { + const auto* children = GetIdChildren({ctx}, root); + Expect(children).ToNotEqual(nullptr); + Expect(children->Size()).ToEqual(2); + Expect(children->Contains(child1)).ToBeTrue(); + Expect(children->Contains(child2)).ToBeTrue(); + }); - It("Combines children from multiple parents into one list", []() - { - TArray outChildren; - GetIdChildren({ctx}, {root, child1}, outChildren); - Expect(outChildren.Size()).ToEqual(3); - Expect(outChildren.Contains(grandchild)).ToBeTrue(); - }); + It("Combines children from multiple parents into one list", []() + { + TArray outChildren; + GetIdChildren({ctx}, {root, child1}, outChildren); + Expect(outChildren.Size()).ToEqual(3); + Expect(outChildren.Contains(grandchild)).ToBeTrue(); + }); - It("Returns null for entities without CParent component", []() - { - Expect(GetIdChildren({ctx}, child2)).ToEqual(nullptr); - }); + It("Returns null for entities without CParent component", []() + { + Expect(GetIdChildren({ctx}, child2)).ToEqual(nullptr); }); +}); - Describe("GetAllIdChildren", []() +Describe("GetAllIdChildren", []() +{ + BeforeEach([]() { - BeforeEach([]() - { - AttachId({ctx}, root, child1); - AttachId({ctx}, child1, grandchild); - }); + AttachId({ctx}, root, child1); + AttachId({ctx}, child1, grandchild); + }); - It("Recurses full tree depth to collect all descendents", []() - { - TArray outChildren; - GetAllIdChildren({ctx}, root, outChildren, 10); - Expect(outChildren.Size()).ToEqual(2); - Expect(outChildren.Contains(grandchild)).ToBeTrue(); - }); + It("Recurses full tree depth to collect all descendents", []() + { + TArray outChildren; + GetAllIdChildren({ctx}, root, outChildren, 10); + Expect(outChildren.Size()).ToEqual(2); + Expect(outChildren.Contains(grandchild)).ToBeTrue(); + }); - It("Respects depth limit to return only immediate children", []() - { - TArray outChildren; - GetAllIdChildren({ctx}, root, outChildren, 1); - Expect(outChildren.Size()).ToEqual(1); - Expect(outChildren.Contains(grandchild)).ToBeFalse(); - }); + It("Respects depth limit to return only immediate children", []() + { + TArray outChildren; + GetAllIdChildren({ctx}, root, outChildren, 1); + Expect(outChildren.Size()).ToEqual(1); + Expect(outChildren.Contains(grandchild)).ToBeFalse(); }); +}); - Describe("GetIdParent", []() +Describe("GetIdParent", []() +{ + BeforeEach([]() { - BeforeEach([]() - { - AttachId({ctx}, root, child1); - AttachId({ctx}, child1, grandchild); - }); + AttachId({ctx}, root, child1); + AttachId({ctx}, child1, grandchild); + }); - It("Returns parent Id for child entities", []() - { - Expect(GetIdParent({ctx}, child1)).ToEqual(root); - Expect(GetIdParent({ctx}, grandchild)).ToEqual(child1); - }); + It("Returns parent Id for child entities", []() + { + Expect(GetIdParent({ctx}, child1)).ToEqual(root); + Expect(GetIdParent({ctx}, grandchild)).ToEqual(child1); + }); - It("Returns unique parents for multiple children", []() - { - TArray outParents; - GetIdParent({ctx}, {child1, grandchild}, outParents); - Expect(outParents.Size()).ToEqual(2); - Expect(outParents.Contains(root)).ToBeTrue(); - Expect(outParents.Contains(child1)).ToBeTrue(); - }); + It("Returns unique parents for multiple children", []() + { + TArray outParents; + GetIdParent({ctx}, {child1, grandchild}, outParents); + Expect(outParents.Size()).ToEqual(2); + Expect(outParents.Contains(root)).ToBeTrue(); + Expect(outParents.Contains(child1)).ToBeTrue(); + }); - It("Returns NoId for root entities without parent", []() - { - Expect(GetIdParent({ctx}, root)).ToEqual(NoId); - }); + It("Returns NoId for root entities without parent", []() + { + Expect(GetIdParent({ctx}, root)).ToEqual(NoId); + }); - It("Returns NoId for entities without CChild component", []() - { - Expect(GetIdParent({ctx}, child2)).ToEqual(NoId); - }); + It("Returns NoId for entities without CChild component", []() + { + Expect(GetIdParent({ctx}, child2)).ToEqual(NoId); }); +}); - Describe("GetAllIdParents", []() +Describe("GetAllIdParents", []() +{ + BeforeEach([]() { - BeforeEach([]() - { - AttachId({ctx}, root, child1); - AttachId({ctx}, child1, grandchild); - }); + AttachId({ctx}, root, child1); + AttachId({ctx}, child1, grandchild); + }); - It("Traverses full ancestry chain from leaf to root", []() - { - TArray outParents; - GetAllIdParents({ctx}, grandchild, outParents); - Expect(outParents.Size()).ToEqual(2); - Expect(outParents[0]).ToEqual(child1); - Expect(outParents[1]).ToEqual(root); - }); + It("Traverses full ancestry chain from leaf to root", []() + { + TArray outParents; + GetAllIdParents({ctx}, grandchild, outParents); + Expect(outParents.Size()).ToEqual(2); + Expect(outParents[0]).ToEqual(child1); + Expect(outParents[1]).ToEqual(root); + }); - It("Returns empty when entity has no CChild component", []() - { - TArray outParents; - GetAllIdParents({ctx}, child2, outParents); - Expect(outParents.IsEmpty()).ToBeTrue(); - }); + It("Returns empty when entity has no CChild component", []() + { + TArray outParents; + GetAllIdParents({ctx}, child2, outParents); + Expect(outParents.IsEmpty()).ToBeTrue(); }); +}); - Describe("FindIdParent", []() +Describe("FindIdParent", []() +{ + BeforeEach([]() { - BeforeEach([]() - { - AttachId({ctx}, root, child1); - AttachId({ctx}, child1, grandchild); - }); + AttachId({ctx}, root, child1); + AttachId({ctx}, child1, grandchild); + }); - It("Finds ancestor two levels up matching predicate", []() + It("Finds ancestor two levels up matching predicate", []() + { + Expect(FindIdParent({ctx}, grandchild, + [&](Id id) { - Expect(FindIdParent({ctx}, grandchild, - [&](Id id) - { - return id == root; - })).ToEqual(root); - }); + return id == root; + })).ToEqual(root); + }); - It("Finds immediate parent matching predicate", []() + It("Finds immediate parent matching predicate", []() + { + Expect(FindIdParent({ctx}, grandchild, + [&](Id id) { - Expect(FindIdParent({ctx}, grandchild, - [&](Id id) - { - return id == child1; - })).ToEqual(child1); - }); + return id == child1; + })).ToEqual(child1); + }); - It("Returns NoId when no ancestor matches predicate", []() + It("Returns NoId when no ancestor matches predicate", []() + { + Expect(IsNone(FindIdParent({ctx}, grandchild, + [](Id) { - Expect(IsNone(FindIdParent({ctx}, grandchild, - [](Id) - { - return false; - }))).ToBeTrue(); - }); + return false; + }))).ToBeTrue(); }); +}); - Describe("FindIdParents", []() +Describe("FindIdParents", []() +{ + It("Finds nearest matching ancestor for deep entity", []() { - It("Finds nearest matching ancestor for deep entity", []() - { - Id intermediate = AddId(ctx); - AttachId({ctx}, root, child1); - AttachId({ctx}, child1, intermediate); - AttachId({ctx}, intermediate, grandchild); - - TArray outParents; - FindIdParents({ctx}, grandchild, outParents, [](Id) - { - return true; - }); - Expect(outParents.Size()).ToEqual(1); - Expect(outParents.Contains(intermediate)).ToBeTrue(); - }); + Id intermediate = AddId(ctx); + AttachId({ctx}, root, child1); + AttachId({ctx}, child1, intermediate); + AttachId({ctx}, intermediate, grandchild); - It("Returns empty when no ancestor matches predicate", []() + TArray outParents; + FindIdParents({ctx}, grandchild, outParents, [&](Id id) { - TArray outParents; - FindIdParents({ctx}, child1, outParents, [](Id) - { - return false; - }); - Expect(outParents.IsEmpty()).ToBeTrue(); + return id == intermediate; }); + Expect(outParents.Size()).ToEqual(1); + Expect(outParents.Contains(intermediate)).ToBeTrue(); }); - Describe("GetIdRoots", []() + It("Returns empty when no ancestor matches predicate", []() { - It("Returns empty when no hierarchy exists", []() + TArray outParents; + FindIdParents({ctx}, child1, outParents, [](Id) { - TArray roots; - GetIdRoots({ctx}, roots); - Expect(roots.IsEmpty()).ToBeTrue(); + return false; }); + Expect(outParents.IsEmpty()).ToBeTrue(); + }); +}); - It("Finds root of single-parent hierarchy", []() - { - AttachId({ctx}, root, {child1, child2}); +Describe("GetIdRoots", []() +{ + It("Returns empty when no hierarchy exists", []() + { + TArray roots; + GetIdRoots({ctx}, roots); + Expect(roots.IsEmpty()).ToBeTrue(); + }); - TArray roots; - GetIdRoots({ctx}, roots); - Expect(roots.Size()).ToEqual(1); - Expect(roots.Contains(root)).ToBeTrue(); - }); + It("Finds root of single-parent hierarchy", []() + { + AttachId({ctx}, root, {child1, child2}); - It("Returns multiple roots from independent trees", []() - { - Id root2 = AddId(ctx); - AttachId({ctx}, root, {child1, child2}); - AttachId({ctx}, root2, child3); - - TArray roots; - GetIdRoots({ctx}, roots); - Expect(roots.Size()).ToEqual(2); - Expect(roots.Contains(root)).ToBeTrue(); - Expect(roots.Contains(root2)).ToBeTrue(); - }); + TArray roots; + GetIdRoots({ctx}, roots); + Expect(roots.Size()).ToEqual(1); + Expect(roots.Contains(root)).ToBeTrue(); + }); - It("Excludes entities that are both parent and child of someone", []() - { - AttachId({ctx}, root, child1); - AttachId({ctx}, child1, grandchild); - - TArray roots; - GetIdRoots({ctx}, roots); - Expect(roots.Size()).ToEqual(1); - Expect(roots.Contains(root)).ToBeTrue(); - Expect(roots.Contains(child1)).ToBeFalse(); - }); + It("Returns multiple roots from independent trees", []() + { + Id root2 = AddId(ctx); + AttachId({ctx}, root, {child1, child2}); + AttachId({ctx}, root2, child3); + + TArray roots; + GetIdRoots({ctx}, roots); + Expect(roots.Size()).ToEqual(2); + Expect(roots.Contains(root)).ToBeTrue(); + Expect(roots.Contains(root2)).ToBeTrue(); }); - Describe("GetIdParentRoots", []() + It("Excludes entities that are both parent and child of someone", []() { - BeforeEach([]() - { - AttachId({ctx}, root, child1); - AttachId({ctx}, child1, grandchild); - }); + AttachId({ctx}, root, child1); + AttachId({ctx}, child1, grandchild); + + TArray roots; + GetIdRoots({ctx}, roots); + Expect(roots.Size()).ToEqual(1); + Expect(roots.Contains(root)).ToBeTrue(); + Expect(roots.Contains(child1)).ToBeFalse(); + }); +}); - It("Walks child chain up to root ancestor", []() - { - TArray roots; - GetIdParentRoots({ctx}, grandchild, roots, false); - Expect(roots.Size()).ToEqual(1); - Expect(roots.Contains(root)).ToBeTrue(); - }); +Describe("GetIdParentRoots", []() +{ + BeforeEach([]() + { + AttachId({ctx}, root, child1); + AttachId({ctx}, child1, grandchild); + }); - It("Handles children from different trees", []() - { - Id root2 = AddId(ctx); - Id childOf2 = AddId(ctx); - AttachId({ctx}, root2, childOf2); - - TArray roots; - GetIdParentRoots({ctx}, {grandchild, childOf2}, roots, false); - Expect(roots.Size()).ToEqual(2); - Expect(roots.Contains(root)).ToBeTrue(); - Expect(roots.Contains(root2)).ToBeTrue(); - }); + It("Walks child chain up to root ancestor", []() + { + TArray roots; + GetIdParentRoots({ctx}, grandchild, roots, false); + Expect(roots.Size()).ToEqual(1); + Expect(roots.Contains(root)).ToBeTrue(); + }); - It("Considers input entities as roots when considerChildren flag is set", []() - { - TArray roots; - GetIdParentRoots({ctx}, {root, grandchild}, roots, true); - Expect(roots.Size()).ToEqual(1); - Expect(roots.Contains(root)).ToBeTrue(); - }); + It("Handles children from different trees", []() + { + Id root2 = AddId(ctx); + Id childOf2 = AddId(ctx); + AttachId({ctx}, root2, childOf2); + + TArray roots; + GetIdParentRoots({ctx}, {grandchild, childOf2}, roots, false); + Expect(roots.Size()).ToEqual(2); + Expect(roots.Contains(root)).ToBeTrue(); + Expect(roots.Contains(root2)).ToBeTrue(); + }); - It("Returns empty for empty input", []() - { - TArray roots; - GetIdParentRoots({ctx}, {}, roots, false); - Expect(roots.IsEmpty()).ToBeTrue(); - }); + It("Considers input entities as roots when considerChildren flag is set", []() + { + TArray roots; + GetIdParentRoots({ctx}, {root, grandchild}, roots, true); + Expect(roots.Size()).ToEqual(1); + Expect(roots.Contains(root)).ToBeTrue(); + }); - It("Returns empty for entities with no parent", []() - { - TArray roots; - GetIdParentRoots({ctx}, child2, roots, false); - Expect(roots.IsEmpty()).ToBeTrue(); - }); + It("Returns empty for empty input", []() + { + TArray roots; + GetIdParentRoots({ctx}, {}, roots, false); + Expect(roots.IsEmpty()).ToBeTrue(); }); - Describe("FixParentIdLinks", []() + It("Returns empty for entities with no parent", []() { - BeforeEach([]() - { - AttachId({ctx}, root, child1); - }); + TArray roots; + GetIdParentRoots({ctx}, child2, roots, false); + Expect(roots.IsEmpty()).ToBeTrue(); + }); +}); - It("Returns false when parent-child links are already correct", []() - { - Expect(FixParentIdLinks({ctx}, root)).ToBeFalse(); - }); +Describe("FixParentIdLinks", []() +{ + BeforeEach([]() + { + AttachId({ctx}, root, child1); + }); - It("Fixes child->parent reference when it does not match parent's list", []() - { - ctx.Get(child1).parent = NoId; + It("Returns false when parent-child links are already correct", []() + { + Expect(FixParentIdLinks({ctx}, root)).ToBeFalse(); + }); - Expect(FixParentIdLinks({ctx}, root)).ToBeTrue(); - Expect(ctx.Get(child1).parent).ToEqual(root); - }); + It("Fixes child->parent reference when it does not match parent's list", []() + { + ctx.Get(child1).parent = NoId; - It("Adds missing CChild component to orphan children", []() - { - ctx.Remove(child1); - Expect(ctx.Has(child1)).ToBeFalse(); + Expect(FixParentIdLinks({ctx}, root)).ToBeTrue(); + Expect(ctx.Get(child1).parent).ToEqual(root); + }); - Expect(FixParentIdLinks({ctx}, root)).ToBeTrue(); - Expect(ctx.Has(child1)).ToBeTrue(); - Expect(ctx.Get(child1).parent).ToEqual(root); - }); + It("Adds missing CChild component to orphan children", []() + { + ctx.Remove(child1); + Expect(ctx.Has(child1)).ToBeFalse(); + + Expect(FixParentIdLinks({ctx}, root)).ToBeTrue(); + Expect(ctx.Has(child1)).ToBeTrue(); + Expect(ctx.Get(child1).parent).ToEqual(root); }); +}); - Describe("ValidateParentIdLinks", []() +Describe("ValidateParentIdLinks", []() +{ + BeforeEach([]() { - BeforeEach([]() - { - AttachId({ctx}, root, child1); - }); + AttachId({ctx}, root, child1); + }); - It("Returns true when all parent-child links are consistent", []() - { - Expect(ValidateParentIdLinks({ctx}, root)).ToBeTrue(); - }); + It("Returns true when all parent-child links are consistent", []() + { + Expect(ValidateParentIdLinks({ctx}, root)).ToBeTrue(); + }); - It("Returns false when child->parent reference is mismatched", []() - { - ctx.Get(child1).parent = NoId; + It("Returns false when child->parent reference is mismatched", []() + { + ctx.Get(child1).parent = NoId; - Expect(ValidateParentIdLinks({ctx}, root)).ToBeFalse(); - }); + Expect(ValidateParentIdLinks({ctx}, root)).ToBeFalse(); + }); - It("Returns false when CChild component is missing from child", []() - { - ctx.Remove(child1); + It("Returns false when CChild component is missing from child", []() + { + ctx.Remove(child1); - Expect(ValidateParentIdLinks({ctx}, root)).ToBeFalse(); - }); + Expect(ValidateParentIdLinks({ctx}, root)).ToBeFalse(); }); }); -return true; -}(); -} // namespace +}); diff --git a/Tests/ECS/IdRegistry.spec.cpp b/Tests/ECS/IdRegistry.spec.cpp index 4c67e91b..47ba1504 100644 --- a/Tests/ECS/IdRegistry.spec.cpp +++ b/Tests/ECS/IdRegistry.spec.cpp @@ -8,160 +8,152 @@ using namespace p; using namespace std::chrono_literals; -namespace +Spec("ECS.IdRegistry", []() { -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() +It("Can create one id", []() { -Spec("ECS.IdRegistry", []() + IdRegistry ids; + Expect(ids.Size()).ToEqual(0); + Id id = ids.Create(); + Expect(id).ToNotEqual(NoId); + Expect(ids.IsValid(id)).ToBeTrue(); + Expect(ids.Size()).ToEqual(1); +}); + +It("Can remove one id", []() { - It("Can create one id", []() - { - IdRegistry ids; - Expect(ids.Size()).ToEqual(0); - Id id = ids.Create(); - Expect(id).ToNotEqual(NoId); - Expect(ids.IsValid(id)).ToBeTrue(); - Expect(ids.Size()).ToEqual(1); - }); - - It("Can remove one id", []() - { - IdRegistry ids; - Id id = ids.Create(); - Expect(ids.Size()).ToEqual(1); - Expect(ids.RemoveInstant(id)).ToBeTrue(); - Expect(ids.IsValid(id)).ToBeFalse(); - Expect(ids.Size()).ToEqual(0); - }); - - It("Can create two and remove first", []() - { - IdRegistry ids; - Id id1 = ids.Create(); - ids.Create(); - Expect(ids.RemoveInstant(id1)).ToBeTrue(); - Expect(ids.IsValid(id1)).ToBeFalse(); - Expect(ids.Size()).ToEqual(1); - }); - - It("Can create two and remove last", []() - { - IdRegistry ids; - ids.Create(); - Id id2 = ids.Create(); - Expect(ids.RemoveInstant(id2)).ToBeTrue(); - Expect(ids.IsValid(id2)).ToBeFalse(); - Expect(ids.Size()).ToEqual(1); - }); - - It("Can remove one id (deferred)", []() - { - IdRegistry ids; - Id id = ids.Create(); - Expect(ids.Size()).ToEqual(1); - Expect(ids.Remove(id)).ToBeTrue(); - Expect(ids.IsValid(id)).ToBeFalse(); - Expect(ids.Size()).ToEqual(0); - }); - - It("Can create two and remove first (deferred)", []() - { - IdRegistry ids; - Id id1 = ids.Create(); - ids.Create(); - Expect(ids.Remove(id1)).ToBeTrue(); - Expect(ids.IsValid(id1)).ToBeFalse(); - Expect(ids.Size()).ToEqual(1); - }); - - It("Can create two and remove last (deferred)", []() - { - IdRegistry ids; - ids.Create(); - Id id2 = ids.Create(); - Expect(ids.Remove(id2)).ToBeTrue(); - Expect(ids.IsValid(id2)).ToBeFalse(); - Expect(ids.Size()).ToEqual(1); - }); - - It("Removed id index gets reused", []() - { - IdRegistry ids; - ids.Create(); - Id id = ids.Create(); - ids.Create(); - Expect(ids.RemoveInstant(id)).ToBeTrue(); - Id id2 = ids.Create(); - Expect(id2.GetIndex()).ToEqual(id.GetIndex()); - Id id3 = ids.Create(); - Expect(id3.GetIndex()).ToNotEqual(id.GetIndex()); - }); - - It("Deferred removed id index doesn't get reused until flushed", []() - { - IdRegistry ids; - ids.Create(); - Id id = ids.Create(); - ids.Create(); - Expect(ids.Remove(id)).ToBeTrue(); - Id id2 = ids.Create(); - Expect(id2.GetIndex()).ToNotEqual(id.GetIndex()); - ids.FlushDeferredRemovals(); - Id id3 = ids.Create(); - Expect(id3.GetIndex()).ToEqual(id.GetIndex()); - Id id4 = ids.Create(); - Expect(id4.GetIndex()).ToNotEqual(id.GetIndex()); - }); - - It("Can create many ids", []() + IdRegistry ids; + Id id = ids.Create(); + Expect(ids.Size()).ToEqual(1); + Expect(ids.RemoveInstant(id)).ToBeTrue(); + Expect(ids.IsValid(id)).ToBeFalse(); + Expect(ids.Size()).ToEqual(0); +}); + +It("Can create two and remove first", []() +{ + IdRegistry ids; + Id id1 = ids.Create(); + ids.Create(); + Expect(ids.RemoveInstant(id1)).ToBeTrue(); + Expect(ids.IsValid(id1)).ToBeFalse(); + Expect(ids.Size()).ToEqual(1); +}); + +It("Can create two and remove last", []() +{ + IdRegistry ids; + ids.Create(); + Id id2 = ids.Create(); + Expect(ids.RemoveInstant(id2)).ToBeTrue(); + Expect(ids.IsValid(id2)).ToBeFalse(); + Expect(ids.Size()).ToEqual(1); +}); + +It("Can remove one id (deferred)", []() +{ + IdRegistry ids; + Id id = ids.Create(); + Expect(ids.Size()).ToEqual(1); + Expect(ids.Remove(id)).ToBeTrue(); + Expect(ids.IsValid(id)).ToBeFalse(); + Expect(ids.Size()).ToEqual(0); +}); + +It("Can create two and remove first (deferred)", []() +{ + IdRegistry ids; + Id id1 = ids.Create(); + ids.Create(); + Expect(ids.Remove(id1)).ToBeTrue(); + Expect(ids.IsValid(id1)).ToBeFalse(); + Expect(ids.Size()).ToEqual(1); +}); + +It("Can create two and remove last (deferred)", []() +{ + IdRegistry ids; + ids.Create(); + Id id2 = ids.Create(); + Expect(ids.Remove(id2)).ToBeTrue(); + Expect(ids.IsValid(id2)).ToBeFalse(); + Expect(ids.Size()).ToEqual(1); +}); + +It("Removed id index gets reused", []() +{ + IdRegistry ids; + ids.Create(); + Id id = ids.Create(); + ids.Create(); + Expect(ids.RemoveInstant(id)).ToBeTrue(); + Id id2 = ids.Create(); + Expect(id2.GetIndex()).ToEqual(id.GetIndex()); + Id id3 = ids.Create(); + Expect(id3.GetIndex()).ToNotEqual(id.GetIndex()); +}); + +It("Deferred removed id index doesn't get reused until flushed", []() +{ + IdRegistry ids; + ids.Create(); + Id id = ids.Create(); + ids.Create(); + Expect(ids.Remove(id)).ToBeTrue(); + Id id2 = ids.Create(); + Expect(id2.GetIndex()).ToNotEqual(id.GetIndex()); + ids.FlushDeferredRemovals(); + Id id3 = ids.Create(); + Expect(id3.GetIndex()).ToEqual(id.GetIndex()); + Id id4 = ids.Create(); + Expect(id4.GetIndex()).ToNotEqual(id.GetIndex()); +}); + +It("Can create many ids", []() +{ + IdRegistry ids; + Expect(ids.Size()).ToEqual(0); + + TArray list(3); + ids.Create(list); + + Expect(ids.Size()).ToEqual(3); + for (i32 i = 0; i < list.Size(); ++i) { - IdRegistry ids; - Expect(ids.Size()).ToEqual(0); + Expect(list[i].GetIndex()).ToEqual(i); + Expect(ids.IsValid(list[i])).ToBeTrue(); + } +}); - TArray list(3); - ids.Create(list); +It("Can remove many ids", []() +{ + IdRegistry ids; + TArray list(3); + ids.Create(list); + Expect(ids.Size()).ToEqual(3); - Expect(ids.Size()).ToEqual(3); - for (i32 i = 0; i < list.Size(); ++i) - { - Expect(list[i].GetIndex()).ToEqual(i); - Expect(ids.IsValid(list[i])).ToBeTrue(); - } - }); + Expect(ids.RemoveInstant(list)).ToBeTrue(); + Expect(ids.Size()).ToEqual(0); - It("Can remove many ids", []() + for (i32 i = 0; i < list.Size(); ++i) { - IdRegistry ids; - TArray list(3); - ids.Create(list); - Expect(ids.Size()).ToEqual(3); + Expect(ids.IsValid(list[i])).ToBeFalse(); + } +}); - Expect(ids.RemoveInstant(list)).ToBeTrue(); - Expect(ids.Size()).ToEqual(0); +It("Can remove many ids (deferred)", []() +{ + IdRegistry ids; + TArray list(3); + ids.Create(list); + Expect(ids.Size()).ToEqual(3); - for (i32 i = 0; i < list.Size(); ++i) - { - Expect(ids.IsValid(list[i])).ToBeFalse(); - } - }); + Expect(ids.Remove(list)).ToBeTrue(); + Expect(ids.Size()).ToEqual(0); - It("Can remove many ids (deferred)", []() + for (i32 i = 0; i < list.Size(); ++i) { - IdRegistry ids; - TArray list(3); - ids.Create(list); - Expect(ids.Size()).ToEqual(3); - - Expect(ids.Remove(list)).ToBeTrue(); - Expect(ids.Size()).ToEqual(0); - - for (i32 i = 0; i < list.Size(); ++i) - { - Expect(ids.IsValid(list[i])).ToBeFalse(); - } - }); + Expect(ids.IsValid(list[i])).ToBeFalse(); + } +}); }); -return true; -}(); -} // namespace diff --git a/Tests/ECS/IdScopes.spec.cpp b/Tests/ECS/IdScopes.spec.cpp index fdf28661..b332090a 100644 --- a/Tests/ECS/IdScopes.spec.cpp +++ b/Tests/ECS/IdScopes.spec.cpp @@ -21,126 +21,118 @@ struct ScopeTypeC }; -namespace -{ -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() -{ Spec("ECS.IdScopes", []() { - Describe("Templated", []() +Describe("Templated", []() +{ + It("Can cache pools", []() + { + IdContext ctx; + TIdScope> scope{ctx}; + + Expect(scope.GetPool()).ToEqual(ctx.GetPool()); + Expect(scope.GetPool()).ToEqual(ctx.GetPool()); + Expect(scope.GetPool()).ToEqual(ctx.GetPool()); + }); + + It("Can check if contained", []() + { + IdContext ctx; + TPool& pool = ctx.AssurePool(); + TIdScope> scope{ctx}; + TIdScope scopeConst{ctx}; + Id id = NoId; + Expect(scope.Has(id)).ToBeFalse(); + Expect(scopeConst.Has(id)).ToBeFalse(); + + id = AddId(ctx); + Expect(scope.Has(id)).ToBeFalse(); + Expect(scopeConst.Has(id)).ToBeFalse(); + + ctx.Add(id); + Expect(scope.Has(id)).ToBeTrue(); + Expect(scopeConst.Has(id)).ToBeTrue(); + + TIdScope scope2{ctx}; + ctx.Add(id); + Expect(scope2.Has(id)).ToBeTrue(); + }); + + It("Can initialize superset", []() { - It("Can cache pools", []() - { - IdContext ctx; - TIdScope> scope{ctx}; - - Expect(scope.GetPool()).ToEqual(ctx.GetPool()); - Expect(scope.GetPool()).ToEqual(ctx.GetPool()); - Expect(scope.GetPool()).ToEqual(ctx.GetPool()); - }); - - It("Can check if contained", []() - { - IdContext ctx; - TPool& pool = ctx.AssurePool(); - TIdScope> scope{ctx}; - TIdScope scopeConst{ctx}; - Id id = NoId; - Expect(scope.Has(id)).ToBeFalse(); - Expect(scopeConst.Has(id)).ToBeFalse(); - - id = AddId(ctx); - Expect(scope.Has(id)).ToBeFalse(); - Expect(scopeConst.Has(id)).ToBeFalse(); - - ctx.Add(id); - Expect(scope.Has(id)).ToBeTrue(); - Expect(scopeConst.Has(id)).ToBeTrue(); - - TIdScope scope2{ctx}; - ctx.Add(id); - Expect(scope2.Has(id)).ToBeTrue(); - }); - - It("Can initialize superset", []() - { - IdContext ctx; - TPool& typePool = ctx.AssurePool(); - - TIdScope> scope1{ctx}; - TIdScope> superset1{scope1}; - Expect(superset1.GetPool()).ToEqual(&typePool); - - TIdScope> scope2{ctx}; - TIdScope superset2{scope2}; - Expect(superset2.GetPool()).ToEqual(&typePool); - - TIdScope> scope3{ctx}; - TIdScope superset3{scope3}; - Expect(superset1.GetPool()).ToEqual(&typePool); - }); - - It("Can mark modify", []() - { - IdContext ctx; - Id id = AddId(ctx); - TIdScope>> scope1{ctx}; - Expect(scope1.Has>(id)).ToBeFalse(); - scope1.Modify(id); - Expect(scope1.Has>(id)).ToBeTrue(); - Expect(scope1.IsModified(id)).ToBeTrue(); - - scope1.Remove>(id); - Expect(scope1.Has>(id)).ToBeFalse(); - Expect(scope1.IsModified(id)).ToBeFalse(); - - scope1.Modify(id); - Expect(scope1.Has>(id)).ToBeTrue(); - Expect(scope1.IsModified(id)).ToBeTrue(); - }); - - It("Can mark modify automatically", []() - { - IdContext ctx; - Id id = AddId(ctx); - using MyScope = TIdScope, CMdfd>; - MyScope scope{ctx}; - Expect(MyScope::WDependencies::Contains>()).ToBeTrue(); - Expect(MyScope::WDependencies::Contains>()).ToBeFalse(); - Expect(MyScope::RWDependencies::Contains>()).ToBeTrue(); - Expect(MyScope::RWDependencies::Contains>()).ToBeTrue(); - - scope.Add(id); // Type B should be auto modified - Expect(scope.IsModified(id)).ToBeTrue(); - scope.Add(id); // Type B should not be auto modified - Expect(scope.IsModified(id)).ToBeFalse(); - - scope.ClearPool>(); - Expect(scope.IsModified(id)).ToBeFalse(); - - scope.Has(id); // Has should never mark modify - Expect(scope.IsModified(id)).ToBeFalse(); - - scope.Get(id); - Expect(scope.IsModified(id)).ToBeFalse(); - scope.Get(id); - Expect(scope.IsModified(id)).ToBeTrue(); - scope.Add(id); // Type B should not be auto modified - Expect(scope.IsModified(id)).ToBeFalse(); - - scope.ClearPool>(); - - scope.Remove(id); - Expect(scope.Has(id)).ToBeFalse(); - Expect(scope.IsModified(id)).ToBeTrue(); - - scope.Remove(id); // Type B should not be auto modified - Expect(scope.Has(id)).ToBeFalse(); - Expect(scope.IsModified(id)).ToBeFalse(); - }); + IdContext ctx; + TPool& typePool = ctx.AssurePool(); + + TIdScope> scope1{ctx}; + TIdScope> superset1{scope1}; + Expect(superset1.GetPool()).ToEqual(&typePool); + + TIdScope> scope2{ctx}; + TIdScope superset2{scope2}; + Expect(superset2.GetPool()).ToEqual(&typePool); + + TIdScope> scope3{ctx}; + TIdScope superset3{scope3}; + Expect(superset1.GetPool()).ToEqual(&typePool); + }); + + It("Can mark modify", []() + { + IdContext ctx; + Id id = AddId(ctx); + TIdScope>> scope1{ctx}; + Expect(scope1.Has>(id)).ToBeFalse(); + scope1.Modify(id); + Expect(scope1.Has>(id)).ToBeTrue(); + Expect(scope1.IsModified(id)).ToBeTrue(); + + scope1.Remove>(id); + Expect(scope1.Has>(id)).ToBeFalse(); + Expect(scope1.IsModified(id)).ToBeFalse(); + + scope1.Modify(id); + Expect(scope1.Has>(id)).ToBeTrue(); + Expect(scope1.IsModified(id)).ToBeTrue(); }); + + It("Can mark modify automatically", []() + { + IdContext ctx; + Id id = AddId(ctx); + using MyScope = TIdScope, CMdfd>; + MyScope scope{ctx}; + Expect(MyScope::WDependencies::Contains>()).ToBeTrue(); + Expect(MyScope::WDependencies::Contains>()).ToBeFalse(); + Expect(MyScope::RWDependencies::Contains>()).ToBeTrue(); + Expect(MyScope::RWDependencies::Contains>()).ToBeTrue(); + + scope.Add(id); // Type B should be auto modified + Expect(scope.IsModified(id)).ToBeTrue(); + scope.Add(id); // Type B should not be auto modified + Expect(scope.IsModified(id)).ToBeFalse(); + + scope.ClearPool>(); + Expect(scope.IsModified(id)).ToBeFalse(); + + scope.Has(id); // Has should never mark modify + Expect(scope.IsModified(id)).ToBeFalse(); + + scope.Get(id); + Expect(scope.IsModified(id)).ToBeFalse(); + scope.Get(id); + Expect(scope.IsModified(id)).ToBeTrue(); + scope.Add(id); // Type B should not be auto modified + Expect(scope.IsModified(id)).ToBeFalse(); + + scope.ClearPool>(); + + scope.Remove(id); + Expect(scope.Has(id)).ToBeFalse(); + Expect(scope.IsModified(id)).ToBeTrue(); + + scope.Remove(id); // Type B should not be auto modified + Expect(scope.Has(id)).ToBeFalse(); + Expect(scope.IsModified(id)).ToBeFalse(); + }); +}); }); -return true; -}(); -} // namespace diff --git a/Tests/ECS/Statics.spec.cpp b/Tests/ECS/Statics.spec.cpp index ebe655b9..e9c0a907 100644 --- a/Tests/ECS/Statics.spec.cpp +++ b/Tests/ECS/Statics.spec.cpp @@ -22,77 +22,69 @@ struct StaticTypeThree }; -namespace +Spec("ECS.Statics", []() { -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() +It("Can set an static", []() { -Spec("ECS.Statics", []() + IdContext ctx; + Expect(ctx.HasStatic()).ToEqual(false); + auto& var = ctx.SetStatic({4}); + Expect(var.i).ToEqual(4); + Expect(ctx.HasStatic()).ToEqual(true); + Expect(ctx.HasStatic()).ToEqual(false); +}); +It("Can set two statics", []() +{ + IdContext ctx; + Expect(ctx.HasStatic()).ToEqual(false); + Expect(ctx.HasStatic()).ToEqual(false); + auto& var1 = ctx.SetStatic({4}); + auto& var2 = ctx.SetStatic({2}); + Expect(var1.i).ToEqual(4); + Expect(var2.i).ToEqual(2); + Expect(ctx.HasStatic()).ToEqual(true); + Expect(ctx.HasStatic()).ToEqual(true); +}); +It("Can replace an static", []() +{ + IdContext ctx; + Expect(ctx.HasStatic()).ToEqual(false); + ctx.SetStatic({4}); + ctx.SetStatic({2}); + Expect(ctx.GetStatic().i).ToEqual(2); + Expect(ctx.HasStatic()).ToEqual(true); +}); +It("Can get or set an static", []() +{ + IdContext ctx; + // Can set + Expect(ctx.GetOrSetStatic({4}).i).ToEqual(4); + // Can get + Expect(ctx.GetOrSetStatic({10}).i).ToEqual(4); +}); +It("Can remove an static", []() { - It("Can set an static", []() - { - IdContext ctx; - Expect(ctx.HasStatic()).ToEqual(false); - auto& var = ctx.SetStatic({4}); - Expect(var.i).ToEqual(4); - Expect(ctx.HasStatic()).ToEqual(true); - Expect(ctx.HasStatic()).ToEqual(false); - }); - It("Can set two statics", []() - { - IdContext ctx; - Expect(ctx.HasStatic()).ToEqual(false); - Expect(ctx.HasStatic()).ToEqual(false); - auto& var1 = ctx.SetStatic({4}); - auto& var2 = ctx.SetStatic({2}); - Expect(var1.i).ToEqual(4); - Expect(var2.i).ToEqual(2); - Expect(ctx.HasStatic()).ToEqual(true); - Expect(ctx.HasStatic()).ToEqual(true); - }); - It("Can replace an static", []() - { - IdContext ctx; - Expect(ctx.HasStatic()).ToEqual(false); - ctx.SetStatic({4}); - ctx.SetStatic({2}); - Expect(ctx.GetStatic().i).ToEqual(2); - Expect(ctx.HasStatic()).ToEqual(true); - }); - It("Can get or set an static", []() - { - IdContext ctx; - // Can set - Expect(ctx.GetOrSetStatic({4}).i).ToEqual(4); - // Can get - Expect(ctx.GetOrSetStatic({10}).i).ToEqual(4); - }); - It("Can remove an static", []() - { - IdContext ctx; - ctx.SetStatic(); - Expect(ctx.HasStatic()).ToEqual(true); - Expect(ctx.RemoveStatic()).ToBeTrue(); - Expect(ctx.HasStatic()).ToEqual(false); + IdContext ctx; + ctx.SetStatic(); + Expect(ctx.HasStatic()).ToEqual(true); + Expect(ctx.RemoveStatic()).ToBeTrue(); + Expect(ctx.HasStatic()).ToEqual(false); - Expect(ctx.RemoveStatic()).ToBeFalse(); - }); + Expect(ctx.RemoveStatic()).ToBeFalse(); +}); - It("Can get statics", []() - { - IdContext ctx; - ctx.SetStatic({4}); - ctx.SetStatic({2}); - Expect(ctx.GetStatic().i).ToEqual(4); - Expect(ctx.GetStatic().i).ToEqual(2); +It("Can get statics", []() +{ + IdContext ctx; + ctx.SetStatic({4}); + ctx.SetStatic({2}); + Expect(ctx.GetStatic().i).ToEqual(4); + Expect(ctx.GetStatic().i).ToEqual(2); - ctx.SetStatic({14}); - Expect(ctx.GetStatic().i).ToEqual(14); + ctx.SetStatic({14}); + Expect(ctx.GetStatic().i).ToEqual(14); - ctx.RemoveStatic(); - Expect(ctx.TryGetStatic()).ToEqual(nullptr); - }); + ctx.RemoveStatic(); + Expect(ctx.TryGetStatic()).ToEqual(nullptr); +}); }); -return true; -}(); -} // namespace diff --git a/Tests/Files/Paths.spec.cpp b/Tests/Files/Paths.spec.cpp index c3d7466e..95b1f16b 100644 --- a/Tests/Files/Paths.spec.cpp +++ b/Tests/Files/Paths.spec.cpp @@ -8,211 +8,203 @@ using namespace p; -namespace -{ -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() -{ Spec("Files.Paths", []() { - It("Can get root name and path", []() - { +It("Can get root name and path", []() +{ #if P_PLATFORM_WINDOWS - Expect(p::GetRootPathName("F:\\SomeFolder\\AnotherFolder")).ToEqual("F:"); - Expect(p::GetRootPath("F:\\SomeFolder\\AnotherFolder")).ToEqual("F:\\"); + Expect(p::GetRootPathName("F:\\SomeFolder\\AnotherFolder")).ToEqual("F:"); + Expect(p::GetRootPath("F:\\SomeFolder\\AnotherFolder")).ToEqual("F:\\"); #elif P_PLATFORM_LINUX - Expect(p::GetRootPathName("/var/SomeFolder/AnotherFolder")).ToEqual(""); - Expect(p::GetRootPath("/var/SomeFolder/AnotherFolder")).ToEqual("/"); + Expect(p::GetRootPathName("/var/SomeFolder/AnotherFolder")).ToEqual(""); + Expect(p::GetRootPath("/var/SomeFolder/AnotherFolder")).ToEqual("/"); #endif - Expect(p::GetRootPathName("/AnotherFolder")).ToEqual(""); - Expect(p::GetRootPath("/AnotherFolder")).ToEqual("/"); - }); + Expect(p::GetRootPathName("/AnotherFolder")).ToEqual(""); + Expect(p::GetRootPath("/AnotherFolder")).ToEqual("/"); +}); - It("Can get relative path", []() - { +It("Can get relative path", []() +{ #if P_PLATFORM_WINDOWS - Expect(p::GetRelativePath("F:\\SomeFolder\\AnotherFolder")).ToEqual("SomeFolder\\AnotherFolder"); + Expect(p::GetRelativePath("F:\\SomeFolder\\AnotherFolder")).ToEqual("SomeFolder\\AnotherFolder"); #endif - Expect(p::GetRelativePath("/var/SomeFolder/AnotherFolder")).ToEqual("var/SomeFolder/AnotherFolder"); - Expect(p::GetRelativePath("/SomeFolder/AnotherFolder")).ToEqual("SomeFolder/AnotherFolder"); - }); + Expect(p::GetRelativePath("/var/SomeFolder/AnotherFolder")).ToEqual("var/SomeFolder/AnotherFolder"); + Expect(p::GetRelativePath("/SomeFolder/AnotherFolder")).ToEqual("SomeFolder/AnotherFolder"); +}); - It("Can check absolute path", []() - { - Expect(p::IsAbsolutePath("//host")).ToEqual(true); +It("Can check absolute path", []() +{ + Expect(p::IsAbsolutePath("//host")).ToEqual(true); #if P_PLATFORM_WINDOWS - Expect(p::IsAbsolutePath("F:\\SomeFolder\\AnotherFolder")).ToEqual(true); + Expect(p::IsAbsolutePath("F:\\SomeFolder\\AnotherFolder")).ToEqual(true); #elif P_PLATFORM_LINUX - Expect(p::IsAbsolutePath("/var/SomeFolder/AnotherFolder")).ToEqual(true); + Expect(p::IsAbsolutePath("/var/SomeFolder/AnotherFolder")).ToEqual(true); #endif - Expect(p::IsAbsolutePath("Executable.exe")).ToEqual(false); - Expect(p::IsAbsolutePath("SomeFolder/AnotherFolder")).ToEqual(false); - }); + Expect(p::IsAbsolutePath("Executable.exe")).ToEqual(false); + Expect(p::IsAbsolutePath("SomeFolder/AnotherFolder")).ToEqual(false); +}); - It("Can check relative path", []() - { +It("Can check relative path", []() +{ #if P_PLATFORM_WINDOWS - Expect(p::IsRelativePath("F:\\SomeFolder\\AnotherFolder")).ToEqual(false); + Expect(p::IsRelativePath("F:\\SomeFolder\\AnotherFolder")).ToEqual(false); #elif P_PLATFORM_LINUX - Expect(p::IsRelativePath("/var/SomeFolder/AnotherFolder")).ToEqual(false); + Expect(p::IsRelativePath("/var/SomeFolder/AnotherFolder")).ToEqual(false); #endif - Expect(p::IsRelativePath("Executable.exe")).ToEqual(true); - Expect(p::IsRelativePath("SomeFolder/AnotherFolder")).ToEqual(true); - }); + Expect(p::IsRelativePath("Executable.exe")).ToEqual(true); + Expect(p::IsRelativePath("SomeFolder/AnotherFolder")).ToEqual(true); +}); - It("Can get parent path", []() - { +It("Can get parent path", []() +{ #if P_PLATFORM_WINDOWS - Expect(p::GetParentPath("F:\\SomeFolder\\AnotherFolder")).ToEqual("F:\\SomeFolder"); + Expect(p::GetParentPath("F:\\SomeFolder\\AnotherFolder")).ToEqual("F:\\SomeFolder"); #endif - Expect(p::GetParentPath("/var/SomeFolder")).ToEqual("/var"); - Expect(p::GetParentPath("/SomeFolder/AnotherFolder")).ToEqual("/SomeFolder"); - Expect(p::GetParentPath("/SomeFolder/SomeFile.txt")).ToEqual("/SomeFolder"); - }); - - It("Executable path is not empty", []() - { - Expect(p::PlatformPaths::GetExecutablePath()).ToNotEqual(""); - }); - - It("Can get extension", []() - { + Expect(p::GetParentPath("/var/SomeFolder")).ToEqual("/var"); + Expect(p::GetParentPath("/SomeFolder/AnotherFolder")).ToEqual("/SomeFolder"); + Expect(p::GetParentPath("/SomeFolder/SomeFile.txt")).ToEqual("/SomeFolder"); +}); + +It("Executable path is not empty", []() +{ + Expect(p::PlatformPaths::GetExecutablePath()).ToNotEqual(""); +}); + +It("Can get extension", []() +{ #if P_PLATFORM_WINDOWS - Expect(p::GetExtension("F:\\SomeFolder\\AnotherFolder.lib")).ToEqual(".lib"); - Expect(p::GetExtension("F:\\AnotherFolder.lib")).ToEqual(".lib"); - Expect(p::GetExtension("F:\\AnotherFolder.")).ToEqual("."); - Expect(p::GetExtension("F:\\AnotherFolder")).ToEqual(""); - Expect(p::GetExtension("F:\\")).ToEqual(""); + Expect(p::GetExtension("F:\\SomeFolder\\AnotherFolder.lib")).ToEqual(".lib"); + Expect(p::GetExtension("F:\\AnotherFolder.lib")).ToEqual(".lib"); + Expect(p::GetExtension("F:\\AnotherFolder.")).ToEqual("."); + Expect(p::GetExtension("F:\\AnotherFolder")).ToEqual(""); + Expect(p::GetExtension("F:\\")).ToEqual(""); #elif P_PLATFORM_LINUX - Expect(p::GetExtension("/var/SomeFolder/AnotherFolder.lib")).ToEqual(".lib"); - Expect(p::GetExtension("/var/AnotherFolder.lib")).ToEqual(".lib"); - Expect(p::GetExtension("/var/AnotherFolder.")).ToEqual("."); - Expect(p::GetExtension("/var/AnotherFolder")).ToEqual(""); - Expect(p::GetExtension("/var/")).ToEqual(""); + Expect(p::GetExtension("/var/SomeFolder/AnotherFolder.lib")).ToEqual(".lib"); + Expect(p::GetExtension("/var/AnotherFolder.lib")).ToEqual(".lib"); + Expect(p::GetExtension("/var/AnotherFolder.")).ToEqual("."); + Expect(p::GetExtension("/var/AnotherFolder")).ToEqual(""); + Expect(p::GetExtension("/var/")).ToEqual(""); #endif - Expect(p::GetExtension("AnotherFolder.lib")).ToEqual(".lib"); - Expect(p::GetExtension("AnotherFolder")).ToEqual(""); - }); + Expect(p::GetExtension("AnotherFolder.lib")).ToEqual(".lib"); + Expect(p::GetExtension("AnotherFolder")).ToEqual(""); +}); - It("Can check extension", []() - { +It("Can check extension", []() +{ #if P_PLATFORM_WINDOWS - Expect(p::HasExtension("F:\\SomeFolder\\AnotherFolder.lib")).ToEqual(true); - Expect(p::HasExtension("F:\\AnotherFolder.lib")).ToEqual(true); - Expect(p::HasExtension("F:\\AnotherFolder.")).ToEqual(true); - Expect(p::HasExtension("F:\\AnotherFolder")).ToEqual(false); - Expect(p::HasExtension("F:\\")).ToEqual(false); + Expect(p::HasExtension("F:\\SomeFolder\\AnotherFolder.lib")).ToEqual(true); + Expect(p::HasExtension("F:\\AnotherFolder.lib")).ToEqual(true); + Expect(p::HasExtension("F:\\AnotherFolder.")).ToEqual(true); + Expect(p::HasExtension("F:\\AnotherFolder")).ToEqual(false); + Expect(p::HasExtension("F:\\")).ToEqual(false); #elif P_PLATFORM_LINUX - Expect(p::HasExtension("/var/SomeFolder/AnotherFolder.lib")).ToEqual(true); - Expect(p::HasExtension("/var/AnotherFolder.lib")).ToEqual(true); - Expect(p::HasExtension("/var/AnotherFolder.")).ToEqual(true); - Expect(p::HasExtension("/var/AnotherFolder")).ToEqual(false); - Expect(p::HasExtension("/var/")).ToEqual(false); + Expect(p::HasExtension("/var/SomeFolder/AnotherFolder.lib")).ToEqual(true); + Expect(p::HasExtension("/var/AnotherFolder.lib")).ToEqual(true); + Expect(p::HasExtension("/var/AnotherFolder.")).ToEqual(true); + Expect(p::HasExtension("/var/AnotherFolder")).ToEqual(false); + Expect(p::HasExtension("/var/")).ToEqual(false); #endif - Expect(p::HasExtension("AnotherFolder.lib")).ToEqual(true); - Expect(p::HasExtension("AnotherFolder")).ToEqual(false); - }); + Expect(p::HasExtension("AnotherFolder.lib")).ToEqual(true); + Expect(p::HasExtension("AnotherFolder")).ToEqual(false); +}); - It("Can replace extension", []() - { - p::String path; +It("Can replace extension", []() +{ + p::String path; #if P_PLATFORM_WINDOWS - path = "F:\\SomeFolder\\AnotherFolder.lib"; - p::ReplaceExtension(path, "txt"); - Expect(path).ToEqual("F:\\SomeFolder\\AnotherFolder.txt"); + path = "F:\\SomeFolder\\AnotherFolder.lib"; + p::ReplaceExtension(path, "txt"); + Expect(path).ToEqual("F:\\SomeFolder\\AnotherFolder.txt"); #elif P_PLATFORM_LINUX - path = "/var/SomeFolder/AnotherFolder.lib"; - p::ReplaceExtension(path, "txt"); - Expect(path).ToEqual("/var/SomeFolder/AnotherFolder.txt"); + path = "/var/SomeFolder/AnotherFolder.lib"; + p::ReplaceExtension(path, "txt"); + Expect(path).ToEqual("/var/SomeFolder/AnotherFolder.txt"); #endif - path = "AnotherFolder.lib"; - p::ReplaceExtension(path, "txt"); - Expect(path).ToEqual("AnotherFolder.txt"); - path = "AnotherFolder."; - p::ReplaceExtension(path, ".txt"); - Expect(path).ToEqual("AnotherFolder.txt"); - path = "AnotherFolder.lib"; - p::ReplaceExtension(path, ".txt"); - Expect(path).ToEqual("AnotherFolder.txt"); - path = "AnotherFolder"; - p::ReplaceExtension(path, "txt"); - Expect(path).ToEqual("AnotherFolder.txt"); - }); - - It("Can get stem", []() - { + path = "AnotherFolder.lib"; + p::ReplaceExtension(path, "txt"); + Expect(path).ToEqual("AnotherFolder.txt"); + path = "AnotherFolder."; + p::ReplaceExtension(path, ".txt"); + Expect(path).ToEqual("AnotherFolder.txt"); + path = "AnotherFolder.lib"; + p::ReplaceExtension(path, ".txt"); + Expect(path).ToEqual("AnotherFolder.txt"); + path = "AnotherFolder"; + p::ReplaceExtension(path, "txt"); + Expect(path).ToEqual("AnotherFolder.txt"); +}); + +It("Can get stem", []() +{ #if P_PLATFORM_WINDOWS - Expect(p::GetStem("F:\\SomeFolder\\AnotherFolder.lib")).ToEqual("AnotherFolder"); - Expect(p::GetStem("F:\\AnotherFolder.lib")).ToEqual("AnotherFolder"); - Expect(p::GetStem("F:\\AnotherFolder.")).ToEqual("AnotherFolder"); - Expect(p::GetStem("F:\\AnotherFolder")).ToEqual("AnotherFolder"); - Expect(p::GetStem("F:\\")).ToEqual(""); + Expect(p::GetStem("F:\\SomeFolder\\AnotherFolder.lib")).ToEqual("AnotherFolder"); + Expect(p::GetStem("F:\\AnotherFolder.lib")).ToEqual("AnotherFolder"); + Expect(p::GetStem("F:\\AnotherFolder.")).ToEqual("AnotherFolder"); + Expect(p::GetStem("F:\\AnotherFolder")).ToEqual("AnotherFolder"); + Expect(p::GetStem("F:\\")).ToEqual(""); #elif P_PLATFORM_LINUX - Expect(p::GetStem("/var/SomeFolder/AnotherFolder.lib")).ToEqual("AnotherFolder"); - Expect(p::GetStem("/var/AnotherFolder.lib")).ToEqual("AnotherFolder"); - Expect(p::GetStem("/var/AnotherFolder.")).ToEqual("AnotherFolder"); - Expect(p::GetStem("/var/AnotherFolder")).ToEqual("AnotherFolder"); - Expect(p::GetStem("/var/")).ToEqual(""); + Expect(p::GetStem("/var/SomeFolder/AnotherFolder.lib")).ToEqual("AnotherFolder"); + Expect(p::GetStem("/var/AnotherFolder.lib")).ToEqual("AnotherFolder"); + Expect(p::GetStem("/var/AnotherFolder.")).ToEqual("AnotherFolder"); + Expect(p::GetStem("/var/AnotherFolder")).ToEqual("AnotherFolder"); + Expect(p::GetStem("/var/")).ToEqual(""); #endif - Expect(p::GetStem("AnotherFolder.lib")).ToEqual("AnotherFolder"); - Expect(p::GetStem("AnotherFolder")).ToEqual("AnotherFolder"); - Expect(p::GetStem("")).ToEqual(""); - }); + Expect(p::GetStem("AnotherFolder.lib")).ToEqual("AnotherFolder"); + Expect(p::GetStem("AnotherFolder")).ToEqual("AnotherFolder"); + Expect(p::GetStem("")).ToEqual(""); +}); - It("Can check stem", []() - { +It("Can check stem", []() +{ #if P_PLATFORM_WINDOWS - Expect(p::HasStem("F:\\SomeFolder\\AnotherFolder.lib")).ToEqual(true); - Expect(p::HasStem("F:\\AnotherFolder.lib")).ToEqual(true); - Expect(p::HasStem("F:\\AnotherFolder.")).ToEqual(true); - Expect(p::HasStem("F:\\AnotherFolder")).ToEqual(true); - Expect(p::HasStem("F:\\")).ToEqual(false); + Expect(p::HasStem("F:\\SomeFolder\\AnotherFolder.lib")).ToEqual(true); + Expect(p::HasStem("F:\\AnotherFolder.lib")).ToEqual(true); + Expect(p::HasStem("F:\\AnotherFolder.")).ToEqual(true); + Expect(p::HasStem("F:\\AnotherFolder")).ToEqual(true); + Expect(p::HasStem("F:\\")).ToEqual(false); #elif P_PLATFORM_LINUX - Expect(p::HasStem("/var/SomeFolder/AnotherFolder.lib")).ToEqual(true); - Expect(p::HasStem("/var/AnotherFolder.lib")).ToEqual(true); - Expect(p::HasStem("/var/AnotherFolder.")).ToEqual(true); - Expect(p::HasStem("/var/AnotherFolder")).ToEqual(true); - Expect(p::HasStem("/var/")).ToEqual(false); + Expect(p::HasStem("/var/SomeFolder/AnotherFolder.lib")).ToEqual(true); + Expect(p::HasStem("/var/AnotherFolder.lib")).ToEqual(true); + Expect(p::HasStem("/var/AnotherFolder.")).ToEqual(true); + Expect(p::HasStem("/var/AnotherFolder")).ToEqual(true); + Expect(p::HasStem("/var/")).ToEqual(false); #endif - Expect(p::HasStem("AnotherFolder.lib")).ToEqual(true); - Expect(p::HasStem("AnotherFolder")).ToEqual(true); - Expect(p::HasStem("")).ToEqual(false); - }); - - - It("Can append to path", []() - { - Expect(p::JoinPaths("", "")).ToEqual(""); - Expect(p::JoinPaths("", "/")).ToEqual("/"); - Expect(p::JoinPaths("", "bar")).ToEqual("bar"); - Expect(p::JoinPaths("", "/bar")).ToEqual("/bar"); - - Expect(p::JoinPaths("/", "")).ToEqual("/"); - Expect(p::JoinPaths("/", "/")).ToEqual("/"); - Expect(p::JoinPaths("/", "bar")).ToEqual("/bar"); - Expect(p::JoinPaths("/", "/bar")).ToEqual("/bar"); - Expect(p::JoinPaths("foo", "/")).ToEqual("/"); - - Expect(p::JoinPaths("foo", "/bar")).ToEqual("/bar"); - Expect(p::JoinPaths("foo/", "")).ToEqual("foo/"); - Expect(p::JoinPaths("foo/", "/")).ToEqual("/"); - Expect(p::JoinPaths("foo/", "bar")).ToEqual("foo/bar"); + Expect(p::HasStem("AnotherFolder.lib")).ToEqual(true); + Expect(p::HasStem("AnotherFolder")).ToEqual(true); + Expect(p::HasStem("")).ToEqual(false); +}); + + +It("Can append to path", []() +{ + Expect(p::JoinPaths("", "")).ToEqual(""); + Expect(p::JoinPaths("", "/")).ToEqual("/"); + Expect(p::JoinPaths("", "bar")).ToEqual("bar"); + Expect(p::JoinPaths("", "/bar")).ToEqual("/bar"); + + Expect(p::JoinPaths("/", "")).ToEqual("/"); + Expect(p::JoinPaths("/", "/")).ToEqual("/"); + Expect(p::JoinPaths("/", "bar")).ToEqual("/bar"); + Expect(p::JoinPaths("/", "/bar")).ToEqual("/bar"); + Expect(p::JoinPaths("foo", "/")).ToEqual("/"); + + Expect(p::JoinPaths("foo", "/bar")).ToEqual("/bar"); + Expect(p::JoinPaths("foo/", "")).ToEqual("foo/"); + Expect(p::JoinPaths("foo/", "/")).ToEqual("/"); + Expect(p::JoinPaths("foo/", "bar")).ToEqual("foo/bar"); #if P_PLATFORM_WINDOWS - Expect(p::JoinPaths("foo", "")).ToEqual("foo\\"); - Expect(p::JoinPaths("foo", "bar")).ToEqual("foo\\bar"); - Expect(p::JoinPaths("foo\\", "\\bar")).ToEqual("\\bar"); - Expect(p::JoinPaths("c:", "bar")).ToEqual("c:bar"); - Expect(p::JoinPaths("\\\\host", "foo")).ToEqual("\\\\host\\foo"); - Expect(p::JoinPaths("\\\\host/", "foo")).ToEqual("\\\\host/foo"); + Expect(p::JoinPaths("foo", "")).ToEqual("foo\\"); + Expect(p::JoinPaths("foo", "bar")).ToEqual("foo\\bar"); + Expect(p::JoinPaths("foo\\", "\\bar")).ToEqual("\\bar"); + Expect(p::JoinPaths("c:", "bar")).ToEqual("c:bar"); + Expect(p::JoinPaths("\\\\host", "foo")).ToEqual("\\\\host\\foo"); + Expect(p::JoinPaths("\\\\host/", "foo")).ToEqual("\\\\host/foo"); #else - Expect(p::JoinPaths("foo", "")).ToEqual("foo/"); - Expect(p::JoinPaths("foo", "bar")).ToEqual("foo/bar"); - Expect(p::JoinPaths("//host", "foo")).ToEqual("//host/foo"); - Expect(p::JoinPaths("//host/", "foo")).ToEqual("//host/foo"); + Expect(p::JoinPaths("foo", "")).ToEqual("foo/"); + Expect(p::JoinPaths("foo", "bar")).ToEqual("foo/bar"); + Expect(p::JoinPaths("//host", "foo")).ToEqual("//host/foo"); + Expect(p::JoinPaths("//host/", "foo")).ToEqual("//host/foo"); #endif - }); }); -return true; -}(); -} // namespace +}); diff --git a/Tests/Math/Color.spec.cpp b/Tests/Math/Color.spec.cpp index fc0cb3d0..da3abaab 100644 --- a/Tests/Math/Color.spec.cpp +++ b/Tests/Math/Color.spec.cpp @@ -7,136 +7,128 @@ using namespace p; -namespace +Spec("Math.Color", []() { -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() +Describe("Helpers", []() { -Spec("Math.Color", []() + It("Can make from rgba", []() + { + auto color = Color::FromRGB(128, 206, 215, 35); + Expect(color.r).ToEqual(128); + Expect(color.g).ToEqual(206); + Expect(color.b).ToEqual(215); + Expect(color.a).ToEqual(35); + }); + It("Can make from Hex", []() + { + auto color = Color::FromHex(0x80ced7); + Expect(color.r).ToEqual(128); + Expect(color.g).ToEqual(206); + Expect(color.b).ToEqual(215); + + auto colora = Color::FromHexAlpha(0x80ced723); + Expect(colora.r).ToEqual(128); + Expect(colora.g).ToEqual(206); + Expect(colora.b).ToEqual(215); + Expect(colora.a).ToEqual(35); + }); + + It("Can make from packed", []() + { + auto argb = Color::FromPackedARGB(0x2380ced7); + Expect(argb.r).ToEqual(128); + Expect(argb.g).ToEqual(206); + Expect(argb.b).ToEqual(215); + Expect(argb.a).ToEqual(35); + + auto abgr = Color::FromPackedABGR(0x23d7ce80); + Expect(abgr.r).ToEqual(128); + Expect(abgr.g).ToEqual(206); + Expect(abgr.b).ToEqual(215); + Expect(abgr.a).ToEqual(35); + + auto rgba = Color::FromPackedRGBA(0x80ced723); + Expect(rgba.r).ToEqual(128); + Expect(rgba.g).ToEqual(206); + Expect(rgba.b).ToEqual(215); + Expect(rgba.a).ToEqual(35); + + auto bgra = Color::FromPackedBGRA(0xd7ce8023); + Expect(bgra.r).ToEqual(128); + Expect(bgra.g).ToEqual(206); + Expect(bgra.b).ToEqual(215); + Expect(bgra.a).ToEqual(35); + }); + + It("Can get as packed", []() + { + auto color = Color(128, 206, 215, 35); + Expect(color.ToPackedARGB()).ToEqual(0x2380ced7); + Expect(color.ToPackedABGR()).ToEqual(0x23d7ce80); + Expect(color.ToPackedRGBA()).ToEqual(0x80ced723); + Expect(color.ToPackedBGRA()).ToEqual(0xd7ce8023); + }); +}); +Describe("LinearColor", []() { - Describe("Helpers", []() + It("Can Shade", []() { - It("Can make from rgba", []() - { - auto color = Color::FromRGB(128, 206, 215, 35); - Expect(color.r).ToEqual(128); - Expect(color.g).ToEqual(206); - Expect(color.b).ToEqual(215); - Expect(color.a).ToEqual(35); - }); - It("Can make from Hex", []() - { - auto color = Color::FromHex(0x80ced7); - Expect(color.r).ToEqual(128); - Expect(color.g).ToEqual(206); - Expect(color.b).ToEqual(215); - - auto colora = Color::FromHexAlpha(0x80ced723); - Expect(colora.r).ToEqual(128); - Expect(colora.g).ToEqual(206); - Expect(colora.b).ToEqual(215); - Expect(colora.a).ToEqual(35); - }); - - It("Can make from packed", []() - { - auto argb = Color::FromPackedARGB(0x2380ced7); - Expect(argb.r).ToEqual(128); - Expect(argb.g).ToEqual(206); - Expect(argb.b).ToEqual(215); - Expect(argb.a).ToEqual(35); - - auto abgr = Color::FromPackedABGR(0x23d7ce80); - Expect(abgr.r).ToEqual(128); - Expect(abgr.g).ToEqual(206); - Expect(abgr.b).ToEqual(215); - Expect(abgr.a).ToEqual(35); - - auto rgba = Color::FromPackedRGBA(0x80ced723); - Expect(rgba.r).ToEqual(128); - Expect(rgba.g).ToEqual(206); - Expect(rgba.b).ToEqual(215); - Expect(rgba.a).ToEqual(35); - - auto bgra = Color::FromPackedBGRA(0xd7ce8023); - Expect(bgra.r).ToEqual(128); - Expect(bgra.g).ToEqual(206); - Expect(bgra.b).ToEqual(215); - Expect(bgra.a).ToEqual(35); - }); - - It("Can get as packed", []() - { - auto color = Color(128, 206, 215, 35); - Expect(color.ToPackedARGB()).ToEqual(0x2380ced7); - Expect(color.ToPackedABGR()).ToEqual(0x23d7ce80); - Expect(color.ToPackedRGBA()).ToEqual(0x80ced723); - Expect(color.ToPackedBGRA()).ToEqual(0xd7ce8023); - }); + Expect(LinearColor::White().Shade(1.0f)).ToEqual(LinearColor::Black()); + Expect(LinearColor::White().Shade(0.5f)).ToEqual(LinearColor::Gray()); + constexpr LinearColor color{Color::FromHex(0x80ced7)}; + Expect(color.Shade(0.5f)).ToEqual(LinearColor{Color::FromHex(0x40676B)}); }); - Describe("LinearColor", []() + + It("Shade doesn't change alpha", []() { - It("Can Shade", []() - { - Expect(LinearColor::White().Shade(1.0f)).ToEqual(LinearColor::Black()); - Expect(LinearColor::White().Shade(0.5f)).ToEqual(LinearColor::Gray()); - constexpr LinearColor color{Color::FromHex(0x80ced7)}; - Expect(color.Shade(0.5f)).ToEqual(LinearColor{Color::FromHex(0x40676B)}); - }); - - It("Shade doesn't change alpha", []() - { - Expect(std::abs(LinearColor::White().Translucency(0.5f).Shade(1.0f).a - 0.5f)) - .ToBeLessOrEqual(0.01f); - }); - - It("Can Tint", []() - { - Expect(LinearColor::Black().Tint(1.0f)).ToEqual(LinearColor::White()); - Expect(LinearColor::Black().Tint(0.5f)).ToEqual(LinearColor::Gray()); - Expect(Color::FromHex(0x80ced7).Tint(0.5f)).ToEqual(Color::FromHex(0xbfe6eb)); - }); - - It("Tint doesn't change alpha", []() - { - Expect(std::abs(LinearColor::Black().Translucency(0.5f).Tint(1.0f).a - 0.5f)) - .ToBeLessOrEqual(0.01f); - }); + Expect(std::abs(LinearColor::White().Translucency(0.5f).Shade(1.0f).a - 0.5f)) + .ToBeLessOrEqual(0.01f); }); - Describe("Color", []() + + It("Can Tint", []() + { + Expect(LinearColor::Black().Tint(1.0f)).ToEqual(LinearColor::White()); + Expect(LinearColor::Black().Tint(0.5f)).ToEqual(LinearColor::Gray()); + Expect(Color::FromHex(0x80ced7).Tint(0.5f)).ToEqual(Color::FromHex(0xbfe6eb)); + }); + + It("Tint doesn't change alpha", []() { - It("Can Shade", []() - { - Expect(Color::White().Shade(1.0f)).ToEqual(Color::Black()); - Expect(Color::White().Shade(0.5f)).ToEqual(Color::Gray()); - Expect(Color::FromHex(0x80ced7).Shade(0.5f)).ToEqual(Color::FromHex(0x40676B)); - }); - - It("Shade doesn't change alpha", []() - { - Expect(Color::White().Translucency(127).Shade(1.0f).a).ToEqual(127); - }); - - It("Can Tint", []() - { - Expect(Color::Black().Tint(1.0f)).ToEqual(Color::White()); - Expect(Color::Black().Tint(0.5f)).ToEqual(Color::Gray()); - Expect(Color::FromHex(0x80ced7).Tint(0.5f)).ToEqual(Color::FromHex(0xbfe6eb)); - }); - - It("Tint doesn't change alpha", []() - { - Expect(Color::Black().Translucency(127).Tint(1.0f).a).ToEqual(127); - }); - - It("Can convert to linear", []() - { - Expect(LinearColor{Color::White()}).ToEqual(LinearColor::White()); - Expect(LinearColor{Color::Black()}).ToEqual(LinearColor::Black()); - Expect(LinearColor{Color::Gray()}).ToEqual(LinearColor::Gray()); - }); + Expect(std::abs(LinearColor::Black().Translucency(0.5f).Tint(1.0f).a - 0.5f)) + .ToBeLessOrEqual(0.01f); + }); +}); +Describe("Color", []() +{ + It("Can Shade", []() + { + Expect(Color::White().Shade(1.0f)).ToEqual(Color::Black()); + Expect(Color::White().Shade(0.5f)).ToEqual(Color::Gray()); + Expect(Color::FromHex(0x80ced7).Shade(0.5f)).ToEqual(Color::FromHex(0x40676B)); + }); + + It("Shade doesn't change alpha", []() + { + Expect(Color::White().Translucency(127).Shade(1.0f).a).ToEqual(127); + }); + + It("Can Tint", []() + { + Expect(Color::Black().Tint(1.0f)).ToEqual(Color::White()); + Expect(Color::Black().Tint(0.5f)).ToEqual(Color::Gray()); + Expect(Color::FromHex(0x80ced7).Tint(0.5f)).ToEqual(Color::FromHex(0xbfe6eb)); + }); + + It("Tint doesn't change alpha", []() + { + Expect(Color::Black().Translucency(127).Tint(1.0f).a).ToEqual(127); + }); + + It("Can convert to linear", []() + { + Expect(LinearColor{Color::White()}).ToEqual(LinearColor::White()); + Expect(LinearColor{Color::Black()}).ToEqual(LinearColor::Black()); + Expect(LinearColor{Color::Gray()}).ToEqual(LinearColor::Gray()); + }); }); }); -return true; -}(); -} // namespace diff --git a/Tests/Math/Math.spec.cpp b/Tests/Math/Math.spec.cpp index 49d6f95d..482592c5 100644 --- a/Tests/Math/Math.spec.cpp +++ b/Tests/Math/Math.spec.cpp @@ -19,350 +19,342 @@ namespace } // namespace -namespace -{ -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() -{ Spec("Math.Math", []() { - Describe("Binary Search", []() +Describe("Binary Search", []() +{ + It("LowerBound", [=]() { - It("LowerBound", [=]() - { - Expect(bottomUp.LowerBound(34)).ToEqual(1); - Expect(bottomUp.LowerBound(100)).ToEqual(3); - Expect(bottomUp.LowerBound(51)).ToEqual(3); + Expect(bottomUp.LowerBound(34)).ToEqual(1); + Expect(bottomUp.LowerBound(100)).ToEqual(3); + Expect(bottomUp.LowerBound(51)).ToEqual(3); - Expect(topDown.LowerBound(34, TGreater<>())).ToEqual(3); - Expect(topDown.LowerBound(100, TGreater<>())).ToEqual(1); - Expect(topDown.LowerBound(51, TGreater<>())).ToEqual(2); - }); + Expect(topDown.LowerBound(34, TGreater<>())).ToEqual(3); + Expect(topDown.LowerBound(100, TGreater<>())).ToEqual(1); + Expect(topDown.LowerBound(51, TGreater<>())).ToEqual(2); + }); - It("UpperBound", [=]() - { - Expect(bottomUp.UpperBound(34)).ToEqual(2); - Expect(bottomUp.UpperBound(100)).ToEqual(4); + It("UpperBound", [=]() + { + Expect(bottomUp.UpperBound(34)).ToEqual(2); + Expect(bottomUp.UpperBound(100)).ToEqual(4); - Expect(topDown.UpperBound(34, TGreater<>())).ToEqual(4); - Expect(topDown.UpperBound(100, TGreater<>())).ToEqual(2); - }); + Expect(topDown.UpperBound(34, TGreater<>())).ToEqual(4); + Expect(topDown.UpperBound(100, TGreater<>())).ToEqual(2); + }); - It("Can find equal", [=]() - { - Expect(bottomUp.FindSorted(0)).ToEqual(NO_INDEX); - Expect(bottomUp.FindSorted(34)).ToEqual(1); - Expect(bottomUp.FindSorted(33)).ToEqual(NO_INDEX); - Expect(bottomUp.FindSorted(121)).ToEqual(NO_INDEX); + It("Can find equal", [=]() + { + Expect(bottomUp.FindSorted(0)).ToEqual(NO_INDEX); + Expect(bottomUp.FindSorted(34)).ToEqual(1); + Expect(bottomUp.FindSorted(33)).ToEqual(NO_INDEX); + Expect(bottomUp.FindSorted(121)).ToEqual(NO_INDEX); - Expect(topDown.FindSorted(34, TGreater<>())).ToEqual(3); - }); + Expect(topDown.FindSorted(34, TGreater<>())).ToEqual(3); + }); - Describe("FindSortedMax", []() + Describe("FindSortedMax", []() + { + Describe("Ordered by a < b", []() { - Describe("Ordered by a < b", []() - { - TArray bottomUp{23, 34, 50, 50, 100, 120}; + TArray bottomUp{23, 34, 50, 50, 100, 120}; - It("Find first item", [=]() - { - auto i4 = bottomUp.FindSortedMax(23, false); - Expect(i4).ToEqual(NO_INDEX); + It("Find first item", [=]() + { + auto i4 = bottomUp.FindSortedMax(23, false); + Expect(i4).ToEqual(NO_INDEX); - auto i5 = bottomUp.FindSortedMax(23, true); - Expect(i5).ToEqual(0); + auto i5 = bottomUp.FindSortedMax(23, true); + Expect(i5).ToEqual(0); - auto i6 = bottomUp.FindSortedMax(22, true); - Expect(i6).ToEqual(NO_INDEX); - }); + auto i6 = bottomUp.FindSortedMax(22, true); + Expect(i6).ToEqual(NO_INDEX); + }); - It("Find any item", [=]() - { - auto i1 = bottomUp.FindSortedMax(34, true); - Expect(i1).ToEqual(1); + It("Find any item", [=]() + { + auto i1 = bottomUp.FindSortedMax(34, true); + Expect(i1).ToEqual(1); - auto i2 = bottomUp.FindSortedMax(33, true); - Expect(i2).ToEqual(0); + auto i2 = bottomUp.FindSortedMax(33, true); + Expect(i2).ToEqual(0); - auto i3 = bottomUp.FindSortedMax(34, false); - Expect(i3).ToEqual(0); - }); + auto i3 = bottomUp.FindSortedMax(34, false); + Expect(i3).ToEqual(0); + }); - It("Find last item", [=]() - { - auto i4 = bottomUp.FindSortedMax(120, false); - Expect(i4).ToEqual(4); + It("Find last item", [=]() + { + auto i4 = bottomUp.FindSortedMax(120, false); + Expect(i4).ToEqual(4); - auto i5 = bottomUp.FindSortedMax(120, true); - Expect(i5).ToEqual(5); + auto i5 = bottomUp.FindSortedMax(120, true); + Expect(i5).ToEqual(5); - auto i6 = bottomUp.FindSortedMax(121, true); - Expect(i6).ToEqual(5); + auto i6 = bottomUp.FindSortedMax(121, true); + Expect(i6).ToEqual(5); - auto i7 = bottomUp.FindSortedMax(100, false); - Expect(i7).ToEqual(3); - }); + auto i7 = bottomUp.FindSortedMax(100, false); + Expect(i7).ToEqual(3); }); + }); - Describe("Ordered by a > b", []() - { - TArray topDown{120, 100, 50, 50, 34, 23}; + Describe("Ordered by a > b", []() + { + TArray topDown{120, 100, 50, 50, 34, 23}; - It("Find first item", [=]() - { - auto i4 = topDown.FindSortedMax(120, true); - Expect(i4).ToEqual(0); + It("Find first item", [=]() + { + auto i4 = topDown.FindSortedMax(120, true); + Expect(i4).ToEqual(0); - auto i5 = topDown.FindSortedMax(120, false); - Expect(i5).ToEqual(1); + auto i5 = topDown.FindSortedMax(120, false); + Expect(i5).ToEqual(1); - auto i6 = topDown.FindSortedMax(121, true); - Expect(i6).ToEqual(0); - }); + auto i6 = topDown.FindSortedMax(121, true); + Expect(i6).ToEqual(0); + }); - It("Find any item", [=]() - { - auto i1 = topDown.FindSortedMax(34, true); - Expect(i1).ToEqual(4); + It("Find any item", [=]() + { + auto i1 = topDown.FindSortedMax(34, true); + Expect(i1).ToEqual(4); - auto i2 = topDown.FindSortedMax(33, true); - Expect(i2).ToEqual(5); + auto i2 = topDown.FindSortedMax(33, true); + Expect(i2).ToEqual(5); - auto i3 = topDown.FindSortedMax(34, false); - Expect(i3).ToEqual(5); - }); + auto i3 = topDown.FindSortedMax(34, false); + Expect(i3).ToEqual(5); + }); - It("Find last item", [=]() - { - auto i4 = topDown.FindSortedMax(23, false); - Expect(i4).ToEqual(NO_INDEX); + It("Find last item", [=]() + { + auto i4 = topDown.FindSortedMax(23, false); + Expect(i4).ToEqual(NO_INDEX); - auto i5 = topDown.FindSortedMax(23, true); - Expect(i5).ToEqual(5); + auto i5 = topDown.FindSortedMax(23, true); + Expect(i5).ToEqual(5); - auto i6 = topDown.FindSortedMax(22, true); - Expect(i6).ToEqual(NO_INDEX); - }); + auto i6 = topDown.FindSortedMax(22, true); + Expect(i6).ToEqual(NO_INDEX); }); + }); - Describe("All same values", []() - { - TArray allEqual{10, 10, 10}; + Describe("All same values", []() + { + TArray allEqual{10, 10, 10}; - It("Doesnt find smaller", [=]() - { - auto i1 = allEqual.FindSortedMax(9, false); - Expect(i1).ToEqual(NO_INDEX); + It("Doesnt find smaller", [=]() + { + auto i1 = allEqual.FindSortedMax(9, false); + Expect(i1).ToEqual(NO_INDEX); - auto i2 = allEqual.FindSortedMax(10, false); - Expect(i2).ToEqual(NO_INDEX); - }); + auto i2 = allEqual.FindSortedMax(10, false); + Expect(i2).ToEqual(NO_INDEX); + }); - It("Finds smaller", [=]() - { - auto i1 = allEqual.FindSortedMax(10, true); - Expect(i1).ToEqual(0); + It("Finds smaller", [=]() + { + auto i1 = allEqual.FindSortedMax(10, true); + Expect(i1).ToEqual(0); - auto i2 = allEqual.FindSortedMax(11, false); - Expect(i2).ToEqual(0); - }); + auto i2 = allEqual.FindSortedMax(11, false); + Expect(i2).ToEqual(0); }); }); - Describe("FindSortedMin", []() + }); + Describe("FindSortedMin", []() + { + Describe("Ordered by a < b", []() { - Describe("Ordered by a < b", []() - { - TArray bottomUp{23, 34, 50, 50, 100, 120}; + TArray bottomUp{23, 34, 50, 50, 100, 120}; - It("Find first item", [=]() - { - auto i1 = bottomUp.FindSortedMin(23, true); - Expect(i1).ToEqual(0); + It("Find first item", [=]() + { + auto i1 = bottomUp.FindSortedMin(23, true); + Expect(i1).ToEqual(0); - auto i2 = bottomUp.FindSortedMin(20, true); - Expect(i2).ToEqual(0); + auto i2 = bottomUp.FindSortedMin(20, true); + Expect(i2).ToEqual(0); - auto i3 = bottomUp.FindSortedMin(23, false); - Expect(i3).ToEqual(1); - }); + auto i3 = bottomUp.FindSortedMin(23, false); + Expect(i3).ToEqual(1); + }); - It("Find any item", [=]() - { - auto i1 = bottomUp.FindSortedMin(33, false); - Expect(i1).ToEqual(1); + It("Find any item", [=]() + { + auto i1 = bottomUp.FindSortedMin(33, false); + Expect(i1).ToEqual(1); - auto i2 = bottomUp.FindSortedMin(34, true); - Expect(i2).ToEqual(1); + auto i2 = bottomUp.FindSortedMin(34, true); + Expect(i2).ToEqual(1); - auto i3 = bottomUp.FindSortedMin(34, false); - Expect(i3).ToEqual(2); - }); + auto i3 = bottomUp.FindSortedMin(34, false); + Expect(i3).ToEqual(2); + }); - It("Find last item", [=]() - { - auto i1 = bottomUp.FindSortedMin(100, false); - Expect(i1).ToEqual(5); + It("Find last item", [=]() + { + auto i1 = bottomUp.FindSortedMin(100, false); + Expect(i1).ToEqual(5); - auto i2 = bottomUp.FindSortedMin(120, false); - Expect(i2).ToEqual(NO_INDEX); + auto i2 = bottomUp.FindSortedMin(120, false); + Expect(i2).ToEqual(NO_INDEX); - auto i3 = bottomUp.FindSortedMin(120, true); - Expect(i3).ToEqual(5); + auto i3 = bottomUp.FindSortedMin(120, true); + Expect(i3).ToEqual(5); - auto i4 = bottomUp.FindSortedMin(121, true); - Expect(i4).ToEqual(NO_INDEX); - }); + auto i4 = bottomUp.FindSortedMin(121, true); + Expect(i4).ToEqual(NO_INDEX); }); + }); - Describe("Ordered by a > b", []() - { - TArray topDown{120, 100, 50, 50, 34, 23}; + Describe("Ordered by a > b", []() + { + TArray topDown{120, 100, 50, 50, 34, 23}; - It("Find first item", [=]() - { - auto i4 = topDown.FindSortedMin(120, true); - Expect(i4).ToEqual(0); + It("Find first item", [=]() + { + auto i4 = topDown.FindSortedMin(120, true); + Expect(i4).ToEqual(0); - auto i5 = topDown.FindSortedMin(120, false); - Expect(i5).ToEqual(NO_INDEX); + auto i5 = topDown.FindSortedMin(120, false); + Expect(i5).ToEqual(NO_INDEX); - auto i6 = topDown.FindSortedMin(121, true); - Expect(i6).ToEqual(NO_INDEX); - }); + auto i6 = topDown.FindSortedMin(121, true); + Expect(i6).ToEqual(NO_INDEX); + }); - It("Find any item", [=]() - { - auto i1 = topDown.FindSortedMin(34, true); - Expect(i1).ToEqual(4); + It("Find any item", [=]() + { + auto i1 = topDown.FindSortedMin(34, true); + Expect(i1).ToEqual(4); - auto i2 = topDown.FindSortedMin(33, true); - Expect(i2).ToEqual(4); + auto i2 = topDown.FindSortedMin(33, true); + Expect(i2).ToEqual(4); - auto i3 = topDown.FindSortedMin(34, false); - Expect(i3).ToEqual(3); - }); + auto i3 = topDown.FindSortedMin(34, false); + Expect(i3).ToEqual(3); + }); - It("Find last item", [=]() - { - auto i4 = topDown.FindSortedMin(23, false); - Expect(i4).ToEqual(4); + It("Find last item", [=]() + { + auto i4 = topDown.FindSortedMin(23, false); + Expect(i4).ToEqual(4); - auto i5 = topDown.FindSortedMin(23, true); - Expect(i5).ToEqual(5); + auto i5 = topDown.FindSortedMin(23, true); + Expect(i5).ToEqual(5); - auto i6 = topDown.FindSortedMin(22, true); - Expect(i6).ToEqual(5); - }); + auto i6 = topDown.FindSortedMin(22, true); + Expect(i6).ToEqual(5); }); + }); - Describe("All same values", []() - { - TArray allEqual{10, 10, 10}; + Describe("All same values", []() + { + TArray allEqual{10, 10, 10}; - It("Doesnt find bigger", [=]() - { - auto i1 = allEqual.FindSortedMin(11, false); - Expect(i1).ToEqual(NO_INDEX); + It("Doesnt find bigger", [=]() + { + auto i1 = allEqual.FindSortedMin(11, false); + Expect(i1).ToEqual(NO_INDEX); - auto i2 = allEqual.FindSortedMin(10, false); - Expect(i2).ToEqual(NO_INDEX); - }); + auto i2 = allEqual.FindSortedMin(10, false); + Expect(i2).ToEqual(NO_INDEX); + }); - It("Finds bigger", [=]() - { - auto i1 = allEqual.FindSortedMin(10, true); - Expect(i1).ToEqual(0); + It("Finds bigger", [=]() + { + auto i1 = allEqual.FindSortedMin(10, true); + Expect(i1).ToEqual(0); - auto i2 = allEqual.FindSortedMin(9, false); - Expect(i2).ToEqual(0); - }); + auto i2 = allEqual.FindSortedMin(9, false); + Expect(i2).ToEqual(0); }); }); }); +}); - It("Can check Infinite", [=]() +It("Can check Infinite", [=]() +{ + Expect(IsInf(0.0)).ToEqual(false); + Expect(IsInf(-0.0)).ToEqual(false); + Expect(IsInf(1.0)).ToEqual(false); + Expect(IsInf(-1.0)).ToEqual(false); + + static constexpr double dInfinite = Limits::Infinity(); + Expect(IsInf(dInfinite)).ToEqual(true); + Expect(IsInf(-dInfinite)).ToEqual(true); + Expect(IsPosInf(-dInfinite)).ToEqual(false); + Expect(IsNegInf(dInfinite)).ToEqual(false); + Expect(IsInf(Limits::Max())).ToEqual(false); + Expect(IsInf(Limits::Lowest())).ToEqual(false); + Expect(IsInf(double(bigNumber))).ToEqual(false); +}); + +It("Can check NAN", [=]() +{ + Expect(IsNAN(0.0)).ToEqual(false); + Expect(IsNAN(Limits::QuietNaN())).ToEqual(true); +}); + +Describe("Roundings", []() +{ + It("Can Floor", [=]() { - Expect(IsInf(0.0)).ToEqual(false); - Expect(IsInf(-0.0)).ToEqual(false); - Expect(IsInf(1.0)).ToEqual(false); - Expect(IsInf(-1.0)).ToEqual(false); + Expect(Floor(0.0)).ToEqual(std::floor(0.0)); + Expect(Floor(-0.0)).ToEqual(std::floor(-0.0)); + Expect(Floor(4.2)).ToEqual(std::floor(4.2)); + Expect(Floor(4.5)).ToEqual(std::floor(4.5)); + Expect(Floor(4.7)).ToEqual(std::floor(4.7)); + Expect(Floor(5.0)).ToEqual(std::floor(5.0)); + Expect(Floor(-4.2)).ToEqual(std::floor(-4.2)); + Expect(Floor(-4.7)).ToEqual(std::floor(-4.7)); + Expect(Floor(-5.0)).ToEqual(std::floor(-5.0)); + Expect(Floor(99999999999999999.0 + 0.5)).ToEqual(99999999999999999.0); static constexpr double dInfinite = Limits::Infinity(); - Expect(IsInf(dInfinite)).ToEqual(true); - Expect(IsInf(-dInfinite)).ToEqual(true); - Expect(IsPosInf(-dInfinite)).ToEqual(false); - Expect(IsNegInf(dInfinite)).ToEqual(false); - Expect(IsInf(Limits::Max())).ToEqual(false); - Expect(IsInf(Limits::Lowest())).ToEqual(false); - Expect(IsInf(double(bigNumber))).ToEqual(false); + Expect(Floor(-dInfinite)).ToEqual(std::floor(-dInfinite)); + Expect(Floor(dInfinite)).ToEqual(std::floor(dInfinite)); + Expect(IsNAN(Floor(Limits::QuietNaN()))).ToEqual(true); }); - - It("Can check NAN", [=]() + It("Can Ceil", [=]() { - Expect(IsNAN(0.0)).ToEqual(false); - Expect(IsNAN(Limits::QuietNaN())).ToEqual(true); + Expect(Ceil(0.0)).ToEqual(std::ceil(0.0)); + Expect(Ceil(-0.0)).ToEqual(std::ceil(-0.0)); + Expect(Ceil(4.2)).ToEqual(std::ceil(4.2)); + Expect(Ceil(4.5)).ToEqual(std::ceil(4.5)); + Expect(Ceil(4.7)).ToEqual(std::ceil(4.7)); + Expect(Ceil(5.0)).ToEqual(std::ceil(5.0)); + Expect(Ceil(-4.2)).ToEqual(std::ceil(-4.2)); + Expect(Ceil(-4.7)).ToEqual(std::ceil(-4.7)); + Expect(Ceil(-5.0)).ToEqual(std::ceil(-5.0)); + Expect(Ceil(99999999999999999.0 - 0.5)).ToEqual(99999999999999999.0); + + static constexpr double dInfinite = Limits::Infinity(); + Expect(Ceil(-dInfinite)).ToEqual(std::ceil(-dInfinite)); + Expect(Ceil(dInfinite)).ToEqual(std::ceil(dInfinite)); + Expect(IsNAN(Ceil(Limits::QuietNaN()))).ToEqual(true); }); - Describe("Roundings", []() + It("Can Round", [=]() { - It("Can Floor", [=]() - { - Expect(Floor(0.0)).ToEqual(std::floor(0.0)); - Expect(Floor(-0.0)).ToEqual(std::floor(-0.0)); - Expect(Floor(4.2)).ToEqual(std::floor(4.2)); - Expect(Floor(4.5)).ToEqual(std::floor(4.5)); - Expect(Floor(4.7)).ToEqual(std::floor(4.7)); - Expect(Floor(5.0)).ToEqual(std::floor(5.0)); - Expect(Floor(-4.2)).ToEqual(std::floor(-4.2)); - Expect(Floor(-4.7)).ToEqual(std::floor(-4.7)); - Expect(Floor(-5.0)).ToEqual(std::floor(-5.0)); - Expect(Floor(99999999999999999.0 + 0.5)).ToEqual(99999999999999999.0); - - static constexpr double dInfinite = Limits::Infinity(); - Expect(Floor(-dInfinite)).ToEqual(std::floor(-dInfinite)); - Expect(Floor(dInfinite)).ToEqual(std::floor(dInfinite)); - Expect(IsNAN(Floor(Limits::QuietNaN()))).ToEqual(true); - }); - It("Can Ceil", [=]() - { - Expect(Ceil(0.0)).ToEqual(std::ceil(0.0)); - Expect(Ceil(-0.0)).ToEqual(std::ceil(-0.0)); - Expect(Ceil(4.2)).ToEqual(std::ceil(4.2)); - Expect(Ceil(4.5)).ToEqual(std::ceil(4.5)); - Expect(Ceil(4.7)).ToEqual(std::ceil(4.7)); - Expect(Ceil(5.0)).ToEqual(std::ceil(5.0)); - Expect(Ceil(-4.2)).ToEqual(std::ceil(-4.2)); - Expect(Ceil(-4.7)).ToEqual(std::ceil(-4.7)); - Expect(Ceil(-5.0)).ToEqual(std::ceil(-5.0)); - Expect(Ceil(99999999999999999.0 - 0.5)).ToEqual(99999999999999999.0); - - static constexpr double dInfinite = Limits::Infinity(); - Expect(Ceil(-dInfinite)).ToEqual(std::ceil(-dInfinite)); - Expect(Ceil(dInfinite)).ToEqual(std::ceil(dInfinite)); - Expect(IsNAN(Ceil(Limits::QuietNaN()))).ToEqual(true); - }); + Expect(Round(0.0)).ToEqual(std::round(0.0)); + Expect(Round(-0.0)).ToEqual(std::round(-0.0)); + Expect(Round(4.2)).ToEqual(std::round(4.2)); + Expect(Round(4.5)).ToEqual(std::round(4.5)); + Expect(Round(4.7)).ToEqual(std::round(4.7)); + Expect(Round(5.0)).ToEqual(std::round(5.0)); + Expect(Round(-4.2)).ToEqual(std::round(-4.2)); + Expect(Round(-4.7)).ToEqual(std::round(-4.7)); + Expect(Round(-5.0)).ToEqual(std::round(-5.0)); + Expect(Round(99999999999999999.0 - 0.4)).ToEqual(99999999999999999.0); - It("Can Round", [=]() - { - Expect(Round(0.0)).ToEqual(std::round(0.0)); - Expect(Round(-0.0)).ToEqual(std::round(-0.0)); - Expect(Round(4.2)).ToEqual(std::round(4.2)); - Expect(Round(4.5)).ToEqual(std::round(4.5)); - Expect(Round(4.7)).ToEqual(std::round(4.7)); - Expect(Round(5.0)).ToEqual(std::round(5.0)); - Expect(Round(-4.2)).ToEqual(std::round(-4.2)); - Expect(Round(-4.7)).ToEqual(std::round(-4.7)); - Expect(Round(-5.0)).ToEqual(std::round(-5.0)); - Expect(Round(99999999999999999.0 - 0.4)).ToEqual(99999999999999999.0); - - static constexpr double dInfinite = Limits::Infinity(); - Expect(Round(-dInfinite)).ToEqual(std::round(-dInfinite)); - Expect(Round(dInfinite)).ToEqual(std::round(dInfinite)); - Expect(IsNAN(Round(Limits::QuietNaN()))).ToEqual(true); - }); + static constexpr double dInfinite = Limits::Infinity(); + Expect(Round(-dInfinite)).ToEqual(std::round(-dInfinite)); + Expect(Round(dInfinite)).ToEqual(std::round(dInfinite)); + Expect(IsNAN(Round(Limits::QuietNaN()))).ToEqual(true); }); }); -return true; -}(); -} // namespace +}); diff --git a/Tests/Math/Vector.spec.cpp b/Tests/Math/Vector.spec.cpp index 82728fae..08f0ee21 100644 --- a/Tests/Math/Vector.spec.cpp +++ b/Tests/Math/Vector.spec.cpp @@ -7,70 +7,62 @@ using namespace p; -namespace -{ -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() -{ Spec("Math.Vector", []() { - Describe("v2", []() +Describe("v2", []() +{ + It("Can reflect", []() { - It("Can reflect", []() - { - p::v2 v{0.f, 1.f}; - p::v2 normal{1.f, 0.f}; - p::v2 v2 = v.Reflect(normal); - Expect(v2.Equals({0.f, 1.f})).ToEqual(true); - v = p::v2{0.f, 1.f}; - normal = p::v2{0.f, 1.f}; - v2 = v.Reflect(normal); - Expect(v2.Equals({0.f, -1.f})).ToEqual(true); - v = p::v2{1.f, 1.f}; - normal = p::v2{0.f, 1.f}; - v2 = v.Reflect(normal); - Expect(v2.Equals({1.f, -1.f})).ToEqual(true); - v = p::v2{1.f, 1.f}; - normal = p::v2{1.f, 0.f}; - v2 = v.Reflect(normal); - Expect(v2.Equals({-1.f, 1.f})).ToEqual(true); - v = p::v2{-1.f, 1.f}; - normal = p::v2{-1.f, 0.f}; - v2 = v.Reflect(normal); - Expect(v2.Equals({1.f, 1.f})).ToEqual(true); - v = p::v2{-1.f, -1.f}; - normal = p::v2{0.f, 1.f}; - v2 = v.Reflect(normal); - Expect(v2.Equals({-1.f, 1.f})).ToEqual(true); - v = p::v2{0.f, 1.f}; - normal = p::v2{0.f, 1.f}; - v2 = v.Reflect(normal); - Expect(v2.Equals({0.f, -1.f})).ToEqual(true); - v = p::v2{0.f, -1.f}; - normal = p::v2{0.f, 1.f}; - v2 = v.Reflect(normal); - Expect(v2.Equals({0.f, 1.f})).ToEqual(true); - }); + p::v2 v{0.f, 1.f}; + p::v2 normal{1.f, 0.f}; + p::v2 v2 = v.Reflect(normal); + Expect(v2.Equals({0.f, 1.f})).ToEqual(true); + v = p::v2{0.f, 1.f}; + normal = p::v2{0.f, 1.f}; + v2 = v.Reflect(normal); + Expect(v2.Equals({0.f, -1.f})).ToEqual(true); + v = p::v2{1.f, 1.f}; + normal = p::v2{0.f, 1.f}; + v2 = v.Reflect(normal); + Expect(v2.Equals({1.f, -1.f})).ToEqual(true); + v = p::v2{1.f, 1.f}; + normal = p::v2{1.f, 0.f}; + v2 = v.Reflect(normal); + Expect(v2.Equals({-1.f, 1.f})).ToEqual(true); + v = p::v2{-1.f, 1.f}; + normal = p::v2{-1.f, 0.f}; + v2 = v.Reflect(normal); + Expect(v2.Equals({1.f, 1.f})).ToEqual(true); + v = p::v2{-1.f, -1.f}; + normal = p::v2{0.f, 1.f}; + v2 = v.Reflect(normal); + Expect(v2.Equals({-1.f, 1.f})).ToEqual(true); + v = p::v2{0.f, 1.f}; + normal = p::v2{0.f, 1.f}; + v2 = v.Reflect(normal); + Expect(v2.Equals({0.f, -1.f})).ToEqual(true); + v = p::v2{0.f, -1.f}; + normal = p::v2{0.f, 1.f}; + v2 = v.Reflect(normal); + Expect(v2.Equals({0.f, 1.f})).ToEqual(true); + }); - It("Can convert to angle", []() - { - float anglea = p::v2{0.f, 1.f}.Angle(); - Expect(anglea).ToEqual(90.f); - float angleb = p::v2{0.f, -1.f}.Angle(); - Expect(angleb).ToEqual(-90.f); - float anglec = p::v2{1.f, 0.f}.Angle(); - Expect(anglec).ToEqual(0.f); - float angled = p::v2{-1.f, 0.f}.Angle(); - Expect(angled).ToEqual(180.f); - }); + It("Can convert to angle", []() + { + float anglea = p::v2{0.f, 1.f}.Angle(); + Expect(anglea).ToEqual(90.f); + float angleb = p::v2{0.f, -1.f}.Angle(); + Expect(angleb).ToEqual(-90.f); + float anglec = p::v2{1.f, 0.f}.Angle(); + Expect(anglec).ToEqual(0.f); + float angled = p::v2{-1.f, 0.f}.Angle(); + Expect(angled).ToEqual(180.f); + }); - It("Can convert from angle", []() - { - Expect(p::v2::FromAngle(0.f).Angle()).ToEqual(0); - Expect(p::v2::FromAngle(90.f).Angle()).ToEqual(90.f); - }); + It("Can convert from angle", []() + { + Expect(p::v2::FromAngle(0.f).Angle()).ToEqual(0); + Expect(p::v2::FromAngle(90.f).Angle()).ToEqual(90.f); }); }); -return true; -}(); -} // namespace +}); diff --git a/Tests/Memory/BestFitArena.spec.cpp b/Tests/Memory/BestFitArena.spec.cpp index dd402df3..3d9752cd 100644 --- a/Tests/Memory/BestFitArena.spec.cpp +++ b/Tests/Memory/BestFitArena.spec.cpp @@ -14,288 +14,280 @@ struct TypeOfSize }; -namespace -{ -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() -{ Spec("Memory.BestFitArena", []() { - It("Reserves a block on construction", []() - { - BestFitArena arena{1024}; - arena.GetStats()->detectLeaks = false; - Expect(arena.GetFreeSize()).ToEqual(1024); - Expect(*arena.GetBlock()).ToNotEqual(nullptr); - Expect(arena.GetBlock().size).ToEqual(1024); - }); - - It("Can allocate", []() - { - BestFitArena arena{1024}; - arena.GetStats()->detectLeaks = false; - - void* p = arena.Alloc(4); - new (p) TypeOfSize<4>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.Contains(p)).ToBeTrue(); - }); - - It("Allocates at correct addresses", []() - { - BestFitArena arena{1024}; - arena.GetStats()->detectLeaks = false; - - const auto* blockPtr = static_cast(*arena.GetBlock()); - - void* p = arena.Alloc(4); - new (p) TypeOfSize<4>(); - Expect(p).ToEqual(blockPtr); - - void* p2 = arena.Alloc(4); - new (p2) TypeOfSize<4>(); - Expect(p2).ToEqual(blockPtr + 4); - }); - - It("Detects there is not enough space", []() - { - BestFitArena arena{32}; - arena.GetStats()->detectLeaks = false; - - // 16 bytes - void* p = arena.Alloc(20); - new (p) TypeOfSize<20>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.Contains(p)).ToBeTrue(); - - // Another 16 bytes - void* p2 = arena.Alloc(6); - new (p2) TypeOfSize<6>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.Contains(p2)).ToBeTrue(); - - // No more space, return null - void* p3 = arena.Alloc(8); // 8 bytes - Expect(p3).ToEqual(nullptr); - }); - - It("Allocates with alignment", []() - { - BestFitArena arena{1024}; - arena.GetStats()->detectLeaks = false; - - void* b = arena.Alloc(1); - new (b) TypeOfSize<1>(); - - // When padding is not 0 (last ptr is not aligned) - void* p = arena.Alloc(4, 8); - new (p) TypeOfSize<4>(); - Expect(GetAlignmentPadding(p, 8)).ToEqual(0); - - // When padding is 0 (last ptr is aligned) - void* p2 = arena.Alloc(4, 16); - new (p2) TypeOfSize<4>(); - Expect(GetAlignmentPadding(p2, 16)).ToEqual(0); - - // When padding is 0 (last ptr is aligned) - void* p3 = arena.Alloc(8, 32); - new (p3) TypeOfSize<8>(); - Expect(GetAlignmentPadding(p3, 32)).ToEqual(0); - }); - - It("Can free", []() - { - BestFitArena arena{64}; - arena.GetStats()->detectLeaks = false; +It("Reserves a block on construction", []() +{ + BestFitArena arena{1024}; + arena.GetStats()->detectLeaks = false; + Expect(arena.GetFreeSize()).ToEqual(1024); + Expect(*arena.GetBlock()).ToNotEqual(nullptr); + Expect(arena.GetBlock().size).ToEqual(1024); +}); - void* p = arena.Alloc(32); - new (p) TypeOfSize<32>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(32); +It("Can allocate", []() +{ + BestFitArena arena{1024}; + arena.GetStats()->detectLeaks = false; - arena.Free(p, 32); - Expect(arena.GetFreeSize()).ToEqual(64); - }); + void* p = arena.Alloc(4); + new (p) TypeOfSize<4>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.Contains(p)).ToBeTrue(); +}); - It("Can free multiple", []() - { - BestFitArena arena{64}; - arena.GetStats()->detectLeaks = false; +It("Allocates at correct addresses", []() +{ + BestFitArena arena{1024}; + arena.GetStats()->detectLeaks = false; - void* p = arena.Alloc(16); - new (p) TypeOfSize<16>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(48); + const auto* blockPtr = static_cast(*arena.GetBlock()); - void* p2 = arena.Alloc(16); - new (p2) TypeOfSize<16>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(32); + void* p = arena.Alloc(4); + new (p) TypeOfSize<4>(); + Expect(p).ToEqual(blockPtr); - arena.Free(p2, 16); - Expect(arena.GetFreeSize()).ToEqual(48); + void* p2 = arena.Alloc(4); + new (p2) TypeOfSize<4>(); + Expect(p2).ToEqual(blockPtr + 4); +}); - arena.Free(p, 16); - Expect(arena.GetFreeSize()).ToEqual(64); - }); +It("Detects there is not enough space", []() +{ + BestFitArena arena{32}; + arena.GetStats()->detectLeaks = false; + + // 16 bytes + void* p = arena.Alloc(20); + new (p) TypeOfSize<20>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.Contains(p)).ToBeTrue(); + + // Another 16 bytes + void* p2 = arena.Alloc(6); + new (p2) TypeOfSize<6>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.Contains(p2)).ToBeTrue(); + + // No more space, return null + void* p3 = arena.Alloc(8); // 8 bytes + Expect(p3).ToEqual(nullptr); +}); - It("Can free in between allocations", []() - { - BestFitArena arena{64}; - arena.GetStats()->detectLeaks = false; - - void* p = arena.Alloc(32); - new (p) TypeOfSize<32>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(32); - - void* p2 = arena.Alloc(30); - new (p2) TypeOfSize<30>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(2); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - - void* p3 = arena.Alloc(2); - new (p3) TypeOfSize<2>(); - Expect(p3).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(0); - // No space left, no free slots - Expect(arena.GetFreeSlots().Size()).ToEqual(0); - - arena.Free(p2, 30); - Expect(arena.GetFreeSize()).ToEqual(30); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - Expect(arena.GetFreeSlots()[0].start).ToEqual(p2); - Expect(arena.GetFreeSlots()[0].End()).ToEqual(p3); - }); - - It("Can merge previous and next slots on free", []() - { - BestFitArena arena{64}; - arena.GetStats()->detectLeaks = false; +It("Allocates with alignment", []() +{ + BestFitArena arena{1024}; + arena.GetStats()->detectLeaks = false; + + void* b = arena.Alloc(1); + new (b) TypeOfSize<1>(); + + // When padding is not 0 (last ptr is not aligned) + void* p = arena.Alloc(4, 8); + new (p) TypeOfSize<4>(); + Expect(GetAlignmentPadding(p, 8)).ToEqual(0); + + // When padding is 0 (last ptr is aligned) + void* p2 = arena.Alloc(4, 16); + new (p2) TypeOfSize<4>(); + Expect(GetAlignmentPadding(p2, 16)).ToEqual(0); + + // When padding is 0 (last ptr is aligned) + void* p3 = arena.Alloc(8, 32); + new (p3) TypeOfSize<8>(); + Expect(GetAlignmentPadding(p3, 32)).ToEqual(0); +}); - void* p = arena.Alloc(9); - new (p) TypeOfSize<9>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(55); +It("Can free", []() +{ + BestFitArena arena{64}; + arena.GetStats()->detectLeaks = false; - void* p2 = arena.Alloc(50); - new (p2) TypeOfSize<50>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(5); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); + void* p = arena.Alloc(32); + new (p) TypeOfSize<32>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(32); - void* p3 = arena.Alloc(5); - new (p3) TypeOfSize<5>(); - Expect(p3).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(0); + arena.Free(p, 32); + Expect(arena.GetFreeSize()).ToEqual(64); +}); - // No space left, no free slots - Expect(arena.GetFreeSlots().Size()).ToEqual(0); +It("Can free multiple", []() +{ + BestFitArena arena{64}; + arena.GetStats()->detectLeaks = false; - arena.Free(p, 9); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); + void* p = arena.Alloc(16); + new (p) TypeOfSize<16>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(48); - arena.Free(p3, 5); - Expect(arena.GetFreeSlots().Size()).ToEqual(2); + void* p2 = arena.Alloc(16); + new (p2) TypeOfSize<16>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(32); - arena.Free(p2, 50); // Slots previous and next are merged - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - Expect(arena.GetFreeSlots()[0].size).ToEqual(64); + arena.Free(p2, 16); + Expect(arena.GetFreeSize()).ToEqual(48); - // Slot contains the entire memory block - Expect(arena.GetFreeSlots()[0].start).ToEqual(arena.GetBlock().data); - Expect(arena.GetFreeSlots()[0].End()).ToEqual(arena.GetBlock().End()); - }); + arena.Free(p, 16); + Expect(arena.GetFreeSize()).ToEqual(64); +}); - It("Can merge previous slot on free", []() - { - BestFitArena arena{48}; - arena.GetStats()->detectLeaks = false; +It("Can free in between allocations", []() +{ + BestFitArena arena{64}; + arena.GetStats()->detectLeaks = false; + + void* p = arena.Alloc(32); + new (p) TypeOfSize<32>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(32); + + void* p2 = arena.Alloc(30); + new (p2) TypeOfSize<30>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(2); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + void* p3 = arena.Alloc(2); + new (p3) TypeOfSize<2>(); + Expect(p3).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(0); + // No space left, no free slots + Expect(arena.GetFreeSlots().Size()).ToEqual(0); + + arena.Free(p2, 30); + Expect(arena.GetFreeSize()).ToEqual(30); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + Expect(arena.GetFreeSlots()[0].start).ToEqual(p2); + Expect(arena.GetFreeSlots()[0].End()).ToEqual(p3); +}); - void* p = arena.Alloc(39); - new (p) TypeOfSize<39>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(9); +It("Can merge previous and next slots on free", []() +{ + BestFitArena arena{64}; + arena.GetStats()->detectLeaks = false; - void* p2 = arena.Alloc(9); - new (p2) TypeOfSize<9>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(0); - Expect(arena.GetFreeSlots().Size()).ToEqual(0); + void* p = arena.Alloc(9); + new (p) TypeOfSize<9>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(55); - arena.Free(p, 39); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); + void* p2 = arena.Alloc(50); + new (p2) TypeOfSize<50>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(5); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); - arena.Free(p2, 9); // Slot is expanded from the front - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - Expect(arena.GetFreeSlots()[0].size).ToEqual(48); + void* p3 = arena.Alloc(5); + new (p3) TypeOfSize<5>(); + Expect(p3).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(0); - // Slot contains the entire memory block - Expect(arena.GetFreeSlots()[0].start).ToEqual(arena.GetBlock().data); - Expect(arena.GetFreeSlots()[0].End()).ToEqual(arena.GetBlock().End()); - }); + // No space left, no free slots + Expect(arena.GetFreeSlots().Size()).ToEqual(0); - It("Can merge next slot on free", []() - { - BestFitArena arena{48}; - arena.GetStats()->detectLeaks = false; + arena.Free(p, 9); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); - void* p = arena.Alloc(24); - new (p) TypeOfSize<24>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(24); + arena.Free(p3, 5); + Expect(arena.GetFreeSlots().Size()).ToEqual(2); - void* p2 = arena.Alloc(24); - new (p2) TypeOfSize<24>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(0); - Expect(arena.GetFreeSlots().Size()).ToEqual(0); + arena.Free(p2, 50); // Slots previous and next are merged + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + Expect(arena.GetFreeSlots()[0].size).ToEqual(64); - arena.Free(p2, 24); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); + // Slot contains the entire memory block + Expect(arena.GetFreeSlots()[0].start).ToEqual(arena.GetBlock().data); + Expect(arena.GetFreeSlots()[0].End()).ToEqual(arena.GetBlock().End()); +}); - arena.Free(p, 24); // Slot is expanded from the back - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - Expect(arena.GetFreeSlots()[0].size).ToEqual(48); +It("Can merge previous slot on free", []() +{ + BestFitArena arena{48}; + arena.GetStats()->detectLeaks = false; + + void* p = arena.Alloc(39); + new (p) TypeOfSize<39>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(9); + + void* p2 = arena.Alloc(9); + new (p2) TypeOfSize<9>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(0); + Expect(arena.GetFreeSlots().Size()).ToEqual(0); + + arena.Free(p, 39); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + arena.Free(p2, 9); // Slot is expanded from the front + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + Expect(arena.GetFreeSlots()[0].size).ToEqual(48); + + // Slot contains the entire memory block + Expect(arena.GetFreeSlots()[0].start).ToEqual(arena.GetBlock().data); + Expect(arena.GetFreeSlots()[0].End()).ToEqual(arena.GetBlock().End()); +}); - // Slot contains the entire memory block - Expect(arena.GetFreeSlots()[0].start).ToEqual(arena.GetBlock().data); - Expect(arena.GetFreeSlots()[0].End()).ToEqual(arena.GetBlock().End()); - }); +It("Can merge next slot on free", []() +{ + BestFitArena arena{48}; + arena.GetStats()->detectLeaks = false; + + void* p = arena.Alloc(24); + new (p) TypeOfSize<24>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(24); + + void* p2 = arena.Alloc(24); + new (p2) TypeOfSize<24>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(0); + Expect(arena.GetFreeSlots().Size()).ToEqual(0); + + arena.Free(p2, 24); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + arena.Free(p, 24); // Slot is expanded from the back + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + Expect(arena.GetFreeSlots()[0].size).ToEqual(48); + + // Slot contains the entire memory block + Expect(arena.GetFreeSlots()[0].start).ToEqual(arena.GetBlock().data); + Expect(arena.GetFreeSlots()[0].End()).ToEqual(arena.GetBlock().End()); +}); - It("Ensures a big alignment leaves a gap", []() +It("Ensures a big alignment leaves a gap", []() +{ + BestFitArena arena{128}; + arena.GetStats()->detectLeaks = false; + + // We ensure first allocation aligns the block (just for the test) + void* p = arena.Alloc(8); + new (p) TypeOfSize<8>(); + Expect(arena.GetFreeSize()).ToEqual(120); + + void* p2 = arena.Alloc(8, 64); + new (p2) TypeOfSize<8>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(112); + + // Alignment is absolute, so the gap between p and p2 is zero + // when the block base lands on a matching 64B boundary. + const bool hasGap = p2 > (u8*)p + 8; + Expect(arena.GetFreeSlots().Size()).ToEqual(hasGap ? 2 : 1); + + // Slot contains the rest if the block + Expect(arena.GetFreeSlots()[0].start).ToEqual((u8*)p2 + 8); + Expect(arena.GetFreeSlots()[0].End()).ToEqual(arena.GetBlock().End()); + + // Slot contains the alignment gap + if (hasGap) { - BestFitArena arena{128}; - arena.GetStats()->detectLeaks = false; - - // We ensure first allocation aligns the block (just for the test) - void* p = arena.Alloc(8); - new (p) TypeOfSize<8>(); - Expect(arena.GetFreeSize()).ToEqual(120); - - void* p2 = arena.Alloc(8, 64); - new (p2) TypeOfSize<8>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(112); - - // Alignment is absolute, so the gap between p and p2 is zero - // when the block base lands on a matching 64B boundary. - const bool hasGap = p2 > (u8*)p + 8; - Expect(arena.GetFreeSlots().Size()).ToEqual(hasGap ? 2 : 1); - - // Slot contains the rest if the block - Expect(arena.GetFreeSlots()[0].start).ToEqual((u8*)p2 + 8); - Expect(arena.GetFreeSlots()[0].End()).ToEqual(arena.GetBlock().End()); - - // Slot contains the alignment gap - if (hasGap) - { - Expect(arena.GetFreeSlots()[1].start).ToEqual((u8*)p + 8); - Expect(arena.GetFreeSlots()[1].End()).ToEqual(p2); - } - }); + Expect(arena.GetFreeSlots()[1].start).ToEqual((u8*)p + 8); + Expect(arena.GetFreeSlots()[1].End()).ToEqual(p2); + } +}); }); -return true; -}(); -} // namespace diff --git a/Tests/Memory/BigBestFitArena.spec.cpp b/Tests/Memory/BigBestFitArena.spec.cpp index 3b5180ec..8d25a921 100644 --- a/Tests/Memory/BigBestFitArena.spec.cpp +++ b/Tests/Memory/BigBestFitArena.spec.cpp @@ -13,301 +13,293 @@ struct TypeOfSize p::u8 data[size]{0}; // Fill data for debugging }; -namespace -{ -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() -{ Spec("Memory.BigBestFitArena", []() { - It("Reserves a block on construction", []() - { - BigBestFitArena arena{1024}; - arena.GetStats()->detectLeaks = false; - Expect(arena.GetFreeSize()).ToEqual(1024); - Expect(*arena.GetBlock()).ToNotEqual(nullptr); - Expect(arena.GetBlock().size).ToEqual(1024); - }); - - It("Can allocate", []() - { - BigBestFitArena arena{1024}; - arena.GetStats()->detectLeaks = false; - - void* p = arena.Alloc(4); - new (p) TypeOfSize<4>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.Contains(p)).ToBeTrue(); - }); - - It("Allocates at correct addresses", []() - { - BigBestFitArena arena{1024}; - arena.GetStats()->detectLeaks = false; - - const auto* blockPtr = static_cast(*arena.GetBlock()); - - void* p = arena.Alloc(4); - new (p) TypeOfSize<4>(); - const void* expectedP = blockPtr + p::GetAlignmentPaddingWithHeader(blockPtr, 8, 8); - Expect(p).ToEqual(expectedP); - - void* p2 = arena.Alloc(4); - new (p2) TypeOfSize<4>(); - void* expectedP2 = - static_cast(p) + 8 + p::GetAlignmentPaddingWithHeader(p, 8, 8); - Expect(p2).ToEqual(expectedP2); - }); - - It("Detects there is not enough space", []() - { - BigBestFitArena arena{32}; - arena.GetStats()->detectLeaks = false; - - // 16 bytes - void* p = arena.Alloc(8); - new (p) TypeOfSize<8>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.Contains(p)).ToBeTrue(); - - // Another 16 bytes - void* p2 = arena.Alloc(4); - new (p2) TypeOfSize<4>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.Contains(p2)).ToBeTrue(); - - // No more space, return null - void* p3 = arena.Alloc(8); // 8 bytes - Expect(p3).ToEqual(nullptr); - }); - - It("Allocates with alignment", []() - { - BigBestFitArena arena{1024}; - arena.GetStats()->detectLeaks = false; - - void* b = arena.Alloc(1); - new (b) TypeOfSize<1>(); +It("Reserves a block on construction", []() +{ + BigBestFitArena arena{1024}; + arena.GetStats()->detectLeaks = false; + Expect(arena.GetFreeSize()).ToEqual(1024); + Expect(*arena.GetBlock()).ToNotEqual(nullptr); + Expect(arena.GetBlock().size).ToEqual(1024); +}); - // When padding is not 0 (last ptr is not aligned) - void* p = arena.Alloc(4, 8); - new (p) TypeOfSize<4>(); - Expect(p::GetAlignmentPadding(p, 8)).ToEqual(0); +It("Can allocate", []() +{ + BigBestFitArena arena{1024}; + arena.GetStats()->detectLeaks = false; - // When padding is 0 (last ptr is aligned) - void* p2 = arena.Alloc(4, 16); - new (p2) TypeOfSize<4>(); - Expect(p::GetAlignmentPadding(p2, 16)).ToEqual(0); + void* p = arena.Alloc(4); + new (p) TypeOfSize<4>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.Contains(p)).ToBeTrue(); +}); - // When padding is 0 (last ptr is aligned) - void* p3 = arena.Alloc(8, 32); - new (p3) TypeOfSize<8>(); - Expect(p::GetAlignmentPadding(p3, 32)).ToEqual(0); - }); +It("Allocates at correct addresses", []() +{ + BigBestFitArena arena{1024}; + arena.GetStats()->detectLeaks = false; - It("Can free", []() - { - BigBestFitArena arena{64}; - arena.GetStats()->detectLeaks = false; + const auto* blockPtr = static_cast(*arena.GetBlock()); - void* p = arena.Alloc(32); - new (p) TypeOfSize<32>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(24); + void* p = arena.Alloc(4); + new (p) TypeOfSize<4>(); + const void* expectedP = blockPtr + p::GetAlignmentPaddingWithHeader(blockPtr, 8, 8); + Expect(p).ToEqual(expectedP); - arena.Free(p, 32); - Expect(arena.GetFreeSize()).ToEqual(64); - }); + void* p2 = arena.Alloc(4); + new (p2) TypeOfSize<4>(); + void* expectedP2 = + static_cast(p) + 8 + p::GetAlignmentPaddingWithHeader(p, 8, 8); + Expect(p2).ToEqual(expectedP2); +}); - It("Can free multiple", []() - { - BigBestFitArena arena{64}; - arena.GetStats()->detectLeaks = false; +It("Detects there is not enough space", []() +{ + BigBestFitArena arena{32}; + arena.GetStats()->detectLeaks = false; + + // 16 bytes + void* p = arena.Alloc(8); + new (p) TypeOfSize<8>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.Contains(p)).ToBeTrue(); + + // Another 16 bytes + void* p2 = arena.Alloc(4); + new (p2) TypeOfSize<4>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.Contains(p2)).ToBeTrue(); + + // No more space, return null + void* p3 = arena.Alloc(8); // 8 bytes + Expect(p3).ToEqual(nullptr); +}); - void* p = arena.Alloc(16); - new (p) TypeOfSize<16>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(40); +It("Allocates with alignment", []() +{ + BigBestFitArena arena{1024}; + arena.GetStats()->detectLeaks = false; + + void* b = arena.Alloc(1); + new (b) TypeOfSize<1>(); + + // When padding is not 0 (last ptr is not aligned) + void* p = arena.Alloc(4, 8); + new (p) TypeOfSize<4>(); + Expect(p::GetAlignmentPadding(p, 8)).ToEqual(0); + + // When padding is 0 (last ptr is aligned) + void* p2 = arena.Alloc(4, 16); + new (p2) TypeOfSize<4>(); + Expect(p::GetAlignmentPadding(p2, 16)).ToEqual(0); + + // When padding is 0 (last ptr is aligned) + void* p3 = arena.Alloc(8, 32); + new (p3) TypeOfSize<8>(); + Expect(p::GetAlignmentPadding(p3, 32)).ToEqual(0); +}); - void* p2 = arena.Alloc(16); - new (p2) TypeOfSize<16>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(16); +It("Can free", []() +{ + BigBestFitArena arena{64}; + arena.GetStats()->detectLeaks = false; - arena.Free(p2, 16); - Expect(arena.GetFreeSize()).ToEqual(40); + void* p = arena.Alloc(32); + new (p) TypeOfSize<32>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(24); - arena.Free(p, 16); - Expect(arena.GetFreeSize()).ToEqual(64); - }); + arena.Free(p, 32); + Expect(arena.GetFreeSize()).ToEqual(64); +}); - It("Can free in between allocations", []() - { - BigBestFitArena arena{64}; - arena.GetStats()->detectLeaks = false; - - void* p = arena.Alloc(16); - new (p) TypeOfSize<16>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(40); - - void* p2 = arena.Alloc(16); - new (p2) TypeOfSize<16>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(16); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - - void* p3 = arena.Alloc(8); - new (p3) TypeOfSize<8>(); - Expect(p3).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(0); - // No space left, no free slots - Expect(arena.GetFreeSlots().Size()).ToEqual(0); - - arena.Free(p2, 16); - Expect(arena.GetFreeSize()).ToEqual(24); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - - auto slot = arena.GetFreeSlots()[0]; - u8* slotStart = (u8*)arena.GetBlock().data + slot.offset; - Expect(slotStart).ToEqual(static_cast(p2) - 8); - Expect(slotStart + slot.size).ToEqual(static_cast(p3) - 8); - }); - - It("Can merge previous and next slots on free", []() - { - BigBestFitArena arena{64}; - arena.GetStats()->detectLeaks = false; +It("Can free multiple", []() +{ + BigBestFitArena arena{64}; + arena.GetStats()->detectLeaks = false; - void* p = arena.Alloc(16); - new (p) TypeOfSize<16>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(40); + void* p = arena.Alloc(16); + new (p) TypeOfSize<16>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(40); - void* p2 = arena.Alloc(16); - new (p2) TypeOfSize<16>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(16); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); + void* p2 = arena.Alloc(16); + new (p2) TypeOfSize<16>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(16); - void* p3 = arena.Alloc(8); - new (p3) TypeOfSize<8>(); - Expect(p3).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(0); + arena.Free(p2, 16); + Expect(arena.GetFreeSize()).ToEqual(40); - // No space left, no free slots - Expect(arena.GetFreeSlots().Size()).ToEqual(0); + arena.Free(p, 16); + Expect(arena.GetFreeSize()).ToEqual(64); +}); - arena.Free(p, 16); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); +It("Can free in between allocations", []() +{ + BigBestFitArena arena{64}; + arena.GetStats()->detectLeaks = false; + + void* p = arena.Alloc(16); + new (p) TypeOfSize<16>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(40); + + void* p2 = arena.Alloc(16); + new (p2) TypeOfSize<16>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(16); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + void* p3 = arena.Alloc(8); + new (p3) TypeOfSize<8>(); + Expect(p3).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(0); + // No space left, no free slots + Expect(arena.GetFreeSlots().Size()).ToEqual(0); + + arena.Free(p2, 16); + Expect(arena.GetFreeSize()).ToEqual(24); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + auto slot = arena.GetFreeSlots()[0]; + u8* slotStart = (u8*)arena.GetBlock().data + slot.offset; + Expect(slotStart).ToEqual(static_cast(p2) - 8); + Expect(slotStart + slot.size).ToEqual(static_cast(p3) - 8); +}); - arena.Free(p3, 8); - Expect(arena.GetFreeSlots().Size()).ToEqual(2); +It("Can merge previous and next slots on free", []() +{ + BigBestFitArena arena{64}; + arena.GetStats()->detectLeaks = false; + + void* p = arena.Alloc(16); + new (p) TypeOfSize<16>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(40); + + void* p2 = arena.Alloc(16); + new (p2) TypeOfSize<16>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(16); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + void* p3 = arena.Alloc(8); + new (p3) TypeOfSize<8>(); + Expect(p3).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(0); + + // No space left, no free slots + Expect(arena.GetFreeSlots().Size()).ToEqual(0); + + arena.Free(p, 16); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + arena.Free(p3, 8); + Expect(arena.GetFreeSlots().Size()).ToEqual(2); + + arena.Free(p2, 16); // Slots previous and next are merged + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + // Slot contains the entire memory block + auto slot = arena.GetFreeSlots()[0]; + u8* slotStart = (u8*)arena.GetBlock().data + slot.offset; + Expect(slotStart).ToEqual(static_cast(arena.GetBlock().data)); + Expect(slotStart + slot.size).ToEqual(static_cast(arena.GetBlock().End())); +}); - arena.Free(p2, 16); // Slots previous and next are merged - Expect(arena.GetFreeSlots().Size()).ToEqual(1); +It("Can merge previous slot on free", []() +{ + BigBestFitArena arena{48}; + arena.GetStats()->detectLeaks = false; + + void* p = arena.Alloc(16); + new (p) TypeOfSize<16>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(24); + + void* p2 = arena.Alloc(16); + new (p2) TypeOfSize<16>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(0); + Expect(arena.GetFreeSlots().Size()).ToEqual(0); + + arena.Free(p, 16); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + arena.Free(p2, 16); // Slot is expanded from the front + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + // Slot contains the entire memory block + auto slot = arena.GetFreeSlots()[0]; + u8* slotStart = (u8*)arena.GetBlock().data + slot.offset; + Expect(slotStart).ToEqual(static_cast(arena.GetBlock().data)); + Expect(slotStart + slot.size).ToEqual(static_cast(arena.GetBlock().End())); +}); - // Slot contains the entire memory block - auto slot = arena.GetFreeSlots()[0]; - u8* slotStart = (u8*)arena.GetBlock().data + slot.offset; - Expect(slotStart).ToEqual(static_cast(arena.GetBlock().data)); - Expect(slotStart + slot.size).ToEqual(static_cast(arena.GetBlock().End())); - }); +It("Can merge next slot on free", []() +{ + BigBestFitArena arena{48}; + arena.GetStats()->detectLeaks = false; + + void* p = arena.Alloc(16); + new (p) TypeOfSize<16>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(24); + + void* p2 = arena.Alloc(16); + new (p2) TypeOfSize<16>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(0); + Expect(arena.GetFreeSlots().Size()).ToEqual(0); + + arena.Free(p2, 16); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + arena.Free(p, 16); // Slot is expanded from the back + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + // Slot contains the entire memory block + auto slot = arena.GetFreeSlots()[0]; + u8* slotStart = (u8*)arena.GetBlock().data + slot.offset; + Expect(slotStart).ToEqual(static_cast(arena.GetBlock().data)); + Expect(slotStart + slot.size).ToEqual(static_cast(arena.GetBlock().End())); +}); - It("Can merge previous slot on free", []() - { - BigBestFitArena arena{48}; - arena.GetStats()->detectLeaks = false; - - void* p = arena.Alloc(16); - new (p) TypeOfSize<16>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(24); - - void* p2 = arena.Alloc(16); - new (p2) TypeOfSize<16>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(0); - Expect(arena.GetFreeSlots().Size()).ToEqual(0); - - arena.Free(p, 16); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - - arena.Free(p2, 16); // Slot is expanded from the front - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - - // Slot contains the entire memory block - auto slot = arena.GetFreeSlots()[0]; - u8* slotStart = (u8*)arena.GetBlock().data + slot.offset; - Expect(slotStart).ToEqual(static_cast(arena.GetBlock().data)); - Expect(slotStart + slot.size).ToEqual(static_cast(arena.GetBlock().End())); - }); - - It("Can merge next slot on free", []() - { - BigBestFitArena arena{48}; - arena.GetStats()->detectLeaks = false; - - void* p = arena.Alloc(16); - new (p) TypeOfSize<16>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(24); - - void* p2 = arena.Alloc(16); - new (p2) TypeOfSize<16>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(0); - Expect(arena.GetFreeSlots().Size()).ToEqual(0); - - arena.Free(p2, 16); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - - arena.Free(p, 16); // Slot is expanded from the back - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - - // Slot contains the entire memory block - auto slot = arena.GetFreeSlots()[0]; - u8* slotStart = (u8*)arena.GetBlock().data + slot.offset; - Expect(slotStart).ToEqual(static_cast(arena.GetBlock().data)); - Expect(slotStart + slot.size).ToEqual(static_cast(arena.GetBlock().End())); - }); - - It("Ensures a big alignment leaves a gap", []() +It("Ensures a big alignment leaves a gap", []() +{ + BigBestFitArena arena{128}; + arena.GetStats()->detectLeaks = false; + + // We ensure first allocation aligns the block (just for the test) + void* p = arena.Alloc(8); + new (p) TypeOfSize<8>(); + Expect(arena.GetFreeSize()).ToEqual(112); + + void* p2 = arena.Alloc(8, 64); + new (p2) TypeOfSize<8>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(96); + + // Alignment is absolute, so the gap between p and p2 is zero + // when the block base lands on a matching 64B boundary. + const bool hasGap = arena.GetAllocationStart(p2) > arena.GetAllocationEnd(p); + Expect(arena.GetFreeSlots().Size()).ToEqual(hasGap ? 2 : 1); + + // Slot contains the rest if the block + auto slot0 = arena.GetFreeSlots()[0]; + u8* slot0Start = (u8*)arena.GetBlock().data + slot0.offset; + Expect(slot0Start).ToEqual(arena.GetAllocationEnd(p2)); + Expect(slot0Start + slot0.size).ToEqual(static_cast(arena.GetBlock().End())); + + // Slot contains the alignment gap + if (hasGap) { - BigBestFitArena arena{128}; - arena.GetStats()->detectLeaks = false; - - // We ensure first allocation aligns the block (just for the test) - void* p = arena.Alloc(8); - new (p) TypeOfSize<8>(); - Expect(arena.GetFreeSize()).ToEqual(112); - - void* p2 = arena.Alloc(8, 64); - new (p2) TypeOfSize<8>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(96); - - // Alignment is absolute, so the gap between p and p2 is zero - // when the block base lands on a matching 64B boundary. - const bool hasGap = arena.GetAllocationStart(p2) > arena.GetAllocationEnd(p); - Expect(arena.GetFreeSlots().Size()).ToEqual(hasGap ? 2 : 1); - - // Slot contains the rest if the block - auto slot0 = arena.GetFreeSlots()[0]; - u8* slot0Start = (u8*)arena.GetBlock().data + slot0.offset; - Expect(slot0Start).ToEqual(arena.GetAllocationEnd(p2)); - Expect(slot0Start + slot0.size).ToEqual(static_cast(arena.GetBlock().End())); - - // Slot contains the alignment gap - if (hasGap) - { - auto slot1 = arena.GetFreeSlots()[1]; - u8* slot1Start = (u8*)arena.GetBlock().data + slot1.offset; - Expect(slot1Start).ToEqual(arena.GetAllocationEnd(p)); - Expect(slot1Start + slot1.size).ToEqual(arena.GetAllocationStart(p2)); - } - }); + auto slot1 = arena.GetFreeSlots()[1]; + u8* slot1Start = (u8*)arena.GetBlock().data + slot1.offset; + Expect(slot1Start).ToEqual(arena.GetAllocationEnd(p)); + Expect(slot1Start + slot1.size).ToEqual(arena.GetAllocationStart(p2)); + } +}); }); -return true; -}(); -} // namespace diff --git a/Tests/Memory/Memory.spec.cpp b/Tests/Memory/Memory.spec.cpp index bdef3340..f65549fe 100644 --- a/Tests/Memory/Memory.spec.cpp +++ b/Tests/Memory/Memory.spec.cpp @@ -53,201 +53,193 @@ struct MoveType }; -namespace +Spec("Memory.Operations", []() { -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() +It("Can default construct", []() { -Spec("Memory.Operations", []() + // Check that it inits to 0 + bool boolValues[2]{true, true}; // Assign simulated garbage + ConstructItems(boolValues, 2); + Expect(boolValues[0]).ToEqual(false); + Expect(boolValues[1]).ToEqual(false); + + u8 u8Values[2]{34, 45}; // Assign simulated garbage + ConstructItems(u8Values, 2, u8(128)); + Expect(u8Values[0]).ToEqual(128); + Expect(u8Values[1]).ToEqual(128); + + u32 u32Values[2]{34, 45}; // Assign simulated garbage + ConstructItems(u32Values, 2); + Expect(u32Values[0]).ToEqual(0); + Expect(u32Values[1]).ToEqual(0); + + u32* ptrValues[2]{(u32*)1, (u32*)2}; // Assign simulated garbage + ConstructItems(ptrValues, 2); + Expect(ptrValues[0]).ToEqual(nullptr); + Expect(ptrValues[1]).ToEqual(nullptr); + + ConstructedType constructedValues[2]; + constructedValues[0].value = 0.234f; // Assign simulated garbage + constructedValues[1].value = 0.234f; + ConstructItems(constructedValues, 2); + Expect(constructedValues[0].value).ToEqual(0.f); + Expect(constructedValues[1].value).ToEqual(0.f); + + BoolsType boolsValues[2]; + boolsValues[0].value1 = false; // Assign simulated garbage + boolsValues[0].value2 = true; + boolsValues[1].value1 = false; + boolsValues[1].value2 = false; + ConstructItems(boolsValues, 2); + Expect(boolsValues[0].value1).ToEqual(true); + Expect(boolsValues[0].value2).ToEqual(false); + Expect(boolsValues[1].value1).ToEqual(true); + Expect(boolsValues[1].value2).ToEqual(false); +}); + +It("Can value construct", []() { - It("Can default construct", []() - { - // Check that it inits to 0 - bool boolValues[2]{true, true}; // Assign simulated garbage - ConstructItems(boolValues, 2); - Expect(boolValues[0]).ToEqual(false); - Expect(boolValues[1]).ToEqual(false); - - u8 u8Values[2]{34, 45}; // Assign simulated garbage - ConstructItems(u8Values, 2, u8(128)); - Expect(u8Values[0]).ToEqual(128); - Expect(u8Values[1]).ToEqual(128); - - u32 u32Values[2]{34, 45}; // Assign simulated garbage - ConstructItems(u32Values, 2); - Expect(u32Values[0]).ToEqual(0); - Expect(u32Values[1]).ToEqual(0); - - u32* ptrValues[2]{(u32*)1, (u32*)2}; // Assign simulated garbage - ConstructItems(ptrValues, 2); - Expect(ptrValues[0]).ToEqual(nullptr); - Expect(ptrValues[1]).ToEqual(nullptr); - - ConstructedType constructedValues[2]; - constructedValues[0].value = 0.234f; // Assign simulated garbage - constructedValues[1].value = 0.234f; - ConstructItems(constructedValues, 2); - Expect(constructedValues[0].value).ToEqual(0.f); - Expect(constructedValues[1].value).ToEqual(0.f); - - BoolsType boolsValues[2]; - boolsValues[0].value1 = false; // Assign simulated garbage - boolsValues[0].value2 = true; - boolsValues[1].value1 = false; - boolsValues[1].value2 = false; - ConstructItems(boolsValues, 2); - Expect(boolsValues[0].value1).ToEqual(true); - Expect(boolsValues[0].value2).ToEqual(false); - Expect(boolsValues[1].value1).ToEqual(true); - Expect(boolsValues[1].value2).ToEqual(false); - }); - - It("Can value construct", []() - { - bool boolValues[2]{false, false}; // Assign simulated garbage - ConstructItems(boolValues, 2, true); - Expect(boolValues[0]).ToEqual(true); - Expect(boolValues[1]).ToEqual(true); - - u8 u8Values[2]{34, 45}; // Assign simulated garbage - ConstructItems(u8Values, 2, u8(128)); - Expect(u8Values[0]).ToEqual(128); - Expect(u8Values[1]).ToEqual(128); - - u32 u32Values[2]{34, 45}; // Assign simulated garbage - ConstructItems(u32Values, 2, u32(128)); - Expect(u32Values[0]).ToEqual(128); - Expect(u32Values[1]).ToEqual(128); - - u32* ptrValues[2]{(u32*)1, (u32*)2}; // Assign simulated garbage - ConstructItems(ptrValues, 2, (u32*)32); - Expect(ptrValues[0]).ToEqual((u32*)32); - Expect(ptrValues[1]).ToEqual((u32*)32); - - ConstructedType constructedValues[2]{{0.234f}, {0.234f}}; // Assign simulated garbage - ConstructItems(constructedValues, 2, ConstructedType(1.f)); - Expect(constructedValues[0].value).ToEqual(1.f); - Expect(constructedValues[1].value).ToEqual(1.f); - - BoolsType boolsValues[2]{ - {.value1 = false, .value2 = true}, - {.value1 = false, .value2 = true} + bool boolValues[2]{false, false}; // Assign simulated garbage + ConstructItems(boolValues, 2, true); + Expect(boolValues[0]).ToEqual(true); + Expect(boolValues[1]).ToEqual(true); + + u8 u8Values[2]{34, 45}; // Assign simulated garbage + ConstructItems(u8Values, 2, u8(128)); + Expect(u8Values[0]).ToEqual(128); + Expect(u8Values[1]).ToEqual(128); + + u32 u32Values[2]{34, 45}; // Assign simulated garbage + ConstructItems(u32Values, 2, u32(128)); + Expect(u32Values[0]).ToEqual(128); + Expect(u32Values[1]).ToEqual(128); + + u32* ptrValues[2]{(u32*)1, (u32*)2}; // Assign simulated garbage + ConstructItems(ptrValues, 2, (u32*)32); + Expect(ptrValues[0]).ToEqual((u32*)32); + Expect(ptrValues[1]).ToEqual((u32*)32); + + ConstructedType constructedValues[2]{{0.234f}, {0.234f}}; // Assign simulated garbage + ConstructItems(constructedValues, 2, ConstructedType(1.f)); + Expect(constructedValues[0].value).ToEqual(1.f); + Expect(constructedValues[1].value).ToEqual(1.f); + + BoolsType boolsValues[2]{ + {.value1 = false, .value2 = true}, + {.value1 = false, .value2 = true} }; // Assign simulated garbage - ConstructItems(boolsValues, 2, BoolsType{.value1 = true, .value2 = true}); - Expect(boolsValues[0].value1).ToEqual(true); - Expect(boolsValues[0].value2).ToEqual(true); - Expect(boolsValues[1].value1).ToEqual(true); - Expect(boolsValues[1].value2).ToEqual(true); - }); - - It("Can copy construct", []() - { - bool boolValues[2]{false, false}; // Assign simulated garbage - bool srcBoolValues[2]{true, false}; - CopyConstructItems(boolValues, 2, srcBoolValues); - Expect(boolValues[0]).ToEqual(true); - Expect(boolValues[1]).ToEqual(false); - - u8 u8Values[2]{34, 45}; // Assign simulated garbage - u8 srcU8Values[2]{128, 129}; - CopyConstructItems(u8Values, 2, srcU8Values); - Expect(u8Values[0]).ToEqual(128); - Expect(u8Values[1]).ToEqual(129); - - u32 u32Values[2]{34, 45}; // Assign simulated garbage - u32 srcU32Values[2]{128, 129}; - CopyConstructItems(u32Values, 2, srcU32Values); - Expect(u32Values[0]).ToEqual(128); - Expect(u32Values[1]).ToEqual(129); - - u32* ptrValues[2]{(u32*)1, (u32*)2}; // Assign simulated garbage - u32* srcPtrValues[2]{(u32*)34, (u32*)23433}; - CopyConstructItems(ptrValues, 2, srcPtrValues); - Expect(ptrValues[0]).ToEqual((u32*)34); - Expect(ptrValues[1]).ToEqual((u32*)23433); - - ConstructedType constructedValues[2]{{0.234f}, {0.234f}}; // Assign simulated garbage - ConstructedType srcConstructedValues[2]{{1.f}, {2.f}}; - CopyConstructItems(constructedValues, 2, srcConstructedValues); - Expect(constructedValues[0].value).ToEqual(1.f); - Expect(constructedValues[1].value).ToEqual(2.f); - - BoolsType boolsValues[2]{ - {false, true}, + ConstructItems(boolsValues, 2, BoolsType{.value1 = true, .value2 = true}); + Expect(boolsValues[0].value1).ToEqual(true); + Expect(boolsValues[0].value2).ToEqual(true); + Expect(boolsValues[1].value1).ToEqual(true); + Expect(boolsValues[1].value2).ToEqual(true); +}); + +It("Can copy construct", []() +{ + bool boolValues[2]{false, false}; // Assign simulated garbage + bool srcBoolValues[2]{true, false}; + CopyConstructItems(boolValues, 2, srcBoolValues); + Expect(boolValues[0]).ToEqual(true); + Expect(boolValues[1]).ToEqual(false); + + u8 u8Values[2]{34, 45}; // Assign simulated garbage + u8 srcU8Values[2]{128, 129}; + CopyConstructItems(u8Values, 2, srcU8Values); + Expect(u8Values[0]).ToEqual(128); + Expect(u8Values[1]).ToEqual(129); + + u32 u32Values[2]{34, 45}; // Assign simulated garbage + u32 srcU32Values[2]{128, 129}; + CopyConstructItems(u32Values, 2, srcU32Values); + Expect(u32Values[0]).ToEqual(128); + Expect(u32Values[1]).ToEqual(129); + + u32* ptrValues[2]{(u32*)1, (u32*)2}; // Assign simulated garbage + u32* srcPtrValues[2]{(u32*)34, (u32*)23433}; + CopyConstructItems(ptrValues, 2, srcPtrValues); + Expect(ptrValues[0]).ToEqual((u32*)34); + Expect(ptrValues[1]).ToEqual((u32*)23433); + + ConstructedType constructedValues[2]{{0.234f}, {0.234f}}; // Assign simulated garbage + ConstructedType srcConstructedValues[2]{{1.f}, {2.f}}; + CopyConstructItems(constructedValues, 2, srcConstructedValues); + Expect(constructedValues[0].value).ToEqual(1.f); + Expect(constructedValues[1].value).ToEqual(2.f); + + BoolsType boolsValues[2]{ + {false, true}, {false, true} }; // Assign simulated garbage - BoolsType srcBoolsValues[2]{ - {true, false}, + BoolsType srcBoolsValues[2]{ + {true, false}, {false, true } }; - CopyConstructItems(boolsValues, 2, srcBoolsValues); - Expect(boolsValues[0].value1).ToEqual(true); - Expect(boolsValues[0].value2).ToEqual(false); - Expect(boolsValues[1].value1).ToEqual(false); - Expect(boolsValues[1].value2).ToEqual(true); - - CopyType copyValues[2]{5, 6}; // Assign simulated garbage - CopyType srcCopyValues[2]{34, 75}; - CopyConstructItems(copyValues, 2, srcCopyValues); - Expect(copyValues[0].value).ToEqual(34); - Expect(copyValues[1].value).ToEqual(75); - }); - - It("Can move construct", []() - { - bool boolValues[2]{false, false}; // Assign simulated garbage - bool srcBoolValues[2]{true, false}; - MoveConstructItems(boolValues, 2, srcBoolValues); - Expect(boolValues[0]).ToEqual(true); - Expect(boolValues[1]).ToEqual(false); - - u8 u8Values[2]{34, 45}; // Assign simulated garbage - u8 srcU8Values[2]{128, 129}; - MoveConstructItems(u8Values, 2, srcU8Values); - Expect(u8Values[0]).ToEqual(128); - Expect(u8Values[1]).ToEqual(129); - - u32 u32Values[2]{34, 45}; // Assign simulated garbage - u32 srcU32Values[2]{128, 129}; - MoveConstructItems(u32Values, 2, srcU32Values); - Expect(u32Values[0]).ToEqual(128); - Expect(u32Values[1]).ToEqual(129); - - u32* ptrValues[2]{(u32*)1, (u32*)2}; // Assign simulated garbage - u32* srcPtrValues[2]{(u32*)34, (u32*)23433}; - MoveConstructItems(ptrValues, 2, srcPtrValues); - Expect(ptrValues[0]).ToEqual((u32*)34); - Expect(ptrValues[1]).ToEqual((u32*)23433); - - ConstructedType constructedValues[2]{{0.234f}, {0.234f}}; // Assign simulated garbage - ConstructedType srcConstructedTypeValues[2]{{1.f}, {2.f}}; - MoveConstructItems(constructedValues, 2, srcConstructedTypeValues); - Expect(constructedValues[0].value).ToEqual(1.f); - Expect(constructedValues[1].value).ToEqual(2.f); - - BoolsType boolsValues[2]{ - {.value1 = false, .value2 = true}, - {.value1 = false, .value2 = true} + CopyConstructItems(boolsValues, 2, srcBoolsValues); + Expect(boolsValues[0].value1).ToEqual(true); + Expect(boolsValues[0].value2).ToEqual(false); + Expect(boolsValues[1].value1).ToEqual(false); + Expect(boolsValues[1].value2).ToEqual(true); + + CopyType copyValues[2]{5, 6}; // Assign simulated garbage + CopyType srcCopyValues[2]{34, 75}; + CopyConstructItems(copyValues, 2, srcCopyValues); + Expect(copyValues[0].value).ToEqual(34); + Expect(copyValues[1].value).ToEqual(75); +}); + +It("Can move construct", []() +{ + bool boolValues[2]{false, false}; // Assign simulated garbage + bool srcBoolValues[2]{true, false}; + MoveConstructItems(boolValues, 2, srcBoolValues); + Expect(boolValues[0]).ToEqual(true); + Expect(boolValues[1]).ToEqual(false); + + u8 u8Values[2]{34, 45}; // Assign simulated garbage + u8 srcU8Values[2]{128, 129}; + MoveConstructItems(u8Values, 2, srcU8Values); + Expect(u8Values[0]).ToEqual(128); + Expect(u8Values[1]).ToEqual(129); + + u32 u32Values[2]{34, 45}; // Assign simulated garbage + u32 srcU32Values[2]{128, 129}; + MoveConstructItems(u32Values, 2, srcU32Values); + Expect(u32Values[0]).ToEqual(128); + Expect(u32Values[1]).ToEqual(129); + + u32* ptrValues[2]{(u32*)1, (u32*)2}; // Assign simulated garbage + u32* srcPtrValues[2]{(u32*)34, (u32*)23433}; + MoveConstructItems(ptrValues, 2, srcPtrValues); + Expect(ptrValues[0]).ToEqual((u32*)34); + Expect(ptrValues[1]).ToEqual((u32*)23433); + + ConstructedType constructedValues[2]{{0.234f}, {0.234f}}; // Assign simulated garbage + ConstructedType srcConstructedTypeValues[2]{{1.f}, {2.f}}; + MoveConstructItems(constructedValues, 2, srcConstructedTypeValues); + Expect(constructedValues[0].value).ToEqual(1.f); + Expect(constructedValues[1].value).ToEqual(2.f); + + BoolsType boolsValues[2]{ + {.value1 = false, .value2 = true}, + {.value1 = false, .value2 = true} }; // Assign simulated garbage - BoolsType srcConstructedType2Values[2]{ - {.value1 = true, .value2 = false}, + BoolsType srcConstructedType2Values[2]{ + {.value1 = true, .value2 = false}, {.value1 = false, .value2 = true } }; - MoveConstructItems(boolsValues, 2, srcConstructedType2Values); - Expect(boolsValues[0].value1).ToEqual(true); - Expect(boolsValues[0].value2).ToEqual(false); - Expect(boolsValues[1].value1).ToEqual(false); - Expect(boolsValues[1].value2).ToEqual(true); - - MoveType moveValues[2]{5, 6}; // Assign simulated garbage - MoveType srcMoveValues[2]{34, 75}; - MoveConstructItems(moveValues, 2, srcMoveValues); - Expect(moveValues[0].value).ToEqual(34); - Expect(moveValues[1].value).ToEqual(75); - Expect(srcMoveValues[0].value).ToEqual(0); - Expect(srcMoveValues[1].value).ToEqual(0); - }); + MoveConstructItems(boolsValues, 2, srcConstructedType2Values); + Expect(boolsValues[0].value1).ToEqual(true); + Expect(boolsValues[0].value2).ToEqual(false); + Expect(boolsValues[1].value1).ToEqual(false); + Expect(boolsValues[1].value2).ToEqual(true); + + MoveType moveValues[2]{5, 6}; // Assign simulated garbage + MoveType srcMoveValues[2]{34, 75}; + MoveConstructItems(moveValues, 2, srcMoveValues); + Expect(moveValues[0].value).ToEqual(34); + Expect(moveValues[1].value).ToEqual(75); + Expect(srcMoveValues[0].value).ToEqual(0); + Expect(srcMoveValues[1].value).ToEqual(0); +}); }); -return true; -}(); -} // namespace diff --git a/Tests/Memory/MemoryStats.spec.cpp b/Tests/Memory/MemoryStats.spec.cpp index 68469bbc..1bc9d4a8 100644 --- a/Tests/Memory/MemoryStats.spec.cpp +++ b/Tests/Memory/MemoryStats.spec.cpp @@ -22,627 +22,619 @@ static const MemoryStatsEvent* LiveFind(const MemoryStats& s, void* ptr) } -namespace -{ -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() -{ Spec("Memory.MemoryStats", []() { - Describe("Basic", []() +Describe("Basic", []() +{ + It("Starts empty", []() { - It("Starts empty", []() - { - MemoryStats s; - s.CollectStats(); - Expect(s.used).ToEqual(0); - Expect(s.totalAllocated).ToEqual(0); - Expect(LiveCount(s)).ToEqual(0); - }); + MemoryStats s; + s.CollectStats(); + Expect(s.used).ToEqual(0); + Expect(s.totalAllocated).ToEqual(0); + Expect(LiveCount(s)).ToEqual(0); + }); - It("Tracks a single add", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 64); - s.CollectStats(); - Expect(s.used).ToEqual(64); - Expect(s.totalAllocated).ToEqual(64); - Expect(LiveCount(s)).ToEqual(1); - Expect(LiveFind(s, (void*)0x1000) != nullptr).ToBeTrue(); - Expect(LiveFind(s, (void*)0x1000)->GetSize()).ToEqual(64); - Expect(LiveFind(s, (void*)0x1000)->IsFree()).ToEqual(false); - }); + It("Tracks a single add", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 64); + s.CollectStats(); + Expect(s.used).ToEqual(64); + Expect(s.totalAllocated).ToEqual(64); + Expect(LiveCount(s)).ToEqual(1); + Expect(LiveFind(s, (void*)0x1000) != nullptr).ToBeTrue(); + Expect(LiveFind(s, (void*)0x1000)->GetSize()).ToEqual(64); + Expect(LiveFind(s, (void*)0x1000)->IsFree()).ToEqual(false); + }); - It("Tracks add plus free", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 64); - s.Remove((void*)0x1000, 64); - s.CollectStats(); - Expect(s.used).ToEqual(0); - Expect(LiveCount(s)).ToEqual(0); - // totalAllocated is cumulative alloc bytes ever. - Expect(s.totalAllocated).ToEqual(64); - }); + It("Tracks add plus free", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 64); + s.Remove((void*)0x1000, 64); + s.CollectStats(); + Expect(s.used).ToEqual(0); + Expect(LiveCount(s)).ToEqual(0); + // totalAllocated is cumulative alloc bytes ever. + Expect(s.totalAllocated).ToEqual(64); + }); - It("Tracks multiple adds", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 16); - s.Add((void*)0x2000, 32); - s.Add((void*)0x3000, 64); - s.CollectStats(); - Expect(s.used).ToEqual(16 + 32 + 64); - Expect(s.totalAllocated).ToEqual(16 + 32 + 64); - Expect(LiveCount(s)).ToEqual(3); - }); + It("Tracks multiple adds", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 16); + s.Add((void*)0x2000, 32); + s.Add((void*)0x3000, 64); + s.CollectStats(); + Expect(s.used).ToEqual(16 + 32 + 64); + Expect(s.totalAllocated).ToEqual(16 + 32 + 64); + Expect(LiveCount(s)).ToEqual(3); + }); + + It("Tracks many adds and frees", []() + { + MemoryStats s; + s.detectLeaks = false; + const sizet N = 100; + TArray buf(N * 16); - It("Tracks many adds and frees", []() + for (sizet i = 0; i < N; ++i) { - MemoryStats s; - s.detectLeaks = false; - const sizet N = 100; - TArray buf(N * 16); + s.Add(&buf[i * 16], 16); + } + for (sizet i = 0; i < N; i += 2) + { + s.Remove(&buf[i * 16], 16); + } + s.CollectStats(); - for (sizet i = 0; i < N; ++i) - { - s.Add(&buf[i * 16], 16); - } - for (sizet i = 0; i < N; i += 2) - { - s.Remove(&buf[i * 16], 16); - } - s.CollectStats(); + Expect(s.used).ToEqual((N / 2) * 16); + Expect(s.totalAllocated).ToEqual(N * 16); + Expect(LiveCount(s)).ToEqual(N / 2); + }); - Expect(s.used).ToEqual((N / 2) * 16); - Expect(s.totalAllocated).ToEqual(N * 16); - Expect(LiveCount(s)).ToEqual(N / 2); - }); + It("Ignores double-free", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 64); + s.Remove((void*)0x1000, 64); + s.Remove((void*)0x1000, 64); + s.CollectStats(); + // The second free matches no live alloc and is ignored. + Expect(s.used).ToEqual(0); + Expect(LiveCount(s)).ToEqual(0); + }); - It("Ignores double-free", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 64); - s.Remove((void*)0x1000, 64); - s.Remove((void*)0x1000, 64); - s.CollectStats(); - // The second free matches no live alloc and is ignored. - Expect(s.used).ToEqual(0); - Expect(LiveCount(s)).ToEqual(0); - }); + It("Ignores free of unknown ptr", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Remove((void*)0xDEAD, 64); + s.CollectStats(); + Expect(s.used).ToEqual(0); + Expect(LiveCount(s)).ToEqual(0); + }); - It("Ignores free of unknown ptr", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Remove((void*)0xDEAD, 64); - s.CollectStats(); - Expect(s.used).ToEqual(0); - Expect(LiveCount(s)).ToEqual(0); - }); + It("Records duplicate allocs as UnfreedRealloc", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 64); + s.Add((void*)0x1000, 128); + s.CollectStats(); + // Same ptr twice: the second alloc is an error and the + // live set is left untouched. + Expect(LiveCount(s)).ToEqual(1); + Expect(LiveFind(s, (void*)0x1000)->GetSize()).ToEqual(64); + Expect(s.errors.Size()).ToEqual(1); + Expect(s.errors[0].kind == MemoryStatsErrorType::UnfreedRealloc).ToBeTrue(); + Expect(s.errors[0].event.GetSize()).ToEqual(128); + Expect(s.used).ToEqual(64); + }); - It("Records duplicate allocs as UnfreedRealloc", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 64); - s.Add((void*)0x1000, 128); - s.CollectStats(); - // Same ptr twice: the second alloc is an error and the - // live set is left untouched. - Expect(LiveCount(s)).ToEqual(1); - Expect(LiveFind(s, (void*)0x1000)->GetSize()).ToEqual(64); - Expect(s.errors.Size()).ToEqual(1); - Expect(s.errors[0].kind == MemoryStatsErrorType::UnfreedRealloc).ToBeTrue(); - Expect(s.errors[0].event.GetSize()).ToEqual(128); - Expect(s.used).ToEqual(64); - }); + It("CheckLeaks always runs when called directly", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 64); + s.CollectStats(); + s.CheckLeaks(); + Expect(LiveCount(s)).ToEqual(1); + Expect(s.used).ToEqual(64); + }); - It("CheckLeaks always runs when called directly", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 64); - s.CollectStats(); - s.CheckLeaks(); - Expect(LiveCount(s)).ToEqual(1); - Expect(s.used).ToEqual(64); - }); + It("Always tracks frees (no trackFrees flag)", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 64); + s.Remove((void*)0x1000, 64); + s.CollectStats(); + Expect(s.used).ToEqual(0); + Expect(LiveCount(s)).ToEqual(0); + }); - It("Always tracks frees (no trackFrees flag)", []() + It("CheckLeaks with null name does not crash", []() + { { + // detectLeaks defaults to true and name defaults to null. MemoryStats s; - s.detectLeaks = false; s.Add((void*)0x1000, 64); - s.Remove((void*)0x1000, 64); s.CollectStats(); - Expect(s.used).ToEqual(0); - Expect(LiveCount(s)).ToEqual(0); - }); + // Destructor runs CheckLeaks with leaks and a null name. + } + }); - It("CheckLeaks with null name does not crash", []() - { - { - // detectLeaks defaults to true and name defaults to null. - MemoryStats s; - s.Add((void*)0x1000, 64); - s.CollectStats(); - // Destructor runs CheckLeaks with leaks and a null name. - } - }); + It("live list only keeps unmatched allocs", []() + { + MemoryStats s; + s.detectLeaks = false; + // allocs: 2 live, 1 matched. frees: 2 (one matches, one stray). + s.Add((void*)0x1000, 64); + s.Add((void*)0x2000, 32); + s.Add((void*)0x3000, 16); + s.Remove((void*)0x3000, 16); + s.Remove((void*)0xDEAD, 16); + s.CollectStats(); + + Expect(LiveCount(s)).ToEqual(2); + Expect(LiveFind(s, (void*)0x1000)->GetSize()).ToEqual(64); + Expect(LiveFind(s, (void*)0x2000)->GetSize()).ToEqual(32); + + // Re-collecting must preserve the live list identically. + s.CollectStats(); + Expect(LiveCount(s)).ToEqual(2); + Expect(s.used).ToEqual(64 + 32); + }); - It("live list only keeps unmatched allocs", []() - { - MemoryStats s; - s.detectLeaks = false; - // allocs: 2 live, 1 matched. frees: 2 (one matches, one stray). - s.Add((void*)0x1000, 64); - s.Add((void*)0x2000, 32); - s.Add((void*)0x3000, 16); - s.Remove((void*)0x3000, 16); - s.Remove((void*)0xDEAD, 16); - s.CollectStats(); + It("Alternating instances on one thread", []() + { + // Exercises thread context reuse when the owner switches. + MemoryStats a; + MemoryStats b; + a.detectLeaks = false; + b.detectLeaks = false; + + a.Add((void*)0x1000, 64); + b.Add((void*)0x2000, 32); + a.Add((void*)0x3000, 16); + b.Remove((void*)0x2000, 32); + + a.CollectStats(); + b.CollectStats(); + + Expect(a.used).ToEqual(64 + 16); + Expect(LiveCount(a)).ToEqual(2); + Expect(b.used).ToEqual(0); + Expect(LiveCount(b)).ToEqual(0); + }); - Expect(LiveCount(s)).ToEqual(2); - Expect(LiveFind(s, (void*)0x1000)->GetSize()).ToEqual(64); - Expect(LiveFind(s, (void*)0x2000)->GetSize()).ToEqual(32); + It("Add after Reset works", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 64); + s.Reset(); + Expect(LiveCount(s)).ToEqual(0); + + s.Add((void*)0x2000, 32); + s.CollectStats(); + Expect(s.used).ToEqual(32); + Expect(s.totalAllocated).ToEqual(32); + Expect(LiveCount(s)).ToEqual(1); + }); - // Re-collecting must preserve the live list identically. - s.CollectStats(); - Expect(LiveCount(s)).ToEqual(2); - Expect(s.used).ToEqual(64 + 32); - }); + It("Duplicate allocs record UnfreedRealloc and live stays usable", []() + { + MemoryStats s; + s.detectLeaks = false; + + // Collect 1: two allocs sharing the same ptr. The second is + // an UnfreedRealloc error; the live set keeps only the first. + s.Add((void*)0x1000, 64); + s.Add((void*)0x1000, 64); + s.CollectStats(); + Expect(LiveCount(s)).ToEqual(1); + Expect(s.errors.Size()).ToEqual(1); + Expect(s.errors[0].kind == MemoryStatsErrorType::UnfreedRealloc).ToBeTrue(); + Expect(s.used).ToEqual(64); + + // Collect 2: freeing the original alloc still works. + s.Remove((void*)0x1000, 64); + s.CollectStats(); + Expect(LiveCount(s)).ToEqual(0); + Expect(s.used).ToEqual(0); + }); - It("Alternating instances on one thread", []() - { - // Exercises thread context reuse when the owner switches. - MemoryStats a; - MemoryStats b; - a.detectLeaks = false; - b.detectLeaks = false; - - a.Add((void*)0x1000, 64); - b.Add((void*)0x2000, 32); - a.Add((void*)0x3000, 16); - b.Remove((void*)0x2000, 32); - - a.CollectStats(); - b.CollectStats(); - - Expect(a.used).ToEqual(64 + 16); - Expect(LiveCount(a)).ToEqual(2); - Expect(b.used).ToEqual(0); - Expect(LiveCount(b)).ToEqual(0); - }); + It("Free with wrong size records SizeMismatch", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 64); + s.Remove((void*)0x1000, 32); // size mismatch + s.CollectStats(); + Expect(LiveCount(s)).ToEqual(1); + Expect(s.errors.Size()).ToEqual(1); + Expect(s.errors[0].kind == MemoryStatsErrorType::SizeMismatch).ToBeTrue(); + Expect(s.errors[0].event.GetSize()).ToEqual(32); + Expect(s.used).ToEqual(64); + + // Correcting the size frees the alloc normally. + s.Remove((void*)0x1000, 64); + s.CollectStats(); + Expect(LiveCount(s)).ToEqual(0); + Expect(s.used).ToEqual(0); + }); - It("Add after Reset works", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 64); - s.Reset(); - Expect(LiveCount(s)).ToEqual(0); + It("Free of unknown ptr records UnknownFree", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 64); + s.Remove((void*)0xDEAD, 64); + s.CollectStats(); + Expect(LiveCount(s)).ToEqual(1); + Expect(s.errors.Size()).ToEqual(1); + Expect(s.errors[0].kind == MemoryStatsErrorType::UnknownFree).ToBeTrue(); + Expect(s.errors[0].event.GetPtr()).ToEqual((u8*)0xDEAD); + Expect(s.used).ToEqual(64); + }); - s.Add((void*)0x2000, 32); - s.CollectStats(); - Expect(s.used).ToEqual(32); - Expect(s.totalAllocated).ToEqual(32); - Expect(LiveCount(s)).ToEqual(1); - }); + It("Ignores null ptr in Remove", []() + { + MemoryStats s; + s.Remove(nullptr, 64); + s.CollectStats(); + Expect(s.used).ToEqual(0); + }); - It("Duplicate allocs record UnfreedRealloc and live stays usable", []() - { - MemoryStats s; - s.detectLeaks = false; + It("Ignores null ptr in Add", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add(nullptr, 64); + s.CollectStats(); + // Add has no null check (unlike Remove), so the event is + // recorded and processed. Add's size is still tracked. + Expect(s.used).ToEqual(64); + Expect(LiveCount(s)).ToEqual(1); + }); - // Collect 1: two allocs sharing the same ptr. The second is - // an UnfreedRealloc error; the live set keeps only the first. - s.Add((void*)0x1000, 64); - s.Add((void*)0x1000, 64); - s.CollectStats(); - Expect(LiveCount(s)).ToEqual(1); - Expect(s.errors.Size()).ToEqual(1); - Expect(s.errors[0].kind == MemoryStatsErrorType::UnfreedRealloc).ToBeTrue(); - Expect(s.used).ToEqual(64); + It("Reset resets state", []() + { + MemoryStats s; + s.Add((void*)0x1000, 64); + s.Add((void*)0x2000, 32); + s.CollectStats(); + Expect(s.used).ToEqual(96); + + s.Reset(); + Expect(s.used).ToEqual(0); + Expect(s.totalAllocated).ToEqual(0); + Expect(LiveCount(s)).ToEqual(0); + }); - // Collect 2: freeing the original alloc still works. - s.Remove((void*)0x1000, 64); - s.CollectStats(); - Expect(LiveCount(s)).ToEqual(0); - Expect(s.used).ToEqual(0); - }); + It("CollectStats is additive", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 64); + s.CollectStats(); + s.Add((void*)0x2000, 32); + s.CollectStats(); + Expect(s.used).ToEqual(96); + Expect(LiveCount(s)).ToEqual(2); + }); - It("Free with wrong size records SizeMismatch", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 64); - s.Remove((void*)0x1000, 32); // size mismatch - s.CollectStats(); - Expect(LiveCount(s)).ToEqual(1); - Expect(s.errors.Size()).ToEqual(1); - Expect(s.errors[0].kind == MemoryStatsErrorType::SizeMismatch).ToBeTrue(); - Expect(s.errors[0].event.GetSize()).ToEqual(32); - Expect(s.used).ToEqual(64); - - // Correcting the size frees the alloc normally. - s.Remove((void*)0x1000, 64); - s.CollectStats(); - Expect(LiveCount(s)).ToEqual(0); - Expect(s.used).ToEqual(0); - }); + It("Re-collecting preserves state", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 64); + s.CollectStats(); + s.CollectStats(); + Expect(s.used).ToEqual(64); + Expect(LiveCount(s)).ToEqual(1); + }); +}); - It("Free of unknown ptr records UnknownFree", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 64); - s.Remove((void*)0xDEAD, 64); - s.CollectStats(); - Expect(LiveCount(s)).ToEqual(1); - Expect(s.errors.Size()).ToEqual(1); - Expect(s.errors[0].kind == MemoryStatsErrorType::UnknownFree).ToBeTrue(); - Expect(s.errors[0].event.GetPtr()).ToEqual((u8*)0xDEAD); - Expect(s.used).ToEqual(64); - }); - It("Ignores null ptr in Remove", []() +Describe("Multiple chunks", []() +{ + It("Spans multiple chunks correctly", []() + { + MemoryStats s; + s.detectLeaks = false; + const sizet N = 10000; + TArray buf(N * 8); + for (sizet i = 0; i < N; ++i) { - MemoryStats s; - s.Remove(nullptr, 64); - s.CollectStats(); - Expect(s.used).ToEqual(0); - }); + s.Add(&buf[i * 8], 8); + } + s.CollectStats(); + Expect(s.used).ToEqual(N * 8); + Expect(s.totalAllocated).ToEqual(N * 8); + Expect(LiveCount(s)).ToEqual(N); + }); - It("Ignores null ptr in Add", []() + It("Handles add/free across chunks", []() + { + MemoryStats s; + s.detectLeaks = false; + const sizet N = 5000; + TArray buf(N * 8); + for (sizet i = 0; i < N; ++i) { - MemoryStats s; - s.detectLeaks = false; - s.Add(nullptr, 64); - s.CollectStats(); - // Add has no null check (unlike Remove), so the event is - // recorded and processed. Add's size is still tracked. - Expect(s.used).ToEqual(64); - Expect(LiveCount(s)).ToEqual(1); - }); - - It("Reset resets state", []() + s.Add(&buf[i * 8], 8); + } + for (sizet i = 0; i < N / 2; ++i) { - MemoryStats s; - s.Add((void*)0x1000, 64); - s.Add((void*)0x2000, 32); - s.CollectStats(); - Expect(s.used).ToEqual(96); - - s.Reset(); - Expect(s.used).ToEqual(0); - Expect(s.totalAllocated).ToEqual(0); - Expect(LiveCount(s)).ToEqual(0); - }); + s.Remove(&buf[i * 8], 8); + } + s.CollectStats(); + Expect(s.used).ToEqual((N / 2) * 8); + Expect(s.totalAllocated).ToEqual(N * 8); + Expect(LiveCount(s)).ToEqual(N / 2); + }); - It("CollectStats is additive", []() + It("Frees chunks between CollectStats calls", []() + { + MemoryStats s; + s.detectLeaks = false; + const sizet N = 10000; + TArray buf(N * 8); + for (sizet i = 0; i < N; ++i) { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 64); - s.CollectStats(); - s.Add((void*)0x2000, 32); - s.CollectStats(); - Expect(s.used).ToEqual(96); - Expect(LiveCount(s)).ToEqual(2); - }); - - It("Re-collecting preserves state", []() + s.Add(&buf[i * 8], 8); + } + s.CollectStats(); + Expect(LiveCount(s)).ToEqual(N); + for (sizet i = 0; i < N / 2; ++i) { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 64); - s.CollectStats(); - s.CollectStats(); - Expect(s.used).ToEqual(64); - Expect(LiveCount(s)).ToEqual(1); - }); + s.Remove(&buf[i * 8], 8); + } + s.CollectStats(); + Expect(LiveCount(s)).ToEqual(N / 2); + Expect(s.used).ToEqual((N / 2) * 8); }); +}); - Describe("Multiple chunks", []() +Describe("Multithreading", []() +{ + It("One thread adds, another collects", []() { - It("Spans multiple chunks correctly", []() - { - MemoryStats s; - s.detectLeaks = false; - const sizet N = 10000; - TArray buf(N * 8); - for (sizet i = 0; i < N; ++i) - { - s.Add(&buf[i * 8], 8); - } - s.CollectStats(); - Expect(s.used).ToEqual(N * 8); - Expect(s.totalAllocated).ToEqual(N * 8); - Expect(LiveCount(s)).ToEqual(N); - }); + MemoryStats s; + const sizet N = 1000; + TArray buf(N * 8); + std::atomic start{false}; + std::atomic producerDone{false}; - It("Handles add/free across chunks", []() + std::thread producer([&]() { - MemoryStats s; - s.detectLeaks = false; - const sizet N = 5000; - TArray buf(N * 8); + while (!start.load(std::memory_order_acquire)) + {} for (sizet i = 0; i < N; ++i) { s.Add(&buf[i * 8], 8); } - for (sizet i = 0; i < N / 2; ++i) - { - s.Remove(&buf[i * 8], 8); - } - s.CollectStats(); - Expect(s.used).ToEqual((N / 2) * 8); - Expect(s.totalAllocated).ToEqual(N * 8); - Expect(LiveCount(s)).ToEqual(N / 2); + producerDone.store(true, std::memory_order_release); }); - It("Frees chunks between CollectStats calls", []() + std::thread consumer([&]() { - MemoryStats s; - s.detectLeaks = false; - const sizet N = 10000; - TArray buf(N * 8); - for (sizet i = 0; i < N; ++i) + while (!start.load(std::memory_order_acquire)) + {} + while (!producerDone.load(std::memory_order_acquire) || LiveCount(s) < N) { - s.Add(&buf[i * 8], 8); - } - s.CollectStats(); - Expect(LiveCount(s)).ToEqual(N); - for (sizet i = 0; i < N / 2; ++i) - { - s.Remove(&buf[i * 8], 8); + s.CollectStats(); + std::this_thread::yield(); } - s.CollectStats(); - Expect(LiveCount(s)).ToEqual(N / 2); - Expect(s.used).ToEqual((N / 2) * 8); }); - }); - - - Describe("Multithreading", []() - { - It("One thread adds, another collects", []() - { - MemoryStats s; - const sizet N = 1000; - TArray buf(N * 8); - std::atomic start{false}; - std::atomic producerDone{false}; - std::thread producer([&]() - { - while (!start.load(std::memory_order_acquire)) - {} - for (sizet i = 0; i < N; ++i) - { - s.Add(&buf[i * 8], 8); - } - producerDone.store(true, std::memory_order_release); - }); + start.store(true, std::memory_order_release); + producer.join(); + consumer.join(); - std::thread consumer([&]() - { - while (!start.load(std::memory_order_acquire)) - {} - while (!producerDone.load(std::memory_order_acquire) || LiveCount(s) < N) - { - s.CollectStats(); - std::this_thread::yield(); - } - }); + Expect(LiveCount(s)).ToEqual(N); + Expect(s.used).ToEqual(N * 8); - start.store(true, std::memory_order_release); - producer.join(); - consumer.join(); - - Expect(LiveCount(s)).ToEqual(N); - Expect(s.used).ToEqual(N * 8); + // Suppress leak warnings at destruction (test buffers are stack). + s.Reset(); + }); - // Suppress leak warnings at destruction (test buffers are stack). - s.Reset(); - }); + It("Many threads add, then collects", []() + { + MemoryStats s; + const sizet N_PER_THREAD = 1000; + const sizet NUM_THREADS = 4; + const sizet N = N_PER_THREAD * NUM_THREADS; - It("Many threads add, then collects", []() + TArray, 0> buffers; + for (sizet t = 0; t < NUM_THREADS; ++t) { - MemoryStats s; - const sizet N_PER_THREAD = 1000; - const sizet NUM_THREADS = 4; - const sizet N = N_PER_THREAD * NUM_THREADS; + TArray buf(N_PER_THREAD * 8); + buffers.Add(Move(buf)); + } - TArray, 0> buffers; - for (sizet t = 0; t < NUM_THREADS; ++t) - { - TArray buf(N_PER_THREAD * 8); - buffers.Add(Move(buf)); - } + std::atomic start{false}; + std::atomic producersDone{0}; + std::vector producers; - std::atomic start{false}; - std::atomic producersDone{0}; - std::vector producers; - - for (sizet t = 0; t < NUM_THREADS; ++t) - { - producers.emplace_back([&, t]() - { - while (!start.load(std::memory_order_acquire)) - {} - for (sizet i = 0; i < N_PER_THREAD; ++i) - { - s.Add(&buffers[t][i * 8], 8); - } - producersDone.fetch_add(1, std::memory_order_release); - }); - } - - std::thread consumer([&]() + for (sizet t = 0; t < NUM_THREADS; ++t) + { + producers.emplace_back([&, t]() { while (!start.load(std::memory_order_acquire)) {} - while (producersDone.load(std::memory_order_acquire) < NUM_THREADS) + for (sizet i = 0; i < N_PER_THREAD; ++i) { - s.CollectStats(); - std::this_thread::yield(); + s.Add(&buffers[t][i * 8], 8); } - s.CollectStats(); + producersDone.fetch_add(1, std::memory_order_release); }); + } - start.store(true, std::memory_order_release); - for (auto& t : producers) + std::thread consumer([&]() + { + while (!start.load(std::memory_order_acquire)) + {} + while (producersDone.load(std::memory_order_acquire) < NUM_THREADS) { - t.join(); + s.CollectStats(); + std::this_thread::yield(); } - consumer.join(); - - Expect(LiveCount(s)).ToEqual(N); - Expect(s.used).ToEqual(N * 8); - Expect(s.totalAllocated).ToEqual(N * 8); - - // Suppress leak warnings at destruction (test buffers are stack). - s.Reset(); + s.CollectStats(); }); - It("Many threads add and remove, then collects", []() + start.store(true, std::memory_order_release); + for (auto& t : producers) { - MemoryStats s; - const sizet N_PER_THREAD = 1000; - const sizet NUM_THREADS = 4; - const sizet N = N_PER_THREAD * NUM_THREADS; + t.join(); + } + consumer.join(); - TArray, 0> buffers; - for (sizet t = 0; t < NUM_THREADS; ++t) - { - TArray buf(N_PER_THREAD * 8); - buffers.Add(Move(buf)); - } + Expect(LiveCount(s)).ToEqual(N); + Expect(s.used).ToEqual(N * 8); + Expect(s.totalAllocated).ToEqual(N * 8); - std::atomic start{false}; - std::atomic producersDone{0}; - std::vector producers; + // Suppress leak warnings at destruction (test buffers are stack). + s.Reset(); + }); - for (sizet t = 0; t < NUM_THREADS; ++t) - { - producers.emplace_back([&, t]() - { - while (!start.load(std::memory_order_acquire)) - {} - for (sizet i = 0; i < N_PER_THREAD; ++i) - { - s.Add(&buffers[t][i * 8], 8); - } - // Free the first half. - for (sizet i = 0; i < N_PER_THREAD / 2; ++i) - { - s.Remove(&buffers[t][i * 8], 8); - } - producersDone.fetch_add(1, std::memory_order_release); - }); - } + It("Many threads add and remove, then collects", []() + { + MemoryStats s; + const sizet N_PER_THREAD = 1000; + const sizet NUM_THREADS = 4; + const sizet N = N_PER_THREAD * NUM_THREADS; + + TArray, 0> buffers; + for (sizet t = 0; t < NUM_THREADS; ++t) + { + TArray buf(N_PER_THREAD * 8); + buffers.Add(Move(buf)); + } + + std::atomic start{false}; + std::atomic producersDone{0}; + std::vector producers; - std::thread consumer([&]() + for (sizet t = 0; t < NUM_THREADS; ++t) + { + producers.emplace_back([&, t]() { while (!start.load(std::memory_order_acquire)) {} - while (producersDone.load(std::memory_order_acquire) < NUM_THREADS) + for (sizet i = 0; i < N_PER_THREAD; ++i) { - s.CollectStats(); - std::this_thread::yield(); + s.Add(&buffers[t][i * 8], 8); } - s.CollectStats(); + // Free the first half. + for (sizet i = 0; i < N_PER_THREAD / 2; ++i) + { + s.Remove(&buffers[t][i * 8], 8); + } + producersDone.fetch_add(1, std::memory_order_release); }); + } - start.store(true, std::memory_order_release); - for (auto& t : producers) + std::thread consumer([&]() + { + while (!start.load(std::memory_order_acquire)) + {} + while (producersDone.load(std::memory_order_acquire) < NUM_THREADS) { - t.join(); + s.CollectStats(); + std::this_thread::yield(); } - consumer.join(); + s.CollectStats(); + }); + + start.store(true, std::memory_order_release); + for (auto& t : producers) + { + t.join(); + } + consumer.join(); - Expect(LiveCount(s)).ToEqual(N / 2); - Expect(s.used).ToEqual((N / 2) * 8); - Expect(s.totalAllocated).ToEqual(N * 8); + Expect(LiveCount(s)).ToEqual(N / 2); + Expect(s.used).ToEqual((N / 2) * 8); + Expect(s.totalAllocated).ToEqual(N * 8); - // Suppress leak warnings at destruction (test buffers are stack). - s.Reset(); - }); + // Suppress leak warnings at destruction (test buffers are stack). + s.Reset(); }); +}); - Describe("Heavy stress", []() +Describe("Heavy stress", []() +{ + It("Many producers, many iterations, no crashes", []() { - It("Many producers, many iterations, no crashes", []() - { - MemoryStats s; - const sizet N_PER_THREAD = 2000; - const sizet NUM_THREADS = 4; - const sizet N = N_PER_THREAD * NUM_THREADS; + MemoryStats s; + const sizet N_PER_THREAD = 2000; + const sizet NUM_THREADS = 4; + const sizet N = N_PER_THREAD * NUM_THREADS; - TArray, 0> buffers; - for (sizet t = 0; t < NUM_THREADS; ++t) - { - TArray buf(N_PER_THREAD * 8); - buffers.Add(Move(buf)); - } - - std::atomic start{false}; - std::atomic producersDone{0}; - std::vector producers; + TArray, 0> buffers; + for (sizet t = 0; t < NUM_THREADS; ++t) + { + TArray buf(N_PER_THREAD * 8); + buffers.Add(Move(buf)); + } - for (sizet t = 0; t < NUM_THREADS; ++t) - { - producers.emplace_back([&, t]() - { - while (!start.load(std::memory_order_acquire)) - {} - for (sizet i = 0; i < N_PER_THREAD; ++i) - { - s.Add(&buffers[t][i * 8], 8); - if (i > 0 && i % 3 == 0) - { - s.Remove(&buffers[t][(i - 1) * 8], 8); - } - } - producersDone.fetch_add(1, std::memory_order_release); - }); - } + std::atomic start{false}; + std::atomic producersDone{0}; + std::vector producers; - std::thread consumer([&]() + for (sizet t = 0; t < NUM_THREADS; ++t) + { + producers.emplace_back([&, t]() { while (!start.load(std::memory_order_acquire)) {} - while (producersDone.load(std::memory_order_acquire) < NUM_THREADS) + for (sizet i = 0; i < N_PER_THREAD; ++i) { - s.CollectStats(); - std::this_thread::yield(); + s.Add(&buffers[t][i * 8], 8); + if (i > 0 && i % 3 == 0) + { + s.Remove(&buffers[t][(i - 1) * 8], 8); + } } - s.CollectStats(); + producersDone.fetch_add(1, std::memory_order_release); }); + } - start.store(true, std::memory_order_release); - for (auto& t : producers) + std::thread consumer([&]() + { + while (!start.load(std::memory_order_acquire)) + {} + while (producersDone.load(std::memory_order_acquire) < NUM_THREADS) { - t.join(); + s.CollectStats(); + std::this_thread::yield(); } - consumer.join(); + s.CollectStats(); + }); - // s.used reflects the net remaining live set. - Expect(s.used).ToEqual(LiveCount(s) * 8); + start.store(true, std::memory_order_release); + for (auto& t : producers) + { + t.join(); + } + consumer.join(); - // Suppress leak warnings at destruction (test buffers are stack). - s.Reset(); - }); + // s.used reflects the net remaining live set. + Expect(s.used).ToEqual(LiveCount(s) * 8); + + // Suppress leak warnings at destruction (test buffers are stack). + s.Reset(); }); }); -return true; -}(); -} // namespace +}); diff --git a/Tests/Memory/MonoLinearArena.spec.cpp b/Tests/Memory/MonoLinearArena.spec.cpp index b891b5f9..59bbee95 100644 --- a/Tests/Memory/MonoLinearArena.spec.cpp +++ b/Tests/Memory/MonoLinearArena.spec.cpp @@ -7,144 +7,136 @@ using namespace p; -namespace +Spec("Memory.MonoLinearArena", []() { -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() +It("Reserves a block on construction", []() { -Spec("Memory.MonoLinearArena", []() + MonoLinearArena arena{1024}; + + Expect(arena.GetAvailableMemory()).ToEqual(1024); + arena.GetStats()->CollectStats(); + Expect(arena.GetStats()->used).ToEqual(0); +}); + +It("Can allocate outside the block", []() +{ + MonoLinearArena arena{256}; + + Expect(arena.GetAvailableMemory()).ToEqual(256); + void* p = arena.Alloc(512); + Expect(arena.GetAvailableMemory()).ToEqual(256); + arena.Free(p, 512); +}); + +It("Can free from outside the block", []() +{ + MonoLinearArena arena{256}; + + void* p = arena.Alloc(512); + Expect(arena.GetAvailableMemory()).ToEqual(256); + arena.Free(p, 512); + Expect(arena.GetAvailableMemory()).ToEqual(256); +}); + +It("Can free active block", []() +{ + MonoLinearArena arena{1024}; + arena.Release(); + + TArray blocks; + arena.GetBlocks(blocks); + Expect(blocks.Size()).ToEqual(1); +}); + +It("Can allocate", []() +{ + MonoLinearArena arena{1024}; + void* p = arena.Alloc(sizeof(float)); + Expect(p).ToNotEqual(nullptr); + arena.GetStats()->CollectStats(); + Expect(arena.GetStats()->used).ToEqual(4); + Expect(arena.GetAvailableMemory()).ToEqual(1024); + arena.Free(p, sizeof(float)); +}); + +It("Can allocate with alignment", []() +{ + MonoLinearArena arena{1024}; + + void* p0 = arena.Alloc(sizeof(bool)); + + // When padding is not 0 (last ptr is not aligned) + void* p1 = arena.Alloc(sizeof(float), 8); + Expect(p::GetAlignmentPadding(p1, 8)).ToEqual(0); + + // When padding is 0 (last ptr is aligned) + void* p2 = arena.Alloc(sizeof(float), 16); + Expect(p::GetAlignmentPadding(p2, 16)).ToEqual(0); + + arena.Free(p0, sizeof(bool)); + arena.Free(p1, sizeof(float)); + arena.Free(p2, sizeof(float)); +}); + +It("Can allocate after release", []() { - It("Reserves a block on construction", []() - { - MonoLinearArena arena{1024}; - - Expect(arena.GetAvailableMemory()).ToEqual(1024); - arena.GetStats()->CollectStats(); - Expect(arena.GetStats()->used).ToEqual(0); - }); - - It("Can allocate outside the block", []() - { - MonoLinearArena arena{256}; - - Expect(arena.GetAvailableMemory()).ToEqual(256); - void* p = arena.Alloc(512); - Expect(arena.GetAvailableMemory()).ToEqual(256); - arena.Free(p, 512); - }); - - It("Can free from outside the block", []() - { - MonoLinearArena arena{256}; - - void* p = arena.Alloc(512); - Expect(arena.GetAvailableMemory()).ToEqual(256); - arena.Free(p, 512); - Expect(arena.GetAvailableMemory()).ToEqual(256); - }); - - It("Can free active block", []() - { - MonoLinearArena arena{1024}; - arena.Release(); - - TArray blocks; - arena.GetBlocks(blocks); - Expect(blocks.Size()).ToEqual(1); - }); - - It("Can allocate", []() - { - MonoLinearArena arena{1024}; - void* p = arena.Alloc(sizeof(float)); - Expect(p).ToNotEqual(nullptr); - arena.GetStats()->CollectStats(); - Expect(arena.GetStats()->used).ToEqual(4); - Expect(arena.GetAvailableMemory()).ToEqual(1024); - arena.Free(p, sizeof(float)); - }); - - It("Can allocate with alignment", []() - { - MonoLinearArena arena{1024}; - - void* p0 = arena.Alloc(sizeof(bool)); - - // When padding is not 0 (last ptr is not aligned) - void* p1 = arena.Alloc(sizeof(float), 8); - Expect(p::GetAlignmentPadding(p1, 8)).ToEqual(0); - - // When padding is 0 (last ptr is aligned) - void* p2 = arena.Alloc(sizeof(float), 16); - Expect(p::GetAlignmentPadding(p2, 16)).ToEqual(0); - - arena.Free(p0, sizeof(bool)); - arena.Free(p1, sizeof(float)); - arena.Free(p2, sizeof(float)); - }); - - It("Can allocate after release", []() - { - MonoLinearArena arena{1024}; - arena.Release(); - void* p = arena.Alloc(sizeof(float)); - Expect(p).ToNotEqual(nullptr); - arena.GetStats()->CollectStats(); - Expect(arena.GetStats()->used).ToEqual(4); - // Buffer size will be as small as the type (4 bytes) - Expect(arena.GetAvailableMemory()).ToEqual(1024); - - arena.Free(p, sizeof(float)); - }); - - It("Can free block after Free", []() - { - MonoLinearArena arena{1024}; - void* p = arena.Alloc(256); - arena.GetStats()->CollectStats(); - Expect(arena.GetStats()->used).ToEqual(256); - arena.Free(p, 256); - arena.GetStats()->CollectStats(); - Expect(arena.GetStats()->used).ToEqual(0); - }); - - It("Allocates at correct addresses", []() - { - MonoLinearArena arena{1024}; - - TArray blocks; - arena.GetBlocks(blocks); - - void* p1 = arena.Alloc(sizeof(float)); - Expect(p1).ToEqual(blocks[0].data); - void* p2 = arena.Alloc(sizeof(float), alignof(float)); - Expect(p2).ToEqual((u8*)blocks[0].data + 4); - - arena.Free(p1, sizeof(float)); - arena.Free(p2, sizeof(float)); - }); - - // Move test to Multi linear - /*It("Allocated new blocks when previous is filled", []() { - MonoLinearArena arena{16}; - - void* p = arena.Alloc(sizeof(float*)); // 8 bytes - arena.Alloc(sizeof(float)); // 4 bytes - Expect(arena.GetStats()->used).ToEqual(12); - Expect(arena.GetAvailableMemory()).ToEqual(16); - - void* p3 = arena.Alloc(sizeof(float*)); // 8 bytes - TArray blocks; - arena.GetBlocks(blocks); - Expect(blocks.Size()).ToEqual(2); - Expect(blocks[0]).ToNotEqual(blocks[1]); - Expect(p).ToEqual(blocks[0].data); - Expect(p3).ToEqual(blocks[1].data); - - Expect(arena.GetStats()->used).ToEqual(8); - Expect(arena.GetAvailableMemory()).ToEqual(16); - });*/ + MonoLinearArena arena{1024}; + arena.Release(); + void* p = arena.Alloc(sizeof(float)); + Expect(p).ToNotEqual(nullptr); + arena.GetStats()->CollectStats(); + Expect(arena.GetStats()->used).ToEqual(4); + // Buffer size will be as small as the type (4 bytes) + Expect(arena.GetAvailableMemory()).ToEqual(1024); + + arena.Free(p, sizeof(float)); +}); + +It("Can free block after Free", []() +{ + MonoLinearArena arena{1024}; + void* p = arena.Alloc(256); + arena.GetStats()->CollectStats(); + Expect(arena.GetStats()->used).ToEqual(256); + arena.Free(p, 256); + arena.GetStats()->CollectStats(); + Expect(arena.GetStats()->used).ToEqual(0); +}); + +It("Allocates at correct addresses", []() +{ + MonoLinearArena arena{1024}; + + TArray blocks; + arena.GetBlocks(blocks); + + void* p1 = arena.Alloc(sizeof(float)); + Expect(p1).ToEqual(blocks[0].data); + void* p2 = arena.Alloc(sizeof(float), alignof(float)); + Expect(p2).ToEqual((u8*)blocks[0].data + 4); + + arena.Free(p1, sizeof(float)); + arena.Free(p2, sizeof(float)); +}); + +// Move test to Multi linear +/*It("Allocated new blocks when previous is filled", []() { + MonoLinearArena arena{16}; + + void* p = arena.Alloc(sizeof(float*)); // 8 bytes + arena.Alloc(sizeof(float)); // 4 bytes + Expect(arena.GetStats()->used).ToEqual(12); + Expect(arena.GetAvailableMemory()).ToEqual(16); + + void* p3 = arena.Alloc(sizeof(float*)); // 8 bytes + TArray blocks; + arena.GetBlocks(blocks); + Expect(blocks.Size()).ToEqual(2); + Expect(blocks[0]).ToNotEqual(blocks[1]); + Expect(p).ToEqual(blocks[0].data); + Expect(p3).ToEqual(blocks[1].data); + + Expect(arena.GetStats()->used).ToEqual(8); + Expect(arena.GetAvailableMemory()).ToEqual(16); +});*/ }); -return true; -}(); -} // namespace diff --git a/Tests/PipeTests/PipeTests.spec.cpp b/Tests/PipeTest.spec.cpp similarity index 60% rename from Tests/PipeTests/PipeTests.spec.cpp rename to Tests/PipeTest.spec.cpp index 526aa28f..ce6e0f9d 100644 --- a/Tests/PipeTests/PipeTests.spec.cpp +++ b/Tests/PipeTest.spec.cpp @@ -1,7 +1,7 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include +#include #include @@ -12,12 +12,7 @@ static int afterEachCount = 0; static int topTestResult = 0; -namespace -{ -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() -{ -Spec("PipeTests", []() +Spec("PipeTest", []() { BeforeEach([]() { @@ -42,12 +37,6 @@ Spec("PipeTests", []() Describe("Expect", []() { - It("ToEqual / ToNotEqual", []() - { - int value = 4; - Expect(value).ToEqual(4); - Expect(value).ToNotEqual(5); - }); It("Relational", []() { int value = 4; @@ -56,23 +45,37 @@ Spec("PipeTests", []() Expect(value).ToBeGreater(3); Expect(value).ToBeGreaterOrEqual(4); }); - It("Booleans", []() - { - bool flag = true; - Expect(flag).ToBeTrue(); - Expect(!flag).ToBeFalse(); - }); - It("Strings", []() + It("Contains", []() { Expect("acidic").ToContain("acid"); + Expect("acidic").ToNotContain("dictionary"); + + Expect(String{"hello"}).ToContain("hello"); + Expect(String{"hello"}).ToContain("llo"); Expect(String{"hello"}).ToNotContain("world"); + Expect(String{"hello"}).ToNotContain("hello friend"); }); - It("Equals int", []() + It("ToEqual/ToNotEqual", []() { + // Ints + Expect(4u).ToEqual(4u); + Expect(4u).ToNotEqual(5u); + u32 value = 4u; + Expect(value).ToEqual(4u); + Expect(value).ToNotEqual(5u); + Expect(4).ToEqual(4); + Expect(4).ToNotEqual(5); + i32 value2 = 4; + Expect(value2).ToEqual(4); + Expect(value2).ToNotEqual(5); + + // Bools + Expect(true).ToBeTrue(); + Expect(false).ToBeFalse(); + bool flag = true; + Expect(flag).ToBeTrue(); + Expect(!flag).ToBeFalse(); }); }); }); -return true; -}(); -} // namespace diff --git a/Tests/PipeTests/CMakeLists.txt b/Tests/PipeTests/CMakeLists.txt deleted file mode 100644 index 3f1ef591..00000000 --- a/Tests/PipeTests/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -# PipeTests self-test (uses the new framework). Bandit-based suite remains unchanged. -add_executable(PipeTestsSelf PipeTests.spec.cpp main.cpp) -pipe_target_define_platform(PipeTestsSelf) -pipe_target_enable_CPP20(PipeTestsSelf) -pipe_target_disable_rtti(PipeTestsSelf PRIVATE) -pipe_target_shared_output_directory(PipeTestsSelf) -target_link_libraries(PipeTestsSelf PUBLIC PipeTest Pipe) -add_test(NAME PipeTestsSelf COMMAND $) diff --git a/Tests/PipeTests/main.cpp b/Tests/PipeTests/main.cpp deleted file mode 100644 index c3e8713e..00000000 --- a/Tests/PipeTests/main.cpp +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2015-2026 Piperift. All Rights Reserved. - -// NOTE: PipeNewDelete is deliberately not included here. PipeTest provides the -// replacement operator new/delete (P_OVERRIDE_NEWDELETE) in its own translation unit; -// including it here too would cause duplicate-definition linker errors. - -#include -#include - - -int main(int argc, char* argv[]) -{ - p::Initialize(); - int result = p::RunTests(argc, argv); - p::Shutdown(); - return result; -} diff --git a/Tests/PipeTime.spec.cpp b/Tests/PipeTime.spec.cpp new file mode 100644 index 00000000..95721ece --- /dev/null +++ b/Tests/PipeTime.spec.cpp @@ -0,0 +1,28 @@ +// Copyright 2015-2026 Piperift. All Rights Reserved. + +#include +#include + + +using namespace p; + + + Spec("Time.DateTime", []() + { + It("Can get day of year", []() + { + DateTime time1{2024, 1, 1}; + Expect(time1.GetDayOfYear()).ToEqual(1); + DateTime time11{2024, 1, 30}; + Expect(time11.GetDayOfYear()).ToEqual(30); + DateTime time12{2024, 1, 31}; + Expect(time12.GetDayOfYear()).ToEqual(31); + + DateTime time2{2024, 2, 1}; + Expect(time2.GetDayOfYear()).ToEqual(32); + DateTime time3{2024, 3, 1}; + Expect(time3.GetDayOfYear()).ToEqual(60); + DateTime time4{2024, 12, 31}; + Expect(time4.GetDayOfYear()).ToEqual(365); + }); + }); diff --git a/Tests/Reflection/MacroReflection.spec.cpp b/Tests/Reflection/MacroReflection.spec.cpp index 76591804..9bd56fec 100644 --- a/Tests/Reflection/MacroReflection.spec.cpp +++ b/Tests/Reflection/MacroReflection.spec.cpp @@ -20,28 +20,20 @@ struct TestStruct }; -namespace -{ -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() -{ Spec("Reflection.Macros", []() { - It("Can get property names", []() - { - p::TypeId testStructType = p::RegisterTypeId(); +It("Can get property names", []() +{ + p::TypeId testStructType = p::RegisterTypeId(); - Expect(p::HasTypeFlags(testStructType, p::TF_Struct)).ToEqual(true); + Expect(p::HasTypeFlags(testStructType, p::TF_Struct)).ToEqual(true); - auto properties = p::GetTypeProperties(testStructType); - Expect(properties.Size()).ToEqual(2); + auto properties = p::GetTypeProperties(testStructType); + Expect(properties.Size()).ToEqual(2); - // Expect(properties[0].typeId).ToEqual(p::GetTypeId>()); - Expect(properties[0]->name.Data()).ToEqual("value0"); - // Expect(properties[1].typeId).ToEqual(p::GetTypeId()); - Expect(properties[1]->name.Data()).ToEqual("value1"); - }); + // Expect(properties[0].typeId).ToEqual(p::GetTypeId>()); + Expect(properties[0]->name.Data()).ToEqual("value0"); + // Expect(properties[1].typeId).ToEqual(p::GetTypeId()); + Expect(properties[1]->name.Data()).ToEqual("value1"); +}); }); -return true; -}(); -} // namespace diff --git a/Tests/Reflection/Object.spec.cpp b/Tests/Reflection/Object.spec.cpp index b893c741..f36da935 100644 --- a/Tests/Reflection/Object.spec.cpp +++ b/Tests/Reflection/Object.spec.cpp @@ -22,33 +22,25 @@ class TestObject : public p::Object }; -namespace -{ -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() -{ Spec("Reflection.Object", []() { - Describe("Pointers", []() +Describe("Pointers", []() +{ + It("Can create object", []() { - It("Can create object", []() - { - auto owner = p::MakeOwned(); - - Expect(owner.Get()).ToNotEqual(nullptr); - Expect(owner->bConstructed).ToEqual(true); - }); - - It("Can create object with owner", []() - { - auto owner = p::MakeOwned(); - auto owner2 = p::MakeOwned(owner); - - Expect(owner2->bConstructed).ToEqual(true); - Expect(owner2->GetOwner().Get()).ToEqual(owner.Get()); - }); + auto owner = p::MakeOwned(); + + Expect(owner.Get()).ToNotEqual(nullptr); + Expect(owner->bConstructed).ToEqual(true); }); + + It("Can create object with owner", []() + { + auto owner = p::MakeOwned(); + auto owner2 = p::MakeOwned(owner); + + Expect(owner2->bConstructed).ToEqual(true); + Expect(owner2->GetOwner().Get()).ToEqual(owner.Get()); + }); +}); }); -return true; -}(); -} // namespace diff --git a/Tests/Reflection/Traits.spec.cpp b/Tests/Reflection/Traits.spec.cpp index 60d8d4d1..8a461c95 100644 --- a/Tests/Reflection/Traits.spec.cpp +++ b/Tests/Reflection/Traits.spec.cpp @@ -39,72 +39,64 @@ namespace p } // namespace p -namespace -{ -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() -{ Spec("Reflection.Traits", []() { - Describe("Read/Write properties", []() +Describe("Read/Write properties", []() +{ + It("Can check for read properties", []() { - It("Can check for read properties", []() - { - Expect(p::HasReadProperties()).ToBeFalse(); - Expect(p::HasReadProperties()).ToBeTrue(); - Expect(p::Readable).ToBeFalse(); - Expect(p::Readable).ToBeTrue(); - }); - - It("Can check for write properties", []() - { - Expect(p::HasWriteProperties()).ToBeFalse(); - Expect(p::HasWriteProperties()).ToBeTrue(); - Expect(p::Writable).ToBeFalse(); - Expect(p::Writable).ToBeTrue(); - }); + Expect(p::HasReadProperties()).ToBeFalse(); + Expect(p::HasReadProperties()).ToBeTrue(); + Expect(p::Readable).ToBeFalse(); + Expect(p::Readable).ToBeTrue(); }); - Describe("Read/Write external", []() + It("Can check for write properties", []() { - It("Can check for read properties", []() - { - Expect(p::Readable).ToBeFalse(); - Expect(p::Readable).ToBeTrue(); - }); - - It("Can check for write properties", []() - { - Expect(p::Writable).ToBeFalse(); - Expect(p::Writable).ToBeTrue(); - }); + Expect(p::HasWriteProperties()).ToBeFalse(); + Expect(p::HasWriteProperties()).ToBeTrue(); + Expect(p::Writable).ToBeFalse(); + Expect(p::Writable).ToBeTrue(); }); +}); - Describe("Read/Write external in namespace", []() +Describe("Read/Write external", []() +{ + It("Can check for read properties", []() { - It("Can check for read properties", []() - { - Expect(p::Readable).ToBeTrue(); - }); - - It("Can check for write properties", []() - { - Expect(p::Writable).ToBeTrue(); - }); + Expect(p::Readable).ToBeFalse(); + Expect(p::Readable).ToBeTrue(); }); - It("Can check super", []() + It("Can check for write properties", []() { - Expect(p::HasSuper()).ToBeFalse(); - Expect(p::HasSuper()).ToBeTrue(); + Expect(p::Writable).ToBeFalse(); + Expect(p::Writable).ToBeTrue(); }); +}); - It("Can build type on Arrays", []() +Describe("Read/Write external in namespace", []() +{ + It("Can check for read properties", []() { - Expect(p::CanBuildType>()).ToBeTrue(); - Expect(p::HasExternalBuildType>()).ToBeTrue(); + Expect(p::Readable).ToBeTrue(); }); + + It("Can check for write properties", []() + { + Expect(p::Writable).ToBeTrue(); + }); +}); + +It("Can check super", []() +{ + Expect(p::HasSuper()).ToBeFalse(); + Expect(p::HasSuper()).ToBeTrue(); +}); + +It("Can build type on Arrays", []() +{ + Expect(p::CanBuildType>()).ToBeTrue(); + Expect(p::HasExternalBuildType>()).ToBeTrue(); +}); }); -return true; -}(); -} // namespace diff --git a/Tests/Reflection/TypeId.spec.cpp b/Tests/Reflection/TypeId.spec.cpp index e3dde3b6..ae309210 100644 --- a/Tests/Reflection/TypeId.spec.cpp +++ b/Tests/Reflection/TypeId.spec.cpp @@ -10,38 +10,30 @@ struct One {}; -namespace -{ -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() -{ Spec("Reflection.TypeId", []() { - It("Ids can be valid and invalid", []() - { - static constexpr TypeId id = GetTypeId(); - Expect(id.IsValid()).ToEqual(true); +It("Ids can be valid and invalid", []() +{ + static constexpr TypeId id = GetTypeId(); + Expect(id.IsValid()).ToEqual(true); - static constexpr TypeId noId{}; - Expect(noId.IsValid()).ToEqual(false); - }); + static constexpr TypeId noId{}; + Expect(noId.IsValid()).ToEqual(false); +}); - It("Different types don't share an id", []() - { - static constexpr TypeId ids[]{ - GetTypeId(), GetTypeId(), GetTypeId(), GetTypeId()}; - static constexpr u32 numIds = sizeof(ids) / sizeof(TypeId); +It("Different types don't share an id", []() +{ + static constexpr TypeId ids[]{ + GetTypeId(), GetTypeId(), GetTypeId(), GetTypeId()}; + static constexpr u32 numIds = sizeof(ids) / sizeof(TypeId); - // Check that no id matches the other - for (u32 i = 0; i < numIds; ++i) + // Check that no id matches the other + for (u32 i = 0; i < numIds; ++i) + { + for (u32 e = i + 1; e < numIds; ++e) { - for (u32 e = i + 1; e < numIds; ++e) - { - Expect(ids[i]).ToNotEqual(ids[e]); - } + Expect(ids[i]).ToNotEqual(ids[e]); } - }); + } +}); }); -return true; -}(); -} // namespace diff --git a/Tests/Reflection/TypeName.spec.cpp b/Tests/Reflection/TypeName.spec.cpp index 27545896..d75ab58c 100644 --- a/Tests/Reflection/TypeName.spec.cpp +++ b/Tests/Reflection/TypeName.spec.cpp @@ -25,75 +25,67 @@ namespace Space } // namespace Space -namespace +Spec("Reflection.TypeName", []() { -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() +It("Can get Platform type names", []() { -Spec("Reflection.TypeName", []() + Expect(GetTypeName()).ToEqual("u8"); + Expect(GetTypeName()).ToEqual("u16"); + Expect(GetTypeName()).ToEqual("u32"); + Expect(GetTypeName()).ToEqual("u64"); + Expect(GetTypeName()).ToEqual("i8"); + Expect(GetTypeName()).ToEqual("i16"); + Expect(GetTypeName()).ToEqual("i32"); + Expect(GetTypeName()).ToEqual("i64"); + Expect(GetTypeName()).ToEqual("char"); + Expect(GetTypeName()).ToEqual("StringView"); + Expect(GetTypeName()).ToEqual("String"); +}); + +It("Can get Native type names", []() { - It("Can get Platform type names", []() - { - Expect(GetTypeName()).ToEqual("u8"); - Expect(GetTypeName()).ToEqual("u16"); - Expect(GetTypeName()).ToEqual("u32"); - Expect(GetTypeName()).ToEqual("u64"); - Expect(GetTypeName()).ToEqual("i8"); - Expect(GetTypeName()).ToEqual("i16"); - Expect(GetTypeName()).ToEqual("i32"); - Expect(GetTypeName()).ToEqual("i64"); - Expect(GetTypeName()).ToEqual("char"); - Expect(GetTypeName()).ToEqual("StringView"); - Expect(GetTypeName()).ToEqual("String"); - }); + Expect(GetTypeName()).ToEqual("bool"); + Expect(GetTypeName()).ToEqual("float"); + Expect(GetTypeName()).ToEqual("double"); +}); - It("Can get Native type names", []() - { - Expect(GetTypeName()).ToEqual("bool"); - Expect(GetTypeName()).ToEqual("float"); - Expect(GetTypeName()).ToEqual("double"); - }); +It("Can get Class names", []() +{ + Expect(GetTypeName()).ToEqual("AClass"); +}); - It("Can get Class names", []() - { - Expect(GetTypeName()).ToEqual("AClass"); - }); +It("Can get Struct names", []() +{ + Expect(GetTypeName()).ToEqual("AnStruct"); +}); - It("Can get Struct names", []() - { - Expect(GetTypeName()).ToEqual("AnStruct"); - }); +It("Can get names with namespaces", []() +{ + Expect(GetTypeName()).ToEqual("Space::Other"); +}); - It("Can get names with namespaces", []() +Describe("Containers", []() +{ + It("Can get TArray names", []() { - Expect(GetTypeName()).ToEqual("Space::Other"); + Expect(GetTypeName>()).ToEqual("TArray"); + Expect(GetFullTypeName>()).ToEqual("TArray"); + Expect(GetFullTypeName>(false)).ToEqual("TArray"); }); - Describe("Containers", []() + It("Can get TMap names", []() { - It("Can get TArray names", []() - { - Expect(GetTypeName>()).ToEqual("TArray"); - Expect(GetFullTypeName>()).ToEqual("TArray"); - Expect(GetFullTypeName>(false)).ToEqual("TArray"); - }); - - It("Can get TMap names", []() - { - auto name = GetTypeName>(); - Expect(name).ToEqual("TMap"); - - auto fullName = GetFullTypeName>(); - Expect(fullName).ToEqual("TMap"); - - - auto namespaceName = GetFullTypeName>(); - Expect(namespaceName).ToEqual("TMap"); - auto noNamespaceName = GetFullTypeName>(false); - Expect(noNamespaceName).ToEqual("TMap"); - }); + auto name = GetTypeName>(); + Expect(name).ToEqual("TMap"); + + auto fullName = GetFullTypeName>(); + Expect(fullName).ToEqual("TMap"); + + + auto namespaceName = GetFullTypeName>(); + Expect(namespaceName).ToEqual("TMap"); + auto noNamespaceName = GetFullTypeName>(false); + Expect(noNamespaceName).ToEqual("TMap"); }); }); -return true; -}(); -} // namespace +}); diff --git a/Tests/Serialization/Binary.spec.cpp b/Tests/Serialization/Binary.spec.cpp index 477e1dd8..9d401371 100644 --- a/Tests/Serialization/Binary.spec.cpp +++ b/Tests/Serialization/Binary.spec.cpp @@ -7,417 +7,409 @@ using namespace p; -namespace -{ -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() -{ Spec("Serialization.Binary", []() { - Describe("Reader", []() +Describe("Reader", []() +{ + It("Can create a reader", []() + { + BinaryFormatReader reader{TArray{}}; + Expect(reader.IsValid()).ToEqual(false); + + BinaryFormatReader reader2{TArray{255}}; + Expect(reader2.IsValid()).ToEqual(true); + }); + + It("Can read from object value", []() + { + TArray data{255}; + BinaryFormatReader reader{data}; + Reader ct = reader; + ct.BeginObject(); + u8 value = 0; + ct.Next(value); + Expect(value).ToEqual(255); + }); + + It("Can read from array values", []() { - It("Can create a reader", []() + TArray data{1, 0, 0, 0, 255}; + BinaryFormatReader reader{data}; + Reader ct = reader; + u32 size = 0; + ct.BeginArray(size); + Expect(size).ToEqual(1); + u8 value = 0; + ct.Next(value); + Expect(value).ToEqual(255); + }); + + It("Can iterate arrays", []() + { + TArray data{2, 0, 0, 0, // Array size of 2 + 6, 0, 0, 0, // size 6 + 'M', 'i', 'g', 'u', 'e', 'l', // + 4, 0, 0, 0, // size 4 + 'J', 'u', 'a', 'n'}; + BinaryFormatReader reader{data}; + + Reader& ct = reader; + ct.BeginObject(); + if (ct.EnterNext("players")) { - BinaryFormatReader reader{TArray{}}; - Expect(reader.IsValid()).ToEqual(false); + static const StringView expected[]{"Miguel", "Juan"}; + u32 size; + ct.BeginArray(size); + for (u32 i = 0; i < size; ++i) + { + StringView name; + ct.Next(name); + Expect(name).ToEqual(expected[i]); + } + ct.Leave(); + } + }); - BinaryFormatReader reader2{TArray{255}}; - Expect(reader2.IsValid()).ToEqual(true); + Describe("Types", []() + { + It("Can read bool values", []() + { + TArray data{1, 0}; + BinaryFormatReader reader{data}; + Reader& ct = reader; + ct.BeginObject(); + bool value = false; + ct.Next("a", value); + Expect(value).ToEqual(true); + ct.Next("b", value); + Expect(value).ToEqual(false); }); - It("Can read from object value", []() + It("Can read i8 values", []() { - TArray data{255}; + TArray data{0, 127, 128}; BinaryFormatReader reader{data}; - Reader ct = reader; + Reader& ct = reader; + ct.BeginObject(); + i8 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(0); + ct.Next("b", value); + Expect(value).ToEqual(127); + ct.Next("b", value); + Expect(value).ToEqual(-128); + }); + + It("Can read u8 values", []() + { + TArray data{0, 255}; + BinaryFormatReader reader{data}; + Reader& ct = reader; ct.BeginObject(); u8 value = 0; - ct.Next(value); + ct.Next("a", value); + Expect(value).ToEqual(0); + ct.Next("b", value); Expect(value).ToEqual(255); }); - It("Can read from array values", []() + It("Can read i16 values", []() { - TArray data{1, 0, 0, 0, 255}; + // Test inbounds and out of bounds values + TArray data{0, 0, 0, 128, 255, 127}; BinaryFormatReader reader{data}; Reader ct = reader; - u32 size = 0; - ct.BeginArray(size); - Expect(size).ToEqual(1); - u8 value = 0; - ct.Next(value); - Expect(value).ToEqual(255); + ct.BeginObject(); + i16 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(0); + ct.Next("b", value); + Expect(value).ToEqual(Limits::Lowest()); + ct.Next("c", value); + Expect(value).ToEqual(Limits::Max()); }); - It("Can iterate arrays", []() + It("Can read u16 values", []() { - TArray data{2, 0, 0, 0, // Array size of 2 - 6, 0, 0, 0, // size 6 - 'M', 'i', 'g', 'u', 'e', 'l', // - 4, 0, 0, 0, // size 4 - 'J', 'u', 'a', 'n'}; + // Test inbounds and out of bounds values + TArray data{0, 0, 255, 255}; BinaryFormatReader reader{data}; + Reader ct = reader; + ct.BeginObject(); + u16 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(0); + ct.Next("b", value); + Expect(value).ToEqual(Limits::Max()); + }); - Reader& ct = reader; + It("Can read i32 values", []() + { + // Test inbounds and out of bounds values + TArray data{0, 0, 0, 0, 0, 0, 0, 128, 255, 255, 255, 127}; + BinaryFormatReader reader{data}; + Reader ct = reader; ct.BeginObject(); - if (ct.EnterNext("players")) - { - static const StringView expected[]{"Miguel", "Juan"}; - u32 size; - ct.BeginArray(size); - for (u32 i = 0; i < size; ++i) - { - StringView name; - ct.Next(name); - Expect(name).ToEqual(expected[i]); - } - ct.Leave(); - } + i32 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(0); + ct.Next("b", value); + Expect(value).ToEqual(Limits::Lowest()); + ct.Next("c", value); + Expect(value).ToEqual(Limits::Max()); }); - Describe("Types", []() + It("Can read u32 values", []() { - It("Can read bool values", []() - { - TArray data{1, 0}; - BinaryFormatReader reader{data}; - Reader& ct = reader; - ct.BeginObject(); - bool value = false; - ct.Next("a", value); - Expect(value).ToEqual(true); - ct.Next("b", value); - Expect(value).ToEqual(false); - }); - - It("Can read i8 values", []() - { - TArray data{0, 127, 128}; - BinaryFormatReader reader{data}; - Reader& ct = reader; - ct.BeginObject(); - i8 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(0); - ct.Next("b", value); - Expect(value).ToEqual(127); - ct.Next("b", value); - Expect(value).ToEqual(-128); - }); - - It("Can read u8 values", []() - { - TArray data{0, 255}; - BinaryFormatReader reader{data}; - Reader& ct = reader; - ct.BeginObject(); - u8 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(0); - ct.Next("b", value); - Expect(value).ToEqual(255); - }); - - It("Can read i16 values", []() - { - // Test inbounds and out of bounds values - TArray data{0, 0, 0, 128, 255, 127}; - BinaryFormatReader reader{data}; - Reader ct = reader; - ct.BeginObject(); - i16 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(0); - ct.Next("b", value); - Expect(value).ToEqual(Limits::Lowest()); - ct.Next("c", value); - Expect(value).ToEqual(Limits::Max()); - }); - - It("Can read u16 values", []() - { - // Test inbounds and out of bounds values - TArray data{0, 0, 255, 255}; - BinaryFormatReader reader{data}; - Reader ct = reader; - ct.BeginObject(); - u16 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(0); - ct.Next("b", value); - Expect(value).ToEqual(Limits::Max()); - }); - - It("Can read i32 values", []() - { - // Test inbounds and out of bounds values - TArray data{0, 0, 0, 0, 0, 0, 0, 128, 255, 255, 255, 127}; - BinaryFormatReader reader{data}; - Reader ct = reader; - ct.BeginObject(); - i32 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(0); - ct.Next("b", value); - Expect(value).ToEqual(Limits::Lowest()); - ct.Next("c", value); - Expect(value).ToEqual(Limits::Max()); - }); - - It("Can read u32 values", []() - { - // Test inbounds and out of bounds values - TArray data{0, 0, 0, 0, 255, 255, 255, 255}; - BinaryFormatReader reader{data}; - Reader ct = reader; - ct.BeginObject(); - u32 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(0); - ct.Next("b", value); - Expect(value).ToEqual(Limits::Max()); - }); - - It("Can read i64 values", []() - { - // Test inbounds and out of bounds values - TArray data{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 255, 255, 255, - 255, 255, 255, 255, 127}; - BinaryFormatReader reader{data}; - Reader ct = reader; - ct.BeginObject(); - i64 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(0); - ct.Next("b", value); - Expect(value).ToEqual(Limits::Lowest()); - ct.Next("c", value); - Expect(value).ToEqual(Limits::Max()); - }); - - It("Can read u64 values", []() - { - // Test inbounds and out of bounds values - TArray data{0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255}; - BinaryFormatReader reader{data}; - Reader ct = reader; - ct.BeginObject(); - u64 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(0); - ct.Next("b", value); - Expect(value).ToEqual(Limits::Max()); - }); - - It("Can read float values", []() - { - TArray data{51, 51, 179, 191, 0, 0, 96, 64}; - BinaryFormatReader reader{data}; - Reader ct = reader; - ct.BeginObject(); - float value = 0.f; - ct.Next("a", value); - Expect(value).ToEqual(-1.4f); - ct.Next("b", value); - Expect(value).ToEqual(3.5f); - }); - - It("Can read double values", []() - { - TArray data{ - 102, 102, 102, 102, 102, 102, 246, 191, 0, 0, 0, 0, 0, 0, 12, 64}; - BinaryFormatReader reader{data}; - Reader ct = reader; - ct.BeginObject(); - double value = 0; - ct.Next("a", value); - Expect(value).ToEqual(-1.4); - ct.Next("b", value); - Expect(value).ToEqual(3.5); - }); - - It("Can read StringView values", []() - { - TArray data{3, 0, 0, 0, 'y', 'e', 's'}; - BinaryFormatReader reader{data}; - Reader ct = reader; - ct.BeginObject(); - StringView string; - ct.Next("a", string); - Expect(string).ToEqual("yes"); - }); + // Test inbounds and out of bounds values + TArray data{0, 0, 0, 0, 255, 255, 255, 255}; + BinaryFormatReader reader{data}; + Reader ct = reader; + ct.BeginObject(); + u32 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(0); + ct.Next("b", value); + Expect(value).ToEqual(Limits::Max()); + }); + + It("Can read i64 values", []() + { + // Test inbounds and out of bounds values + TArray data{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 255, 255, 255, + 255, 255, 255, 255, 127}; + BinaryFormatReader reader{data}; + Reader ct = reader; + ct.BeginObject(); + i64 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(0); + ct.Next("b", value); + Expect(value).ToEqual(Limits::Lowest()); + ct.Next("c", value); + Expect(value).ToEqual(Limits::Max()); + }); + + It("Can read u64 values", []() + { + // Test inbounds and out of bounds values + TArray data{0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255}; + BinaryFormatReader reader{data}; + Reader ct = reader; + ct.BeginObject(); + u64 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(0); + ct.Next("b", value); + Expect(value).ToEqual(Limits::Max()); + }); + + It("Can read float values", []() + { + TArray data{51, 51, 179, 191, 0, 0, 96, 64}; + BinaryFormatReader reader{data}; + Reader ct = reader; + ct.BeginObject(); + float value = 0.f; + ct.Next("a", value); + Expect(value).ToEqual(-1.4f); + ct.Next("b", value); + Expect(value).ToEqual(3.5f); + }); + + It("Can read double values", []() + { + TArray data{ + 102, 102, 102, 102, 102, 102, 246, 191, 0, 0, 0, 0, 0, 0, 12, 64}; + BinaryFormatReader reader{data}; + Reader ct = reader; + ct.BeginObject(); + double value = 0; + ct.Next("a", value); + Expect(value).ToEqual(-1.4); + ct.Next("b", value); + Expect(value).ToEqual(3.5); + }); + + It("Can read StringView values", []() + { + TArray data{3, 0, 0, 0, 'y', 'e', 's'}; + BinaryFormatReader reader{data}; + Reader ct = reader; + ct.BeginObject(); + StringView string; + ct.Next("a", string); + Expect(string).ToEqual("yes"); }); }); +}); + +Describe("Writer", []() +{ + It("Can create a writer", []() + { + BinaryFormatWriter writer{}; + Expect(writer.IsValid()).ToEqual(true); + }); - Describe("Writer", []() + It("Can write to object key", []() { - It("Can create a writer", []() + BinaryFormatWriter writer{}; + Writer& ct = writer; + ct.BeginObject(); + ct.Next("name", StringView{"Miguel"}); + + TArray expected{6, 0, 0, 0, 'M', 'i', 'g', 'u', 'e', 'l'}; + Expect(writer.GetData()).ToEqual(TView{expected}); + }); + + It("Can write arrays", []() + { + BinaryFormatWriter writer{}; + Writer& ct = writer; + ct.BeginArray(2); + ct.Next(u8(255)); + ct.Next(u8(255)); + + TArray expected{2, 0, 0, 0, 255, 255}; + Expect(writer.GetData()).ToEqual(TView{expected}); + }); + + Describe("Types", []() + { + It("Can write bool values", []() { BinaryFormatWriter writer{}; - Expect(writer.IsValid()).ToEqual(true); + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", true); + ct.Next("b", false); + TArray expected{1, 0}; + Expect(writer.GetData()).ToEqual(TView(expected)); }); - It("Can write to object key", []() + It("Can write i8 values", []() { BinaryFormatWriter writer{}; - Writer& ct = writer; + Writer ct = writer; ct.BeginObject(); - ct.Next("name", StringView{"Miguel"}); + ct.Next("a", i8(127)); + ct.Next("b", i8(-128)); + TArray expected{127, 128}; + Expect(writer.GetData()).ToEqual(TView(expected)); + }); - TArray expected{6, 0, 0, 0, 'M', 'i', 'g', 'u', 'e', 'l'}; - Expect(writer.GetData()).ToEqual(TView{expected}); + It("Can write u8 values", []() + { + BinaryFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", u8(0)); + ct.Next("b", u8(255)); + TArray expected{0, 255}; + Expect(writer.GetData()).ToEqual(TView(expected)); }); - It("Can write arrays", []() + It("Can write i16 values", []() { BinaryFormatWriter writer{}; - Writer& ct = writer; - ct.BeginArray(2); - ct.Next(u8(255)); - ct.Next(u8(255)); + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", Limits::Max()); + ct.Next("b", Limits::Lowest()); + TArray expected{255, 127, 0, 128}; + Expect(writer.GetData()).ToEqual(TView(expected)); + }); - TArray expected{2, 0, 0, 0, 255, 255}; - Expect(writer.GetData()).ToEqual(TView{expected}); + It("Can write u16 values", []() + { + BinaryFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", Limits::Max()); + ct.Next("b", Limits::Lowest()); + TArray expected{255, 255, 0, 0}; + Expect(writer.GetData()).ToEqual(TView(expected)); }); - Describe("Types", []() + It("Can write i32 values", []() { - It("Can write bool values", []() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", true); - ct.Next("b", false); - TArray expected{1, 0}; - Expect(writer.GetData()).ToEqual(TView(expected)); - }); - - It("Can write i8 values", []() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", i8(127)); - ct.Next("b", i8(-128)); - TArray expected{127, 128}; - Expect(writer.GetData()).ToEqual(TView(expected)); - }); - - It("Can write u8 values", []() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", u8(0)); - ct.Next("b", u8(255)); - TArray expected{0, 255}; - Expect(writer.GetData()).ToEqual(TView(expected)); - }); - - It("Can write i16 values", []() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", Limits::Max()); - ct.Next("b", Limits::Lowest()); - TArray expected{255, 127, 0, 128}; - Expect(writer.GetData()).ToEqual(TView(expected)); - }); - - It("Can write u16 values", []() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", Limits::Max()); - ct.Next("b", Limits::Lowest()); - TArray expected{255, 255, 0, 0}; - Expect(writer.GetData()).ToEqual(TView(expected)); - }); - - It("Can write i32 values", []() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", Limits::Max()); - ct.Next("b", Limits::Lowest()); - TArray expected{255, 255, 255, 127, 0, 0, 0, 128}; - Expect(writer.GetData()).ToEqual(TView(expected)); - }); - - It("Can write u32 values", []() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", Limits::Max()); - ct.Next("b", Limits::Lowest()); - TArray expected{255, 255, 255, 255, 0, 0, 0, 0}; - Expect(writer.GetData()).ToEqual(TView(expected)); - }); - - It("Can write i64 values", []() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", Limits::Max()); - ct.Next("b", Limits::Lowest()); - TArray expected{ - 255, 255, 255, 255, 255, 255, 255, 127, 0, 0, 0, 0, 0, 0, 0, 128}; - Expect(writer.GetData()).ToEqual(TView(expected)); - }); - - It("Can write u64 values", []() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", Limits::Max()); - ct.Next("b", Limits::Lowest()); - TArray expected{ - 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}; - Expect(writer.GetData()).ToEqual(TView(expected)); - }); - - It("Can write float values", []() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", -1.4f); - ct.Next("b", 3.5f); - TArray expected{51, 51, 179, 191, 0, 0, 96, 64}; - Expect(writer.GetData()).ToEqual(TView(expected)); - }); - - It("Can write double values", []() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", -1.4); - ct.Next("b", 3.5); - TArray expected{ - 102, 102, 102, 102, 102, 102, 246, 191, 0, 0, 0, 0, 0, 0, 12, 64}; - Expect(writer.GetData()).ToEqual(TView(expected)); - }); - - It("Can write StringView values", []() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", StringView{"yes"}); - TArray expected{3, 0, 0, 0, 'y', 'e', 's'}; - Expect(writer.GetData()).ToEqual(TView(expected)); - }); + BinaryFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", Limits::Max()); + ct.Next("b", Limits::Lowest()); + TArray expected{255, 255, 255, 127, 0, 0, 0, 128}; + Expect(writer.GetData()).ToEqual(TView(expected)); + }); + + It("Can write u32 values", []() + { + BinaryFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", Limits::Max()); + ct.Next("b", Limits::Lowest()); + TArray expected{255, 255, 255, 255, 0, 0, 0, 0}; + Expect(writer.GetData()).ToEqual(TView(expected)); + }); + + It("Can write i64 values", []() + { + BinaryFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", Limits::Max()); + ct.Next("b", Limits::Lowest()); + TArray expected{ + 255, 255, 255, 255, 255, 255, 255, 127, 0, 0, 0, 0, 0, 0, 0, 128}; + Expect(writer.GetData()).ToEqual(TView(expected)); + }); + + It("Can write u64 values", []() + { + BinaryFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", Limits::Max()); + ct.Next("b", Limits::Lowest()); + TArray expected{ + 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}; + Expect(writer.GetData()).ToEqual(TView(expected)); + }); + + It("Can write float values", []() + { + BinaryFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", -1.4f); + ct.Next("b", 3.5f); + TArray expected{51, 51, 179, 191, 0, 0, 96, 64}; + Expect(writer.GetData()).ToEqual(TView(expected)); + }); + + It("Can write double values", []() + { + BinaryFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", -1.4); + ct.Next("b", 3.5); + TArray expected{ + 102, 102, 102, 102, 102, 102, 246, 191, 0, 0, 0, 0, 0, 0, 12, 64}; + Expect(writer.GetData()).ToEqual(TView(expected)); + }); + + It("Can write StringView values", []() + { + BinaryFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", StringView{"yes"}); + TArray expected{3, 0, 0, 0, 'y', 'e', 's'}; + Expect(writer.GetData()).ToEqual(TView(expected)); }); }); }); -return true; -}(); -} // namespace +}); diff --git a/Tests/Serialization/Json.spec.cpp b/Tests/Serialization/Json.spec.cpp index b56d6e17..e175a41a 100644 --- a/Tests/Serialization/Json.spec.cpp +++ b/Tests/Serialization/Json.spec.cpp @@ -7,432 +7,424 @@ using namespace p; -namespace -{ -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() -{ Spec("Serialization.Json", []() { - Describe("Reader", []() +Describe("Reader", []() +{ + It("Can create a reader", []() + { + JsonFormatReader reader{"{}"}; + Expect(reader.IsValid()).ToBeTrue(); + }); + + It("Can read from object value", []() + { + String data{"{\"name\": \"Miguel\"}"}; + JsonFormatReader reader{data}; + + Reader& ct = reader; + ct.BeginObject(); + String name; + ct.Next("name", name); + + Expect(name.data()).ToEqual("Miguel"); + }); + + It("Can read from array values", []() { - It("Can create a reader", []() + String data{"{\"players\": [\"Miguel\", \"Juan\"]}"}; + JsonFormatReader reader{data}; + + Reader& ct = reader; + ct.BeginObject(); + if (ct.EnterNext("players")) { - JsonFormatReader reader{"{}"}; - Expect(reader.IsValid()).ToBeTrue(); - }); + u32 size; + ct.BeginArray(size); + String name; + ct.Next(name); + Expect(name.data()).ToEqual("Miguel"); + + ct.Next(name); + Expect(name.data()).ToEqual("Juan"); + + ct.Leave(); + } + }); - It("Can read from object value", []() + It("Can iterate arrays", []() + { + String data{"{\"players\": [\"Miguel\", \"Juan\"]}"}; + JsonFormatReader reader{data}; + + Reader& ct = reader; + ct.BeginObject(); + if (ct.EnterNext("players")) { - String data{"{\"name\": \"Miguel\"}"}; - JsonFormatReader reader{data}; + static const StringView expected[]{"Miguel", "Juan"}; + u32 size; + ct.BeginArray(size); + for (u32 i = 0; i < size; ++i) + { + StringView name; + ct.Next(name); + Expect(name).ToEqual(expected[i]); + } + ct.Leave(); + } + }); + + It("Can check types", []() + { + String data{"{\"players\": [\"Miguel\", \"Juan\"]}"}; + JsonFormatReader reader{data}; + + Reader& ct = reader; + Expect(reader.IsObject()).ToEqual(true); + ct.BeginObject(); + if (ct.EnterNext("players")) + { + Expect(reader.IsArray()).ToEqual(true); + ct.Leave(); + } + }); + + It("Can find multiple keys", []() + { + String data{"{\"one\": \"Miguel\", \"other\": \"Juan\"}"}; + JsonFormatReader reader{data}; + + Reader& ct = reader; + Expect(reader.IsObject()).ToEqual(true); + ct.BeginObject(); + StringView name; + ct.Next("one", name); + Expect(name).ToEqual("Miguel"); + + ct.Next("other", name); + Expect(name).ToEqual("Juan"); + }); + + It("Can find multiple unordered keys", []() + { + String data{"{\"one\": \"Miguel\", \"other\": \"Juan\"}"}; + JsonFormatReader reader{data}; + + Reader& ct = reader; + Expect(reader.IsObject()).ToEqual(true); + ct.BeginObject(); + StringView name; + ct.Next("other", name); + Expect(name).ToEqual("Juan"); + + ct.Next("one", name); + Expect(name).ToEqual("Miguel"); + }); + Describe("Types", []() + { + It("Can read bool values", []() + { + JsonFormatReader reader{"{\"alive\": true}"}; Reader& ct = reader; ct.BeginObject(); - String name; - ct.Next("name", name); + bool value = false; + ct.Next("alive", value); + Expect(value).ToEqual(true); - Expect(name.data()).ToEqual("Miguel"); + JsonFormatReader reader2{"{\"alive\": false}"}; + ct = reader2; + ct.BeginObject(); + bool value2 = true; + ct.Next("alive", value2); + Expect(value2).ToEqual(false); }); - It("Can read from array values", []() + It("Can read i8 values", []() { - String data{"{\"players\": [\"Miguel\", \"Juan\"]}"}; - JsonFormatReader reader{data}; - + JsonFormatReader reader{"{\"alive\": -3}"}; Reader& ct = reader; ct.BeginObject(); - if (ct.EnterNext("players")) - { - u32 size; - ct.BeginArray(size); - String name; - ct.Next(name); - Expect(name.data()).ToEqual("Miguel"); + i8 value = 0; + ct.Next("alive", value); + Expect(value).ToEqual(-3); - ct.Next(name); - Expect(name.data()).ToEqual("Juan"); - - ct.Leave(); - } + JsonFormatReader reader2{"{\"alive\": -1.344}"}; + ct = reader2; + ct.BeginObject(); + i8 value2 = 0; + ct.Next("alive", value2); + Expect(value2).ToEqual(-1); }); - It("Can iterate arrays", []() + It("Can read u8 values", []() { - String data{"{\"players\": [\"Miguel\", \"Juan\"]}"}; - JsonFormatReader reader{data}; - + JsonFormatReader reader{"{\"alive\": 3}"}; Reader& ct = reader; ct.BeginObject(); - if (ct.EnterNext("players")) - { - static const StringView expected[]{"Miguel", "Juan"}; - u32 size; - ct.BeginArray(size); - for (u32 i = 0; i < size; ++i) - { - StringView name; - ct.Next(name); - Expect(name).ToEqual(expected[i]); - } - ct.Leave(); - } + u8 value = 0; + ct.Next("alive", value); + Expect(value).ToEqual(3); + + JsonFormatReader reader2{"{\"alive\": 1.344}"}; + ct = reader2; + ct.BeginObject(); + u8 value2 = 0; + ct.Next("alive", value2); + Expect(value2).ToEqual(1); }); - It("Can check types", []() + It("Can read i16 values", []() { - String data{"{\"players\": [\"Miguel\", \"Juan\"]}"}; - JsonFormatReader reader{data}; + // Test inbounds and out of bounds values + JsonFormatReader reader{ + Format("{{\"a\":{},\"b\":{},\"c\":{},\"d\":{}}}", Limits::Max(), + Limits::Lowest(), Limits::Max(), Limits::Lowest())}; + Reader ct = reader; + ct.BeginObject(); + i16 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(Limits::Max()); + ct.Next("b", value); + Expect(value).ToEqual(Limits::Lowest()); + ct.Next("c", value); + Expect(value).ToEqual(Limits::Max()); + ct.Next("d", value); + Expect(value).ToEqual(Limits::Lowest()); + }); - Reader& ct = reader; - Expect(reader.IsObject()).ToEqual(true); + It("Can read u16 values", []() + { + JsonFormatReader reader{Format("{{\"a\":{},\"b\":{},\"c\":{}}}", + Limits::Max(), Limits::Lowest(), -32)}; + Reader ct = reader; ct.BeginObject(); - if (ct.EnterNext("players")) - { - Expect(reader.IsArray()).ToEqual(true); - ct.Leave(); - } + u16 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(Limits::Max()); + ct.Next("b", value); + Expect(value).ToEqual(Limits::Lowest()); + ct.Next("c", value); + Expect(value).ToEqual(0); }); - It("Can find multiple keys", []() + It("Can read i32 values", []() { - String data{"{\"one\": \"Miguel\", \"other\": \"Juan\"}"}; - JsonFormatReader reader{data}; + // Test inbounds and out of bounds values + JsonFormatReader reader{ + Format("{{\"a\":{},\"b\":{},\"c\":{},\"d\":{}}}", Limits::Max(), + Limits::Lowest(), Limits::Max(), Limits::Lowest())}; + Reader ct = reader; + ct.BeginObject(); + i32 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(Limits::Max()); + ct.Next("b", value); + Expect(value).ToEqual(Limits::Lowest()); + ct.Next("c", value); + Expect(value).ToEqual(Limits::Max()); + ct.Next("d", value); + Expect(value).ToEqual(Limits::Lowest()); + }); + It("Can read u32 values", []() + { + JsonFormatReader reader{Format("{{\"a\":{},\"b\":{},\"c\":{}}}", + Limits::Max(), Limits::Lowest(), -32)}; + Reader ct = reader; + ct.BeginObject(); + u32 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(Limits::Max()); + ct.Next("b", value); + Expect(value).ToEqual(Limits::Lowest()); + ct.Next("c", value); + Expect(value).ToEqual(0); + }); + + It("Can read float values", []() + { + JsonFormatReader reader{"{\"alive\": 0.344}"}; Reader& ct = reader; - Expect(reader.IsObject()).ToEqual(true); ct.BeginObject(); - StringView name; - ct.Next("one", name); - Expect(name).ToEqual("Miguel"); + float value = 0.f; + ct.Next("alive", value); + Expect(value).ToEqual(0.344f); - ct.Next("other", name); - Expect(name).ToEqual("Juan"); + JsonFormatReader reader2{"{\"alive\": 4}"}; + ct = reader2; + ct.BeginObject(); + float value2 = 0.f; + ct.Next("alive", value2); + Expect(value2).ToEqual(4.f); }); - It("Can find multiple unordered keys", []() + It("Can read StringView values", []() { - String data{"{\"one\": \"Miguel\", \"other\": \"Juan\"}"}; - JsonFormatReader reader{data}; - + JsonFormatReader reader{"{\"alive\": \"yes\"}"}; Reader& ct = reader; - Expect(reader.IsObject()).ToEqual(true); ct.BeginObject(); - StringView name; - ct.Next("other", name); - Expect(name).ToEqual("Juan"); - - ct.Next("one", name); - Expect(name).ToEqual("Miguel"); + StringView value; + ct.Next("alive", value); + Expect(value).ToEqual("yes"); }); + }); +}); + +Describe("Writer", []() +{ + It("Can create a writer", []() + { + JsonFormatWriter writer{}; + Expect(writer.IsValid()).ToEqual(true); + }); + + It("Can write to object key", []() + { + JsonFormatWriter writer{}; + Writer& ct = writer; + ct.BeginObject(); + ct.Next("name", StringView{"Miguel"}); + Expect(writer.ToString(false)).ToEqual("{\"name\":\"Miguel\"}"); + }); + + It("Can write arrays", []() + { + JsonFormatWriter writer{}; - Describe("Types", []() + Writer& ct = writer; + ct.BeginObject(); + if (ct.EnterNext("players")) { - It("Can read bool values", []() - { - JsonFormatReader reader{"{\"alive\": true}"}; - Reader& ct = reader; - ct.BeginObject(); - bool value = false; - ct.Next("alive", value); - Expect(value).ToEqual(true); - - JsonFormatReader reader2{"{\"alive\": false}"}; - ct = reader2; - ct.BeginObject(); - bool value2 = true; - ct.Next("alive", value2); - Expect(value2).ToEqual(false); - }); - - It("Can read i8 values", []() - { - JsonFormatReader reader{"{\"alive\": -3}"}; - Reader& ct = reader; - ct.BeginObject(); - i8 value = 0; - ct.Next("alive", value); - Expect(value).ToEqual(-3); - - JsonFormatReader reader2{"{\"alive\": -1.344}"}; - ct = reader2; - ct.BeginObject(); - i8 value2 = 0; - ct.Next("alive", value2); - Expect(value2).ToEqual(-1); - }); - - It("Can read u8 values", []() - { - JsonFormatReader reader{"{\"alive\": 3}"}; - Reader& ct = reader; - ct.BeginObject(); - u8 value = 0; - ct.Next("alive", value); - Expect(value).ToEqual(3); - - JsonFormatReader reader2{"{\"alive\": 1.344}"}; - ct = reader2; - ct.BeginObject(); - u8 value2 = 0; - ct.Next("alive", value2); - Expect(value2).ToEqual(1); - }); - - It("Can read i16 values", []() - { - // Test inbounds and out of bounds values - JsonFormatReader reader{ - Format("{{\"a\":{},\"b\":{},\"c\":{},\"d\":{}}}", Limits::Max(), - Limits::Lowest(), Limits::Max(), Limits::Lowest())}; - Reader ct = reader; - ct.BeginObject(); - i16 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(Limits::Max()); - ct.Next("b", value); - Expect(value).ToEqual(Limits::Lowest()); - ct.Next("c", value); - Expect(value).ToEqual(Limits::Max()); - ct.Next("d", value); - Expect(value).ToEqual(Limits::Lowest()); - }); - - It("Can read u16 values", []() - { - JsonFormatReader reader{Format("{{\"a\":{},\"b\":{},\"c\":{}}}", - Limits::Max(), Limits::Lowest(), -32)}; - Reader ct = reader; - ct.BeginObject(); - u16 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(Limits::Max()); - ct.Next("b", value); - Expect(value).ToEqual(Limits::Lowest()); - ct.Next("c", value); - Expect(value).ToEqual(0); - }); - - It("Can read i32 values", []() - { - // Test inbounds and out of bounds values - JsonFormatReader reader{ - Format("{{\"a\":{},\"b\":{},\"c\":{},\"d\":{}}}", Limits::Max(), - Limits::Lowest(), Limits::Max(), Limits::Lowest())}; - Reader ct = reader; - ct.BeginObject(); - i32 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(Limits::Max()); - ct.Next("b", value); - Expect(value).ToEqual(Limits::Lowest()); - ct.Next("c", value); - Expect(value).ToEqual(Limits::Max()); - ct.Next("d", value); - Expect(value).ToEqual(Limits::Lowest()); - }); - - It("Can read u32 values", []() - { - JsonFormatReader reader{Format("{{\"a\":{},\"b\":{},\"c\":{}}}", - Limits::Max(), Limits::Lowest(), -32)}; - Reader ct = reader; - ct.BeginObject(); - u32 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(Limits::Max()); - ct.Next("b", value); - Expect(value).ToEqual(Limits::Lowest()); - ct.Next("c", value); - Expect(value).ToEqual(0); - }); - - It("Can read float values", []() - { - JsonFormatReader reader{"{\"alive\": 0.344}"}; - Reader& ct = reader; - ct.BeginObject(); - float value = 0.f; - ct.Next("alive", value); - Expect(value).ToEqual(0.344f); - - JsonFormatReader reader2{"{\"alive\": 4}"}; - ct = reader2; - ct.BeginObject(); - float value2 = 0.f; - ct.Next("alive", value2); - Expect(value2).ToEqual(4.f); - }); - - It("Can read StringView values", []() + static const StringView expected[]{"Miguel", "Juan"}; + u32 size = 2; + ct.BeginArray(size); + for (u32 i = 0; i < size; ++i) { - JsonFormatReader reader{"{\"alive\": \"yes\"}"}; - Reader& ct = reader; - ct.BeginObject(); - StringView value; - ct.Next("alive", value); - Expect(value).ToEqual("yes"); - }); - }); + ct.Next(expected[i]); + } + ct.Leave(); + } + Expect(writer.ToString(false)).ToEqual("{\"players\":[\"Miguel\",\"Juan\"]}"); + }); + + It("Can write multiple object keys", []() + { + JsonFormatWriter writer{}; + Writer& ct = writer; + ct.BeginObject(); + ct.Next("one", StringView{"Miguel"}); + ct.Next("other", StringView{"Juan"}); + Expect( + writer.ToString(false)).ToEqual("{\"one\":\"Miguel\",\"other\":\"Juan\"}"); }); - Describe("Writer", []() + Describe("Types", []() { - It("Can create a writer", []() + It("Can write bool values", []() { JsonFormatWriter writer{}; - Expect(writer.IsValid()).ToEqual(true); + Writer& ct = writer; + ct.BeginObject(); + ct.Next("alive", true); + Expect(writer.ToString(false)).ToEqual("{\"alive\":true}"); + + JsonFormatWriter writer2{}; + ct = writer2; + ct.BeginObject(); + ct.Next("alive", false); + Expect(writer2.ToString(false)).ToEqual("{\"alive\":false}"); }); - It("Can write to object key", []() + It("Can write i8 values", []() { JsonFormatWriter writer{}; - Writer& ct = writer; + Writer ct = writer; ct.BeginObject(); - ct.Next("name", StringView{"Miguel"}); - Expect(writer.ToString(false)).ToEqual("{\"name\":\"Miguel\"}"); + ct.Next("alive", i8(-3)); + Expect(writer.ToString(false)).ToEqual("{\"alive\":-3}"); }); - It("Can write arrays", []() + It("Can write u8 values", []() { JsonFormatWriter writer{}; - - Writer& ct = writer; + Writer ct = writer; ct.BeginObject(); - if (ct.EnterNext("players")) - { - static const StringView expected[]{"Miguel", "Juan"}; - u32 size = 2; - ct.BeginArray(size); - for (u32 i = 0; i < size; ++i) - { - ct.Next(expected[i]); - } - ct.Leave(); - } - Expect(writer.ToString(false)).ToEqual("{\"players\":[\"Miguel\",\"Juan\"]}"); + ct.Next("alive", u8(3)); + Expect(writer.ToString(false)).ToEqual("{\"alive\":3}"); }); - It("Can write multiple object keys", []() + It("Can write i16 values", []() { JsonFormatWriter writer{}; - Writer& ct = writer; + Writer ct = writer; ct.BeginObject(); - ct.Next("one", StringView{"Miguel"}); - ct.Next("other", StringView{"Juan"}); + ct.Next("a", i16(-3000)); + ct.Next("b", Limits::Max()); + ct.Next("c", Limits::Lowest()); Expect( - writer.ToString(false)).ToEqual("{\"one\":\"Miguel\",\"other\":\"Juan\"}"); + writer.ToString(false)).ToEqual("{\"a\":-3000,\"b\":32767,\"c\":-32768}"); }); - Describe("Types", []() + It("Can write u16 values", []() { - It("Can write bool values", []() - { - JsonFormatWriter writer{}; - Writer& ct = writer; - ct.BeginObject(); - ct.Next("alive", true); - Expect(writer.ToString(false)).ToEqual("{\"alive\":true}"); - - JsonFormatWriter writer2{}; - ct = writer2; - ct.BeginObject(); - ct.Next("alive", false); - Expect(writer2.ToString(false)).ToEqual("{\"alive\":false}"); - }); - - It("Can write i8 values", []() - { - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("alive", i8(-3)); - Expect(writer.ToString(false)).ToEqual("{\"alive\":-3}"); - }); - - It("Can write u8 values", []() - { - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("alive", u8(3)); - Expect(writer.ToString(false)).ToEqual("{\"alive\":3}"); - }); - - It("Can write i16 values", []() - { - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", i16(-3000)); - ct.Next("b", Limits::Max()); - ct.Next("c", Limits::Lowest()); - Expect( - writer.ToString(false)).ToEqual("{\"a\":-3000,\"b\":32767,\"c\":-32768}"); - }); - - It("Can write u16 values", []() - { - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", u16(3000)); - ct.Next("b", Limits::Max()); - ct.Next("c", Limits::Lowest()); - Expect(writer.ToString(false)).ToEqual("{\"a\":3000,\"b\":65535,\"c\":0}"); - }); - - It("Can write u32 values", []() - { - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("alive", u32(35533)); - Expect(writer.ToString(false)).ToEqual("{\"alive\":35533}"); - }); - - It("Can write i32 values", []() - { - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - i32 value = 0; - ct.Next("alive", u32(35533)); - Expect(writer.ToString(false)).ToEqual("{\"alive\":35533}"); - - JsonFormatWriter writer2{}; - ct = writer2; - ct.BeginObject(); - ct.Next("alive", i32(-35533)); - Expect(writer2.ToString(false)).ToEqual("{\"alive\":-35533}"); - }); - - It("Can write float values", []() - { - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("alive", 0.344f); - Expect(Strings::Contains(writer.ToString(false), "0.344")).ToEqual(true); - - JsonFormatWriter writer2{}; - ct = writer2; - ct.BeginObject(); - ct.Next("alive", 4.f); - Expect(writer2.ToString(false)).ToEqual("{\"alive\":4.0}"); - }); - - It("Can write StringView values", []() - { - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("alive", StringView{"yes"}); - Expect(writer.ToString(false)).ToEqual("{\"alive\":\"yes\"}"); - }); + JsonFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", u16(3000)); + ct.Next("b", Limits::Max()); + ct.Next("c", Limits::Lowest()); + Expect(writer.ToString(false)).ToEqual("{\"a\":3000,\"b\":65535,\"c\":0}"); + }); + + It("Can write u32 values", []() + { + JsonFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("alive", u32(35533)); + Expect(writer.ToString(false)).ToEqual("{\"alive\":35533}"); + }); + + It("Can write i32 values", []() + { + JsonFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + i32 value = 0; + ct.Next("alive", u32(35533)); + Expect(writer.ToString(false)).ToEqual("{\"alive\":35533}"); + + JsonFormatWriter writer2{}; + ct = writer2; + ct.BeginObject(); + ct.Next("alive", i32(-35533)); + Expect(writer2.ToString(false)).ToEqual("{\"alive\":-35533}"); + }); + + It("Can write float values", []() + { + JsonFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("alive", 0.344f); + Expect(Strings::Contains(writer.ToString(false), "0.344")).ToEqual(true); + + JsonFormatWriter writer2{}; + ct = writer2; + ct.BeginObject(); + ct.Next("alive", 4.f); + Expect(writer2.ToString(false)).ToEqual("{\"alive\":4.0}"); + }); + + It("Can write StringView values", []() + { + JsonFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("alive", StringView{"yes"}); + Expect(writer.ToString(false)).ToEqual("{\"alive\":\"yes\"}"); }); }); }); -return true; -}(); -} // namespace +}); diff --git a/Tests/Serialization/Serialization.spec.cpp b/Tests/Serialization/Serialization.spec.cpp index ddf11a1b..17dd9f4a 100644 --- a/Tests/Serialization/Serialization.spec.cpp +++ b/Tests/Serialization/Serialization.spec.cpp @@ -89,111 +89,103 @@ struct p::TFlags : public p::DefaultTFlags }; -namespace +Spec("Serialization", []() { -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() +Describe("Serializers in global scope", []() { -Spec("Serialization", []() + It("Can use custom Read()", []() + { + SerTypeA val{}; + JsonFormatReader reader{"{\"type\": {\"value\": true }}"}; + + Reader& ct = reader; + ct.BeginObject(); + ct.Next("type", val); + Expect(val.value).ToEqual(true); + }); + + It("Can use custom Write()", []() + { + SerTypeA val{}; + val.value = true; + + JsonFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("type", val); + Expect(writer.ToString(false)).ToEqual("{\"type\":{\"value\":true}}"); + }); + + It("Can use Serialize() instead of Read()", []() + { + SerTypeB val{}; + JsonFormatReader reader{"{\"type\": {\"value\": true }}"}; + + Reader& ct = reader; + ct.BeginObject(); + ct.Next("type", val); + Expect(val.value).ToEqual(true); + }); + + It("Can use Serialize() instead of Write()", []() + { + SerTypeB val{}; + val.value = true; + + JsonFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("type", val); + Expect(writer.ToString(false)).ToEqual("{\"type\":{\"value\":true}}"); + }); +}); + +Describe("Serializers as members", []() { - Describe("Serializers in global scope", []() + It("Can use custom Read()", []() { - It("Can use custom Read()", []() - { - SerTypeA val{}; - JsonFormatReader reader{"{\"type\": {\"value\": true }}"}; - - Reader& ct = reader; - ct.BeginObject(); - ct.Next("type", val); - Expect(val.value).ToEqual(true); - }); - - It("Can use custom Write()", []() - { - SerTypeA val{}; - val.value = true; - - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("type", val); - Expect(writer.ToString(false)).ToEqual("{\"type\":{\"value\":true}}"); - }); - - It("Can use Serialize() instead of Read()", []() - { - SerTypeB val{}; - JsonFormatReader reader{"{\"type\": {\"value\": true }}"}; - - Reader& ct = reader; - ct.BeginObject(); - ct.Next("type", val); - Expect(val.value).ToEqual(true); - }); - - It("Can use Serialize() instead of Write()", []() - { - SerTypeB val{}; - val.value = true; - - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("type", val); - Expect(writer.ToString(false)).ToEqual("{\"type\":{\"value\":true}}"); - }); + SerTypeC val{}; + JsonFormatReader reader{"{\"type\": {\"value\": true }}"}; + + Reader& ct = reader; + ct.BeginObject(); + ct.Next("type", val); + Expect(val.value).ToEqual(true); }); - Describe("Serializers as members", []() + It("Can use custom Write()", []() { - It("Can use custom Read()", []() - { - SerTypeC val{}; - JsonFormatReader reader{"{\"type\": {\"value\": true }}"}; - - Reader& ct = reader; - ct.BeginObject(); - ct.Next("type", val); - Expect(val.value).ToEqual(true); - }); - - It("Can use custom Write()", []() - { - SerTypeC val{}; - val.value = true; - - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("type", val); - Expect(writer.ToString(false)).ToEqual("{\"type\":{\"value\":true}}"); - }); - - It("Can use Serialize() instead of Read()", []() - { - SerTypeD val{}; - JsonFormatReader reader{"{\"type\": {\"value\": true }}"}; - - Reader& ct = reader; - ct.BeginObject(); - ct.Next("type", val); - Expect(val.value).ToEqual(true); - }); - - It("Can use Serialize() instead of Write()", []() - { - SerTypeD val{}; - val.value = true; - - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("type", val); - Expect(writer.ToString(false)).ToEqual("{\"type\":{\"value\":true}}"); - }); + SerTypeC val{}; + val.value = true; + + JsonFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("type", val); + Expect(writer.ToString(false)).ToEqual("{\"type\":{\"value\":true}}"); }); + + It("Can use Serialize() instead of Read()", []() + { + SerTypeD val{}; + JsonFormatReader reader{"{\"type\": {\"value\": true }}"}; + + Reader& ct = reader; + ct.BeginObject(); + ct.Next("type", val); + Expect(val.value).ToEqual(true); + }); + + It("Can use Serialize() instead of Write()", []() + { + SerTypeD val{}; + val.value = true; + + JsonFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("type", val); + Expect(writer.ToString(false)).ToEqual("{\"type\":{\"value\":true}}"); + }); +}); }); -return true; -}(); -} // namespace diff --git a/Tests/Time.spec.cpp b/Tests/Time.spec.cpp deleted file mode 100644 index 703900f3..00000000 --- a/Tests/Time.spec.cpp +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2015-2026 Piperift. All Rights Reserved. - -#include -#include - - -using namespace p; - - -namespace -{ -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() -{ -Spec("Time.DateTime", []() -{ - It("Can get day of year", []() - { - DateTime time1{2024, 1, 1}; - Expect(time1.GetDayOfYear()).ToEqual(1); - DateTime time11{2024, 1, 30}; - Expect(time11.GetDayOfYear()).ToEqual(30); - DateTime time12{2024, 1, 31}; - Expect(time12.GetDayOfYear()).ToEqual(31); - - DateTime time2{2024, 2, 1}; - Expect(time2.GetDayOfYear()).ToEqual(32); - DateTime time3{2024, 3, 1}; - Expect(time3.GetDayOfYear()).ToEqual(60); - DateTime time4{2024, 12, 31}; - Expect(time4.GetDayOfYear()).ToEqual(365); - }); -}); -return true; -}(); -} // namespace From ab5ffb079179d62f1ba85d5bd2b54d9ecc2b3ee8 Mon Sep 17 00:00:00 2001 From: muit Date: Fri, 4 Sep 2026 12:58:04 +0200 Subject: [PATCH 14/25] fix: missing period in PipeTests colored summary format string --- Src/Tests/PipeTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Src/Tests/PipeTest.cpp b/Src/Tests/PipeTest.cpp index 48fbc32b..031308ba 100644 --- a/Src/Tests/PipeTest.cpp +++ b/Src/Tests/PipeTest.cpp @@ -341,7 +341,7 @@ namespace p i32 passed = context.runTests - context.failedTests; if (settings.useColor) { - Info("{}PipeTest{}: {}{} run{}, {}{}{} passed{}, {}{}{} failed{}, {}{} skipped{}", cb, + Info("{}PipeTest{}: {}{} run{}, {}{}{} passed{}, {}{}{} failed{}, {}{} skipped{}.", cb, kColorReset, context.runTests, cr, cr, passed > 0 ? kColorGreen : "", passed, cr, context.failedTests > 0 ? kColorRed : "", context.failedTests, cr, context.skippedTests > 0 ? kColorYellow : "", context.skippedTests, cr); From f156ef17cd65ef254e8890423c958d9f2bef18c6 Mon Sep 17 00:00:00 2001 From: muit Date: Fri, 4 Sep 2026 13:02:47 +0200 Subject: [PATCH 15/25] fix: correct placeholder count in colored summary format string --- Src/Tests/PipeTest.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Src/Tests/PipeTest.cpp b/Src/Tests/PipeTest.cpp index 031308ba..2d0edea7 100644 --- a/Src/Tests/PipeTest.cpp +++ b/Src/Tests/PipeTest.cpp @@ -341,9 +341,9 @@ namespace p i32 passed = context.runTests - context.failedTests; if (settings.useColor) { - Info("{}PipeTest{}: {}{} run{}, {}{}{} passed{}, {}{}{} failed{}, {}{} skipped{}.", cb, - kColorReset, context.runTests, cr, cr, passed > 0 ? kColorGreen : "", passed, - cr, context.failedTests > 0 ? kColorRed : "", context.failedTests, cr, + Info("{}PipeTest{}: {} run, {}{}{} passed{}, {}{}{} failed{}, {}{} skipped{}.", cb, + kColorReset, context.runTests, passed > 0 ? kColorGreen : "", passed, cr, + context.failedTests > 0 ? kColorRed : "", context.failedTests, cr, context.skippedTests > 0 ? kColorYellow : "", context.skippedTests, cr); } else From e06836f8a5c691f52a0916ba6cb793741d94d9c3 Mon Sep 17 00:00:00 2001 From: muit Date: Fri, 4 Sep 2026 13:40:46 +0200 Subject: [PATCH 16/25] Added log colors and docs --- Docs/Log.md | 95 +++++++++++++++++++++++++++++++++++++++++ Include/Pipe/Core/Log.h | 41 ++++++++++++++++++ Src/Core/Log.cpp | 39 ++++++----------- 3 files changed, 148 insertions(+), 27 deletions(-) create mode 100644 Docs/Log.md diff --git a/Docs/Log.md b/Docs/Log.md new file mode 100644 index 00000000..b0ebd163 --- /dev/null +++ b/Docs/Log.md @@ -0,0 +1,95 @@ +# Log + +The logging system in Pipe provides a callback-based logger with three severity levels: `Info`, `Warning`, and `Error`. + +## Default Behavior + +The default logger outputs messages with no formatting: + +```cpp +p::Info("hello world"); // prints: hello world +p::Warning("something"); // prints: something +p::Error("bad state"); // prints: bad state +``` + +## API + +### Logger Struct + +```cpp +struct Logger +{ + std::function infoCallback; + std::function warningCallback; + std::function errorCallback; +}; +``` + +Each field is a callback invoked for its respective log level. All three must be provided. + +### InitLog / ShutdownLog + +```cpp +void InitLog(Logger* logger = nullptr); +void ShutdownLog(); +``` + +- `InitLog(nullptr)` — uses the default logger. +- `InitLog(&myLogger)` — uses a custom logger. The pointer must remain valid until `ShutdownLog()`. +- `ShutdownLog()` — clears the active logger. Calls to `Info`/`Warning`/`Error` become no-ops until `InitLog` is called again. + +### Info / Warning / Error + +```cpp +void Info(StringView msg); +void Warning(StringView msg); +void Error(StringView msg); +``` + +These dispatch to the corresponding callback on the active logger. If no logger is active messages are silently ignored. + +Text can be formatted (using `p::Format`): + +```cpp +p::Info("Loaded {} assets.", count); +p::Warning("Deprecated: {}", feature); +p::Error("Failed to open {}", path); +``` + +## Custom Logger Example + +The following reproduces the old default format with timestamps and level tags: + +```cpp +p::Logger timestampedLogger{ + .infoCallback = [](p::StringView msg) { + p::String text; + auto now = p::DateTime::Now(); + now.ToString("[%Y/%m/%d %H:%M:%S]", text); + p::FormatTo(text, "[Info] {}\n", msg); + std::cout << text; + }, + .warningCallback = [](p::StringView msg) { + p::String text; + auto now = p::DateTime::Now(); + now.ToString("[%Y/%m/%d %H:%M:%S]", text); + p::FormatTo(text, "[Warning] {}\n", msg); + std::cout << text; + }, + .errorCallback = [](p::StringView msg) { + p::String text; + auto now = p::DateTime::Now(); + now.ToString("[%Y/%m/%d %H:%M:%S]", text); + p::FormatTo(text, "[Error] {}\n", msg); + std::cout << text; + } +}; + +p::InitLog(×tampedLogger); +p::Info("Server Started"); // [2026/09/04 12:00:00] [Info] server started +``` + +Outputs: +``` +[2026/09/04 12:00:00] [Info] server started +``` diff --git a/Include/Pipe/Core/Log.h b/Include/Pipe/Core/Log.h index 0140e847..720113e1 100644 --- a/Include/Pipe/Core/Log.h +++ b/Include/Pipe/Core/Log.h @@ -9,6 +9,47 @@ namespace p { + namespace Terminal + { + // Foreground colors + inline constexpr const char* ColorReset = "\033[0m"; + inline constexpr const char* Black = "\033[30m"; + inline constexpr const char* Red = "\033[31m"; + inline constexpr const char* Green = "\033[32m"; + inline constexpr const char* Yellow = "\033[33m"; + inline constexpr const char* Blue = "\033[34m"; + inline constexpr const char* Magenta = "\033[35m"; + inline constexpr const char* Cyan = "\033[36m"; + inline constexpr const char* White = "\033[37m"; + inline constexpr const char* BrightBlack = "\033[90m"; + inline constexpr const char* BrightRed = "\033[91m"; + inline constexpr const char* BrightGreen = "\033[92m"; + inline constexpr const char* BrightYellow = "\033[93m"; + inline constexpr const char* BrightBlue = "\033[94m"; + inline constexpr const char* BrightMagenta = "\033[95m"; + inline constexpr const char* BrightCyan = "\033[96m"; + inline constexpr const char* BrightWhite = "\033[97m"; + + // Background colors + inline constexpr const char* BgBlack = "\033[40m"; + inline constexpr const char* BgRed = "\033[41m"; + inline constexpr const char* BgGreen = "\033[42m"; + inline constexpr const char* BgYellow = "\033[43m"; + inline constexpr const char* BgBlue = "\033[44m"; + inline constexpr const char* BgMagenta = "\033[45m"; + inline constexpr const char* BgCyan = "\033[46m"; + inline constexpr const char* BgWhite = "\033[47m"; + inline constexpr const char* BgBrightBlack = "\033[100m"; + inline constexpr const char* BgBrightRed = "\033[101m"; + inline constexpr const char* BgBrightGreen = "\033[102m"; + inline constexpr const char* BgBrightYellow = "\033[103m"; + inline constexpr const char* BgBrightBlue = "\033[104m"; + inline constexpr const char* BgBrightMagenta = "\033[105m"; + inline constexpr const char* BgBrightCyan = "\033[106m"; + inline constexpr const char* BgBrightWhite = "\033[107m"; + } // namespace Terminal + + struct Logger { std::function infoCallback; diff --git a/Src/Core/Log.cpp b/Src/Core/Log.cpp index 17fc2c92..1105f989 100644 --- a/Src/Core/Log.cpp +++ b/Src/Core/Log.cpp @@ -2,39 +2,24 @@ #include "Pipe/Core/Log.h" -#include "Pipe/Files/Files.h" -#include "Pipe/Files/Paths.h" -#include "Pipe/Memory/OwnPtr.h" -#include "PipeTime.h" - #include namespace p { // clang-format off const Logger defaultLogger = Logger{ - .infoCallback = [](StringView msg) { - String text; - auto now = DateTime::Now(); - now.ToString("[%Y/%m/%d %H:%M:%S]", text); - FormatTo(text, "[Info] {}\n", msg); - std::cout << text; - }, - .warningCallback = [](StringView msg) { - String text; - auto now = DateTime::Now(); - now.ToString("[%Y/%m/%d %H:%M:%S]", text); - FormatTo(text, "[Warning] {}\n", msg); - std::cout << text; - }, - .errorCallback = [](StringView msg) { - String text; - auto now = DateTime::Now(); - now.ToString("[%Y/%m/%d %H:%M:%S]", text); - FormatTo(text, "[Error] {}\n", msg); - std::cout << text; - } - }; + .infoCallback = [](StringView msg) + { + std::cout << msg << '\n'; + }, + .warningCallback = [](StringView msg) + { + std::cout << msg << '\n'; + }, + .errorCallback = [](StringView msg) + { + std::cerr << msg << '\n'; + }}; // clang-format on const Logger* globalLogger = nullptr; From b5ab97f919c906dd9cfb3ac10a02706340e7caa9 Mon Sep 17 00:00:00 2001 From: muit Date: Sat, 5 Sep 2026 00:31:38 +0200 Subject: [PATCH 17/25] Added new reporters, fixes, help and version and formatting --- CMakeLists.txt | 3 +- Include/Misc/PipeDebug.h | 17 +- Include/PipeColor.h | 5 +- Include/PipePlatform.h | 4 + Include/PipeReflect.h | 2 +- Include/PipeSerialize.h | 4 +- Include/PipeTest.h | 26 +- Include/PipeTime.h | 2 +- Src/Core/Log.cpp | 4 +- Src/Core/Tag.cpp | 20 +- Src/PipeFiles.cpp | 4 +- Src/PipeMemoryArenas.cpp | 3 +- Src/PipeSerialize.cpp | 2 +- Src/PipeTime.cpp | 4 +- Src/Tests/PipeTest.cpp | 804 +++++++++- Tests/CMakeLists.txt | 2 +- Tests/Core/Function.spec.cpp | 92 +- Tests/Core/OwnPtr.spec.cpp | 490 +++--- Tests/Core/PageBuffer.spec.cpp | 128 +- Tests/Core/PlatformProcess.spec.cpp | 12 +- Tests/Core/Set.spec.cpp | 142 +- Tests/Core/SpinLock.spec.cpp | 266 ++-- Tests/Core/String.spec.cpp | 1671 ++++++++++---------- Tests/Core/StringView.spec.cpp | 172 +- Tests/Core/Tag.spec.cpp | 170 +- Tests/ECS/Components.spec.cpp | 451 +++--- Tests/ECS/ECS.spec.cpp | 100 +- Tests/ECS/Filtering.spec.cpp | 352 ++--- Tests/ECS/Hierarchy.spec.cpp | 731 +++++---- Tests/ECS/IdRegistry.spec.cpp | 276 ++-- Tests/ECS/IdScopes.spec.cpp | 222 +-- Tests/ECS/Statics.spec.cpp | 120 +- Tests/Files/Paths.spec.cpp | 322 ++-- Tests/Math/Color.spec.cpp | 234 +-- Tests/Math/Math.spec.cpp | 514 +++--- Tests/Math/Vector.spec.cpp | 104 +- Tests/Memory/BestFitArena.spec.cpp | 490 +++--- Tests/Memory/BigBestFitArena.spec.cpp | 531 ++++--- Tests/Memory/Memory.spec.cpp | 376 ++--- Tests/Memory/MemoryStats.spec.cpp | 1014 ++++++------ Tests/Memory/MonoLinearArena.spec.cpp | 262 +-- Tests/PipeTime.spec.cpp | 32 +- Tests/Reflection/MacroReflection.spec.cpp | 24 +- Tests/Reflection/Object.spec.cpp | 36 +- Tests/Reflection/Traits.spec.cpp | 90 +- Tests/Reflection/TypeId.spec.cpp | 40 +- Tests/Reflection/TypeName.spec.cpp | 106 +- Tests/Serialization/Binary.spec.cpp | 714 +++++---- Tests/Serialization/Json.spec.cpp | 704 ++++----- Tests/Serialization/Serialization.spec.cpp | 186 +-- Tests/main.cpp | 2 +- 51 files changed, 6371 insertions(+), 5711 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6837a989..e3c8be61 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON) ################################################################################ # Project -project(Pipe VERSION 0.1 LANGUAGES CXX C) +project(Pipe VERSION 1.7 LANGUAGES CXX C) if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) @@ -65,6 +65,7 @@ target_include_directories(Pipe PRIVATE $GetTypeId()); - const ImU32 dotCol = ac.DWColor(); + const p::Color ac = details::GetArenaColor(va.arena->GetTypeId()); + const ImU32 dotCol = ac.DWColor(); sizet arenaUsed = 0; sizet arenaCapacity = 0; bool found = false; @@ -2614,16 +2614,16 @@ namespace p static String usedStr; static String capStr; static String line; - const float h = ImGui::GetTextLineHeight(); - const float sq = h * 0.7f; + const float h = ImGui::GetTextLineHeight(); + const float sq = h * 0.7f; const float pad = (h - sq) * 0.5f; for (i32 r = 0; r < rowCount; ++r) { const TipRow& row = rows[r]; usedStr.clear(); Strings::ParseMemorySizeTo(usedStr, row.used); - const ImVec4 col{row.col.r / 255.0f, row.col.g / 255.0f, - row.col.b / 255.0f, 1.0f}; + const ImVec4 col{ + row.col.r / 255.0f, row.col.g / 255.0f, row.col.b / 255.0f, 1.0f}; const ImVec2 p = ImGui::GetCursorScreenPos(); ImGui::GetWindowDrawList()->AddRectFilled(ImVec2(p.x, p.y + pad), ImVec2(p.x + sq, p.y + pad + sq), row.col.DWColor()); @@ -4464,9 +4464,8 @@ namespace p dst.parentArenaIdx = src.parentArenaIdx; // Deep-copy live allocs so the capture owns its data. - dst.ownedLiveAllocs = src.captured - ? src.ownedLiveAllocs - : (src.live ? *src.live : TSet{}); + dst.ownedLiveAllocs = src.captured ? src.ownedLiveAllocs + : (src.live ? *src.live : TSet{}); dst.captured = true; dst.live = nullptr; diff --git a/Include/PipeColor.h b/Include/PipeColor.h index 55f4b0ca..95e362b7 100644 --- a/Include/PipeColor.h +++ b/Include/PipeColor.h @@ -3,8 +3,8 @@ #pragma once #include "Pipe/Core/FixedString.h" -#include "PipeStrings.h" #include "PipeMath.h" +#include "PipeStrings.h" #include "PipeVectors.h" @@ -727,8 +727,7 @@ namespace p { if (includeAlpha) { - return Format( - "{:02X}{:02X}{:02X}{:02X}", this->r, this->g, this->b, this->a); + return Format("{:02X}{:02X}{:02X}{:02X}", this->r, this->g, this->b, this->a); } return Format("{:02X}{:02X}{:02X}", this->r, this->g, this->b); } diff --git a/Include/PipePlatform.h b/Include/PipePlatform.h index 1f4f9e4d..d29b955b 100644 --- a/Include/PipePlatform.h +++ b/Include/PipePlatform.h @@ -7,6 +7,10 @@ #include #include +#ifndef P_VERSION + #define P_VERSION "0.0" +#endif + // Platform Break includes #ifdef _MSC_VER #elif defined(__i386__) || defined(__x86_64__) diff --git a/Include/PipeReflect.h b/Include/PipeReflect.h index c615fcdc..a3e45cfa 100644 --- a/Include/PipeReflect.h +++ b/Include/PipeReflect.h @@ -5,7 +5,6 @@ #include "Pipe/Core/EnumFlags.h" #include "Pipe/Core/Guid.h" #include "Pipe/Core/Macros.h" -#include "PipeStrings.h" #include "Pipe/Core/StringView.h" #include "Pipe/Core/Tag.h" #include "Pipe/Core/TypeId.h" @@ -15,6 +14,7 @@ #include "Pipe/Memory/OwnPtr.h" #include "PipeColor.h" #include "PipeSerialize.h" +#include "PipeStrings.h" #include "PipeVectors.h" diff --git a/Include/PipeSerialize.h b/Include/PipeSerialize.h index e2f55ef0..022eb07e 100644 --- a/Include/PipeSerialize.h +++ b/Include/PipeSerialize.h @@ -1,16 +1,16 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. #pragma once -#include "PipeStrings.h" #include "Pipe/Core/StringView.h" #include "Pipe/Core/Templates.h" #include "Pipe/Core/TypeFlags.h" #include "Pipe/Core/TypeId.h" #include "Pipe/Core/TypeTraits.h" -#include "PipeContainers.h" #include "PipeColor.h" +#include "PipeContainers.h" #include "PipePlatform.h" #include "PipeSerializeFwd.h" +#include "PipeStrings.h" #include diff --git a/Include/PipeTest.h b/Include/PipeTest.h index cdd1d43a..fedfc0c7 100644 --- a/Include/PipeTest.h +++ b/Include/PipeTest.h @@ -56,12 +56,25 @@ namespace p // Teardown hook attached to the current describe. void AfterEach(std::function fn); + // Which reporter formats the test output. + enum class TestReporter : u8 + { + Spec, // Verbose, bandit-style "describe / it ... OK" output (default). + Dots, // Compact progress: one character (., F, S) per test. + Singleline, // Single progress line updated in place, "\r" based. + Info, // Verbose "begin/end" contexts, "[ PASS ]" tests, timing support. + }; + // Settings for a test run. struct TestSettings { - StringView filter; // Empty runs all; otherwise substring match on full name. - bool listOnly = false; // List test names without running. - bool useColor = true; // Colorized output. + StringView only; // Run only describe/it containing substring. + StringView skip; // Skip all describe/it containing substring. + bool dryRun = false; // Report full tree as SKIPPED, run nothing (bandit semantics). + bool breakOnFailure = false; // Stop the test run on the first failing test. + bool useColor = true; // Colorized output. + bool reportTiming = false; // Report per-test timing information. + TestReporter reporter = TestReporter::Dots; }; int RunTests(const TestSettings& settings); @@ -133,6 +146,9 @@ namespace p // Format failure message from source location + description. void Fail(const std::source_location& loc, StringView message); + // Counts an assertion. Used to detect tests that ran no expects. + void CountAssert(); + // True when both Actual and Expected can be viewed as a StringView (string-ish). template struct IsStringBoth : std::false_type @@ -172,7 +188,9 @@ namespace p { public: ExpectValue(const Actual& value, const std::source_location& loc) : value(value), loc(loc) - {} + { + details::CountAssert(); + } template void ToEqual(const Expected& expected) const diff --git a/Include/PipeTime.h b/Include/PipeTime.h index 1437a136..5afecd85 100644 --- a/Include/PipeTime.h +++ b/Include/PipeTime.h @@ -3,8 +3,8 @@ #pragma once #include "Pipe/Core/Checks.h" -#include "PipeStrings.h" #include "PipePlatform.h" +#include "PipeStrings.h" #include diff --git a/Src/Core/Log.cpp b/Src/Core/Log.cpp index 1105f989..2d1e2899 100644 --- a/Src/Core/Log.cpp +++ b/Src/Core/Log.cpp @@ -14,11 +14,11 @@ namespace p }, .warningCallback = [](StringView msg) { - std::cout << msg << '\n'; + std::cout << "\033[33m" << msg << "\033[0m\n"; }, .errorCallback = [](StringView msg) { - std::cerr << msg << '\n'; + std::cerr << "\033[31m" << msg << "\033[0m\n"; }}; // clang-format on diff --git a/Src/Core/Tag.cpp b/Src/Core/Tag.cpp index 42de9fee..68a28df1 100644 --- a/Src/Core/Tag.cpp +++ b/Src/Core/Tag.cpp @@ -212,6 +212,25 @@ namespace p } } + // Slow path: alloc & insert must be atomic. Re-check under exclusive lock, another thread + // may have inserted the same key already (duplicates would break string uniqueness). + ExclusiveScopedLock lock{stringsListMutex}; + + index = strings.LowerBound(hash); + if (index != NO_INDEX) + { + const auto& ref = strings[index]; + if (hash == ref.hash) + { + // Found existing key + return *ref.str; + } + } + else + { + index = strings.Size(); // Insert at the end of the list + } + const sizet size = value.size(); auto* header = static_cast(arena.Alloc(GetAllocSize(size), alignof(TagHeader))); header->activeTags = 0; @@ -222,7 +241,6 @@ namespace p p::CopyMem(data, value.data(), sizeof(char) * size); data[header->size] = '\0'; - ExclusiveScopedLock lock{stringsListMutex}; strings.Insert(index, {hash, header}); return *header; } diff --git a/Src/PipeFiles.cpp b/Src/PipeFiles.cpp index 59e4e2dc..ab69eb36 100644 --- a/Src/PipeFiles.cpp +++ b/Src/PipeFiles.cpp @@ -138,9 +138,7 @@ namespace p { switch (error) { - case FWE_FileNotFound: - lastFileWatcherError = Format("File not found ({})", log); - break; + case FWE_FileNotFound: lastFileWatcherError = Format("File not found ({})", log); break; case FWE_FileRepeated: lastFileWatcherError = Format("File repeated in watches ({})", log); break; diff --git a/Src/PipeMemoryArenas.cpp b/Src/PipeMemoryArenas.cpp index 55c89967..e0df2130 100644 --- a/Src/PipeMemoryArenas.cpp +++ b/Src/PipeMemoryArenas.cpp @@ -561,8 +561,7 @@ namespace p ReduceSlot( slotIndex, slot, ToOffset(header, block.data), ToOffset(header->end, block.data)); - const sizet realSize = - static_cast(header->end) - reinterpret_cast(header); + const sizet realSize = static_cast(header->end) - reinterpret_cast(header); freeSize -= realSize; stats.Add(header, realSize); return ptr; diff --git a/Src/PipeSerialize.cpp b/Src/PipeSerialize.cpp index 9349063b..779502f6 100644 --- a/Src/PipeSerialize.cpp +++ b/Src/PipeSerialize.cpp @@ -5,10 +5,10 @@ #include "Pipe/Core/Checks.h" #include "Pipe/Core/Guid.h" #include "Pipe/Core/Log.h" -#include "PipeStrings.h" #include "Pipe/Core/Tag.h" #include "Pipe/Extern/yyjson.h" #include "PipeMath.h" +#include "PipeStrings.h" static void* yyjson_malloc(void* ctx, p::sizet size) diff --git a/Src/PipeTime.cpp b/Src/PipeTime.cpp index 4f510183..089a2edd 100644 --- a/Src/PipeTime.cpp +++ b/Src/PipeTime.cpp @@ -296,8 +296,8 @@ namespace p case MonthOfYear::December: MonthStr = "Dec"; break; } - return Format("{}, {:02d} {} {} {:02i}:{:02i}:{:02i} GMT", DayStr, GetDay(), - MonthStr, GetYear(), GetHour(), GetMinute(), GetSecond()); + return Format("{}, {:02d} {} {} {:02i}:{:02i}:{:02i} GMT", DayStr, GetDay(), MonthStr, + GetYear(), GetHour(), GetMinute(), GetSecond()); } diff --git a/Src/Tests/PipeTest.cpp b/Src/Tests/PipeTest.cpp index 2d0edea7..a2c1a657 100644 --- a/Src/Tests/PipeTest.cpp +++ b/Src/Tests/PipeTest.cpp @@ -9,8 +9,14 @@ #include "Pipe.h" #include "Pipe/Core/Log.h" +#include "Pipe/Memory/UniquePtr.h" #include "PipeStrings.h" #include "PipeTest.h" +#include "PipeTime.h" + +#if P_PLATFORM_WINDOWS + #include +#endif namespace p @@ -46,6 +52,26 @@ namespace p i32 runTests = 0; i32 skippedTests = 0; i32 currentTestFailureCount = 0; + // Assertions made by the current test (0 = no expects ran). + i32 currentTestAssertCount = 0; + // Set once a test has failed (--break-on-failure stops the run). + bool encounteredFailure = false; + + // Active describe names during a run (outermost first). + TArray contextStack; + // Formatted failure blocks, one per failed test: "context it:\nmessage\n". + TArray failures; + // Assertion detail accumulated for the current test (file:line: msg). + String currentFailureDetail; + + TestReporter reporter = TestReporter::Spec; + bool useColor = true; + bool reportTiming = false; + + // Wall time of the last executed test body, in seconds. + double lastTestDuration = 0.0; + // Sum of all executed test bodies this run, in seconds. + double totalDuration = 0.0; }; // Function-local static: initialized on first use regardless of the @@ -66,14 +92,27 @@ namespace p namespace details { + void CountAssert() + { + ++GetTestContext().currentTestAssertCount; + } + void Fail(const std::source_location& loc, StringView message) { - Error("PipeTest: {}:{}: {}", loc.file_name(), loc.line(), message); - ++GetTestContext().currentTestFailureCount; + // Not printed immediately: failures are reported by the reporter + // at the end of the run, so they never interleave with deferred + // context output. + TestContext& context = GetTestContext(); + ++context.currentTestFailureCount; + if (!context.currentFailureDetail.empty()) + { + context.currentFailureDetail += "\n"; + } + context.currentFailureDetail += + Format("{}:{}: {}", loc.file_name(), loc.line(), message); } } // namespace details - void RegisterSpec(StringView name, TFunction fn) { TestContext& context = GetTestContext(); @@ -169,56 +208,475 @@ namespace p namespace { - // ANSI color codes (used when useColor is true). - const char* kColorReset = "\033[0m"; - const char* kColorGreen = "\033[32m"; - const char* kColorRed = "\033[31m"; - const char* kColorYellow = "\033[33m"; - const char* kColorCyan = "\033[36m"; - const char* kColorDim = "\033[90m"; - - static String FullName(const TestDescribe& describe, const TestCase& test) + // Color aliases for readability. + using Terminal::Blue; + using Terminal::BrightBlack; + using Terminal::ColorReset; + using Terminal::Cyan; + using Terminal::Green; + using Terminal::Red; + using Terminal::Yellow; + + // Full test name: all enclosing describe names plus the test name, + // e.g. "Containers.BitArray.Copy.Can copy empty". Used for filtering + // and failure reports, so `--only` matches any parent describe too. + static String FullName(StringView testName) { + TestContext& context = GetTestContext(); String result; - if (!describe.name.empty()) + for (i32 i = 0; i < context.contextStack.Size(); ++i) { - result += describe.name; - result += "."; + result += context.contextStack[i]; + result += '.'; } - result += test.name; + result += testName; return result; } - static bool MatchesFilter(StringView fullName, StringView filter) + // Whether a test (by full describe+it name) should run given the + // `only`/`skip` substring selection and the skip set. + static bool Matches(StringView fullName, StringView only, StringView skip) { - return filter.empty() || Strings::Contains(fullName, filter); + const bool included = only.empty() || Strings::Contains(fullName, only); + const bool excluded = !skip.empty() && Strings::Contains(fullName, skip); + return included && !excluded; } - static void ListNested(const TestDescribe& describe, StringView filter) + // Color a string for terminal output, honoring the useColor flag. + static String Colored(const char* color, StringView text) + { + TestContext& context = GetTestContext(); + if (!context.useColor) + { + return String{text}; + } + return Format("{}{}{}", color, text, Terminal::ColorReset); + } + + // Formats a duration with 2 significant digits, switching to smaller + // units before std-format would fall back to exponent notation: + // 0.0001208 -> "0.00012s", 0.000012 -> "0.012ms", 4e-07 -> "0.4us". + static String FormatDuration(double seconds) + { + if (seconds == 0) + { + return "0s"; + } + const double abs = seconds < 0 ? -seconds : seconds; + if (abs >= 100) + { + return Format("{:.0f}s", seconds); + } + if (abs >= 1e-4) + { + return Format("{:.2}s", seconds); + } + if (abs >= 1e-7) + { + return Format("{:.2}ms", seconds * 1e3); + } + if (abs >= 1e-10) + { + return Format("{:.2}us", seconds * 1e6); + } + return Format("{:.2}ns", seconds * 1e9); + } + + // Timing suffix for test status lines, e.g. " (0.00012s)". + // Empty unless --report-timing was set. + static String TimingSuffix() + { + TestContext& context = GetTestContext(); + if (!context.reportTiming) + { + return {}; + } + return Colored(Yellow, Format(" ({})", FormatDuration(context.lastTestDuration))); + } + + // ---- Reporter interface ---- + // Mirrors bandit's reporter callbacks. Each reporter formats the run + // differently; all share the same per-test execution flow in RunNested. + struct ITestReporter + { + virtual ~ITestReporter() = default; + + virtual void TestRunStarting() {} + virtual void TestRunComplete() = 0; + virtual void ContextStarting(StringView) {} + virtual void ContextEnded(StringView) {} + virtual void ItStarting(StringView) {} + virtual void ItSucceeded(StringView) {} + // Test passed but made no assertions (e.g. smoke tests). + virtual void ItSucceededNoAssertions(StringView) {} + virtual void ItFailed(StringView) {} + virtual void ItUnknownError(StringView) {} + virtual void ItSkipped(StringView) {} + }; + + // Shared summary footer (defined below; forward-declared for reporters). + static void WriteSummary(); + + // ---- Spec reporter (default, verbose) ---- + // bandit's `spec` reporter: indented contexts, "- it ... OK". + struct SpecReporter : ITestReporter + { + i32 indentation = 0; + String lastIt; + + String Indent() const + { + // One tab per level: the bandit VSCode adapter's parser pops + // one parent per character of indentation decrease, so any + // multi-space indent breaks its hierarchy detection. + String result; + result.assign(indentation, '\t'); + return result; + } + + + void ContextStarting(StringView desc) override + { + Info("{}describe {}", Indent(), desc); + ++indentation; + } + + void ContextEnded(StringView) override + { + --indentation; + } + + void ItStarting(StringView desc) override + { + lastIt = String{desc}; + } + + void ItSucceeded(StringView) override + { + Info("{}- it {} ... {}{}", Indent(), lastIt, Colored(Green, "OK"), TimingSuffix()); + } + + void ItSucceededNoAssertions(StringView) override + { + Info("{}- it {} ... {}{}", Indent(), lastIt, Colored(Yellow, "OK"), TimingSuffix()); + } + + void ItFailed(StringView) override + { + // stdout, not stderr: tooling (e.g. the bandit VSCode adapter) + // parses test results from stdout only. + Info( + "{}- it {} ... {}{}", Indent(), lastIt, Colored(Red, "FAILED"), TimingSuffix()); + } + + void ItUnknownError(StringView) override + { + Info("{}- it {} ... {}{}", Indent(), lastIt, Colored(Red, "ERROR"), TimingSuffix()); + } + + void ItSkipped(StringView desc) override + { + Info("{}- it {} ... {}", Indent(), desc, Colored(Yellow, "SKIPPED")); + } + + void TestRunComplete() override; + }; + + // ---- Dots reporter (compact) ---- + // bandit's `dots` reporter: one character per test, laid out on shared + // lines (a fresh line every kLineWidth tests) rather than one line each. + struct DotsReporter : ITestReporter { - for (const TestDescribe& sub : describe.describes) + bool anyResults = false; + + void ItSucceeded(StringView) override { - ListNested(sub, filter); + std::cout << Colored(Green, "."); + anyResults = true; } - for (const TestCase& test : describe.tests) + + void ItSucceededNoAssertions(StringView) override { - String full = FullName(describe, test); - if (MatchesFilter(full, filter)) + std::cout << Colored(Yellow, "."); + anyResults = true; + } + + void ItFailed(StringView) override + { + std::cout << Colored(Red, "F"); + anyResults = true; + } + + void ItUnknownError(StringView) override + { + std::cout << Colored(Red, "E"); + anyResults = true; + } + + void ItSkipped(StringView) override + { + std::cout << Colored(Yellow, "S"); + anyResults = true; + } + + void TestRunComplete() override + { + if (anyResults) { - if (test.skip) - { - Info(" {}[SKIP]{} {}", kColorYellow, kColorReset, full); - } - else + std::cout << std::endl; + } + WriteSummary(); + } + }; + + // ---- Singleline reporter ---- + // bandit's `singleline` reporter: prints a live status line after each test. + struct SinglelineReporter : ITestReporter + { + void ItSucceeded(StringView) override + { + PrintStatus(); + } + + void ItSucceededNoAssertions(StringView) override + { + PrintStatus(); + } + + void ItFailed(StringView) override + { + PrintStatus(); + } + + void ItUnknownError(StringView) override + { + PrintStatus(); + } + + void PrintStatus() + { + TestContext& context = GetTestContext(); + i32 run = context.runTests; + i32 failed = context.failedTests; + i32 passed = run - failed; + if (run <= 0) + { + Error("Could not find any tests."); + return; + } + + Info("Executed {} tests.", run); + if (failed == 0) + { + if (failed <= 0) + {} + Info("{}\n {} failed.", run, passed, Colored(Red, Format("{}", failed))); + } + else + { + Info("Executed {} tests.", run); + } + } + + void TestRunComplete() override; + }; + + // ---- Info reporter (verbose with timing support) ---- + // bandit's `info` reporter: "begin/end " lines with per-context + // totals, "[ PASS ] / [ FAIL ] / -ERROR->" test lines, a failures list + // and its own summary. Honors --report-timing on every test line. + struct InfoReporter : ITestReporter + { + // One entry per active describe, outermost first. + struct ContextInfo + { + String name; + i32 total = 0; + i32 skipped = 0; + i32 failed = 0; + }; + + TArray stack; + // Depth of contexts whose begin/end lines are visible. + i32 activeIndex = 0; + + String Indent() const + { + String result; + result.assign(size_t(activeIndex) * 2, ' '); + return result; + } + + void TestRunStarting() override {} + + void ContextStarting(StringView desc) override + { + Info("{}{} {}", Indent(), Colored(Blue, "begin"), desc); + ContextInfo info; + info.name = String{desc}; + stack.Add(info); + ++activeIndex; + } + + void ContextEnded(StringView) override + { + ContextInfo& info = stack.Last(); + --activeIndex; + String line = Format("{}{} {}", Indent(), Colored(Blue, "end"), info.name); + if (info.total > 0) + { + line += Format(" {} total", info.total); + } + if (info.skipped > 0) + { + line += Colored(Yellow, Format(" {} skipped", info.skipped)); + } + if (info.failed > 0) + { + line += Colored(Red, Format(" {} failed", info.failed)); + } + Info("{}", line); + + // Merge counts into the parent context. + if (stack.Size() > 1) + { + ContextInfo& parent = stack[stack.Size() - 2]; + parent.total += info.total; + parent.skipped += info.skipped; + parent.failed += info.failed; + } + stack.RemoveLast(); + } + + void ItStarting(StringView) override {} + + void ItSucceeded(StringView desc) override + { + stack.Last().total++; + Info("{}{} it {}{}", Indent(), Colored(Green, "[ PASS ]"), desc, TimingSuffix()); + } + + // No assertions ran (smoke test): show as a neutral [ TEST ]. + void ItSucceededNoAssertions(StringView desc) override + { + stack.Last().total++; + Info("{}{} it {}{}", Indent(), Colored(Yellow, "[ TEST ]"), desc, TimingSuffix()); + } + + void ItFailed(StringView desc) override + { + ContextInfo& info = stack.Last(); + info.total++; + info.failed++; + Info("{}{} it {}{}", Indent(), Colored(Red, "[ FAIL ]"), desc, TimingSuffix()); + } + + void ItUnknownError(StringView desc) override + { + ContextInfo& info = stack.Last(); + info.total++; + info.failed++; + Info("{}{} it {}{}", Indent(), Colored(Red, "-ERROR->"), desc, TimingSuffix()); + } + + // bandit's info reporter counts skipped tests silently. + void ItSkipped(StringView) override + { + ContextInfo& info = stack.Last(); + info.total++; + info.skipped++; + } + + void TestRunComplete() override + { + TestContext& context = GetTestContext(); + i32 succeeded = context.runTests - context.failedTests; + + Info(""); + if (context.failedTests > 0) + { + Info("{}", Colored(Red, "List of failures:")); + for (const String& failure : context.failures) { - Info(" {}{}{}", kColorDim, full, kColorReset); + Info(" (*) {}", Colored(Red, failure)); } } + + Info("Tests run: {}", context.runTests); + if (context.skippedTests > 0) + { + Info("{}", Colored(Yellow, Format("Skipped: {}", context.skippedTests))); + } + if (succeeded > 0) + { + Info("{}", Colored(Green, Format("Passed: {}", succeeded))); + } + if (context.failedTests > 0) + { + Info("{}", Colored(Red, Format("Failed: {}", context.failedTests))); + } + if (context.reportTiming) + { + Info("{}", Colored(Yellow, Format("Total time: {}", + FormatDuration(context.totalDuration)))); + } + } + }; + + // ---- Summary (shared by all reporters) ---- + // bandit's summary: success/failure header + failure blocks + totals line. + static void WriteSummary() + { + TestContext& context = GetTestContext(); + i32 succeeded = context.runTests - context.failedTests; + + if (context.failedTests == 0) + { + Info("{}", Colored(Green, "Success!")); + } + else + { + // stdout, not stderr: tooling parses results from stdout only. + Info("{}", Colored(Red, "There were failures!")); + for (const String& failure : context.failures) + { + Info("{}", failure); + } + } + + String line = Format( + "Test run complete. {} tests run. {} succeeded.", context.runTests, succeeded); + if (context.skippedTests > 0) + { + line += Format(" {} skipped.", context.skippedTests); + } + if (context.failedTests > 0) + { + line += Format(" {} failed.", context.failedTests); + } + Info("{}", line); + if (context.reportTiming) + { + Info("{}", Colored(Yellow, + Format("Total time: {}", FormatDuration(context.totalDuration)))); } } + void SpecReporter::TestRunComplete() + { + // A newline separates per-test output from the summary block. + Info(""); + WriteSummary(); + } + + void SinglelineReporter::TestRunComplete() + { + WriteSummary(); + } + static void RunNested(TestDescribe& describe, TArray>& beforeHooks, - TArray>& afterHooks, StringView filter, bool useColor) + TArray>& afterHooks, StringView only, StringView skip, + bool breakOnFailure, ITestReporter& reporter) { TestContext& context = GetTestContext(); if (describe.beforeEach) @@ -230,23 +688,32 @@ namespace p afterHooks.Add(describe.afterEach); } + if (!describe.name.empty()) + { + context.contextStack.Add(describe.name); + reporter.ContextStarting(describe.name); + } + for (TestDescribe& sub : describe.describes) { - RunNested(sub, beforeHooks, afterHooks, filter, useColor); + RunNested(sub, beforeHooks, afterHooks, only, skip, breakOnFailure, reporter); } for (TestCase& test : describe.tests) { - if (test.skip) + // Bandit semantics: tests not selected by the filters, and + // tests marked skip, are reported as SKIPPED, not hidden. + // With break-on-failure, everything after the first failure + // is skipped too. + if (test.skip || !Matches(FullName(test.name), only, skip) + || (breakOnFailure && context.encounteredFailure)) { ++context.skippedTests; - continue; - } - if (!MatchesFilter(FullName(describe, test), filter)) - { + reporter.ItSkipped(test.name); continue; } ++context.runTests; + reporter.ItStarting(test.name); for (auto& hook : beforeHooks) { @@ -254,50 +721,67 @@ namespace p } context.currentTestFailureCount = 0; + context.currentTestAssertCount = 0; + context.currentFailureDetail = {}; bool passed = true; + bool unknown = false; + const DateTime testStart = DateTime::Now(); try { test.body(); } catch (...) { - passed = false; - Error("PipeTest: test failed by exception: {}", FullName(describe, test)); + passed = false; + unknown = true; } - passed = passed && (context.currentTestFailureCount == 0); + passed = passed && (context.currentTestFailureCount == 0); + const Timespan testElapsed = DateTime::Now() - testStart; + context.lastTestDuration = testElapsed.GetTotalSeconds(); for (i32 i = afterHooks.Size(); i > 0; --i) { afterHooks[i - 1](); } - String name = FullName(describe, test); + String full = FullName(test.name); + if (passed) { - if (useColor) + if (context.currentTestAssertCount == 0) { - Info(" {}[PASS]{} {}{}{}{}", kColorGreen, kColorReset, kColorDim, name, - kColorReset, kColorReset); + reporter.ItSucceededNoAssertions(test.name); } else { - Info(" [PASS] {}", name); + reporter.ItSucceeded(test.name); } } else { ++context.failedTests; - if (useColor) + context.encounteredFailure = true; + if (unknown) { - Error(" {}[FAIL]{} {}", kColorRed, kColorReset, name); + reporter.ItUnknownError(test.name); + context.failures.Add(full + ":\nUnknown exception\n"); } else { - Error(" [FAIL] {}", name); + reporter.ItFailed(test.name); + String detail = context.currentFailureDetail; + context.failures.Add( + detail.empty() ? (full + ":\n") : (full + ":\n" + detail + "\n")); } } } + if (!describe.name.empty()) + { + reporter.ContextEnded(describe.name); + context.contextStack.RemoveLast(); + } + if (describe.beforeEach) { beforeHooks.RemoveLast(); @@ -307,50 +791,107 @@ namespace p afterHooks.RemoveLast(); } } + + // Dry run: report the full test tree without executing anything. + // Matches bandit's run policy, where `--dry-run` skips every `it` + // (reported as SKIPPED) before any `--only`/`--skip` filter applies, + // so the whole structure is always visible. Test tooling relies on + // this to discover tests. + static void DryRunNested(TestDescribe& describe, ITestReporter& reporter) + { + TestContext& context = GetTestContext(); + if (!describe.name.empty()) + { + context.contextStack.Add(describe.name); + reporter.ContextStarting(describe.name); + } + + for (TestDescribe& sub : describe.describes) + { + DryRunNested(sub, reporter); + } + + for (TestCase& test : describe.tests) + { + reporter.ItSkipped(test.name); + } + + if (!describe.name.empty()) + { + reporter.ContextEnded(describe.name); + context.contextStack.RemoveLast(); + } + } } // namespace int RunTests(const TestSettings& settings) { +#if P_PLATFORM_WINDOWS + // Enable ANSI escape sequences on the Windows console, otherwise + // color codes print as garbage (e.g. "←[32m"). The flag is per + // handle, so both stdout (reporters) and stderr (Error/Warning logs) + // need it. + { + const DWORD handleIds[] = {STD_OUTPUT_HANDLE, STD_ERROR_HANDLE}; + for (DWORD id : handleIds) + { + HANDLE handle = GetStdHandle(id); + DWORD mode = 0; + if (handle != INVALID_HANDLE_VALUE && GetConsoleMode(handle, &mode)) + { + SetConsoleMode(handle, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING); + } + } + } +#endif TestContext& context = GetTestContext(); context.runTests = 0; context.failedTests = 0; context.skippedTests = 0; + context.reporter = settings.reporter; + context.useColor = settings.useColor; + context.reportTiming = settings.reportTiming; + context.contextStack.Clear(); + context.failures.Clear(); + context.encounteredFailure = false; - const char* cr = settings.useColor ? kColorReset : ""; - const char* cb = settings.useColor ? kColorCyan : ""; - - Info("{}{}describe(s) registered.{}", cb, context.root.describes.Size(), cr); - - // --list: print test names and exit. - if (settings.listOnly) + // --dry-run/--list: report the full tree without executing (bandit + // semantics). Always uses the spec reporter; it is the only format + // with a meaningful test listing. + if (settings.dryRun) { - for (TestDescribe& spec : context.root.describes) - { - String specName = spec.name.empty() ? String{"(unnamed)"} : String{spec.name}; - Info("{}", specName); - ListNested(spec, settings.filter); - } + SpecReporter specReporter; + specReporter.TestRunStarting(); + DryRunNested(context.root, specReporter); + specReporter.TestRunComplete(); return 0; } + TUniquePtr reporter; + switch (settings.reporter) + { + case TestReporter::Dots: reporter = MakeUnique(); break; + case TestReporter::Singleline: reporter = MakeUnique(); break; + case TestReporter::Info: reporter = MakeUnique(); break; + case TestReporter::Spec: + default: reporter = MakeUnique(); break; + } + + reporter->TestRunStarting(); + + const DateTime runStart = DateTime::Now(); + TArray> beforeHooks; TArray> afterHooks; - RunNested(context.root, beforeHooks, afterHooks, settings.filter, settings.useColor); + RunNested(context.root, beforeHooks, afterHooks, settings.only, settings.skip, + settings.breakOnFailure, *reporter.Get()); - i32 passed = context.runTests - context.failedTests; - if (settings.useColor) - { - Info("{}PipeTest{}: {} run, {}{}{} passed{}, {}{}{} failed{}, {}{} skipped{}.", cb, - kColorReset, context.runTests, passed > 0 ? kColorGreen : "", passed, cr, - context.failedTests > 0 ? kColorRed : "", context.failedTests, cr, - context.skippedTests > 0 ? kColorYellow : "", context.skippedTests, cr); - } - else - { - Info("PipeTest: {} run, {} passed, {} failed, {} skipped.", context.runTests, passed, - context.failedTests, context.skippedTests); - } + // Total duration is wall time from run start to run end. + const Timespan runElapsed = DateTime::Now() - runStart; + context.totalDuration = runElapsed.GetTotalSeconds(); + + reporter->TestRunComplete(); return context.failedTests == 0 ? 0 : 1; } @@ -361,32 +902,121 @@ namespace p for (i32 i = 1; i < argc; ++i) { const StringView arg{argv[i]}; - if (Strings::StartsWith(arg, StringView{"--filter="})) + if (Strings::StartsWith(arg, StringView{"--only="})) + { + settings.only = Strings::RemoveFromStart(arg, StringView{"--only="}); + } + else if (Strings::StartsWith(arg, StringView{"--skip="})) + { + settings.skip = Strings::RemoveFromStart(arg, StringView{"--skip="}); + } + else if (Strings::StartsWith(arg, StringView{"--reporter="})) { - settings.filter = Strings::RemoveFromStart(arg, StringView{"--filter="}); + const StringView name = Strings::RemoveFromStart(arg, StringView{"--reporter="}); + if (Strings::Equals(name, StringView{"dots"})) + { + settings.reporter = TestReporter::Dots; + } + else if (Strings::Equals(name, StringView{"singleline"})) + { + settings.reporter = TestReporter::Singleline; + } + else if (Strings::Equals(name, StringView{"spec"})) + { + settings.reporter = TestReporter::Spec; + } + else if (Strings::Equals(name, StringView{"info"})) + { + settings.reporter = TestReporter::Info; + } + else + { + Warning("PipeTest: unknown reporter '{}'. Using 'dots'.", name); + } } - else if (Strings::Equals(arg, StringView{"--filter"}) || Strings::Equals(arg, StringView{"-f"})) + else if (Strings::Equals(arg, StringView{"--reporter"}) + || Strings::Equals(arg, StringView{"-r"})) { if (i + 1 < argc) { - settings.filter = StringView{argv[++i]}; + const StringView name{argv[++i]}; + if (Strings::Equals(name, StringView{"dots"})) + { + settings.reporter = TestReporter::Dots; + } + else if (Strings::Equals(name, StringView{"singleline"})) + { + settings.reporter = TestReporter::Singleline; + } + else if (Strings::Equals(name, StringView{"spec"})) + { + settings.reporter = TestReporter::Spec; + } + else if (Strings::Equals(name, StringView{"info"})) + { + settings.reporter = TestReporter::Info; + } + else + { + Warning("PipeTest: unknown reporter '{}'. Using 'dots'.", name); + } + } + } + else if (Strings::Equals(arg, StringView{"--report-timing"})) + { + settings.reportTiming = true; + } + else if (Strings::StartsWith(arg, StringView{"--colorizer="})) + { + const StringView name = Strings::RemoveFromStart(arg, StringView{"--colorizer="}); + if (Strings::Equals(name, StringView{"off"})) + { + settings.useColor = false; } + // 'dark'/'light' keep color enabled (default). } - else if (Strings::Equals(arg, StringView{"--list"}) || Strings::Equals(arg, StringView{"-l"})) + else if (Strings::Equals(arg, StringView{"--version"})) { - settings.listOnly = true; + Info("Pipe version {}", P_VERSION); + return 0; } - else if (Strings::Equals(arg, StringView{"--no-color"})) + else if (Strings::Equals(arg, StringView{"--help"})) { - settings.useColor = false; + Info("USAGE: [options]"); + Info(""); + Info("Options:"); + Info(" --version, Print version of Pipe"); + Info(" --help, Print usage and exit."); + Info( + " --skip=, Skip all 'describe' and 'it' containing substring"); + Info( + " --only=, Run only 'describe' and 'it' containing substring"); + Info(" --break-on-failure, Stop test run on first failing test"); + Info(" --dry-run, Skip all tests. Use to list available tests"); + Info(" --report-timing, Instruct reporter to report timing information"); + Info(" --reporter=, Select reporter: dots, info, singleline, spec"); + Info(" --colorizer=, Select color theme: off"); + Info(" --no-color, Disable colorized output"); + return 0; } - else if (Strings::StartsWith(arg, StringView{"--"})) + else if (Strings::Equals(arg, StringView{"--break-on-failure"})) + { + settings.breakOnFailure = true; + } + else if (Strings::Equals(arg, StringView{"--list"}) + || Strings::Equals(arg, StringView{"-l"}) + || Strings::Equals(arg, StringView{"--dry-run"})) { - Warning("PipeTest: unknown argument '{}'. Ignoring.", arg); + settings.dryRun = true; } - else if (settings.filter.empty()) + else if (Strings::Equals(arg, StringView{"--no-color"}) + || Strings::Equals(arg, StringView{"-c"})) + { + settings.useColor = false; + } + else if (Strings::StartsWith(arg, StringView{"--"})) { - settings.filter = arg; + // Warning("PipeTest: unknown argument '{}'. Ignoring.", arg); } } return RunTests(settings); diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 343194f5..a87397a4 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -11,4 +11,4 @@ pipe_target_shared_output_directory(PipeTests) target_link_libraries(PipeTests PUBLIC Pipe PipeTest) pipe_add_sanitizers(PipeTests) -add_test(NAME PipeTests COMMAND $) +add_test(NAME PipeTests COMMAND $ --reporter=spec) diff --git a/Tests/Core/Function.spec.cpp b/Tests/Core/Function.spec.cpp index 88154fd8..25f865f4 100644 --- a/Tests/Core/Function.spec.cpp +++ b/Tests/Core/Function.spec.cpp @@ -1,8 +1,8 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include #include +#include using namespace p; @@ -30,60 +30,60 @@ inline bool Foo::called = false; Spec("Core.Function", []() { -It("Can create empty", []() -{ - TFunction func{}; - Expect(func.IsBound()).ToEqual(false); - Expect(bool(func)).ToEqual(false); -}); - -It("Can create from function", []() -{ - TFunction func{Foo::StaticFunc}; + It("Can create empty", []() + { + TFunction func{}; + Expect(func.IsBound()).ToEqual(false); + Expect(bool(func)).ToEqual(false); + }); - Expect(func.IsBound()).ToEqual(true); -}); + It("Can create from function", []() + { + TFunction func{Foo::StaticFunc}; -It("Can compare functions", []() -{ - TFunction func1{Foo::StaticFunc}; - TFunction func2{Foo::StaticFunc}; - TFunction func3{&Foo::StaticFunc}; + Expect(func.IsBound()).ToEqual(true); + }); - TFunction func4{}; + It("Can compare functions", []() + { + TFunction func1{Foo::StaticFunc}; + TFunction func2{Foo::StaticFunc}; + TFunction func3{&Foo::StaticFunc}; - TFunction func5{Foo::OtherStaticFunc}; + TFunction func4{}; - Expect(func1 == func2).ToEqual(true); - Expect(func1 == func3).ToEqual(true); - Expect(func1 == func4).ToEqual(false); - // Expect(func1 == func5).ToEqual(false); -}); + TFunction func5{Foo::OtherStaticFunc}; -It("Can call static functions", []() -{ - TFunction func1{Foo::StaticFunc}; - TFunction func2{&Foo::StaticFunc}; + Expect(func1 == func2).ToEqual(true); + Expect(func1 == func3).ToEqual(true); + Expect(func1 == func4).ToEqual(false); + // Expect(func1 == func5).ToEqual(false); + }); - Foo::called = false; - func1(); - Expect(Foo::called).ToEqual(true); + It("Can call static functions", []() + { + TFunction func1{Foo::StaticFunc}; + TFunction func2{&Foo::StaticFunc}; - Foo::called = false; - func2(); - Expect(Foo::called).ToEqual(true); -}); + Foo::called = false; + func1(); + Expect(Foo::called).ToEqual(true); -It("Can call lambda functions", []() -{ - static bool called; - called = false; + Foo::called = false; + func2(); + Expect(Foo::called).ToEqual(true); + }); - TFunction func = []() + It("Can call lambda functions", []() { - called = true; - }; - func(); - Expect(called).ToEqual(true); -}); + static bool called; + called = false; + + TFunction func = []() + { + called = true; + }; + func(); + Expect(called).ToEqual(true); + }); }); diff --git a/Tests/Core/OwnPtr.spec.cpp b/Tests/Core/OwnPtr.spec.cpp index 3aea1bf0..9c93517d 100644 --- a/Tests/Core/OwnPtr.spec.cpp +++ b/Tests/Core/OwnPtr.spec.cpp @@ -1,7 +1,7 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include +#include using namespace p; @@ -36,307 +36,307 @@ struct MockStruct template using PtrBuilder = TestPtrBuilder; - bool bCalledNew = false; + bool bCalledNew = false; inline static bool bCalledDelete = false; }; Spec("Core.OwnPtr", []() { -Describe("Owner pointer", []() -{ - It("Can initialize to empty", []() - { - TOwnPtr ptr; - Expect(ptr.IsValid()).ToEqual(false); - Expect(ptr.Get()).ToEqual(nullptr); - }); - - It("Can instantiate", []() - { - TOwnPtr ptr = MakeOwned(); - Expect(ptr.IsValid()).ToEqual(true); - Expect(ptr.Get()).ToNotEqual(nullptr); - }); - - It("Owner can release", []() + Describe("Owner pointer", []() { - TOwnPtr owner = MakeOwned(); - Expect(owner.IsValid()).ToEqual(true); + It("Can initialize to empty", []() + { + TOwnPtr ptr; + Expect(ptr.IsValid()).ToEqual(false); + Expect(ptr.Get()).ToEqual(nullptr); + }); - owner.Delete(); - Expect(owner.IsValid()).ToEqual(false); - }); + It("Can instantiate", []() + { + TOwnPtr ptr = MakeOwned(); + Expect(ptr.IsValid()).ToEqual(true); + Expect(ptr.Get()).ToNotEqual(nullptr); + }); - It("Owner is released when destroyed", []() - { - TPtr ptr; + It("Owner can release", []() { TOwnPtr owner = MakeOwned(); + Expect(owner.IsValid()).ToEqual(true); - ptr = owner; - Expect(ptr.IsValid()).ToEqual(true); - } - Expect(ptr.IsValid()).ToEqual(false); - }); + owner.Delete(); + Expect(owner.IsValid()).ToEqual(false); + }); - Describe("Ptr Builder", []() - { - It("Calls custom new", []() + It("Owner is released when destroyed", []() { - auto owner = MakeOwned(); - Expect(owner->bCalledNew).ToEqual(true); + TPtr ptr; + { + TOwnPtr owner = MakeOwned(); + + ptr = owner; + Expect(ptr.IsValid()).ToEqual(true); + } + Expect(ptr.IsValid()).ToEqual(false); }); - It("Calls custom delete", []() + Describe("Ptr Builder", []() { - MockStruct::bCalledDelete = false; - auto owner = MakeOwned(); - Expect(MockStruct::bCalledDelete).ToEqual(false); - owner.Delete(); - Expect(MockStruct::bCalledDelete).ToEqual(true); + It("Calls custom new", []() + { + auto owner = MakeOwned(); + Expect(owner->bCalledNew).ToEqual(true); + }); + + It("Calls custom delete", []() + { + MockStruct::bCalledDelete = false; + auto owner = MakeOwned(); + Expect(MockStruct::bCalledDelete).ToEqual(false); + owner.Delete(); + Expect(MockStruct::bCalledDelete).ToEqual(true); + }); }); }); -}); - -Describe("Weak pointer", []() -{ - It("Can initialize to empty", []() - { - TPtr ptr; - Expect(ptr.IsValid()).ToEqual(false); - Expect(ptr.Get()).ToEqual(nullptr); - }); - It("Can initialize from owner", []() + Describe("Weak pointer", []() { - TOwnPtr owner = MakeOwned(); - TPtr ptr = owner; + It("Can initialize to empty", []() + { + TPtr ptr; + Expect(ptr.IsValid()).ToEqual(false); + Expect(ptr.Get()).ToEqual(nullptr); + }); - Expect(ptr.IsValid()).ToEqual(true); - Expect(ptr.Get()).ToNotEqual(nullptr); - }); + It("Can initialize from owner", []() + { + TOwnPtr owner = MakeOwned(); + TPtr ptr = owner; - It("Can copy from other weak", []() - { - TOwnPtr owner = MakeOwned(); - auto* raw = owner.Get(); - TPtr ptr = owner; - TPtr ptr2 = ptr; - - Expect(ptr2.IsValid()).ToEqual(true); - Expect(ptr.Get()).ToEqual(raw); - Expect(ptr2.Get()).ToEqual(raw); - }); + Expect(ptr.IsValid()).ToEqual(true); + Expect(ptr.Get()).ToNotEqual(nullptr); + }); - It("Can move from other weak", []() - { - TOwnPtr owner = MakeOwned(); - auto* raw = owner.Get(); - auto weak = owner.AsPtr(); - auto movedWeak = Move(weak); + It("Can copy from other weak", []() + { + TOwnPtr owner = MakeOwned(); + auto* raw = owner.Get(); + TPtr ptr = owner; + TPtr ptr2 = ptr; - Expect(weak.IsValid()).ToEqual(false); - Expect(movedWeak.IsValid()).ToEqual(true); + Expect(ptr2.IsValid()).ToEqual(true); + Expect(ptr.Get()).ToEqual(raw); + Expect(ptr2.Get()).ToEqual(raw); + }); - Expect(weak.Get()).ToEqual(nullptr); - Expect(movedWeak.Get()).ToEqual(raw); - }); + It("Can move from other weak", []() + { + TOwnPtr owner = MakeOwned(); + auto* raw = owner.Get(); + auto weak = owner.AsPtr(); + auto movedWeak = Move(weak); - It("Ptr is null after IsValid() == false", []() - { - TOwnPtr owner = MakeOwned(); - TPtr ptr = owner; - owner.Delete(); + Expect(weak.IsValid()).ToEqual(false); + Expect(movedWeak.IsValid()).ToEqual(true); - Expect(ptr.Get()).ToNotEqual(nullptr); + Expect(weak.Get()).ToEqual(nullptr); + Expect(movedWeak.Get()).ToEqual(raw); + }); - Expect(ptr.IsValid()).ToEqual(false); - Expect(ptr.Get()).ToEqual(nullptr); - }); -}); + It("Ptr is null after IsValid() == false", []() + { + TOwnPtr owner = MakeOwned(); + TPtr ptr = owner; + owner.Delete(); -Describe("Comparisons", []() -{ - It("Owner can equal Owner", []() - { - auto owner = MakeOwned(); - auto owner2 = MakeOwned(); - TOwnPtr ownerEmpty; - - Expect(owner == owner).ToEqual(true); - Expect(owner == owner2).ToEqual(false); - Expect(ownerEmpty == ownerEmpty).ToEqual(true); - Expect(owner == ownerEmpty).ToEqual(false); - - Expect(owner != owner).ToEqual(false); - Expect(owner != owner2).ToEqual(true); - Expect(ownerEmpty != ownerEmpty).ToEqual(false); - Expect(owner != ownerEmpty).ToEqual(true); - }); + Expect(ptr.Get()).ToNotEqual(nullptr); - It("Owner can equal Weak", []() - { - auto owner = MakeOwned(); - auto owner2 = MakeOwned(); - auto weak = owner.AsPtr(); - TOwnPtr ownerEmpty; - TPtr weakEmpty; - - Expect(owner == weak).ToEqual(true); - Expect(owner2 == weak).ToEqual(false); - Expect(ownerEmpty == weak).ToEqual(false); - Expect(ownerEmpty == weakEmpty).ToEqual(true); - - Expect(owner != weak).ToEqual(false); - Expect(owner2 != weak).ToEqual(true); - Expect(ownerEmpty != weak).ToEqual(true); - Expect(ownerEmpty != weakEmpty).ToEqual(false); + Expect(ptr.IsValid()).ToEqual(false); + Expect(ptr.Get()).ToEqual(nullptr); + }); }); - It("Weak can equal Weak", []() + Describe("Comparisons", []() { - auto owner = MakeOwned(); - auto owner2 = MakeOwned(); - auto weak = owner.AsPtr(); - auto weak2 = owner2.AsPtr(); - TPtr weakEmpty; - - Expect(weak == weak).ToEqual(true); - Expect(weak2 == weak).ToEqual(false); - Expect(weakEmpty == weak).ToEqual(false); - Expect(weakEmpty == weakEmpty).ToEqual(true); - - Expect(weak != weak).ToEqual(false); - Expect(weak2 != weak).ToEqual(true); - Expect(weakEmpty != weak).ToEqual(true); - Expect(weakEmpty != weakEmpty).ToEqual(false); - }); + It("Owner can equal Owner", []() + { + auto owner = MakeOwned(); + auto owner2 = MakeOwned(); + TOwnPtr ownerEmpty; + + Expect(owner == owner).ToEqual(true); + Expect(owner == owner2).ToEqual(false); + Expect(ownerEmpty == ownerEmpty).ToEqual(true); + Expect(owner == ownerEmpty).ToEqual(false); + + Expect(owner != owner).ToEqual(false); + Expect(owner != owner2).ToEqual(true); + Expect(ownerEmpty != ownerEmpty).ToEqual(false); + Expect(owner != ownerEmpty).ToEqual(true); + }); - It("Weak can equal Owner", []() - { - auto owner = MakeOwned(); - auto owner2 = MakeOwned(); - auto weak = owner.AsPtr(); - auto weak2 = owner2.AsPtr(); - TOwnPtr ownerEmpty; - TPtr weakEmpty; - - Expect(weak == owner).ToEqual(true); - Expect(weak2 == owner).ToEqual(false); - Expect(weakEmpty == owner).ToEqual(false); - Expect(weakEmpty == ownerEmpty).ToEqual(true); - - Expect(weak != owner).ToEqual(false); - Expect(weak2 != owner).ToEqual(true); - Expect(weakEmpty != owner).ToEqual(true); - Expect(weakEmpty != ownerEmpty).ToEqual(false); - }); -}); + It("Owner can equal Weak", []() + { + auto owner = MakeOwned(); + auto owner2 = MakeOwned(); + auto weak = owner.AsPtr(); + TOwnPtr ownerEmpty; + TPtr weakEmpty; + + Expect(owner == weak).ToEqual(true); + Expect(owner2 == weak).ToEqual(false); + Expect(ownerEmpty == weak).ToEqual(false); + Expect(ownerEmpty == weakEmpty).ToEqual(true); + + Expect(owner != weak).ToEqual(false); + Expect(owner2 != weak).ToEqual(true); + Expect(ownerEmpty != weak).ToEqual(true); + Expect(ownerEmpty != weakEmpty).ToEqual(false); + }); -Describe("Counter", []() -{ - It("Adds weaks", []() - { - auto owner = MakeOwned(); - const auto* counter = owner.GetCounter(); - Expect(counter->weakCount).ToEqual(0u); + It("Weak can equal Weak", []() + { + auto owner = MakeOwned(); + auto owner2 = MakeOwned(); + auto weak = owner.AsPtr(); + auto weak2 = owner2.AsPtr(); + TPtr weakEmpty; + + Expect(weak == weak).ToEqual(true); + Expect(weak2 == weak).ToEqual(false); + Expect(weakEmpty == weak).ToEqual(false); + Expect(weakEmpty == weakEmpty).ToEqual(true); + + Expect(weak != weak).ToEqual(false); + Expect(weak2 != weak).ToEqual(true); + Expect(weakEmpty != weak).ToEqual(true); + Expect(weakEmpty != weakEmpty).ToEqual(false); + }); - auto weak = owner.AsPtr(); - Expect(counter->weakCount).ToEqual(1u); + It("Weak can equal Owner", []() + { + auto owner = MakeOwned(); + auto owner2 = MakeOwned(); + auto weak = owner.AsPtr(); + auto weak2 = owner2.AsPtr(); + TOwnPtr ownerEmpty; + TPtr weakEmpty; + + Expect(weak == owner).ToEqual(true); + Expect(weak2 == owner).ToEqual(false); + Expect(weakEmpty == owner).ToEqual(false); + Expect(weakEmpty == ownerEmpty).ToEqual(true); + + Expect(weak != owner).ToEqual(false); + Expect(weak2 != owner).ToEqual(true); + Expect(weakEmpty != owner).ToEqual(true); + Expect(weakEmpty != ownerEmpty).ToEqual(false); + }); }); - It("Removes weaks", []() + Describe("Counter", []() { - auto owner = MakeOwned(); - const auto* counter = owner.GetCounter(); + It("Adds weaks", []() { + auto owner = MakeOwned(); + const auto* counter = owner.GetCounter(); + Expect(counter->weakCount).ToEqual(0u); + auto weak = owner.AsPtr(); Expect(counter->weakCount).ToEqual(1u); - } - Expect(counter->weakCount).ToEqual(0u); - }); + }); - It("Removes with owner release", []() - { - auto owner = MakeOwned(); - Expect(owner.GetCounter()).ToNotEqual(nullptr); + It("Removes weaks", []() + { + auto owner = MakeOwned(); + const auto* counter = owner.GetCounter(); + { + auto weak = owner.AsPtr(); + Expect(counter->weakCount).ToEqual(1u); + } + Expect(counter->weakCount).ToEqual(0u); + }); - owner.Delete(); - Expect(owner.GetCounter()).ToEqual(nullptr); - }); + It("Removes with owner release", []() + { + auto owner = MakeOwned(); + Expect(owner.GetCounter()).ToNotEqual(nullptr); - It("Removes with no weakCount left", []() - { - auto owner = MakeOwned(); - auto weak = owner.AsPtr(); - Expect(weak.GetCounter()).ToNotEqual(nullptr); + owner.Delete(); + Expect(owner.GetCounter()).ToEqual(nullptr); + }); + + It("Removes with no weakCount left", []() + { + auto owner = MakeOwned(); + auto weak = owner.AsPtr(); + Expect(weak.GetCounter()).ToNotEqual(nullptr); - owner.Delete(); - Expect(weak.GetCounter()).ToNotEqual(nullptr); + owner.Delete(); + Expect(weak.GetCounter()).ToNotEqual(nullptr); - weak.Reset(); - Expect(owner.GetCounter()).ToEqual(nullptr); + weak.Reset(); + Expect(owner.GetCounter()).ToEqual(nullptr); + }); }); -}); -It("Can detect custom PtrBuilders", []() -{ - Expect(p::HasCustomPtrBuilder::value).ToEqual(false); - Expect(p::HasCustomPtrBuilder::value).ToEqual(true); -}); + It("Can detect custom PtrBuilders", []() + { + Expect(p::HasCustomPtrBuilder::value).ToEqual(false); + Expect(p::HasCustomPtrBuilder::value).ToEqual(true); + }); -Describe("Typeless pointer", []() -{ - It("Can convert to OwnPtr from TOwnPtr", []() + Describe("Typeless pointer", []() { - TOwnPtr typedPtr = MakeOwned(); - Expect(typedPtr.IsValid()).ToEqual(true); + It("Can convert to OwnPtr from TOwnPtr", []() + { + TOwnPtr typedPtr = MakeOwned(); + Expect(typedPtr.IsValid()).ToEqual(true); - EmptyStruct* data = typedPtr.Get(); + EmptyStruct* data = typedPtr.Get(); - OwnPtr ptr = Move(typedPtr); - Expect(typedPtr.IsValid()).ToEqual(false); - Expect(ptr.IsValid()).ToEqual(true); - Expect(ptr.Get()).ToEqual(data); - Expect(ptr.Get()).ToEqual(data); - }); + OwnPtr ptr = Move(typedPtr); + Expect(typedPtr.IsValid()).ToEqual(false); + Expect(ptr.IsValid()).ToEqual(true); + Expect(ptr.Get()).ToEqual(data); + Expect(ptr.Get()).ToEqual(data); + }); - It("Can convert to TOwnPtr from OwnPtr", []() - { - OwnPtr ptr = MakeOwned(); - Expect(ptr.IsValid()).ToEqual(true); - auto* data = ptr.Get(); - - TOwnPtr typedPtr = Move(ptr); - Expect(ptr.IsValid()).ToEqual(false); - Expect(typedPtr.IsValid()).ToEqual(true); - Expect(typedPtr.Get()).ToEqual(data); - }); + It("Can convert to TOwnPtr from OwnPtr", []() + { + OwnPtr ptr = MakeOwned(); + Expect(ptr.IsValid()).ToEqual(true); + auto* data = ptr.Get(); - It("Can move", []() - { - OwnPtr ptr1 = MakeOwned(); - Expect(ptr1.IsValid()).ToEqual(true); - Expect(ptr1.GetId()).ToEqual(GetTypeId()); - auto* data = ptr1.Get(); - - OwnPtr ptr2 = Move(ptr1); - Expect(ptr1.IsValid()).ToEqual(false); - Expect(ptr1.Get()).ToEqual(nullptr); - Expect(ptr1.GetId()).ToEqual(TypeId::None()); - - Expect(ptr2.IsValid()).ToEqual(true); - Expect(ptr2.Get()).ToEqual(data); - Expect(ptr2.GetId()).ToEqual(GetTypeId()); - }); + TOwnPtr typedPtr = Move(ptr); + Expect(ptr.IsValid()).ToEqual(false); + Expect(typedPtr.IsValid()).ToEqual(true); + Expect(typedPtr.Get()).ToEqual(data); + }); - It("Cant retrive invalid types", []() - { - OwnPtr ptr = MakeOwned(); - Expect(ptr.Get()).ToNotEqual(nullptr); - Expect(ptr.Get()).ToEqual(nullptr); + It("Can move", []() + { + OwnPtr ptr1 = MakeOwned(); + Expect(ptr1.IsValid()).ToEqual(true); + Expect(ptr1.GetId()).ToEqual(GetTypeId()); + auto* data = ptr1.Get(); + + OwnPtr ptr2 = Move(ptr1); + Expect(ptr1.IsValid()).ToEqual(false); + Expect(ptr1.Get()).ToEqual(nullptr); + Expect(ptr1.GetId()).ToEqual(TypeId::None()); + + Expect(ptr2.IsValid()).ToEqual(true); + Expect(ptr2.Get()).ToEqual(data); + Expect(ptr2.GetId()).ToEqual(GetTypeId()); + }); + + It("Cant retrive invalid types", []() + { + OwnPtr ptr = MakeOwned(); + Expect(ptr.Get()).ToNotEqual(nullptr); + Expect(ptr.Get()).ToEqual(nullptr); + }); }); }); -}); diff --git a/Tests/Core/PageBuffer.spec.cpp b/Tests/Core/PageBuffer.spec.cpp index 71a6a9e7..91cac829 100644 --- a/Tests/Core/PageBuffer.spec.cpp +++ b/Tests/Core/PageBuffer.spec.cpp @@ -2,8 +2,8 @@ #include "PipeMemory.h" -#include #include +#include using namespace p; @@ -27,80 +27,80 @@ struct Dummy Spec("ECS.PageBuffer", []() { -It("Can reserve", []() -{ - TPageBuffer buffer{GetCurrentArena()}; - - Expect(buffer.GetPagesSize()).ToEqual(0); - Expect(buffer.Capacity()).ToEqual(0); - - buffer.Reserve(2); - Expect(buffer.GetPagesSize()).ToEqual(1); - Expect(buffer.Capacity()).ToEqual(2); + It("Can reserve", []() + { + TPageBuffer buffer{GetCurrentArena()}; - buffer.Reserve(6); - Expect(buffer.GetPagesSize()).ToEqual(3); - Expect(buffer.Capacity()).ToEqual(6); -}); + Expect(buffer.GetPagesSize()).ToEqual(0); + Expect(buffer.Capacity()).ToEqual(0); -It("Can shrink", []() -{ - TPageBuffer buffer{GetCurrentArena()}; - buffer.Reserve(7); - Expect(buffer.GetPagesSize()).ToEqual(4); - - buffer.Shrink(4); - Expect(buffer.GetPagesSize()).ToEqual(2); - Expect(buffer.Capacity()).ToEqual(4); -}); + buffer.Reserve(2); + Expect(buffer.GetPagesSize()).ToEqual(1); + Expect(buffer.Capacity()).ToEqual(2); -It("Can insert", []() -{ - TPageBuffer buffer{GetCurrentArena()}; - buffer.Reserve(4); + buffer.Reserve(6); + Expect(buffer.GetPagesSize()).ToEqual(3); + Expect(buffer.Capacity()).ToEqual(6); + }); - buffer.Insert(0); - Expect(buffer[0].created).ToEqual(true); - Expect(buffer[0].destroyed).ToEqual(false); + It("Can shrink", []() + { + TPageBuffer buffer{GetCurrentArena()}; + buffer.Reserve(7); + Expect(buffer.GetPagesSize()).ToEqual(4); - buffer.Insert(3); - Expect(buffer[3].created).ToEqual(true); - Expect(buffer[3].destroyed).ToEqual(false); -}); + buffer.Shrink(4); + Expect(buffer.GetPagesSize()).ToEqual(2); + Expect(buffer.Capacity()).ToEqual(4); + }); -It("Can remove", []() -{ - TPageBuffer buffer{GetCurrentArena()}; - buffer.Reserve(4); + It("Can insert", []() + { + TPageBuffer buffer{GetCurrentArena()}; + buffer.Reserve(4); - buffer.Insert(0); - buffer.Insert(3); + buffer.Insert(0); + Expect(buffer[0].created).ToEqual(true); + Expect(buffer[0].destroyed).ToEqual(false); - buffer.RemoveAt(0); - // Temporarily disabled due to GCC only test fail - // Expect(buffer[0].destroyed).ToEqual(true); + buffer.Insert(3); + Expect(buffer[3].created).ToEqual(true); + Expect(buffer[3].destroyed).ToEqual(false); + }); - buffer.RemoveAt(3); - // Temporarily disabled due to GCC only test fail - // Expect(buffer[3].destroyed).ToEqual(true); -}); + It("Can remove", []() + { + TPageBuffer buffer{GetCurrentArena()}; + buffer.Reserve(4); -It("Points to correct page", []() -{ - TPageBuffer buffer{GetCurrentArena()}; - buffer.Reserve(7); + buffer.Insert(0); + buffer.Insert(3); - buffer.AssurePage(0); - Expect(buffer.AssurePage(0)).ToNotEqual(nullptr); - Expect(buffer.AssurePage(1)).ToNotEqual(nullptr); - Expect(buffer.AssurePage(2)).ToNotEqual(nullptr); - Expect(buffer.AssurePage(5)).ToNotEqual(nullptr); + buffer.RemoveAt(0); + // Temporarily disabled due to GCC only test fail + // Expect(buffer[0].destroyed).ToEqual(true); + buffer.RemoveAt(3); + // Temporarily disabled due to GCC only test fail + // Expect(buffer[3].destroyed).ToEqual(true); + }); - Expect(buffer.FindPage(0)).ToNotEqual(nullptr); - Expect(buffer.FindPage(1)).ToNotEqual(nullptr); - Expect(buffer.FindPage(2)).ToNotEqual(nullptr); - Expect(buffer.FindPage(5)).ToNotEqual(nullptr); - Expect(buffer.FindPage(6)).ToEqual(nullptr); -}); + It("Points to correct page", []() + { + TPageBuffer buffer{GetCurrentArena()}; + buffer.Reserve(7); + + buffer.AssurePage(0); + Expect(buffer.AssurePage(0)).ToNotEqual(nullptr); + Expect(buffer.AssurePage(1)).ToNotEqual(nullptr); + Expect(buffer.AssurePage(2)).ToNotEqual(nullptr); + Expect(buffer.AssurePage(5)).ToNotEqual(nullptr); + + + Expect(buffer.FindPage(0)).ToNotEqual(nullptr); + Expect(buffer.FindPage(1)).ToNotEqual(nullptr); + Expect(buffer.FindPage(2)).ToNotEqual(nullptr); + Expect(buffer.FindPage(5)).ToNotEqual(nullptr); + Expect(buffer.FindPage(6)).ToEqual(nullptr); + }); }); diff --git a/Tests/Core/PlatformProcess.spec.cpp b/Tests/Core/PlatformProcess.spec.cpp index 60de5581..e928fdcf 100644 --- a/Tests/Core/PlatformProcess.spec.cpp +++ b/Tests/Core/PlatformProcess.spec.cpp @@ -3,8 +3,8 @@ #include "Pipe/Core/Log.h" #include "Pipe/Core/Subprocess.h" -#include #include +#include using namespace p; @@ -12,12 +12,12 @@ using namespace p; Spec("Core.Subprocess", []() { -It("Can run process", []() -{ - Expect(p::RunProcess({""}).IsSet()).ToEqual(false); + It("Can run process", []() + { + Expect(p::RunProcess({""}).IsSet()).ToEqual(false); #if defined(_MSC_VER) // Test with a silent command (no stdout) - Expect(p::RunProcess({"cmd", "/c", "exit", "0"}).IsSet()).ToEqual(true); + Expect(p::RunProcess({"cmd", "/c", "exit", "0"}).IsSet()).ToEqual(true); #endif -}); + }); }); diff --git a/Tests/Core/Set.spec.cpp b/Tests/Core/Set.spec.cpp index 4c96d06a..f0da4e25 100644 --- a/Tests/Core/Set.spec.cpp +++ b/Tests/Core/Set.spec.cpp @@ -1,7 +1,7 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include +#include using namespace p; @@ -16,74 +16,74 @@ struct TypeOfSize Spec("Core.Set", []() { -It("Can initialize", []() -{ - TSet data1{}; - TSet data2(u32(3)); - TSet data3{5, 4, 3, 2}; - - Expect(data1.Size()).ToEqual(0); - Expect(data2.Size()).ToEqual(0); - Expect(data3.Size()).ToEqual(4); - - Expect(data3[2]).ToEqual(2); - Expect(data3[3]).ToEqual(3); - Expect(data3[4]).ToEqual(4); - Expect(data3[5]).ToEqual(5); -}); - -It("Can copy", []() -{ - TSet data1{6, 5}; - TSet data2{data1}; - Expect(data1.Size()).ToEqual(2); - Expect(data2.Size()).ToEqual(2); - Expect(data2[5]).ToEqual(5); - Expect(data2[6]).ToEqual(6); - - TSet data3{6, 5}; - TSet data4; - data4 = data3; - Expect(data3.Size()).ToEqual(2); - Expect(data4.Size()).ToEqual(2); - Expect(data4[5]).ToEqual(5); - Expect(data4[6]).ToEqual(6); -}); - -It("Can move", []() -{ - TSet data1{4, 3}; - Expect(data1.Size()).ToEqual(2); - - TSet data2{Move(data1)}; - Expect(data1.Size()).ToEqual(0); - Expect(data2.Size()).ToEqual(2); - - TSet data3{4, 3}; - TSet data4; - Expect(data3.Size()).ToEqual(2); - Expect(data4.Size()).ToEqual(0); - - data4 = Move(data3); - Expect(data3.Size()).ToEqual(0); - Expect(data4.Size()).ToEqual(2); - Expect(data4[3]).ToEqual(3); - Expect(data4[4]).ToEqual(4); -}); - -It("Can access data", []() -{ - TSet data1; - TSet data2{1, 5}; - - Expect(data1.Size()).ToEqual(0); - Expect(data2.Size()).ToBeGreaterOrEqual(2); - - Expect(data1.Contains(3)).ToEqual(false); - Expect(data2.Contains(1)).ToEqual(true); - Expect(data2.Contains(5)).ToEqual(true); - Expect(data2.Contains(34)).ToEqual(false); - Expect(data2[1]).ToEqual(1); - Expect(data2[5]).ToEqual(5); -}); + It("Can initialize", []() + { + TSet data1{}; + TSet data2(u32(3)); + TSet data3{5, 4, 3, 2}; + + Expect(data1.Size()).ToEqual(0); + Expect(data2.Size()).ToEqual(0); + Expect(data3.Size()).ToEqual(4); + + Expect(data3[2]).ToEqual(2); + Expect(data3[3]).ToEqual(3); + Expect(data3[4]).ToEqual(4); + Expect(data3[5]).ToEqual(5); + }); + + It("Can copy", []() + { + TSet data1{6, 5}; + TSet data2{data1}; + Expect(data1.Size()).ToEqual(2); + Expect(data2.Size()).ToEqual(2); + Expect(data2[5]).ToEqual(5); + Expect(data2[6]).ToEqual(6); + + TSet data3{6, 5}; + TSet data4; + data4 = data3; + Expect(data3.Size()).ToEqual(2); + Expect(data4.Size()).ToEqual(2); + Expect(data4[5]).ToEqual(5); + Expect(data4[6]).ToEqual(6); + }); + + It("Can move", []() + { + TSet data1{4, 3}; + Expect(data1.Size()).ToEqual(2); + + TSet data2{Move(data1)}; + Expect(data1.Size()).ToEqual(0); + Expect(data2.Size()).ToEqual(2); + + TSet data3{4, 3}; + TSet data4; + Expect(data3.Size()).ToEqual(2); + Expect(data4.Size()).ToEqual(0); + + data4 = Move(data3); + Expect(data3.Size()).ToEqual(0); + Expect(data4.Size()).ToEqual(2); + Expect(data4[3]).ToEqual(3); + Expect(data4[4]).ToEqual(4); + }); + + It("Can access data", []() + { + TSet data1; + TSet data2{1, 5}; + + Expect(data1.Size()).ToEqual(0); + Expect(data2.Size()).ToBeGreaterOrEqual(2); + + Expect(data1.Contains(3)).ToEqual(false); + Expect(data2.Contains(1)).ToEqual(true); + Expect(data2.Contains(5)).ToEqual(true); + Expect(data2.Contains(34)).ToEqual(false); + Expect(data2[1]).ToEqual(1); + Expect(data2[5]).ToEqual(5); + }); }); diff --git a/Tests/Core/SpinLock.spec.cpp b/Tests/Core/SpinLock.spec.cpp index 6701ac4f..5bfb2f55 100644 --- a/Tests/Core/SpinLock.spec.cpp +++ b/Tests/Core/SpinLock.spec.cpp @@ -1,7 +1,7 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include +#include #include #include @@ -13,170 +13,170 @@ using namespace p; Spec("Core.SpinLock", []() { -Describe("SpinLock", []() -{ - It("Acquires and releases exclusively", []() + Describe("SpinLock", []() { - SpinLock lock; - ScopedLock guard(lock); + It("Acquires and releases exclusively", []() + { + SpinLock lock; + ScopedLock guard(lock); - Expect(lock.Locked()).ToBeTrue(); - Expect(lock.TryLock()).ToBeFalse(); - }); + Expect(lock.Locked()).ToBeTrue(); + Expect(lock.TryLock()).ToBeFalse(); + }); - It("Allows serialized writers to increment a counter", []() - { - SpinLock lock; - i32 counter = 0; + It("Allows serialized writers to increment a counter", []() + { + SpinLock lock; + i32 counter = 0; - constexpr i32 kThreads = 4; - constexpr i32 kPerThread = 10'000; + constexpr i32 kThreads = 4; + constexpr i32 kPerThread = 10'000; - std::vector threads; - std::atomic start{false}; - for (i32 t = 0; t < kThreads; ++t) - { - threads.emplace_back([&]() + std::vector threads; + std::atomic start{false}; + for (i32 t = 0; t < kThreads; ++t) { - while (!start.load(std::memory_order_acquire)) - {} - for (i32 i = 0; i < kPerThread; ++i) + threads.emplace_back([&]() { - ScopedLock guard(lock); - ++counter; - } - }); - } - - start.store(true, std::memory_order_release); - for (auto& thread : threads) - { - thread.join(); - } + while (!start.load(std::memory_order_acquire)) + {} + for (i32 i = 0; i < kPerThread; ++i) + { + ScopedLock guard(lock); + ++counter; + } + }); + } + + start.store(true, std::memory_order_release); + for (auto& thread : threads) + { + thread.join(); + } - Expect(counter).ToEqual(kThreads * kPerThread); + Expect(counter).ToEqual(kThreads * kPerThread); + }); }); -}); -Describe("SharedSpinLock", []() -{ - It("Exclusive lock excludes a second exclusive lock", []() + Describe("SharedSpinLock", []() { - SharedSpinLock lock; - ExclusiveScopedLock writer(lock); + It("Exclusive lock excludes a second exclusive lock", []() + { + SharedSpinLock lock; + ExclusiveScopedLock writer(lock); - Expect(lock.TryLockExclusive()).ToBeFalse(); - }); + Expect(lock.TryLockExclusive()).ToBeFalse(); + }); - It("Exclusive lock excludes shared locks", []() - { - SharedSpinLock lock; - ExclusiveScopedLock writer(lock); + It("Exclusive lock excludes shared locks", []() + { + SharedSpinLock lock; + ExclusiveScopedLock writer(lock); - Expect(lock.TryLockShared()).ToBeFalse(); - }); + Expect(lock.TryLockShared()).ToBeFalse(); + }); - It("Shared lock excludes an exclusive lock", []() - { - SharedSpinLock lock; - SharedScopedLock reader(lock); + It("Shared lock excludes an exclusive lock", []() + { + SharedSpinLock lock; + SharedScopedLock reader(lock); - Expect(lock.TryLockExclusive()).ToBeFalse(); - }); + Expect(lock.TryLockExclusive()).ToBeFalse(); + }); - It("Allows multiple overlapping shared locks", []() - { - SharedSpinLock lock; + It("Allows multiple overlapping shared locks", []() + { + SharedSpinLock lock; - SharedScopedLock r1(lock); - SharedScopedLock r2(lock); - SharedScopedLock r3(lock); + SharedScopedLock r1(lock); + SharedScopedLock r2(lock); + SharedScopedLock r3(lock); - // Readers coexist: shared still acquirable. - Expect(lock.TryLockShared()).ToBeTrue(); - lock.UnlockShared(); + // Readers coexist: shared still acquirable. + Expect(lock.TryLockShared()).ToBeTrue(); + lock.UnlockShared(); - Expect(lock.TryLockExclusive()).ToBeFalse(); - }); + Expect(lock.TryLockExclusive()).ToBeFalse(); + }); - It("Writers exclude each other", []() - { - SharedSpinLock lock; + It("Writers exclude each other", []() + { + SharedSpinLock lock; - ExclusiveScopedLock w1(lock); - Expect(lock.TryLockExclusive()).ToBeFalse(); - }); + ExclusiveScopedLock w1(lock); + Expect(lock.TryLockExclusive()).ToBeFalse(); + }); - It("Writes under exclusive lock are mutually excluded", []() - { - SharedSpinLock lock; - i32 counter = 0; + It("Writes under exclusive lock are mutually excluded", []() + { + SharedSpinLock lock; + i32 counter = 0; - constexpr i32 kThreads = 4; - constexpr i32 kPerThread = 10'000; + constexpr i32 kThreads = 4; + constexpr i32 kPerThread = 10'000; - std::vector threads; - std::atomic start{false}; - for (i32 t = 0; t < kThreads; ++t) - { - threads.emplace_back([&]() + std::vector threads; + std::atomic start{false}; + for (i32 t = 0; t < kThreads; ++t) { - while (!start.load(std::memory_order_acquire)) - {} - for (i32 i = 0; i < kPerThread; ++i) + threads.emplace_back([&]() { - ExclusiveScopedLock writer(lock); - ++counter; - } - }); - } - - start.store(true, std::memory_order_release); - for (auto& thread : threads) - { - thread.join(); - } + while (!start.load(std::memory_order_acquire)) + {} + for (i32 i = 0; i < kPerThread; ++i) + { + ExclusiveScopedLock writer(lock); + ++counter; + } + }); + } + + start.store(true, std::memory_order_release); + for (auto& thread : threads) + { + thread.join(); + } - Expect(counter).ToEqual(kThreads * kPerThread); - }); + Expect(counter).ToEqual(kThreads * kPerThread); + }); - It("Shared readers run concurrently without tearing shared state", []() - { - SharedSpinLock lock; - i32 value = 0; - - constexpr i32 kThreads = 4; - constexpr i32 kIterations = 10'000; - - // Shared-side readers are allowed to overlap, so they must only - // read. This just checks that many threads can take the shared - // side simultaneously without deadlocking or corrupting the lock. - std::vector threads; - std::atomic start{false}; - std::atomic reads{0}; - for (i32 t = 0; t < kThreads; ++t) + It("Shared readers run concurrently without tearing shared state", []() { - threads.emplace_back([&]() + SharedSpinLock lock; + i32 value = 0; + + constexpr i32 kThreads = 4; + constexpr i32 kIterations = 10'000; + + // Shared-side readers are allowed to overlap, so they must only + // read. This just checks that many threads can take the shared + // side simultaneously without deadlocking or corrupting the lock. + std::vector threads; + std::atomic start{false}; + std::atomic reads{0}; + for (i32 t = 0; t < kThreads; ++t) { - while (!start.load(std::memory_order_acquire)) - {} - for (i32 i = 0; i < kIterations; ++i) + threads.emplace_back([&]() { - SharedScopedLock reader(lock); - const i32 v = value; - (void)v; - reads.fetch_add(1, std::memory_order_relaxed); - } - }); - } - - start.store(true, std::memory_order_release); - for (auto& thread : threads) - { - thread.join(); - } + while (!start.load(std::memory_order_acquire)) + {} + for (i32 i = 0; i < kIterations; ++i) + { + SharedScopedLock reader(lock); + const i32 v = value; + (void)v; + reads.fetch_add(1, std::memory_order_relaxed); + } + }); + } + + start.store(true, std::memory_order_release); + for (auto& thread : threads) + { + thread.join(); + } - Expect(reads.load()).ToEqual(kThreads * kIterations); + Expect(reads.load()).ToEqual(kThreads * kIterations); + }); }); }); -}); diff --git a/Tests/Core/String.spec.cpp b/Tests/Core/String.spec.cpp index 807f3b3c..0ee3d779 100644 --- a/Tests/Core/String.spec.cpp +++ b/Tests/Core/String.spec.cpp @@ -1,9 +1,9 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include #include #include +#include #include #include @@ -20,952 +20,953 @@ static const char* arenaLongText = "This string is long enough to exceed the inl Spec("Strings", []() { -Describe("String", []() -{ - Describe("Construction", []() + Describe("String", []() { - It("Can default construct", []() + Describe("Construction", []() { - String v{}; - Expect(v.size()).ToEqual(0u); - Expect(v.empty()).ToBeTrue(); - Expect(v.length()).ToEqual(0u); - // c_str() must always return a valid pointer to a null terminator - Expect(v.c_str() != nullptr).ToBeTrue(); - Expect(v.c_str()[0]).ToEqual('\0'); - Expect(v.data() != nullptr).ToBeTrue(); - Expect(v.data()[0]).ToEqual('\0'); - }); + It("Can default construct", []() + { + String v{}; + Expect(v.size()).ToEqual(0u); + Expect(v.empty()).ToBeTrue(); + Expect(v.length()).ToEqual(0u); + // c_str() must always return a valid pointer to a null terminator + Expect(v.c_str() != nullptr).ToBeTrue(); + Expect(v.c_str()[0]).ToEqual('\0'); + Expect(v.data() != nullptr).ToBeTrue(); + Expect(v.data()[0]).ToEqual('\0'); + }); - It("Can construct from literal", []() - { - String v{"Kiwi"}; - Expect(v).ToEqual("Kiwi"); - Expect(v.size()).ToEqual(4u); - }); + It("Can construct from literal", []() + { + String v{"Kiwi"}; + Expect(v).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4u); + }); - It("Can construct from literal with count", []() - { - String v{"KiwiApple", 4}; - Expect(v).ToEqual("Kiwi"); - Expect(v.size()).ToEqual(4u); - }); + It("Can construct from literal with count", []() + { + String v{"KiwiApple", 4}; + Expect(v).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4u); + }); - It("Can construct from count and char", []() - { - String v(5, 'x'); - Expect(v).ToEqual("xxxxx"); - Expect(v.size()).ToEqual(5u); - }); + It("Can construct from count and char", []() + { + String v(5, 'x'); + Expect(v).ToEqual("xxxxx"); + Expect(v.size()).ToEqual(5u); + }); - It("Can construct from string view", []() - { - StringView str{"Kiwi"}; - String v{str}; - Expect(v).ToEqual("Kiwi"); - Expect(v.size()).ToEqual(4u); - }); + It("Can construct from string view", []() + { + StringView str{"Kiwi"}; + String v{str}; + Expect(v).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4u); + }); - It("Can construct from string view with pos and count", []() - { - StringView str{"KiwiApple"}; - String v{str, 4, 5}; - Expect(v).ToEqual("Apple"); - }); + It("Can construct from string view with pos and count", []() + { + StringView str{"KiwiApple"}; + String v{str, 4, 5}; + Expect(v).ToEqual("Apple"); + }); - It("Can construct from substring", []() - { - String str{"KiwiApple"}; - String v{str, 4}; - Expect(v).ToEqual("Apple"); - String v2{str, 4, 3}; - Expect(v2).ToEqual("App"); - }); + It("Can construct from substring", []() + { + String str{"KiwiApple"}; + String v{str, 4}; + Expect(v).ToEqual("Apple"); + String v2{str, 4, 3}; + Expect(v2).ToEqual("App"); + }); - It("Can construct from iterators", []() - { - std::string_view sv = "Kiwi"; - String v{sv.begin(), sv.end()}; - Expect(v).ToEqual("Kiwi"); - }); + It("Can construct from iterators", []() + { + std::string_view sv = "Kiwi"; + String v{sv.begin(), sv.end()}; + Expect(v).ToEqual("Kiwi"); + }); - It("Can construct from initializer list", []() - { - String v{'K', 'i', 'w', 'i'}; - Expect(v).ToEqual("Kiwi"); - }); + It("Can construct from initializer list", []() + { + String v{'K', 'i', 'w', 'i'}; + Expect(v).ToEqual("Kiwi"); + }); - It("Can copy construct", []() - { - String v{"Kiwi"}; - String v2{v}; - Expect(v2).ToEqual("Kiwi"); - Expect(v).ToEqual("Kiwi"); - }); + It("Can copy construct", []() + { + String v{"Kiwi"}; + String v2{v}; + Expect(v2).ToEqual("Kiwi"); + Expect(v).ToEqual("Kiwi"); + }); - It("Can move construct", []() - { - String v{"Kiwi"}; - String v2{Move(v)}; - Expect(v2).ToEqual("Kiwi"); - // Moved-from string is valid and empty - Expect(v.size()).ToEqual(0u); - Expect(v.empty()).ToBeTrue(); - Expect(v.c_str()[0]).ToEqual('\0'); + It("Can move construct", []() + { + String v{"Kiwi"}; + String v2{Move(v)}; + Expect(v2).ToEqual("Kiwi"); + // Moved-from string is valid and empty + Expect(v.size()).ToEqual(0u); + Expect(v.empty()).ToBeTrue(); + Expect(v.c_str()[0]).ToEqual('\0'); + }); }); - }); - Describe("Assignment", []() - { - It("Can assign from literal", []() + Describe("Assignment", []() { - String v; - v = "Kiwi"; - Expect(v).ToEqual("Kiwi"); - }); + It("Can assign from literal", []() + { + String v; + v = "Kiwi"; + Expect(v).ToEqual("Kiwi"); + }); - It("Can copy assign", []() - { - String vKiwi{"Kiwi"}; - String vApple{"Apple"}; - String vCopy = vKiwi; - Expect(vCopy).ToEqual("Kiwi"); - vCopy = vApple; - Expect(vCopy).ToEqual("Apple"); - Expect(vCopy).ToEqual(vApple); - }); + It("Can copy assign", []() + { + String vKiwi{"Kiwi"}; + String vApple{"Apple"}; + String vCopy = vKiwi; + Expect(vCopy).ToEqual("Kiwi"); + vCopy = vApple; + Expect(vCopy).ToEqual("Apple"); + Expect(vCopy).ToEqual(vApple); + }); - It("Can move assign", []() - { - String vKiwi{"Kiwi"}; - String vApple{"Apple"}; - String vMove = Move(vKiwi); - Expect(vKiwi.size()).ToEqual(0u); - Expect(vMove).ToEqual("Kiwi"); - vMove = Move(vApple); - Expect(vApple.size()).ToEqual(0u); - Expect(vMove).ToEqual("Apple"); - }); + It("Can move assign", []() + { + String vKiwi{"Kiwi"}; + String vApple{"Apple"}; + String vMove = Move(vKiwi); + Expect(vKiwi.size()).ToEqual(0u); + Expect(vMove).ToEqual("Kiwi"); + vMove = Move(vApple); + Expect(vApple.size()).ToEqual(0u); + Expect(vMove).ToEqual("Apple"); + }); - It("Can assign char", []() - { - String v; - v = 'x'; - Expect(v).ToEqual("x"); - }); + It("Can assign char", []() + { + String v; + v = 'x'; + Expect(v).ToEqual("x"); + }); - It("Can assign initializer list", []() - { - String v; - v = {'K', 'i', 'w', 'i'}; - Expect(v).ToEqual("Kiwi"); - }); + It("Can assign initializer list", []() + { + String v; + v = {'K', 'i', 'w', 'i'}; + Expect(v).ToEqual("Kiwi"); + }); - It("Can assign string view", []() - { - String v; - StringView sv{"Kiwi"}; - v = sv; - Expect(v).ToEqual("Kiwi"); - }); + It("Can assign string view", []() + { + String v; + StringView sv{"Kiwi"}; + v = sv; + Expect(v).ToEqual("Kiwi"); + }); - It("Can assign", []() - { - String v; - v.assign("Kiwi"); - Expect(v).ToEqual("Kiwi"); - v.assign("KiwiApple", 4); - Expect(v).ToEqual("Kiwi"); - v.assign(3, 'x'); - Expect(v).ToEqual("xxx"); - String other{"Apple"}; - v.assign(other); - Expect(v).ToEqual("Apple"); - v.assign(other, 2, 2); - Expect(v).ToEqual("pl"); - StringView sv{"KiwiApple"}; - v.assign(sv, 4, 5); - Expect(v).ToEqual("Apple"); - v.assign({'a', 'b', 'c'}); - Expect(v).ToEqual("abc"); - }); + It("Can assign", []() + { + String v; + v.assign("Kiwi"); + Expect(v).ToEqual("Kiwi"); + v.assign("KiwiApple", 4); + Expect(v).ToEqual("Kiwi"); + v.assign(3, 'x'); + Expect(v).ToEqual("xxx"); + String other{"Apple"}; + v.assign(other); + Expect(v).ToEqual("Apple"); + v.assign(other, 2, 2); + Expect(v).ToEqual("pl"); + StringView sv{"KiwiApple"}; + v.assign(sv, 4, 5); + Expect(v).ToEqual("Apple"); + v.assign({'a', 'b', 'c'}); + Expect(v).ToEqual("abc"); + }); - It("Can self assign", []() - { - String v{"Kiwi"}; - const String& ref = v; - v = ref; - Expect(v).ToEqual("Kiwi"); - }); + It("Can self assign", []() + { + String v{"Kiwi"}; + const String& ref = v; + v = ref; + Expect(v).ToEqual("Kiwi"); + }); - It("Can self assign substrings", []() - { - String v{longText}; - v.assign(v.c_str() + 10); - Expect(v).ToEqual("ABCDEFGHIJ0123456789ABC"); - }); + It("Can self assign substrings", []() + { + String v{longText}; + v.assign(v.c_str() + 10); + Expect(v).ToEqual("ABCDEFGHIJ0123456789ABC"); + }); - It("Can self assign substrings with count", []() - { - String v{longText}; - v.assign(v.c_str() + 5, 10); - Expect(v).ToEqual("56789ABCDE"); + It("Can self assign substrings with count", []() + { + String v{longText}; + v.assign(v.c_str() + 5, 10); + Expect(v).ToEqual("56789ABCDE"); + }); }); - }); - Describe("Element access", []() - { - It("Can index", []() + Describe("Element access", []() { - String v{"Kiwi"}; - Expect(v[0]).ToEqual('K'); - Expect(v[3]).ToEqual('i'); - v[0] = 'k'; - Expect(v).ToEqual("kiwi"); - // pos == size() returns reference to null char - Expect(v[4]).ToEqual('\0'); - }); + It("Can index", []() + { + String v{"Kiwi"}; + Expect(v[0]).ToEqual('K'); + Expect(v[3]).ToEqual('i'); + v[0] = 'k'; + Expect(v).ToEqual("kiwi"); + // pos == size() returns reference to null char + Expect(v[4]).ToEqual('\0'); + }); - It("Can access at", []() - { - String v{"Kiwi"}; - Expect(v.at(0)).ToEqual('K'); - Expect(v.at(3)).ToEqual('i'); - v.at(0) = 'k'; - Expect(v).ToEqual("kiwi"); - }); + It("Can access at", []() + { + String v{"Kiwi"}; + Expect(v.at(0)).ToEqual('K'); + Expect(v.at(3)).ToEqual('i'); + v.at(0) = 'k'; + Expect(v).ToEqual("kiwi"); + }); - It("Can access front and back", []() - { - String v{"Kiwi"}; - Expect(v.front()).ToEqual('K'); - Expect(v.back()).ToEqual('i'); - v.front() = 'P'; - v.back() = 's'; - Expect(v).ToEqual("Piws"); - }); + It("Can access front and back", []() + { + String v{"Kiwi"}; + Expect(v.front()).ToEqual('K'); + Expect(v.back()).ToEqual('i'); + v.front() = 'P'; + v.back() = 's'; + Expect(v).ToEqual("Piws"); + }); - It("Can retrieve data", []() - { - String v{"Kiwi"}; - Expect(v.data()).ToEqual("Kiwi"); - Expect(v.size()).ToEqual(4u); - Expect(strlen(v.data())).ToEqual(4u); - }); + It("Can retrieve data", []() + { + String v{"Kiwi"}; + Expect(v.data()).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4u); + Expect(strlen(v.data())).ToEqual(4u); + }); - It("Can convert to string view", []() - { - String v{"Kiwi"}; - StringView sv = v; - Expect(sv.size()).ToEqual(4u); - Expect(sv).ToEqual(StringView{"Kiwi"}); - StringView wsv{v}; - Expect(wsv).ToEqual(StringView{"Kiwi"}); + It("Can convert to string view", []() + { + String v{"Kiwi"}; + StringView sv = v; + Expect(sv.size()).ToEqual(4u); + Expect(sv).ToEqual(StringView{"Kiwi"}); + StringView wsv{v}; + Expect(wsv).ToEqual(StringView{"Kiwi"}); + }); }); - }); - Describe("Iterators", []() - { - It("Can iterate", []() + Describe("Iterators", []() { - String v{"Kiwi"}; - u32 i = 0; - for (char c : v) - { - Expect(c).ToEqual("Kiwi"[i]); - ++i; - } - Expect(i).ToEqual(4u); - }); + It("Can iterate", []() + { + String v{"Kiwi"}; + u32 i = 0; + for (char c : v) + { + Expect(c).ToEqual("Kiwi"[i]); + ++i; + } + Expect(i).ToEqual(4u); + }); - It("Can iterate const", []() - { - const String v{"Kiwi"}; - u32 i = 0; - for (char c : v) - { - Expect(c).ToEqual("Kiwi"[i]); - ++i; - } - Expect(i).ToEqual(4u); - }); + It("Can iterate const", []() + { + const String v{"Kiwi"}; + u32 i = 0; + for (char c : v) + { + Expect(c).ToEqual("Kiwi"[i]); + ++i; + } + Expect(i).ToEqual(4u); + }); - It("Can iterate manually", []() - { - String v{"Kiwi"}; - auto it = v.begin(); - auto end = v.end(); - Expect(end - it).ToEqual(4); - Expect(*it).ToEqual('K'); - Expect(it[2]).ToEqual('w'); - ++it; - Expect(*it).ToEqual('i'); - it += 2; - Expect(*it).ToEqual('i'); - --it; - Expect(*it).ToEqual('w'); - Expect(it == v.begin() + 2).ToBeTrue(); - Expect(it != v.begin()).ToBeTrue(); - }); + It("Can iterate manually", []() + { + String v{"Kiwi"}; + auto it = v.begin(); + auto end = v.end(); + Expect(end - it).ToEqual(4); + Expect(*it).ToEqual('K'); + Expect(it[2]).ToEqual('w'); + ++it; + Expect(*it).ToEqual('i'); + it += 2; + Expect(*it).ToEqual('i'); + --it; + Expect(*it).ToEqual('w'); + Expect(it == v.begin() + 2).ToBeTrue(); + Expect(it != v.begin()).ToBeTrue(); + }); - It("Can iterate reverse", []() - { - String v{"Kiwi"}; - u32 i = 0; - for (auto rit = v.rbegin(); rit != v.rend(); ++rit) - { - Expect(*rit).ToEqual("Kiwi"[3 - i]); - ++i; - } - Expect(i).ToEqual(4u); - }); + It("Can iterate reverse", []() + { + String v{"Kiwi"}; + u32 i = 0; + for (auto rit = v.rbegin(); rit != v.rend(); ++rit) + { + Expect(*rit).ToEqual("Kiwi"[3 - i]); + ++i; + } + Expect(i).ToEqual(4u); + }); - It("Can iterate c-variants", []() - { - String v{"Kiwi"}; - Expect(*v.cbegin()).ToEqual('K'); - Expect(*(v.cend() - 1)).ToEqual('i'); - Expect(*v.crbegin()).ToEqual('i'); - Expect(*(v.crend() - 1)).ToEqual('K'); - }); + It("Can iterate c-variants", []() + { + String v{"Kiwi"}; + Expect(*v.cbegin()).ToEqual('K'); + Expect(*(v.cend() - 1)).ToEqual('i'); + Expect(*v.crbegin()).ToEqual('i'); + Expect(*(v.crend() - 1)).ToEqual('K'); + }); - It("Can mutate through iterators", []() - { - String v{"Kiwi"}; - std::transform(v.begin(), v.end(), v.begin(), [](char c) + It("Can mutate through iterators", []() { - return char(c + 1); + String v{"Kiwi"}; + std::transform(v.begin(), v.end(), v.begin(), [](char c) + { + return char(c + 1); + }); + Expect(v).ToEqual("Ljxj"); }); - Expect(v).ToEqual("Ljxj"); }); - }); - Describe("Capacity", []() - { - It("Can query size and length", []() + Describe("Capacity", []() { - String v{"Kiwi"}; - Expect(v.size()).ToEqual(4u); - Expect(v.length()).ToEqual(4u); - Expect(v.empty()).ToBeFalse(); - }); + It("Can query size and length", []() + { + String v{"Kiwi"}; + Expect(v.size()).ToEqual(4u); + Expect(v.length()).ToEqual(4u); + Expect(v.empty()).ToBeFalse(); + }); - It("Has short string optimization", []() - { - String v{"Kiwi"}; - // Short strings must fit in the internal buffer - Expect(v.capacity() >= 15u).ToBeTrue(); - Expect(v.capacity() <= 32u).ToBeTrue(); - }); + It("Has short string optimization", []() + { + String v{"Kiwi"}; + // Short strings must fit in the internal buffer + Expect(v.capacity() >= 15u).ToBeTrue(); + Expect(v.capacity() <= 32u).ToBeTrue(); + }); - It("Can reserve", []() - { - String v; - v.reserve(100); - Expect(v.capacity() >= 100u).ToBeTrue(); - Expect(v.size()).ToEqual(0u); - v = "Kiwi"; - Expect(v).ToEqual("Kiwi"); - Expect(v.capacity() >= 100u).ToBeTrue(); - }); + It("Can reserve", []() + { + String v; + v.reserve(100); + Expect(v.capacity() >= 100u).ToBeTrue(); + Expect(v.size()).ToEqual(0u); + v = "Kiwi"; + Expect(v).ToEqual("Kiwi"); + Expect(v.capacity() >= 100u).ToBeTrue(); + }); - It("Can shrink to fit", []() - { - String v; - v.reserve(100); - v = "Kiwi"; - v.shrink_to_fit(); - Expect(v).ToEqual("Kiwi"); - Expect(v.capacity() >= 4u).ToBeTrue(); - Expect(v.capacity() < 100u).ToBeTrue(); - }); + It("Can shrink to fit", []() + { + String v; + v.reserve(100); + v = "Kiwi"; + v.shrink_to_fit(); + Expect(v).ToEqual("Kiwi"); + Expect(v.capacity() >= 4u).ToBeTrue(); + Expect(v.capacity() < 100u).ToBeTrue(); + }); - It("Has max size", []() - { - String v; - // Lengths are stored internally as i32 - Expect(v.max_size()).ToEqual(sizet(Limits::Max() - 1)); + It("Has max size", []() + { + String v; + // Lengths are stored internally as i32 + Expect(v.max_size()).ToEqual(sizet(Limits::Max() - 1)); + }); }); - }); - Describe("Modifiers", []() - { - It("Can clear", []() + Describe("Modifiers", []() { - String v{"Kiwi"}; - v.clear(); - Expect(v.empty()).ToBeTrue(); - Expect(v.size()).ToEqual(0u); - Expect(v.c_str()[0]).ToEqual('\0'); - }); + It("Can clear", []() + { + String v{"Kiwi"}; + v.clear(); + Expect(v.empty()).ToBeTrue(); + Expect(v.size()).ToEqual(0u); + Expect(v.c_str()[0]).ToEqual('\0'); + }); - It("Can push and pop back", []() - { - String v{"Ki"}; - v.push_back('w'); - v.push_back('i'); - Expect(v).ToEqual("Kiwi"); - Expect(v.back()).ToEqual('i'); - v.pop_back(); - Expect(v).ToEqual("Kiw"); - v.pop_back(); - v.pop_back(); - v.pop_back(); - Expect(v).ToEqual(""); - Expect(v.empty()).ToBeTrue(); - }); + It("Can push and pop back", []() + { + String v{"Ki"}; + v.push_back('w'); + v.push_back('i'); + Expect(v).ToEqual("Kiwi"); + Expect(v.back()).ToEqual('i'); + v.pop_back(); + Expect(v).ToEqual("Kiw"); + v.pop_back(); + v.pop_back(); + v.pop_back(); + Expect(v).ToEqual(""); + Expect(v.empty()).ToBeTrue(); + }); - It("Can append", []() - { - String v{"Kiwi"}; - v.append("Apple"); - Expect(v).ToEqual("KiwiApple"); - v.append("Orange", 3); - Expect(v).ToEqual("KiwiAppleOra"); - v.append(3, '-'); - Expect(v).ToEqual("KiwiAppleOra---"); - String other{"End"}; - v.append(other); - Expect(v).ToEqual("KiwiAppleOra---End"); - v.append(other, 1, 2); - Expect(v).ToEqual("KiwiAppleOra---Endnd"); - StringView sv{"View"}; - v.append(sv); - Expect(v).ToEqual("KiwiAppleOra---EndndView"); - v.append(sv, 2, 2); - Expect(v).ToEqual("KiwiAppleOra---EndndViewew"); - v.append({'!', '?'}); - Expect(v).ToEqual("KiwiAppleOra---EndndViewew!?"); - }); + It("Can append", []() + { + String v{"Kiwi"}; + v.append("Apple"); + Expect(v).ToEqual("KiwiApple"); + v.append("Orange", 3); + Expect(v).ToEqual("KiwiAppleOra"); + v.append(3, '-'); + Expect(v).ToEqual("KiwiAppleOra---"); + String other{"End"}; + v.append(other); + Expect(v).ToEqual("KiwiAppleOra---End"); + v.append(other, 1, 2); + Expect(v).ToEqual("KiwiAppleOra---Endnd"); + StringView sv{"View"}; + v.append(sv); + Expect(v).ToEqual("KiwiAppleOra---EndndView"); + v.append(sv, 2, 2); + Expect(v).ToEqual("KiwiAppleOra---EndndViewew"); + v.append({'!', '?'}); + Expect(v).ToEqual("KiwiAppleOra---EndndViewew!?"); + }); - It("Can append with operator+=", []() - { - String v{"Kiwi"}; - v += "Apple"; - Expect(v).ToEqual("KiwiApple"); - v += '!'; - Expect(v).ToEqual("KiwiApple!"); - String other{"End"}; - v += other; - Expect(v).ToEqual("KiwiApple!End"); - v += StringView{"View"}; - Expect(v).ToEqual("KiwiApple!EndView"); - v += {'a', 'b'}; - Expect(v).ToEqual("KiwiApple!EndViewab"); - }); + It("Can append with operator+=", []() + { + String v{"Kiwi"}; + v += "Apple"; + Expect(v).ToEqual("KiwiApple"); + v += '!'; + Expect(v).ToEqual("KiwiApple!"); + String other{"End"}; + v += other; + Expect(v).ToEqual("KiwiApple!End"); + v += StringView{"View"}; + Expect(v).ToEqual("KiwiApple!EndView"); + v += {'a', 'b'}; + Expect(v).ToEqual("KiwiApple!EndViewab"); + }); - It("Can insert", []() - { - String v{"KiwiApple"}; - v.insert(4, "Orange"); - Expect(v).ToEqual("KiwiOrangeApple"); - v.insert(0, "-"); - Expect(v).ToEqual("-KiwiOrangeApple"); - v.insert(v.size(), "!"); - Expect(v).ToEqual("-KiwiOrangeApple!"); - v.insert(0, 3, '='); - Expect(v).ToEqual("===-KiwiOrangeApple!"); - String other{"XX"}; - v.insert(3, other); - Expect(v).ToEqual("===XX-KiwiOrangeApple!"); - StringView sv{"YY"}; - v.insert(5, sv); - Expect(v).ToEqual("===XXYY-KiwiOrangeApple!"); - v.insert(0, 2, 'Z'); - Expect(v).ToEqual("ZZ===XXYY-KiwiOrangeApple!"); - }); + It("Can insert", []() + { + String v{"KiwiApple"}; + v.insert(4, "Orange"); + Expect(v).ToEqual("KiwiOrangeApple"); + v.insert(0, "-"); + Expect(v).ToEqual("-KiwiOrangeApple"); + v.insert(v.size(), "!"); + Expect(v).ToEqual("-KiwiOrangeApple!"); + v.insert(0, 3, '='); + Expect(v).ToEqual("===-KiwiOrangeApple!"); + String other{"XX"}; + v.insert(3, other); + Expect(v).ToEqual("===XX-KiwiOrangeApple!"); + StringView sv{"YY"}; + v.insert(5, sv); + Expect(v).ToEqual("===XXYY-KiwiOrangeApple!"); + v.insert(0, 2, 'Z'); + Expect(v).ToEqual("ZZ===XXYY-KiwiOrangeApple!"); + }); - It("Can insert with iterator", []() - { - String v{"Kiwi"}; - auto it = v.insert(v.begin() + 2, '-'); - Expect(*it).ToEqual('-'); - Expect(v).ToEqual("Ki-wi"); - v.insert(v.end(), 3, '!'); - Expect(v).ToEqual("Ki-wi!!!"); - String other{"AB"}; - v.insert(v.begin(), other.begin(), other.end()); - Expect(v).ToEqual("ABKi-wi!!!"); - v.insert(v.begin() + 2, {'x', 'y'}); - Expect(v).ToEqual("ABxyKi-wi!!!"); - }); + It("Can insert with iterator", []() + { + String v{"Kiwi"}; + auto it = v.insert(v.begin() + 2, '-'); + Expect(*it).ToEqual('-'); + Expect(v).ToEqual("Ki-wi"); + v.insert(v.end(), 3, '!'); + Expect(v).ToEqual("Ki-wi!!!"); + String other{"AB"}; + v.insert(v.begin(), other.begin(), other.end()); + Expect(v).ToEqual("ABKi-wi!!!"); + v.insert(v.begin() + 2, {'x', 'y'}); + Expect(v).ToEqual("ABxyKi-wi!!!"); + }); - It("Can erase", []() - { - String v{"KiwiApple"}; - v.erase(4, 5); - Expect(v).ToEqual("Kiwi"); - v.erase(2); - Expect(v).ToEqual("Ki"); - v.erase(0, 1); - Expect(v).ToEqual("i"); - v.erase(0, 10); - Expect(v).ToEqual(""); - }); + It("Can erase", []() + { + String v{"KiwiApple"}; + v.erase(4, 5); + Expect(v).ToEqual("Kiwi"); + v.erase(2); + Expect(v).ToEqual("Ki"); + v.erase(0, 1); + Expect(v).ToEqual("i"); + v.erase(0, 10); + Expect(v).ToEqual(""); + }); - It("Can erase with iterator", []() - { - String v{"Kiwi"}; - auto it = v.erase(v.begin()); - Expect(*it).ToEqual('i'); - Expect(v).ToEqual("iwi"); - v.erase(v.begin() + 1, v.end()); - Expect(v).ToEqual("i"); - }); + It("Can erase with iterator", []() + { + String v{"Kiwi"}; + auto it = v.erase(v.begin()); + Expect(*it).ToEqual('i'); + Expect(v).ToEqual("iwi"); + v.erase(v.begin() + 1, v.end()); + Expect(v).ToEqual("i"); + }); - It("Can replace", []() - { - String v{"KiwiApple"}; - v.replace(0, 4, "Orange"); - Expect(v).ToEqual("OrangeApple"); - v.replace(0, 6, "X"); - Expect(v).ToEqual("XApple"); - v.replace(v.size() - 3, 3, "Z"); - Expect(v).ToEqual("XApZ"); - String other{"Kiwi"}; - v.replace(0, 4, other); - Expect(v).ToEqual("Kiwi"); - StringView sv{"Two"}; - v.replace(0, 4, sv); - Expect(v).ToEqual("Two"); - v.replace(0, 3, 2, 'y'); - Expect(v).ToEqual("yy"); - }); + It("Can replace", []() + { + String v{"KiwiApple"}; + v.replace(0, 4, "Orange"); + Expect(v).ToEqual("OrangeApple"); + v.replace(0, 6, "X"); + Expect(v).ToEqual("XApple"); + v.replace(v.size() - 3, 3, "Z"); + Expect(v).ToEqual("XApZ"); + String other{"Kiwi"}; + v.replace(0, 4, other); + Expect(v).ToEqual("Kiwi"); + StringView sv{"Two"}; + v.replace(0, 4, sv); + Expect(v).ToEqual("Two"); + v.replace(0, 3, 2, 'y'); + Expect(v).ToEqual("yy"); + }); - It("Can replace with iterators", []() - { - String v{"KiwiApple"}; - v.replace(v.begin(), v.begin() + 4, "Orange"); - Expect(v).ToEqual("OrangeApple"); - }); + It("Can replace with iterators", []() + { + String v{"KiwiApple"}; + v.replace(v.begin(), v.begin() + 4, "Orange"); + Expect(v).ToEqual("OrangeApple"); + }); - It("Can resize", []() - { - String v{"Kiwi"}; - v.resize(2); - Expect(v).ToEqual("Ki"); - v.resize(4); - Expect(v.size()).ToEqual(4u); - Expect(v[2]).ToEqual('\0'); - Expect(v[3]).ToEqual('\0'); - v.resize(6, 'x'); - Expect(v[4]).ToEqual('x'); - Expect(v[5]).ToEqual('x'); - Expect(v.size()).ToEqual(6u); - }); + It("Can resize", []() + { + String v{"Kiwi"}; + v.resize(2); + Expect(v).ToEqual("Ki"); + v.resize(4); + Expect(v.size()).ToEqual(4u); + Expect(v[2]).ToEqual('\0'); + Expect(v[3]).ToEqual('\0'); + v.resize(6, 'x'); + Expect(v[4]).ToEqual('x'); + Expect(v[5]).ToEqual('x'); + Expect(v.size()).ToEqual(6u); + }); - It("Can swap", []() - { - String a{"Kiwi"}; - String b{"Apple"}; - a.swap(b); - Expect(a).ToEqual("Apple"); - Expect(b).ToEqual("Kiwi"); - }); + It("Can swap", []() + { + String a{"Kiwi"}; + String b{"Apple"}; + a.swap(b); + Expect(a).ToEqual("Apple"); + Expect(b).ToEqual("Kiwi"); + }); - It("Can append from self", []() - { - String v{longText}; - v.append(v.c_str()); - Expect(v).ToEqual(std::string{longText} + std::string{longText}); - }); + It("Can append from self", []() + { + String v{longText}; + v.append(v.c_str()); + Expect(v).ToEqual(std::string{longText} + std::string{longText}); + }); - It("Can append self substring", []() - { - String v{longText}; - v.append(v.c_str() + 5); - Expect(v).ToEqual(std::string{longText} + std::string{longText.substr(5)}); - }); + It("Can append self substring", []() + { + String v{longText}; + v.append(v.c_str() + 5); + Expect(v).ToEqual(std::string{longText} + std::string{longText.substr(5)}); + }); - It("Can insert from self", []() - { - String v{longText}; - v.insert(0, v.c_str()); - Expect(v).ToEqual(std::string{longText} + std::string{longText}); - }); + It("Can insert from self", []() + { + String v{longText}; + v.insert(0, v.c_str()); + Expect(v).ToEqual(std::string{longText} + std::string{longText}); + }); - It("Can insert self substring", []() - { - String v{longText}; - v.insert(4, v.c_str() + 5); - Expect(v).ToEqual(std::string{longText.substr(0, 4)} + std::string{longText.substr(5)} - + std::string{longText.substr(4)}); - }); + It("Can insert self substring", []() + { + String v{longText}; + v.insert(4, v.c_str() + 5); + Expect(v).ToEqual(std::string{longText.substr(0, 4)} + + std::string{longText.substr(5)} + + std::string{longText.substr(4)}); + }); - It("Can replace with self", []() - { - String v{longText}; - v.replace(0, 4, v.c_str()); - Expect(v).ToEqual(std::string{longText} + std::string{longText.substr(4)}); - }); + It("Can replace with self", []() + { + String v{longText}; + v.replace(0, 4, v.c_str()); + Expect(v).ToEqual(std::string{longText} + std::string{longText.substr(4)}); + }); - It("Can replace self substring with count", []() - { - String v{longText}; - v.replace(5, 10, v.c_str() + 2, 5); - Expect(v).ToEqual(std::string{longText.substr(0, 5)} + "23456" - + std::string{longText.substr(15)}); + It("Can replace self substring with count", []() + { + String v{longText}; + v.replace(5, 10, v.c_str() + 2, 5); + Expect(v).ToEqual(std::string{longText.substr(0, 5)} + "23456" + + std::string{longText.substr(15)}); + }); }); - }); - Describe("Operations", []() - { - It("Can get substr", []() + Describe("Operations", []() { - String v{"KiwiApple"}; - Expect(v.substr()).ToEqual("KiwiApple"); - Expect(v.substr(4)).ToEqual("Apple"); - Expect(v.substr(4, 3)).ToEqual("App"); - Expect(v.substr(0, 100)).ToEqual("KiwiApple"); - }); + It("Can get substr", []() + { + String v{"KiwiApple"}; + Expect(v.substr()).ToEqual("KiwiApple"); + Expect(v.substr(4)).ToEqual("Apple"); + Expect(v.substr(4, 3)).ToEqual("App"); + Expect(v.substr(0, 100)).ToEqual("KiwiApple"); + }); - It("Can copy out", []() - { - String v{"KiwiApple"}; - char buffer[16]{}; - const auto count = v.copy(buffer, 4, 4); - Expect(count).ToEqual(4u); - Expect(buffer).ToEqual("Appl"); - buffer[count] = '\0'; - }); + It("Can copy out", []() + { + String v{"KiwiApple"}; + char buffer[16]{}; + const auto count = v.copy(buffer, 4, 4); + Expect(count).ToEqual(4u); + Expect(buffer).ToEqual("Appl"); + buffer[count] = '\0'; + }); - It("Can compare", []() - { - String v{"Kiwi"}; - String other{"Kiwi"}; - String apple{"Apple"}; - Expect(v.compare(other)).ToEqual(0); - Expect(v.compare(apple) > 0).ToBeTrue(); - Expect(apple.compare(v) < 0).ToBeTrue(); - Expect(v.compare("Kiwi")).ToEqual(0); - Expect(v.compare("Kiwi2") < 0).ToBeTrue(); - Expect(v.compare(StringView{"Kiwi"})).ToEqual(0); - Expect(v.compare(0, 2, String{"Ki"})).ToEqual(0); - Expect(v.compare(2, 2, String{"wi"})).ToEqual(0); - }); + It("Can compare", []() + { + String v{"Kiwi"}; + String other{"Kiwi"}; + String apple{"Apple"}; + Expect(v.compare(other)).ToEqual(0); + Expect(v.compare(apple) > 0).ToBeTrue(); + Expect(apple.compare(v) < 0).ToBeTrue(); + Expect(v.compare("Kiwi")).ToEqual(0); + Expect(v.compare("Kiwi2") < 0).ToBeTrue(); + Expect(v.compare(StringView{"Kiwi"})).ToEqual(0); + Expect(v.compare(0, 2, String{"Ki"})).ToEqual(0); + Expect(v.compare(2, 2, String{"wi"})).ToEqual(0); + }); - It("Can check prefix and suffix", []() - { - String v{"KiwiApple"}; - Expect(v.starts_with("Kiwi")).ToBeTrue(); - Expect(v.starts_with('K')).ToBeTrue(); - Expect(v.starts_with(StringView{"Ki"})).ToBeTrue(); - Expect(v.starts_with("Apple")).ToBeFalse(); - Expect(v.ends_with("Apple")).ToBeTrue(); - Expect(v.ends_with('e')).ToBeTrue(); - Expect(v.ends_with(StringView{"le"})).ToBeTrue(); - Expect(v.ends_with("Kiwi")).ToBeFalse(); - }); + It("Can check prefix and suffix", []() + { + String v{"KiwiApple"}; + Expect(v.starts_with("Kiwi")).ToBeTrue(); + Expect(v.starts_with('K')).ToBeTrue(); + Expect(v.starts_with(StringView{"Ki"})).ToBeTrue(); + Expect(v.starts_with("Apple")).ToBeFalse(); + Expect(v.ends_with("Apple")).ToBeTrue(); + Expect(v.ends_with('e')).ToBeTrue(); + Expect(v.ends_with(StringView{"le"})).ToBeTrue(); + Expect(v.ends_with("Kiwi")).ToBeFalse(); + }); - It("Can check contains", []() - { - String v{"KiwiApple"}; - Expect(v.contains("wiA")).ToBeTrue(); - Expect(v.contains('A')).ToBeTrue(); - Expect(v.contains(StringView{"zzz"})).ToBeFalse(); - Expect(v.contains('z')).ToBeFalse(); - }); + It("Can check contains", []() + { + String v{"KiwiApple"}; + Expect(v.contains("wiA")).ToBeTrue(); + Expect(v.contains('A')).ToBeTrue(); + Expect(v.contains(StringView{"zzz"})).ToBeFalse(); + Expect(v.contains('z')).ToBeFalse(); + }); - It("Can find", []() - { - String v{"KiwiKiwi"}; - Expect(v.find("Kiwi")).ToEqual(0u); - Expect(v.find("Kiwi", 1)).ToEqual(4u); - Expect(v.find("Kiwi", 5)).ToEqual(String::npos); - Expect(v.find('i')).ToEqual(1u); - Expect(v.find('i', 6)).ToEqual(7u); - Expect(v.find('z')).ToEqual(String::npos); - Expect(v.find(String{"Kiwi"})).ToEqual(0u); - Expect(v.find(StringView{"Kiwi"})).ToEqual(0u); - }); + It("Can find", []() + { + String v{"KiwiKiwi"}; + Expect(v.find("Kiwi")).ToEqual(0u); + Expect(v.find("Kiwi", 1)).ToEqual(4u); + Expect(v.find("Kiwi", 5)).ToEqual(String::npos); + Expect(v.find('i')).ToEqual(1u); + Expect(v.find('i', 6)).ToEqual(7u); + Expect(v.find('z')).ToEqual(String::npos); + Expect(v.find(String{"Kiwi"})).ToEqual(0u); + Expect(v.find(StringView{"Kiwi"})).ToEqual(0u); + }); - It("Can rfind", []() - { - String v{"KiwiKiwi"}; - Expect(v.rfind("Kiwi")).ToEqual(4u); - Expect(v.rfind("Kiwi", 3)).ToEqual(0u); - Expect(v.rfind('i')).ToEqual(7u); - Expect(v.rfind('i', 5)).ToEqual(5u); - Expect(v.rfind('z')).ToEqual(String::npos); - Expect(v.rfind(String{"Kiwi"})).ToEqual(4u); - Expect(v.rfind(StringView{"Kiwi"})).ToEqual(4u); - }); + It("Can rfind", []() + { + String v{"KiwiKiwi"}; + Expect(v.rfind("Kiwi")).ToEqual(4u); + Expect(v.rfind("Kiwi", 3)).ToEqual(0u); + Expect(v.rfind('i')).ToEqual(7u); + Expect(v.rfind('i', 5)).ToEqual(5u); + Expect(v.rfind('z')).ToEqual(String::npos); + Expect(v.rfind(String{"Kiwi"})).ToEqual(4u); + Expect(v.rfind(StringView{"Kiwi"})).ToEqual(4u); + }); - It("Can find first of", []() - { - String v{"KiwiApple"}; - Expect(v.find_first_of("pl")).ToEqual(5u); - Expect(v.find_first_of("pl", 6)).ToEqual(6u); - Expect(v.find_first_of('z')).ToEqual(String::npos); - Expect(v.find_first_of("xyz")).ToEqual(String::npos); - Expect(v.find_first_of(StringView{"Ap"})).ToEqual(4u); - }); + It("Can find first of", []() + { + String v{"KiwiApple"}; + Expect(v.find_first_of("pl")).ToEqual(5u); + Expect(v.find_first_of("pl", 6)).ToEqual(6u); + Expect(v.find_first_of('z')).ToEqual(String::npos); + Expect(v.find_first_of("xyz")).ToEqual(String::npos); + Expect(v.find_first_of(StringView{"Ap"})).ToEqual(4u); + }); - It("Can find last of", []() - { - String v{"KiwiApple"}; - Expect(v.find_last_of("pl")).ToEqual(7u); - Expect(v.find_last_of("pl", 6)).ToEqual(6u); - Expect(v.find_last_of('z')).ToEqual(String::npos); - Expect(v.find_last_of(StringView{"Ap"})).ToEqual(6u); - }); + It("Can find last of", []() + { + String v{"KiwiApple"}; + Expect(v.find_last_of("pl")).ToEqual(7u); + Expect(v.find_last_of("pl", 6)).ToEqual(6u); + Expect(v.find_last_of('z')).ToEqual(String::npos); + Expect(v.find_last_of(StringView{"Ap"})).ToEqual(6u); + }); - It("Can find first not of", []() - { - String v{"aaab"}; - Expect(v.find_first_not_of("a")).ToEqual(3u); - Expect(v.find_first_not_of("ab")).ToEqual(String::npos); - Expect(v.find_first_not_of('a')).ToEqual(3u); - Expect(v.find_first_not_of("ab", 3)).ToEqual(String::npos); - }); + It("Can find first not of", []() + { + String v{"aaab"}; + Expect(v.find_first_not_of("a")).ToEqual(3u); + Expect(v.find_first_not_of("ab")).ToEqual(String::npos); + Expect(v.find_first_not_of('a')).ToEqual(3u); + Expect(v.find_first_not_of("ab", 3)).ToEqual(String::npos); + }); - It("Can find last not of", []() - { - String v{"baaa"}; - Expect(v.find_last_not_of("a")).ToEqual(0u); - Expect(v.find_last_not_of("ab")).ToEqual(String::npos); - Expect(v.find_last_not_of('a')).ToEqual(0u); - Expect(v.find_last_not_of("ab", 0)).ToEqual(String::npos); - }); + It("Can find last not of", []() + { + String v{"baaa"}; + Expect(v.find_last_not_of("a")).ToEqual(0u); + Expect(v.find_last_not_of("ab")).ToEqual(String::npos); + Expect(v.find_last_not_of('a')).ToEqual(0u); + Expect(v.find_last_not_of("ab", 0)).ToEqual(String::npos); + }); - It("Has npos", []() - { - Expect(String::npos).ToEqual(sizet(-1)); - Expect(StringView::npos).ToEqual(String::npos); + It("Has npos", []() + { + Expect(String::npos).ToEqual(sizet(-1)); + Expect(StringView::npos).ToEqual(String::npos); + }); }); - }); - Describe("Operators", []() - { - It("Can concatenate", []() + Describe("Operators", []() { - String a{"Kiwi"}; - String b{"Apple"}; - Expect(a + b).ToEqual("KiwiApple"); - Expect(a + "X").ToEqual("KiwiX"); - Expect("X" + a).ToEqual("XKiwi"); - Expect(a + '!').ToEqual("Kiwi!"); - Expect('!' + a).ToEqual("!Kiwi"); - Expect(a + StringView{"V"}).ToEqual("KiwiV"); - Expect(StringView{"V"} + a).ToEqual("VKiwi"); - }); + It("Can concatenate", []() + { + String a{"Kiwi"}; + String b{"Apple"}; + Expect(a + b).ToEqual("KiwiApple"); + Expect(a + "X").ToEqual("KiwiX"); + Expect("X" + a).ToEqual("XKiwi"); + Expect(a + '!').ToEqual("Kiwi!"); + Expect('!' + a).ToEqual("!Kiwi"); + Expect(a + StringView{"V"}).ToEqual("KiwiV"); + Expect(StringView{"V"} + a).ToEqual("VKiwi"); + }); - It("Can chain concatenate", []() - { - String a{"Kiwi"}; - String result = a + " " + "Apple" + '!'; - Expect(result).ToEqual("Kiwi Apple!"); - }); + It("Can chain concatenate", []() + { + String a{"Kiwi"}; + String result = a + " " + "Apple" + '!'; + Expect(result).ToEqual("Kiwi Apple!"); + }); - It("Can compare with other types", []() - { - String v{"Kiwi"}; - Expect(v == String{"Kiwi"}).ToBeTrue(); - Expect(v != String{"Apple"}).ToBeTrue(); - Expect(v == "Kiwi").ToBeTrue(); - Expect(v != "Apple").ToBeTrue(); - Expect("Kiwi" == v).ToBeTrue(); - Expect("Apple" != v).ToBeTrue(); - Expect(v < "Lime").ToBeTrue(); - Expect("Lime" > v).ToBeTrue(); - Expect(v <= String{"Kiwi"}).ToBeTrue(); - Expect(v >= String{"Kiwi"}).ToBeTrue(); - Expect(v == StringView{"Kiwi"}).ToBeTrue(); - Expect(StringView{"Kiwi"} == v).ToBeTrue(); - Expect(v != StringView{"Apple"}).ToBeTrue(); - Expect(StringView{"Apple"} != v).ToBeTrue(); - Expect(v < StringView{"Lime"}).ToBeTrue(); - Expect(StringView{"Lime"} > v).ToBeTrue(); - }); + It("Can compare with other types", []() + { + String v{"Kiwi"}; + Expect(v == String{"Kiwi"}).ToBeTrue(); + Expect(v != String{"Apple"}).ToBeTrue(); + Expect(v == "Kiwi").ToBeTrue(); + Expect(v != "Apple").ToBeTrue(); + Expect("Kiwi" == v).ToBeTrue(); + Expect("Apple" != v).ToBeTrue(); + Expect(v < "Lime").ToBeTrue(); + Expect("Lime" > v).ToBeTrue(); + Expect(v <= String{"Kiwi"}).ToBeTrue(); + Expect(v >= String{"Kiwi"}).ToBeTrue(); + Expect(v == StringView{"Kiwi"}).ToBeTrue(); + Expect(StringView{"Kiwi"} == v).ToBeTrue(); + Expect(v != StringView{"Apple"}).ToBeTrue(); + Expect(StringView{"Apple"} != v).ToBeTrue(); + Expect(v < StringView{"Lime"}).ToBeTrue(); + Expect(StringView{"Lime"} > v).ToBeTrue(); + }); - It("Can three-way compare", []() - { - String a{"Kiwi"}; - String b{"Lime"}; - Expect((a <=> b) < 0).ToBeTrue(); - Expect((b <=> a) > 0).ToBeTrue(); - Expect((a <=> String{"Kiwi"}) == 0).ToBeTrue(); - Expect((a <=> "Kiwi") == 0).ToBeTrue(); + It("Can three-way compare", []() + { + String a{"Kiwi"}; + String b{"Lime"}; + Expect((a <=> b) < 0).ToBeTrue(); + Expect((b <=> a) > 0).ToBeTrue(); + Expect((a <=> String{"Kiwi"}) == 0).ToBeTrue(); + Expect((a <=> "Kiwi") == 0).ToBeTrue(); + }); }); - }); - Describe("Memory", []() - { - It("Keeps data valid when growing", []() + Describe("Memory", []() { - String v; - for (char c = 'a'; c <= 'z'; ++c) - { - v.push_back(c); - } - Expect(v.size()).ToEqual(26u); - Expect(v).ToEqual("abcdefghijklmnopqrstuvwxyz"); - Expect(v.c_str()[26]).ToEqual('\0'); - }); + It("Keeps data valid when growing", []() + { + String v; + for (char c = 'a'; c <= 'z'; ++c) + { + v.push_back(c); + } + Expect(v.size()).ToEqual(26u); + Expect(v).ToEqual("abcdefghijklmnopqrstuvwxyz"); + Expect(v.c_str()[26]).ToEqual('\0'); + }); - It("Can reuse capacity", []() - { - String v; - v.reserve(1000); - const auto cap = v.capacity(); - for (u32 i = 0; i < 100; ++i) + It("Can reuse capacity", []() { - v.assign("KiwiAppleOrangeBanana"); - v.clear(); - } - Expect(v.capacity()).ToEqual(cap); - }); + String v; + v.reserve(1000); + const auto cap = v.capacity(); + for (u32 i = 0; i < 100; ++i) + { + v.assign("KiwiAppleOrangeBanana"); + v.clear(); + } + Expect(v.capacity()).ToEqual(cap); + }); - It("Is valid after move assignment", []() - { - String a{"Kiwi"}; - String b; - b = Move(a); - Expect(b).ToEqual("Kiwi"); - a = "Reused"; - Expect(a).ToEqual("Reused"); + It("Is valid after move assignment", []() + { + String a{"Kiwi"}; + String b; + b = Move(a); + Expect(b).ToEqual("Kiwi"); + a = "Reused"; + Expect(a).ToEqual("Reused"); + }); }); - }); - Describe("Format & Hash", []() - { - It("Can be formatted", []() + Describe("Format & Hash", []() { - String v{"Kiwi"}; - Expect(std::format("{}", v)).ToEqual("Kiwi"); - Expect(Format("{}-{}", v, 5)).ToEqual("Kiwi-5"); - String out; - FormatTo(out, "{}!", v); - Expect(out).ToEqual("Kiwi!"); - }); + It("Can be formatted", []() + { + String v{"Kiwi"}; + Expect(std::format("{}", v)).ToEqual("Kiwi"); + Expect(Format("{}-{}", v, 5)).ToEqual("Kiwi-5"); + String out; + FormatTo(out, "{}!", v); + Expect(out).ToEqual("Kiwi!"); + }); - It("Can be hashed", []() - { - String v{"Kiwi"}; - Expect(GetHash(v)).ToEqual(GetStringHash("Kiwi")); - Expect(GetHash(StringView{"Kiwi"})).ToEqual(GetHash(v)); + It("Can be hashed", []() + { + String v{"Kiwi"}; + Expect(GetHash(v)).ToEqual(GetStringHash("Kiwi")); + Expect(GetHash(StringView{"Kiwi"})).ToEqual(GetHash(v)); + }); }); - }); - Describe("Arena", []() - { - It("Can default construct on an arena", []() + Describe("Arena", []() { - MonoLinearArena arena{Memory::KB * 4}; - String v{arena}; - Expect(v.empty()).ToBeTrue(); - Expect(&v.GetArena()).ToEqual(static_cast(&arena)); - // Short strings still use the inline buffer - v = "Kiwi"; - Expect(v).ToEqual("Kiwi"); - Expect(v.capacity() <= 32u).ToBeTrue(); - }); + It("Can default construct on an arena", []() + { + MonoLinearArena arena{Memory::KB * 4}; + String v{arena}; + Expect(v.empty()).ToBeTrue(); + Expect(&v.GetArena()).ToEqual(static_cast(&arena)); + // Short strings still use the inline buffer + v = "Kiwi"; + Expect(v).ToEqual("Kiwi"); + Expect(v.capacity() <= 32u).ToBeTrue(); + }); - It("Can allocate on an arena", []() - { - MonoLinearArena arena{Memory::KB * 4}; - String v{arena, arenaLongText}; - Expect(v).ToEqual(arenaLongText); - Expect(&v.GetArena()).ToEqual(static_cast(&arena)); - // Long strings must allocate on the arena, not the current arena - Expect(v.capacity() >= v.size()).ToBeTrue(); - }); + It("Can allocate on an arena", []() + { + MonoLinearArena arena{Memory::KB * 4}; + String v{arena, arenaLongText}; + Expect(v).ToEqual(arenaLongText); + Expect(&v.GetArena()).ToEqual(static_cast(&arena)); + // Long strings must allocate on the arena, not the current arena + Expect(v.capacity() >= v.size()).ToBeTrue(); + }); - It("Can construct with count and char on an arena", []() - { - MonoLinearArena arena{Memory::KB * 4}; - String v{arena, 64, 'x'}; - Expect(v.size()).ToEqual(64u); - Expect(&v.GetArena()).ToEqual(static_cast(&arena)); - }); + It("Can construct with count and char on an arena", []() + { + MonoLinearArena arena{Memory::KB * 4}; + String v{arena, 64, 'x'}; + Expect(v.size()).ToEqual(64u); + Expect(&v.GetArena()).ToEqual(static_cast(&arena)); + }); - It("Can copy into an arena", []() - { - MonoLinearArena arena{Memory::KB * 4}; - String original{arenaLongText}; - String v{arena, original}; - Expect(v).ToEqual(original); - Expect(&v.GetArena()).ToEqual(static_cast(&arena)); - }); + It("Can copy into an arena", []() + { + MonoLinearArena arena{Memory::KB * 4}; + String original{arenaLongText}; + String v{arena, original}; + Expect(v).ToEqual(original); + Expect(&v.GetArena()).ToEqual(static_cast(&arena)); + }); - It("Keeps its arena when assigned", []() - { - MonoLinearArena arena{Memory::KB * 4}; - String v{arena}; - v.assign(arenaLongText); - v.append(" with some extra content to force a reallocation"); - Expect(&v.GetArena()).ToEqual(static_cast(&arena)); - Expect(v.starts_with("This string")).ToBeTrue(); + It("Keeps its arena when assigned", []() + { + MonoLinearArena arena{Memory::KB * 4}; + String v{arena}; + v.assign(arenaLongText); + v.append(" with some extra content to force a reallocation"); + Expect(&v.GetArena()).ToEqual(static_cast(&arena)); + Expect(v.starts_with("This string")).ToBeTrue(); + }); }); - }); - Describe("Strings helpers", []() - { - It("RemoveFromStart", []() + Describe("Strings helpers", []() { - String v{"KiwiApple"}; - Strings::RemoveFromStart(v, 4); - Expect(v).ToEqual("Apple"); - Strings::RemoveFromStart(v, 100); - Expect(v.empty()).ToBeTrue(); - }); + It("RemoveFromStart", []() + { + String v{"KiwiApple"}; + Strings::RemoveFromStart(v, 4); + Expect(v).ToEqual("Apple"); + Strings::RemoveFromStart(v, 100); + Expect(v.empty()).ToBeTrue(); + }); - It("RemoveFromEnd", []() - { - String v{"KiwiApple"}; - Strings::RemoveFromEnd(v, 5); - Expect(v).ToEqual("Kiwi"); - Strings::RemoveFromEnd(v, StringView{"wi"}); - Expect(v).ToEqual("Ki"); - Strings::RemoveFromEnd(v, 100); - Expect(v.empty()).ToBeTrue(); - }); + It("RemoveFromEnd", []() + { + String v{"KiwiApple"}; + Strings::RemoveFromEnd(v, 5); + Expect(v).ToEqual("Kiwi"); + Strings::RemoveFromEnd(v, StringView{"wi"}); + Expect(v).ToEqual("Ki"); + Strings::RemoveFromEnd(v, 100); + Expect(v.empty()).ToBeTrue(); + }); - It("RemoveCharFromEnd", []() - { - String v{"Kiwi!"}; - Expect(Strings::RemoveCharFromEnd(v, '!')).ToBeTrue(); - Expect(v).ToEqual("Kiwi"); - Expect(Strings::RemoveCharFromEnd(v, '!')).ToBeFalse(); - Expect(v).ToEqual("Kiwi"); - }); + It("RemoveCharFromEnd", []() + { + String v{"Kiwi!"}; + Expect(Strings::RemoveCharFromEnd(v, '!')).ToBeTrue(); + Expect(v).ToEqual("Kiwi"); + Expect(Strings::RemoveCharFromEnd(v, '!')).ToBeFalse(); + Expect(v).ToEqual("Kiwi"); + }); - It("ToSentenceCase", []() - { - Expect(Strings::ToSentenceCase("")).ToEqual(""); - Expect(Strings::ToSentenceCase("papa")).ToEqual("Papa"); - Expect(Strings::ToSentenceCase("papa ")).ToEqual("Papa "); - Expect(Strings::ToSentenceCase("papa3")).ToEqual("Papa 3"); - Expect(Strings::ToSentenceCase("MisterPotato")).ToEqual("Mister Potato"); - }); + It("ToSentenceCase", []() + { + Expect(Strings::ToSentenceCase("")).ToEqual(""); + Expect(Strings::ToSentenceCase("papa")).ToEqual("Papa"); + Expect(Strings::ToSentenceCase("papa ")).ToEqual("Papa "); + Expect(Strings::ToSentenceCase("papa3")).ToEqual("Papa 3"); + Expect(Strings::ToSentenceCase("MisterPotato")).ToEqual("Mister Potato"); + }); - It("Convert u16 to u8", []() - { - TString utf16string{0x41, 0x0448, 0x65e5, 0xd834, 0xdd1e}; - TString u = Strings::Convert>(utf16string); - Expect(u.size()).ToEqual(10u); - }); - It("Convert u8 to u16", []() - { - TString utf8_with_surrogates = "\xe6\x97\xa5\xd1\x88\xf0\x9d\x84\x9e"; - TString utf16result = - Strings::Convert>(utf8_with_surrogates); - Expect(utf16result.size()).ToEqual(4u); - Expect(utf16result[2] == 0xd834).ToBeTrue(); - Expect(utf16result[3] == 0xdd1e).ToBeTrue(); - }); - It("Convert u32 to u8", []() - { - TString utf32string = {0x448, 0x65E5, 0x10346}; - TString utf8result = Strings::Convert>(utf32string); - Expect(utf8result.size()).ToEqual(9u); - }); - It("Convert u8 to u32", []() - { - TString twochars = "\xe6\x97\xa5\xd1\x88"; - TString utf32result = Strings::Convert>(twochars); - Expect(utf32result.size()).ToEqual(2u); + It("Convert u16 to u8", []() + { + TString utf16string{0x41, 0x0448, 0x65e5, 0xd834, 0xdd1e}; + TString u = Strings::Convert>(utf16string); + Expect(u.size()).ToEqual(10u); + }); + It("Convert u8 to u16", []() + { + TString utf8_with_surrogates = "\xe6\x97\xa5\xd1\x88\xf0\x9d\x84\x9e"; + TString utf16result = + Strings::Convert>(utf8_with_surrogates); + Expect(utf16result.size()).ToEqual(4u); + Expect(utf16result[2] == 0xd834).ToBeTrue(); + Expect(utf16result[3] == 0xdd1e).ToBeTrue(); + }); + It("Convert u32 to u8", []() + { + TString utf32string = {0x448, 0x65E5, 0x10346}; + TString utf8result = Strings::Convert>(utf32string); + Expect(utf8result.size()).ToEqual(9u); + }); + It("Convert u8 to u32", []() + { + TString twochars = "\xe6\x97\xa5\xd1\x88"; + TString utf32result = Strings::Convert>(twochars); + Expect(utf32result.size()).ToEqual(2u); + }); }); }); }); -}); diff --git a/Tests/Core/StringView.spec.cpp b/Tests/Core/StringView.spec.cpp index 5137d182..cf312a44 100644 --- a/Tests/Core/StringView.spec.cpp +++ b/Tests/Core/StringView.spec.cpp @@ -1,8 +1,8 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include #include +#include using namespace p; @@ -10,102 +10,102 @@ using namespace p; Spec("Strings", []() { -Describe("StringView", []() -{ - It("Can assign from literal", []() + Describe("StringView", []() { - StringView v{"Kiwi"}; - Expect(v).ToEqual("Kiwi"); - Expect(v.size()).ToEqual(4); - }); + It("Can assign from literal", []() + { + StringView v{"Kiwi"}; + Expect(v).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4); + }); - It("Can assign from string", []() - { - String str{"Kiwi"}; - StringView v{str}; - Expect(v).ToEqual("Kiwi"); - Expect(v.size()).ToEqual(4); - }); + It("Can assign from string", []() + { + String str{"Kiwi"}; + StringView v{str}; + Expect(v).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4); + }); - It("Can copy empty", []() - { - StringView str{}; - StringView str2{" "}; - Expect(str.empty()).ToEqual(true); - Expect((u8*)str.data()).ToEqual(nullptr); - Expect(str2.empty()).ToEqual(false); - Expect((u8*)str2.data()).ToNotEqual(nullptr); - str2 = str; - Expect(str2.empty()).ToEqual(true); - Expect((u8*)str2.data()).ToEqual(nullptr); - }); + It("Can copy empty", []() + { + StringView str{}; + StringView str2{" "}; + Expect(str.empty()).ToEqual(true); + Expect((u8*)str.data()).ToEqual(nullptr); + Expect(str2.empty()).ToEqual(false); + Expect((u8*)str2.data()).ToNotEqual(nullptr); + str2 = str; + Expect(str2.empty()).ToEqual(true); + Expect((u8*)str2.data()).ToEqual(nullptr); + }); - It("Can retrieve string data", []() - { - StringView v{"Kiwi"}; - Expect(v.data()).ToEqual("Kiwi"); - Expect(v.size()).ToEqual(4); - StringView v2{}; - Expect((u8*)v2.data()).ToEqual(nullptr); - Expect(v2.size()).ToEqual(0); - }); + It("Can retrieve string data", []() + { + StringView v{"Kiwi"}; + Expect(v.data()).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4); + StringView v2{}; + Expect((u8*)v2.data()).ToEqual(nullptr); + Expect(v2.size()).ToEqual(0); + }); - It("Can compare", []() - { - StringView vKiwi{"Kiwi"}; - StringView vKiwi2{"Kiwi"}; - StringView vApple{"Apple"}; - Expect(vKiwi).ToEqual(vKiwi2); - Expect(vKiwi).ToNotEqual(vApple); - }); + It("Can compare", []() + { + StringView vKiwi{"Kiwi"}; + StringView vKiwi2{"Kiwi"}; + StringView vApple{"Apple"}; + Expect(vKiwi).ToEqual(vKiwi2); + Expect(vKiwi).ToNotEqual(vApple); + }); - It("Can copy", []() - { - StringView vKiwi{"Kiwi"}; - StringView vApple{"Apple"}; - StringView vCopy = vKiwi; - Expect(vCopy).ToEqual("Kiwi"); - Expect(vCopy).ToEqual(vKiwi); - Expect(vCopy).ToNotEqual(vApple); - vCopy = vApple; - Expect(vCopy).ToEqual("Apple"); - Expect(vCopy).ToNotEqual(vKiwi); - Expect(vCopy).ToEqual(vApple); - }); + It("Can copy", []() + { + StringView vKiwi{"Kiwi"}; + StringView vApple{"Apple"}; + StringView vCopy = vKiwi; + Expect(vCopy).ToEqual("Kiwi"); + Expect(vCopy).ToEqual(vKiwi); + Expect(vCopy).ToNotEqual(vApple); + vCopy = vApple; + Expect(vCopy).ToEqual("Apple"); + Expect(vCopy).ToNotEqual(vKiwi); + Expect(vCopy).ToEqual(vApple); + }); - It("Can move", []() - { - StringView vKiwi{"Kiwi"}; - StringView vApple{"Apple"}; - StringView vMove = Move(vKiwi); - Expect(vMove).ToEqual("Kiwi"); - vMove = Move(vApple); - Expect(vMove).ToEqual("Apple"); - }); + It("Can move", []() + { + StringView vKiwi{"Kiwi"}; + StringView vApple{"Apple"}; + StringView vMove = Move(vKiwi); + Expect(vMove).ToEqual("Kiwi"); + vMove = Move(vApple); + Expect(vMove).ToEqual("Apple"); + }); - Describe("Strings", []() - { - It("Can Find", []() + Describe("Strings", []() { - StringView v{"Kiwiwi"}; + It("Can Find", []() + { + StringView v{"Kiwiwi"}; - // Find Chars - Expect(Strings::Find(v, 'K', FindDir::Front)).ToEqual(0); - Expect(Strings::Find(v, 'K', FindDir::Back)).ToEqual(0); - Expect(Strings::Find(v, 'i', FindDir::Front)).ToEqual(1); - Expect(Strings::Find(v, 'i', FindDir::Back)).ToEqual(5); - // Find last chars - Expect(Strings::Find(v, 'w', FindDir::Front, true)).ToEqual(0); // 'K' - Expect(Strings::Find(v, 'w', FindDir::Back, true)).ToEqual(5); // 'i' - Expect(Strings::Find(v, 'K', FindDir::Front, true)).ToEqual(1); // 'i' - Expect(Strings::Find(v, 'i', FindDir::Back, true)).ToEqual(4); // 'w' + // Find Chars + Expect(Strings::Find(v, 'K', FindDir::Front)).ToEqual(0); + Expect(Strings::Find(v, 'K', FindDir::Back)).ToEqual(0); + Expect(Strings::Find(v, 'i', FindDir::Front)).ToEqual(1); + Expect(Strings::Find(v, 'i', FindDir::Back)).ToEqual(5); + // Find last chars + Expect(Strings::Find(v, 'w', FindDir::Front, true)).ToEqual(0); // 'K' + Expect(Strings::Find(v, 'w', FindDir::Back, true)).ToEqual(5); // 'i' + Expect(Strings::Find(v, 'K', FindDir::Front, true)).ToEqual(1); // 'i' + Expect(Strings::Find(v, 'i', FindDir::Back, true)).ToEqual(4); // 'w' - // Find Sub-strings - Expect(Strings::Find(v, "Ki", FindDir::Front)).ToEqual(0); - Expect(Strings::Find(v, "Ki", FindDir::Back)).ToEqual(0); - Expect(Strings::Find(v, "wi", FindDir::Front)).ToEqual(2); - Expect(Strings::Find(v, "wi", FindDir::Back)).ToEqual(4); + // Find Sub-strings + Expect(Strings::Find(v, "Ki", FindDir::Front)).ToEqual(0); + Expect(Strings::Find(v, "Ki", FindDir::Back)).ToEqual(0); + Expect(Strings::Find(v, "wi", FindDir::Front)).ToEqual(2); + Expect(Strings::Find(v, "wi", FindDir::Back)).ToEqual(4); + }); }); }); }); -}); diff --git a/Tests/Core/Tag.spec.cpp b/Tests/Core/Tag.spec.cpp index b1f8bfff..35979413 100644 --- a/Tests/Core/Tag.spec.cpp +++ b/Tests/Core/Tag.spec.cpp @@ -1,7 +1,7 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include +#include using namespace p; @@ -9,96 +9,96 @@ using namespace p; Spec("Core.Tag", []() { -It("Can copy empty", []() -{ - Tag tag{}; - Tag tag2{"Ahh"}; - Expect(p::GetHash(tag)).ToEqual(0); - Expect(tag.IsNone()).ToEqual(true); - Expect(p::GetHash(tag2)).ToNotEqual(0); - Expect(tag2.IsNone()).ToEqual(false); - tag2 = tag; - Expect(p::GetHash(tag2)).ToEqual(0); - Expect(tag2.IsNone()).ToEqual(true); -}); -It("Can assign from literal", []() -{ - Tag tag{"Kiwi"}; - Expect(tag.AsString()).ToEqual("Kiwi"); -}); + It("Can copy empty", []() + { + Tag tag{}; + Tag tag2{"Ahh"}; + Expect(p::GetHash(tag)).ToEqual(0); + Expect(tag.IsNone()).ToEqual(true); + Expect(p::GetHash(tag2)).ToNotEqual(0); + Expect(tag2.IsNone()).ToEqual(false); + tag2 = tag; + Expect(p::GetHash(tag2)).ToEqual(0); + Expect(tag2.IsNone()).ToEqual(true); + }); + It("Can assign from literal", []() + { + Tag tag{"Kiwi"}; + Expect(tag.AsString()).ToEqual("Kiwi"); + }); -It("Can assign from string", []() -{ - String str{"Kiwi"}; - Tag tag{str}; - Expect(tag.AsString()).ToEqual("Kiwi"); -}); + It("Can assign from string", []() + { + String str{"Kiwi"}; + Tag tag{str}; + Expect(tag.AsString()).ToEqual("Kiwi"); + }); -It("Can retrieve string data", []() -{ - Tag tag{"Kiwi"}; - Expect(tag.AsString()).ToEqual("Kiwi"); -}); + It("Can retrieve string data", []() + { + Tag tag{"Kiwi"}; + Expect(tag.AsString()).ToEqual("Kiwi"); + }); -It("Can compare tags", []() -{ - Tag tagKiwi{"Kiwi"}; - Tag tagKiwi2{"Kiwi"}; - Tag tagApple{"Apple"}; - Expect(tagKiwi).ToEqual(tagKiwi2); - Expect(tagKiwi).ToNotEqual(tagApple); -}); + It("Can compare tags", []() + { + Tag tagKiwi{"Kiwi"}; + Tag tagKiwi2{"Kiwi"}; + Tag tagApple{"Apple"}; + Expect(tagKiwi).ToEqual(tagKiwi2); + Expect(tagKiwi).ToNotEqual(tagApple); + }); -It("Different instances share string allocation", []() -{ - Tag tagKiwi{"Kiwi"}; - Tag tagKiwi2{"Kiwi"}; - Tag tagApple{"Apple"}; - Expect(tagKiwi.AsString().data()).ToEqual(tagKiwi2.AsString().data()); - Expect(tagKiwi.AsString().data()).ToNotEqual(tagApple.AsString().data()); -}); + It("Different instances share string allocation", []() + { + Tag tagKiwi{"Kiwi"}; + Tag tagKiwi2{"Kiwi"}; + Tag tagApple{"Apple"}; + Expect(tagKiwi.AsString().data()).ToEqual(tagKiwi2.AsString().data()); + Expect(tagKiwi.AsString().data()).ToNotEqual(tagApple.AsString().data()); + }); -It("Can check invalid/none", []() -{ - Tag tagValid{"Kiwi"}; - Tag tagInvalid{}; - Expect(tagValid.IsNone()).ToEqual(false); - Expect(tagValid).ToNotEqual(Tag::None()); - Expect(tagInvalid.IsNone()).ToEqual(true); - Expect(tagInvalid).ToEqual(Tag::None()); -}); + It("Can check invalid/none", []() + { + Tag tagValid{"Kiwi"}; + Tag tagInvalid{}; + Expect(tagValid.IsNone()).ToEqual(false); + Expect(tagValid).ToNotEqual(Tag::None()); + Expect(tagInvalid.IsNone()).ToEqual(true); + Expect(tagInvalid).ToEqual(Tag::None()); + }); -It("Contains correct hashes", []() -{ - Tag tagKiwi{"Kiwi"}; - Tag tagKiwi2{"Kiwi"}; - Expect(p::GetHash(tagKiwi)).ToEqual(p::GetHash(tagKiwi2)); - Expect(tagKiwi.GetStringHash()).ToEqual(p::GetHash("Kiwi")); -}); + It("Contains correct hashes", []() + { + Tag tagKiwi{"Kiwi"}; + Tag tagKiwi2{"Kiwi"}; + Expect(p::GetHash(tagKiwi)).ToEqual(p::GetHash(tagKiwi2)); + Expect(tagKiwi.GetStringHash()).ToEqual(p::GetHash("Kiwi")); + }); -It("Can copy tag", []() -{ - Tag tagKiwi{"Kiwi"}; - Tag tagApple{"Apple"}; - Tag tagCopy = tagKiwi; - Expect(tagCopy.AsString()).ToEqual("Kiwi"); - Expect(tagCopy).ToEqual(tagKiwi); - Expect(tagCopy).ToNotEqual(tagApple); - tagCopy = tagApple; - Expect(tagCopy.AsString()).ToEqual("Apple"); - Expect(tagCopy).ToNotEqual(tagKiwi); - Expect(tagCopy).ToEqual(tagApple); -}); + It("Can copy tag", []() + { + Tag tagKiwi{"Kiwi"}; + Tag tagApple{"Apple"}; + Tag tagCopy = tagKiwi; + Expect(tagCopy.AsString()).ToEqual("Kiwi"); + Expect(tagCopy).ToEqual(tagKiwi); + Expect(tagCopy).ToNotEqual(tagApple); + tagCopy = tagApple; + Expect(tagCopy.AsString()).ToEqual("Apple"); + Expect(tagCopy).ToNotEqual(tagKiwi); + Expect(tagCopy).ToEqual(tagApple); + }); -It("Can move tag", []() -{ - Tag tagKiwi{"Kiwi"}; - Tag tagApple{"Apple"}; - Tag tagMove = Move(tagKiwi); - Expect(tagKiwi).ToEqual(Tag::None()); - Expect(tagMove.AsString()).ToEqual("Kiwi"); - tagMove = Move(tagApple); - Expect(tagApple).ToEqual(Tag::None()); - Expect(tagMove.AsString()).ToEqual("Apple"); -}); + It("Can move tag", []() + { + Tag tagKiwi{"Kiwi"}; + Tag tagApple{"Apple"}; + Tag tagMove = Move(tagKiwi); + Expect(tagKiwi).ToEqual(Tag::None()); + Expect(tagMove.AsString()).ToEqual("Kiwi"); + tagMove = Move(tagApple); + Expect(tagApple).ToEqual(Tag::None()); + Expect(tagMove.AsString()).ToEqual("Apple"); + }); }); diff --git a/Tests/ECS/Components.spec.cpp b/Tests/ECS/Components.spec.cpp index 0998e656..68cedba8 100644 --- a/Tests/ECS/Components.spec.cpp +++ b/Tests/ECS/Components.spec.cpp @@ -1,7 +1,7 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include +#include using namespace p; @@ -46,245 +46,244 @@ u32 TestComponent::destructed = 0; Spec("ECS.Components", []() { -It("Can add one component", []() -{ - IdContext ctx; - Id id = AddId(ctx); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - - ctx.Add(id); - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - - ctx.Add(id); - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.TryGet(id)).ToNotEqual(nullptr); -}); - -It("Can remove one component", []() -{ - IdContext ctx; - Id id = AddId(ctx); - ctx.Add(id); - - ctx.Remove(id); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - - NonEmptyComponent::destructed = 0; - ctx.Remove(id); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - Expect(NonEmptyComponent::destructed).ToEqual(1); -}); - -It("Can add many components", []() -{ - IdContext ctx; - TArray ids{3}; - AddId(ctx, ids); - ctx.AddN(ids, NonEmptyComponent{2}); - - for (Id id : ids) + It("Can add one component", []() { - auto* data = ctx.TryGet(id); - Expect(data).ToNotEqual(nullptr); - Expect(data->a).ToEqual(2); - } -}); - -It("Can remove many components", []() -{ - IdContext ctx; - TArray ids{3}; - AddId(ctx, ids); - ctx.AddN(ids, NonEmptyComponent{2}); - - NonEmptyComponent::destructed = 0; - TView firstTwo{ids.Data(), ids.Data() + 2}; - ctx.Remove(firstTwo); - Expect(NonEmptyComponent::destructed).ToEqual(2); - Expect(ctx.TryGet(ids[0])).ToEqual(nullptr); - Expect(ctx.TryGet(ids[1])).ToEqual(nullptr); - Expect(ctx.TryGet(ids[2])).ToNotEqual(nullptr); - - // Repeat in different order - ctx.AddN(ids, NonEmptyComponent{2}); - - NonEmptyComponent::destructed = 0; - TView lastTwo{ids.Data() + 1, ids.Data() + 3}; - ctx.Remove(lastTwo); - Expect(NonEmptyComponent::destructed).ToEqual(2); - Expect(ctx.TryGet(ids[0])).ToNotEqual(nullptr); - Expect(ctx.TryGet(ids[1])).ToEqual(nullptr); - Expect(ctx.TryGet(ids[2])).ToEqual(nullptr); -}); + IdContext ctx; + Id id = AddId(ctx); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + + ctx.Add(id); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + + ctx.Add(id); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.TryGet(id)).ToNotEqual(nullptr); + }); + + It("Can remove one component", []() + { + IdContext ctx; + Id id = AddId(ctx); + ctx.Add(id); + + ctx.Remove(id); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + + NonEmptyComponent::destructed = 0; + ctx.Remove(id); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + Expect(NonEmptyComponent::destructed).ToEqual(1); + }); + + It("Can add many components", []() + { + IdContext ctx; + TArray ids{3}; + AddId(ctx, ids); + ctx.AddN(ids, NonEmptyComponent{2}); + + for (Id id : ids) + { + auto* data = ctx.TryGet(id); + Expect(data).ToNotEqual(nullptr); + Expect(data->a).ToEqual(2); + } + }); + + It("Can remove many components", []() + { + IdContext ctx; + TArray ids{3}; + AddId(ctx, ids); + ctx.AddN(ids, NonEmptyComponent{2}); + + NonEmptyComponent::destructed = 0; + TView firstTwo{ids.Data(), ids.Data() + 2}; + ctx.Remove(firstTwo); + Expect(NonEmptyComponent::destructed).ToEqual(2); + Expect(ctx.TryGet(ids[0])).ToEqual(nullptr); + Expect(ctx.TryGet(ids[1])).ToEqual(nullptr); + Expect(ctx.TryGet(ids[2])).ToNotEqual(nullptr); + + // Repeat in different order + ctx.AddN(ids, NonEmptyComponent{2}); + + NonEmptyComponent::destructed = 0; + TView lastTwo{ids.Data() + 1, ids.Data() + 3}; + ctx.Remove(lastTwo); + Expect(NonEmptyComponent::destructed).ToEqual(2); + Expect(ctx.TryGet(ids[0])).ToNotEqual(nullptr); + Expect(ctx.TryGet(ids[1])).ToEqual(nullptr); + Expect(ctx.TryGet(ids[2])).ToEqual(nullptr); + }); + + It("Components are removed after node is deleted", []() + { + IdContext ctx; + Id id = AddId(ctx); + ctx.Add(id); -It("Components are removed after node is deleted", []() -{ - IdContext ctx; - Id id = AddId(ctx); - ctx.Add(id); + RmId(ctx, id, p::RmIdFlags::Instant); + Expect(ctx.IsValid(id)).ToBeFalse(); - RmId(ctx, id, p::RmIdFlags::Instant); - Expect(ctx.IsValid(id)).ToBeFalse(); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + }); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); -}); + It("Components are removed after node is deleted (deferred)", []() + { + IdContext ctx; + Id id = AddId(ctx); + ctx.Add(id); + + RmId(ctx, id); + Expect(ctx.IsValid(id)).ToBeFalse(); + + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.TryGet(id)).ToNotEqual(nullptr); + + FlushDeferredRemovals(ctx); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + }); + + It("Components keep state when added", []() + { + IdContext ctx; + Id id = AddId(ctx); + ctx.AddN(id, NonEmptyComponent{2}); + Expect(ctx.TryGet(id)).ToNotEqual(nullptr); + Expect(ctx.Get(id).a).ToEqual(2); + }); + + It("Can copy registry", []() + { + IdContext ctxa; -It("Components are removed after node is deleted (deferred)", []() -{ - IdContext ctx; - Id id = AddId(ctx); - ctx.Add(id); - - RmId(ctx, id); - Expect(ctx.IsValid(id)).ToBeFalse(); - - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.TryGet(id)).ToNotEqual(nullptr); - - FlushDeferredRemovals(ctx); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); -}); + Id id = AddId(ctxa); + ctxa.Add(id); + Id id2 = AddId(ctxa); + ctxa.AddN(id2, NonEmptyComponent{2}); -It("Components keep state when added", []() -{ - IdContext ctx; - Id id = AddId(ctx); - ctx.AddN(id, NonEmptyComponent{2}); - Expect(ctx.TryGet(id)).ToNotEqual(nullptr); - Expect(ctx.Get(id).a).ToEqual(2); -}); + IdContext ctxb{ctxa}; + Expect(ctxb.Has(id)).ToBeTrue(); + Expect(ctxb.Has(id)).ToBeTrue(); + Expect(ctxb.TryGet(id)).ToNotEqual(nullptr); -It("Can copy registry", []() -{ - IdContext ctxa; + // Holds component values + Expect(ctxb.Has(id2)).ToBeTrue(); + Expect(ctxb.Get(id2).a).ToEqual(2); + }); - Id id = AddId(ctxa); - ctxa.Add(id); - Id id2 = AddId(ctxa); - ctxa.AddN(id2, NonEmptyComponent{2}); + It("Can check components", []() + { + IdContext ctx; + Id id = NoId; + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.Has(id)).ToBeFalse(); - IdContext ctxb{ctxa}; - Expect(ctxb.Has(id)).ToBeTrue(); - Expect(ctxb.Has(id)).ToBeTrue(); - Expect(ctxb.TryGet(id)).ToNotEqual(nullptr); + id = AddId(ctx); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.Has(id)).ToBeFalse(); - // Holds component values - Expect(ctxb.Has(id2)).ToBeTrue(); - Expect(ctxb.Get(id2).a).ToEqual(2); -}); + ctx.Add(id); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.Has(id)).ToBeTrue(); + }); -It("Can check components", []() -{ - IdContext ctx; - Id id = NoId; - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.Has(id)).ToBeFalse(); - - id = AddId(ctx); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.Has(id)).ToBeFalse(); - - ctx.Add(id); - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.Has(id)).ToBeTrue(); -}); + It("Can destroy components on reset", []() + { + NonEmptyComponent::destructed = 0; + TestComponent::destructed = 0; -It("Can destroy components on reset", []() -{ - NonEmptyComponent::destructed = 0; - TestComponent::destructed = 0; - - IdContext ctx; - TArray ids{3}; - AddId(ctx, ids); - ctx.AddN(ids, NonEmptyComponent{2}); - ctx.AddN(ids); - - ctx.Remove(ids); - ctx.Remove(ids[0]); - Expect( - NonEmptyComponent::destructed).ToEqual(4); // 3 + 1 (passed by value on Add()) - Expect(TestComponent::destructed).ToEqual(2); // 1 + 1 (passed by value on Add()) - - NonEmptyComponent::destructed = 0; - TestComponent::destructed = 0; - ctx.Reset(); - - Expect(NonEmptyComponent::destructed).ToEqual(0); - Expect(TestComponent::destructed).ToEqual(2); -}); + IdContext ctx; + TArray ids{3}; + AddId(ctx, ids); + ctx.AddN(ids, NonEmptyComponent{2}); + ctx.AddN(ids); -It("Components are removed with the entity", []() -{ - IdContext ctx; - Id id = AddId(ctx); - ctx.Add(id); - RmId(ctx, id, p::RmIdFlags::Instant); - Expect(ctx.IsValid(id)).ToBeFalse(); - - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); -}); + ctx.Remove(ids); + ctx.Remove(ids[0]); + Expect(NonEmptyComponent::destructed).ToEqual(4); // 3 + 1 (passed by value on Add()) + Expect(TestComponent::destructed).ToEqual(2); // 1 + 1 (passed by value on Add()) -It("Components are removed with the entity (deferred)", []() -{ - IdContext ctx; - Id id = AddId(ctx); - ctx.Add(id); - RmId(ctx, id); - Expect(ctx.IsValid(id)).ToBeFalse(); - - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.TryGet(id)).ToNotEqual(nullptr); - - FlushDeferredRemovals(ctx); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.TryGet(id)).ToEqual(nullptr); -}); + NonEmptyComponent::destructed = 0; + TestComponent::destructed = 0; + ctx.Reset(); -It("Can access components on recicled entities", []() -{ - IdContext ctx; - Id id = AddId(ctx); - ctx.Add(id); - RmId(ctx, id); - - id = AddId(ctx); - ctx.Add(id); - Expect(ctx.Has(id)).ToBeFalse(); - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.TryGet(id)).ToNotEqual(nullptr); -}); + Expect(NonEmptyComponent::destructed).ToEqual(0); + Expect(TestComponent::destructed).ToEqual(2); + }); -It("Can access CRemoved", []() -{ - IdContext ctx; - Id id = AddId(ctx); - ctx.Add(id); - RmId(ctx, id); - - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.Has(id)).ToBeTrue(); - Expect(ctx.TryGet(id)).ToNotEqual(nullptr); -}); + It("Components are removed with the entity", []() + { + IdContext ctx; + Id id = AddId(ctx); + ctx.Add(id); + RmId(ctx, id, p::RmIdFlags::Instant); + Expect(ctx.IsValid(id)).ToBeFalse(); + + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + }); + + It("Components are removed with the entity (deferred)", []() + { + IdContext ctx; + Id id = AddId(ctx); + ctx.Add(id); + RmId(ctx, id); + Expect(ctx.IsValid(id)).ToBeFalse(); + + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.TryGet(id)).ToNotEqual(nullptr); + + FlushDeferredRemovals(ctx); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.TryGet(id)).ToEqual(nullptr); + }); + + It("Can access components on recicled entities", []() + { + IdContext ctx; + Id id = AddId(ctx); + ctx.Add(id); + RmId(ctx, id); + + id = AddId(ctx); + ctx.Add(id); + Expect(ctx.Has(id)).ToBeFalse(); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.TryGet(id)).ToNotEqual(nullptr); + }); + + It("Can access CRemoved", []() + { + IdContext ctx; + Id id = AddId(ctx); + ctx.Add(id); + RmId(ctx, id); + + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.Has(id)).ToBeTrue(); + Expect(ctx.TryGet(id)).ToNotEqual(nullptr); + }); }); diff --git a/Tests/ECS/ECS.spec.cpp b/Tests/ECS/ECS.spec.cpp index 231d9e61..ad544833 100644 --- a/Tests/ECS/ECS.spec.cpp +++ b/Tests/ECS/ECS.spec.cpp @@ -1,7 +1,7 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include +#include using namespace p; @@ -17,53 +17,53 @@ struct ECSTypeB Spec("ECS", []() { -It("Can copy context", []() -{ - static IdContext* ctxPtr = nullptr; - - IdContext origin; - Id id = AddId(origin); - - ctxPtr = &origin; - origin.Add(id); - - IdContext target{origin}; - Expect(origin.IsValid(id)).ToEqual(true); - Expect(origin.Has(id)).ToEqual(true); - Expect(target.IsValid(id)).ToEqual(true); - Expect(target.Has(id)).ToEqual(true); - - ctxPtr = ⌖ - target.Add(id); - Expect(target.Has(id)).ToEqual(true); -}); - -It("Can move context", []() -{ - static IdContext* ctxPtr = nullptr; - - IdContext origin; - Id id = AddId(origin); - - ctxPtr = &origin; - origin.Add(id); - Expect(origin.Has(id)).ToEqual(true); - - IdContext target{Move(origin)}; - Expect(origin.IsValid(id)).ToEqual(false); - - Expect(target.IsValid(id)).ToEqual(true); - Expect(target.Has(id)).ToEqual(true); - - ctxPtr = ⌖ - target.Add(id); - Expect(target.Has(id)).ToEqual(true); -}); - -It("Can assure pool", []() -{ - IdContext origin; - TPool& pool = origin.AssurePool(); - Expect(pool.Size()).ToEqual(0); -}); + It("Can copy context", []() + { + static IdContext* ctxPtr = nullptr; + + IdContext origin; + Id id = AddId(origin); + + ctxPtr = &origin; + origin.Add(id); + + IdContext target{origin}; + Expect(origin.IsValid(id)).ToEqual(true); + Expect(origin.Has(id)).ToEqual(true); + Expect(target.IsValid(id)).ToEqual(true); + Expect(target.Has(id)).ToEqual(true); + + ctxPtr = ⌖ + target.Add(id); + Expect(target.Has(id)).ToEqual(true); + }); + + It("Can move context", []() + { + static IdContext* ctxPtr = nullptr; + + IdContext origin; + Id id = AddId(origin); + + ctxPtr = &origin; + origin.Add(id); + Expect(origin.Has(id)).ToEqual(true); + + IdContext target{Move(origin)}; + Expect(origin.IsValid(id)).ToEqual(false); + + Expect(target.IsValid(id)).ToEqual(true); + Expect(target.Has(id)).ToEqual(true); + + ctxPtr = ⌖ + target.Add(id); + Expect(target.Has(id)).ToEqual(true); + }); + + It("Can assure pool", []() + { + IdContext origin; + TPool& pool = origin.AssurePool(); + Expect(pool.Size()).ToEqual(0); + }); }); diff --git a/Tests/ECS/Filtering.spec.cpp b/Tests/ECS/Filtering.spec.cpp index f23ffefa..56d552f5 100644 --- a/Tests/ECS/Filtering.spec.cpp +++ b/Tests/ECS/Filtering.spec.cpp @@ -1,7 +1,7 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include +#include using namespace p; @@ -28,203 +28,203 @@ namespace Spec("ECS.Filtering", []() { -BeforeEach([]() -{ - ctx = {}; - id1 = AddId(ctx); - id2 = AddId(ctx); - id3 = AddId(ctx); - id4 = AddId(ctx); - id5 = AddId(ctx); - ctx.Add(id1); - ctx.Add(id2); - ctx.Add(id3); - ctx.Add(id4); - ctx.Add(id5); -}); - -Describe("FindAllIdsWith/FindAllIdsWithAny", []() -{ - It("Can get list matching all", []() + BeforeEach([]() { - TIdScope access{ctx}; - TArray typeIds = FindAllIdsWith(access); - Expect(typeIds.Contains(id1)).ToBeTrue(); - Expect(typeIds.Contains(id2)).ToBeTrue(); - Expect(typeIds.Contains(id3)).ToBeFalse(); - - TArray type2Ids = FindAllIdsWith(access); - Expect(type2Ids.Contains(id1)).ToBeFalse(); - Expect(type2Ids.Contains(id2)).ToBeTrue(); - Expect(type2Ids.Contains(id3)).ToBeTrue(); + ctx = {}; + id1 = AddId(ctx); + id2 = AddId(ctx); + id3 = AddId(ctx); + id4 = AddId(ctx); + id5 = AddId(ctx); + ctx.Add(id1); + ctx.Add(id2); + ctx.Add(id3); + ctx.Add(id4); + ctx.Add(id5); }); - It("Can get list matching any", []() + Describe("FindAllIdsWith/FindAllIdsWithAny", []() { - TIdScope access{ctx}; - TArray typeIds = FindAllIdsWithAny(access); - Expect(typeIds.Contains(id1)).ToBeTrue(); - Expect(typeIds.Contains(id2)).ToBeTrue(); - Expect(typeIds.Contains(id3)).ToBeFalse(); - - TArray type2Ids = FindAllIdsWithAny(access); - Expect(type2Ids.Contains(id1)).ToBeTrue(); - Expect(type2Ids.Contains(id2)).ToBeTrue(); - Expect(type2Ids.Contains(id3)).ToBeTrue(); + It("Can get list matching all", []() + { + TIdScope access{ctx}; + TArray typeIds = FindAllIdsWith(access); + Expect(typeIds.Contains(id1)).ToBeTrue(); + Expect(typeIds.Contains(id2)).ToBeTrue(); + Expect(typeIds.Contains(id3)).ToBeFalse(); + + TArray type2Ids = FindAllIdsWith(access); + Expect(type2Ids.Contains(id1)).ToBeFalse(); + Expect(type2Ids.Contains(id2)).ToBeTrue(); + Expect(type2Ids.Contains(id3)).ToBeTrue(); + }); + + It("Can get list matching any", []() + { + TIdScope access{ctx}; + TArray typeIds = FindAllIdsWithAny(access); + Expect(typeIds.Contains(id1)).ToBeTrue(); + Expect(typeIds.Contains(id2)).ToBeTrue(); + Expect(typeIds.Contains(id3)).ToBeFalse(); + + TArray type2Ids = FindAllIdsWithAny(access); + Expect(type2Ids.Contains(id1)).ToBeTrue(); + Expect(type2Ids.Contains(id2)).ToBeTrue(); + Expect(type2Ids.Contains(id3)).ToBeTrue(); + }); + + It("Doesn't list removed ids", []() + { + TIdScope access{ctx}; + RmId(ctx, id2, RmIdFlags::Instant); + RmId(ctx, id3, RmIdFlags::Instant); + RmId(ctx, id4, RmIdFlags::Instant); + + TArray ids = FindAllIdsWith(access); + Expect(ids.Contains(NoId)).ToBeFalse(); + Expect(ids.Size()).ToEqual(1); + }); + + It("Doesn't list (deferred) removed ids", []() + { + TIdScope access{ctx}; + RmId(ctx, id2); + RmId(ctx, id3); + RmId(ctx, id4); + + FlushDeferredRemovals(ctx); + + TArray ids = FindAllIdsWith(access); + Expect(ids.Contains(NoId)).ToBeFalse(); + Expect(ids.Size()).ToEqual(1); + }); }); - It("Doesn't list removed ids", []() + Describe("ExcludeIdsWith", []() { - TIdScope access{ctx}; - RmId(ctx, id2, RmIdFlags::Instant); - RmId(ctx, id3, RmIdFlags::Instant); - RmId(ctx, id4, RmIdFlags::Instant); - - TArray ids = FindAllIdsWith(access); - Expect(ids.Contains(NoId)).ToBeFalse(); - Expect(ids.Size()).ToEqual(1); + It("Removes ids containing component", []() + { + TIdScope access{ctx}; + TArray typeIds = FindAllIdsWithAny(access); + + ExcludeIdsWith(access, typeIds); + Expect(typeIds.Contains(id1)).ToBeTrue(); + Expect(typeIds.Contains(id2)).ToBeFalse(); + Expect(typeIds.Contains(id3)).ToBeFalse(); + }); + + It("Removes ids not containing component", []() + { + TIdScope access{ctx}; + TArray typeIds = FindAllIdsWithAny(access); + + ExcludeIdsWithout(access, typeIds); + Expect(typeIds.Contains(id1)).ToBeFalse(); + Expect(typeIds.Contains(id2)).ToBeTrue(); + Expect(typeIds.Contains(id3)).ToBeFalse(); + }); + + It("Removes ids containing multiple component", []() + { + TIdScope access{ctx}; + TArray typeIds = FindAllIdsWithAny(access); + + ExcludeIdsWith(access, typeIds); + Expect(typeIds.Contains(id1)).ToBeTrue(); + Expect(typeIds.Contains(id2)).ToBeFalse(); + Expect(typeIds.Contains(id3)).ToBeFalse(); + }); }); - It("Doesn't list (deferred) removed ids", []() + Describe("FindIdsWith", []() { - TIdScope access{ctx}; - RmId(ctx, id2); - RmId(ctx, id3); - RmId(ctx, id4); - - FlushDeferredRemovals(ctx); - - TArray ids = FindAllIdsWith(access); - Expect(ids.Contains(NoId)).ToBeFalse(); - Expect(ids.Size()).ToEqual(1); + It("Finds ids containing a component from a list", []() + { + TArray source{id1, id2, id3}; + + TIdScope access{ctx}; + TArray typeIds = FindIdsWith(access, source); + Expect(typeIds.Contains(id1)).ToBeTrue(); + Expect(typeIds.Contains(id2)).ToBeTrue(); + Expect(typeIds.Contains(id3)).ToBeFalse(); + }); + + It("Finds ids not containing a component from a list", []() + { + TArray source{id1, id2, id3}; + + TIdScope access{ctx}; + TArray ids = FindIdsWithout(access, source); + Expect(ids.Contains(id1)).ToBeFalse(); + Expect(ids.Contains(id2)).ToBeFalse(); + Expect(ids.Contains(id3)).ToBeTrue(); + }); }); -}); -Describe("ExcludeIdsWith", []() -{ - It("Removes ids containing component", []() - { - TIdScope access{ctx}; - TArray typeIds = FindAllIdsWithAny(access); - - ExcludeIdsWith(access, typeIds); - Expect(typeIds.Contains(id1)).ToBeTrue(); - Expect(typeIds.Contains(id2)).ToBeFalse(); - Expect(typeIds.Contains(id3)).ToBeFalse(); - }); - - It("Removes ids not containing component", []() + Describe("ExtractIdsWith", []() { - TIdScope access{ctx}; - TArray typeIds = FindAllIdsWithAny(access); - - ExcludeIdsWithout(access, typeIds); - Expect(typeIds.Contains(id1)).ToBeFalse(); - Expect(typeIds.Contains(id2)).ToBeTrue(); - Expect(typeIds.Contains(id3)).ToBeFalse(); + It("Finds and removes ids containing a component from a list", []() + { + TArray source{id1, id2, id3}; + + TIdScope access{ctx}; + TArray ids = ExtractIdsWith(access, source); + Expect(ids.Contains(id1)).ToBeTrue(); + Expect(ids.Contains(id2)).ToBeTrue(); + Expect(ids.Contains(id3)).ToBeFalse(); + Expect(source.Contains(id1)).ToBeFalse(); + Expect(source.Contains(id2)).ToBeFalse(); + Expect(source.Contains(id3)).ToBeTrue(); + }); + + It("Finds and removes ids not containing a component from a list", []() + { + TArray source{id1, id2, id3}; + + TIdScope access{ctx}; + TArray ids = ExtractIdsWithout(access, source); + Expect(ids.Contains(id1)).ToBeFalse(); + Expect(ids.Contains(id2)).ToBeFalse(); + Expect(ids.Contains(id3)).ToBeTrue(); + Expect(source.Contains(id1)).ToBeTrue(); + Expect(source.Contains(id2)).ToBeTrue(); + Expect(source.Contains(id3)).ToBeFalse(); + }); }); - It("Removes ids containing multiple component", []() + It("Can filter directly from ECS", []() { - TIdScope access{ctx}; - TArray typeIds = FindAllIdsWithAny(access); + TArray ids1 = FindAllIdsWith(ctx); + Expect(ids1.Contains(id1)).ToBeTrue(); - ExcludeIdsWith(access, typeIds); - Expect(typeIds.Contains(id1)).ToBeTrue(); - Expect(typeIds.Contains(id2)).ToBeFalse(); - Expect(typeIds.Contains(id3)).ToBeFalse(); - }); -}); + TArray ids2 = FindAllIdsWithAny(ctx); + Expect(ids2.Contains(id1)).ToBeTrue(); -Describe("FindIdsWith", []() -{ - It("Finds ids containing a component from a list", []() - { - TArray source{id1, id2, id3}; + TArray ids3 = FindAllIdsWithAny(ctx); + ExcludeIdsWith(ctx, ids3); + Expect(ids3.Contains(id1)).ToBeTrue(); - TIdScope access{ctx}; - TArray typeIds = FindIdsWith(access, source); - Expect(typeIds.Contains(id1)).ToBeTrue(); - Expect(typeIds.Contains(id2)).ToBeTrue(); - Expect(typeIds.Contains(id3)).ToBeFalse(); + TArray ids4 = FindAllIdsWithAny(ctx); + ExcludeIdsWithout(ctx, ids4); + Expect(ids4.Contains(id1)).ToBeFalse(); }); - It("Finds ids not containing a component from a list", []() + It("Can filter CRemoved", []() { - TArray source{id1, id2, id3}; - - TIdScope access{ctx}; - TArray ids = FindIdsWithout(access, source); - Expect(ids.Contains(id1)).ToBeFalse(); - Expect(ids.Contains(id2)).ToBeFalse(); - Expect(ids.Contains(id3)).ToBeTrue(); - }); -}); - -Describe("ExtractIdsWith", []() -{ - It("Finds and removes ids containing a component from a list", []() - { - TArray source{id1, id2, id3}; - - TIdScope access{ctx}; - TArray ids = ExtractIdsWith(access, source); - Expect(ids.Contains(id1)).ToBeTrue(); - Expect(ids.Contains(id2)).ToBeTrue(); - Expect(ids.Contains(id3)).ToBeFalse(); - Expect(source.Contains(id1)).ToBeFalse(); - Expect(source.Contains(id2)).ToBeFalse(); - Expect(source.Contains(id3)).ToBeTrue(); - }); + RmId(ctx, id1); + RmId(ctx, id2); + RmId(ctx, id3); - It("Finds and removes ids not containing a component from a list", []() - { - TArray source{id1, id2, id3}; - - TIdScope access{ctx}; - TArray ids = ExtractIdsWithout(access, source); - Expect(ids.Contains(id1)).ToBeFalse(); - Expect(ids.Contains(id2)).ToBeFalse(); - Expect(ids.Contains(id3)).ToBeTrue(); - Expect(source.Contains(id1)).ToBeTrue(); - Expect(source.Contains(id2)).ToBeTrue(); - Expect(source.Contains(id3)).ToBeFalse(); + TArray ids1 = FindAllIdsWith(ctx); + Expect(ids1.Contains(id1)).ToBeTrue(); + TArray ids2 = FindAllIdsWith(ctx); + Expect(ids2.Contains(id1)).ToBeTrue(); + Expect(ids2.Contains(id2)).ToBeTrue(); + Expect(ids2.Contains(id3)).ToBeTrue(); + Expect(ids2.Size()).ToEqual(3); + + TArray ids3 = FindAllIdsWith(ctx); + Expect(ids3.Contains(id1)).ToBeTrue(); + Expect(ids3.Contains(id2)).ToBeTrue(); }); }); - -It("Can filter directly from ECS", []() -{ - TArray ids1 = FindAllIdsWith(ctx); - Expect(ids1.Contains(id1)).ToBeTrue(); - - TArray ids2 = FindAllIdsWithAny(ctx); - Expect(ids2.Contains(id1)).ToBeTrue(); - - TArray ids3 = FindAllIdsWithAny(ctx); - ExcludeIdsWith(ctx, ids3); - Expect(ids3.Contains(id1)).ToBeTrue(); - - TArray ids4 = FindAllIdsWithAny(ctx); - ExcludeIdsWithout(ctx, ids4); - Expect(ids4.Contains(id1)).ToBeFalse(); -}); - -It("Can filter CRemoved", []() -{ - RmId(ctx, id1); - RmId(ctx, id2); - RmId(ctx, id3); - - TArray ids1 = FindAllIdsWith(ctx); - Expect(ids1.Contains(id1)).ToBeTrue(); - TArray ids2 = FindAllIdsWith(ctx); - Expect(ids2.Contains(id1)).ToBeTrue(); - Expect(ids2.Contains(id2)).ToBeTrue(); - Expect(ids2.Contains(id3)).ToBeTrue(); - Expect(ids2.Size()).ToEqual(3); - - TArray ids3 = FindAllIdsWith(ctx); - Expect(ids3.Contains(id1)).ToBeTrue(); - Expect(ids3.Contains(id2)).ToBeTrue(); -}); -}); diff --git a/Tests/ECS/Hierarchy.spec.cpp b/Tests/ECS/Hierarchy.spec.cpp index 032bd68b..a9674aaa 100644 --- a/Tests/ECS/Hierarchy.spec.cpp +++ b/Tests/ECS/Hierarchy.spec.cpp @@ -1,8 +1,8 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include +#include using namespace p; @@ -21,469 +21,466 @@ namespace Spec("ECS.Hierarchy", []() { -BeforeEach([]() -{ - ctx = {}; - root = AddId(ctx); - child1 = AddId(ctx); - child2 = AddId(ctx); - child3 = AddId(ctx); - grandchild = AddId(ctx); -}); - -Describe("AttachId", []() -{ - It("Creates bidirectional parent-child link for single child", []() + BeforeEach([]() { - AttachId({ctx}, root, child1); - - Expect(ctx.Has(root)).ToBeTrue(); - Expect(ctx.Has(child1)).ToBeTrue(); - Expect(ctx.Get(root).children.Size()).ToEqual(1); - Expect(ctx.Get(root).children[0]).ToEqual(child1); - Expect(ctx.Get(child1).parent).ToEqual(root); + ctx = {}; + root = AddId(ctx); + child1 = AddId(ctx); + child2 = AddId(ctx); + child3 = AddId(ctx); + grandchild = AddId(ctx); }); - It("Appends multiple children to same parent", []() + Describe("AttachId", []() { - AttachId({ctx}, root, {child1, child2, child3}); - - Expect(ctx.Has(root)).ToBeTrue(); - Expect(ctx.Has(child1)).ToBeTrue(); - Expect(ctx.Has(child2)).ToBeTrue(); - Expect(ctx.Has(child3)).ToBeTrue(); - Expect(ctx.Get(root).children.Size()).ToEqual(3); - Expect(ctx.Get(root).children[0]).ToEqual(child1); - Expect(ctx.Get(root).children[1]).ToEqual(child2); - Expect(ctx.Get(root).children[2]).ToEqual(child3); - Expect(ctx.Get(child1).parent).ToEqual(root); - Expect(ctx.Get(child2).parent).ToEqual(root); - Expect(ctx.Get(child3).parent).ToEqual(root); - }); -}); + It("Creates bidirectional parent-child link for single child", []() + { + AttachId({ctx}, root, child1); -Describe("AttachIdAfter", []() -{ - It("Inserts child after specified sibling preserving order", []() - { - AttachId({ctx}, root, {child1, child3}); - AttachIdAfter({ctx}, root, child2, child1); + Expect(ctx.Has(root)).ToBeTrue(); + Expect(ctx.Has(child1)).ToBeTrue(); + Expect(ctx.Get(root).children.Size()).ToEqual(1); + Expect(ctx.Get(root).children[0]).ToEqual(child1); + Expect(ctx.Get(child1).parent).ToEqual(root); + }); - Expect(ctx.Get(root).children.Size()).ToEqual(3); - Expect(ctx.Get(root).children.FindIndex(child2)).ToEqual(1); + It("Appends multiple children to same parent", []() + { + AttachId({ctx}, root, {child1, child2, child3}); + + Expect(ctx.Has(root)).ToBeTrue(); + Expect(ctx.Has(child1)).ToBeTrue(); + Expect(ctx.Has(child2)).ToBeTrue(); + Expect(ctx.Has(child3)).ToBeTrue(); + Expect(ctx.Get(root).children.Size()).ToEqual(3); + Expect(ctx.Get(root).children[0]).ToEqual(child1); + Expect(ctx.Get(root).children[1]).ToEqual(child2); + Expect(ctx.Get(root).children[2]).ToEqual(child3); + Expect(ctx.Get(child1).parent).ToEqual(root); + Expect(ctx.Get(child2).parent).ToEqual(root); + Expect(ctx.Get(child3).parent).ToEqual(root); + }); }); -}); -Describe("TransferIdChildren", []() -{ - It("Moves children from old parent to new parent", []() + Describe("AttachIdAfter", []() { - Id newRoot = AddId(ctx); - AttachId({ctx}, root, {child1, child2}); - TransferIdChildren({ctx}, {child1, child2}, newRoot); - - Expect(ctx.Get(root).children.IsEmpty()).ToBeTrue(); - Expect(ctx.Has(newRoot)).ToBeTrue(); - Expect(ctx.Get(newRoot).children.Size()).ToEqual(2); - Expect(ctx.Get(child1).parent).ToEqual(newRoot); - Expect(ctx.Get(child2).parent).ToEqual(newRoot); - }); -}); + It("Inserts child after specified sibling preserving order", []() + { + AttachId({ctx}, root, {child1, child3}); + AttachIdAfter({ctx}, root, child2, child1); -Describe("DetachIdParent", []() -{ - BeforeEach([]() - { - AttachId({ctx}, root, {child1, child2}); + Expect(ctx.Get(root).children.Size()).ToEqual(3); + Expect(ctx.Get(root).children.FindIndex(child2)).ToEqual(1); + }); }); - It("Retains CChild component when keepComponents is true", []() + Describe("TransferIdChildren", []() { - DetachIdParent({ctx}, child1, true); - - Expect(ctx.Has(child1)).ToBeTrue(); - Expect(ctx.Get(child1).parent).ToEqual(NoId); - Expect(ctx.Get(root).children.Size()).ToEqual(1); + It("Moves children from old parent to new parent", []() + { + Id newRoot = AddId(ctx); + AttachId({ctx}, root, {child1, child2}); + TransferIdChildren({ctx}, {child1, child2}, newRoot); + + Expect(ctx.Get(root).children.IsEmpty()).ToBeTrue(); + Expect(ctx.Has(newRoot)).ToBeTrue(); + Expect(ctx.Get(newRoot).children.Size()).ToEqual(2); + Expect(ctx.Get(child1).parent).ToEqual(newRoot); + Expect(ctx.Get(child2).parent).ToEqual(newRoot); + }); }); - It("Removes CChild from detached child and removes from parent list", []() + Describe("DetachIdParent", []() { - DetachIdParent({ctx}, child1, false); + BeforeEach([]() + { + AttachId({ctx}, root, {child1, child2}); + }); - Expect(ctx.Has(child1)).ToBeFalse(); - Expect(ctx.Get(root).children.Contains(child1)).ToBeFalse(); - }); + It("Retains CChild component when keepComponents is true", []() + { + DetachIdParent({ctx}, child1, true); - It("Removes empty CParent when all children are detached", []() - { - DetachIdParent({ctx}, {child1, child2}, false); + Expect(ctx.Has(child1)).ToBeTrue(); + Expect(ctx.Get(child1).parent).ToEqual(NoId); + Expect(ctx.Get(root).children.Size()).ToEqual(1); + }); - Expect(ctx.Has(child1)).ToBeFalse(); - Expect(ctx.Has(child2)).ToBeFalse(); - Expect(ctx.Has(root)).ToBeFalse(); - }); -}); + It("Removes CChild from detached child and removes from parent list", []() + { + DetachIdParent({ctx}, child1, false); -Describe("DetachIdChildren", []() -{ - BeforeEach([]() - { - AttachId({ctx}, root, {child1, child2}); - }); + Expect(ctx.Has(child1)).ToBeFalse(); + Expect(ctx.Get(root).children.Contains(child1)).ToBeFalse(); + }); - It("Severes all children but retains CChild when keepComponents is true", []() - { - DetachIdChildren({ctx}, root, true); + It("Removes empty CParent when all children are detached", []() + { + DetachIdParent({ctx}, {child1, child2}, false); - Expect(ctx.Has(child1)).ToBeTrue(); - Expect(ctx.Has(child2)).ToBeTrue(); - Expect(ctx.Get(child1).parent).ToEqual(NoId); - Expect(ctx.Get(child2).parent).ToEqual(NoId); - Expect(ctx.Get(root).children.IsEmpty()).ToBeTrue(); + Expect(ctx.Has(child1)).ToBeFalse(); + Expect(ctx.Has(child2)).ToBeFalse(); + Expect(ctx.Has(root)).ToBeFalse(); + }); }); - It("Removes CChild and CParent when keepComponents is false", []() + Describe("DetachIdChildren", []() { - DetachIdChildren({ctx}, root, false); + BeforeEach([]() + { + AttachId({ctx}, root, {child1, child2}); + }); - Expect(ctx.Has(child1)).ToBeFalse(); - Expect(ctx.Has(child2)).ToBeFalse(); - Expect(ctx.Has(root)).ToBeFalse(); - }); -}); + It("Severes all children but retains CChild when keepComponents is true", []() + { + DetachIdChildren({ctx}, root, true); -Describe("GetIdChildren", []() -{ - BeforeEach([]() - { - AttachId({ctx}, root, {child1, child2}); - AttachId({ctx}, child1, grandchild); - }); + Expect(ctx.Has(child1)).ToBeTrue(); + Expect(ctx.Has(child2)).ToBeTrue(); + Expect(ctx.Get(child1).parent).ToEqual(NoId); + Expect(ctx.Get(child2).parent).ToEqual(NoId); + Expect(ctx.Get(root).children.IsEmpty()).ToBeTrue(); + }); - It("Returns child list for parent entities", []() - { - const auto* children = GetIdChildren({ctx}, root); - Expect(children).ToNotEqual(nullptr); - Expect(children->Size()).ToEqual(2); - Expect(children->Contains(child1)).ToBeTrue(); - Expect(children->Contains(child2)).ToBeTrue(); - }); + It("Removes CChild and CParent when keepComponents is false", []() + { + DetachIdChildren({ctx}, root, false); - It("Combines children from multiple parents into one list", []() - { - TArray outChildren; - GetIdChildren({ctx}, {root, child1}, outChildren); - Expect(outChildren.Size()).ToEqual(3); - Expect(outChildren.Contains(grandchild)).ToBeTrue(); + Expect(ctx.Has(child1)).ToBeFalse(); + Expect(ctx.Has(child2)).ToBeFalse(); + Expect(ctx.Has(root)).ToBeFalse(); + }); }); - It("Returns null for entities without CParent component", []() + Describe("GetIdChildren", []() { - Expect(GetIdChildren({ctx}, child2)).ToEqual(nullptr); - }); -}); + BeforeEach([]() + { + AttachId({ctx}, root, {child1, child2}); + AttachId({ctx}, child1, grandchild); + }); -Describe("GetAllIdChildren", []() -{ - BeforeEach([]() - { - AttachId({ctx}, root, child1); - AttachId({ctx}, child1, grandchild); - }); + It("Returns child list for parent entities", []() + { + const auto* children = GetIdChildren({ctx}, root); + Expect(children).ToNotEqual(nullptr); + Expect(children->Size()).ToEqual(2); + Expect(children->Contains(child1)).ToBeTrue(); + Expect(children->Contains(child2)).ToBeTrue(); + }); - It("Recurses full tree depth to collect all descendents", []() - { - TArray outChildren; - GetAllIdChildren({ctx}, root, outChildren, 10); - Expect(outChildren.Size()).ToEqual(2); - Expect(outChildren.Contains(grandchild)).ToBeTrue(); - }); + It("Combines children from multiple parents into one list", []() + { + TArray outChildren; + GetIdChildren({ctx}, {root, child1}, outChildren); + Expect(outChildren.Size()).ToEqual(3); + Expect(outChildren.Contains(grandchild)).ToBeTrue(); + }); - It("Respects depth limit to return only immediate children", []() - { - TArray outChildren; - GetAllIdChildren({ctx}, root, outChildren, 1); - Expect(outChildren.Size()).ToEqual(1); - Expect(outChildren.Contains(grandchild)).ToBeFalse(); + It("Returns null for entities without CParent component", []() + { + Expect(GetIdChildren({ctx}, child2)).ToEqual(nullptr); + }); }); -}); -Describe("GetIdParent", []() -{ - BeforeEach([]() + Describe("GetAllIdChildren", []() { - AttachId({ctx}, root, child1); - AttachId({ctx}, child1, grandchild); - }); + BeforeEach([]() + { + AttachId({ctx}, root, child1); + AttachId({ctx}, child1, grandchild); + }); - It("Returns parent Id for child entities", []() - { - Expect(GetIdParent({ctx}, child1)).ToEqual(root); - Expect(GetIdParent({ctx}, grandchild)).ToEqual(child1); - }); + It("Recurses full tree depth to collect all descendents", []() + { + TArray outChildren; + GetAllIdChildren({ctx}, root, outChildren, 10); + Expect(outChildren.Size()).ToEqual(2); + Expect(outChildren.Contains(grandchild)).ToBeTrue(); + }); - It("Returns unique parents for multiple children", []() - { - TArray outParents; - GetIdParent({ctx}, {child1, grandchild}, outParents); - Expect(outParents.Size()).ToEqual(2); - Expect(outParents.Contains(root)).ToBeTrue(); - Expect(outParents.Contains(child1)).ToBeTrue(); + It("Respects depth limit to return only immediate children", []() + { + TArray outChildren; + GetAllIdChildren({ctx}, root, outChildren, 1); + Expect(outChildren.Size()).ToEqual(1); + Expect(outChildren.Contains(grandchild)).ToBeFalse(); + }); }); - It("Returns NoId for root entities without parent", []() + Describe("GetIdParent", []() { - Expect(GetIdParent({ctx}, root)).ToEqual(NoId); - }); + BeforeEach([]() + { + AttachId({ctx}, root, child1); + AttachId({ctx}, child1, grandchild); + }); - It("Returns NoId for entities without CChild component", []() - { - Expect(GetIdParent({ctx}, child2)).ToEqual(NoId); - }); -}); + It("Returns parent Id for child entities", []() + { + Expect(GetIdParent({ctx}, child1)).ToEqual(root); + Expect(GetIdParent({ctx}, grandchild)).ToEqual(child1); + }); -Describe("GetAllIdParents", []() -{ - BeforeEach([]() - { - AttachId({ctx}, root, child1); - AttachId({ctx}, child1, grandchild); - }); + It("Returns unique parents for multiple children", []() + { + TArray outParents; + GetIdParent({ctx}, {child1, grandchild}, outParents); + Expect(outParents.Size()).ToEqual(2); + Expect(outParents.Contains(root)).ToBeTrue(); + Expect(outParents.Contains(child1)).ToBeTrue(); + }); - It("Traverses full ancestry chain from leaf to root", []() - { - TArray outParents; - GetAllIdParents({ctx}, grandchild, outParents); - Expect(outParents.Size()).ToEqual(2); - Expect(outParents[0]).ToEqual(child1); - Expect(outParents[1]).ToEqual(root); - }); + It("Returns NoId for root entities without parent", []() + { + Expect(GetIdParent({ctx}, root)).ToEqual(NoId); + }); - It("Returns empty when entity has no CChild component", []() - { - TArray outParents; - GetAllIdParents({ctx}, child2, outParents); - Expect(outParents.IsEmpty()).ToBeTrue(); + It("Returns NoId for entities without CChild component", []() + { + Expect(GetIdParent({ctx}, child2)).ToEqual(NoId); + }); }); -}); -Describe("FindIdParent", []() -{ - BeforeEach([]() + Describe("GetAllIdParents", []() { - AttachId({ctx}, root, child1); - AttachId({ctx}, child1, grandchild); - }); + BeforeEach([]() + { + AttachId({ctx}, root, child1); + AttachId({ctx}, child1, grandchild); + }); - It("Finds ancestor two levels up matching predicate", []() - { - Expect(FindIdParent({ctx}, grandchild, - [&](Id id) + It("Traverses full ancestry chain from leaf to root", []() { - return id == root; - })).ToEqual(root); - }); + TArray outParents; + GetAllIdParents({ctx}, grandchild, outParents); + Expect(outParents.Size()).ToEqual(2); + Expect(outParents[0]).ToEqual(child1); + Expect(outParents[1]).ToEqual(root); + }); - It("Finds immediate parent matching predicate", []() - { - Expect(FindIdParent({ctx}, grandchild, - [&](Id id) + It("Returns empty when entity has no CChild component", []() { - return id == child1; - })).ToEqual(child1); + TArray outParents; + GetAllIdParents({ctx}, child2, outParents); + Expect(outParents.IsEmpty()).ToBeTrue(); + }); }); - It("Returns NoId when no ancestor matches predicate", []() + Describe("FindIdParent", []() { - Expect(IsNone(FindIdParent({ctx}, grandchild, - [](Id) + BeforeEach([]() { - return false; - }))).ToBeTrue(); - }); -}); + AttachId({ctx}, root, child1); + AttachId({ctx}, child1, grandchild); + }); -Describe("FindIdParents", []() -{ - It("Finds nearest matching ancestor for deep entity", []() - { - Id intermediate = AddId(ctx); - AttachId({ctx}, root, child1); - AttachId({ctx}, child1, intermediate); - AttachId({ctx}, intermediate, grandchild); + It("Finds ancestor two levels up matching predicate", []() + { + Expect(FindIdParent({ctx}, grandchild, [&](Id id) + { + return id == root; + })).ToEqual(root); + }); - TArray outParents; - FindIdParents({ctx}, grandchild, outParents, [&](Id id) + It("Finds immediate parent matching predicate", []() { - return id == intermediate; + Expect(FindIdParent({ctx}, grandchild, [&](Id id) + { + return id == child1; + })).ToEqual(child1); }); - Expect(outParents.Size()).ToEqual(1); - Expect(outParents.Contains(intermediate)).ToBeTrue(); - }); - It("Returns empty when no ancestor matches predicate", []() - { - TArray outParents; - FindIdParents({ctx}, child1, outParents, [](Id) + It("Returns NoId when no ancestor matches predicate", []() { - return false; + Expect(IsNone(FindIdParent({ctx}, grandchild, [](Id) + { + return false; + }))).ToBeTrue(); }); - Expect(outParents.IsEmpty()).ToBeTrue(); }); -}); -Describe("GetIdRoots", []() -{ - It("Returns empty when no hierarchy exists", []() + Describe("FindIdParents", []() { - TArray roots; - GetIdRoots({ctx}, roots); - Expect(roots.IsEmpty()).ToBeTrue(); + It("Finds nearest matching ancestor for deep entity", []() + { + Id intermediate = AddId(ctx); + AttachId({ctx}, root, child1); + AttachId({ctx}, child1, intermediate); + AttachId({ctx}, intermediate, grandchild); + + TArray outParents; + FindIdParents({ctx}, grandchild, outParents, [&](Id id) + { + return id == intermediate; + }); + Expect(outParents.Size()).ToEqual(1); + Expect(outParents.Contains(intermediate)).ToBeTrue(); + }); + + It("Returns empty when no ancestor matches predicate", []() + { + TArray outParents; + FindIdParents({ctx}, child1, outParents, [](Id) + { + return false; + }); + Expect(outParents.IsEmpty()).ToBeTrue(); + }); }); - It("Finds root of single-parent hierarchy", []() + Describe("GetIdRoots", []() { - AttachId({ctx}, root, {child1, child2}); + It("Returns empty when no hierarchy exists", []() + { + TArray roots; + GetIdRoots({ctx}, roots); + Expect(roots.IsEmpty()).ToBeTrue(); + }); - TArray roots; - GetIdRoots({ctx}, roots); - Expect(roots.Size()).ToEqual(1); - Expect(roots.Contains(root)).ToBeTrue(); - }); + It("Finds root of single-parent hierarchy", []() + { + AttachId({ctx}, root, {child1, child2}); - It("Returns multiple roots from independent trees", []() - { - Id root2 = AddId(ctx); - AttachId({ctx}, root, {child1, child2}); - AttachId({ctx}, root2, child3); - - TArray roots; - GetIdRoots({ctx}, roots); - Expect(roots.Size()).ToEqual(2); - Expect(roots.Contains(root)).ToBeTrue(); - Expect(roots.Contains(root2)).ToBeTrue(); - }); + TArray roots; + GetIdRoots({ctx}, roots); + Expect(roots.Size()).ToEqual(1); + Expect(roots.Contains(root)).ToBeTrue(); + }); - It("Excludes entities that are both parent and child of someone", []() - { - AttachId({ctx}, root, child1); - AttachId({ctx}, child1, grandchild); - - TArray roots; - GetIdRoots({ctx}, roots); - Expect(roots.Size()).ToEqual(1); - Expect(roots.Contains(root)).ToBeTrue(); - Expect(roots.Contains(child1)).ToBeFalse(); - }); -}); + It("Returns multiple roots from independent trees", []() + { + Id root2 = AddId(ctx); + AttachId({ctx}, root, {child1, child2}); + AttachId({ctx}, root2, child3); + + TArray roots; + GetIdRoots({ctx}, roots); + Expect(roots.Size()).ToEqual(2); + Expect(roots.Contains(root)).ToBeTrue(); + Expect(roots.Contains(root2)).ToBeTrue(); + }); -Describe("GetIdParentRoots", []() -{ - BeforeEach([]() - { - AttachId({ctx}, root, child1); - AttachId({ctx}, child1, grandchild); + It("Excludes entities that are both parent and child of someone", []() + { + AttachId({ctx}, root, child1); + AttachId({ctx}, child1, grandchild); + + TArray roots; + GetIdRoots({ctx}, roots); + Expect(roots.Size()).ToEqual(1); + Expect(roots.Contains(root)).ToBeTrue(); + Expect(roots.Contains(child1)).ToBeFalse(); + }); }); - It("Walks child chain up to root ancestor", []() + Describe("GetIdParentRoots", []() { - TArray roots; - GetIdParentRoots({ctx}, grandchild, roots, false); - Expect(roots.Size()).ToEqual(1); - Expect(roots.Contains(root)).ToBeTrue(); - }); + BeforeEach([]() + { + AttachId({ctx}, root, child1); + AttachId({ctx}, child1, grandchild); + }); - It("Handles children from different trees", []() - { - Id root2 = AddId(ctx); - Id childOf2 = AddId(ctx); - AttachId({ctx}, root2, childOf2); - - TArray roots; - GetIdParentRoots({ctx}, {grandchild, childOf2}, roots, false); - Expect(roots.Size()).ToEqual(2); - Expect(roots.Contains(root)).ToBeTrue(); - Expect(roots.Contains(root2)).ToBeTrue(); - }); + It("Walks child chain up to root ancestor", []() + { + TArray roots; + GetIdParentRoots({ctx}, grandchild, roots, false); + Expect(roots.Size()).ToEqual(1); + Expect(roots.Contains(root)).ToBeTrue(); + }); - It("Considers input entities as roots when considerChildren flag is set", []() - { - TArray roots; - GetIdParentRoots({ctx}, {root, grandchild}, roots, true); - Expect(roots.Size()).ToEqual(1); - Expect(roots.Contains(root)).ToBeTrue(); - }); + It("Handles children from different trees", []() + { + Id root2 = AddId(ctx); + Id childOf2 = AddId(ctx); + AttachId({ctx}, root2, childOf2); + + TArray roots; + GetIdParentRoots({ctx}, {grandchild, childOf2}, roots, false); + Expect(roots.Size()).ToEqual(2); + Expect(roots.Contains(root)).ToBeTrue(); + Expect(roots.Contains(root2)).ToBeTrue(); + }); - It("Returns empty for empty input", []() - { - TArray roots; - GetIdParentRoots({ctx}, {}, roots, false); - Expect(roots.IsEmpty()).ToBeTrue(); - }); + It("Considers input entities as roots when considerChildren flag is set", []() + { + TArray roots; + GetIdParentRoots({ctx}, {root, grandchild}, roots, true); + Expect(roots.Size()).ToEqual(1); + Expect(roots.Contains(root)).ToBeTrue(); + }); - It("Returns empty for entities with no parent", []() - { - TArray roots; - GetIdParentRoots({ctx}, child2, roots, false); - Expect(roots.IsEmpty()).ToBeTrue(); - }); -}); + It("Returns empty for empty input", []() + { + TArray roots; + GetIdParentRoots({ctx}, {}, roots, false); + Expect(roots.IsEmpty()).ToBeTrue(); + }); -Describe("FixParentIdLinks", []() -{ - BeforeEach([]() - { - AttachId({ctx}, root, child1); + It("Returns empty for entities with no parent", []() + { + TArray roots; + GetIdParentRoots({ctx}, child2, roots, false); + Expect(roots.IsEmpty()).ToBeTrue(); + }); }); - It("Returns false when parent-child links are already correct", []() + Describe("FixParentIdLinks", []() { - Expect(FixParentIdLinks({ctx}, root)).ToBeFalse(); - }); + BeforeEach([]() + { + AttachId({ctx}, root, child1); + }); - It("Fixes child->parent reference when it does not match parent's list", []() - { - ctx.Get(child1).parent = NoId; + It("Returns false when parent-child links are already correct", []() + { + Expect(FixParentIdLinks({ctx}, root)).ToBeFalse(); + }); - Expect(FixParentIdLinks({ctx}, root)).ToBeTrue(); - Expect(ctx.Get(child1).parent).ToEqual(root); - }); + It("Fixes child->parent reference when it does not match parent's list", []() + { + ctx.Get(child1).parent = NoId; - It("Adds missing CChild component to orphan children", []() - { - ctx.Remove(child1); - Expect(ctx.Has(child1)).ToBeFalse(); + Expect(FixParentIdLinks({ctx}, root)).ToBeTrue(); + Expect(ctx.Get(child1).parent).ToEqual(root); + }); - Expect(FixParentIdLinks({ctx}, root)).ToBeTrue(); - Expect(ctx.Has(child1)).ToBeTrue(); - Expect(ctx.Get(child1).parent).ToEqual(root); - }); -}); + It("Adds missing CChild component to orphan children", []() + { + ctx.Remove(child1); + Expect(ctx.Has(child1)).ToBeFalse(); -Describe("ValidateParentIdLinks", []() -{ - BeforeEach([]() - { - AttachId({ctx}, root, child1); + Expect(FixParentIdLinks({ctx}, root)).ToBeTrue(); + Expect(ctx.Has(child1)).ToBeTrue(); + Expect(ctx.Get(child1).parent).ToEqual(root); + }); }); - It("Returns true when all parent-child links are consistent", []() + Describe("ValidateParentIdLinks", []() { - Expect(ValidateParentIdLinks({ctx}, root)).ToBeTrue(); - }); + BeforeEach([]() + { + AttachId({ctx}, root, child1); + }); - It("Returns false when child->parent reference is mismatched", []() - { - ctx.Get(child1).parent = NoId; + It("Returns true when all parent-child links are consistent", []() + { + Expect(ValidateParentIdLinks({ctx}, root)).ToBeTrue(); + }); - Expect(ValidateParentIdLinks({ctx}, root)).ToBeFalse(); - }); + It("Returns false when child->parent reference is mismatched", []() + { + ctx.Get(child1).parent = NoId; - It("Returns false when CChild component is missing from child", []() - { - ctx.Remove(child1); + Expect(ValidateParentIdLinks({ctx}, root)).ToBeFalse(); + }); + + It("Returns false when CChild component is missing from child", []() + { + ctx.Remove(child1); - Expect(ValidateParentIdLinks({ctx}, root)).ToBeFalse(); + Expect(ValidateParentIdLinks({ctx}, root)).ToBeFalse(); + }); }); }); -}); diff --git a/Tests/ECS/IdRegistry.spec.cpp b/Tests/ECS/IdRegistry.spec.cpp index 47ba1504..fceebb2e 100644 --- a/Tests/ECS/IdRegistry.spec.cpp +++ b/Tests/ECS/IdRegistry.spec.cpp @@ -1,7 +1,7 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include +#include using namespace p; @@ -10,150 +10,150 @@ using namespace std::chrono_literals; Spec("ECS.IdRegistry", []() { -It("Can create one id", []() -{ - IdRegistry ids; - Expect(ids.Size()).ToEqual(0); - Id id = ids.Create(); - Expect(id).ToNotEqual(NoId); - Expect(ids.IsValid(id)).ToBeTrue(); - Expect(ids.Size()).ToEqual(1); -}); - -It("Can remove one id", []() -{ - IdRegistry ids; - Id id = ids.Create(); - Expect(ids.Size()).ToEqual(1); - Expect(ids.RemoveInstant(id)).ToBeTrue(); - Expect(ids.IsValid(id)).ToBeFalse(); - Expect(ids.Size()).ToEqual(0); -}); - -It("Can create two and remove first", []() -{ - IdRegistry ids; - Id id1 = ids.Create(); - ids.Create(); - Expect(ids.RemoveInstant(id1)).ToBeTrue(); - Expect(ids.IsValid(id1)).ToBeFalse(); - Expect(ids.Size()).ToEqual(1); -}); - -It("Can create two and remove last", []() -{ - IdRegistry ids; - ids.Create(); - Id id2 = ids.Create(); - Expect(ids.RemoveInstant(id2)).ToBeTrue(); - Expect(ids.IsValid(id2)).ToBeFalse(); - Expect(ids.Size()).ToEqual(1); -}); - -It("Can remove one id (deferred)", []() -{ - IdRegistry ids; - Id id = ids.Create(); - Expect(ids.Size()).ToEqual(1); - Expect(ids.Remove(id)).ToBeTrue(); - Expect(ids.IsValid(id)).ToBeFalse(); - Expect(ids.Size()).ToEqual(0); -}); - -It("Can create two and remove first (deferred)", []() -{ - IdRegistry ids; - Id id1 = ids.Create(); - ids.Create(); - Expect(ids.Remove(id1)).ToBeTrue(); - Expect(ids.IsValid(id1)).ToBeFalse(); - Expect(ids.Size()).ToEqual(1); -}); - -It("Can create two and remove last (deferred)", []() -{ - IdRegistry ids; - ids.Create(); - Id id2 = ids.Create(); - Expect(ids.Remove(id2)).ToBeTrue(); - Expect(ids.IsValid(id2)).ToBeFalse(); - Expect(ids.Size()).ToEqual(1); -}); - -It("Removed id index gets reused", []() -{ - IdRegistry ids; - ids.Create(); - Id id = ids.Create(); - ids.Create(); - Expect(ids.RemoveInstant(id)).ToBeTrue(); - Id id2 = ids.Create(); - Expect(id2.GetIndex()).ToEqual(id.GetIndex()); - Id id3 = ids.Create(); - Expect(id3.GetIndex()).ToNotEqual(id.GetIndex()); -}); - -It("Deferred removed id index doesn't get reused until flushed", []() -{ - IdRegistry ids; - ids.Create(); - Id id = ids.Create(); - ids.Create(); - Expect(ids.Remove(id)).ToBeTrue(); - Id id2 = ids.Create(); - Expect(id2.GetIndex()).ToNotEqual(id.GetIndex()); - ids.FlushDeferredRemovals(); - Id id3 = ids.Create(); - Expect(id3.GetIndex()).ToEqual(id.GetIndex()); - Id id4 = ids.Create(); - Expect(id4.GetIndex()).ToNotEqual(id.GetIndex()); -}); - -It("Can create many ids", []() -{ - IdRegistry ids; - Expect(ids.Size()).ToEqual(0); - - TArray list(3); - ids.Create(list); - - Expect(ids.Size()).ToEqual(3); - for (i32 i = 0; i < list.Size(); ++i) + It("Can create one id", []() { - Expect(list[i].GetIndex()).ToEqual(i); - Expect(ids.IsValid(list[i])).ToBeTrue(); - } -}); + IdRegistry ids; + Expect(ids.Size()).ToEqual(0); + Id id = ids.Create(); + Expect(id).ToNotEqual(NoId); + Expect(ids.IsValid(id)).ToBeTrue(); + Expect(ids.Size()).ToEqual(1); + }); + + It("Can remove one id", []() + { + IdRegistry ids; + Id id = ids.Create(); + Expect(ids.Size()).ToEqual(1); + Expect(ids.RemoveInstant(id)).ToBeTrue(); + Expect(ids.IsValid(id)).ToBeFalse(); + Expect(ids.Size()).ToEqual(0); + }); + + It("Can create two and remove first", []() + { + IdRegistry ids; + Id id1 = ids.Create(); + ids.Create(); + Expect(ids.RemoveInstant(id1)).ToBeTrue(); + Expect(ids.IsValid(id1)).ToBeFalse(); + Expect(ids.Size()).ToEqual(1); + }); + + It("Can create two and remove last", []() + { + IdRegistry ids; + ids.Create(); + Id id2 = ids.Create(); + Expect(ids.RemoveInstant(id2)).ToBeTrue(); + Expect(ids.IsValid(id2)).ToBeFalse(); + Expect(ids.Size()).ToEqual(1); + }); + + It("Can remove one id (deferred)", []() + { + IdRegistry ids; + Id id = ids.Create(); + Expect(ids.Size()).ToEqual(1); + Expect(ids.Remove(id)).ToBeTrue(); + Expect(ids.IsValid(id)).ToBeFalse(); + Expect(ids.Size()).ToEqual(0); + }); + + It("Can create two and remove first (deferred)", []() + { + IdRegistry ids; + Id id1 = ids.Create(); + ids.Create(); + Expect(ids.Remove(id1)).ToBeTrue(); + Expect(ids.IsValid(id1)).ToBeFalse(); + Expect(ids.Size()).ToEqual(1); + }); + + It("Can create two and remove last (deferred)", []() + { + IdRegistry ids; + ids.Create(); + Id id2 = ids.Create(); + Expect(ids.Remove(id2)).ToBeTrue(); + Expect(ids.IsValid(id2)).ToBeFalse(); + Expect(ids.Size()).ToEqual(1); + }); + + It("Removed id index gets reused", []() + { + IdRegistry ids; + ids.Create(); + Id id = ids.Create(); + ids.Create(); + Expect(ids.RemoveInstant(id)).ToBeTrue(); + Id id2 = ids.Create(); + Expect(id2.GetIndex()).ToEqual(id.GetIndex()); + Id id3 = ids.Create(); + Expect(id3.GetIndex()).ToNotEqual(id.GetIndex()); + }); + + It("Deferred removed id index doesn't get reused until flushed", []() + { + IdRegistry ids; + ids.Create(); + Id id = ids.Create(); + ids.Create(); + Expect(ids.Remove(id)).ToBeTrue(); + Id id2 = ids.Create(); + Expect(id2.GetIndex()).ToNotEqual(id.GetIndex()); + ids.FlushDeferredRemovals(); + Id id3 = ids.Create(); + Expect(id3.GetIndex()).ToEqual(id.GetIndex()); + Id id4 = ids.Create(); + Expect(id4.GetIndex()).ToNotEqual(id.GetIndex()); + }); + + It("Can create many ids", []() + { + IdRegistry ids; + Expect(ids.Size()).ToEqual(0); -It("Can remove many ids", []() -{ - IdRegistry ids; - TArray list(3); - ids.Create(list); - Expect(ids.Size()).ToEqual(3); + TArray list(3); + ids.Create(list); - Expect(ids.RemoveInstant(list)).ToBeTrue(); - Expect(ids.Size()).ToEqual(0); + Expect(ids.Size()).ToEqual(3); + for (i32 i = 0; i < list.Size(); ++i) + { + Expect(list[i].GetIndex()).ToEqual(i); + Expect(ids.IsValid(list[i])).ToBeTrue(); + } + }); - for (i32 i = 0; i < list.Size(); ++i) + It("Can remove many ids", []() { - Expect(ids.IsValid(list[i])).ToBeFalse(); - } -}); + IdRegistry ids; + TArray list(3); + ids.Create(list); + Expect(ids.Size()).ToEqual(3); -It("Can remove many ids (deferred)", []() -{ - IdRegistry ids; - TArray list(3); - ids.Create(list); - Expect(ids.Size()).ToEqual(3); + Expect(ids.RemoveInstant(list)).ToBeTrue(); + Expect(ids.Size()).ToEqual(0); - Expect(ids.Remove(list)).ToBeTrue(); - Expect(ids.Size()).ToEqual(0); + for (i32 i = 0; i < list.Size(); ++i) + { + Expect(ids.IsValid(list[i])).ToBeFalse(); + } + }); - for (i32 i = 0; i < list.Size(); ++i) + It("Can remove many ids (deferred)", []() { - Expect(ids.IsValid(list[i])).ToBeFalse(); - } -}); + IdRegistry ids; + TArray list(3); + ids.Create(list); + Expect(ids.Size()).ToEqual(3); + + Expect(ids.Remove(list)).ToBeTrue(); + Expect(ids.Size()).ToEqual(0); + + for (i32 i = 0; i < list.Size(); ++i) + { + Expect(ids.IsValid(list[i])).ToBeFalse(); + } + }); }); diff --git a/Tests/ECS/IdScopes.spec.cpp b/Tests/ECS/IdScopes.spec.cpp index b332090a..784de555 100644 --- a/Tests/ECS/IdScopes.spec.cpp +++ b/Tests/ECS/IdScopes.spec.cpp @@ -1,7 +1,7 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include +#include using namespace p; @@ -23,116 +23,116 @@ struct ScopeTypeC Spec("ECS.IdScopes", []() { -Describe("Templated", []() -{ - It("Can cache pools", []() - { - IdContext ctx; - TIdScope> scope{ctx}; - - Expect(scope.GetPool()).ToEqual(ctx.GetPool()); - Expect(scope.GetPool()).ToEqual(ctx.GetPool()); - Expect(scope.GetPool()).ToEqual(ctx.GetPool()); - }); - - It("Can check if contained", []() - { - IdContext ctx; - TPool& pool = ctx.AssurePool(); - TIdScope> scope{ctx}; - TIdScope scopeConst{ctx}; - Id id = NoId; - Expect(scope.Has(id)).ToBeFalse(); - Expect(scopeConst.Has(id)).ToBeFalse(); - - id = AddId(ctx); - Expect(scope.Has(id)).ToBeFalse(); - Expect(scopeConst.Has(id)).ToBeFalse(); - - ctx.Add(id); - Expect(scope.Has(id)).ToBeTrue(); - Expect(scopeConst.Has(id)).ToBeTrue(); - - TIdScope scope2{ctx}; - ctx.Add(id); - Expect(scope2.Has(id)).ToBeTrue(); - }); - - It("Can initialize superset", []() + Describe("Templated", []() { - IdContext ctx; - TPool& typePool = ctx.AssurePool(); - - TIdScope> scope1{ctx}; - TIdScope> superset1{scope1}; - Expect(superset1.GetPool()).ToEqual(&typePool); - - TIdScope> scope2{ctx}; - TIdScope superset2{scope2}; - Expect(superset2.GetPool()).ToEqual(&typePool); - - TIdScope> scope3{ctx}; - TIdScope superset3{scope3}; - Expect(superset1.GetPool()).ToEqual(&typePool); - }); - - It("Can mark modify", []() - { - IdContext ctx; - Id id = AddId(ctx); - TIdScope>> scope1{ctx}; - Expect(scope1.Has>(id)).ToBeFalse(); - scope1.Modify(id); - Expect(scope1.Has>(id)).ToBeTrue(); - Expect(scope1.IsModified(id)).ToBeTrue(); - - scope1.Remove>(id); - Expect(scope1.Has>(id)).ToBeFalse(); - Expect(scope1.IsModified(id)).ToBeFalse(); - - scope1.Modify(id); - Expect(scope1.Has>(id)).ToBeTrue(); - Expect(scope1.IsModified(id)).ToBeTrue(); + It("Can cache pools", []() + { + IdContext ctx; + TIdScope> scope{ctx}; + + Expect(scope.GetPool()).ToEqual(ctx.GetPool()); + Expect(scope.GetPool()).ToEqual(ctx.GetPool()); + Expect(scope.GetPool()).ToEqual(ctx.GetPool()); + }); + + It("Can check if contained", []() + { + IdContext ctx; + TPool& pool = ctx.AssurePool(); + TIdScope> scope{ctx}; + TIdScope scopeConst{ctx}; + Id id = NoId; + Expect(scope.Has(id)).ToBeFalse(); + Expect(scopeConst.Has(id)).ToBeFalse(); + + id = AddId(ctx); + Expect(scope.Has(id)).ToBeFalse(); + Expect(scopeConst.Has(id)).ToBeFalse(); + + ctx.Add(id); + Expect(scope.Has(id)).ToBeTrue(); + Expect(scopeConst.Has(id)).ToBeTrue(); + + TIdScope scope2{ctx}; + ctx.Add(id); + Expect(scope2.Has(id)).ToBeTrue(); + }); + + It("Can initialize superset", []() + { + IdContext ctx; + TPool& typePool = ctx.AssurePool(); + + TIdScope> scope1{ctx}; + TIdScope> superset1{scope1}; + Expect(superset1.GetPool()).ToEqual(&typePool); + + TIdScope> scope2{ctx}; + TIdScope superset2{scope2}; + Expect(superset2.GetPool()).ToEqual(&typePool); + + TIdScope> scope3{ctx}; + TIdScope superset3{scope3}; + Expect(superset1.GetPool()).ToEqual(&typePool); + }); + + It("Can mark modify", []() + { + IdContext ctx; + Id id = AddId(ctx); + TIdScope>> scope1{ctx}; + Expect(scope1.Has>(id)).ToBeFalse(); + scope1.Modify(id); + Expect(scope1.Has>(id)).ToBeTrue(); + Expect(scope1.IsModified(id)).ToBeTrue(); + + scope1.Remove>(id); + Expect(scope1.Has>(id)).ToBeFalse(); + Expect(scope1.IsModified(id)).ToBeFalse(); + + scope1.Modify(id); + Expect(scope1.Has>(id)).ToBeTrue(); + Expect(scope1.IsModified(id)).ToBeTrue(); + }); + + It("Can mark modify automatically", []() + { + IdContext ctx; + Id id = AddId(ctx); + using MyScope = TIdScope, CMdfd>; + MyScope scope{ctx}; + Expect(MyScope::WDependencies::Contains>()).ToBeTrue(); + Expect(MyScope::WDependencies::Contains>()).ToBeFalse(); + Expect(MyScope::RWDependencies::Contains>()).ToBeTrue(); + Expect(MyScope::RWDependencies::Contains>()).ToBeTrue(); + + scope.Add(id); // Type B should be auto modified + Expect(scope.IsModified(id)).ToBeTrue(); + scope.Add(id); // Type B should not be auto modified + Expect(scope.IsModified(id)).ToBeFalse(); + + scope.ClearPool>(); + Expect(scope.IsModified(id)).ToBeFalse(); + + scope.Has(id); // Has should never mark modify + Expect(scope.IsModified(id)).ToBeFalse(); + + scope.Get(id); + Expect(scope.IsModified(id)).ToBeFalse(); + scope.Get(id); + Expect(scope.IsModified(id)).ToBeTrue(); + scope.Add(id); // Type B should not be auto modified + Expect(scope.IsModified(id)).ToBeFalse(); + + scope.ClearPool>(); + + scope.Remove(id); + Expect(scope.Has(id)).ToBeFalse(); + Expect(scope.IsModified(id)).ToBeTrue(); + + scope.Remove(id); // Type B should not be auto modified + Expect(scope.Has(id)).ToBeFalse(); + Expect(scope.IsModified(id)).ToBeFalse(); + }); }); - - It("Can mark modify automatically", []() - { - IdContext ctx; - Id id = AddId(ctx); - using MyScope = TIdScope, CMdfd>; - MyScope scope{ctx}; - Expect(MyScope::WDependencies::Contains>()).ToBeTrue(); - Expect(MyScope::WDependencies::Contains>()).ToBeFalse(); - Expect(MyScope::RWDependencies::Contains>()).ToBeTrue(); - Expect(MyScope::RWDependencies::Contains>()).ToBeTrue(); - - scope.Add(id); // Type B should be auto modified - Expect(scope.IsModified(id)).ToBeTrue(); - scope.Add(id); // Type B should not be auto modified - Expect(scope.IsModified(id)).ToBeFalse(); - - scope.ClearPool>(); - Expect(scope.IsModified(id)).ToBeFalse(); - - scope.Has(id); // Has should never mark modify - Expect(scope.IsModified(id)).ToBeFalse(); - - scope.Get(id); - Expect(scope.IsModified(id)).ToBeFalse(); - scope.Get(id); - Expect(scope.IsModified(id)).ToBeTrue(); - scope.Add(id); // Type B should not be auto modified - Expect(scope.IsModified(id)).ToBeFalse(); - - scope.ClearPool>(); - - scope.Remove(id); - Expect(scope.Has(id)).ToBeFalse(); - Expect(scope.IsModified(id)).ToBeTrue(); - - scope.Remove(id); // Type B should not be auto modified - Expect(scope.Has(id)).ToBeFalse(); - Expect(scope.IsModified(id)).ToBeFalse(); - }); -}); }); diff --git a/Tests/ECS/Statics.spec.cpp b/Tests/ECS/Statics.spec.cpp index e9c0a907..b6945f3d 100644 --- a/Tests/ECS/Statics.spec.cpp +++ b/Tests/ECS/Statics.spec.cpp @@ -1,7 +1,7 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include +#include using namespace p; @@ -24,67 +24,67 @@ struct StaticTypeThree Spec("ECS.Statics", []() { -It("Can set an static", []() -{ - IdContext ctx; - Expect(ctx.HasStatic()).ToEqual(false); - auto& var = ctx.SetStatic({4}); - Expect(var.i).ToEqual(4); - Expect(ctx.HasStatic()).ToEqual(true); - Expect(ctx.HasStatic()).ToEqual(false); -}); -It("Can set two statics", []() -{ - IdContext ctx; - Expect(ctx.HasStatic()).ToEqual(false); - Expect(ctx.HasStatic()).ToEqual(false); - auto& var1 = ctx.SetStatic({4}); - auto& var2 = ctx.SetStatic({2}); - Expect(var1.i).ToEqual(4); - Expect(var2.i).ToEqual(2); - Expect(ctx.HasStatic()).ToEqual(true); - Expect(ctx.HasStatic()).ToEqual(true); -}); -It("Can replace an static", []() -{ - IdContext ctx; - Expect(ctx.HasStatic()).ToEqual(false); - ctx.SetStatic({4}); - ctx.SetStatic({2}); - Expect(ctx.GetStatic().i).ToEqual(2); - Expect(ctx.HasStatic()).ToEqual(true); -}); -It("Can get or set an static", []() -{ - IdContext ctx; - // Can set - Expect(ctx.GetOrSetStatic({4}).i).ToEqual(4); - // Can get - Expect(ctx.GetOrSetStatic({10}).i).ToEqual(4); -}); -It("Can remove an static", []() -{ - IdContext ctx; - ctx.SetStatic(); - Expect(ctx.HasStatic()).ToEqual(true); - Expect(ctx.RemoveStatic()).ToBeTrue(); - Expect(ctx.HasStatic()).ToEqual(false); + It("Can set an static", []() + { + IdContext ctx; + Expect(ctx.HasStatic()).ToEqual(false); + auto& var = ctx.SetStatic({4}); + Expect(var.i).ToEqual(4); + Expect(ctx.HasStatic()).ToEqual(true); + Expect(ctx.HasStatic()).ToEqual(false); + }); + It("Can set two statics", []() + { + IdContext ctx; + Expect(ctx.HasStatic()).ToEqual(false); + Expect(ctx.HasStatic()).ToEqual(false); + auto& var1 = ctx.SetStatic({4}); + auto& var2 = ctx.SetStatic({2}); + Expect(var1.i).ToEqual(4); + Expect(var2.i).ToEqual(2); + Expect(ctx.HasStatic()).ToEqual(true); + Expect(ctx.HasStatic()).ToEqual(true); + }); + It("Can replace an static", []() + { + IdContext ctx; + Expect(ctx.HasStatic()).ToEqual(false); + ctx.SetStatic({4}); + ctx.SetStatic({2}); + Expect(ctx.GetStatic().i).ToEqual(2); + Expect(ctx.HasStatic()).ToEqual(true); + }); + It("Can get or set an static", []() + { + IdContext ctx; + // Can set + Expect(ctx.GetOrSetStatic({4}).i).ToEqual(4); + // Can get + Expect(ctx.GetOrSetStatic({10}).i).ToEqual(4); + }); + It("Can remove an static", []() + { + IdContext ctx; + ctx.SetStatic(); + Expect(ctx.HasStatic()).ToEqual(true); + Expect(ctx.RemoveStatic()).ToBeTrue(); + Expect(ctx.HasStatic()).ToEqual(false); - Expect(ctx.RemoveStatic()).ToBeFalse(); -}); + Expect(ctx.RemoveStatic()).ToBeFalse(); + }); -It("Can get statics", []() -{ - IdContext ctx; - ctx.SetStatic({4}); - ctx.SetStatic({2}); - Expect(ctx.GetStatic().i).ToEqual(4); - Expect(ctx.GetStatic().i).ToEqual(2); + It("Can get statics", []() + { + IdContext ctx; + ctx.SetStatic({4}); + ctx.SetStatic({2}); + Expect(ctx.GetStatic().i).ToEqual(4); + Expect(ctx.GetStatic().i).ToEqual(2); - ctx.SetStatic({14}); - Expect(ctx.GetStatic().i).ToEqual(14); + ctx.SetStatic({14}); + Expect(ctx.GetStatic().i).ToEqual(14); - ctx.RemoveStatic(); - Expect(ctx.TryGetStatic()).ToEqual(nullptr); -}); + ctx.RemoveStatic(); + Expect(ctx.TryGetStatic()).ToEqual(nullptr); + }); }); diff --git a/Tests/Files/Paths.spec.cpp b/Tests/Files/Paths.spec.cpp index 95b1f16b..24db9c8e 100644 --- a/Tests/Files/Paths.spec.cpp +++ b/Tests/Files/Paths.spec.cpp @@ -1,8 +1,8 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include #include +#include using namespace p; @@ -10,201 +10,203 @@ using namespace p; Spec("Files.Paths", []() { -It("Can get root name and path", []() -{ + It("Can get root name and path", []() + { #if P_PLATFORM_WINDOWS - Expect(p::GetRootPathName("F:\\SomeFolder\\AnotherFolder")).ToEqual("F:"); - Expect(p::GetRootPath("F:\\SomeFolder\\AnotherFolder")).ToEqual("F:\\"); + Expect(p::GetRootPathName("F:\\SomeFolder\\AnotherFolder")).ToEqual("F:"); + Expect(p::GetRootPath("F:\\SomeFolder\\AnotherFolder")).ToEqual("F:\\"); #elif P_PLATFORM_LINUX - Expect(p::GetRootPathName("/var/SomeFolder/AnotherFolder")).ToEqual(""); - Expect(p::GetRootPath("/var/SomeFolder/AnotherFolder")).ToEqual("/"); + Expect(p::GetRootPathName("/var/SomeFolder/AnotherFolder")).ToEqual(""); + Expect(p::GetRootPath("/var/SomeFolder/AnotherFolder")).ToEqual("/"); #endif - Expect(p::GetRootPathName("/AnotherFolder")).ToEqual(""); - Expect(p::GetRootPath("/AnotherFolder")).ToEqual("/"); -}); + Expect(p::GetRootPathName("/AnotherFolder")).ToEqual(""); + Expect(p::GetRootPath("/AnotherFolder")).ToEqual("/"); + }); -It("Can get relative path", []() -{ + It("Can get relative path", []() + { #if P_PLATFORM_WINDOWS - Expect(p::GetRelativePath("F:\\SomeFolder\\AnotherFolder")).ToEqual("SomeFolder\\AnotherFolder"); + Expect(p::GetRelativePath("F:\\SomeFolder\\AnotherFolder")) + .ToEqual("SomeFolder\\AnotherFolder"); #endif - Expect(p::GetRelativePath("/var/SomeFolder/AnotherFolder")).ToEqual("var/SomeFolder/AnotherFolder"); - Expect(p::GetRelativePath("/SomeFolder/AnotherFolder")).ToEqual("SomeFolder/AnotherFolder"); -}); - -It("Can check absolute path", []() -{ - Expect(p::IsAbsolutePath("//host")).ToEqual(true); + Expect(p::GetRelativePath("/var/SomeFolder/AnotherFolder")) + .ToEqual("var/SomeFolder/AnotherFolder"); + Expect(p::GetRelativePath("/SomeFolder/AnotherFolder")).ToEqual("SomeFolder/AnotherFolder"); + }); + + It("Can check absolute path", []() + { + Expect(p::IsAbsolutePath("//host")).ToEqual(true); #if P_PLATFORM_WINDOWS - Expect(p::IsAbsolutePath("F:\\SomeFolder\\AnotherFolder")).ToEqual(true); + Expect(p::IsAbsolutePath("F:\\SomeFolder\\AnotherFolder")).ToEqual(true); #elif P_PLATFORM_LINUX - Expect(p::IsAbsolutePath("/var/SomeFolder/AnotherFolder")).ToEqual(true); + Expect(p::IsAbsolutePath("/var/SomeFolder/AnotherFolder")).ToEqual(true); #endif - Expect(p::IsAbsolutePath("Executable.exe")).ToEqual(false); - Expect(p::IsAbsolutePath("SomeFolder/AnotherFolder")).ToEqual(false); -}); + Expect(p::IsAbsolutePath("Executable.exe")).ToEqual(false); + Expect(p::IsAbsolutePath("SomeFolder/AnotherFolder")).ToEqual(false); + }); -It("Can check relative path", []() -{ + It("Can check relative path", []() + { #if P_PLATFORM_WINDOWS - Expect(p::IsRelativePath("F:\\SomeFolder\\AnotherFolder")).ToEqual(false); + Expect(p::IsRelativePath("F:\\SomeFolder\\AnotherFolder")).ToEqual(false); #elif P_PLATFORM_LINUX - Expect(p::IsRelativePath("/var/SomeFolder/AnotherFolder")).ToEqual(false); + Expect(p::IsRelativePath("/var/SomeFolder/AnotherFolder")).ToEqual(false); #endif - Expect(p::IsRelativePath("Executable.exe")).ToEqual(true); - Expect(p::IsRelativePath("SomeFolder/AnotherFolder")).ToEqual(true); -}); + Expect(p::IsRelativePath("Executable.exe")).ToEqual(true); + Expect(p::IsRelativePath("SomeFolder/AnotherFolder")).ToEqual(true); + }); -It("Can get parent path", []() -{ + It("Can get parent path", []() + { #if P_PLATFORM_WINDOWS - Expect(p::GetParentPath("F:\\SomeFolder\\AnotherFolder")).ToEqual("F:\\SomeFolder"); + Expect(p::GetParentPath("F:\\SomeFolder\\AnotherFolder")).ToEqual("F:\\SomeFolder"); #endif - Expect(p::GetParentPath("/var/SomeFolder")).ToEqual("/var"); - Expect(p::GetParentPath("/SomeFolder/AnotherFolder")).ToEqual("/SomeFolder"); - Expect(p::GetParentPath("/SomeFolder/SomeFile.txt")).ToEqual("/SomeFolder"); -}); - -It("Executable path is not empty", []() -{ - Expect(p::PlatformPaths::GetExecutablePath()).ToNotEqual(""); -}); - -It("Can get extension", []() -{ + Expect(p::GetParentPath("/var/SomeFolder")).ToEqual("/var"); + Expect(p::GetParentPath("/SomeFolder/AnotherFolder")).ToEqual("/SomeFolder"); + Expect(p::GetParentPath("/SomeFolder/SomeFile.txt")).ToEqual("/SomeFolder"); + }); + + It("Executable path is not empty", []() + { + Expect(p::PlatformPaths::GetExecutablePath()).ToNotEqual(""); + }); + + It("Can get extension", []() + { #if P_PLATFORM_WINDOWS - Expect(p::GetExtension("F:\\SomeFolder\\AnotherFolder.lib")).ToEqual(".lib"); - Expect(p::GetExtension("F:\\AnotherFolder.lib")).ToEqual(".lib"); - Expect(p::GetExtension("F:\\AnotherFolder.")).ToEqual("."); - Expect(p::GetExtension("F:\\AnotherFolder")).ToEqual(""); - Expect(p::GetExtension("F:\\")).ToEqual(""); + Expect(p::GetExtension("F:\\SomeFolder\\AnotherFolder.lib")).ToEqual(".lib"); + Expect(p::GetExtension("F:\\AnotherFolder.lib")).ToEqual(".lib"); + Expect(p::GetExtension("F:\\AnotherFolder.")).ToEqual("."); + Expect(p::GetExtension("F:\\AnotherFolder")).ToEqual(""); + Expect(p::GetExtension("F:\\")).ToEqual(""); #elif P_PLATFORM_LINUX - Expect(p::GetExtension("/var/SomeFolder/AnotherFolder.lib")).ToEqual(".lib"); - Expect(p::GetExtension("/var/AnotherFolder.lib")).ToEqual(".lib"); - Expect(p::GetExtension("/var/AnotherFolder.")).ToEqual("."); - Expect(p::GetExtension("/var/AnotherFolder")).ToEqual(""); - Expect(p::GetExtension("/var/")).ToEqual(""); + Expect(p::GetExtension("/var/SomeFolder/AnotherFolder.lib")).ToEqual(".lib"); + Expect(p::GetExtension("/var/AnotherFolder.lib")).ToEqual(".lib"); + Expect(p::GetExtension("/var/AnotherFolder.")).ToEqual("."); + Expect(p::GetExtension("/var/AnotherFolder")).ToEqual(""); + Expect(p::GetExtension("/var/")).ToEqual(""); #endif - Expect(p::GetExtension("AnotherFolder.lib")).ToEqual(".lib"); - Expect(p::GetExtension("AnotherFolder")).ToEqual(""); -}); + Expect(p::GetExtension("AnotherFolder.lib")).ToEqual(".lib"); + Expect(p::GetExtension("AnotherFolder")).ToEqual(""); + }); -It("Can check extension", []() -{ + It("Can check extension", []() + { #if P_PLATFORM_WINDOWS - Expect(p::HasExtension("F:\\SomeFolder\\AnotherFolder.lib")).ToEqual(true); - Expect(p::HasExtension("F:\\AnotherFolder.lib")).ToEqual(true); - Expect(p::HasExtension("F:\\AnotherFolder.")).ToEqual(true); - Expect(p::HasExtension("F:\\AnotherFolder")).ToEqual(false); - Expect(p::HasExtension("F:\\")).ToEqual(false); + Expect(p::HasExtension("F:\\SomeFolder\\AnotherFolder.lib")).ToEqual(true); + Expect(p::HasExtension("F:\\AnotherFolder.lib")).ToEqual(true); + Expect(p::HasExtension("F:\\AnotherFolder.")).ToEqual(true); + Expect(p::HasExtension("F:\\AnotherFolder")).ToEqual(false); + Expect(p::HasExtension("F:\\")).ToEqual(false); #elif P_PLATFORM_LINUX - Expect(p::HasExtension("/var/SomeFolder/AnotherFolder.lib")).ToEqual(true); - Expect(p::HasExtension("/var/AnotherFolder.lib")).ToEqual(true); - Expect(p::HasExtension("/var/AnotherFolder.")).ToEqual(true); - Expect(p::HasExtension("/var/AnotherFolder")).ToEqual(false); - Expect(p::HasExtension("/var/")).ToEqual(false); + Expect(p::HasExtension("/var/SomeFolder/AnotherFolder.lib")).ToEqual(true); + Expect(p::HasExtension("/var/AnotherFolder.lib")).ToEqual(true); + Expect(p::HasExtension("/var/AnotherFolder.")).ToEqual(true); + Expect(p::HasExtension("/var/AnotherFolder")).ToEqual(false); + Expect(p::HasExtension("/var/")).ToEqual(false); #endif - Expect(p::HasExtension("AnotherFolder.lib")).ToEqual(true); - Expect(p::HasExtension("AnotherFolder")).ToEqual(false); -}); + Expect(p::HasExtension("AnotherFolder.lib")).ToEqual(true); + Expect(p::HasExtension("AnotherFolder")).ToEqual(false); + }); -It("Can replace extension", []() -{ - p::String path; + It("Can replace extension", []() + { + p::String path; #if P_PLATFORM_WINDOWS - path = "F:\\SomeFolder\\AnotherFolder.lib"; - p::ReplaceExtension(path, "txt"); - Expect(path).ToEqual("F:\\SomeFolder\\AnotherFolder.txt"); + path = "F:\\SomeFolder\\AnotherFolder.lib"; + p::ReplaceExtension(path, "txt"); + Expect(path).ToEqual("F:\\SomeFolder\\AnotherFolder.txt"); #elif P_PLATFORM_LINUX - path = "/var/SomeFolder/AnotherFolder.lib"; - p::ReplaceExtension(path, "txt"); - Expect(path).ToEqual("/var/SomeFolder/AnotherFolder.txt"); + path = "/var/SomeFolder/AnotherFolder.lib"; + p::ReplaceExtension(path, "txt"); + Expect(path).ToEqual("/var/SomeFolder/AnotherFolder.txt"); #endif - path = "AnotherFolder.lib"; - p::ReplaceExtension(path, "txt"); - Expect(path).ToEqual("AnotherFolder.txt"); - path = "AnotherFolder."; - p::ReplaceExtension(path, ".txt"); - Expect(path).ToEqual("AnotherFolder.txt"); - path = "AnotherFolder.lib"; - p::ReplaceExtension(path, ".txt"); - Expect(path).ToEqual("AnotherFolder.txt"); - path = "AnotherFolder"; - p::ReplaceExtension(path, "txt"); - Expect(path).ToEqual("AnotherFolder.txt"); -}); - -It("Can get stem", []() -{ + path = "AnotherFolder.lib"; + p::ReplaceExtension(path, "txt"); + Expect(path).ToEqual("AnotherFolder.txt"); + path = "AnotherFolder."; + p::ReplaceExtension(path, ".txt"); + Expect(path).ToEqual("AnotherFolder.txt"); + path = "AnotherFolder.lib"; + p::ReplaceExtension(path, ".txt"); + Expect(path).ToEqual("AnotherFolder.txt"); + path = "AnotherFolder"; + p::ReplaceExtension(path, "txt"); + Expect(path).ToEqual("AnotherFolder.txt"); + }); + + It("Can get stem", []() + { #if P_PLATFORM_WINDOWS - Expect(p::GetStem("F:\\SomeFolder\\AnotherFolder.lib")).ToEqual("AnotherFolder"); - Expect(p::GetStem("F:\\AnotherFolder.lib")).ToEqual("AnotherFolder"); - Expect(p::GetStem("F:\\AnotherFolder.")).ToEqual("AnotherFolder"); - Expect(p::GetStem("F:\\AnotherFolder")).ToEqual("AnotherFolder"); - Expect(p::GetStem("F:\\")).ToEqual(""); + Expect(p::GetStem("F:\\SomeFolder\\AnotherFolder.lib")).ToEqual("AnotherFolder"); + Expect(p::GetStem("F:\\AnotherFolder.lib")).ToEqual("AnotherFolder"); + Expect(p::GetStem("F:\\AnotherFolder.")).ToEqual("AnotherFolder"); + Expect(p::GetStem("F:\\AnotherFolder")).ToEqual("AnotherFolder"); + Expect(p::GetStem("F:\\")).ToEqual(""); #elif P_PLATFORM_LINUX - Expect(p::GetStem("/var/SomeFolder/AnotherFolder.lib")).ToEqual("AnotherFolder"); - Expect(p::GetStem("/var/AnotherFolder.lib")).ToEqual("AnotherFolder"); - Expect(p::GetStem("/var/AnotherFolder.")).ToEqual("AnotherFolder"); - Expect(p::GetStem("/var/AnotherFolder")).ToEqual("AnotherFolder"); - Expect(p::GetStem("/var/")).ToEqual(""); + Expect(p::GetStem("/var/SomeFolder/AnotherFolder.lib")).ToEqual("AnotherFolder"); + Expect(p::GetStem("/var/AnotherFolder.lib")).ToEqual("AnotherFolder"); + Expect(p::GetStem("/var/AnotherFolder.")).ToEqual("AnotherFolder"); + Expect(p::GetStem("/var/AnotherFolder")).ToEqual("AnotherFolder"); + Expect(p::GetStem("/var/")).ToEqual(""); #endif - Expect(p::GetStem("AnotherFolder.lib")).ToEqual("AnotherFolder"); - Expect(p::GetStem("AnotherFolder")).ToEqual("AnotherFolder"); - Expect(p::GetStem("")).ToEqual(""); -}); + Expect(p::GetStem("AnotherFolder.lib")).ToEqual("AnotherFolder"); + Expect(p::GetStem("AnotherFolder")).ToEqual("AnotherFolder"); + Expect(p::GetStem("")).ToEqual(""); + }); -It("Can check stem", []() -{ + It("Can check stem", []() + { #if P_PLATFORM_WINDOWS - Expect(p::HasStem("F:\\SomeFolder\\AnotherFolder.lib")).ToEqual(true); - Expect(p::HasStem("F:\\AnotherFolder.lib")).ToEqual(true); - Expect(p::HasStem("F:\\AnotherFolder.")).ToEqual(true); - Expect(p::HasStem("F:\\AnotherFolder")).ToEqual(true); - Expect(p::HasStem("F:\\")).ToEqual(false); + Expect(p::HasStem("F:\\SomeFolder\\AnotherFolder.lib")).ToEqual(true); + Expect(p::HasStem("F:\\AnotherFolder.lib")).ToEqual(true); + Expect(p::HasStem("F:\\AnotherFolder.")).ToEqual(true); + Expect(p::HasStem("F:\\AnotherFolder")).ToEqual(true); + Expect(p::HasStem("F:\\")).ToEqual(false); #elif P_PLATFORM_LINUX - Expect(p::HasStem("/var/SomeFolder/AnotherFolder.lib")).ToEqual(true); - Expect(p::HasStem("/var/AnotherFolder.lib")).ToEqual(true); - Expect(p::HasStem("/var/AnotherFolder.")).ToEqual(true); - Expect(p::HasStem("/var/AnotherFolder")).ToEqual(true); - Expect(p::HasStem("/var/")).ToEqual(false); + Expect(p::HasStem("/var/SomeFolder/AnotherFolder.lib")).ToEqual(true); + Expect(p::HasStem("/var/AnotherFolder.lib")).ToEqual(true); + Expect(p::HasStem("/var/AnotherFolder.")).ToEqual(true); + Expect(p::HasStem("/var/AnotherFolder")).ToEqual(true); + Expect(p::HasStem("/var/")).ToEqual(false); #endif - Expect(p::HasStem("AnotherFolder.lib")).ToEqual(true); - Expect(p::HasStem("AnotherFolder")).ToEqual(true); - Expect(p::HasStem("")).ToEqual(false); -}); - - -It("Can append to path", []() -{ - Expect(p::JoinPaths("", "")).ToEqual(""); - Expect(p::JoinPaths("", "/")).ToEqual("/"); - Expect(p::JoinPaths("", "bar")).ToEqual("bar"); - Expect(p::JoinPaths("", "/bar")).ToEqual("/bar"); - - Expect(p::JoinPaths("/", "")).ToEqual("/"); - Expect(p::JoinPaths("/", "/")).ToEqual("/"); - Expect(p::JoinPaths("/", "bar")).ToEqual("/bar"); - Expect(p::JoinPaths("/", "/bar")).ToEqual("/bar"); - Expect(p::JoinPaths("foo", "/")).ToEqual("/"); - - Expect(p::JoinPaths("foo", "/bar")).ToEqual("/bar"); - Expect(p::JoinPaths("foo/", "")).ToEqual("foo/"); - Expect(p::JoinPaths("foo/", "/")).ToEqual("/"); - Expect(p::JoinPaths("foo/", "bar")).ToEqual("foo/bar"); + Expect(p::HasStem("AnotherFolder.lib")).ToEqual(true); + Expect(p::HasStem("AnotherFolder")).ToEqual(true); + Expect(p::HasStem("")).ToEqual(false); + }); + + + It("Can append to path", []() + { + Expect(p::JoinPaths("", "")).ToEqual(""); + Expect(p::JoinPaths("", "/")).ToEqual("/"); + Expect(p::JoinPaths("", "bar")).ToEqual("bar"); + Expect(p::JoinPaths("", "/bar")).ToEqual("/bar"); + + Expect(p::JoinPaths("/", "")).ToEqual("/"); + Expect(p::JoinPaths("/", "/")).ToEqual("/"); + Expect(p::JoinPaths("/", "bar")).ToEqual("/bar"); + Expect(p::JoinPaths("/", "/bar")).ToEqual("/bar"); + Expect(p::JoinPaths("foo", "/")).ToEqual("/"); + + Expect(p::JoinPaths("foo", "/bar")).ToEqual("/bar"); + Expect(p::JoinPaths("foo/", "")).ToEqual("foo/"); + Expect(p::JoinPaths("foo/", "/")).ToEqual("/"); + Expect(p::JoinPaths("foo/", "bar")).ToEqual("foo/bar"); #if P_PLATFORM_WINDOWS - Expect(p::JoinPaths("foo", "")).ToEqual("foo\\"); - Expect(p::JoinPaths("foo", "bar")).ToEqual("foo\\bar"); - Expect(p::JoinPaths("foo\\", "\\bar")).ToEqual("\\bar"); - Expect(p::JoinPaths("c:", "bar")).ToEqual("c:bar"); - Expect(p::JoinPaths("\\\\host", "foo")).ToEqual("\\\\host\\foo"); - Expect(p::JoinPaths("\\\\host/", "foo")).ToEqual("\\\\host/foo"); + Expect(p::JoinPaths("foo", "")).ToEqual("foo\\"); + Expect(p::JoinPaths("foo", "bar")).ToEqual("foo\\bar"); + Expect(p::JoinPaths("foo\\", "\\bar")).ToEqual("\\bar"); + Expect(p::JoinPaths("c:", "bar")).ToEqual("c:bar"); + Expect(p::JoinPaths("\\\\host", "foo")).ToEqual("\\\\host\\foo"); + Expect(p::JoinPaths("\\\\host/", "foo")).ToEqual("\\\\host/foo"); #else - Expect(p::JoinPaths("foo", "")).ToEqual("foo/"); - Expect(p::JoinPaths("foo", "bar")).ToEqual("foo/bar"); - Expect(p::JoinPaths("//host", "foo")).ToEqual("//host/foo"); - Expect(p::JoinPaths("//host/", "foo")).ToEqual("//host/foo"); + Expect(p::JoinPaths("foo", "")).ToEqual("foo/"); + Expect(p::JoinPaths("foo", "bar")).ToEqual("foo/bar"); + Expect(p::JoinPaths("//host", "foo")).ToEqual("//host/foo"); + Expect(p::JoinPaths("//host/", "foo")).ToEqual("//host/foo"); #endif -}); + }); }); diff --git a/Tests/Math/Color.spec.cpp b/Tests/Math/Color.spec.cpp index da3abaab..c4c00ef0 100644 --- a/Tests/Math/Color.spec.cpp +++ b/Tests/Math/Color.spec.cpp @@ -1,7 +1,7 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include +#include using namespace p; @@ -9,126 +9,126 @@ using namespace p; Spec("Math.Color", []() { -Describe("Helpers", []() -{ - It("Can make from rgba", []() - { - auto color = Color::FromRGB(128, 206, 215, 35); - Expect(color.r).ToEqual(128); - Expect(color.g).ToEqual(206); - Expect(color.b).ToEqual(215); - Expect(color.a).ToEqual(35); - }); - It("Can make from Hex", []() + Describe("Helpers", []() { - auto color = Color::FromHex(0x80ced7); - Expect(color.r).ToEqual(128); - Expect(color.g).ToEqual(206); - Expect(color.b).ToEqual(215); - - auto colora = Color::FromHexAlpha(0x80ced723); - Expect(colora.r).ToEqual(128); - Expect(colora.g).ToEqual(206); - Expect(colora.b).ToEqual(215); - Expect(colora.a).ToEqual(35); + It("Can make from rgba", []() + { + auto color = Color::FromRGB(128, 206, 215, 35); + Expect(color.r).ToEqual(128); + Expect(color.g).ToEqual(206); + Expect(color.b).ToEqual(215); + Expect(color.a).ToEqual(35); + }); + It("Can make from Hex", []() + { + auto color = Color::FromHex(0x80ced7); + Expect(color.r).ToEqual(128); + Expect(color.g).ToEqual(206); + Expect(color.b).ToEqual(215); + + auto colora = Color::FromHexAlpha(0x80ced723); + Expect(colora.r).ToEqual(128); + Expect(colora.g).ToEqual(206); + Expect(colora.b).ToEqual(215); + Expect(colora.a).ToEqual(35); + }); + + It("Can make from packed", []() + { + auto argb = Color::FromPackedARGB(0x2380ced7); + Expect(argb.r).ToEqual(128); + Expect(argb.g).ToEqual(206); + Expect(argb.b).ToEqual(215); + Expect(argb.a).ToEqual(35); + + auto abgr = Color::FromPackedABGR(0x23d7ce80); + Expect(abgr.r).ToEqual(128); + Expect(abgr.g).ToEqual(206); + Expect(abgr.b).ToEqual(215); + Expect(abgr.a).ToEqual(35); + + auto rgba = Color::FromPackedRGBA(0x80ced723); + Expect(rgba.r).ToEqual(128); + Expect(rgba.g).ToEqual(206); + Expect(rgba.b).ToEqual(215); + Expect(rgba.a).ToEqual(35); + + auto bgra = Color::FromPackedBGRA(0xd7ce8023); + Expect(bgra.r).ToEqual(128); + Expect(bgra.g).ToEqual(206); + Expect(bgra.b).ToEqual(215); + Expect(bgra.a).ToEqual(35); + }); + + It("Can get as packed", []() + { + auto color = Color(128, 206, 215, 35); + Expect(color.ToPackedARGB()).ToEqual(0x2380ced7); + Expect(color.ToPackedABGR()).ToEqual(0x23d7ce80); + Expect(color.ToPackedRGBA()).ToEqual(0x80ced723); + Expect(color.ToPackedBGRA()).ToEqual(0xd7ce8023); + }); }); - - It("Can make from packed", []() + Describe("LinearColor", []() { - auto argb = Color::FromPackedARGB(0x2380ced7); - Expect(argb.r).ToEqual(128); - Expect(argb.g).ToEqual(206); - Expect(argb.b).ToEqual(215); - Expect(argb.a).ToEqual(35); - - auto abgr = Color::FromPackedABGR(0x23d7ce80); - Expect(abgr.r).ToEqual(128); - Expect(abgr.g).ToEqual(206); - Expect(abgr.b).ToEqual(215); - Expect(abgr.a).ToEqual(35); - - auto rgba = Color::FromPackedRGBA(0x80ced723); - Expect(rgba.r).ToEqual(128); - Expect(rgba.g).ToEqual(206); - Expect(rgba.b).ToEqual(215); - Expect(rgba.a).ToEqual(35); - - auto bgra = Color::FromPackedBGRA(0xd7ce8023); - Expect(bgra.r).ToEqual(128); - Expect(bgra.g).ToEqual(206); - Expect(bgra.b).ToEqual(215); - Expect(bgra.a).ToEqual(35); + It("Can Shade", []() + { + Expect(LinearColor::White().Shade(1.0f)).ToEqual(LinearColor::Black()); + Expect(LinearColor::White().Shade(0.5f)).ToEqual(LinearColor::Gray()); + constexpr LinearColor color{Color::FromHex(0x80ced7)}; + Expect(color.Shade(0.5f)).ToEqual(LinearColor{Color::FromHex(0x40676B)}); + }); + + It("Shade doesn't change alpha", []() + { + Expect(std::abs(LinearColor::White().Translucency(0.5f).Shade(1.0f).a - 0.5f)) + .ToBeLessOrEqual(0.01f); + }); + + It("Can Tint", []() + { + Expect(LinearColor::Black().Tint(1.0f)).ToEqual(LinearColor::White()); + Expect(LinearColor::Black().Tint(0.5f)).ToEqual(LinearColor::Gray()); + Expect(Color::FromHex(0x80ced7).Tint(0.5f)).ToEqual(Color::FromHex(0xbfe6eb)); + }); + + It("Tint doesn't change alpha", []() + { + Expect(std::abs(LinearColor::Black().Translucency(0.5f).Tint(1.0f).a - 0.5f)) + .ToBeLessOrEqual(0.01f); + }); }); - - It("Can get as packed", []() + Describe("Color", []() { - auto color = Color(128, 206, 215, 35); - Expect(color.ToPackedARGB()).ToEqual(0x2380ced7); - Expect(color.ToPackedABGR()).ToEqual(0x23d7ce80); - Expect(color.ToPackedRGBA()).ToEqual(0x80ced723); - Expect(color.ToPackedBGRA()).ToEqual(0xd7ce8023); + It("Can Shade", []() + { + Expect(Color::White().Shade(1.0f)).ToEqual(Color::Black()); + Expect(Color::White().Shade(0.5f)).ToEqual(Color::Gray()); + Expect(Color::FromHex(0x80ced7).Shade(0.5f)).ToEqual(Color::FromHex(0x40676B)); + }); + + It("Shade doesn't change alpha", []() + { + Expect(Color::White().Translucency(127).Shade(1.0f).a).ToEqual(127); + }); + + It("Can Tint", []() + { + Expect(Color::Black().Tint(1.0f)).ToEqual(Color::White()); + Expect(Color::Black().Tint(0.5f)).ToEqual(Color::Gray()); + Expect(Color::FromHex(0x80ced7).Tint(0.5f)).ToEqual(Color::FromHex(0xbfe6eb)); + }); + + It("Tint doesn't change alpha", []() + { + Expect(Color::Black().Translucency(127).Tint(1.0f).a).ToEqual(127); + }); + + It("Can convert to linear", []() + { + Expect(LinearColor{Color::White()}).ToEqual(LinearColor::White()); + Expect(LinearColor{Color::Black()}).ToEqual(LinearColor::Black()); + Expect(LinearColor{Color::Gray()}).ToEqual(LinearColor::Gray()); + }); }); }); -Describe("LinearColor", []() -{ - It("Can Shade", []() - { - Expect(LinearColor::White().Shade(1.0f)).ToEqual(LinearColor::Black()); - Expect(LinearColor::White().Shade(0.5f)).ToEqual(LinearColor::Gray()); - constexpr LinearColor color{Color::FromHex(0x80ced7)}; - Expect(color.Shade(0.5f)).ToEqual(LinearColor{Color::FromHex(0x40676B)}); - }); - - It("Shade doesn't change alpha", []() - { - Expect(std::abs(LinearColor::White().Translucency(0.5f).Shade(1.0f).a - 0.5f)) - .ToBeLessOrEqual(0.01f); - }); - - It("Can Tint", []() - { - Expect(LinearColor::Black().Tint(1.0f)).ToEqual(LinearColor::White()); - Expect(LinearColor::Black().Tint(0.5f)).ToEqual(LinearColor::Gray()); - Expect(Color::FromHex(0x80ced7).Tint(0.5f)).ToEqual(Color::FromHex(0xbfe6eb)); - }); - - It("Tint doesn't change alpha", []() - { - Expect(std::abs(LinearColor::Black().Translucency(0.5f).Tint(1.0f).a - 0.5f)) - .ToBeLessOrEqual(0.01f); - }); -}); -Describe("Color", []() -{ - It("Can Shade", []() - { - Expect(Color::White().Shade(1.0f)).ToEqual(Color::Black()); - Expect(Color::White().Shade(0.5f)).ToEqual(Color::Gray()); - Expect(Color::FromHex(0x80ced7).Shade(0.5f)).ToEqual(Color::FromHex(0x40676B)); - }); - - It("Shade doesn't change alpha", []() - { - Expect(Color::White().Translucency(127).Shade(1.0f).a).ToEqual(127); - }); - - It("Can Tint", []() - { - Expect(Color::Black().Tint(1.0f)).ToEqual(Color::White()); - Expect(Color::Black().Tint(0.5f)).ToEqual(Color::Gray()); - Expect(Color::FromHex(0x80ced7).Tint(0.5f)).ToEqual(Color::FromHex(0xbfe6eb)); - }); - - It("Tint doesn't change alpha", []() - { - Expect(Color::Black().Translucency(127).Tint(1.0f).a).ToEqual(127); - }); - - It("Can convert to linear", []() - { - Expect(LinearColor{Color::White()}).ToEqual(LinearColor::White()); - Expect(LinearColor{Color::Black()}).ToEqual(LinearColor::Black()); - Expect(LinearColor{Color::Gray()}).ToEqual(LinearColor::Gray()); - }); -}); -}); diff --git a/Tests/Math/Math.spec.cpp b/Tests/Math/Math.spec.cpp index 482592c5..a5be7804 100644 --- a/Tests/Math/Math.spec.cpp +++ b/Tests/Math/Math.spec.cpp @@ -1,10 +1,10 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include #include #include #include +#include #include @@ -21,340 +21,340 @@ namespace Spec("Math.Math", []() { -Describe("Binary Search", []() -{ - It("LowerBound", [=]() + Describe("Binary Search", []() { - Expect(bottomUp.LowerBound(34)).ToEqual(1); - Expect(bottomUp.LowerBound(100)).ToEqual(3); - Expect(bottomUp.LowerBound(51)).ToEqual(3); + It("LowerBound", [=]() + { + Expect(bottomUp.LowerBound(34)).ToEqual(1); + Expect(bottomUp.LowerBound(100)).ToEqual(3); + Expect(bottomUp.LowerBound(51)).ToEqual(3); - Expect(topDown.LowerBound(34, TGreater<>())).ToEqual(3); - Expect(topDown.LowerBound(100, TGreater<>())).ToEqual(1); - Expect(topDown.LowerBound(51, TGreater<>())).ToEqual(2); - }); + Expect(topDown.LowerBound(34, TGreater<>())).ToEqual(3); + Expect(topDown.LowerBound(100, TGreater<>())).ToEqual(1); + Expect(topDown.LowerBound(51, TGreater<>())).ToEqual(2); + }); - It("UpperBound", [=]() - { - Expect(bottomUp.UpperBound(34)).ToEqual(2); - Expect(bottomUp.UpperBound(100)).ToEqual(4); + It("UpperBound", [=]() + { + Expect(bottomUp.UpperBound(34)).ToEqual(2); + Expect(bottomUp.UpperBound(100)).ToEqual(4); - Expect(topDown.UpperBound(34, TGreater<>())).ToEqual(4); - Expect(topDown.UpperBound(100, TGreater<>())).ToEqual(2); - }); + Expect(topDown.UpperBound(34, TGreater<>())).ToEqual(4); + Expect(topDown.UpperBound(100, TGreater<>())).ToEqual(2); + }); - It("Can find equal", [=]() - { - Expect(bottomUp.FindSorted(0)).ToEqual(NO_INDEX); - Expect(bottomUp.FindSorted(34)).ToEqual(1); - Expect(bottomUp.FindSorted(33)).ToEqual(NO_INDEX); - Expect(bottomUp.FindSorted(121)).ToEqual(NO_INDEX); + It("Can find equal", [=]() + { + Expect(bottomUp.FindSorted(0)).ToEqual(NO_INDEX); + Expect(bottomUp.FindSorted(34)).ToEqual(1); + Expect(bottomUp.FindSorted(33)).ToEqual(NO_INDEX); + Expect(bottomUp.FindSorted(121)).ToEqual(NO_INDEX); - Expect(topDown.FindSorted(34, TGreater<>())).ToEqual(3); - }); + Expect(topDown.FindSorted(34, TGreater<>())).ToEqual(3); + }); - Describe("FindSortedMax", []() - { - Describe("Ordered by a < b", []() + Describe("FindSortedMax", []() { - TArray bottomUp{23, 34, 50, 50, 100, 120}; - - It("Find first item", [=]() + Describe("Ordered by a < b", []() { - auto i4 = bottomUp.FindSortedMax(23, false); - Expect(i4).ToEqual(NO_INDEX); + TArray bottomUp{23, 34, 50, 50, 100, 120}; - auto i5 = bottomUp.FindSortedMax(23, true); - Expect(i5).ToEqual(0); + It("Find first item", [=]() + { + auto i4 = bottomUp.FindSortedMax(23, false); + Expect(i4).ToEqual(NO_INDEX); - auto i6 = bottomUp.FindSortedMax(22, true); - Expect(i6).ToEqual(NO_INDEX); - }); + auto i5 = bottomUp.FindSortedMax(23, true); + Expect(i5).ToEqual(0); - It("Find any item", [=]() - { - auto i1 = bottomUp.FindSortedMax(34, true); - Expect(i1).ToEqual(1); + auto i6 = bottomUp.FindSortedMax(22, true); + Expect(i6).ToEqual(NO_INDEX); + }); - auto i2 = bottomUp.FindSortedMax(33, true); - Expect(i2).ToEqual(0); + It("Find any item", [=]() + { + auto i1 = bottomUp.FindSortedMax(34, true); + Expect(i1).ToEqual(1); - auto i3 = bottomUp.FindSortedMax(34, false); - Expect(i3).ToEqual(0); - }); + auto i2 = bottomUp.FindSortedMax(33, true); + Expect(i2).ToEqual(0); - It("Find last item", [=]() - { - auto i4 = bottomUp.FindSortedMax(120, false); - Expect(i4).ToEqual(4); + auto i3 = bottomUp.FindSortedMax(34, false); + Expect(i3).ToEqual(0); + }); - auto i5 = bottomUp.FindSortedMax(120, true); - Expect(i5).ToEqual(5); + It("Find last item", [=]() + { + auto i4 = bottomUp.FindSortedMax(120, false); + Expect(i4).ToEqual(4); - auto i6 = bottomUp.FindSortedMax(121, true); - Expect(i6).ToEqual(5); + auto i5 = bottomUp.FindSortedMax(120, true); + Expect(i5).ToEqual(5); - auto i7 = bottomUp.FindSortedMax(100, false); - Expect(i7).ToEqual(3); - }); - }); + auto i6 = bottomUp.FindSortedMax(121, true); + Expect(i6).ToEqual(5); - Describe("Ordered by a > b", []() - { - TArray topDown{120, 100, 50, 50, 34, 23}; + auto i7 = bottomUp.FindSortedMax(100, false); + Expect(i7).ToEqual(3); + }); + }); - It("Find first item", [=]() + Describe("Ordered by a > b", []() { - auto i4 = topDown.FindSortedMax(120, true); - Expect(i4).ToEqual(0); + TArray topDown{120, 100, 50, 50, 34, 23}; - auto i5 = topDown.FindSortedMax(120, false); - Expect(i5).ToEqual(1); + It("Find first item", [=]() + { + auto i4 = topDown.FindSortedMax(120, true); + Expect(i4).ToEqual(0); - auto i6 = topDown.FindSortedMax(121, true); - Expect(i6).ToEqual(0); - }); + auto i5 = topDown.FindSortedMax(120, false); + Expect(i5).ToEqual(1); - It("Find any item", [=]() - { - auto i1 = topDown.FindSortedMax(34, true); - Expect(i1).ToEqual(4); + auto i6 = topDown.FindSortedMax(121, true); + Expect(i6).ToEqual(0); + }); - auto i2 = topDown.FindSortedMax(33, true); - Expect(i2).ToEqual(5); + It("Find any item", [=]() + { + auto i1 = topDown.FindSortedMax(34, true); + Expect(i1).ToEqual(4); - auto i3 = topDown.FindSortedMax(34, false); - Expect(i3).ToEqual(5); - }); + auto i2 = topDown.FindSortedMax(33, true); + Expect(i2).ToEqual(5); - It("Find last item", [=]() - { - auto i4 = topDown.FindSortedMax(23, false); - Expect(i4).ToEqual(NO_INDEX); + auto i3 = topDown.FindSortedMax(34, false); + Expect(i3).ToEqual(5); + }); - auto i5 = topDown.FindSortedMax(23, true); - Expect(i5).ToEqual(5); + It("Find last item", [=]() + { + auto i4 = topDown.FindSortedMax(23, false); + Expect(i4).ToEqual(NO_INDEX); - auto i6 = topDown.FindSortedMax(22, true); - Expect(i6).ToEqual(NO_INDEX); - }); - }); + auto i5 = topDown.FindSortedMax(23, true); + Expect(i5).ToEqual(5); - Describe("All same values", []() - { - TArray allEqual{10, 10, 10}; + auto i6 = topDown.FindSortedMax(22, true); + Expect(i6).ToEqual(NO_INDEX); + }); + }); - It("Doesnt find smaller", [=]() + Describe("All same values", []() { - auto i1 = allEqual.FindSortedMax(9, false); - Expect(i1).ToEqual(NO_INDEX); + TArray allEqual{10, 10, 10}; - auto i2 = allEqual.FindSortedMax(10, false); - Expect(i2).ToEqual(NO_INDEX); - }); + It("Doesnt find smaller", [=]() + { + auto i1 = allEqual.FindSortedMax(9, false); + Expect(i1).ToEqual(NO_INDEX); - It("Finds smaller", [=]() - { - auto i1 = allEqual.FindSortedMax(10, true); - Expect(i1).ToEqual(0); + auto i2 = allEqual.FindSortedMax(10, false); + Expect(i2).ToEqual(NO_INDEX); + }); - auto i2 = allEqual.FindSortedMax(11, false); - Expect(i2).ToEqual(0); + It("Finds smaller", [=]() + { + auto i1 = allEqual.FindSortedMax(10, true); + Expect(i1).ToEqual(0); + + auto i2 = allEqual.FindSortedMax(11, false); + Expect(i2).ToEqual(0); + }); }); }); - }); - Describe("FindSortedMin", []() - { - Describe("Ordered by a < b", []() + Describe("FindSortedMin", []() { - TArray bottomUp{23, 34, 50, 50, 100, 120}; - - It("Find first item", [=]() + Describe("Ordered by a < b", []() { - auto i1 = bottomUp.FindSortedMin(23, true); - Expect(i1).ToEqual(0); + TArray bottomUp{23, 34, 50, 50, 100, 120}; - auto i2 = bottomUp.FindSortedMin(20, true); - Expect(i2).ToEqual(0); + It("Find first item", [=]() + { + auto i1 = bottomUp.FindSortedMin(23, true); + Expect(i1).ToEqual(0); - auto i3 = bottomUp.FindSortedMin(23, false); - Expect(i3).ToEqual(1); - }); + auto i2 = bottomUp.FindSortedMin(20, true); + Expect(i2).ToEqual(0); - It("Find any item", [=]() - { - auto i1 = bottomUp.FindSortedMin(33, false); - Expect(i1).ToEqual(1); + auto i3 = bottomUp.FindSortedMin(23, false); + Expect(i3).ToEqual(1); + }); - auto i2 = bottomUp.FindSortedMin(34, true); - Expect(i2).ToEqual(1); + It("Find any item", [=]() + { + auto i1 = bottomUp.FindSortedMin(33, false); + Expect(i1).ToEqual(1); - auto i3 = bottomUp.FindSortedMin(34, false); - Expect(i3).ToEqual(2); - }); + auto i2 = bottomUp.FindSortedMin(34, true); + Expect(i2).ToEqual(1); - It("Find last item", [=]() - { - auto i1 = bottomUp.FindSortedMin(100, false); - Expect(i1).ToEqual(5); + auto i3 = bottomUp.FindSortedMin(34, false); + Expect(i3).ToEqual(2); + }); - auto i2 = bottomUp.FindSortedMin(120, false); - Expect(i2).ToEqual(NO_INDEX); + It("Find last item", [=]() + { + auto i1 = bottomUp.FindSortedMin(100, false); + Expect(i1).ToEqual(5); - auto i3 = bottomUp.FindSortedMin(120, true); - Expect(i3).ToEqual(5); + auto i2 = bottomUp.FindSortedMin(120, false); + Expect(i2).ToEqual(NO_INDEX); - auto i4 = bottomUp.FindSortedMin(121, true); - Expect(i4).ToEqual(NO_INDEX); - }); - }); + auto i3 = bottomUp.FindSortedMin(120, true); + Expect(i3).ToEqual(5); - Describe("Ordered by a > b", []() - { - TArray topDown{120, 100, 50, 50, 34, 23}; + auto i4 = bottomUp.FindSortedMin(121, true); + Expect(i4).ToEqual(NO_INDEX); + }); + }); - It("Find first item", [=]() + Describe("Ordered by a > b", []() { - auto i4 = topDown.FindSortedMin(120, true); - Expect(i4).ToEqual(0); + TArray topDown{120, 100, 50, 50, 34, 23}; - auto i5 = topDown.FindSortedMin(120, false); - Expect(i5).ToEqual(NO_INDEX); + It("Find first item", [=]() + { + auto i4 = topDown.FindSortedMin(120, true); + Expect(i4).ToEqual(0); - auto i6 = topDown.FindSortedMin(121, true); - Expect(i6).ToEqual(NO_INDEX); - }); + auto i5 = topDown.FindSortedMin(120, false); + Expect(i5).ToEqual(NO_INDEX); - It("Find any item", [=]() - { - auto i1 = topDown.FindSortedMin(34, true); - Expect(i1).ToEqual(4); + auto i6 = topDown.FindSortedMin(121, true); + Expect(i6).ToEqual(NO_INDEX); + }); - auto i2 = topDown.FindSortedMin(33, true); - Expect(i2).ToEqual(4); + It("Find any item", [=]() + { + auto i1 = topDown.FindSortedMin(34, true); + Expect(i1).ToEqual(4); - auto i3 = topDown.FindSortedMin(34, false); - Expect(i3).ToEqual(3); - }); + auto i2 = topDown.FindSortedMin(33, true); + Expect(i2).ToEqual(4); - It("Find last item", [=]() - { - auto i4 = topDown.FindSortedMin(23, false); - Expect(i4).ToEqual(4); + auto i3 = topDown.FindSortedMin(34, false); + Expect(i3).ToEqual(3); + }); - auto i5 = topDown.FindSortedMin(23, true); - Expect(i5).ToEqual(5); + It("Find last item", [=]() + { + auto i4 = topDown.FindSortedMin(23, false); + Expect(i4).ToEqual(4); - auto i6 = topDown.FindSortedMin(22, true); - Expect(i6).ToEqual(5); - }); - }); + auto i5 = topDown.FindSortedMin(23, true); + Expect(i5).ToEqual(5); - Describe("All same values", []() - { - TArray allEqual{10, 10, 10}; + auto i6 = topDown.FindSortedMin(22, true); + Expect(i6).ToEqual(5); + }); + }); - It("Doesnt find bigger", [=]() + Describe("All same values", []() { - auto i1 = allEqual.FindSortedMin(11, false); - Expect(i1).ToEqual(NO_INDEX); + TArray allEqual{10, 10, 10}; - auto i2 = allEqual.FindSortedMin(10, false); - Expect(i2).ToEqual(NO_INDEX); - }); + It("Doesnt find bigger", [=]() + { + auto i1 = allEqual.FindSortedMin(11, false); + Expect(i1).ToEqual(NO_INDEX); - It("Finds bigger", [=]() - { - auto i1 = allEqual.FindSortedMin(10, true); - Expect(i1).ToEqual(0); + auto i2 = allEqual.FindSortedMin(10, false); + Expect(i2).ToEqual(NO_INDEX); + }); + + It("Finds bigger", [=]() + { + auto i1 = allEqual.FindSortedMin(10, true); + Expect(i1).ToEqual(0); - auto i2 = allEqual.FindSortedMin(9, false); - Expect(i2).ToEqual(0); + auto i2 = allEqual.FindSortedMin(9, false); + Expect(i2).ToEqual(0); + }); }); }); }); -}); - -It("Can check Infinite", [=]() -{ - Expect(IsInf(0.0)).ToEqual(false); - Expect(IsInf(-0.0)).ToEqual(false); - Expect(IsInf(1.0)).ToEqual(false); - Expect(IsInf(-1.0)).ToEqual(false); - - static constexpr double dInfinite = Limits::Infinity(); - Expect(IsInf(dInfinite)).ToEqual(true); - Expect(IsInf(-dInfinite)).ToEqual(true); - Expect(IsPosInf(-dInfinite)).ToEqual(false); - Expect(IsNegInf(dInfinite)).ToEqual(false); - Expect(IsInf(Limits::Max())).ToEqual(false); - Expect(IsInf(Limits::Lowest())).ToEqual(false); - Expect(IsInf(double(bigNumber))).ToEqual(false); -}); - -It("Can check NAN", [=]() -{ - Expect(IsNAN(0.0)).ToEqual(false); - Expect(IsNAN(Limits::QuietNaN())).ToEqual(true); -}); -Describe("Roundings", []() -{ - It("Can Floor", [=]() + It("Can check Infinite", [=]() { - Expect(Floor(0.0)).ToEqual(std::floor(0.0)); - Expect(Floor(-0.0)).ToEqual(std::floor(-0.0)); - Expect(Floor(4.2)).ToEqual(std::floor(4.2)); - Expect(Floor(4.5)).ToEqual(std::floor(4.5)); - Expect(Floor(4.7)).ToEqual(std::floor(4.7)); - Expect(Floor(5.0)).ToEqual(std::floor(5.0)); - Expect(Floor(-4.2)).ToEqual(std::floor(-4.2)); - Expect(Floor(-4.7)).ToEqual(std::floor(-4.7)); - Expect(Floor(-5.0)).ToEqual(std::floor(-5.0)); - Expect(Floor(99999999999999999.0 + 0.5)).ToEqual(99999999999999999.0); + Expect(IsInf(0.0)).ToEqual(false); + Expect(IsInf(-0.0)).ToEqual(false); + Expect(IsInf(1.0)).ToEqual(false); + Expect(IsInf(-1.0)).ToEqual(false); static constexpr double dInfinite = Limits::Infinity(); - Expect(Floor(-dInfinite)).ToEqual(std::floor(-dInfinite)); - Expect(Floor(dInfinite)).ToEqual(std::floor(dInfinite)); - Expect(IsNAN(Floor(Limits::QuietNaN()))).ToEqual(true); + Expect(IsInf(dInfinite)).ToEqual(true); + Expect(IsInf(-dInfinite)).ToEqual(true); + Expect(IsPosInf(-dInfinite)).ToEqual(false); + Expect(IsNegInf(dInfinite)).ToEqual(false); + Expect(IsInf(Limits::Max())).ToEqual(false); + Expect(IsInf(Limits::Lowest())).ToEqual(false); + Expect(IsInf(double(bigNumber))).ToEqual(false); }); - It("Can Ceil", [=]() - { - Expect(Ceil(0.0)).ToEqual(std::ceil(0.0)); - Expect(Ceil(-0.0)).ToEqual(std::ceil(-0.0)); - Expect(Ceil(4.2)).ToEqual(std::ceil(4.2)); - Expect(Ceil(4.5)).ToEqual(std::ceil(4.5)); - Expect(Ceil(4.7)).ToEqual(std::ceil(4.7)); - Expect(Ceil(5.0)).ToEqual(std::ceil(5.0)); - Expect(Ceil(-4.2)).ToEqual(std::ceil(-4.2)); - Expect(Ceil(-4.7)).ToEqual(std::ceil(-4.7)); - Expect(Ceil(-5.0)).ToEqual(std::ceil(-5.0)); - Expect(Ceil(99999999999999999.0 - 0.5)).ToEqual(99999999999999999.0); - static constexpr double dInfinite = Limits::Infinity(); - Expect(Ceil(-dInfinite)).ToEqual(std::ceil(-dInfinite)); - Expect(Ceil(dInfinite)).ToEqual(std::ceil(dInfinite)); - Expect(IsNAN(Ceil(Limits::QuietNaN()))).ToEqual(true); + It("Can check NAN", [=]() + { + Expect(IsNAN(0.0)).ToEqual(false); + Expect(IsNAN(Limits::QuietNaN())).ToEqual(true); }); - It("Can Round", [=]() + Describe("Roundings", []() { - Expect(Round(0.0)).ToEqual(std::round(0.0)); - Expect(Round(-0.0)).ToEqual(std::round(-0.0)); - Expect(Round(4.2)).ToEqual(std::round(4.2)); - Expect(Round(4.5)).ToEqual(std::round(4.5)); - Expect(Round(4.7)).ToEqual(std::round(4.7)); - Expect(Round(5.0)).ToEqual(std::round(5.0)); - Expect(Round(-4.2)).ToEqual(std::round(-4.2)); - Expect(Round(-4.7)).ToEqual(std::round(-4.7)); - Expect(Round(-5.0)).ToEqual(std::round(-5.0)); - Expect(Round(99999999999999999.0 - 0.4)).ToEqual(99999999999999999.0); + It("Can Floor", [=]() + { + Expect(Floor(0.0)).ToEqual(std::floor(0.0)); + Expect(Floor(-0.0)).ToEqual(std::floor(-0.0)); + Expect(Floor(4.2)).ToEqual(std::floor(4.2)); + Expect(Floor(4.5)).ToEqual(std::floor(4.5)); + Expect(Floor(4.7)).ToEqual(std::floor(4.7)); + Expect(Floor(5.0)).ToEqual(std::floor(5.0)); + Expect(Floor(-4.2)).ToEqual(std::floor(-4.2)); + Expect(Floor(-4.7)).ToEqual(std::floor(-4.7)); + Expect(Floor(-5.0)).ToEqual(std::floor(-5.0)); + Expect(Floor(99999999999999999.0 + 0.5)).ToEqual(99999999999999999.0); + + static constexpr double dInfinite = Limits::Infinity(); + Expect(Floor(-dInfinite)).ToEqual(std::floor(-dInfinite)); + Expect(Floor(dInfinite)).ToEqual(std::floor(dInfinite)); + Expect(IsNAN(Floor(Limits::QuietNaN()))).ToEqual(true); + }); + It("Can Ceil", [=]() + { + Expect(Ceil(0.0)).ToEqual(std::ceil(0.0)); + Expect(Ceil(-0.0)).ToEqual(std::ceil(-0.0)); + Expect(Ceil(4.2)).ToEqual(std::ceil(4.2)); + Expect(Ceil(4.5)).ToEqual(std::ceil(4.5)); + Expect(Ceil(4.7)).ToEqual(std::ceil(4.7)); + Expect(Ceil(5.0)).ToEqual(std::ceil(5.0)); + Expect(Ceil(-4.2)).ToEqual(std::ceil(-4.2)); + Expect(Ceil(-4.7)).ToEqual(std::ceil(-4.7)); + Expect(Ceil(-5.0)).ToEqual(std::ceil(-5.0)); + Expect(Ceil(99999999999999999.0 - 0.5)).ToEqual(99999999999999999.0); + + static constexpr double dInfinite = Limits::Infinity(); + Expect(Ceil(-dInfinite)).ToEqual(std::ceil(-dInfinite)); + Expect(Ceil(dInfinite)).ToEqual(std::ceil(dInfinite)); + Expect(IsNAN(Ceil(Limits::QuietNaN()))).ToEqual(true); + }); - static constexpr double dInfinite = Limits::Infinity(); - Expect(Round(-dInfinite)).ToEqual(std::round(-dInfinite)); - Expect(Round(dInfinite)).ToEqual(std::round(dInfinite)); - Expect(IsNAN(Round(Limits::QuietNaN()))).ToEqual(true); + It("Can Round", [=]() + { + Expect(Round(0.0)).ToEqual(std::round(0.0)); + Expect(Round(-0.0)).ToEqual(std::round(-0.0)); + Expect(Round(4.2)).ToEqual(std::round(4.2)); + Expect(Round(4.5)).ToEqual(std::round(4.5)); + Expect(Round(4.7)).ToEqual(std::round(4.7)); + Expect(Round(5.0)).ToEqual(std::round(5.0)); + Expect(Round(-4.2)).ToEqual(std::round(-4.2)); + Expect(Round(-4.7)).ToEqual(std::round(-4.7)); + Expect(Round(-5.0)).ToEqual(std::round(-5.0)); + Expect(Round(99999999999999999.0 - 0.4)).ToEqual(99999999999999999.0); + + static constexpr double dInfinite = Limits::Infinity(); + Expect(Round(-dInfinite)).ToEqual(std::round(-dInfinite)); + Expect(Round(dInfinite)).ToEqual(std::round(dInfinite)); + Expect(IsNAN(Round(Limits::QuietNaN()))).ToEqual(true); + }); }); }); -}); diff --git a/Tests/Math/Vector.spec.cpp b/Tests/Math/Vector.spec.cpp index 08f0ee21..d4fb99c5 100644 --- a/Tests/Math/Vector.spec.cpp +++ b/Tests/Math/Vector.spec.cpp @@ -9,60 +9,60 @@ using namespace p; Spec("Math.Vector", []() { -Describe("v2", []() -{ - It("Can reflect", []() + Describe("v2", []() { - p::v2 v{0.f, 1.f}; - p::v2 normal{1.f, 0.f}; - p::v2 v2 = v.Reflect(normal); - Expect(v2.Equals({0.f, 1.f})).ToEqual(true); - v = p::v2{0.f, 1.f}; - normal = p::v2{0.f, 1.f}; - v2 = v.Reflect(normal); - Expect(v2.Equals({0.f, -1.f})).ToEqual(true); - v = p::v2{1.f, 1.f}; - normal = p::v2{0.f, 1.f}; - v2 = v.Reflect(normal); - Expect(v2.Equals({1.f, -1.f})).ToEqual(true); - v = p::v2{1.f, 1.f}; - normal = p::v2{1.f, 0.f}; - v2 = v.Reflect(normal); - Expect(v2.Equals({-1.f, 1.f})).ToEqual(true); - v = p::v2{-1.f, 1.f}; - normal = p::v2{-1.f, 0.f}; - v2 = v.Reflect(normal); - Expect(v2.Equals({1.f, 1.f})).ToEqual(true); - v = p::v2{-1.f, -1.f}; - normal = p::v2{0.f, 1.f}; - v2 = v.Reflect(normal); - Expect(v2.Equals({-1.f, 1.f})).ToEqual(true); - v = p::v2{0.f, 1.f}; - normal = p::v2{0.f, 1.f}; - v2 = v.Reflect(normal); - Expect(v2.Equals({0.f, -1.f})).ToEqual(true); - v = p::v2{0.f, -1.f}; - normal = p::v2{0.f, 1.f}; - v2 = v.Reflect(normal); - Expect(v2.Equals({0.f, 1.f})).ToEqual(true); - }); + It("Can reflect", []() + { + p::v2 v{0.f, 1.f}; + p::v2 normal{1.f, 0.f}; + p::v2 v2 = v.Reflect(normal); + Expect(v2.Equals({0.f, 1.f})).ToEqual(true); + v = p::v2{0.f, 1.f}; + normal = p::v2{0.f, 1.f}; + v2 = v.Reflect(normal); + Expect(v2.Equals({0.f, -1.f})).ToEqual(true); + v = p::v2{1.f, 1.f}; + normal = p::v2{0.f, 1.f}; + v2 = v.Reflect(normal); + Expect(v2.Equals({1.f, -1.f})).ToEqual(true); + v = p::v2{1.f, 1.f}; + normal = p::v2{1.f, 0.f}; + v2 = v.Reflect(normal); + Expect(v2.Equals({-1.f, 1.f})).ToEqual(true); + v = p::v2{-1.f, 1.f}; + normal = p::v2{-1.f, 0.f}; + v2 = v.Reflect(normal); + Expect(v2.Equals({1.f, 1.f})).ToEqual(true); + v = p::v2{-1.f, -1.f}; + normal = p::v2{0.f, 1.f}; + v2 = v.Reflect(normal); + Expect(v2.Equals({-1.f, 1.f})).ToEqual(true); + v = p::v2{0.f, 1.f}; + normal = p::v2{0.f, 1.f}; + v2 = v.Reflect(normal); + Expect(v2.Equals({0.f, -1.f})).ToEqual(true); + v = p::v2{0.f, -1.f}; + normal = p::v2{0.f, 1.f}; + v2 = v.Reflect(normal); + Expect(v2.Equals({0.f, 1.f})).ToEqual(true); + }); - It("Can convert to angle", []() - { - float anglea = p::v2{0.f, 1.f}.Angle(); - Expect(anglea).ToEqual(90.f); - float angleb = p::v2{0.f, -1.f}.Angle(); - Expect(angleb).ToEqual(-90.f); - float anglec = p::v2{1.f, 0.f}.Angle(); - Expect(anglec).ToEqual(0.f); - float angled = p::v2{-1.f, 0.f}.Angle(); - Expect(angled).ToEqual(180.f); - }); + It("Can convert to angle", []() + { + float anglea = p::v2{0.f, 1.f}.Angle(); + Expect(anglea).ToEqual(90.f); + float angleb = p::v2{0.f, -1.f}.Angle(); + Expect(angleb).ToEqual(-90.f); + float anglec = p::v2{1.f, 0.f}.Angle(); + Expect(anglec).ToEqual(0.f); + float angled = p::v2{-1.f, 0.f}.Angle(); + Expect(angled).ToEqual(180.f); + }); - It("Can convert from angle", []() - { - Expect(p::v2::FromAngle(0.f).Angle()).ToEqual(0); - Expect(p::v2::FromAngle(90.f).Angle()).ToEqual(90.f); + It("Can convert from angle", []() + { + Expect(p::v2::FromAngle(0.f).Angle()).ToEqual(0); + Expect(p::v2::FromAngle(90.f).Angle()).ToEqual(90.f); + }); }); }); -}); diff --git a/Tests/Memory/BestFitArena.spec.cpp b/Tests/Memory/BestFitArena.spec.cpp index 3d9752cd..b3ba0198 100644 --- a/Tests/Memory/BestFitArena.spec.cpp +++ b/Tests/Memory/BestFitArena.spec.cpp @@ -1,7 +1,7 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include +#include using namespace p; @@ -16,278 +16,278 @@ struct TypeOfSize Spec("Memory.BestFitArena", []() { -It("Reserves a block on construction", []() -{ - BestFitArena arena{1024}; - arena.GetStats()->detectLeaks = false; - Expect(arena.GetFreeSize()).ToEqual(1024); - Expect(*arena.GetBlock()).ToNotEqual(nullptr); - Expect(arena.GetBlock().size).ToEqual(1024); -}); + It("Reserves a block on construction", []() + { + BestFitArena arena{1024}; + arena.GetStats()->detectLeaks = false; + Expect(arena.GetFreeSize()).ToEqual(1024); + Expect(*arena.GetBlock()).ToNotEqual(nullptr); + Expect(arena.GetBlock().size).ToEqual(1024); + }); + + It("Can allocate", []() + { + BestFitArena arena{1024}; + arena.GetStats()->detectLeaks = false; -It("Can allocate", []() -{ - BestFitArena arena{1024}; - arena.GetStats()->detectLeaks = false; + void* p = arena.Alloc(4); + new (p) TypeOfSize<4>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.Contains(p)).ToBeTrue(); + }); - void* p = arena.Alloc(4); - new (p) TypeOfSize<4>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.Contains(p)).ToBeTrue(); -}); + It("Allocates at correct addresses", []() + { + BestFitArena arena{1024}; + arena.GetStats()->detectLeaks = false; -It("Allocates at correct addresses", []() -{ - BestFitArena arena{1024}; - arena.GetStats()->detectLeaks = false; + const auto* blockPtr = static_cast(*arena.GetBlock()); - const auto* blockPtr = static_cast(*arena.GetBlock()); + void* p = arena.Alloc(4); + new (p) TypeOfSize<4>(); + Expect(p).ToEqual(blockPtr); - void* p = arena.Alloc(4); - new (p) TypeOfSize<4>(); - Expect(p).ToEqual(blockPtr); + void* p2 = arena.Alloc(4); + new (p2) TypeOfSize<4>(); + Expect(p2).ToEqual(blockPtr + 4); + }); - void* p2 = arena.Alloc(4); - new (p2) TypeOfSize<4>(); - Expect(p2).ToEqual(blockPtr + 4); -}); + It("Detects there is not enough space", []() + { + BestFitArena arena{32}; + arena.GetStats()->detectLeaks = false; + + // 16 bytes + void* p = arena.Alloc(20); + new (p) TypeOfSize<20>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.Contains(p)).ToBeTrue(); + + // Another 16 bytes + void* p2 = arena.Alloc(6); + new (p2) TypeOfSize<6>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.Contains(p2)).ToBeTrue(); + + // No more space, return null + void* p3 = arena.Alloc(8); // 8 bytes + Expect(p3).ToEqual(nullptr); + }); + + It("Allocates with alignment", []() + { + BestFitArena arena{1024}; + arena.GetStats()->detectLeaks = false; -It("Detects there is not enough space", []() -{ - BestFitArena arena{32}; - arena.GetStats()->detectLeaks = false; - - // 16 bytes - void* p = arena.Alloc(20); - new (p) TypeOfSize<20>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.Contains(p)).ToBeTrue(); - - // Another 16 bytes - void* p2 = arena.Alloc(6); - new (p2) TypeOfSize<6>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.Contains(p2)).ToBeTrue(); - - // No more space, return null - void* p3 = arena.Alloc(8); // 8 bytes - Expect(p3).ToEqual(nullptr); -}); + void* b = arena.Alloc(1); + new (b) TypeOfSize<1>(); -It("Allocates with alignment", []() -{ - BestFitArena arena{1024}; - arena.GetStats()->detectLeaks = false; - - void* b = arena.Alloc(1); - new (b) TypeOfSize<1>(); - - // When padding is not 0 (last ptr is not aligned) - void* p = arena.Alloc(4, 8); - new (p) TypeOfSize<4>(); - Expect(GetAlignmentPadding(p, 8)).ToEqual(0); - - // When padding is 0 (last ptr is aligned) - void* p2 = arena.Alloc(4, 16); - new (p2) TypeOfSize<4>(); - Expect(GetAlignmentPadding(p2, 16)).ToEqual(0); - - // When padding is 0 (last ptr is aligned) - void* p3 = arena.Alloc(8, 32); - new (p3) TypeOfSize<8>(); - Expect(GetAlignmentPadding(p3, 32)).ToEqual(0); -}); + // When padding is not 0 (last ptr is not aligned) + void* p = arena.Alloc(4, 8); + new (p) TypeOfSize<4>(); + Expect(GetAlignmentPadding(p, 8)).ToEqual(0); -It("Can free", []() -{ - BestFitArena arena{64}; - arena.GetStats()->detectLeaks = false; + // When padding is 0 (last ptr is aligned) + void* p2 = arena.Alloc(4, 16); + new (p2) TypeOfSize<4>(); + Expect(GetAlignmentPadding(p2, 16)).ToEqual(0); - void* p = arena.Alloc(32); - new (p) TypeOfSize<32>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(32); + // When padding is 0 (last ptr is aligned) + void* p3 = arena.Alloc(8, 32); + new (p3) TypeOfSize<8>(); + Expect(GetAlignmentPadding(p3, 32)).ToEqual(0); + }); - arena.Free(p, 32); - Expect(arena.GetFreeSize()).ToEqual(64); -}); + It("Can free", []() + { + BestFitArena arena{64}; + arena.GetStats()->detectLeaks = false; -It("Can free multiple", []() -{ - BestFitArena arena{64}; - arena.GetStats()->detectLeaks = false; + void* p = arena.Alloc(32); + new (p) TypeOfSize<32>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(32); - void* p = arena.Alloc(16); - new (p) TypeOfSize<16>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(48); + arena.Free(p, 32); + Expect(arena.GetFreeSize()).ToEqual(64); + }); - void* p2 = arena.Alloc(16); - new (p2) TypeOfSize<16>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(32); + It("Can free multiple", []() + { + BestFitArena arena{64}; + arena.GetStats()->detectLeaks = false; - arena.Free(p2, 16); - Expect(arena.GetFreeSize()).ToEqual(48); + void* p = arena.Alloc(16); + new (p) TypeOfSize<16>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(48); - arena.Free(p, 16); - Expect(arena.GetFreeSize()).ToEqual(64); -}); + void* p2 = arena.Alloc(16); + new (p2) TypeOfSize<16>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(32); -It("Can free in between allocations", []() -{ - BestFitArena arena{64}; - arena.GetStats()->detectLeaks = false; - - void* p = arena.Alloc(32); - new (p) TypeOfSize<32>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(32); - - void* p2 = arena.Alloc(30); - new (p2) TypeOfSize<30>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(2); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - - void* p3 = arena.Alloc(2); - new (p3) TypeOfSize<2>(); - Expect(p3).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(0); - // No space left, no free slots - Expect(arena.GetFreeSlots().Size()).ToEqual(0); - - arena.Free(p2, 30); - Expect(arena.GetFreeSize()).ToEqual(30); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - Expect(arena.GetFreeSlots()[0].start).ToEqual(p2); - Expect(arena.GetFreeSlots()[0].End()).ToEqual(p3); -}); + arena.Free(p2, 16); + Expect(arena.GetFreeSize()).ToEqual(48); -It("Can merge previous and next slots on free", []() -{ - BestFitArena arena{64}; - arena.GetStats()->detectLeaks = false; + arena.Free(p, 16); + Expect(arena.GetFreeSize()).ToEqual(64); + }); - void* p = arena.Alloc(9); - new (p) TypeOfSize<9>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(55); + It("Can free in between allocations", []() + { + BestFitArena arena{64}; + arena.GetStats()->detectLeaks = false; + + void* p = arena.Alloc(32); + new (p) TypeOfSize<32>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(32); + + void* p2 = arena.Alloc(30); + new (p2) TypeOfSize<30>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(2); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + void* p3 = arena.Alloc(2); + new (p3) TypeOfSize<2>(); + Expect(p3).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(0); + // No space left, no free slots + Expect(arena.GetFreeSlots().Size()).ToEqual(0); + + arena.Free(p2, 30); + Expect(arena.GetFreeSize()).ToEqual(30); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + Expect(arena.GetFreeSlots()[0].start).ToEqual(p2); + Expect(arena.GetFreeSlots()[0].End()).ToEqual(p3); + }); + + It("Can merge previous and next slots on free", []() + { + BestFitArena arena{64}; + arena.GetStats()->detectLeaks = false; - void* p2 = arena.Alloc(50); - new (p2) TypeOfSize<50>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(5); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); + void* p = arena.Alloc(9); + new (p) TypeOfSize<9>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(55); - void* p3 = arena.Alloc(5); - new (p3) TypeOfSize<5>(); - Expect(p3).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(0); + void* p2 = arena.Alloc(50); + new (p2) TypeOfSize<50>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(5); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); - // No space left, no free slots - Expect(arena.GetFreeSlots().Size()).ToEqual(0); + void* p3 = arena.Alloc(5); + new (p3) TypeOfSize<5>(); + Expect(p3).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(0); - arena.Free(p, 9); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); + // No space left, no free slots + Expect(arena.GetFreeSlots().Size()).ToEqual(0); - arena.Free(p3, 5); - Expect(arena.GetFreeSlots().Size()).ToEqual(2); + arena.Free(p, 9); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); - arena.Free(p2, 50); // Slots previous and next are merged - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - Expect(arena.GetFreeSlots()[0].size).ToEqual(64); + arena.Free(p3, 5); + Expect(arena.GetFreeSlots().Size()).ToEqual(2); - // Slot contains the entire memory block - Expect(arena.GetFreeSlots()[0].start).ToEqual(arena.GetBlock().data); - Expect(arena.GetFreeSlots()[0].End()).ToEqual(arena.GetBlock().End()); -}); + arena.Free(p2, 50); // Slots previous and next are merged + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + Expect(arena.GetFreeSlots()[0].size).ToEqual(64); -It("Can merge previous slot on free", []() -{ - BestFitArena arena{48}; - arena.GetStats()->detectLeaks = false; - - void* p = arena.Alloc(39); - new (p) TypeOfSize<39>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(9); - - void* p2 = arena.Alloc(9); - new (p2) TypeOfSize<9>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(0); - Expect(arena.GetFreeSlots().Size()).ToEqual(0); - - arena.Free(p, 39); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - - arena.Free(p2, 9); // Slot is expanded from the front - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - Expect(arena.GetFreeSlots()[0].size).ToEqual(48); - - // Slot contains the entire memory block - Expect(arena.GetFreeSlots()[0].start).ToEqual(arena.GetBlock().data); - Expect(arena.GetFreeSlots()[0].End()).ToEqual(arena.GetBlock().End()); -}); + // Slot contains the entire memory block + Expect(arena.GetFreeSlots()[0].start).ToEqual(arena.GetBlock().data); + Expect(arena.GetFreeSlots()[0].End()).ToEqual(arena.GetBlock().End()); + }); -It("Can merge next slot on free", []() -{ - BestFitArena arena{48}; - arena.GetStats()->detectLeaks = false; - - void* p = arena.Alloc(24); - new (p) TypeOfSize<24>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(24); - - void* p2 = arena.Alloc(24); - new (p2) TypeOfSize<24>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(0); - Expect(arena.GetFreeSlots().Size()).ToEqual(0); - - arena.Free(p2, 24); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - - arena.Free(p, 24); // Slot is expanded from the back - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - Expect(arena.GetFreeSlots()[0].size).ToEqual(48); - - // Slot contains the entire memory block - Expect(arena.GetFreeSlots()[0].start).ToEqual(arena.GetBlock().data); - Expect(arena.GetFreeSlots()[0].End()).ToEqual(arena.GetBlock().End()); -}); + It("Can merge previous slot on free", []() + { + BestFitArena arena{48}; + arena.GetStats()->detectLeaks = false; -It("Ensures a big alignment leaves a gap", []() -{ - BestFitArena arena{128}; - arena.GetStats()->detectLeaks = false; - - // We ensure first allocation aligns the block (just for the test) - void* p = arena.Alloc(8); - new (p) TypeOfSize<8>(); - Expect(arena.GetFreeSize()).ToEqual(120); - - void* p2 = arena.Alloc(8, 64); - new (p2) TypeOfSize<8>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(112); - - // Alignment is absolute, so the gap between p and p2 is zero - // when the block base lands on a matching 64B boundary. - const bool hasGap = p2 > (u8*)p + 8; - Expect(arena.GetFreeSlots().Size()).ToEqual(hasGap ? 2 : 1); - - // Slot contains the rest if the block - Expect(arena.GetFreeSlots()[0].start).ToEqual((u8*)p2 + 8); - Expect(arena.GetFreeSlots()[0].End()).ToEqual(arena.GetBlock().End()); - - // Slot contains the alignment gap - if (hasGap) + void* p = arena.Alloc(39); + new (p) TypeOfSize<39>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(9); + + void* p2 = arena.Alloc(9); + new (p2) TypeOfSize<9>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(0); + Expect(arena.GetFreeSlots().Size()).ToEqual(0); + + arena.Free(p, 39); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + arena.Free(p2, 9); // Slot is expanded from the front + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + Expect(arena.GetFreeSlots()[0].size).ToEqual(48); + + // Slot contains the entire memory block + Expect(arena.GetFreeSlots()[0].start).ToEqual(arena.GetBlock().data); + Expect(arena.GetFreeSlots()[0].End()).ToEqual(arena.GetBlock().End()); + }); + + It("Can merge next slot on free", []() { - Expect(arena.GetFreeSlots()[1].start).ToEqual((u8*)p + 8); - Expect(arena.GetFreeSlots()[1].End()).ToEqual(p2); - } -}); + BestFitArena arena{48}; + arena.GetStats()->detectLeaks = false; + + void* p = arena.Alloc(24); + new (p) TypeOfSize<24>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(24); + + void* p2 = arena.Alloc(24); + new (p2) TypeOfSize<24>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(0); + Expect(arena.GetFreeSlots().Size()).ToEqual(0); + + arena.Free(p2, 24); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + arena.Free(p, 24); // Slot is expanded from the back + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + Expect(arena.GetFreeSlots()[0].size).ToEqual(48); + + // Slot contains the entire memory block + Expect(arena.GetFreeSlots()[0].start).ToEqual(arena.GetBlock().data); + Expect(arena.GetFreeSlots()[0].End()).ToEqual(arena.GetBlock().End()); + }); + + It("Ensures a big alignment leaves a gap", []() + { + BestFitArena arena{128}; + arena.GetStats()->detectLeaks = false; + + // We ensure first allocation aligns the block (just for the test) + void* p = arena.Alloc(8); + new (p) TypeOfSize<8>(); + Expect(arena.GetFreeSize()).ToEqual(120); + + void* p2 = arena.Alloc(8, 64); + new (p2) TypeOfSize<8>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(112); + + // Alignment is absolute, so the gap between p and p2 is zero + // when the block base lands on a matching 64B boundary. + const bool hasGap = p2 > (u8*)p + 8; + Expect(arena.GetFreeSlots().Size()).ToEqual(hasGap ? 2 : 1); + + // Slot contains the rest if the block + Expect(arena.GetFreeSlots()[0].start).ToEqual((u8*)p2 + 8); + Expect(arena.GetFreeSlots()[0].End()).ToEqual(arena.GetBlock().End()); + + // Slot contains the alignment gap + if (hasGap) + { + Expect(arena.GetFreeSlots()[1].start).ToEqual((u8*)p + 8); + Expect(arena.GetFreeSlots()[1].End()).ToEqual(p2); + } + }); }); diff --git a/Tests/Memory/BigBestFitArena.spec.cpp b/Tests/Memory/BigBestFitArena.spec.cpp index 8d25a921..227ab40f 100644 --- a/Tests/Memory/BigBestFitArena.spec.cpp +++ b/Tests/Memory/BigBestFitArena.spec.cpp @@ -1,7 +1,7 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include +#include using namespace p; @@ -15,291 +15,290 @@ struct TypeOfSize Spec("Memory.BigBestFitArena", []() { -It("Reserves a block on construction", []() -{ - BigBestFitArena arena{1024}; - arena.GetStats()->detectLeaks = false; - Expect(arena.GetFreeSize()).ToEqual(1024); - Expect(*arena.GetBlock()).ToNotEqual(nullptr); - Expect(arena.GetBlock().size).ToEqual(1024); -}); + It("Reserves a block on construction", []() + { + BigBestFitArena arena{1024}; + arena.GetStats()->detectLeaks = false; + Expect(arena.GetFreeSize()).ToEqual(1024); + Expect(*arena.GetBlock()).ToNotEqual(nullptr); + Expect(arena.GetBlock().size).ToEqual(1024); + }); + + It("Can allocate", []() + { + BigBestFitArena arena{1024}; + arena.GetStats()->detectLeaks = false; -It("Can allocate", []() -{ - BigBestFitArena arena{1024}; - arena.GetStats()->detectLeaks = false; + void* p = arena.Alloc(4); + new (p) TypeOfSize<4>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.Contains(p)).ToBeTrue(); + }); - void* p = arena.Alloc(4); - new (p) TypeOfSize<4>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.Contains(p)).ToBeTrue(); -}); + It("Allocates at correct addresses", []() + { + BigBestFitArena arena{1024}; + arena.GetStats()->detectLeaks = false; -It("Allocates at correct addresses", []() -{ - BigBestFitArena arena{1024}; - arena.GetStats()->detectLeaks = false; + const auto* blockPtr = static_cast(*arena.GetBlock()); - const auto* blockPtr = static_cast(*arena.GetBlock()); + void* p = arena.Alloc(4); + new (p) TypeOfSize<4>(); + const void* expectedP = blockPtr + p::GetAlignmentPaddingWithHeader(blockPtr, 8, 8); + Expect(p).ToEqual(expectedP); - void* p = arena.Alloc(4); - new (p) TypeOfSize<4>(); - const void* expectedP = blockPtr + p::GetAlignmentPaddingWithHeader(blockPtr, 8, 8); - Expect(p).ToEqual(expectedP); + void* p2 = arena.Alloc(4); + new (p2) TypeOfSize<4>(); + void* expectedP2 = static_cast(p) + 8 + p::GetAlignmentPaddingWithHeader(p, 8, 8); + Expect(p2).ToEqual(expectedP2); + }); - void* p2 = arena.Alloc(4); - new (p2) TypeOfSize<4>(); - void* expectedP2 = - static_cast(p) + 8 + p::GetAlignmentPaddingWithHeader(p, 8, 8); - Expect(p2).ToEqual(expectedP2); -}); + It("Detects there is not enough space", []() + { + BigBestFitArena arena{32}; + arena.GetStats()->detectLeaks = false; + + // 16 bytes + void* p = arena.Alloc(8); + new (p) TypeOfSize<8>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.Contains(p)).ToBeTrue(); + + // Another 16 bytes + void* p2 = arena.Alloc(4); + new (p2) TypeOfSize<4>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.Contains(p2)).ToBeTrue(); + + // No more space, return null + void* p3 = arena.Alloc(8); // 8 bytes + Expect(p3).ToEqual(nullptr); + }); + + It("Allocates with alignment", []() + { + BigBestFitArena arena{1024}; + arena.GetStats()->detectLeaks = false; -It("Detects there is not enough space", []() -{ - BigBestFitArena arena{32}; - arena.GetStats()->detectLeaks = false; - - // 16 bytes - void* p = arena.Alloc(8); - new (p) TypeOfSize<8>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.Contains(p)).ToBeTrue(); - - // Another 16 bytes - void* p2 = arena.Alloc(4); - new (p2) TypeOfSize<4>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.Contains(p2)).ToBeTrue(); - - // No more space, return null - void* p3 = arena.Alloc(8); // 8 bytes - Expect(p3).ToEqual(nullptr); -}); + void* b = arena.Alloc(1); + new (b) TypeOfSize<1>(); -It("Allocates with alignment", []() -{ - BigBestFitArena arena{1024}; - arena.GetStats()->detectLeaks = false; - - void* b = arena.Alloc(1); - new (b) TypeOfSize<1>(); - - // When padding is not 0 (last ptr is not aligned) - void* p = arena.Alloc(4, 8); - new (p) TypeOfSize<4>(); - Expect(p::GetAlignmentPadding(p, 8)).ToEqual(0); - - // When padding is 0 (last ptr is aligned) - void* p2 = arena.Alloc(4, 16); - new (p2) TypeOfSize<4>(); - Expect(p::GetAlignmentPadding(p2, 16)).ToEqual(0); - - // When padding is 0 (last ptr is aligned) - void* p3 = arena.Alloc(8, 32); - new (p3) TypeOfSize<8>(); - Expect(p::GetAlignmentPadding(p3, 32)).ToEqual(0); -}); + // When padding is not 0 (last ptr is not aligned) + void* p = arena.Alloc(4, 8); + new (p) TypeOfSize<4>(); + Expect(p::GetAlignmentPadding(p, 8)).ToEqual(0); -It("Can free", []() -{ - BigBestFitArena arena{64}; - arena.GetStats()->detectLeaks = false; + // When padding is 0 (last ptr is aligned) + void* p2 = arena.Alloc(4, 16); + new (p2) TypeOfSize<4>(); + Expect(p::GetAlignmentPadding(p2, 16)).ToEqual(0); - void* p = arena.Alloc(32); - new (p) TypeOfSize<32>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(24); + // When padding is 0 (last ptr is aligned) + void* p3 = arena.Alloc(8, 32); + new (p3) TypeOfSize<8>(); + Expect(p::GetAlignmentPadding(p3, 32)).ToEqual(0); + }); - arena.Free(p, 32); - Expect(arena.GetFreeSize()).ToEqual(64); -}); + It("Can free", []() + { + BigBestFitArena arena{64}; + arena.GetStats()->detectLeaks = false; -It("Can free multiple", []() -{ - BigBestFitArena arena{64}; - arena.GetStats()->detectLeaks = false; + void* p = arena.Alloc(32); + new (p) TypeOfSize<32>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(24); + + arena.Free(p, 32); + Expect(arena.GetFreeSize()).ToEqual(64); + }); - void* p = arena.Alloc(16); - new (p) TypeOfSize<16>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(40); + It("Can free multiple", []() + { + BigBestFitArena arena{64}; + arena.GetStats()->detectLeaks = false; - void* p2 = arena.Alloc(16); - new (p2) TypeOfSize<16>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(16); + void* p = arena.Alloc(16); + new (p) TypeOfSize<16>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(40); - arena.Free(p2, 16); - Expect(arena.GetFreeSize()).ToEqual(40); + void* p2 = arena.Alloc(16); + new (p2) TypeOfSize<16>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(16); - arena.Free(p, 16); - Expect(arena.GetFreeSize()).ToEqual(64); -}); + arena.Free(p2, 16); + Expect(arena.GetFreeSize()).ToEqual(40); -It("Can free in between allocations", []() -{ - BigBestFitArena arena{64}; - arena.GetStats()->detectLeaks = false; - - void* p = arena.Alloc(16); - new (p) TypeOfSize<16>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(40); - - void* p2 = arena.Alloc(16); - new (p2) TypeOfSize<16>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(16); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - - void* p3 = arena.Alloc(8); - new (p3) TypeOfSize<8>(); - Expect(p3).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(0); - // No space left, no free slots - Expect(arena.GetFreeSlots().Size()).ToEqual(0); - - arena.Free(p2, 16); - Expect(arena.GetFreeSize()).ToEqual(24); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - - auto slot = arena.GetFreeSlots()[0]; - u8* slotStart = (u8*)arena.GetBlock().data + slot.offset; - Expect(slotStart).ToEqual(static_cast(p2) - 8); - Expect(slotStart + slot.size).ToEqual(static_cast(p3) - 8); -}); + arena.Free(p, 16); + Expect(arena.GetFreeSize()).ToEqual(64); + }); -It("Can merge previous and next slots on free", []() -{ - BigBestFitArena arena{64}; - arena.GetStats()->detectLeaks = false; - - void* p = arena.Alloc(16); - new (p) TypeOfSize<16>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(40); - - void* p2 = arena.Alloc(16); - new (p2) TypeOfSize<16>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(16); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - - void* p3 = arena.Alloc(8); - new (p3) TypeOfSize<8>(); - Expect(p3).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(0); - - // No space left, no free slots - Expect(arena.GetFreeSlots().Size()).ToEqual(0); - - arena.Free(p, 16); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - - arena.Free(p3, 8); - Expect(arena.GetFreeSlots().Size()).ToEqual(2); - - arena.Free(p2, 16); // Slots previous and next are merged - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - - // Slot contains the entire memory block - auto slot = arena.GetFreeSlots()[0]; - u8* slotStart = (u8*)arena.GetBlock().data + slot.offset; - Expect(slotStart).ToEqual(static_cast(arena.GetBlock().data)); - Expect(slotStart + slot.size).ToEqual(static_cast(arena.GetBlock().End())); -}); + It("Can free in between allocations", []() + { + BigBestFitArena arena{64}; + arena.GetStats()->detectLeaks = false; + + void* p = arena.Alloc(16); + new (p) TypeOfSize<16>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(40); + + void* p2 = arena.Alloc(16); + new (p2) TypeOfSize<16>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(16); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + void* p3 = arena.Alloc(8); + new (p3) TypeOfSize<8>(); + Expect(p3).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(0); + // No space left, no free slots + Expect(arena.GetFreeSlots().Size()).ToEqual(0); + + arena.Free(p2, 16); + Expect(arena.GetFreeSize()).ToEqual(24); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + auto slot = arena.GetFreeSlots()[0]; + u8* slotStart = (u8*)arena.GetBlock().data + slot.offset; + Expect(slotStart).ToEqual(static_cast(p2) - 8); + Expect(slotStart + slot.size).ToEqual(static_cast(p3) - 8); + }); + + It("Can merge previous and next slots on free", []() + { + BigBestFitArena arena{64}; + arena.GetStats()->detectLeaks = false; -It("Can merge previous slot on free", []() -{ - BigBestFitArena arena{48}; - arena.GetStats()->detectLeaks = false; - - void* p = arena.Alloc(16); - new (p) TypeOfSize<16>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(24); - - void* p2 = arena.Alloc(16); - new (p2) TypeOfSize<16>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(0); - Expect(arena.GetFreeSlots().Size()).ToEqual(0); - - arena.Free(p, 16); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - - arena.Free(p2, 16); // Slot is expanded from the front - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - - // Slot contains the entire memory block - auto slot = arena.GetFreeSlots()[0]; - u8* slotStart = (u8*)arena.GetBlock().data + slot.offset; - Expect(slotStart).ToEqual(static_cast(arena.GetBlock().data)); - Expect(slotStart + slot.size).ToEqual(static_cast(arena.GetBlock().End())); -}); + void* p = arena.Alloc(16); + new (p) TypeOfSize<16>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(40); -It("Can merge next slot on free", []() -{ - BigBestFitArena arena{48}; - arena.GetStats()->detectLeaks = false; - - void* p = arena.Alloc(16); - new (p) TypeOfSize<16>(); - Expect(p).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(24); - - void* p2 = arena.Alloc(16); - new (p2) TypeOfSize<16>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(0); - Expect(arena.GetFreeSlots().Size()).ToEqual(0); - - arena.Free(p2, 16); - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - - arena.Free(p, 16); // Slot is expanded from the back - Expect(arena.GetFreeSlots().Size()).ToEqual(1); - - // Slot contains the entire memory block - auto slot = arena.GetFreeSlots()[0]; - u8* slotStart = (u8*)arena.GetBlock().data + slot.offset; - Expect(slotStart).ToEqual(static_cast(arena.GetBlock().data)); - Expect(slotStart + slot.size).ToEqual(static_cast(arena.GetBlock().End())); -}); + void* p2 = arena.Alloc(16); + new (p2) TypeOfSize<16>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(16); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); -It("Ensures a big alignment leaves a gap", []() -{ - BigBestFitArena arena{128}; - arena.GetStats()->detectLeaks = false; - - // We ensure first allocation aligns the block (just for the test) - void* p = arena.Alloc(8); - new (p) TypeOfSize<8>(); - Expect(arena.GetFreeSize()).ToEqual(112); - - void* p2 = arena.Alloc(8, 64); - new (p2) TypeOfSize<8>(); - Expect(p2).ToNotEqual(nullptr); - Expect(arena.GetFreeSize()).ToEqual(96); - - // Alignment is absolute, so the gap between p and p2 is zero - // when the block base lands on a matching 64B boundary. - const bool hasGap = arena.GetAllocationStart(p2) > arena.GetAllocationEnd(p); - Expect(arena.GetFreeSlots().Size()).ToEqual(hasGap ? 2 : 1); - - // Slot contains the rest if the block - auto slot0 = arena.GetFreeSlots()[0]; - u8* slot0Start = (u8*)arena.GetBlock().data + slot0.offset; - Expect(slot0Start).ToEqual(arena.GetAllocationEnd(p2)); - Expect(slot0Start + slot0.size).ToEqual(static_cast(arena.GetBlock().End())); - - // Slot contains the alignment gap - if (hasGap) + void* p3 = arena.Alloc(8); + new (p3) TypeOfSize<8>(); + Expect(p3).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(0); + + // No space left, no free slots + Expect(arena.GetFreeSlots().Size()).ToEqual(0); + + arena.Free(p, 16); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + arena.Free(p3, 8); + Expect(arena.GetFreeSlots().Size()).ToEqual(2); + + arena.Free(p2, 16); // Slots previous and next are merged + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + // Slot contains the entire memory block + auto slot = arena.GetFreeSlots()[0]; + u8* slotStart = (u8*)arena.GetBlock().data + slot.offset; + Expect(slotStart).ToEqual(static_cast(arena.GetBlock().data)); + Expect(slotStart + slot.size).ToEqual(static_cast(arena.GetBlock().End())); + }); + + It("Can merge previous slot on free", []() { - auto slot1 = arena.GetFreeSlots()[1]; - u8* slot1Start = (u8*)arena.GetBlock().data + slot1.offset; - Expect(slot1Start).ToEqual(arena.GetAllocationEnd(p)); - Expect(slot1Start + slot1.size).ToEqual(arena.GetAllocationStart(p2)); - } -}); + BigBestFitArena arena{48}; + arena.GetStats()->detectLeaks = false; + + void* p = arena.Alloc(16); + new (p) TypeOfSize<16>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(24); + + void* p2 = arena.Alloc(16); + new (p2) TypeOfSize<16>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(0); + Expect(arena.GetFreeSlots().Size()).ToEqual(0); + + arena.Free(p, 16); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + arena.Free(p2, 16); // Slot is expanded from the front + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + // Slot contains the entire memory block + auto slot = arena.GetFreeSlots()[0]; + u8* slotStart = (u8*)arena.GetBlock().data + slot.offset; + Expect(slotStart).ToEqual(static_cast(arena.GetBlock().data)); + Expect(slotStart + slot.size).ToEqual(static_cast(arena.GetBlock().End())); + }); + + It("Can merge next slot on free", []() + { + BigBestFitArena arena{48}; + arena.GetStats()->detectLeaks = false; + + void* p = arena.Alloc(16); + new (p) TypeOfSize<16>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(24); + + void* p2 = arena.Alloc(16); + new (p2) TypeOfSize<16>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(0); + Expect(arena.GetFreeSlots().Size()).ToEqual(0); + + arena.Free(p2, 16); + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + arena.Free(p, 16); // Slot is expanded from the back + Expect(arena.GetFreeSlots().Size()).ToEqual(1); + + // Slot contains the entire memory block + auto slot = arena.GetFreeSlots()[0]; + u8* slotStart = (u8*)arena.GetBlock().data + slot.offset; + Expect(slotStart).ToEqual(static_cast(arena.GetBlock().data)); + Expect(slotStart + slot.size).ToEqual(static_cast(arena.GetBlock().End())); + }); + + It("Ensures a big alignment leaves a gap", []() + { + BigBestFitArena arena{128}; + arena.GetStats()->detectLeaks = false; + + // We ensure first allocation aligns the block (just for the test) + void* p = arena.Alloc(8); + new (p) TypeOfSize<8>(); + Expect(arena.GetFreeSize()).ToEqual(112); + + void* p2 = arena.Alloc(8, 64); + new (p2) TypeOfSize<8>(); + Expect(p2).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(96); + + // Alignment is absolute, so the gap between p and p2 is zero + // when the block base lands on a matching 64B boundary. + const bool hasGap = arena.GetAllocationStart(p2) > arena.GetAllocationEnd(p); + Expect(arena.GetFreeSlots().Size()).ToEqual(hasGap ? 2 : 1); + + // Slot contains the rest if the block + auto slot0 = arena.GetFreeSlots()[0]; + u8* slot0Start = (u8*)arena.GetBlock().data + slot0.offset; + Expect(slot0Start).ToEqual(arena.GetAllocationEnd(p2)); + Expect(slot0Start + slot0.size).ToEqual(static_cast(arena.GetBlock().End())); + + // Slot contains the alignment gap + if (hasGap) + { + auto slot1 = arena.GetFreeSlots()[1]; + u8* slot1Start = (u8*)arena.GetBlock().data + slot1.offset; + Expect(slot1Start).ToEqual(arena.GetAllocationEnd(p)); + Expect(slot1Start + slot1.size).ToEqual(arena.GetAllocationStart(p2)); + } + }); }); diff --git a/Tests/Memory/Memory.spec.cpp b/Tests/Memory/Memory.spec.cpp index f65549fe..c3da6f0b 100644 --- a/Tests/Memory/Memory.spec.cpp +++ b/Tests/Memory/Memory.spec.cpp @@ -1,7 +1,7 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include +#include using namespace p; @@ -55,191 +55,191 @@ struct MoveType Spec("Memory.Operations", []() { -It("Can default construct", []() -{ - // Check that it inits to 0 - bool boolValues[2]{true, true}; // Assign simulated garbage - ConstructItems(boolValues, 2); - Expect(boolValues[0]).ToEqual(false); - Expect(boolValues[1]).ToEqual(false); - - u8 u8Values[2]{34, 45}; // Assign simulated garbage - ConstructItems(u8Values, 2, u8(128)); - Expect(u8Values[0]).ToEqual(128); - Expect(u8Values[1]).ToEqual(128); - - u32 u32Values[2]{34, 45}; // Assign simulated garbage - ConstructItems(u32Values, 2); - Expect(u32Values[0]).ToEqual(0); - Expect(u32Values[1]).ToEqual(0); - - u32* ptrValues[2]{(u32*)1, (u32*)2}; // Assign simulated garbage - ConstructItems(ptrValues, 2); - Expect(ptrValues[0]).ToEqual(nullptr); - Expect(ptrValues[1]).ToEqual(nullptr); - - ConstructedType constructedValues[2]; - constructedValues[0].value = 0.234f; // Assign simulated garbage - constructedValues[1].value = 0.234f; - ConstructItems(constructedValues, 2); - Expect(constructedValues[0].value).ToEqual(0.f); - Expect(constructedValues[1].value).ToEqual(0.f); - - BoolsType boolsValues[2]; - boolsValues[0].value1 = false; // Assign simulated garbage - boolsValues[0].value2 = true; - boolsValues[1].value1 = false; - boolsValues[1].value2 = false; - ConstructItems(boolsValues, 2); - Expect(boolsValues[0].value1).ToEqual(true); - Expect(boolsValues[0].value2).ToEqual(false); - Expect(boolsValues[1].value1).ToEqual(true); - Expect(boolsValues[1].value2).ToEqual(false); -}); - -It("Can value construct", []() -{ - bool boolValues[2]{false, false}; // Assign simulated garbage - ConstructItems(boolValues, 2, true); - Expect(boolValues[0]).ToEqual(true); - Expect(boolValues[1]).ToEqual(true); - - u8 u8Values[2]{34, 45}; // Assign simulated garbage - ConstructItems(u8Values, 2, u8(128)); - Expect(u8Values[0]).ToEqual(128); - Expect(u8Values[1]).ToEqual(128); - - u32 u32Values[2]{34, 45}; // Assign simulated garbage - ConstructItems(u32Values, 2, u32(128)); - Expect(u32Values[0]).ToEqual(128); - Expect(u32Values[1]).ToEqual(128); - - u32* ptrValues[2]{(u32*)1, (u32*)2}; // Assign simulated garbage - ConstructItems(ptrValues, 2, (u32*)32); - Expect(ptrValues[0]).ToEqual((u32*)32); - Expect(ptrValues[1]).ToEqual((u32*)32); - - ConstructedType constructedValues[2]{{0.234f}, {0.234f}}; // Assign simulated garbage - ConstructItems(constructedValues, 2, ConstructedType(1.f)); - Expect(constructedValues[0].value).ToEqual(1.f); - Expect(constructedValues[1].value).ToEqual(1.f); - - BoolsType boolsValues[2]{ - {.value1 = false, .value2 = true}, - {.value1 = false, .value2 = true} - }; // Assign simulated garbage - ConstructItems(boolsValues, 2, BoolsType{.value1 = true, .value2 = true}); - Expect(boolsValues[0].value1).ToEqual(true); - Expect(boolsValues[0].value2).ToEqual(true); - Expect(boolsValues[1].value1).ToEqual(true); - Expect(boolsValues[1].value2).ToEqual(true); -}); - -It("Can copy construct", []() -{ - bool boolValues[2]{false, false}; // Assign simulated garbage - bool srcBoolValues[2]{true, false}; - CopyConstructItems(boolValues, 2, srcBoolValues); - Expect(boolValues[0]).ToEqual(true); - Expect(boolValues[1]).ToEqual(false); - - u8 u8Values[2]{34, 45}; // Assign simulated garbage - u8 srcU8Values[2]{128, 129}; - CopyConstructItems(u8Values, 2, srcU8Values); - Expect(u8Values[0]).ToEqual(128); - Expect(u8Values[1]).ToEqual(129); - - u32 u32Values[2]{34, 45}; // Assign simulated garbage - u32 srcU32Values[2]{128, 129}; - CopyConstructItems(u32Values, 2, srcU32Values); - Expect(u32Values[0]).ToEqual(128); - Expect(u32Values[1]).ToEqual(129); - - u32* ptrValues[2]{(u32*)1, (u32*)2}; // Assign simulated garbage - u32* srcPtrValues[2]{(u32*)34, (u32*)23433}; - CopyConstructItems(ptrValues, 2, srcPtrValues); - Expect(ptrValues[0]).ToEqual((u32*)34); - Expect(ptrValues[1]).ToEqual((u32*)23433); - - ConstructedType constructedValues[2]{{0.234f}, {0.234f}}; // Assign simulated garbage - ConstructedType srcConstructedValues[2]{{1.f}, {2.f}}; - CopyConstructItems(constructedValues, 2, srcConstructedValues); - Expect(constructedValues[0].value).ToEqual(1.f); - Expect(constructedValues[1].value).ToEqual(2.f); - - BoolsType boolsValues[2]{ - {false, true}, - {false, true} - }; // Assign simulated garbage - BoolsType srcBoolsValues[2]{ - {true, false}, - {false, true } - }; - CopyConstructItems(boolsValues, 2, srcBoolsValues); - Expect(boolsValues[0].value1).ToEqual(true); - Expect(boolsValues[0].value2).ToEqual(false); - Expect(boolsValues[1].value1).ToEqual(false); - Expect(boolsValues[1].value2).ToEqual(true); - - CopyType copyValues[2]{5, 6}; // Assign simulated garbage - CopyType srcCopyValues[2]{34, 75}; - CopyConstructItems(copyValues, 2, srcCopyValues); - Expect(copyValues[0].value).ToEqual(34); - Expect(copyValues[1].value).ToEqual(75); -}); - -It("Can move construct", []() -{ - bool boolValues[2]{false, false}; // Assign simulated garbage - bool srcBoolValues[2]{true, false}; - MoveConstructItems(boolValues, 2, srcBoolValues); - Expect(boolValues[0]).ToEqual(true); - Expect(boolValues[1]).ToEqual(false); - - u8 u8Values[2]{34, 45}; // Assign simulated garbage - u8 srcU8Values[2]{128, 129}; - MoveConstructItems(u8Values, 2, srcU8Values); - Expect(u8Values[0]).ToEqual(128); - Expect(u8Values[1]).ToEqual(129); - - u32 u32Values[2]{34, 45}; // Assign simulated garbage - u32 srcU32Values[2]{128, 129}; - MoveConstructItems(u32Values, 2, srcU32Values); - Expect(u32Values[0]).ToEqual(128); - Expect(u32Values[1]).ToEqual(129); - - u32* ptrValues[2]{(u32*)1, (u32*)2}; // Assign simulated garbage - u32* srcPtrValues[2]{(u32*)34, (u32*)23433}; - MoveConstructItems(ptrValues, 2, srcPtrValues); - Expect(ptrValues[0]).ToEqual((u32*)34); - Expect(ptrValues[1]).ToEqual((u32*)23433); - - ConstructedType constructedValues[2]{{0.234f}, {0.234f}}; // Assign simulated garbage - ConstructedType srcConstructedTypeValues[2]{{1.f}, {2.f}}; - MoveConstructItems(constructedValues, 2, srcConstructedTypeValues); - Expect(constructedValues[0].value).ToEqual(1.f); - Expect(constructedValues[1].value).ToEqual(2.f); - - BoolsType boolsValues[2]{ - {.value1 = false, .value2 = true}, - {.value1 = false, .value2 = true} - }; // Assign simulated garbage - BoolsType srcConstructedType2Values[2]{ - {.value1 = true, .value2 = false}, - {.value1 = false, .value2 = true } - }; - MoveConstructItems(boolsValues, 2, srcConstructedType2Values); - Expect(boolsValues[0].value1).ToEqual(true); - Expect(boolsValues[0].value2).ToEqual(false); - Expect(boolsValues[1].value1).ToEqual(false); - Expect(boolsValues[1].value2).ToEqual(true); - - MoveType moveValues[2]{5, 6}; // Assign simulated garbage - MoveType srcMoveValues[2]{34, 75}; - MoveConstructItems(moveValues, 2, srcMoveValues); - Expect(moveValues[0].value).ToEqual(34); - Expect(moveValues[1].value).ToEqual(75); - Expect(srcMoveValues[0].value).ToEqual(0); - Expect(srcMoveValues[1].value).ToEqual(0); -}); + It("Can default construct", []() + { + // Check that it inits to 0 + bool boolValues[2]{true, true}; // Assign simulated garbage + ConstructItems(boolValues, 2); + Expect(boolValues[0]).ToEqual(false); + Expect(boolValues[1]).ToEqual(false); + + u8 u8Values[2]{34, 45}; // Assign simulated garbage + ConstructItems(u8Values, 2, u8(128)); + Expect(u8Values[0]).ToEqual(128); + Expect(u8Values[1]).ToEqual(128); + + u32 u32Values[2]{34, 45}; // Assign simulated garbage + ConstructItems(u32Values, 2); + Expect(u32Values[0]).ToEqual(0); + Expect(u32Values[1]).ToEqual(0); + + u32* ptrValues[2]{(u32*)1, (u32*)2}; // Assign simulated garbage + ConstructItems(ptrValues, 2); + Expect(ptrValues[0]).ToEqual(nullptr); + Expect(ptrValues[1]).ToEqual(nullptr); + + ConstructedType constructedValues[2]; + constructedValues[0].value = 0.234f; // Assign simulated garbage + constructedValues[1].value = 0.234f; + ConstructItems(constructedValues, 2); + Expect(constructedValues[0].value).ToEqual(0.f); + Expect(constructedValues[1].value).ToEqual(0.f); + + BoolsType boolsValues[2]; + boolsValues[0].value1 = false; // Assign simulated garbage + boolsValues[0].value2 = true; + boolsValues[1].value1 = false; + boolsValues[1].value2 = false; + ConstructItems(boolsValues, 2); + Expect(boolsValues[0].value1).ToEqual(true); + Expect(boolsValues[0].value2).ToEqual(false); + Expect(boolsValues[1].value1).ToEqual(true); + Expect(boolsValues[1].value2).ToEqual(false); + }); + + It("Can value construct", []() + { + bool boolValues[2]{false, false}; // Assign simulated garbage + ConstructItems(boolValues, 2, true); + Expect(boolValues[0]).ToEqual(true); + Expect(boolValues[1]).ToEqual(true); + + u8 u8Values[2]{34, 45}; // Assign simulated garbage + ConstructItems(u8Values, 2, u8(128)); + Expect(u8Values[0]).ToEqual(128); + Expect(u8Values[1]).ToEqual(128); + + u32 u32Values[2]{34, 45}; // Assign simulated garbage + ConstructItems(u32Values, 2, u32(128)); + Expect(u32Values[0]).ToEqual(128); + Expect(u32Values[1]).ToEqual(128); + + u32* ptrValues[2]{(u32*)1, (u32*)2}; // Assign simulated garbage + ConstructItems(ptrValues, 2, (u32*)32); + Expect(ptrValues[0]).ToEqual((u32*)32); + Expect(ptrValues[1]).ToEqual((u32*)32); + + ConstructedType constructedValues[2]{{0.234f}, {0.234f}}; // Assign simulated garbage + ConstructItems(constructedValues, 2, ConstructedType(1.f)); + Expect(constructedValues[0].value).ToEqual(1.f); + Expect(constructedValues[1].value).ToEqual(1.f); + + BoolsType boolsValues[2]{ + {.value1 = false, .value2 = true}, + {.value1 = false, .value2 = true} + }; // Assign simulated garbage + ConstructItems(boolsValues, 2, BoolsType{.value1 = true, .value2 = true}); + Expect(boolsValues[0].value1).ToEqual(true); + Expect(boolsValues[0].value2).ToEqual(true); + Expect(boolsValues[1].value1).ToEqual(true); + Expect(boolsValues[1].value2).ToEqual(true); + }); + + It("Can copy construct", []() + { + bool boolValues[2]{false, false}; // Assign simulated garbage + bool srcBoolValues[2]{true, false}; + CopyConstructItems(boolValues, 2, srcBoolValues); + Expect(boolValues[0]).ToEqual(true); + Expect(boolValues[1]).ToEqual(false); + + u8 u8Values[2]{34, 45}; // Assign simulated garbage + u8 srcU8Values[2]{128, 129}; + CopyConstructItems(u8Values, 2, srcU8Values); + Expect(u8Values[0]).ToEqual(128); + Expect(u8Values[1]).ToEqual(129); + + u32 u32Values[2]{34, 45}; // Assign simulated garbage + u32 srcU32Values[2]{128, 129}; + CopyConstructItems(u32Values, 2, srcU32Values); + Expect(u32Values[0]).ToEqual(128); + Expect(u32Values[1]).ToEqual(129); + + u32* ptrValues[2]{(u32*)1, (u32*)2}; // Assign simulated garbage + u32* srcPtrValues[2]{(u32*)34, (u32*)23433}; + CopyConstructItems(ptrValues, 2, srcPtrValues); + Expect(ptrValues[0]).ToEqual((u32*)34); + Expect(ptrValues[1]).ToEqual((u32*)23433); + + ConstructedType constructedValues[2]{{0.234f}, {0.234f}}; // Assign simulated garbage + ConstructedType srcConstructedValues[2]{{1.f}, {2.f}}; + CopyConstructItems(constructedValues, 2, srcConstructedValues); + Expect(constructedValues[0].value).ToEqual(1.f); + Expect(constructedValues[1].value).ToEqual(2.f); + + BoolsType boolsValues[2]{ + {false, true}, + {false, true} + }; // Assign simulated garbage + BoolsType srcBoolsValues[2]{ + {true, false}, + {false, true } + }; + CopyConstructItems(boolsValues, 2, srcBoolsValues); + Expect(boolsValues[0].value1).ToEqual(true); + Expect(boolsValues[0].value2).ToEqual(false); + Expect(boolsValues[1].value1).ToEqual(false); + Expect(boolsValues[1].value2).ToEqual(true); + + CopyType copyValues[2]{5, 6}; // Assign simulated garbage + CopyType srcCopyValues[2]{34, 75}; + CopyConstructItems(copyValues, 2, srcCopyValues); + Expect(copyValues[0].value).ToEqual(34); + Expect(copyValues[1].value).ToEqual(75); + }); + + It("Can move construct", []() + { + bool boolValues[2]{false, false}; // Assign simulated garbage + bool srcBoolValues[2]{true, false}; + MoveConstructItems(boolValues, 2, srcBoolValues); + Expect(boolValues[0]).ToEqual(true); + Expect(boolValues[1]).ToEqual(false); + + u8 u8Values[2]{34, 45}; // Assign simulated garbage + u8 srcU8Values[2]{128, 129}; + MoveConstructItems(u8Values, 2, srcU8Values); + Expect(u8Values[0]).ToEqual(128); + Expect(u8Values[1]).ToEqual(129); + + u32 u32Values[2]{34, 45}; // Assign simulated garbage + u32 srcU32Values[2]{128, 129}; + MoveConstructItems(u32Values, 2, srcU32Values); + Expect(u32Values[0]).ToEqual(128); + Expect(u32Values[1]).ToEqual(129); + + u32* ptrValues[2]{(u32*)1, (u32*)2}; // Assign simulated garbage + u32* srcPtrValues[2]{(u32*)34, (u32*)23433}; + MoveConstructItems(ptrValues, 2, srcPtrValues); + Expect(ptrValues[0]).ToEqual((u32*)34); + Expect(ptrValues[1]).ToEqual((u32*)23433); + + ConstructedType constructedValues[2]{{0.234f}, {0.234f}}; // Assign simulated garbage + ConstructedType srcConstructedTypeValues[2]{{1.f}, {2.f}}; + MoveConstructItems(constructedValues, 2, srcConstructedTypeValues); + Expect(constructedValues[0].value).ToEqual(1.f); + Expect(constructedValues[1].value).ToEqual(2.f); + + BoolsType boolsValues[2]{ + {.value1 = false, .value2 = true}, + {.value1 = false, .value2 = true} + }; // Assign simulated garbage + BoolsType srcConstructedType2Values[2]{ + {.value1 = true, .value2 = false}, + {.value1 = false, .value2 = true } + }; + MoveConstructItems(boolsValues, 2, srcConstructedType2Values); + Expect(boolsValues[0].value1).ToEqual(true); + Expect(boolsValues[0].value2).ToEqual(false); + Expect(boolsValues[1].value1).ToEqual(false); + Expect(boolsValues[1].value2).ToEqual(true); + + MoveType moveValues[2]{5, 6}; // Assign simulated garbage + MoveType srcMoveValues[2]{34, 75}; + MoveConstructItems(moveValues, 2, srcMoveValues); + Expect(moveValues[0].value).ToEqual(34); + Expect(moveValues[1].value).ToEqual(75); + Expect(srcMoveValues[0].value).ToEqual(0); + Expect(srcMoveValues[1].value).ToEqual(0); + }); }); diff --git a/Tests/Memory/MemoryStats.spec.cpp b/Tests/Memory/MemoryStats.spec.cpp index 1bc9d4a8..b6038591 100644 --- a/Tests/Memory/MemoryStats.spec.cpp +++ b/Tests/Memory/MemoryStats.spec.cpp @@ -1,7 +1,7 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include +#include #include #include @@ -24,617 +24,617 @@ static const MemoryStatsEvent* LiveFind(const MemoryStats& s, void* ptr) Spec("Memory.MemoryStats", []() { -Describe("Basic", []() -{ - It("Starts empty", []() - { - MemoryStats s; - s.CollectStats(); - Expect(s.used).ToEqual(0); - Expect(s.totalAllocated).ToEqual(0); - Expect(LiveCount(s)).ToEqual(0); - }); - - It("Tracks a single add", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 64); - s.CollectStats(); - Expect(s.used).ToEqual(64); - Expect(s.totalAllocated).ToEqual(64); - Expect(LiveCount(s)).ToEqual(1); - Expect(LiveFind(s, (void*)0x1000) != nullptr).ToBeTrue(); - Expect(LiveFind(s, (void*)0x1000)->GetSize()).ToEqual(64); - Expect(LiveFind(s, (void*)0x1000)->IsFree()).ToEqual(false); - }); - - It("Tracks add plus free", []() + Describe("Basic", []() { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 64); - s.Remove((void*)0x1000, 64); - s.CollectStats(); - Expect(s.used).ToEqual(0); - Expect(LiveCount(s)).ToEqual(0); - // totalAllocated is cumulative alloc bytes ever. - Expect(s.totalAllocated).ToEqual(64); - }); + It("Starts empty", []() + { + MemoryStats s; + s.CollectStats(); + Expect(s.used).ToEqual(0); + Expect(s.totalAllocated).ToEqual(0); + Expect(LiveCount(s)).ToEqual(0); + }); - It("Tracks multiple adds", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 16); - s.Add((void*)0x2000, 32); - s.Add((void*)0x3000, 64); - s.CollectStats(); - Expect(s.used).ToEqual(16 + 32 + 64); - Expect(s.totalAllocated).ToEqual(16 + 32 + 64); - Expect(LiveCount(s)).ToEqual(3); - }); + It("Tracks a single add", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 64); + s.CollectStats(); + Expect(s.used).ToEqual(64); + Expect(s.totalAllocated).ToEqual(64); + Expect(LiveCount(s)).ToEqual(1); + Expect(LiveFind(s, (void*)0x1000) != nullptr).ToBeTrue(); + Expect(LiveFind(s, (void*)0x1000)->GetSize()).ToEqual(64); + Expect(LiveFind(s, (void*)0x1000)->IsFree()).ToEqual(false); + }); - It("Tracks many adds and frees", []() - { - MemoryStats s; - s.detectLeaks = false; - const sizet N = 100; - TArray buf(N * 16); + It("Tracks add plus free", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 64); + s.Remove((void*)0x1000, 64); + s.CollectStats(); + Expect(s.used).ToEqual(0); + Expect(LiveCount(s)).ToEqual(0); + // totalAllocated is cumulative alloc bytes ever. + Expect(s.totalAllocated).ToEqual(64); + }); - for (sizet i = 0; i < N; ++i) + It("Tracks multiple adds", []() { - s.Add(&buf[i * 16], 16); - } - for (sizet i = 0; i < N; i += 2) + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 16); + s.Add((void*)0x2000, 32); + s.Add((void*)0x3000, 64); + s.CollectStats(); + Expect(s.used).ToEqual(16 + 32 + 64); + Expect(s.totalAllocated).ToEqual(16 + 32 + 64); + Expect(LiveCount(s)).ToEqual(3); + }); + + It("Tracks many adds and frees", []() { - s.Remove(&buf[i * 16], 16); - } - s.CollectStats(); + MemoryStats s; + s.detectLeaks = false; + const sizet N = 100; + TArray buf(N * 16); - Expect(s.used).ToEqual((N / 2) * 16); - Expect(s.totalAllocated).ToEqual(N * 16); - Expect(LiveCount(s)).ToEqual(N / 2); - }); + for (sizet i = 0; i < N; ++i) + { + s.Add(&buf[i * 16], 16); + } + for (sizet i = 0; i < N; i += 2) + { + s.Remove(&buf[i * 16], 16); + } + s.CollectStats(); - It("Ignores double-free", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 64); - s.Remove((void*)0x1000, 64); - s.Remove((void*)0x1000, 64); - s.CollectStats(); - // The second free matches no live alloc and is ignored. - Expect(s.used).ToEqual(0); - Expect(LiveCount(s)).ToEqual(0); - }); + Expect(s.used).ToEqual((N / 2) * 16); + Expect(s.totalAllocated).ToEqual(N * 16); + Expect(LiveCount(s)).ToEqual(N / 2); + }); - It("Ignores free of unknown ptr", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Remove((void*)0xDEAD, 64); - s.CollectStats(); - Expect(s.used).ToEqual(0); - Expect(LiveCount(s)).ToEqual(0); - }); + It("Ignores double-free", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 64); + s.Remove((void*)0x1000, 64); + s.Remove((void*)0x1000, 64); + s.CollectStats(); + // The second free matches no live alloc and is ignored. + Expect(s.used).ToEqual(0); + Expect(LiveCount(s)).ToEqual(0); + }); - It("Records duplicate allocs as UnfreedRealloc", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 64); - s.Add((void*)0x1000, 128); - s.CollectStats(); - // Same ptr twice: the second alloc is an error and the - // live set is left untouched. - Expect(LiveCount(s)).ToEqual(1); - Expect(LiveFind(s, (void*)0x1000)->GetSize()).ToEqual(64); - Expect(s.errors.Size()).ToEqual(1); - Expect(s.errors[0].kind == MemoryStatsErrorType::UnfreedRealloc).ToBeTrue(); - Expect(s.errors[0].event.GetSize()).ToEqual(128); - Expect(s.used).ToEqual(64); - }); + It("Ignores free of unknown ptr", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Remove((void*)0xDEAD, 64); + s.CollectStats(); + Expect(s.used).ToEqual(0); + Expect(LiveCount(s)).ToEqual(0); + }); - It("CheckLeaks always runs when called directly", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 64); - s.CollectStats(); - s.CheckLeaks(); - Expect(LiveCount(s)).ToEqual(1); - Expect(s.used).ToEqual(64); - }); + It("Records duplicate allocs as UnfreedRealloc", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 64); + s.Add((void*)0x1000, 128); + s.CollectStats(); + // Same ptr twice: the second alloc is an error and the + // live set is left untouched. + Expect(LiveCount(s)).ToEqual(1); + Expect(LiveFind(s, (void*)0x1000)->GetSize()).ToEqual(64); + Expect(s.errors.Size()).ToEqual(1); + Expect(s.errors[0].kind == MemoryStatsErrorType::UnfreedRealloc).ToBeTrue(); + Expect(s.errors[0].event.GetSize()).ToEqual(128); + Expect(s.used).ToEqual(64); + }); - It("Always tracks frees (no trackFrees flag)", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 64); - s.Remove((void*)0x1000, 64); - s.CollectStats(); - Expect(s.used).ToEqual(0); - Expect(LiveCount(s)).ToEqual(0); - }); + It("CheckLeaks always runs when called directly", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 64); + s.CollectStats(); + s.CheckLeaks(); + Expect(LiveCount(s)).ToEqual(1); + Expect(s.used).ToEqual(64); + }); - It("CheckLeaks with null name does not crash", []() - { + It("Always tracks frees (no trackFrees flag)", []() { - // detectLeaks defaults to true and name defaults to null. MemoryStats s; + s.detectLeaks = false; s.Add((void*)0x1000, 64); + s.Remove((void*)0x1000, 64); s.CollectStats(); - // Destructor runs CheckLeaks with leaks and a null name. - } - }); + Expect(s.used).ToEqual(0); + Expect(LiveCount(s)).ToEqual(0); + }); - It("live list only keeps unmatched allocs", []() - { - MemoryStats s; - s.detectLeaks = false; - // allocs: 2 live, 1 matched. frees: 2 (one matches, one stray). - s.Add((void*)0x1000, 64); - s.Add((void*)0x2000, 32); - s.Add((void*)0x3000, 16); - s.Remove((void*)0x3000, 16); - s.Remove((void*)0xDEAD, 16); - s.CollectStats(); - - Expect(LiveCount(s)).ToEqual(2); - Expect(LiveFind(s, (void*)0x1000)->GetSize()).ToEqual(64); - Expect(LiveFind(s, (void*)0x2000)->GetSize()).ToEqual(32); - - // Re-collecting must preserve the live list identically. - s.CollectStats(); - Expect(LiveCount(s)).ToEqual(2); - Expect(s.used).ToEqual(64 + 32); - }); + It("CheckLeaks with null name does not crash", []() + { + { + // detectLeaks defaults to true and name defaults to null. + MemoryStats s; + s.Add((void*)0x1000, 64); + s.CollectStats(); + // Destructor runs CheckLeaks with leaks and a null name. + } + }); - It("Alternating instances on one thread", []() - { - // Exercises thread context reuse when the owner switches. - MemoryStats a; - MemoryStats b; - a.detectLeaks = false; - b.detectLeaks = false; - - a.Add((void*)0x1000, 64); - b.Add((void*)0x2000, 32); - a.Add((void*)0x3000, 16); - b.Remove((void*)0x2000, 32); - - a.CollectStats(); - b.CollectStats(); - - Expect(a.used).ToEqual(64 + 16); - Expect(LiveCount(a)).ToEqual(2); - Expect(b.used).ToEqual(0); - Expect(LiveCount(b)).ToEqual(0); - }); + It("live list only keeps unmatched allocs", []() + { + MemoryStats s; + s.detectLeaks = false; + // allocs: 2 live, 1 matched. frees: 2 (one matches, one stray). + s.Add((void*)0x1000, 64); + s.Add((void*)0x2000, 32); + s.Add((void*)0x3000, 16); + s.Remove((void*)0x3000, 16); + s.Remove((void*)0xDEAD, 16); + s.CollectStats(); - It("Add after Reset works", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 64); - s.Reset(); - Expect(LiveCount(s)).ToEqual(0); - - s.Add((void*)0x2000, 32); - s.CollectStats(); - Expect(s.used).ToEqual(32); - Expect(s.totalAllocated).ToEqual(32); - Expect(LiveCount(s)).ToEqual(1); - }); + Expect(LiveCount(s)).ToEqual(2); + Expect(LiveFind(s, (void*)0x1000)->GetSize()).ToEqual(64); + Expect(LiveFind(s, (void*)0x2000)->GetSize()).ToEqual(32); - It("Duplicate allocs record UnfreedRealloc and live stays usable", []() - { - MemoryStats s; - s.detectLeaks = false; - - // Collect 1: two allocs sharing the same ptr. The second is - // an UnfreedRealloc error; the live set keeps only the first. - s.Add((void*)0x1000, 64); - s.Add((void*)0x1000, 64); - s.CollectStats(); - Expect(LiveCount(s)).ToEqual(1); - Expect(s.errors.Size()).ToEqual(1); - Expect(s.errors[0].kind == MemoryStatsErrorType::UnfreedRealloc).ToBeTrue(); - Expect(s.used).ToEqual(64); - - // Collect 2: freeing the original alloc still works. - s.Remove((void*)0x1000, 64); - s.CollectStats(); - Expect(LiveCount(s)).ToEqual(0); - Expect(s.used).ToEqual(0); - }); + // Re-collecting must preserve the live list identically. + s.CollectStats(); + Expect(LiveCount(s)).ToEqual(2); + Expect(s.used).ToEqual(64 + 32); + }); - It("Free with wrong size records SizeMismatch", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 64); - s.Remove((void*)0x1000, 32); // size mismatch - s.CollectStats(); - Expect(LiveCount(s)).ToEqual(1); - Expect(s.errors.Size()).ToEqual(1); - Expect(s.errors[0].kind == MemoryStatsErrorType::SizeMismatch).ToBeTrue(); - Expect(s.errors[0].event.GetSize()).ToEqual(32); - Expect(s.used).ToEqual(64); - - // Correcting the size frees the alloc normally. - s.Remove((void*)0x1000, 64); - s.CollectStats(); - Expect(LiveCount(s)).ToEqual(0); - Expect(s.used).ToEqual(0); - }); + It("Alternating instances on one thread", []() + { + // Exercises thread context reuse when the owner switches. + MemoryStats a; + MemoryStats b; + a.detectLeaks = false; + b.detectLeaks = false; + + a.Add((void*)0x1000, 64); + b.Add((void*)0x2000, 32); + a.Add((void*)0x3000, 16); + b.Remove((void*)0x2000, 32); + + a.CollectStats(); + b.CollectStats(); + + Expect(a.used).ToEqual(64 + 16); + Expect(LiveCount(a)).ToEqual(2); + Expect(b.used).ToEqual(0); + Expect(LiveCount(b)).ToEqual(0); + }); - It("Free of unknown ptr records UnknownFree", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 64); - s.Remove((void*)0xDEAD, 64); - s.CollectStats(); - Expect(LiveCount(s)).ToEqual(1); - Expect(s.errors.Size()).ToEqual(1); - Expect(s.errors[0].kind == MemoryStatsErrorType::UnknownFree).ToBeTrue(); - Expect(s.errors[0].event.GetPtr()).ToEqual((u8*)0xDEAD); - Expect(s.used).ToEqual(64); - }); + It("Add after Reset works", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 64); + s.Reset(); + Expect(LiveCount(s)).ToEqual(0); - It("Ignores null ptr in Remove", []() - { - MemoryStats s; - s.Remove(nullptr, 64); - s.CollectStats(); - Expect(s.used).ToEqual(0); - }); + s.Add((void*)0x2000, 32); + s.CollectStats(); + Expect(s.used).ToEqual(32); + Expect(s.totalAllocated).ToEqual(32); + Expect(LiveCount(s)).ToEqual(1); + }); - It("Ignores null ptr in Add", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Add(nullptr, 64); - s.CollectStats(); - // Add has no null check (unlike Remove), so the event is - // recorded and processed. Add's size is still tracked. - Expect(s.used).ToEqual(64); - Expect(LiveCount(s)).ToEqual(1); - }); + It("Duplicate allocs record UnfreedRealloc and live stays usable", []() + { + MemoryStats s; + s.detectLeaks = false; - It("Reset resets state", []() - { - MemoryStats s; - s.Add((void*)0x1000, 64); - s.Add((void*)0x2000, 32); - s.CollectStats(); - Expect(s.used).ToEqual(96); - - s.Reset(); - Expect(s.used).ToEqual(0); - Expect(s.totalAllocated).ToEqual(0); - Expect(LiveCount(s)).ToEqual(0); - }); + // Collect 1: two allocs sharing the same ptr. The second is + // an UnfreedRealloc error; the live set keeps only the first. + s.Add((void*)0x1000, 64); + s.Add((void*)0x1000, 64); + s.CollectStats(); + Expect(LiveCount(s)).ToEqual(1); + Expect(s.errors.Size()).ToEqual(1); + Expect(s.errors[0].kind == MemoryStatsErrorType::UnfreedRealloc).ToBeTrue(); + Expect(s.used).ToEqual(64); - It("CollectStats is additive", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 64); - s.CollectStats(); - s.Add((void*)0x2000, 32); - s.CollectStats(); - Expect(s.used).ToEqual(96); - Expect(LiveCount(s)).ToEqual(2); - }); + // Collect 2: freeing the original alloc still works. + s.Remove((void*)0x1000, 64); + s.CollectStats(); + Expect(LiveCount(s)).ToEqual(0); + Expect(s.used).ToEqual(0); + }); - It("Re-collecting preserves state", []() - { - MemoryStats s; - s.detectLeaks = false; - s.Add((void*)0x1000, 64); - s.CollectStats(); - s.CollectStats(); - Expect(s.used).ToEqual(64); - Expect(LiveCount(s)).ToEqual(1); - }); -}); + It("Free with wrong size records SizeMismatch", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 64); + s.Remove((void*)0x1000, 32); // size mismatch + s.CollectStats(); + Expect(LiveCount(s)).ToEqual(1); + Expect(s.errors.Size()).ToEqual(1); + Expect(s.errors[0].kind == MemoryStatsErrorType::SizeMismatch).ToBeTrue(); + Expect(s.errors[0].event.GetSize()).ToEqual(32); + Expect(s.used).ToEqual(64); + + // Correcting the size frees the alloc normally. + s.Remove((void*)0x1000, 64); + s.CollectStats(); + Expect(LiveCount(s)).ToEqual(0); + Expect(s.used).ToEqual(0); + }); + It("Free of unknown ptr records UnknownFree", []() + { + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 64); + s.Remove((void*)0xDEAD, 64); + s.CollectStats(); + Expect(LiveCount(s)).ToEqual(1); + Expect(s.errors.Size()).ToEqual(1); + Expect(s.errors[0].kind == MemoryStatsErrorType::UnknownFree).ToBeTrue(); + Expect(s.errors[0].event.GetPtr()).ToEqual((u8*)0xDEAD); + Expect(s.used).ToEqual(64); + }); -Describe("Multiple chunks", []() -{ - It("Spans multiple chunks correctly", []() - { - MemoryStats s; - s.detectLeaks = false; - const sizet N = 10000; - TArray buf(N * 8); - for (sizet i = 0; i < N; ++i) + It("Ignores null ptr in Remove", []() { - s.Add(&buf[i * 8], 8); - } - s.CollectStats(); - Expect(s.used).ToEqual(N * 8); - Expect(s.totalAllocated).ToEqual(N * 8); - Expect(LiveCount(s)).ToEqual(N); - }); + MemoryStats s; + s.Remove(nullptr, 64); + s.CollectStats(); + Expect(s.used).ToEqual(0); + }); - It("Handles add/free across chunks", []() - { - MemoryStats s; - s.detectLeaks = false; - const sizet N = 5000; - TArray buf(N * 8); - for (sizet i = 0; i < N; ++i) + It("Ignores null ptr in Add", []() { - s.Add(&buf[i * 8], 8); - } - for (sizet i = 0; i < N / 2; ++i) + MemoryStats s; + s.detectLeaks = false; + s.Add(nullptr, 64); + s.CollectStats(); + // Add has no null check (unlike Remove), so the event is + // recorded and processed. Add's size is still tracked. + Expect(s.used).ToEqual(64); + Expect(LiveCount(s)).ToEqual(1); + }); + + It("Reset resets state", []() { - s.Remove(&buf[i * 8], 8); - } - s.CollectStats(); - Expect(s.used).ToEqual((N / 2) * 8); - Expect(s.totalAllocated).ToEqual(N * 8); - Expect(LiveCount(s)).ToEqual(N / 2); - }); + MemoryStats s; + s.Add((void*)0x1000, 64); + s.Add((void*)0x2000, 32); + s.CollectStats(); + Expect(s.used).ToEqual(96); - It("Frees chunks between CollectStats calls", []() - { - MemoryStats s; - s.detectLeaks = false; - const sizet N = 10000; - TArray buf(N * 8); - for (sizet i = 0; i < N; ++i) + s.Reset(); + Expect(s.used).ToEqual(0); + Expect(s.totalAllocated).ToEqual(0); + Expect(LiveCount(s)).ToEqual(0); + }); + + It("CollectStats is additive", []() { - s.Add(&buf[i * 8], 8); - } - s.CollectStats(); - Expect(LiveCount(s)).ToEqual(N); - for (sizet i = 0; i < N / 2; ++i) + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 64); + s.CollectStats(); + s.Add((void*)0x2000, 32); + s.CollectStats(); + Expect(s.used).ToEqual(96); + Expect(LiveCount(s)).ToEqual(2); + }); + + It("Re-collecting preserves state", []() { - s.Remove(&buf[i * 8], 8); - } - s.CollectStats(); - Expect(LiveCount(s)).ToEqual(N / 2); - Expect(s.used).ToEqual((N / 2) * 8); + MemoryStats s; + s.detectLeaks = false; + s.Add((void*)0x1000, 64); + s.CollectStats(); + s.CollectStats(); + Expect(s.used).ToEqual(64); + Expect(LiveCount(s)).ToEqual(1); + }); }); -}); -Describe("Multithreading", []() -{ - It("One thread adds, another collects", []() + Describe("Multiple chunks", []() { - MemoryStats s; - const sizet N = 1000; - TArray buf(N * 8); - std::atomic start{false}; - std::atomic producerDone{false}; - - std::thread producer([&]() + It("Spans multiple chunks correctly", []() { - while (!start.load(std::memory_order_acquire)) - {} + MemoryStats s; + s.detectLeaks = false; + const sizet N = 10000; + TArray buf(N * 8); for (sizet i = 0; i < N; ++i) { s.Add(&buf[i * 8], 8); } - producerDone.store(true, std::memory_order_release); + s.CollectStats(); + Expect(s.used).ToEqual(N * 8); + Expect(s.totalAllocated).ToEqual(N * 8); + Expect(LiveCount(s)).ToEqual(N); }); - std::thread consumer([&]() + It("Handles add/free across chunks", []() { - while (!start.load(std::memory_order_acquire)) - {} - while (!producerDone.load(std::memory_order_acquire) || LiveCount(s) < N) + MemoryStats s; + s.detectLeaks = false; + const sizet N = 5000; + TArray buf(N * 8); + for (sizet i = 0; i < N; ++i) { - s.CollectStats(); - std::this_thread::yield(); + s.Add(&buf[i * 8], 8); + } + for (sizet i = 0; i < N / 2; ++i) + { + s.Remove(&buf[i * 8], 8); } + s.CollectStats(); + Expect(s.used).ToEqual((N / 2) * 8); + Expect(s.totalAllocated).ToEqual(N * 8); + Expect(LiveCount(s)).ToEqual(N / 2); }); - start.store(true, std::memory_order_release); - producer.join(); - consumer.join(); - - Expect(LiveCount(s)).ToEqual(N); - Expect(s.used).ToEqual(N * 8); - - // Suppress leak warnings at destruction (test buffers are stack). - s.Reset(); + It("Frees chunks between CollectStats calls", []() + { + MemoryStats s; + s.detectLeaks = false; + const sizet N = 10000; + TArray buf(N * 8); + for (sizet i = 0; i < N; ++i) + { + s.Add(&buf[i * 8], 8); + } + s.CollectStats(); + Expect(LiveCount(s)).ToEqual(N); + for (sizet i = 0; i < N / 2; ++i) + { + s.Remove(&buf[i * 8], 8); + } + s.CollectStats(); + Expect(LiveCount(s)).ToEqual(N / 2); + Expect(s.used).ToEqual((N / 2) * 8); + }); }); - It("Many threads add, then collects", []() - { - MemoryStats s; - const sizet N_PER_THREAD = 1000; - const sizet NUM_THREADS = 4; - const sizet N = N_PER_THREAD * NUM_THREADS; - TArray, 0> buffers; - for (sizet t = 0; t < NUM_THREADS; ++t) + Describe("Multithreading", []() + { + It("One thread adds, another collects", []() { - TArray buf(N_PER_THREAD * 8); - buffers.Add(Move(buf)); - } + MemoryStats s; + const sizet N = 1000; + TArray buf(N * 8); + std::atomic start{false}; + std::atomic producerDone{false}; - std::atomic start{false}; - std::atomic producersDone{0}; - std::vector producers; + std::thread producer([&]() + { + while (!start.load(std::memory_order_acquire)) + {} + for (sizet i = 0; i < N; ++i) + { + s.Add(&buf[i * 8], 8); + } + producerDone.store(true, std::memory_order_release); + }); - for (sizet t = 0; t < NUM_THREADS; ++t) - { - producers.emplace_back([&, t]() + std::thread consumer([&]() { while (!start.load(std::memory_order_acquire)) {} - for (sizet i = 0; i < N_PER_THREAD; ++i) + while (!producerDone.load(std::memory_order_acquire) || LiveCount(s) < N) { - s.Add(&buffers[t][i * 8], 8); + s.CollectStats(); + std::this_thread::yield(); } - producersDone.fetch_add(1, std::memory_order_release); }); - } - std::thread consumer([&]() + start.store(true, std::memory_order_release); + producer.join(); + consumer.join(); + + Expect(LiveCount(s)).ToEqual(N); + Expect(s.used).ToEqual(N * 8); + + // Suppress leak warnings at destruction (test buffers are stack). + s.Reset(); + }); + + It("Many threads add, then collects", []() { - while (!start.load(std::memory_order_acquire)) - {} - while (producersDone.load(std::memory_order_acquire) < NUM_THREADS) + MemoryStats s; + const sizet N_PER_THREAD = 1000; + const sizet NUM_THREADS = 4; + const sizet N = N_PER_THREAD * NUM_THREADS; + + TArray, 0> buffers; + for (sizet t = 0; t < NUM_THREADS; ++t) { - s.CollectStats(); - std::this_thread::yield(); + TArray buf(N_PER_THREAD * 8); + buffers.Add(Move(buf)); } - s.CollectStats(); - }); - start.store(true, std::memory_order_release); - for (auto& t : producers) - { - t.join(); - } - consumer.join(); + std::atomic start{false}; + std::atomic producersDone{0}; + std::vector producers; - Expect(LiveCount(s)).ToEqual(N); - Expect(s.used).ToEqual(N * 8); - Expect(s.totalAllocated).ToEqual(N * 8); + for (sizet t = 0; t < NUM_THREADS; ++t) + { + producers.emplace_back([&, t]() + { + while (!start.load(std::memory_order_acquire)) + {} + for (sizet i = 0; i < N_PER_THREAD; ++i) + { + s.Add(&buffers[t][i * 8], 8); + } + producersDone.fetch_add(1, std::memory_order_release); + }); + } - // Suppress leak warnings at destruction (test buffers are stack). - s.Reset(); - }); + std::thread consumer([&]() + { + while (!start.load(std::memory_order_acquire)) + {} + while (producersDone.load(std::memory_order_acquire) < NUM_THREADS) + { + s.CollectStats(); + std::this_thread::yield(); + } + s.CollectStats(); + }); - It("Many threads add and remove, then collects", []() - { - MemoryStats s; - const sizet N_PER_THREAD = 1000; - const sizet NUM_THREADS = 4; - const sizet N = N_PER_THREAD * NUM_THREADS; + start.store(true, std::memory_order_release); + for (auto& t : producers) + { + t.join(); + } + consumer.join(); - TArray, 0> buffers; - for (sizet t = 0; t < NUM_THREADS; ++t) - { - TArray buf(N_PER_THREAD * 8); - buffers.Add(Move(buf)); - } + Expect(LiveCount(s)).ToEqual(N); + Expect(s.used).ToEqual(N * 8); + Expect(s.totalAllocated).ToEqual(N * 8); - std::atomic start{false}; - std::atomic producersDone{0}; - std::vector producers; + // Suppress leak warnings at destruction (test buffers are stack). + s.Reset(); + }); - for (sizet t = 0; t < NUM_THREADS; ++t) + It("Many threads add and remove, then collects", []() { - producers.emplace_back([&, t]() + MemoryStats s; + const sizet N_PER_THREAD = 1000; + const sizet NUM_THREADS = 4; + const sizet N = N_PER_THREAD * NUM_THREADS; + + TArray, 0> buffers; + for (sizet t = 0; t < NUM_THREADS; ++t) + { + TArray buf(N_PER_THREAD * 8); + buffers.Add(Move(buf)); + } + + std::atomic start{false}; + std::atomic producersDone{0}; + std::vector producers; + + for (sizet t = 0; t < NUM_THREADS; ++t) + { + producers.emplace_back([&, t]() + { + while (!start.load(std::memory_order_acquire)) + {} + for (sizet i = 0; i < N_PER_THREAD; ++i) + { + s.Add(&buffers[t][i * 8], 8); + } + // Free the first half. + for (sizet i = 0; i < N_PER_THREAD / 2; ++i) + { + s.Remove(&buffers[t][i * 8], 8); + } + producersDone.fetch_add(1, std::memory_order_release); + }); + } + + std::thread consumer([&]() { while (!start.load(std::memory_order_acquire)) {} - for (sizet i = 0; i < N_PER_THREAD; ++i) - { - s.Add(&buffers[t][i * 8], 8); - } - // Free the first half. - for (sizet i = 0; i < N_PER_THREAD / 2; ++i) + while (producersDone.load(std::memory_order_acquire) < NUM_THREADS) { - s.Remove(&buffers[t][i * 8], 8); + s.CollectStats(); + std::this_thread::yield(); } - producersDone.fetch_add(1, std::memory_order_release); + s.CollectStats(); }); - } - std::thread consumer([&]() - { - while (!start.load(std::memory_order_acquire)) - {} - while (producersDone.load(std::memory_order_acquire) < NUM_THREADS) + start.store(true, std::memory_order_release); + for (auto& t : producers) { - s.CollectStats(); - std::this_thread::yield(); + t.join(); } - s.CollectStats(); - }); - - start.store(true, std::memory_order_release); - for (auto& t : producers) - { - t.join(); - } - consumer.join(); + consumer.join(); - Expect(LiveCount(s)).ToEqual(N / 2); - Expect(s.used).ToEqual((N / 2) * 8); - Expect(s.totalAllocated).ToEqual(N * 8); + Expect(LiveCount(s)).ToEqual(N / 2); + Expect(s.used).ToEqual((N / 2) * 8); + Expect(s.totalAllocated).ToEqual(N * 8); - // Suppress leak warnings at destruction (test buffers are stack). - s.Reset(); + // Suppress leak warnings at destruction (test buffers are stack). + s.Reset(); + }); }); -}); -Describe("Heavy stress", []() -{ - It("Many producers, many iterations, no crashes", []() + Describe("Heavy stress", []() { - MemoryStats s; - const sizet N_PER_THREAD = 2000; - const sizet NUM_THREADS = 4; - const sizet N = N_PER_THREAD * NUM_THREADS; - - TArray, 0> buffers; - for (sizet t = 0; t < NUM_THREADS; ++t) + It("Many producers, many iterations, no crashes", []() { - TArray buf(N_PER_THREAD * 8); - buffers.Add(Move(buf)); - } + MemoryStats s; + const sizet N_PER_THREAD = 2000; + const sizet NUM_THREADS = 4; + const sizet N = N_PER_THREAD * NUM_THREADS; - std::atomic start{false}; - std::atomic producersDone{0}; - std::vector producers; + TArray, 0> buffers; + for (sizet t = 0; t < NUM_THREADS; ++t) + { + TArray buf(N_PER_THREAD * 8); + buffers.Add(Move(buf)); + } - for (sizet t = 0; t < NUM_THREADS; ++t) - { - producers.emplace_back([&, t]() + std::atomic start{false}; + std::atomic producersDone{0}; + std::vector producers; + + for (sizet t = 0; t < NUM_THREADS; ++t) { - while (!start.load(std::memory_order_acquire)) - {} - for (sizet i = 0; i < N_PER_THREAD; ++i) + producers.emplace_back([&, t]() { - s.Add(&buffers[t][i * 8], 8); - if (i > 0 && i % 3 == 0) + while (!start.load(std::memory_order_acquire)) + {} + for (sizet i = 0; i < N_PER_THREAD; ++i) { - s.Remove(&buffers[t][(i - 1) * 8], 8); + s.Add(&buffers[t][i * 8], 8); + if (i > 0 && i % 3 == 0) + { + s.Remove(&buffers[t][(i - 1) * 8], 8); + } } + producersDone.fetch_add(1, std::memory_order_release); + }); + } + + std::thread consumer([&]() + { + while (!start.load(std::memory_order_acquire)) + {} + while (producersDone.load(std::memory_order_acquire) < NUM_THREADS) + { + s.CollectStats(); + std::this_thread::yield(); } - producersDone.fetch_add(1, std::memory_order_release); + s.CollectStats(); }); - } - std::thread consumer([&]() - { - while (!start.load(std::memory_order_acquire)) - {} - while (producersDone.load(std::memory_order_acquire) < NUM_THREADS) + start.store(true, std::memory_order_release); + for (auto& t : producers) { - s.CollectStats(); - std::this_thread::yield(); + t.join(); } - s.CollectStats(); - }); + consumer.join(); - start.store(true, std::memory_order_release); - for (auto& t : producers) - { - t.join(); - } - consumer.join(); - - // s.used reflects the net remaining live set. - Expect(s.used).ToEqual(LiveCount(s) * 8); + // s.used reflects the net remaining live set. + Expect(s.used).ToEqual(LiveCount(s) * 8); - // Suppress leak warnings at destruction (test buffers are stack). - s.Reset(); + // Suppress leak warnings at destruction (test buffers are stack). + s.Reset(); + }); }); }); -}); diff --git a/Tests/Memory/MonoLinearArena.spec.cpp b/Tests/Memory/MonoLinearArena.spec.cpp index 59bbee95..94eeb016 100644 --- a/Tests/Memory/MonoLinearArena.spec.cpp +++ b/Tests/Memory/MonoLinearArena.spec.cpp @@ -1,7 +1,7 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include +#include using namespace p; @@ -9,134 +9,134 @@ using namespace p; Spec("Memory.MonoLinearArena", []() { -It("Reserves a block on construction", []() -{ - MonoLinearArena arena{1024}; - - Expect(arena.GetAvailableMemory()).ToEqual(1024); - arena.GetStats()->CollectStats(); - Expect(arena.GetStats()->used).ToEqual(0); -}); - -It("Can allocate outside the block", []() -{ - MonoLinearArena arena{256}; - - Expect(arena.GetAvailableMemory()).ToEqual(256); - void* p = arena.Alloc(512); - Expect(arena.GetAvailableMemory()).ToEqual(256); - arena.Free(p, 512); -}); - -It("Can free from outside the block", []() -{ - MonoLinearArena arena{256}; - - void* p = arena.Alloc(512); - Expect(arena.GetAvailableMemory()).ToEqual(256); - arena.Free(p, 512); - Expect(arena.GetAvailableMemory()).ToEqual(256); -}); - -It("Can free active block", []() -{ - MonoLinearArena arena{1024}; - arena.Release(); - - TArray blocks; - arena.GetBlocks(blocks); - Expect(blocks.Size()).ToEqual(1); -}); - -It("Can allocate", []() -{ - MonoLinearArena arena{1024}; - void* p = arena.Alloc(sizeof(float)); - Expect(p).ToNotEqual(nullptr); - arena.GetStats()->CollectStats(); - Expect(arena.GetStats()->used).ToEqual(4); - Expect(arena.GetAvailableMemory()).ToEqual(1024); - arena.Free(p, sizeof(float)); -}); - -It("Can allocate with alignment", []() -{ - MonoLinearArena arena{1024}; - - void* p0 = arena.Alloc(sizeof(bool)); - - // When padding is not 0 (last ptr is not aligned) - void* p1 = arena.Alloc(sizeof(float), 8); - Expect(p::GetAlignmentPadding(p1, 8)).ToEqual(0); - - // When padding is 0 (last ptr is aligned) - void* p2 = arena.Alloc(sizeof(float), 16); - Expect(p::GetAlignmentPadding(p2, 16)).ToEqual(0); - - arena.Free(p0, sizeof(bool)); - arena.Free(p1, sizeof(float)); - arena.Free(p2, sizeof(float)); -}); - -It("Can allocate after release", []() -{ - MonoLinearArena arena{1024}; - arena.Release(); - void* p = arena.Alloc(sizeof(float)); - Expect(p).ToNotEqual(nullptr); - arena.GetStats()->CollectStats(); - Expect(arena.GetStats()->used).ToEqual(4); - // Buffer size will be as small as the type (4 bytes) - Expect(arena.GetAvailableMemory()).ToEqual(1024); - - arena.Free(p, sizeof(float)); -}); - -It("Can free block after Free", []() -{ - MonoLinearArena arena{1024}; - void* p = arena.Alloc(256); - arena.GetStats()->CollectStats(); - Expect(arena.GetStats()->used).ToEqual(256); - arena.Free(p, 256); - arena.GetStats()->CollectStats(); - Expect(arena.GetStats()->used).ToEqual(0); -}); - -It("Allocates at correct addresses", []() -{ - MonoLinearArena arena{1024}; - - TArray blocks; - arena.GetBlocks(blocks); - - void* p1 = arena.Alloc(sizeof(float)); - Expect(p1).ToEqual(blocks[0].data); - void* p2 = arena.Alloc(sizeof(float), alignof(float)); - Expect(p2).ToEqual((u8*)blocks[0].data + 4); - - arena.Free(p1, sizeof(float)); - arena.Free(p2, sizeof(float)); -}); - -// Move test to Multi linear -/*It("Allocated new blocks when previous is filled", []() { - MonoLinearArena arena{16}; - - void* p = arena.Alloc(sizeof(float*)); // 8 bytes - arena.Alloc(sizeof(float)); // 4 bytes - Expect(arena.GetStats()->used).ToEqual(12); - Expect(arena.GetAvailableMemory()).ToEqual(16); - - void* p3 = arena.Alloc(sizeof(float*)); // 8 bytes - TArray blocks; - arena.GetBlocks(blocks); - Expect(blocks.Size()).ToEqual(2); - Expect(blocks[0]).ToNotEqual(blocks[1]); - Expect(p).ToEqual(blocks[0].data); - Expect(p3).ToEqual(blocks[1].data); - - Expect(arena.GetStats()->used).ToEqual(8); - Expect(arena.GetAvailableMemory()).ToEqual(16); -});*/ + It("Reserves a block on construction", []() + { + MonoLinearArena arena{1024}; + + Expect(arena.GetAvailableMemory()).ToEqual(1024); + arena.GetStats()->CollectStats(); + Expect(arena.GetStats()->used).ToEqual(0); + }); + + It("Can allocate outside the block", []() + { + MonoLinearArena arena{256}; + + Expect(arena.GetAvailableMemory()).ToEqual(256); + void* p = arena.Alloc(512); + Expect(arena.GetAvailableMemory()).ToEqual(256); + arena.Free(p, 512); + }); + + It("Can free from outside the block", []() + { + MonoLinearArena arena{256}; + + void* p = arena.Alloc(512); + Expect(arena.GetAvailableMemory()).ToEqual(256); + arena.Free(p, 512); + Expect(arena.GetAvailableMemory()).ToEqual(256); + }); + + It("Can free active block", []() + { + MonoLinearArena arena{1024}; + arena.Release(); + + TArray blocks; + arena.GetBlocks(blocks); + Expect(blocks.Size()).ToEqual(1); + }); + + It("Can allocate", []() + { + MonoLinearArena arena{1024}; + void* p = arena.Alloc(sizeof(float)); + Expect(p).ToNotEqual(nullptr); + arena.GetStats()->CollectStats(); + Expect(arena.GetStats()->used).ToEqual(4); + Expect(arena.GetAvailableMemory()).ToEqual(1024); + arena.Free(p, sizeof(float)); + }); + + It("Can allocate with alignment", []() + { + MonoLinearArena arena{1024}; + + void* p0 = arena.Alloc(sizeof(bool)); + + // When padding is not 0 (last ptr is not aligned) + void* p1 = arena.Alloc(sizeof(float), 8); + Expect(p::GetAlignmentPadding(p1, 8)).ToEqual(0); + + // When padding is 0 (last ptr is aligned) + void* p2 = arena.Alloc(sizeof(float), 16); + Expect(p::GetAlignmentPadding(p2, 16)).ToEqual(0); + + arena.Free(p0, sizeof(bool)); + arena.Free(p1, sizeof(float)); + arena.Free(p2, sizeof(float)); + }); + + It("Can allocate after release", []() + { + MonoLinearArena arena{1024}; + arena.Release(); + void* p = arena.Alloc(sizeof(float)); + Expect(p).ToNotEqual(nullptr); + arena.GetStats()->CollectStats(); + Expect(arena.GetStats()->used).ToEqual(4); + // Buffer size will be as small as the type (4 bytes) + Expect(arena.GetAvailableMemory()).ToEqual(1024); + + arena.Free(p, sizeof(float)); + }); + + It("Can free block after Free", []() + { + MonoLinearArena arena{1024}; + void* p = arena.Alloc(256); + arena.GetStats()->CollectStats(); + Expect(arena.GetStats()->used).ToEqual(256); + arena.Free(p, 256); + arena.GetStats()->CollectStats(); + Expect(arena.GetStats()->used).ToEqual(0); + }); + + It("Allocates at correct addresses", []() + { + MonoLinearArena arena{1024}; + + TArray blocks; + arena.GetBlocks(blocks); + + void* p1 = arena.Alloc(sizeof(float)); + Expect(p1).ToEqual(blocks[0].data); + void* p2 = arena.Alloc(sizeof(float), alignof(float)); + Expect(p2).ToEqual((u8*)blocks[0].data + 4); + + arena.Free(p1, sizeof(float)); + arena.Free(p2, sizeof(float)); + }); + + // Move test to Multi linear + /*It("Allocated new blocks when previous is filled", []() { + MonoLinearArena arena{16}; + + void* p = arena.Alloc(sizeof(float*)); // 8 bytes + arena.Alloc(sizeof(float)); // 4 bytes + Expect(arena.GetStats()->used).ToEqual(12); + Expect(arena.GetAvailableMemory()).ToEqual(16); + + void* p3 = arena.Alloc(sizeof(float*)); // 8 bytes + TArray blocks; + arena.GetBlocks(blocks); + Expect(blocks.Size()).ToEqual(2); + Expect(blocks[0]).ToNotEqual(blocks[1]); + Expect(p).ToEqual(blocks[0].data); + Expect(p3).ToEqual(blocks[1].data); + + Expect(arena.GetStats()->used).ToEqual(8); + Expect(arena.GetAvailableMemory()).ToEqual(16); + });*/ }); diff --git a/Tests/PipeTime.spec.cpp b/Tests/PipeTime.spec.cpp index 95721ece..37931de5 100644 --- a/Tests/PipeTime.spec.cpp +++ b/Tests/PipeTime.spec.cpp @@ -7,22 +7,22 @@ using namespace p; - Spec("Time.DateTime", []() +Spec("Time.DateTime", []() +{ + It("Can get day of year", []() { - It("Can get day of year", []() - { - DateTime time1{2024, 1, 1}; - Expect(time1.GetDayOfYear()).ToEqual(1); - DateTime time11{2024, 1, 30}; - Expect(time11.GetDayOfYear()).ToEqual(30); - DateTime time12{2024, 1, 31}; - Expect(time12.GetDayOfYear()).ToEqual(31); + DateTime time1{2024, 1, 1}; + Expect(time1.GetDayOfYear()).ToEqual(1); + DateTime time11{2024, 1, 30}; + Expect(time11.GetDayOfYear()).ToEqual(30); + DateTime time12{2024, 1, 31}; + Expect(time12.GetDayOfYear()).ToEqual(31); - DateTime time2{2024, 2, 1}; - Expect(time2.GetDayOfYear()).ToEqual(32); - DateTime time3{2024, 3, 1}; - Expect(time3.GetDayOfYear()).ToEqual(60); - DateTime time4{2024, 12, 31}; - Expect(time4.GetDayOfYear()).ToEqual(365); - }); + DateTime time2{2024, 2, 1}; + Expect(time2.GetDayOfYear()).ToEqual(32); + DateTime time3{2024, 3, 1}; + Expect(time3.GetDayOfYear()).ToEqual(60); + DateTime time4{2024, 12, 31}; + Expect(time4.GetDayOfYear()).ToEqual(365); }); +}); diff --git a/Tests/Reflection/MacroReflection.spec.cpp b/Tests/Reflection/MacroReflection.spec.cpp index 9bd56fec..1bc7f70e 100644 --- a/Tests/Reflection/MacroReflection.spec.cpp +++ b/Tests/Reflection/MacroReflection.spec.cpp @@ -1,8 +1,8 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include #include +#include using namespace p; @@ -22,18 +22,18 @@ struct TestStruct Spec("Reflection.Macros", []() { -It("Can get property names", []() -{ - p::TypeId testStructType = p::RegisterTypeId(); + It("Can get property names", []() + { + p::TypeId testStructType = p::RegisterTypeId(); - Expect(p::HasTypeFlags(testStructType, p::TF_Struct)).ToEqual(true); + Expect(p::HasTypeFlags(testStructType, p::TF_Struct)).ToEqual(true); - auto properties = p::GetTypeProperties(testStructType); - Expect(properties.Size()).ToEqual(2); + auto properties = p::GetTypeProperties(testStructType); + Expect(properties.Size()).ToEqual(2); - // Expect(properties[0].typeId).ToEqual(p::GetTypeId>()); - Expect(properties[0]->name.Data()).ToEqual("value0"); - // Expect(properties[1].typeId).ToEqual(p::GetTypeId()); - Expect(properties[1]->name.Data()).ToEqual("value1"); -}); + // Expect(properties[0].typeId).ToEqual(p::GetTypeId>()); + Expect(properties[0]->name.Data()).ToEqual("value0"); + // Expect(properties[1].typeId).ToEqual(p::GetTypeId()); + Expect(properties[1]->name.Data()).ToEqual("value1"); + }); }); diff --git a/Tests/Reflection/Object.spec.cpp b/Tests/Reflection/Object.spec.cpp index f36da935..61049f5e 100644 --- a/Tests/Reflection/Object.spec.cpp +++ b/Tests/Reflection/Object.spec.cpp @@ -1,7 +1,7 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include +#include using namespace p; @@ -24,23 +24,23 @@ class TestObject : public p::Object Spec("Reflection.Object", []() { -Describe("Pointers", []() -{ - It("Can create object", []() + Describe("Pointers", []() { - auto owner = p::MakeOwned(); - - Expect(owner.Get()).ToNotEqual(nullptr); - Expect(owner->bConstructed).ToEqual(true); + It("Can create object", []() + { + auto owner = p::MakeOwned(); + + Expect(owner.Get()).ToNotEqual(nullptr); + Expect(owner->bConstructed).ToEqual(true); + }); + + It("Can create object with owner", []() + { + auto owner = p::MakeOwned(); + auto owner2 = p::MakeOwned(owner); + + Expect(owner2->bConstructed).ToEqual(true); + Expect(owner2->GetOwner().Get()).ToEqual(owner.Get()); + }); }); - - It("Can create object with owner", []() - { - auto owner = p::MakeOwned(); - auto owner2 = p::MakeOwned(owner); - - Expect(owner2->bConstructed).ToEqual(true); - Expect(owner2->GetOwner().Get()).ToEqual(owner.Get()); - }); -}); }); diff --git a/Tests/Reflection/Traits.spec.cpp b/Tests/Reflection/Traits.spec.cpp index 8a461c95..0fceeb80 100644 --- a/Tests/Reflection/Traits.spec.cpp +++ b/Tests/Reflection/Traits.spec.cpp @@ -1,9 +1,9 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include #include #include +#include using namespace p; @@ -41,62 +41,62 @@ namespace p Spec("Reflection.Traits", []() { -Describe("Read/Write properties", []() -{ - It("Can check for read properties", []() + Describe("Read/Write properties", []() { - Expect(p::HasReadProperties()).ToBeFalse(); - Expect(p::HasReadProperties()).ToBeTrue(); - Expect(p::Readable).ToBeFalse(); - Expect(p::Readable).ToBeTrue(); + It("Can check for read properties", []() + { + Expect(p::HasReadProperties()).ToBeFalse(); + Expect(p::HasReadProperties()).ToBeTrue(); + Expect(p::Readable).ToBeFalse(); + Expect(p::Readable).ToBeTrue(); + }); + + It("Can check for write properties", []() + { + Expect(p::HasWriteProperties()).ToBeFalse(); + Expect(p::HasWriteProperties()).ToBeTrue(); + Expect(p::Writable).ToBeFalse(); + Expect(p::Writable).ToBeTrue(); + }); }); - It("Can check for write properties", []() + Describe("Read/Write external", []() { - Expect(p::HasWriteProperties()).ToBeFalse(); - Expect(p::HasWriteProperties()).ToBeTrue(); - Expect(p::Writable).ToBeFalse(); - Expect(p::Writable).ToBeTrue(); + It("Can check for read properties", []() + { + Expect(p::Readable).ToBeFalse(); + Expect(p::Readable).ToBeTrue(); + }); + + It("Can check for write properties", []() + { + Expect(p::Writable).ToBeFalse(); + Expect(p::Writable).ToBeTrue(); + }); }); -}); -Describe("Read/Write external", []() -{ - It("Can check for read properties", []() + Describe("Read/Write external in namespace", []() { - Expect(p::Readable).ToBeFalse(); - Expect(p::Readable).ToBeTrue(); + It("Can check for read properties", []() + { + Expect(p::Readable).ToBeTrue(); + }); + + It("Can check for write properties", []() + { + Expect(p::Writable).ToBeTrue(); + }); }); - It("Can check for write properties", []() + It("Can check super", []() { - Expect(p::Writable).ToBeFalse(); - Expect(p::Writable).ToBeTrue(); + Expect(p::HasSuper()).ToBeFalse(); + Expect(p::HasSuper()).ToBeTrue(); }); -}); -Describe("Read/Write external in namespace", []() -{ - It("Can check for read properties", []() + It("Can build type on Arrays", []() { - Expect(p::Readable).ToBeTrue(); + Expect(p::CanBuildType>()).ToBeTrue(); + Expect(p::HasExternalBuildType>()).ToBeTrue(); }); - - It("Can check for write properties", []() - { - Expect(p::Writable).ToBeTrue(); - }); -}); - -It("Can check super", []() -{ - Expect(p::HasSuper()).ToBeFalse(); - Expect(p::HasSuper()).ToBeTrue(); -}); - -It("Can build type on Arrays", []() -{ - Expect(p::CanBuildType>()).ToBeTrue(); - Expect(p::HasExternalBuildType>()).ToBeTrue(); -}); }); diff --git a/Tests/Reflection/TypeId.spec.cpp b/Tests/Reflection/TypeId.spec.cpp index ae309210..2e5df256 100644 --- a/Tests/Reflection/TypeId.spec.cpp +++ b/Tests/Reflection/TypeId.spec.cpp @@ -1,7 +1,7 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include +#include using namespace p; @@ -12,28 +12,28 @@ struct One Spec("Reflection.TypeId", []() { -It("Ids can be valid and invalid", []() -{ - static constexpr TypeId id = GetTypeId(); - Expect(id.IsValid()).ToEqual(true); - - static constexpr TypeId noId{}; - Expect(noId.IsValid()).ToEqual(false); -}); + It("Ids can be valid and invalid", []() + { + static constexpr TypeId id = GetTypeId(); + Expect(id.IsValid()).ToEqual(true); -It("Different types don't share an id", []() -{ - static constexpr TypeId ids[]{ - GetTypeId(), GetTypeId(), GetTypeId(), GetTypeId()}; - static constexpr u32 numIds = sizeof(ids) / sizeof(TypeId); + static constexpr TypeId noId{}; + Expect(noId.IsValid()).ToEqual(false); + }); - // Check that no id matches the other - for (u32 i = 0; i < numIds; ++i) + It("Different types don't share an id", []() { - for (u32 e = i + 1; e < numIds; ++e) + static constexpr TypeId ids[]{ + GetTypeId(), GetTypeId(), GetTypeId(), GetTypeId()}; + static constexpr u32 numIds = sizeof(ids) / sizeof(TypeId); + + // Check that no id matches the other + for (u32 i = 0; i < numIds; ++i) { - Expect(ids[i]).ToNotEqual(ids[e]); + for (u32 e = i + 1; e < numIds; ++e) + { + Expect(ids[i]).ToNotEqual(ids[e]); + } } - } -}); + }); }); diff --git a/Tests/Reflection/TypeName.spec.cpp b/Tests/Reflection/TypeName.spec.cpp index d75ab58c..c672a1f5 100644 --- a/Tests/Reflection/TypeName.spec.cpp +++ b/Tests/Reflection/TypeName.spec.cpp @@ -1,12 +1,12 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include #include #include #include #include #include +#include using namespace p; @@ -27,65 +27,65 @@ namespace Space Spec("Reflection.TypeName", []() { -It("Can get Platform type names", []() -{ - Expect(GetTypeName()).ToEqual("u8"); - Expect(GetTypeName()).ToEqual("u16"); - Expect(GetTypeName()).ToEqual("u32"); - Expect(GetTypeName()).ToEqual("u64"); - Expect(GetTypeName()).ToEqual("i8"); - Expect(GetTypeName()).ToEqual("i16"); - Expect(GetTypeName()).ToEqual("i32"); - Expect(GetTypeName()).ToEqual("i64"); - Expect(GetTypeName()).ToEqual("char"); - Expect(GetTypeName()).ToEqual("StringView"); - Expect(GetTypeName()).ToEqual("String"); -}); - -It("Can get Native type names", []() -{ - Expect(GetTypeName()).ToEqual("bool"); - Expect(GetTypeName()).ToEqual("float"); - Expect(GetTypeName()).ToEqual("double"); -}); - -It("Can get Class names", []() -{ - Expect(GetTypeName()).ToEqual("AClass"); -}); - -It("Can get Struct names", []() -{ - Expect(GetTypeName()).ToEqual("AnStruct"); -}); - -It("Can get names with namespaces", []() -{ - Expect(GetTypeName()).ToEqual("Space::Other"); -}); + It("Can get Platform type names", []() + { + Expect(GetTypeName()).ToEqual("u8"); + Expect(GetTypeName()).ToEqual("u16"); + Expect(GetTypeName()).ToEqual("u32"); + Expect(GetTypeName()).ToEqual("u64"); + Expect(GetTypeName()).ToEqual("i8"); + Expect(GetTypeName()).ToEqual("i16"); + Expect(GetTypeName()).ToEqual("i32"); + Expect(GetTypeName()).ToEqual("i64"); + Expect(GetTypeName()).ToEqual("char"); + Expect(GetTypeName()).ToEqual("StringView"); + Expect(GetTypeName()).ToEqual("String"); + }); -Describe("Containers", []() -{ - It("Can get TArray names", []() + It("Can get Native type names", []() { - Expect(GetTypeName>()).ToEqual("TArray"); - Expect(GetFullTypeName>()).ToEqual("TArray"); - Expect(GetFullTypeName>(false)).ToEqual("TArray"); + Expect(GetTypeName()).ToEqual("bool"); + Expect(GetTypeName()).ToEqual("float"); + Expect(GetTypeName()).ToEqual("double"); }); - It("Can get TMap names", []() + It("Can get Class names", []() { - auto name = GetTypeName>(); - Expect(name).ToEqual("TMap"); + Expect(GetTypeName()).ToEqual("AClass"); + }); - auto fullName = GetFullTypeName>(); - Expect(fullName).ToEqual("TMap"); + It("Can get Struct names", []() + { + Expect(GetTypeName()).ToEqual("AnStruct"); + }); + It("Can get names with namespaces", []() + { + Expect(GetTypeName()).ToEqual("Space::Other"); + }); - auto namespaceName = GetFullTypeName>(); - Expect(namespaceName).ToEqual("TMap"); - auto noNamespaceName = GetFullTypeName>(false); - Expect(noNamespaceName).ToEqual("TMap"); + Describe("Containers", []() + { + It("Can get TArray names", []() + { + Expect(GetTypeName>()).ToEqual("TArray"); + Expect(GetFullTypeName>()).ToEqual("TArray"); + Expect(GetFullTypeName>(false)).ToEqual("TArray"); + }); + + It("Can get TMap names", []() + { + auto name = GetTypeName>(); + Expect(name).ToEqual("TMap"); + + auto fullName = GetFullTypeName>(); + Expect(fullName).ToEqual("TMap"); + + + auto namespaceName = GetFullTypeName>(); + Expect(namespaceName).ToEqual("TMap"); + auto noNamespaceName = GetFullTypeName>(false); + Expect(noNamespaceName).ToEqual("TMap"); + }); }); }); -}); diff --git a/Tests/Serialization/Binary.spec.cpp b/Tests/Serialization/Binary.spec.cpp index 9d401371..b923b33d 100644 --- a/Tests/Serialization/Binary.spec.cpp +++ b/Tests/Serialization/Binary.spec.cpp @@ -1,7 +1,7 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include +#include using namespace p; @@ -9,407 +9,405 @@ using namespace p; Spec("Serialization.Binary", []() { -Describe("Reader", []() -{ - It("Can create a reader", []() - { - BinaryFormatReader reader{TArray{}}; - Expect(reader.IsValid()).ToEqual(false); - - BinaryFormatReader reader2{TArray{255}}; - Expect(reader2.IsValid()).ToEqual(true); - }); - - It("Can read from object value", []() - { - TArray data{255}; - BinaryFormatReader reader{data}; - Reader ct = reader; - ct.BeginObject(); - u8 value = 0; - ct.Next(value); - Expect(value).ToEqual(255); - }); - - It("Can read from array values", []() + Describe("Reader", []() { - TArray data{1, 0, 0, 0, 255}; - BinaryFormatReader reader{data}; - Reader ct = reader; - u32 size = 0; - ct.BeginArray(size); - Expect(size).ToEqual(1); - u8 value = 0; - ct.Next(value); - Expect(value).ToEqual(255); - }); - - It("Can iterate arrays", []() - { - TArray data{2, 0, 0, 0, // Array size of 2 - 6, 0, 0, 0, // size 6 - 'M', 'i', 'g', 'u', 'e', 'l', // - 4, 0, 0, 0, // size 4 - 'J', 'u', 'a', 'n'}; - BinaryFormatReader reader{data}; - - Reader& ct = reader; - ct.BeginObject(); - if (ct.EnterNext("players")) + It("Can create a reader", []() { - static const StringView expected[]{"Miguel", "Juan"}; - u32 size; - ct.BeginArray(size); - for (u32 i = 0; i < size; ++i) - { - StringView name; - ct.Next(name); - Expect(name).ToEqual(expected[i]); - } - ct.Leave(); - } - }); + BinaryFormatReader reader{TArray{}}; + Expect(reader.IsValid()).ToEqual(false); - Describe("Types", []() - { - It("Can read bool values", []() - { - TArray data{1, 0}; - BinaryFormatReader reader{data}; - Reader& ct = reader; - ct.BeginObject(); - bool value = false; - ct.Next("a", value); - Expect(value).ToEqual(true); - ct.Next("b", value); - Expect(value).ToEqual(false); - }); - - It("Can read i8 values", []() - { - TArray data{0, 127, 128}; - BinaryFormatReader reader{data}; - Reader& ct = reader; - ct.BeginObject(); - i8 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(0); - ct.Next("b", value); - Expect(value).ToEqual(127); - ct.Next("b", value); - Expect(value).ToEqual(-128); + BinaryFormatReader reader2{TArray{255}}; + Expect(reader2.IsValid()).ToEqual(true); }); - It("Can read u8 values", []() + It("Can read from object value", []() { - TArray data{0, 255}; + TArray data{255}; BinaryFormatReader reader{data}; - Reader& ct = reader; + Reader ct = reader; ct.BeginObject(); u8 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(0); - ct.Next("b", value); + ct.Next(value); Expect(value).ToEqual(255); }); - It("Can read i16 values", []() - { - // Test inbounds and out of bounds values - TArray data{0, 0, 0, 128, 255, 127}; - BinaryFormatReader reader{data}; - Reader ct = reader; - ct.BeginObject(); - i16 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(0); - ct.Next("b", value); - Expect(value).ToEqual(Limits::Lowest()); - ct.Next("c", value); - Expect(value).ToEqual(Limits::Max()); - }); - - It("Can read u16 values", []() - { - // Test inbounds and out of bounds values - TArray data{0, 0, 255, 255}; - BinaryFormatReader reader{data}; - Reader ct = reader; - ct.BeginObject(); - u16 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(0); - ct.Next("b", value); - Expect(value).ToEqual(Limits::Max()); - }); - - It("Can read i32 values", []() - { - // Test inbounds and out of bounds values - TArray data{0, 0, 0, 0, 0, 0, 0, 128, 255, 255, 255, 127}; - BinaryFormatReader reader{data}; - Reader ct = reader; - ct.BeginObject(); - i32 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(0); - ct.Next("b", value); - Expect(value).ToEqual(Limits::Lowest()); - ct.Next("c", value); - Expect(value).ToEqual(Limits::Max()); - }); - - It("Can read u32 values", []() - { - // Test inbounds and out of bounds values - TArray data{0, 0, 0, 0, 255, 255, 255, 255}; - BinaryFormatReader reader{data}; - Reader ct = reader; - ct.BeginObject(); - u32 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(0); - ct.Next("b", value); - Expect(value).ToEqual(Limits::Max()); - }); - - It("Can read i64 values", []() + It("Can read from array values", []() { - // Test inbounds and out of bounds values - TArray data{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 255, 255, 255, - 255, 255, 255, 255, 127}; + TArray data{1, 0, 0, 0, 255}; BinaryFormatReader reader{data}; Reader ct = reader; - ct.BeginObject(); - i64 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(0); - ct.Next("b", value); - Expect(value).ToEqual(Limits::Lowest()); - ct.Next("c", value); - Expect(value).ToEqual(Limits::Max()); - }); - - It("Can read u64 values", []() - { - // Test inbounds and out of bounds values - TArray data{0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255}; - BinaryFormatReader reader{data}; - Reader ct = reader; - ct.BeginObject(); - u64 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(0); - ct.Next("b", value); - Expect(value).ToEqual(Limits::Max()); + u32 size = 0; + ct.BeginArray(size); + Expect(size).ToEqual(1); + u8 value = 0; + ct.Next(value); + Expect(value).ToEqual(255); }); - It("Can read float values", []() + It("Can iterate arrays", []() { - TArray data{51, 51, 179, 191, 0, 0, 96, 64}; + TArray data{2, 0, 0, 0, // Array size of 2 + 6, 0, 0, 0, // size 6 + 'M', 'i', 'g', 'u', 'e', 'l', // + 4, 0, 0, 0, // size 4 + 'J', 'u', 'a', 'n'}; BinaryFormatReader reader{data}; - Reader ct = reader; - ct.BeginObject(); - float value = 0.f; - ct.Next("a", value); - Expect(value).ToEqual(-1.4f); - ct.Next("b", value); - Expect(value).ToEqual(3.5f); - }); - It("Can read double values", []() - { - TArray data{ - 102, 102, 102, 102, 102, 102, 246, 191, 0, 0, 0, 0, 0, 0, 12, 64}; - BinaryFormatReader reader{data}; - Reader ct = reader; + Reader& ct = reader; ct.BeginObject(); - double value = 0; - ct.Next("a", value); - Expect(value).ToEqual(-1.4); - ct.Next("b", value); - Expect(value).ToEqual(3.5); + if (ct.EnterNext("players")) + { + static const StringView expected[]{"Miguel", "Juan"}; + u32 size; + ct.BeginArray(size); + for (u32 i = 0; i < size; ++i) + { + StringView name; + ct.Next(name); + Expect(name).ToEqual(expected[i]); + } + ct.Leave(); + } }); - It("Can read StringView values", []() + Describe("Types", []() { - TArray data{3, 0, 0, 0, 'y', 'e', 's'}; - BinaryFormatReader reader{data}; - Reader ct = reader; - ct.BeginObject(); - StringView string; - ct.Next("a", string); - Expect(string).ToEqual("yes"); + It("Can read bool values", []() + { + TArray data{1, 0}; + BinaryFormatReader reader{data}; + Reader& ct = reader; + ct.BeginObject(); + bool value = false; + ct.Next("a", value); + Expect(value).ToEqual(true); + ct.Next("b", value); + Expect(value).ToEqual(false); + }); + + It("Can read i8 values", []() + { + TArray data{0, 127, 128}; + BinaryFormatReader reader{data}; + Reader& ct = reader; + ct.BeginObject(); + i8 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(0); + ct.Next("b", value); + Expect(value).ToEqual(127); + ct.Next("b", value); + Expect(value).ToEqual(-128); + }); + + It("Can read u8 values", []() + { + TArray data{0, 255}; + BinaryFormatReader reader{data}; + Reader& ct = reader; + ct.BeginObject(); + u8 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(0); + ct.Next("b", value); + Expect(value).ToEqual(255); + }); + + It("Can read i16 values", []() + { + // Test inbounds and out of bounds values + TArray data{0, 0, 0, 128, 255, 127}; + BinaryFormatReader reader{data}; + Reader ct = reader; + ct.BeginObject(); + i16 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(0); + ct.Next("b", value); + Expect(value).ToEqual(Limits::Lowest()); + ct.Next("c", value); + Expect(value).ToEqual(Limits::Max()); + }); + + It("Can read u16 values", []() + { + // Test inbounds and out of bounds values + TArray data{0, 0, 255, 255}; + BinaryFormatReader reader{data}; + Reader ct = reader; + ct.BeginObject(); + u16 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(0); + ct.Next("b", value); + Expect(value).ToEqual(Limits::Max()); + }); + + It("Can read i32 values", []() + { + // Test inbounds and out of bounds values + TArray data{0, 0, 0, 0, 0, 0, 0, 128, 255, 255, 255, 127}; + BinaryFormatReader reader{data}; + Reader ct = reader; + ct.BeginObject(); + i32 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(0); + ct.Next("b", value); + Expect(value).ToEqual(Limits::Lowest()); + ct.Next("c", value); + Expect(value).ToEqual(Limits::Max()); + }); + + It("Can read u32 values", []() + { + // Test inbounds and out of bounds values + TArray data{0, 0, 0, 0, 255, 255, 255, 255}; + BinaryFormatReader reader{data}; + Reader ct = reader; + ct.BeginObject(); + u32 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(0); + ct.Next("b", value); + Expect(value).ToEqual(Limits::Max()); + }); + + It("Can read i64 values", []() + { + // Test inbounds and out of bounds values + TArray data{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 255, 255, 255, + 255, 255, 255, 255, 127}; + BinaryFormatReader reader{data}; + Reader ct = reader; + ct.BeginObject(); + i64 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(0); + ct.Next("b", value); + Expect(value).ToEqual(Limits::Lowest()); + ct.Next("c", value); + Expect(value).ToEqual(Limits::Max()); + }); + + It("Can read u64 values", []() + { + // Test inbounds and out of bounds values + TArray data{0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255}; + BinaryFormatReader reader{data}; + Reader ct = reader; + ct.BeginObject(); + u64 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(0); + ct.Next("b", value); + Expect(value).ToEqual(Limits::Max()); + }); + + It("Can read float values", []() + { + TArray data{51, 51, 179, 191, 0, 0, 96, 64}; + BinaryFormatReader reader{data}; + Reader ct = reader; + ct.BeginObject(); + float value = 0.f; + ct.Next("a", value); + Expect(value).ToEqual(-1.4f); + ct.Next("b", value); + Expect(value).ToEqual(3.5f); + }); + + It("Can read double values", []() + { + TArray data{102, 102, 102, 102, 102, 102, 246, 191, 0, 0, 0, 0, 0, 0, 12, 64}; + BinaryFormatReader reader{data}; + Reader ct = reader; + ct.BeginObject(); + double value = 0; + ct.Next("a", value); + Expect(value).ToEqual(-1.4); + ct.Next("b", value); + Expect(value).ToEqual(3.5); + }); + + It("Can read StringView values", []() + { + TArray data{3, 0, 0, 0, 'y', 'e', 's'}; + BinaryFormatReader reader{data}; + Reader ct = reader; + ct.BeginObject(); + StringView string; + ct.Next("a", string); + Expect(string).ToEqual("yes"); + }); }); }); -}); - -Describe("Writer", []() -{ - It("Can create a writer", []() - { - BinaryFormatWriter writer{}; - Expect(writer.IsValid()).ToEqual(true); - }); - It("Can write to object key", []() + Describe("Writer", []() { - BinaryFormatWriter writer{}; - Writer& ct = writer; - ct.BeginObject(); - ct.Next("name", StringView{"Miguel"}); - - TArray expected{6, 0, 0, 0, 'M', 'i', 'g', 'u', 'e', 'l'}; - Expect(writer.GetData()).ToEqual(TView{expected}); - }); - - It("Can write arrays", []() - { - BinaryFormatWriter writer{}; - Writer& ct = writer; - ct.BeginArray(2); - ct.Next(u8(255)); - ct.Next(u8(255)); - - TArray expected{2, 0, 0, 0, 255, 255}; - Expect(writer.GetData()).ToEqual(TView{expected}); - }); - - Describe("Types", []() - { - It("Can write bool values", []() + It("Can create a writer", []() { BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", true); - ct.Next("b", false); - TArray expected{1, 0}; - Expect(writer.GetData()).ToEqual(TView(expected)); + Expect(writer.IsValid()).ToEqual(true); }); - It("Can write i8 values", []() + It("Can write to object key", []() { BinaryFormatWriter writer{}; - Writer ct = writer; + Writer& ct = writer; ct.BeginObject(); - ct.Next("a", i8(127)); - ct.Next("b", i8(-128)); - TArray expected{127, 128}; - Expect(writer.GetData()).ToEqual(TView(expected)); - }); + ct.Next("name", StringView{"Miguel"}); - It("Can write u8 values", []() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", u8(0)); - ct.Next("b", u8(255)); - TArray expected{0, 255}; - Expect(writer.GetData()).ToEqual(TView(expected)); + TArray expected{6, 0, 0, 0, 'M', 'i', 'g', 'u', 'e', 'l'}; + Expect(writer.GetData()).ToEqual(TView{expected}); }); - It("Can write i16 values", []() + It("Can write arrays", []() { BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", Limits::Max()); - ct.Next("b", Limits::Lowest()); - TArray expected{255, 127, 0, 128}; - Expect(writer.GetData()).ToEqual(TView(expected)); - }); + Writer& ct = writer; + ct.BeginArray(2); + ct.Next(u8(255)); + ct.Next(u8(255)); - It("Can write u16 values", []() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", Limits::Max()); - ct.Next("b", Limits::Lowest()); - TArray expected{255, 255, 0, 0}; - Expect(writer.GetData()).ToEqual(TView(expected)); + TArray expected{2, 0, 0, 0, 255, 255}; + Expect(writer.GetData()).ToEqual(TView{expected}); }); - It("Can write i32 values", []() + Describe("Types", []() { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", Limits::Max()); - ct.Next("b", Limits::Lowest()); - TArray expected{255, 255, 255, 127, 0, 0, 0, 128}; - Expect(writer.GetData()).ToEqual(TView(expected)); - }); - - It("Can write u32 values", []() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", Limits::Max()); - ct.Next("b", Limits::Lowest()); - TArray expected{255, 255, 255, 255, 0, 0, 0, 0}; - Expect(writer.GetData()).ToEqual(TView(expected)); - }); - - It("Can write i64 values", []() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", Limits::Max()); - ct.Next("b", Limits::Lowest()); - TArray expected{ - 255, 255, 255, 255, 255, 255, 255, 127, 0, 0, 0, 0, 0, 0, 0, 128}; - Expect(writer.GetData()).ToEqual(TView(expected)); - }); - - It("Can write u64 values", []() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", Limits::Max()); - ct.Next("b", Limits::Lowest()); - TArray expected{ - 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}; - Expect(writer.GetData()).ToEqual(TView(expected)); - }); - - It("Can write float values", []() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", -1.4f); - ct.Next("b", 3.5f); - TArray expected{51, 51, 179, 191, 0, 0, 96, 64}; - Expect(writer.GetData()).ToEqual(TView(expected)); - }); - - It("Can write double values", []() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", -1.4); - ct.Next("b", 3.5); - TArray expected{ - 102, 102, 102, 102, 102, 102, 246, 191, 0, 0, 0, 0, 0, 0, 12, 64}; - Expect(writer.GetData()).ToEqual(TView(expected)); - }); - - It("Can write StringView values", []() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", StringView{"yes"}); - TArray expected{3, 0, 0, 0, 'y', 'e', 's'}; - Expect(writer.GetData()).ToEqual(TView(expected)); + It("Can write bool values", []() + { + BinaryFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", true); + ct.Next("b", false); + TArray expected{1, 0}; + Expect(writer.GetData()).ToEqual(TView(expected)); + }); + + It("Can write i8 values", []() + { + BinaryFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", i8(127)); + ct.Next("b", i8(-128)); + TArray expected{127, 128}; + Expect(writer.GetData()).ToEqual(TView(expected)); + }); + + It("Can write u8 values", []() + { + BinaryFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", u8(0)); + ct.Next("b", u8(255)); + TArray expected{0, 255}; + Expect(writer.GetData()).ToEqual(TView(expected)); + }); + + It("Can write i16 values", []() + { + BinaryFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", Limits::Max()); + ct.Next("b", Limits::Lowest()); + TArray expected{255, 127, 0, 128}; + Expect(writer.GetData()).ToEqual(TView(expected)); + }); + + It("Can write u16 values", []() + { + BinaryFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", Limits::Max()); + ct.Next("b", Limits::Lowest()); + TArray expected{255, 255, 0, 0}; + Expect(writer.GetData()).ToEqual(TView(expected)); + }); + + It("Can write i32 values", []() + { + BinaryFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", Limits::Max()); + ct.Next("b", Limits::Lowest()); + TArray expected{255, 255, 255, 127, 0, 0, 0, 128}; + Expect(writer.GetData()).ToEqual(TView(expected)); + }); + + It("Can write u32 values", []() + { + BinaryFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", Limits::Max()); + ct.Next("b", Limits::Lowest()); + TArray expected{255, 255, 255, 255, 0, 0, 0, 0}; + Expect(writer.GetData()).ToEqual(TView(expected)); + }); + + It("Can write i64 values", []() + { + BinaryFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", Limits::Max()); + ct.Next("b", Limits::Lowest()); + TArray expected{ + 255, 255, 255, 255, 255, 255, 255, 127, 0, 0, 0, 0, 0, 0, 0, 128}; + Expect(writer.GetData()).ToEqual(TView(expected)); + }); + + It("Can write u64 values", []() + { + BinaryFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", Limits::Max()); + ct.Next("b", Limits::Lowest()); + TArray expected{255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}; + Expect(writer.GetData()).ToEqual(TView(expected)); + }); + + It("Can write float values", []() + { + BinaryFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", -1.4f); + ct.Next("b", 3.5f); + TArray expected{51, 51, 179, 191, 0, 0, 96, 64}; + Expect(writer.GetData()).ToEqual(TView(expected)); + }); + + It("Can write double values", []() + { + BinaryFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", -1.4); + ct.Next("b", 3.5); + TArray expected{ + 102, 102, 102, 102, 102, 102, 246, 191, 0, 0, 0, 0, 0, 0, 12, 64}; + Expect(writer.GetData()).ToEqual(TView(expected)); + }); + + It("Can write StringView values", []() + { + BinaryFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", StringView{"yes"}); + TArray expected{3, 0, 0, 0, 'y', 'e', 's'}; + Expect(writer.GetData()).ToEqual(TView(expected)); + }); }); }); }); -}); diff --git a/Tests/Serialization/Json.spec.cpp b/Tests/Serialization/Json.spec.cpp index e175a41a..2373fa39 100644 --- a/Tests/Serialization/Json.spec.cpp +++ b/Tests/Serialization/Json.spec.cpp @@ -1,7 +1,7 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include +#include using namespace p; @@ -9,422 +9,420 @@ using namespace p; Spec("Serialization.Json", []() { -Describe("Reader", []() -{ - It("Can create a reader", []() - { - JsonFormatReader reader{"{}"}; - Expect(reader.IsValid()).ToBeTrue(); - }); - - It("Can read from object value", []() - { - String data{"{\"name\": \"Miguel\"}"}; - JsonFormatReader reader{data}; - - Reader& ct = reader; - ct.BeginObject(); - String name; - ct.Next("name", name); - - Expect(name.data()).ToEqual("Miguel"); - }); - - It("Can read from array values", []() - { - String data{"{\"players\": [\"Miguel\", \"Juan\"]}"}; - JsonFormatReader reader{data}; - - Reader& ct = reader; - ct.BeginObject(); - if (ct.EnterNext("players")) - { - u32 size; - ct.BeginArray(size); - String name; - ct.Next(name); - Expect(name.data()).ToEqual("Miguel"); - - ct.Next(name); - Expect(name.data()).ToEqual("Juan"); - - ct.Leave(); - } - }); - - It("Can iterate arrays", []() + Describe("Reader", []() { - String data{"{\"players\": [\"Miguel\", \"Juan\"]}"}; - JsonFormatReader reader{data}; - - Reader& ct = reader; - ct.BeginObject(); - if (ct.EnterNext("players")) + It("Can create a reader", []() { - static const StringView expected[]{"Miguel", "Juan"}; - u32 size; - ct.BeginArray(size); - for (u32 i = 0; i < size; ++i) - { - StringView name; - ct.Next(name); - Expect(name).ToEqual(expected[i]); - } - ct.Leave(); - } - }); - - It("Can check types", []() - { - String data{"{\"players\": [\"Miguel\", \"Juan\"]}"}; - JsonFormatReader reader{data}; + JsonFormatReader reader{"{}"}; + Expect(reader.IsValid()).ToBeTrue(); + }); - Reader& ct = reader; - Expect(reader.IsObject()).ToEqual(true); - ct.BeginObject(); - if (ct.EnterNext("players")) + It("Can read from object value", []() { - Expect(reader.IsArray()).ToEqual(true); - ct.Leave(); - } - }); - - It("Can find multiple keys", []() - { - String data{"{\"one\": \"Miguel\", \"other\": \"Juan\"}"}; - JsonFormatReader reader{data}; - - Reader& ct = reader; - Expect(reader.IsObject()).ToEqual(true); - ct.BeginObject(); - StringView name; - ct.Next("one", name); - Expect(name).ToEqual("Miguel"); - - ct.Next("other", name); - Expect(name).ToEqual("Juan"); - }); - - It("Can find multiple unordered keys", []() - { - String data{"{\"one\": \"Miguel\", \"other\": \"Juan\"}"}; - JsonFormatReader reader{data}; - - Reader& ct = reader; - Expect(reader.IsObject()).ToEqual(true); - ct.BeginObject(); - StringView name; - ct.Next("other", name); - Expect(name).ToEqual("Juan"); - - ct.Next("one", name); - Expect(name).ToEqual("Miguel"); - }); + String data{"{\"name\": \"Miguel\"}"}; + JsonFormatReader reader{data}; - Describe("Types", []() - { - It("Can read bool values", []() - { - JsonFormatReader reader{"{\"alive\": true}"}; Reader& ct = reader; ct.BeginObject(); - bool value = false; - ct.Next("alive", value); - Expect(value).ToEqual(true); + String name; + ct.Next("name", name); - JsonFormatReader reader2{"{\"alive\": false}"}; - ct = reader2; - ct.BeginObject(); - bool value2 = true; - ct.Next("alive", value2); - Expect(value2).ToEqual(false); + Expect(name.data()).ToEqual("Miguel"); }); - It("Can read i8 values", []() + It("Can read from array values", []() { - JsonFormatReader reader{"{\"alive\": -3}"}; - Reader& ct = reader; - ct.BeginObject(); - i8 value = 0; - ct.Next("alive", value); - Expect(value).ToEqual(-3); + String data{"{\"players\": [\"Miguel\", \"Juan\"]}"}; + JsonFormatReader reader{data}; - JsonFormatReader reader2{"{\"alive\": -1.344}"}; - ct = reader2; - ct.BeginObject(); - i8 value2 = 0; - ct.Next("alive", value2); - Expect(value2).ToEqual(-1); - }); - - It("Can read u8 values", []() - { - JsonFormatReader reader{"{\"alive\": 3}"}; Reader& ct = reader; ct.BeginObject(); - u8 value = 0; - ct.Next("alive", value); - Expect(value).ToEqual(3); - - JsonFormatReader reader2{"{\"alive\": 1.344}"}; - ct = reader2; - ct.BeginObject(); - u8 value2 = 0; - ct.Next("alive", value2); - Expect(value2).ToEqual(1); - }); - - It("Can read i16 values", []() - { - // Test inbounds and out of bounds values - JsonFormatReader reader{ - Format("{{\"a\":{},\"b\":{},\"c\":{},\"d\":{}}}", Limits::Max(), - Limits::Lowest(), Limits::Max(), Limits::Lowest())}; - Reader ct = reader; - ct.BeginObject(); - i16 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(Limits::Max()); - ct.Next("b", value); - Expect(value).ToEqual(Limits::Lowest()); - ct.Next("c", value); - Expect(value).ToEqual(Limits::Max()); - ct.Next("d", value); - Expect(value).ToEqual(Limits::Lowest()); - }); + if (ct.EnterNext("players")) + { + u32 size; + ct.BeginArray(size); + String name; + ct.Next(name); + Expect(name.data()).ToEqual("Miguel"); - It("Can read u16 values", []() - { - JsonFormatReader reader{Format("{{\"a\":{},\"b\":{},\"c\":{}}}", - Limits::Max(), Limits::Lowest(), -32)}; - Reader ct = reader; - ct.BeginObject(); - u16 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(Limits::Max()); - ct.Next("b", value); - Expect(value).ToEqual(Limits::Lowest()); - ct.Next("c", value); - Expect(value).ToEqual(0); - }); + ct.Next(name); + Expect(name.data()).ToEqual("Juan"); - It("Can read i32 values", []() - { - // Test inbounds and out of bounds values - JsonFormatReader reader{ - Format("{{\"a\":{},\"b\":{},\"c\":{},\"d\":{}}}", Limits::Max(), - Limits::Lowest(), Limits::Max(), Limits::Lowest())}; - Reader ct = reader; - ct.BeginObject(); - i32 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(Limits::Max()); - ct.Next("b", value); - Expect(value).ToEqual(Limits::Lowest()); - ct.Next("c", value); - Expect(value).ToEqual(Limits::Max()); - ct.Next("d", value); - Expect(value).ToEqual(Limits::Lowest()); + ct.Leave(); + } }); - It("Can read u32 values", []() + It("Can iterate arrays", []() { - JsonFormatReader reader{Format("{{\"a\":{},\"b\":{},\"c\":{}}}", - Limits::Max(), Limits::Lowest(), -32)}; - Reader ct = reader; - ct.BeginObject(); - u32 value = 0; - ct.Next("a", value); - Expect(value).ToEqual(Limits::Max()); - ct.Next("b", value); - Expect(value).ToEqual(Limits::Lowest()); - ct.Next("c", value); - Expect(value).ToEqual(0); - }); + String data{"{\"players\": [\"Miguel\", \"Juan\"]}"}; + JsonFormatReader reader{data}; - It("Can read float values", []() - { - JsonFormatReader reader{"{\"alive\": 0.344}"}; Reader& ct = reader; ct.BeginObject(); - float value = 0.f; - ct.Next("alive", value); - Expect(value).ToEqual(0.344f); - - JsonFormatReader reader2{"{\"alive\": 4}"}; - ct = reader2; - ct.BeginObject(); - float value2 = 0.f; - ct.Next("alive", value2); - Expect(value2).ToEqual(4.f); + if (ct.EnterNext("players")) + { + static const StringView expected[]{"Miguel", "Juan"}; + u32 size; + ct.BeginArray(size); + for (u32 i = 0; i < size; ++i) + { + StringView name; + ct.Next(name); + Expect(name).ToEqual(expected[i]); + } + ct.Leave(); + } }); - It("Can read StringView values", []() + It("Can check types", []() { - JsonFormatReader reader{"{\"alive\": \"yes\"}"}; + String data{"{\"players\": [\"Miguel\", \"Juan\"]}"}; + JsonFormatReader reader{data}; + Reader& ct = reader; + Expect(reader.IsObject()).ToEqual(true); ct.BeginObject(); - StringView value; - ct.Next("alive", value); - Expect(value).ToEqual("yes"); - }); - }); -}); - -Describe("Writer", []() -{ - It("Can create a writer", []() - { - JsonFormatWriter writer{}; - Expect(writer.IsValid()).ToEqual(true); - }); - - It("Can write to object key", []() - { - JsonFormatWriter writer{}; - Writer& ct = writer; - ct.BeginObject(); - ct.Next("name", StringView{"Miguel"}); - Expect(writer.ToString(false)).ToEqual("{\"name\":\"Miguel\"}"); - }); - - It("Can write arrays", []() - { - JsonFormatWriter writer{}; - - Writer& ct = writer; - ct.BeginObject(); - if (ct.EnterNext("players")) - { - static const StringView expected[]{"Miguel", "Juan"}; - u32 size = 2; - ct.BeginArray(size); - for (u32 i = 0; i < size; ++i) + if (ct.EnterNext("players")) { - ct.Next(expected[i]); + Expect(reader.IsArray()).ToEqual(true); + ct.Leave(); } - ct.Leave(); - } - Expect(writer.ToString(false)).ToEqual("{\"players\":[\"Miguel\",\"Juan\"]}"); - }); - - It("Can write multiple object keys", []() - { - JsonFormatWriter writer{}; - Writer& ct = writer; - ct.BeginObject(); - ct.Next("one", StringView{"Miguel"}); - ct.Next("other", StringView{"Juan"}); - Expect( - writer.ToString(false)).ToEqual("{\"one\":\"Miguel\",\"other\":\"Juan\"}"); - }); + }); - Describe("Types", []() - { - It("Can write bool values", []() + It("Can find multiple keys", []() { - JsonFormatWriter writer{}; - Writer& ct = writer; - ct.BeginObject(); - ct.Next("alive", true); - Expect(writer.ToString(false)).ToEqual("{\"alive\":true}"); + String data{"{\"one\": \"Miguel\", \"other\": \"Juan\"}"}; + JsonFormatReader reader{data}; - JsonFormatWriter writer2{}; - ct = writer2; + Reader& ct = reader; + Expect(reader.IsObject()).ToEqual(true); ct.BeginObject(); - ct.Next("alive", false); - Expect(writer2.ToString(false)).ToEqual("{\"alive\":false}"); - }); + StringView name; + ct.Next("one", name); + Expect(name).ToEqual("Miguel"); - It("Can write i8 values", []() - { - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("alive", i8(-3)); - Expect(writer.ToString(false)).ToEqual("{\"alive\":-3}"); + ct.Next("other", name); + Expect(name).ToEqual("Juan"); }); - It("Can write u8 values", []() + It("Can find multiple unordered keys", []() { - JsonFormatWriter writer{}; - Writer ct = writer; + String data{"{\"one\": \"Miguel\", \"other\": \"Juan\"}"}; + JsonFormatReader reader{data}; + + Reader& ct = reader; + Expect(reader.IsObject()).ToEqual(true); ct.BeginObject(); - ct.Next("alive", u8(3)); - Expect(writer.ToString(false)).ToEqual("{\"alive\":3}"); + StringView name; + ct.Next("other", name); + Expect(name).ToEqual("Juan"); + + ct.Next("one", name); + Expect(name).ToEqual("Miguel"); }); - It("Can write i16 values", []() + Describe("Types", []() { - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", i16(-3000)); - ct.Next("b", Limits::Max()); - ct.Next("c", Limits::Lowest()); - Expect( - writer.ToString(false)).ToEqual("{\"a\":-3000,\"b\":32767,\"c\":-32768}"); + It("Can read bool values", []() + { + JsonFormatReader reader{"{\"alive\": true}"}; + Reader& ct = reader; + ct.BeginObject(); + bool value = false; + ct.Next("alive", value); + Expect(value).ToEqual(true); + + JsonFormatReader reader2{"{\"alive\": false}"}; + ct = reader2; + ct.BeginObject(); + bool value2 = true; + ct.Next("alive", value2); + Expect(value2).ToEqual(false); + }); + + It("Can read i8 values", []() + { + JsonFormatReader reader{"{\"alive\": -3}"}; + Reader& ct = reader; + ct.BeginObject(); + i8 value = 0; + ct.Next("alive", value); + Expect(value).ToEqual(-3); + + JsonFormatReader reader2{"{\"alive\": -1.344}"}; + ct = reader2; + ct.BeginObject(); + i8 value2 = 0; + ct.Next("alive", value2); + Expect(value2).ToEqual(-1); + }); + + It("Can read u8 values", []() + { + JsonFormatReader reader{"{\"alive\": 3}"}; + Reader& ct = reader; + ct.BeginObject(); + u8 value = 0; + ct.Next("alive", value); + Expect(value).ToEqual(3); + + JsonFormatReader reader2{"{\"alive\": 1.344}"}; + ct = reader2; + ct.BeginObject(); + u8 value2 = 0; + ct.Next("alive", value2); + Expect(value2).ToEqual(1); + }); + + It("Can read i16 values", []() + { + // Test inbounds and out of bounds values + JsonFormatReader reader{ + Format("{{\"a\":{},\"b\":{},\"c\":{},\"d\":{}}}", Limits::Max(), + Limits::Lowest(), Limits::Max(), Limits::Lowest())}; + Reader ct = reader; + ct.BeginObject(); + i16 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(Limits::Max()); + ct.Next("b", value); + Expect(value).ToEqual(Limits::Lowest()); + ct.Next("c", value); + Expect(value).ToEqual(Limits::Max()); + ct.Next("d", value); + Expect(value).ToEqual(Limits::Lowest()); + }); + + It("Can read u16 values", []() + { + JsonFormatReader reader{Format("{{\"a\":{},\"b\":{},\"c\":{}}}", Limits::Max(), + Limits::Lowest(), -32)}; + Reader ct = reader; + ct.BeginObject(); + u16 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(Limits::Max()); + ct.Next("b", value); + Expect(value).ToEqual(Limits::Lowest()); + ct.Next("c", value); + Expect(value).ToEqual(0); + }); + + It("Can read i32 values", []() + { + // Test inbounds and out of bounds values + JsonFormatReader reader{ + Format("{{\"a\":{},\"b\":{},\"c\":{},\"d\":{}}}", Limits::Max(), + Limits::Lowest(), Limits::Max(), Limits::Lowest())}; + Reader ct = reader; + ct.BeginObject(); + i32 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(Limits::Max()); + ct.Next("b", value); + Expect(value).ToEqual(Limits::Lowest()); + ct.Next("c", value); + Expect(value).ToEqual(Limits::Max()); + ct.Next("d", value); + Expect(value).ToEqual(Limits::Lowest()); + }); + + It("Can read u32 values", []() + { + JsonFormatReader reader{Format("{{\"a\":{},\"b\":{},\"c\":{}}}", Limits::Max(), + Limits::Lowest(), -32)}; + Reader ct = reader; + ct.BeginObject(); + u32 value = 0; + ct.Next("a", value); + Expect(value).ToEqual(Limits::Max()); + ct.Next("b", value); + Expect(value).ToEqual(Limits::Lowest()); + ct.Next("c", value); + Expect(value).ToEqual(0); + }); + + It("Can read float values", []() + { + JsonFormatReader reader{"{\"alive\": 0.344}"}; + Reader& ct = reader; + ct.BeginObject(); + float value = 0.f; + ct.Next("alive", value); + Expect(value).ToEqual(0.344f); + + JsonFormatReader reader2{"{\"alive\": 4}"}; + ct = reader2; + ct.BeginObject(); + float value2 = 0.f; + ct.Next("alive", value2); + Expect(value2).ToEqual(4.f); + }); + + It("Can read StringView values", []() + { + JsonFormatReader reader{"{\"alive\": \"yes\"}"}; + Reader& ct = reader; + ct.BeginObject(); + StringView value; + ct.Next("alive", value); + Expect(value).ToEqual("yes"); + }); }); + }); - It("Can write u16 values", []() + Describe("Writer", []() + { + It("Can create a writer", []() { JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", u16(3000)); - ct.Next("b", Limits::Max()); - ct.Next("c", Limits::Lowest()); - Expect(writer.ToString(false)).ToEqual("{\"a\":3000,\"b\":65535,\"c\":0}"); + Expect(writer.IsValid()).ToEqual(true); }); - It("Can write u32 values", []() + It("Can write to object key", []() { JsonFormatWriter writer{}; - Writer ct = writer; + Writer& ct = writer; ct.BeginObject(); - ct.Next("alive", u32(35533)); - Expect(writer.ToString(false)).ToEqual("{\"alive\":35533}"); + ct.Next("name", StringView{"Miguel"}); + Expect(writer.ToString(false)).ToEqual("{\"name\":\"Miguel\"}"); }); - It("Can write i32 values", []() + It("Can write arrays", []() { JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - i32 value = 0; - ct.Next("alive", u32(35533)); - Expect(writer.ToString(false)).ToEqual("{\"alive\":35533}"); - JsonFormatWriter writer2{}; - ct = writer2; + Writer& ct = writer; ct.BeginObject(); - ct.Next("alive", i32(-35533)); - Expect(writer2.ToString(false)).ToEqual("{\"alive\":-35533}"); + if (ct.EnterNext("players")) + { + static const StringView expected[]{"Miguel", "Juan"}; + u32 size = 2; + ct.BeginArray(size); + for (u32 i = 0; i < size; ++i) + { + ct.Next(expected[i]); + } + ct.Leave(); + } + Expect(writer.ToString(false)).ToEqual("{\"players\":[\"Miguel\",\"Juan\"]}"); }); - It("Can write float values", []() + It("Can write multiple object keys", []() { JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("alive", 0.344f); - Expect(Strings::Contains(writer.ToString(false), "0.344")).ToEqual(true); - - JsonFormatWriter writer2{}; - ct = writer2; + Writer& ct = writer; ct.BeginObject(); - ct.Next("alive", 4.f); - Expect(writer2.ToString(false)).ToEqual("{\"alive\":4.0}"); + ct.Next("one", StringView{"Miguel"}); + ct.Next("other", StringView{"Juan"}); + Expect(writer.ToString(false)).ToEqual("{\"one\":\"Miguel\",\"other\":\"Juan\"}"); }); - It("Can write StringView values", []() + Describe("Types", []() { - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("alive", StringView{"yes"}); - Expect(writer.ToString(false)).ToEqual("{\"alive\":\"yes\"}"); + It("Can write bool values", []() + { + JsonFormatWriter writer{}; + Writer& ct = writer; + ct.BeginObject(); + ct.Next("alive", true); + Expect(writer.ToString(false)).ToEqual("{\"alive\":true}"); + + JsonFormatWriter writer2{}; + ct = writer2; + ct.BeginObject(); + ct.Next("alive", false); + Expect(writer2.ToString(false)).ToEqual("{\"alive\":false}"); + }); + + It("Can write i8 values", []() + { + JsonFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("alive", i8(-3)); + Expect(writer.ToString(false)).ToEqual("{\"alive\":-3}"); + }); + + It("Can write u8 values", []() + { + JsonFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("alive", u8(3)); + Expect(writer.ToString(false)).ToEqual("{\"alive\":3}"); + }); + + It("Can write i16 values", []() + { + JsonFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", i16(-3000)); + ct.Next("b", Limits::Max()); + ct.Next("c", Limits::Lowest()); + Expect(writer.ToString(false)).ToEqual("{\"a\":-3000,\"b\":32767,\"c\":-32768}"); + }); + + It("Can write u16 values", []() + { + JsonFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("a", u16(3000)); + ct.Next("b", Limits::Max()); + ct.Next("c", Limits::Lowest()); + Expect(writer.ToString(false)).ToEqual("{\"a\":3000,\"b\":65535,\"c\":0}"); + }); + + It("Can write u32 values", []() + { + JsonFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("alive", u32(35533)); + Expect(writer.ToString(false)).ToEqual("{\"alive\":35533}"); + }); + + It("Can write i32 values", []() + { + JsonFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + i32 value = 0; + ct.Next("alive", u32(35533)); + Expect(writer.ToString(false)).ToEqual("{\"alive\":35533}"); + + JsonFormatWriter writer2{}; + ct = writer2; + ct.BeginObject(); + ct.Next("alive", i32(-35533)); + Expect(writer2.ToString(false)).ToEqual("{\"alive\":-35533}"); + }); + + It("Can write float values", []() + { + JsonFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("alive", 0.344f); + Expect(Strings::Contains(writer.ToString(false), "0.344")).ToEqual(true); + + JsonFormatWriter writer2{}; + ct = writer2; + ct.BeginObject(); + ct.Next("alive", 4.f); + Expect(writer2.ToString(false)).ToEqual("{\"alive\":4.0}"); + }); + + It("Can write StringView values", []() + { + JsonFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("alive", StringView{"yes"}); + Expect(writer.ToString(false)).ToEqual("{\"alive\":\"yes\"}"); + }); }); }); }); -}); diff --git a/Tests/Serialization/Serialization.spec.cpp b/Tests/Serialization/Serialization.spec.cpp index 17dd9f4a..b549e96d 100644 --- a/Tests/Serialization/Serialization.spec.cpp +++ b/Tests/Serialization/Serialization.spec.cpp @@ -1,7 +1,7 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include +#include using namespace p; @@ -91,101 +91,101 @@ struct p::TFlags : public p::DefaultTFlags Spec("Serialization", []() { -Describe("Serializers in global scope", []() -{ - It("Can use custom Read()", []() - { - SerTypeA val{}; - JsonFormatReader reader{"{\"type\": {\"value\": true }}"}; - - Reader& ct = reader; - ct.BeginObject(); - ct.Next("type", val); - Expect(val.value).ToEqual(true); - }); - - It("Can use custom Write()", []() - { - SerTypeA val{}; - val.value = true; - - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("type", val); - Expect(writer.ToString(false)).ToEqual("{\"type\":{\"value\":true}}"); - }); - - It("Can use Serialize() instead of Read()", []() - { - SerTypeB val{}; - JsonFormatReader reader{"{\"type\": {\"value\": true }}"}; - - Reader& ct = reader; - ct.BeginObject(); - ct.Next("type", val); - Expect(val.value).ToEqual(true); - }); - - It("Can use Serialize() instead of Write()", []() - { - SerTypeB val{}; - val.value = true; - - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("type", val); - Expect(writer.ToString(false)).ToEqual("{\"type\":{\"value\":true}}"); - }); -}); - -Describe("Serializers as members", []() -{ - It("Can use custom Read()", []() - { - SerTypeC val{}; - JsonFormatReader reader{"{\"type\": {\"value\": true }}"}; - - Reader& ct = reader; - ct.BeginObject(); - ct.Next("type", val); - Expect(val.value).ToEqual(true); - }); - - It("Can use custom Write()", []() + Describe("Serializers in global scope", []() { - SerTypeC val{}; - val.value = true; - - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("type", val); - Expect(writer.ToString(false)).ToEqual("{\"type\":{\"value\":true}}"); - }); - - It("Can use Serialize() instead of Read()", []() - { - SerTypeD val{}; - JsonFormatReader reader{"{\"type\": {\"value\": true }}"}; - - Reader& ct = reader; - ct.BeginObject(); - ct.Next("type", val); - Expect(val.value).ToEqual(true); + It("Can use custom Read()", []() + { + SerTypeA val{}; + JsonFormatReader reader{"{\"type\": {\"value\": true }}"}; + + Reader& ct = reader; + ct.BeginObject(); + ct.Next("type", val); + Expect(val.value).ToEqual(true); + }); + + It("Can use custom Write()", []() + { + SerTypeA val{}; + val.value = true; + + JsonFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("type", val); + Expect(writer.ToString(false)).ToEqual("{\"type\":{\"value\":true}}"); + }); + + It("Can use Serialize() instead of Read()", []() + { + SerTypeB val{}; + JsonFormatReader reader{"{\"type\": {\"value\": true }}"}; + + Reader& ct = reader; + ct.BeginObject(); + ct.Next("type", val); + Expect(val.value).ToEqual(true); + }); + + It("Can use Serialize() instead of Write()", []() + { + SerTypeB val{}; + val.value = true; + + JsonFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("type", val); + Expect(writer.ToString(false)).ToEqual("{\"type\":{\"value\":true}}"); + }); }); - It("Can use Serialize() instead of Write()", []() + Describe("Serializers as members", []() { - SerTypeD val{}; - val.value = true; - - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("type", val); - Expect(writer.ToString(false)).ToEqual("{\"type\":{\"value\":true}}"); + It("Can use custom Read()", []() + { + SerTypeC val{}; + JsonFormatReader reader{"{\"type\": {\"value\": true }}"}; + + Reader& ct = reader; + ct.BeginObject(); + ct.Next("type", val); + Expect(val.value).ToEqual(true); + }); + + It("Can use custom Write()", []() + { + SerTypeC val{}; + val.value = true; + + JsonFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("type", val); + Expect(writer.ToString(false)).ToEqual("{\"type\":{\"value\":true}}"); + }); + + It("Can use Serialize() instead of Read()", []() + { + SerTypeD val{}; + JsonFormatReader reader{"{\"type\": {\"value\": true }}"}; + + Reader& ct = reader; + ct.BeginObject(); + ct.Next("type", val); + Expect(val.value).ToEqual(true); + }); + + It("Can use Serialize() instead of Write()", []() + { + SerTypeD val{}; + val.value = true; + + JsonFormatWriter writer{}; + Writer ct = writer; + ct.BeginObject(); + ct.Next("type", val); + Expect(writer.ToString(false)).ToEqual("{\"type\":{\"value\":true}}"); + }); }); }); -}); diff --git a/Tests/main.cpp b/Tests/main.cpp index d5220744..f9916c8d 100644 --- a/Tests/main.cpp +++ b/Tests/main.cpp @@ -4,9 +4,9 @@ // replacement operator new/delete (P_OVERRIDE_NEWDELETE) in its own translation unit; // including it here too would cause duplicate-definition linker errors. -#include #include #include +#include #include From 7e540b04c59a0193aaa304ce1b088a167400c93a Mon Sep 17 00:00:00 2001 From: muit Date: Sun, 6 Sep 2026 00:21:07 +0200 Subject: [PATCH 18/25] P_SPEC macro improvements, added TTypeId --- Include/Pipe/Core/TypeId.h | 35 +++++++++ Include/PipeTest.h | 57 +++++++++----- Src/Tests/PipeTest.cpp | 89 ++++++++++++---------- Tests/Containers/Arrays.spec.cpp | 4 +- Tests/Core/Function.spec.cpp | 2 +- Tests/Core/OwnPtr.spec.cpp | 2 +- Tests/Core/PageBuffer.spec.cpp | 2 +- Tests/Core/PlatformProcess.spec.cpp | 4 +- Tests/Core/Set.spec.cpp | 2 +- Tests/Core/SpinLock.spec.cpp | 2 +- Tests/Core/String.spec.cpp | 2 +- Tests/Core/StringView.spec.cpp | 2 +- Tests/Core/Tag.spec.cpp | 2 +- Tests/ECS/Components.spec.cpp | 2 +- Tests/ECS/ECS.spec.cpp | 2 +- Tests/ECS/Filtering.spec.cpp | 2 +- Tests/ECS/Hierarchy.spec.cpp | 2 +- Tests/ECS/IdRegistry.spec.cpp | 2 +- Tests/ECS/IdScopes.spec.cpp | 2 +- Tests/ECS/Statics.spec.cpp | 2 +- Tests/Files/Paths.spec.cpp | 2 +- Tests/Math/Color.spec.cpp | 2 +- Tests/Math/Math.spec.cpp | 2 +- Tests/Math/Vector.spec.cpp | 2 +- Tests/Memory/BestFitArena.spec.cpp | 2 +- Tests/Memory/BigBestFitArena.spec.cpp | 2 +- Tests/Memory/Memory.spec.cpp | 2 +- Tests/Memory/MemoryStats.spec.cpp | 2 +- Tests/Memory/MonoLinearArena.spec.cpp | 2 +- Tests/PipeTest.spec.cpp | 2 +- Tests/PipeTime.spec.cpp | 2 +- Tests/Reflection/MacroReflection.spec.cpp | 2 +- Tests/Reflection/Object.spec.cpp | 2 +- Tests/Reflection/Traits.spec.cpp | 2 +- Tests/Reflection/TypeId.spec.cpp | 2 +- Tests/Reflection/TypeName.spec.cpp | 2 +- Tests/Serialization/Binary.spec.cpp | 2 +- Tests/Serialization/Json.spec.cpp | 2 +- Tests/Serialization/Serialization.spec.cpp | 2 +- 39 files changed, 163 insertions(+), 94 deletions(-) diff --git a/Include/Pipe/Core/TypeId.h b/Include/Pipe/Core/TypeId.h index a604bc1e..5c48a9b9 100644 --- a/Include/Pipe/Core/TypeId.h +++ b/Include/Pipe/Core/TypeId.h @@ -101,6 +101,41 @@ namespace p return GetTypeId>(); } + // A TypeId bound to a base type. + template + struct TTypeId : public TypeId + { + constexpr TTypeId() : TypeId(GetTypeId()) {} + constexpr TTypeId(p::Undefined) : TypeId(GetTypeId()) {} + + // From another TTypeId bound to a compatible (same or derived) type. + template T2> + constexpr TTypeId(const TTypeId& other) : TypeId(other) + {} + + // From a runtime TypeId. + TTypeId(TypeId id) : TypeId(IsCompatible(GetTypeId(), id) ? id : TypeId{}) {} + + constexpr TTypeId& operator=(const TTypeId&) = default; + template T2> + constexpr TTypeId& operator=(const TTypeId& other) + { + TypeId::operator=(other); + return *this; + } + TTypeId& operator=(TypeId id) + { + TypeId::operator=(IsCompatible(GetTypeId(), id) ? id : TypeId{}); + return *this; + } + + private: + static bool IsCompatible(TypeId parentId, TypeId childId) + { + return parentId == childId || IsTypeParentOf(parentId, childId); + } + }; + #pragma region Castable struct Castable diff --git a/Include/PipeTest.h b/Include/PipeTest.h index fedfc0c7..0a8ea6c6 100644 --- a/Include/PipeTest.h +++ b/Include/PipeTest.h @@ -5,6 +5,7 @@ #include "Pipe/Core/Function.h" #include "Pipe/Core/Macros.h" #include "Pipe/Core/StringView.h" +#include "Pipe/Core/TypeId.h" #include "PipeStrings.h" #include @@ -38,11 +39,35 @@ namespace p void RegisterSpec(StringView name, TFunction fn); void RegisterSpec(TFunction fn); - // Self-registering top-level. Spec(name, fn) opens a first describe named `name`. - // fn runs immediately during registration, so TFunction (non-owning) is safe. - // Macro handles static-init registration at file scope. -#define Spec(...) \ - static const bool P_CAT(_pipeSpecReg_, __COUNTER__) = (::p::RegisterSpec(__VA_ARGS__), true); + // Self-registering top-level describe. Registers its spec body on + // construction (same pattern as TTypeAutoRegister). The body runs + // immediately during registration, so TFunction (non-owning) is safe. + struct TSpecAutoRegister + { + constexpr TSpecAutoRegister(StringView name, TFunction fn) + { + RegisterSpec(name, fn); + } + constexpr TSpecAutoRegister(TFunction fn) + { + RegisterSpec(fn); + } + }; + + // Declares the spec's body function and registers it on static init. + // The body is written as a plain function block after the macro, so it + // is NOT part of the macro arguments and preprocessor directives are + // allowed inside: + // + // P_SPEC("Files.Paths", []() + // { + // It("Some test", []() { + // #if P_PLATFORM_WINDOWS + // ... + // #endif + // }); + // }); +#define P_SPEC static const p::TSpecAutoRegister P_CAT(_pipeSpecReg_, __COUNTER__) // Nested describe. Only valid inside a Spec; otherwise logs an error and ignores. void Describe(StringView name, TFunction fn); @@ -56,14 +81,9 @@ namespace p // Teardown hook attached to the current describe. void AfterEach(std::function fn); - // Which reporter formats the test output. - enum class TestReporter : u8 - { - Spec, // Verbose, bandit-style "describe / it ... OK" output (default). - Dots, // Compact progress: one character (., F, S) per test. - Singleline, // Single progress line updated in place, "\r" based. - Info, // Verbose "begin/end" contexts, "[ PASS ]" tests, timing support. - }; + // Reporter interface (defined in Src/Tests/PipeTest.cpp). Forward + // declaration so TestSettings can reference its type id. + struct ITestReporter; // Settings for a test run. struct TestSettings @@ -71,10 +91,13 @@ namespace p StringView only; // Run only describe/it containing substring. StringView skip; // Skip all describe/it containing substring. bool dryRun = false; // Report full tree as SKIPPED, run nothing (bandit semantics). - bool breakOnFailure = false; // Stop the test run on the first failing test. - bool useColor = true; // Colorized output. - bool reportTiming = false; // Report per-test timing information. - TestReporter reporter = TestReporter::Dots; + bool breakOnFailure = false; // Stop the test run on the first failing test. + bool useColor = true; // Colorized output. + bool reportTiming = false; // Report per-test timing information. + // Reporter to use, identified by type. Invalid (default) means Spec. + // Bound to ITestReporter, so only reporter types can be assigned + // (e.g. TTypeId::Of()). + TTypeId reporter; }; int RunTests(const TestSettings& settings); diff --git a/Src/Tests/PipeTest.cpp b/Src/Tests/PipeTest.cpp index a2c1a657..e3ecfd07 100644 --- a/Src/Tests/PipeTest.cpp +++ b/Src/Tests/PipeTest.cpp @@ -64,9 +64,10 @@ namespace p // Assertion detail accumulated for the current test (file:line: msg). String currentFailureDetail; - TestReporter reporter = TestReporter::Spec; - bool useColor = true; - bool reportTiming = false; + // Id of the selected reporter (matches settings.reporter). + TTypeId reporter{}; + bool useColor = true; + bool reportTiming = false; // Wall time of the last executed test body, in seconds. double lastTestDuration = 0.0; @@ -75,7 +76,7 @@ namespace p }; // Function-local static: initialized on first use regardless of the - // static-init order of other translation units, so a `Spec` call + // static-init order of other translation units, so a `P_SPEC` call // at file scope in a separate TU can safely register during static init. TestContext& GetTestContext() { @@ -205,6 +206,26 @@ namespace p current->afterEach = fn; } + // Reporter interface (public so TestSettings can reference its type id). + // Mirrors bandit's reporter callbacks. Each reporter formats the run + // differently; all share the same per-test execution flow in RunNested. + struct ITestReporter + { + virtual ~ITestReporter() = default; + + virtual void TestRunStarting() {} + virtual void TestRunComplete() = 0; + virtual void ContextStarting(StringView) {} + virtual void ContextEnded(StringView) {} + virtual void ItStarting(StringView) {} + virtual void ItSucceeded(StringView) {} + // Test passed but made no assertions (e.g. smoke tests). + virtual void ItSucceededNoAssertions(StringView) {} + virtual void ItFailed(StringView) {} + virtual void ItUnknownError(StringView) {} + virtual void ItSkipped(StringView) {} + }; + namespace { @@ -294,26 +315,6 @@ namespace p return Colored(Yellow, Format(" ({})", FormatDuration(context.lastTestDuration))); } - // ---- Reporter interface ---- - // Mirrors bandit's reporter callbacks. Each reporter formats the run - // differently; all share the same per-test execution flow in RunNested. - struct ITestReporter - { - virtual ~ITestReporter() = default; - - virtual void TestRunStarting() {} - virtual void TestRunComplete() = 0; - virtual void ContextStarting(StringView) {} - virtual void ContextEnded(StringView) {} - virtual void ItStarting(StringView) {} - virtual void ItSucceeded(StringView) {} - // Test passed but made no assertions (e.g. smoke tests). - virtual void ItSucceededNoAssertions(StringView) {} - virtual void ItFailed(StringView) {} - virtual void ItUnknownError(StringView) {} - virtual void ItSkipped(StringView) {} - }; - // Shared summary footer (defined below; forward-declared for reporters). static void WriteSummary(); @@ -868,14 +869,24 @@ namespace p return 0; } + const TypeId reporterId = settings.reporter; TUniquePtr reporter; - switch (settings.reporter) + if (reporterId == GetTypeId()) + { + reporter = MakeUnique(); + } + else if (reporterId == GetTypeId()) + { + reporter = MakeUnique(); + } + else if (reporterId == GetTypeId()) + { + reporter = MakeUnique(); + } + else { - case TestReporter::Dots: reporter = MakeUnique(); break; - case TestReporter::Singleline: reporter = MakeUnique(); break; - case TestReporter::Info: reporter = MakeUnique(); break; - case TestReporter::Spec: - default: reporter = MakeUnique(); break; + // Spec (also the fallback for an unset/unknown reporter id). + reporter = MakeUnique(); } reporter->TestRunStarting(); @@ -915,19 +926,19 @@ namespace p const StringView name = Strings::RemoveFromStart(arg, StringView{"--reporter="}); if (Strings::Equals(name, StringView{"dots"})) { - settings.reporter = TestReporter::Dots; + settings.reporter = TTypeId(); } else if (Strings::Equals(name, StringView{"singleline"})) { - settings.reporter = TestReporter::Singleline; + settings.reporter = TTypeId(); } else if (Strings::Equals(name, StringView{"spec"})) { - settings.reporter = TestReporter::Spec; + settings.reporter = TTypeId(); } else if (Strings::Equals(name, StringView{"info"})) { - settings.reporter = TestReporter::Info; + settings.reporter = TTypeId(); } else { @@ -942,23 +953,23 @@ namespace p const StringView name{argv[++i]}; if (Strings::Equals(name, StringView{"dots"})) { - settings.reporter = TestReporter::Dots; + settings.reporter = TTypeId(); } else if (Strings::Equals(name, StringView{"singleline"})) { - settings.reporter = TestReporter::Singleline; + settings.reporter = TTypeId(); } else if (Strings::Equals(name, StringView{"spec"})) { - settings.reporter = TestReporter::Spec; + settings.reporter = TTypeId(); } else if (Strings::Equals(name, StringView{"info"})) { - settings.reporter = TestReporter::Info; + settings.reporter = TTypeId(); } else { - Warning("PipeTest: unknown reporter '{}'. Using 'dots'.", name); + Warning("PipeTest: unknown reporter '{}'. Using 'spec'.", name); } } } diff --git a/Tests/Containers/Arrays.spec.cpp b/Tests/Containers/Arrays.spec.cpp index 43524093..c454c26f 100644 --- a/Tests/Containers/Arrays.spec.cpp +++ b/Tests/Containers/Arrays.spec.cpp @@ -41,7 +41,7 @@ struct CopyType }; -Spec("Containers.Array", []() +P_SPEC("Containers.Array", []() { It("Can initialize", []() { @@ -960,7 +960,7 @@ Spec("Containers.Array", []() }); }); -Spec("Containers.BitArray", []() +P_SPEC("Containers.BitArray", []() { It("Can initialize", []() { diff --git a/Tests/Core/Function.spec.cpp b/Tests/Core/Function.spec.cpp index 25f865f4..b1d1aa7b 100644 --- a/Tests/Core/Function.spec.cpp +++ b/Tests/Core/Function.spec.cpp @@ -28,7 +28,7 @@ struct Foo inline bool Foo::called = false; -Spec("Core.Function", []() +P_SPEC("Core.Function", []() { It("Can create empty", []() { diff --git a/Tests/Core/OwnPtr.spec.cpp b/Tests/Core/OwnPtr.spec.cpp index 9c93517d..103e7e89 100644 --- a/Tests/Core/OwnPtr.spec.cpp +++ b/Tests/Core/OwnPtr.spec.cpp @@ -41,7 +41,7 @@ struct MockStruct }; -Spec("Core.OwnPtr", []() +P_SPEC("Core.OwnPtr", []() { Describe("Owner pointer", []() { diff --git a/Tests/Core/PageBuffer.spec.cpp b/Tests/Core/PageBuffer.spec.cpp index 91cac829..3071b8f2 100644 --- a/Tests/Core/PageBuffer.spec.cpp +++ b/Tests/Core/PageBuffer.spec.cpp @@ -25,7 +25,7 @@ struct Dummy }; -Spec("ECS.PageBuffer", []() +P_SPEC("ECS.PageBuffer", []() { It("Can reserve", []() { diff --git a/Tests/Core/PlatformProcess.spec.cpp b/Tests/Core/PlatformProcess.spec.cpp index e928fdcf..eb946ca8 100644 --- a/Tests/Core/PlatformProcess.spec.cpp +++ b/Tests/Core/PlatformProcess.spec.cpp @@ -10,13 +10,13 @@ using namespace p; -Spec("Core.Subprocess", []() +P_SPEC("Core.Subprocess", []() { It("Can run process", []() { Expect(p::RunProcess({""}).IsSet()).ToEqual(false); -#if defined(_MSC_VER) // Test with a silent command (no stdout) +#if defined(P_PLATFORM_WINDOWS) Expect(p::RunProcess({"cmd", "/c", "exit", "0"}).IsSet()).ToEqual(true); #endif }); diff --git a/Tests/Core/Set.spec.cpp b/Tests/Core/Set.spec.cpp index f0da4e25..b2a796b0 100644 --- a/Tests/Core/Set.spec.cpp +++ b/Tests/Core/Set.spec.cpp @@ -14,7 +14,7 @@ struct TypeOfSize }; -Spec("Core.Set", []() +P_SPEC("Core.Set", []() { It("Can initialize", []() { diff --git a/Tests/Core/SpinLock.spec.cpp b/Tests/Core/SpinLock.spec.cpp index 5bfb2f55..4a98c691 100644 --- a/Tests/Core/SpinLock.spec.cpp +++ b/Tests/Core/SpinLock.spec.cpp @@ -11,7 +11,7 @@ using namespace p; -Spec("Core.SpinLock", []() +P_SPEC("Core.SpinLock", []() { Describe("SpinLock", []() { diff --git a/Tests/Core/String.spec.cpp b/Tests/Core/String.spec.cpp index 0ee3d779..f6c05d8d 100644 --- a/Tests/Core/String.spec.cpp +++ b/Tests/Core/String.spec.cpp @@ -18,7 +18,7 @@ static const StringView longText = "0123456789ABCDEFGHIJ0123456789ABC"; static const char* arenaLongText = "This string is long enough to exceed the inline capacity"; -Spec("Strings", []() +P_SPEC("Strings", []() { Describe("String", []() { diff --git a/Tests/Core/StringView.spec.cpp b/Tests/Core/StringView.spec.cpp index cf312a44..6249b228 100644 --- a/Tests/Core/StringView.spec.cpp +++ b/Tests/Core/StringView.spec.cpp @@ -8,7 +8,7 @@ using namespace p; -Spec("Strings", []() +P_SPEC("Strings", []() { Describe("StringView", []() { diff --git a/Tests/Core/Tag.spec.cpp b/Tests/Core/Tag.spec.cpp index 35979413..d23c6d90 100644 --- a/Tests/Core/Tag.spec.cpp +++ b/Tests/Core/Tag.spec.cpp @@ -7,7 +7,7 @@ using namespace p; -Spec("Core.Tag", []() +P_SPEC("Core.Tag", []() { It("Can copy empty", []() { diff --git a/Tests/ECS/Components.spec.cpp b/Tests/ECS/Components.spec.cpp index 68cedba8..8132f12f 100644 --- a/Tests/ECS/Components.spec.cpp +++ b/Tests/ECS/Components.spec.cpp @@ -44,7 +44,7 @@ struct TestComponent u32 TestComponent::destructed = 0; -Spec("ECS.Components", []() +P_SPEC("ECS.Components", []() { It("Can add one component", []() { diff --git a/Tests/ECS/ECS.spec.cpp b/Tests/ECS/ECS.spec.cpp index ad544833..28f5495b 100644 --- a/Tests/ECS/ECS.spec.cpp +++ b/Tests/ECS/ECS.spec.cpp @@ -15,7 +15,7 @@ struct ECSTypeB {}; -Spec("ECS", []() +P_SPEC("ECS", []() { It("Can copy context", []() { diff --git a/Tests/ECS/Filtering.spec.cpp b/Tests/ECS/Filtering.spec.cpp index 56d552f5..b3c87a10 100644 --- a/Tests/ECS/Filtering.spec.cpp +++ b/Tests/ECS/Filtering.spec.cpp @@ -26,7 +26,7 @@ namespace } // namespace -Spec("ECS.Filtering", []() +P_SPEC("ECS.Filtering", []() { BeforeEach([]() { diff --git a/Tests/ECS/Hierarchy.spec.cpp b/Tests/ECS/Hierarchy.spec.cpp index a9674aaa..edf24193 100644 --- a/Tests/ECS/Hierarchy.spec.cpp +++ b/Tests/ECS/Hierarchy.spec.cpp @@ -19,7 +19,7 @@ namespace } // namespace -Spec("ECS.Hierarchy", []() +P_SPEC("ECS.Hierarchy", []() { BeforeEach([]() { diff --git a/Tests/ECS/IdRegistry.spec.cpp b/Tests/ECS/IdRegistry.spec.cpp index fceebb2e..8250c787 100644 --- a/Tests/ECS/IdRegistry.spec.cpp +++ b/Tests/ECS/IdRegistry.spec.cpp @@ -8,7 +8,7 @@ using namespace p; using namespace std::chrono_literals; -Spec("ECS.IdRegistry", []() +P_SPEC("ECS.IdRegistry", []() { It("Can create one id", []() { diff --git a/Tests/ECS/IdScopes.spec.cpp b/Tests/ECS/IdScopes.spec.cpp index 784de555..85433b39 100644 --- a/Tests/ECS/IdScopes.spec.cpp +++ b/Tests/ECS/IdScopes.spec.cpp @@ -21,7 +21,7 @@ struct ScopeTypeC }; -Spec("ECS.IdScopes", []() +P_SPEC("ECS.IdScopes", []() { Describe("Templated", []() { diff --git a/Tests/ECS/Statics.spec.cpp b/Tests/ECS/Statics.spec.cpp index b6945f3d..49f51d61 100644 --- a/Tests/ECS/Statics.spec.cpp +++ b/Tests/ECS/Statics.spec.cpp @@ -22,7 +22,7 @@ struct StaticTypeThree }; -Spec("ECS.Statics", []() +P_SPEC("ECS.Statics", []() { It("Can set an static", []() { diff --git a/Tests/Files/Paths.spec.cpp b/Tests/Files/Paths.spec.cpp index 24db9c8e..1b571edf 100644 --- a/Tests/Files/Paths.spec.cpp +++ b/Tests/Files/Paths.spec.cpp @@ -8,7 +8,7 @@ using namespace p; -Spec("Files.Paths", []() +P_SPEC("Files.Paths", []() { It("Can get root name and path", []() { diff --git a/Tests/Math/Color.spec.cpp b/Tests/Math/Color.spec.cpp index c4c00ef0..be9c5110 100644 --- a/Tests/Math/Color.spec.cpp +++ b/Tests/Math/Color.spec.cpp @@ -7,7 +7,7 @@ using namespace p; -Spec("Math.Color", []() +P_SPEC("Math.Color", []() { Describe("Helpers", []() { diff --git a/Tests/Math/Math.spec.cpp b/Tests/Math/Math.spec.cpp index a5be7804..c13f5264 100644 --- a/Tests/Math/Math.spec.cpp +++ b/Tests/Math/Math.spec.cpp @@ -19,7 +19,7 @@ namespace } // namespace -Spec("Math.Math", []() +P_SPEC("Math.Math", []() { Describe("Binary Search", []() { diff --git a/Tests/Math/Vector.spec.cpp b/Tests/Math/Vector.spec.cpp index d4fb99c5..85dab325 100644 --- a/Tests/Math/Vector.spec.cpp +++ b/Tests/Math/Vector.spec.cpp @@ -7,7 +7,7 @@ using namespace p; -Spec("Math.Vector", []() +P_SPEC("Math.Vector", []() { Describe("v2", []() { diff --git a/Tests/Memory/BestFitArena.spec.cpp b/Tests/Memory/BestFitArena.spec.cpp index b3ba0198..d12cb796 100644 --- a/Tests/Memory/BestFitArena.spec.cpp +++ b/Tests/Memory/BestFitArena.spec.cpp @@ -14,7 +14,7 @@ struct TypeOfSize }; -Spec("Memory.BestFitArena", []() +P_SPEC("Memory.BestFitArena", []() { It("Reserves a block on construction", []() { diff --git a/Tests/Memory/BigBestFitArena.spec.cpp b/Tests/Memory/BigBestFitArena.spec.cpp index 227ab40f..6cd6605f 100644 --- a/Tests/Memory/BigBestFitArena.spec.cpp +++ b/Tests/Memory/BigBestFitArena.spec.cpp @@ -13,7 +13,7 @@ struct TypeOfSize p::u8 data[size]{0}; // Fill data for debugging }; -Spec("Memory.BigBestFitArena", []() +P_SPEC("Memory.BigBestFitArena", []() { It("Reserves a block on construction", []() { diff --git a/Tests/Memory/Memory.spec.cpp b/Tests/Memory/Memory.spec.cpp index c3da6f0b..39b5a2d0 100644 --- a/Tests/Memory/Memory.spec.cpp +++ b/Tests/Memory/Memory.spec.cpp @@ -53,7 +53,7 @@ struct MoveType }; -Spec("Memory.Operations", []() +P_SPEC("Memory.Operations", []() { It("Can default construct", []() { diff --git a/Tests/Memory/MemoryStats.spec.cpp b/Tests/Memory/MemoryStats.spec.cpp index b6038591..d7c36243 100644 --- a/Tests/Memory/MemoryStats.spec.cpp +++ b/Tests/Memory/MemoryStats.spec.cpp @@ -22,7 +22,7 @@ static const MemoryStatsEvent* LiveFind(const MemoryStats& s, void* ptr) } -Spec("Memory.MemoryStats", []() +P_SPEC("Memory.MemoryStats", []() { Describe("Basic", []() { diff --git a/Tests/Memory/MonoLinearArena.spec.cpp b/Tests/Memory/MonoLinearArena.spec.cpp index 94eeb016..db7b2944 100644 --- a/Tests/Memory/MonoLinearArena.spec.cpp +++ b/Tests/Memory/MonoLinearArena.spec.cpp @@ -7,7 +7,7 @@ using namespace p; -Spec("Memory.MonoLinearArena", []() +P_SPEC("Memory.MonoLinearArena", []() { It("Reserves a block on construction", []() { diff --git a/Tests/PipeTest.spec.cpp b/Tests/PipeTest.spec.cpp index ce6e0f9d..69cc532f 100644 --- a/Tests/PipeTest.spec.cpp +++ b/Tests/PipeTest.spec.cpp @@ -12,7 +12,7 @@ static int afterEachCount = 0; static int topTestResult = 0; -Spec("PipeTest", []() +P_SPEC("PipeTest", []() { BeforeEach([]() { diff --git a/Tests/PipeTime.spec.cpp b/Tests/PipeTime.spec.cpp index 37931de5..c824f97f 100644 --- a/Tests/PipeTime.spec.cpp +++ b/Tests/PipeTime.spec.cpp @@ -7,7 +7,7 @@ using namespace p; -Spec("Time.DateTime", []() +P_SPEC("Time.DateTime", []() { It("Can get day of year", []() { diff --git a/Tests/Reflection/MacroReflection.spec.cpp b/Tests/Reflection/MacroReflection.spec.cpp index 1bc7f70e..6b32e93a 100644 --- a/Tests/Reflection/MacroReflection.spec.cpp +++ b/Tests/Reflection/MacroReflection.spec.cpp @@ -20,7 +20,7 @@ struct TestStruct }; -Spec("Reflection.Macros", []() +P_SPEC("Reflection.Macros", []() { It("Can get property names", []() { diff --git a/Tests/Reflection/Object.spec.cpp b/Tests/Reflection/Object.spec.cpp index 61049f5e..5d68dbe5 100644 --- a/Tests/Reflection/Object.spec.cpp +++ b/Tests/Reflection/Object.spec.cpp @@ -22,7 +22,7 @@ class TestObject : public p::Object }; -Spec("Reflection.Object", []() +P_SPEC("Reflection.Object", []() { Describe("Pointers", []() { diff --git a/Tests/Reflection/Traits.spec.cpp b/Tests/Reflection/Traits.spec.cpp index 0fceeb80..7cad0b02 100644 --- a/Tests/Reflection/Traits.spec.cpp +++ b/Tests/Reflection/Traits.spec.cpp @@ -39,7 +39,7 @@ namespace p } // namespace p -Spec("Reflection.Traits", []() +P_SPEC("Reflection.Traits", []() { Describe("Read/Write properties", []() { diff --git a/Tests/Reflection/TypeId.spec.cpp b/Tests/Reflection/TypeId.spec.cpp index 2e5df256..8dc27003 100644 --- a/Tests/Reflection/TypeId.spec.cpp +++ b/Tests/Reflection/TypeId.spec.cpp @@ -10,7 +10,7 @@ struct One {}; -Spec("Reflection.TypeId", []() +P_SPEC("Reflection.TypeId", []() { It("Ids can be valid and invalid", []() { diff --git a/Tests/Reflection/TypeName.spec.cpp b/Tests/Reflection/TypeName.spec.cpp index c672a1f5..c58178fa 100644 --- a/Tests/Reflection/TypeName.spec.cpp +++ b/Tests/Reflection/TypeName.spec.cpp @@ -25,7 +25,7 @@ namespace Space } // namespace Space -Spec("Reflection.TypeName", []() +P_SPEC("Reflection.TypeName", []() { It("Can get Platform type names", []() { diff --git a/Tests/Serialization/Binary.spec.cpp b/Tests/Serialization/Binary.spec.cpp index b923b33d..f26ed08b 100644 --- a/Tests/Serialization/Binary.spec.cpp +++ b/Tests/Serialization/Binary.spec.cpp @@ -7,7 +7,7 @@ using namespace p; -Spec("Serialization.Binary", []() +P_SPEC("Serialization.Binary", []() { Describe("Reader", []() { diff --git a/Tests/Serialization/Json.spec.cpp b/Tests/Serialization/Json.spec.cpp index 2373fa39..d73d6b9c 100644 --- a/Tests/Serialization/Json.spec.cpp +++ b/Tests/Serialization/Json.spec.cpp @@ -7,7 +7,7 @@ using namespace p; -Spec("Serialization.Json", []() +P_SPEC("Serialization.Json", []() { Describe("Reader", []() { diff --git a/Tests/Serialization/Serialization.spec.cpp b/Tests/Serialization/Serialization.spec.cpp index b549e96d..37a7a082 100644 --- a/Tests/Serialization/Serialization.spec.cpp +++ b/Tests/Serialization/Serialization.spec.cpp @@ -89,7 +89,7 @@ struct p::TFlags : public p::DefaultTFlags }; -Spec("Serialization", []() +P_SPEC("Serialization", []() { Describe("Serializers in global scope", []() { From b480ebce5882add7f724c84faf8bbe660716a204 Mon Sep 17 00:00:00 2001 From: muit Date: Sun, 6 Sep 2026 10:26:03 +0200 Subject: [PATCH 19/25] Refactored Reflection and Type headers --- Include/Pipe/Core/Broadcast.h | 2 +- Include/Pipe/Core/Object.h | 117 +++++++++++ Include/Pipe/Core/TypeId.h | 174 ---------------- Include/Pipe/Memory/OwnPtr.h | 27 ++- Include/PipeECS.h | 1 + Include/PipeMemory.h | 2 +- Include/PipeReflect.h | 133 +----------- Include/PipeSerialize.h | 2 +- Include/PipeTest.h | 2 +- Include/{Pipe/Core/TypeName.h => PipeType.h} | 189 +++++++++++++++++- Src/Core/Object.cpp | 32 +++ Src/PipeReflect.cpp | 26 --- Src/Tests/PipeTest.cpp | 14 +- .../MacroReflection.spec.cpp | 0 Tests/{Reflection => Reflect}/Object.spec.cpp | 2 +- Tests/{Reflection => Reflect}/Traits.spec.cpp | 0 .../Binary.spec.cpp | 0 .../Json.spec.cpp | 0 .../Serialization.spec.cpp | 0 Tests/Type/Castable.spec.cpp | 157 +++++++++++++++ Tests/{Reflection => Type}/TypeId.spec.cpp | 0 Tests/{Reflection => Type}/TypeName.spec.cpp | 0 22 files changed, 534 insertions(+), 346 deletions(-) create mode 100644 Include/Pipe/Core/Object.h delete mode 100644 Include/Pipe/Core/TypeId.h rename Include/{Pipe/Core/TypeName.h => PipeType.h} (58%) create mode 100644 Src/Core/Object.cpp rename Tests/{Reflection => Reflect}/MacroReflection.spec.cpp (100%) rename Tests/{Reflection => Reflect}/Object.spec.cpp (96%) rename Tests/{Reflection => Reflect}/Traits.spec.cpp (100%) rename Tests/{Serialization => Serialize}/Binary.spec.cpp (100%) rename Tests/{Serialization => Serialize}/Json.spec.cpp (100%) rename Tests/{Serialization => Serialize}/Serialization.spec.cpp (100%) create mode 100644 Tests/Type/Castable.spec.cpp rename Tests/{Reflection => Type}/TypeId.spec.cpp (100%) rename Tests/{Reflection => Type}/TypeName.spec.cpp (100%) diff --git a/Include/Pipe/Core/Broadcast.h b/Include/Pipe/Core/Broadcast.h index cdf9c916..28ab4a04 100644 --- a/Include/Pipe/Core/Broadcast.h +++ b/Include/Pipe/Core/Broadcast.h @@ -133,7 +133,7 @@ namespace p { if (instance && method) { - if constexpr (IsObject) + if constexpr (Derived) { return Bind(instance->AsPtr(), Move(method)); } diff --git a/Include/Pipe/Core/Object.h b/Include/Pipe/Core/Object.h new file mode 100644 index 00000000..36222a4a --- /dev/null +++ b/Include/Pipe/Core/Object.h @@ -0,0 +1,117 @@ +// Copyright 2015-2026 Piperift. All Rights Reserved. + +#pragma once + +#include "Pipe/Memory/OwnPtr.h" +#include "PipeReflect.h" +#include "PipeType.h" + +#include + + +namespace p +{ + class P_API BaseObject : public Castable + { + protected: + BaseObject() = default; + + public: + virtual ~BaseObject() = default; + + TPtr AsPtr() const; + }; + + + // For shared export purposes, we separate pointers from the exported Class + struct P_API ObjectOwnership + { + TPtr self; + TPtr owner; + static TPtr nextOwner; + + + ObjectOwnership(); + const TPtr& AsPtr() const; + const TPtr& GetOwner() const; + }; + + + template + struct TObjectPtrBuilder : public TPtrBuilder + { + template + static T* New(Arena& arena, Args&&... args, const TPtr& owner = {}) + { + // Sets owner during construction + // TODO: Fix self not existing at the moment of construction + ObjectOwnership::nextOwner = owner; + return new (p::Alloc(arena)) T(std::forward(args)...); + } + + // Allow creation of classes using reflection + static T* New(Arena& arena, TypeId type, TPtr owner = {}) + { + if (GetTypeId() == type || IsTypeParentOf(GetTypeId(), type)) + { + if (auto* ops = GetTypeObjectOps(type)) + { + // Sets owner during construction + // TODO: Fix self not existing at the moment of construction + ObjectOwnership::nextOwner = owner; + return Cast(ops->onNew(arena)); + } + } + return nullptr; + } + + static void Delete(Arena& arena, void* rawPtr) + { + T* ptr = static_cast(rawPtr); + const sizet typeSize = GetTypeSize(ptr->GetTypeId()); + ptr->~T(); + arena.Free((void*)ptr, typeSize); // size depends on inheritance! + } + }; + + + class P_API Object : public BaseObject + { + public: + using Self = Object; + template + using PtrBuilder = TObjectPtrBuilder; + + p::TypeId ProvideTypeId() const override + { + return p::GetTypeId(); + } + + static constexpr p::TypeFlags staticFlags = TF_None; + + P_REFLECTION_BODY({}) + + private: + ObjectOwnership ownership; + + + public: + Object() = default; + + void ChangeOwner(const TPtr& inOwner); + template + TPtr AsPtr() const + { + return Cast(ownership.AsPtr()); + } + template + TPtr GetOwner() const + { + return Cast(ownership.GetOwner()); + } + }; + + + template + concept IsObject = Derived; +} // namespace p \ No newline at end of file diff --git a/Include/Pipe/Core/TypeId.h b/Include/Pipe/Core/TypeId.h deleted file mode 100644 index 5c48a9b9..00000000 --- a/Include/Pipe/Core/TypeId.h +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright 2015-2026 Piperift. All Rights Reserved. -#pragma once - -#include "Pipe/Core/Hash.h" -#include "Pipe/Core/Utility.h" -#include "PipePlatform.h" - -#if P_DEBUG - #include "Pipe/Core/StringView.h" - #include "Pipe/Core/TypeName.h" -#endif - -#include -#include - - -namespace p -{ - struct P_API TypeId - { - protected: - u64 id; -#if P_DEBUG - StringView debugName; -#endif - - - public: - constexpr TypeId() : id{0} {} - constexpr TypeId(p::Undefined) {} - explicit constexpr TypeId(u64 id) : id{id} {} -#if P_DEBUG - explicit constexpr TypeId(u64 id, StringView debugName) : id{id}, debugName{debugName} {} -#endif - - constexpr u64 GetId() const - { - return id; - } - - constexpr bool IsValid() const - { - return id != 0; - } - - constexpr auto operator==(const TypeId& other) const - { - return id == other.id; - } - constexpr auto operator<(const TypeId& other) const - { - return id < other.id; - } - constexpr auto operator>(const TypeId& other) const - { - return id > other.id; - } - constexpr auto operator<=(const TypeId& other) const - { - return id <= other.id; - } - constexpr auto operator>=(const TypeId& other) const - { - return id >= other.id; - } - constexpr operator bool() const - { - return IsValid(); - } - - static consteval TypeId None() - { - return TypeId{}; - } - }; - - inline sizet GetHash(const TypeId& id) - { - return GetHash(id.GetId()); - } - - inline std::ostream& operator<<(std::ostream& stream, TypeId typeId) - { - stream << "TypeId(id=" << typeId.GetId() << ")"; - return stream; - } - - template - inline consteval TypeId GetTypeId() requires(!IsConst) - { - return TypeId{p::GetStringHash(P_UNIQUE_FUNCTION_ID), -#if P_DEBUG - GetTypeName() -#endif - }; - } - - template - inline consteval TypeId GetTypeId() requires(IsConst) - { - return GetTypeId>(); - } - - // A TypeId bound to a base type. - template - struct TTypeId : public TypeId - { - constexpr TTypeId() : TypeId(GetTypeId()) {} - constexpr TTypeId(p::Undefined) : TypeId(GetTypeId()) {} - - // From another TTypeId bound to a compatible (same or derived) type. - template T2> - constexpr TTypeId(const TTypeId& other) : TypeId(other) - {} - - // From a runtime TypeId. - TTypeId(TypeId id) : TypeId(IsCompatible(GetTypeId(), id) ? id : TypeId{}) {} - - constexpr TTypeId& operator=(const TTypeId&) = default; - template T2> - constexpr TTypeId& operator=(const TTypeId& other) - { - TypeId::operator=(other); - return *this; - } - TTypeId& operator=(TypeId id) - { - TypeId::operator=(IsCompatible(GetTypeId(), id) ? id : TypeId{}); - return *this; - } - - private: - static bool IsCompatible(TypeId parentId, TypeId childId) - { - return parentId == childId || IsTypeParentOf(parentId, childId); - } - }; - - -#pragma region Castable - struct Castable - { - private: - mutable TypeId typeId; - - public: - TypeId GetTypeId() const - { - if (!typeId) - { - typeId = ProvideTypeId(); - } - return typeId; - } - - protected: - virtual TypeId ProvideTypeId() const = 0; - }; - - template - concept IsCastable = Derived, Castable, false>; -#pragma endregion Castable -} // namespace p - - -template<> -struct std::formatter : public std::formatter -{ - template - auto format(const p::TypeId& typeId, FormatContext& ctx) const - { - return formatter::format(typeId.GetId(), ctx); - } -}; diff --git a/Include/Pipe/Memory/OwnPtr.h b/Include/Pipe/Memory/OwnPtr.h index 6f98b5fd..21ab6b46 100644 --- a/Include/Pipe/Memory/OwnPtr.h +++ b/Include/Pipe/Memory/OwnPtr.h @@ -3,7 +3,6 @@ #pragma once #include "Pipe/Core/Checks.h" -#include "Pipe/Core/TypeId.h" #include "Pipe/Core/TypeTraits.h" #include "Pipe/Core/Utility.h" #include "Pipe/Memory/PtrBuilder.h" @@ -577,4 +576,30 @@ namespace p { return GetHash(ptr.GetRaw()); } + + +#pragma region Casts + template + TPtr Cast(const TPtr& value) + { + if (Cast(value.Get())) + { + TPtr ptr{}; + ptr.CopyFromUnsafe(value); + return ptr; + } + return {}; + } + + template + TPtr Cast(const TOwnPtr& value) + { + if constexpr (Derived) // Is T2 is T or its base + { + return TPtr{value}; + } + TPtr ptr{value}; + return Cast(ptr); + } +#pragma endregion Casts } // namespace p diff --git a/Include/PipeECS.h b/Include/PipeECS.h index 87630d5f..fa8f77e2 100644 --- a/Include/PipeECS.h +++ b/Include/PipeECS.h @@ -5,6 +5,7 @@ #include "Pipe/Core/PageBuffer.h" #include "Pipe/Core/Templates.h" #include "Pipe/Core/TypeTraits.h" +#include "Pipe/Memory/OwnPtr.h" #include "Pipe/Memory/UniquePtr.h" #include "PipeContainers.h" #include "PipeECSFwd.h" diff --git a/Include/PipeMemory.h b/Include/PipeMemory.h index 103da13a..34549366 100644 --- a/Include/PipeMemory.h +++ b/Include/PipeMemory.h @@ -3,10 +3,10 @@ #pragma once #include "Pipe/Core/Limits.h" -#include "Pipe/Core/TypeId.h" #include "Pipe/Core/TypeTraits.h" #include "Pipe/Core/Utility.h" #include "PipeContainersFwd.h" +#include "PipeType.h" namespace p diff --git a/Include/PipeReflect.h b/Include/PipeReflect.h index a3e45cfa..5a5a3abe 100644 --- a/Include/PipeReflect.h +++ b/Include/PipeReflect.h @@ -7,14 +7,12 @@ #include "Pipe/Core/Macros.h" #include "Pipe/Core/StringView.h" #include "Pipe/Core/Tag.h" -#include "Pipe/Core/TypeId.h" -#include "Pipe/Core/TypeName.h" #include "Pipe/Extern/magic_enum.hpp" #include "Pipe/Files/STDFileSystem.h" -#include "Pipe/Memory/OwnPtr.h" #include "PipeColor.h" #include "PipeSerialize.h" #include "PipeStrings.h" +#include "PipeType.h" #include "PipeVectors.h" @@ -29,9 +27,6 @@ namespace p class BaseObject; #pragma region Traits - template - concept IsObject = Derived; - template consteval bool HasSuper() { @@ -571,7 +566,7 @@ namespace p if constexpr (IsStructOrClass) { AddTypeFlags(TF_Struct); - if constexpr (IsObject) + if constexpr (Derived) { static ObjectTypeOps objectOps; AssignSerializableTypeOps(objectOps); @@ -846,107 +841,6 @@ P_NATIVE_NAMED(p::Color, "Color") namespace p { -#pragma region Objects - class P_API BaseObject : public Castable - { - protected: - BaseObject() = default; - - public: - virtual ~BaseObject() = default; - - TPtr AsPtr() const; - }; - - - // For shared export purposes, we separate pointers from the exported Class - struct P_API ObjectOwnership - { - TPtr self; - TPtr owner; - static TPtr nextOwner; - - - ObjectOwnership(); - const TPtr& AsPtr() const; - const TPtr& GetOwner() const; - }; - - - template - struct TObjectPtrBuilder : public TPtrBuilder - { - template - static T* New(Arena& arena, Args&&... args, const TPtr& owner = {}) - { - // Sets owner during construction - // TODO: Fix self not existing at the moment of construction - ObjectOwnership::nextOwner = owner; - return new (p::Alloc(arena)) T(std::forward(args)...); - } - - // Allow creation of classes using reflection - static T* New(Arena& arena, TypeId type, TPtr owner = {}) - { - if (GetTypeId() == type || IsTypeParentOf(GetTypeId(), type)) - { - if (auto* ops = GetTypeObjectOps(type)) - { - // Sets owner during construction - // TODO: Fix self not existing at the moment of construction - ObjectOwnership::nextOwner = owner; - return Cast(ops->onNew(arena)); - } - } - return nullptr; - } - - static void Delete(Arena& arena, void* rawPtr) - { - T* ptr = static_cast(rawPtr); - const sizet typeSize = GetTypeSize(ptr->GetTypeId()); - ptr->~T(); - arena.Free((void*)ptr, typeSize); // size depends on inheritance! - } - }; - - - class P_API Object : public BaseObject - { - public: - using Self = Object; - template - using PtrBuilder = TObjectPtrBuilder; - - p::TypeId ProvideTypeId() const override - { - return p::GetTypeId(); - } - - static constexpr p::TypeFlags staticFlags = TF_None; - - P_REFLECTION_BODY({}) - - private: - ObjectOwnership ownership; - - - public: - Object() = default; - - void ChangeOwner(const TPtr& inOwner); - template - TPtr AsPtr() const - { - return Cast(ownership.AsPtr()); - } - template - TPtr GetOwner() const - { - return Cast(ownership.GetOwner()); - } - }; -#pragma endregion Objects #pragma region Casts @@ -981,28 +875,5 @@ namespace p } return nullptr; } - - template - TPtr Cast(const TPtr& value) - { - if (Cast(value.Get())) - { - TPtr ptr{}; - ptr.CopyFromUnsafe(value); - return ptr; - } - return {}; - } - - template - TPtr Cast(const TOwnPtr& value) - { - if constexpr (Derived) // Is T2 is T or its base - { - return TPtr{value}; - } - TPtr ptr{value}; - return Cast(ptr); - } #pragma endregion Casts }; // namespace p diff --git a/Include/PipeSerialize.h b/Include/PipeSerialize.h index 022eb07e..99603ae3 100644 --- a/Include/PipeSerialize.h +++ b/Include/PipeSerialize.h @@ -4,13 +4,13 @@ #include "Pipe/Core/StringView.h" #include "Pipe/Core/Templates.h" #include "Pipe/Core/TypeFlags.h" -#include "Pipe/Core/TypeId.h" #include "Pipe/Core/TypeTraits.h" #include "PipeColor.h" #include "PipeContainers.h" #include "PipePlatform.h" #include "PipeSerializeFwd.h" #include "PipeStrings.h" +#include "PipeType.h" #include diff --git a/Include/PipeTest.h b/Include/PipeTest.h index 0a8ea6c6..f9e519f3 100644 --- a/Include/PipeTest.h +++ b/Include/PipeTest.h @@ -5,7 +5,7 @@ #include "Pipe/Core/Function.h" #include "Pipe/Core/Macros.h" #include "Pipe/Core/StringView.h" -#include "Pipe/Core/TypeId.h" +#include "PipeReflect.h" #include "PipeStrings.h" #include diff --git a/Include/Pipe/Core/TypeName.h b/Include/PipeType.h similarity index 58% rename from Include/Pipe/Core/TypeName.h rename to Include/PipeType.h index cf682324..15afbf54 100644 --- a/Include/Pipe/Core/TypeName.h +++ b/Include/PipeType.h @@ -1,12 +1,98 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. + #pragma once #include "Pipe/Core/FixedString.h" +#include "Pipe/Core/Hash.h" #include "Pipe/Core/StringView.h" +#include "Pipe/Core/Utility.h" +#include "PipePlatform.h" + +#include +#include namespace p { + struct P_API TypeId + { + protected: + u64 id; +#if P_DEBUG + StringView debugName; +#endif + + + public: + constexpr TypeId() : id{0} {} + constexpr TypeId(p::Undefined) {} + explicit constexpr TypeId(u64 id) : id{id} {} +#if P_DEBUG + explicit constexpr TypeId(u64 id, StringView debugName) : id{id}, debugName{debugName} {} +#endif + + constexpr u64 GetId() const + { + return id; + } + + StringView GetDebugName() const + { +#if P_DEBUG + return debugName; +#else + return {}; +#endif + } + + constexpr bool IsValid() const + { + return id != 0; + } + + constexpr auto operator==(const TypeId& other) const + { + return id == other.id; + } + constexpr auto operator<(const TypeId& other) const + { + return id < other.id; + } + constexpr auto operator>(const TypeId& other) const + { + return id > other.id; + } + constexpr auto operator<=(const TypeId& other) const + { + return id <= other.id; + } + constexpr auto operator>=(const TypeId& other) const + { + return id >= other.id; + } + constexpr operator bool() const + { + return IsValid(); + } + + static consteval TypeId None() + { + return TypeId{}; + } + }; + + inline sizet GetHash(const TypeId& id) + { + return GetHash(id.GetId()); + } + + inline std::ostream& operator<<(std::ostream& stream, TypeId typeId) + { + stream << "TypeId(id=" << typeId.GetId() << ")"; + return stream; + } + + namespace TypeName { template @@ -172,15 +258,114 @@ namespace p } template - consteval StringView GetTypeName(bool includeNamespaces = true) requires(IsMap()) + inline consteval StringView GetTypeName(bool includeNamespaces = true) requires(IsMap()) { return "TMap"; } + + + template + inline consteval TypeId GetTypeId() requires(!IsConst) + { + return TypeId{p::GetStringHash(P_UNIQUE_FUNCTION_ID), +#if P_DEBUG + GetTypeName() +#endif + }; + } + + template + inline consteval TypeId GetTypeId() requires(IsConst) + { + return GetTypeId>(); + } + + // A TypeId bound to a base type. + template + struct TTypeId : public TypeId + { + constexpr TTypeId() : TypeId(GetTypeId()) {} + constexpr TTypeId(p::Undefined) : TypeId(GetTypeId()) {} + + // From another TTypeId bound to a compatible (same or derived) type. + template T2> + constexpr TTypeId(const TTypeId& other) : TypeId(other) + {} + + // From a runtime TypeId. + TTypeId(TypeId id) : TypeId(IsCompatible(GetTypeId(), id) ? id : TypeId{}) {} + + constexpr TTypeId& operator=(const TTypeId&) = default; + template T2> + constexpr TTypeId& operator=(const TTypeId& other) + { + TypeId::operator=(other); + return *this; + } + TTypeId& operator=(TypeId id) + { + TypeId::operator=(IsCompatible(GetTypeId(), id) ? id : TypeId{}); + return *this; + } + + private: + static bool IsCompatible(TypeId parentId, TypeId childId) + { + return parentId == childId || IsTypeParentOf(parentId, childId); + } + }; + + +#pragma region Castable + struct Castable + { + private: + mutable TypeId typeId; + + public: + TypeId GetTypeId() const + { + if (!typeId) + { + typeId = ProvideTypeId(); + } + return typeId; + } + + protected: + virtual TypeId ProvideTypeId() const = 0; + }; + + template + concept IsCastable = Derived, Castable, false>; +#pragma endregion Castable } // namespace p + +template<> +struct std::formatter : public std::formatter +{ + template + auto format(const p::TypeId& typeId, FormatContext& ctx) const + { +#if P_DEBUG + const p::StringView debugName = typeId.GetDebugName(); + if (!debugName.empty()) + { + return std::formatter{}.format(debugName, ctx); + } +#endif + return formatter::format(typeId.GetId(), ctx); + } +}; + +template +struct std::formatter> : public std::formatter +{}; + #define P_OVERRIDE_TYPE_NAME(type, name) \ template<> \ inline consteval p::StringView p::GetFullTypeName(bool includeNamespaces) \ { \ return name; \ - } + } \ No newline at end of file diff --git a/Src/Core/Object.cpp b/Src/Core/Object.cpp new file mode 100644 index 00000000..67bdd81c --- /dev/null +++ b/Src/Core/Object.cpp @@ -0,0 +1,32 @@ +// Copyright 2015-2026 Piperift. All Rights Reserved. + +#include "Pipe/Core/Object.h" + +namespace p +{ + TPtr BaseObject::AsPtr() const + { + return static_cast(this)->AsPtr(); + } + + + TPtr ObjectOwnership::nextOwner{}; + + ObjectOwnership::ObjectOwnership() : self{}, owner{Move(nextOwner)} + { + ; + } + const TPtr& ObjectOwnership::AsPtr() const + { + return self; + } + const TPtr& ObjectOwnership::GetOwner() const + { + return owner; + } + + void Object::ChangeOwner(const TPtr& inOwner) + { + ownership.owner = inOwner; + } +} // namespace p \ No newline at end of file diff --git a/Src/PipeReflect.cpp b/Src/PipeReflect.cpp index 2af9374b..32894e8e 100644 --- a/Src/PipeReflect.cpp +++ b/Src/PipeReflect.cpp @@ -444,30 +444,4 @@ namespace p P_CheckEditingType; GetRegistry().operations[currentEdit.index] = operations; } - - TPtr BaseObject::AsPtr() const - { - return static_cast(this)->AsPtr(); - } - - - TPtr ObjectOwnership::nextOwner{}; - - ObjectOwnership::ObjectOwnership() : self{}, owner{Move(nextOwner)} - { - ; - } - const TPtr& ObjectOwnership::AsPtr() const - { - return self; - } - const TPtr& ObjectOwnership::GetOwner() const - { - return owner; - } - - void Object::ChangeOwner(const TPtr& inOwner) - { - ownership.owner = inOwner; - } } // namespace p diff --git a/Src/Tests/PipeTest.cpp b/Src/Tests/PipeTest.cpp index e3ecfd07..ebe7a161 100644 --- a/Src/Tests/PipeTest.cpp +++ b/Src/Tests/PipeTest.cpp @@ -9,7 +9,7 @@ #include "Pipe.h" #include "Pipe/Core/Log.h" -#include "Pipe/Memory/UniquePtr.h" +#include "Pipe/Memory/OwnPtr.h" #include "PipeStrings.h" #include "PipeTest.h" #include "PipeTime.h" @@ -870,23 +870,23 @@ namespace p } const TypeId reporterId = settings.reporter; - TUniquePtr reporter; + TOwnPtr reporter; if (reporterId == GetTypeId()) { - reporter = MakeUnique(); + reporter = MakeOwned(); } else if (reporterId == GetTypeId()) { - reporter = MakeUnique(); + reporter = MakeOwned(); } else if (reporterId == GetTypeId()) { - reporter = MakeUnique(); + reporter = MakeOwned(); } else { // Spec (also the fallback for an unset/unknown reporter id). - reporter = MakeUnique(); + reporter = MakeOwned(); } reporter->TestRunStarting(); @@ -896,7 +896,7 @@ namespace p TArray> beforeHooks; TArray> afterHooks; RunNested(context.root, beforeHooks, afterHooks, settings.only, settings.skip, - settings.breakOnFailure, *reporter.Get()); + settings.breakOnFailure, *reporter); // Total duration is wall time from run start to run end. const Timespan runElapsed = DateTime::Now() - runStart; diff --git a/Tests/Reflection/MacroReflection.spec.cpp b/Tests/Reflect/MacroReflection.spec.cpp similarity index 100% rename from Tests/Reflection/MacroReflection.spec.cpp rename to Tests/Reflect/MacroReflection.spec.cpp diff --git a/Tests/Reflection/Object.spec.cpp b/Tests/Reflect/Object.spec.cpp similarity index 96% rename from Tests/Reflection/Object.spec.cpp rename to Tests/Reflect/Object.spec.cpp index 5d68dbe5..1c089f89 100644 --- a/Tests/Reflection/Object.spec.cpp +++ b/Tests/Reflect/Object.spec.cpp @@ -1,6 +1,6 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include diff --git a/Tests/Reflection/Traits.spec.cpp b/Tests/Reflect/Traits.spec.cpp similarity index 100% rename from Tests/Reflection/Traits.spec.cpp rename to Tests/Reflect/Traits.spec.cpp diff --git a/Tests/Serialization/Binary.spec.cpp b/Tests/Serialize/Binary.spec.cpp similarity index 100% rename from Tests/Serialization/Binary.spec.cpp rename to Tests/Serialize/Binary.spec.cpp diff --git a/Tests/Serialization/Json.spec.cpp b/Tests/Serialize/Json.spec.cpp similarity index 100% rename from Tests/Serialization/Json.spec.cpp rename to Tests/Serialize/Json.spec.cpp diff --git a/Tests/Serialization/Serialization.spec.cpp b/Tests/Serialize/Serialization.spec.cpp similarity index 100% rename from Tests/Serialization/Serialization.spec.cpp rename to Tests/Serialize/Serialization.spec.cpp diff --git a/Tests/Type/Castable.spec.cpp b/Tests/Type/Castable.spec.cpp new file mode 100644 index 00000000..39874f58 --- /dev/null +++ b/Tests/Type/Castable.spec.cpp @@ -0,0 +1,157 @@ +// Copyright 2015-2026 Piperift. All Rights Reserved. + +#include +#include +#include + + +using namespace p; + +struct CastableBase : p::Castable +{ + p::TypeId ProvideTypeId() const override + { + return p::GetTypeId(); + } +}; + +struct CastableDerived : CastableBase +{ + using Super = CastableBase; + + p::TypeId ProvideTypeId() const override + { + return p::GetTypeId(); + } +}; + +struct CastableLeaf : CastableDerived +{ + using Super = CastableDerived; + + p::TypeId ProvideTypeId() const override + { + return p::GetTypeId(); + } +}; + +struct CastableCounter : p::Castable +{ + mutable i32 provideCalls = 0; + + p::TypeId ProvideTypeId() const override + { + ++provideCalls; + return p::GetTypeId(); + } +}; + + +P_SPEC("Reflection.Castable", []() +{ + static_assert(p::IsCastable); + static_assert(p::IsCastable); + static_assert(!p::IsCastable); + + Describe("Basics", []() + { + It("Provides the compile-time type id", []() + { + CastableBase base; + CastableDerived derived; + + Expect(base.GetTypeId()).ToEqual(p::GetTypeId()); + Expect(derived.GetTypeId()).ToEqual(p::GetTypeId()); + Expect(derived.GetTypeId()).ToNotEqual(base.GetTypeId()); + }); + + It("Computes and caches the type id lazily", []() + { + const CastableCounter counter; + + Expect(counter.provideCalls).ToEqual(0); + + const TypeId first = counter.GetTypeId(); + Expect(counter.provideCalls).ToEqual(1); + + const TypeId second = counter.GetTypeId(); + Expect(counter.provideCalls).ToEqual(1); + Expect(second).ToEqual(first); + }); + }); + + Describe("Hierarchy", []() + { + BeforeEach([]() + { + // Registers the full chain: CastableLeaf -> CastableDerived -> CastableBase. + p::RegisterTypeId(); + }); + + It("IsTypeParentOf reflects the registered hierarchy", []() + { + const TypeId baseId = p::GetTypeId(); + const TypeId midId = p::GetTypeId(); + const TypeId leafId = p::GetTypeId(); + + Expect(p::IsTypeParentOf(baseId, leafId)).ToEqual(true); + Expect(p::IsTypeParentOf(midId, leafId)).ToEqual(true); + Expect(p::IsTypeParentOf(baseId, midId)).ToEqual(true); + + Expect(p::IsTypeParentOf(leafId, baseId)).ToEqual(false); + Expect(p::IsTypeParentOf(midId, baseId)).ToEqual(false); + }); + + It("Up-casts without checks", []() + { + auto leaf = p::MakeOwned(); + + Expect(p::Cast(leaf.Get())).ToEqual(leaf.Get()); + }); + + It("Down-casts only when the runtime type matches", []() + { + auto derived = p::MakeOwned(); + auto plain = p::MakeOwned(); + + CastableBase* asBase = derived.Get(); + Expect(p::Cast(asBase)).ToEqual(derived.Get()); + + Expect(p::Cast(plain.Get())).ToEqual(nullptr); + }); + + It("Down-casts through registered ancestors", []() + { + auto leaf = p::MakeOwned(); + auto mid = p::MakeOwned(); + + // A base pointer to a leaf resolves to the intermediate type + // through the registered parent chain. + CastableBase* asBase = leaf.Get(); + Expect(p::Cast(asBase)).ToEqual(leaf.Get()); + + // A derived instance is not a leaf. + Expect(p::Cast(mid.Get())).ToEqual(nullptr); + }); + + It("TTypeId binds compatible types", []() + { + const TypeId baseId = p::GetTypeId(); + const TypeId leafId = p::GetTypeId(); + + // A derived runtime id can be bound to a base TTypeId. It keeps the + // derived id, which matches the base via the parent chain. + TTypeId baseType{leafId}; + Expect(baseType.IsValid()).ToEqual(true); + Expect(baseType).ToEqual(leafId); + + // A parent runtime id can't be bound to a child TTypeId. + TTypeId leafType{baseId}; + Expect(leafType.IsValid()).ToEqual(false); + + // Converting a child TTypeId to its base stays valid. + TTypeId converted{TTypeId{}}; + Expect(converted.IsValid()).ToEqual(true); + }); + }); +}); \ No newline at end of file diff --git a/Tests/Reflection/TypeId.spec.cpp b/Tests/Type/TypeId.spec.cpp similarity index 100% rename from Tests/Reflection/TypeId.spec.cpp rename to Tests/Type/TypeId.spec.cpp diff --git a/Tests/Reflection/TypeName.spec.cpp b/Tests/Type/TypeName.spec.cpp similarity index 100% rename from Tests/Reflection/TypeName.spec.cpp rename to Tests/Type/TypeName.spec.cpp From 2808c5faaa27595475c2681fc24e1872dc5564a6 Mon Sep 17 00:00:00 2001 From: muit Date: Sun, 6 Sep 2026 10:42:50 +0200 Subject: [PATCH 20/25] Solved dependency issue --- Include/PipeType.h | 18 ++++++++++-------- Src/Core/PipeType.cpp | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 8 deletions(-) create mode 100644 Src/Core/PipeType.cpp diff --git a/Include/PipeType.h b/Include/PipeType.h index 15afbf54..0c26690c 100644 --- a/Include/PipeType.h +++ b/Include/PipeType.h @@ -280,6 +280,13 @@ namespace p return GetTypeId>(); } + + namespace details + { + P_API bool IsTypeIdCompatible(TypeId parentId, TypeId childId); + } // namespace details + + // A TypeId bound to a base type. template struct TTypeId : public TypeId @@ -293,7 +300,8 @@ namespace p {} // From a runtime TypeId. - TTypeId(TypeId id) : TypeId(IsCompatible(GetTypeId(), id) ? id : TypeId{}) {} + TTypeId(TypeId id) : TypeId(details::IsTypeIdCompatible(GetTypeId(), id) ? id : TypeId{}) + {} constexpr TTypeId& operator=(const TTypeId&) = default; template T2> @@ -304,15 +312,9 @@ namespace p } TTypeId& operator=(TypeId id) { - TypeId::operator=(IsCompatible(GetTypeId(), id) ? id : TypeId{}); + TypeId::operator=(details::IsTypeIdCompatible(GetTypeId(), id) ? id : TypeId{}); return *this; } - - private: - static bool IsCompatible(TypeId parentId, TypeId childId) - { - return parentId == childId || IsTypeParentOf(parentId, childId); - } }; diff --git a/Src/Core/PipeType.cpp b/Src/Core/PipeType.cpp new file mode 100644 index 00000000..52fea60c --- /dev/null +++ b/Src/Core/PipeType.cpp @@ -0,0 +1,14 @@ +// Copyright 2015-2026 Piperift. All Rights Reserved. + +#include "PipeType.h" + +#include "PipeReflect.h" + + +namespace p::details +{ + bool IsTypeIdCompatible(TypeId parentId, TypeId childId) + { + return parentId == childId || IsTypeParentOf(parentId, childId); + } +} // namespace p::details From dc14d8e09e1907781ed1d97822e04291b947b478 Mon Sep 17 00:00:00 2001 From: muit Date: Sun, 6 Sep 2026 10:46:14 +0200 Subject: [PATCH 21/25] Small fix for Windows --- Include/PipeTest.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Include/PipeTest.h b/Include/PipeTest.h index f9e519f3..d42d2f9d 100644 --- a/Include/PipeTest.h +++ b/Include/PipeTest.h @@ -42,13 +42,13 @@ namespace p // Self-registering top-level describe. Registers its spec body on // construction (same pattern as TTypeAutoRegister). The body runs // immediately during registration, so TFunction (non-owning) is safe. - struct TSpecAutoRegister + struct SpecAutoRegister { - constexpr TSpecAutoRegister(StringView name, TFunction fn) + SpecAutoRegister(StringView name, TFunction fn) { RegisterSpec(name, fn); } - constexpr TSpecAutoRegister(TFunction fn) + SpecAutoRegister(TFunction fn) { RegisterSpec(fn); } @@ -67,7 +67,7 @@ namespace p // #endif // }); // }); -#define P_SPEC static const p::TSpecAutoRegister P_CAT(_pipeSpecReg_, __COUNTER__) +#define P_SPEC static const p::SpecAutoRegister P_CAT(_pipeSpecReg_, __COUNTER__) // Nested describe. Only valid inside a Spec; otherwise logs an error and ignores. void Describe(StringView name, TFunction fn); From 3d27ec36ee6307e49e54971ecc8e6ab1f8d221e4 Mon Sep 17 00:00:00 2001 From: muit Date: Sun, 6 Sep 2026 10:48:32 +0200 Subject: [PATCH 22/25] SMall fix on non-windows builds --- Tests/Core/PlatformProcess.spec.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Core/PlatformProcess.spec.cpp b/Tests/Core/PlatformProcess.spec.cpp index eb946ca8..9f77e44d 100644 --- a/Tests/Core/PlatformProcess.spec.cpp +++ b/Tests/Core/PlatformProcess.spec.cpp @@ -16,7 +16,7 @@ P_SPEC("Core.Subprocess", []() { Expect(p::RunProcess({""}).IsSet()).ToEqual(false); -#if defined(P_PLATFORM_WINDOWS) +#if P_PLATFORM_WINDOWS Expect(p::RunProcess({"cmd", "/c", "exit", "0"}).IsSet()).ToEqual(true); #endif }); From f5ccde2c520022f12ae098ac2358c81244779235 Mon Sep 17 00:00:00 2001 From: muit Date: Sun, 6 Sep 2026 10:51:11 +0200 Subject: [PATCH 23/25] Removed plans --- Docs/Plans/2026-09-04-pipe-tests-framework.md | 1195 ----------------- 1 file changed, 1195 deletions(-) delete mode 100644 Docs/Plans/2026-09-04-pipe-tests-framework.md diff --git a/Docs/Plans/2026-09-04-pipe-tests-framework.md b/Docs/Plans/2026-09-04-pipe-tests-framework.md deleted file mode 100644 index a035e04a..00000000 --- a/Docs/Plans/2026-09-04-pipe-tests-framework.md +++ /dev/null @@ -1,1195 +0,0 @@ -# PipeTests Framework Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Build a Pipe-native test framework (`PipeTests` module) that mirrors Bandit's structure with imgui-style global context, used by both PipeTests and RiftTests. - -**Architecture:** A new `PipeTests` module in the Pipe submodule (`Include/PipeTest.h` + `Src/PipeTest.cpp`) built as a **separate CMake library target** (never compiled into the runtime `Pipe` library). Global registration cursor tracks the current test group as functions are called. `Expect(value)` returns a fluent matcher. `p::RunTests(argc, argv)` runs the suite. Existing Bandit tests are NOT migrated and Bandit is NOT removed until the final task. - -**Macro-free registration (decision 2026-09-04):** The framework uses NO macros. `Spec`/`Describe`/`It`/`XIt`/`BeforeEach`/`AfterEach` are plain functions in namespace `p`. A bare function call is ill-formed at namespace scope, so each spec file wraps its `Spec` call in a TU-local static registrar (the macro-free equivalent of what `go_bandit` expands to); it auto-registers via static init — no manual registration calls needed, `main()` only calls `p::RunTests`. The registry uses a function-local `static` (`GetTestContext()`), so it initializes on first use regardless of translation-unit order. `Spec(fn)` (nameless, go_bandit-style) and `Spec(name, fn)` are both supported; `Spec(fn)` registers into the virtual root describe. - -```cpp -namespace -{ -// Auto-registers via static init (macro-free go_bandit equivalent). -const bool autoRegistered = []() -{ -Spec("Strings", []() -{ - // ... Describe/It ... -}); -return true; -}(); -} // namespace -``` - -**Design changes (2026-09-04, post-Task 6):** -- `RunTests` split: `RunTests(int argc, char** argv)` parses argv into a `TestSettings` struct (`StringView filter`; `--filter=X`, `--filter X`, or positional) and forwards to `RunTests(const TestSettings&)`, so other systems can run tests programmatically without text args. -- Pipe types throughout: `i32` counters, `TFunction` for immediately-invoked callbacks (`Spec`/`Describe`), `TArray`/`String`/`StringView`. Stored bodies/hooks (`It`/`XIt`/`BeforeEach`/`AfterEach`, hook stacks) stay `std::function` (owning) because `TFunction` is a non-owning view and would dangle. -- Internals renamed: `TestGroup` → `TestDescribe` (`describes` field), `RegistryState` → `TestContext`, `State()` → `GetTestContext()`, `CurrentGroup()` → `CurrentDescribe()`, `currentGroup` → `currentDescribe`. -- Targets renamed: framework library `PipeTestsLib` → `PipeTest` (alias `Pipe::TestsLib` → `Pipe::Test`); test executable `PipeTests` → `PipeTesting` → `PipeTests` (no alias, ctest `PipeTests`). - -**Tech Stack:** C++20, CMake 3.26+, no exceptions, no RTTI (`-fno-rtti`). Pipe core types: `StringView`, `String`, `TArray`, `TFunction`, `i32`, `std::function` (stored callbacks only), `p::Format`, `p::Info/Warning/Error`. - -## Global Constraints - -- Namespace is `p`; user adds `using namespace p;`. -- Coding style: CamelCase functions, `camelBack` parameters/variables, tabs, 100-col limit, `.clang-format` (Microsoft base). Comments only where code is insufficient. -- No exceptions, no RTTI project-wide. -- Prefer `StringView` over `String`. -- Minimal templates; C-like C++. -- Do NOT modify or remove Bandit or existing `*spec.cpp` files until the final task (Task 6). -- Build/test commands: - - Configure: `cmake -S . -B Build` - - Build: `cmake --build Build --config Release` - - Test: `cd Build && ctest --output-on-failure -j2 -C Release` -- All Pipe work happens in the Pipe submodule directory `Extern/Pipe` (branch `feature/pipe-tests`). Run git commands from inside `Extern/Pipe`. - ---- - -### Task 1: Add `PipeTests` library target and unconditional build - -**Files:** -- Modify: `Extern/Pipe/CMakeLists.txt` (Pipe library block ~lines 50-81; add new target after line 81) - -**Interfaces:** -- Consumes: existing `Pipe` library target. -- Produces: CMake target `PipeTests` (linkable by other targets), available unconditionally (regardless of `PIPE_BUILD_TESTS`). - -- [ ] **Step 1: Add the `PipeTests` library target** - -Append after the `Pipe` library block in `Extern/Pipe/CMakeLists.txt` (after line 81, before the `PIPE_BUILD_TESTS` block): - -```cmake -################################################################################ -# PipeTests (test framework library, not part of the runtime Pipe library) - -add_library(PipeTest STATIC Src/PipeTest.cpp) -add_library(Pipe::Test ALIAS PipeTest) -pipe_target_define_platform(PipeTests) -target_include_directories(PipeTests PUBLIC $) -pipe_target_enable_CPP20(PipeTests) -pipe_target_disable_rtti(PipeTests PRIVATE) -pipe_target_shared_output_directory(PipeTests) -target_link_libraries(PipeTests PUBLIC Pipe) -``` - -Note: `Src/PipeTest.cpp` does not exist yet; CMake will fail until Task 2 creates it. - -- [ ] **Step 2: Ensure `Src/PipeTest.cpp` is excluded from the `Pipe` library glob** - -The `Pipe` library compiles `Src/*.cpp` via `file(GLOB_RECURSE PIPE_SOURCE_FILES CONFIGURE_DEPENDS Src/*.cpp Src/*.c)` (line 65). `PipeTests.cpp` in `Src/` would be globbed into `Pipe`. Since the git repo does not track glob output, verify the exclusion after Task 2 by confirming the `Pipe` target does not include `PipeTests.cpp` (build command in Task 2 will confirm). -**If needed**: remove `Src/PipeTest.cpp` match from the glob by excluding subdirectory — glob includes it. To keep `PipeTests.cpp` out of `Pipe`, place it under a subdirectory instead: put the implementation at `Src/Tests/PipeTest.cpp` (not `Src/PipeTest.cpp`), and point the `PipeTests` target at `Src/Tests/PipeTest.cpp`. The `Pipe` glob `Src/*.cpp` (non-recursive at top level only matches `PipeTests.cpp` if directly in `Src/`; the actual glob is `GLOB_RECURSE ... Src/*.cpp` which is recursive and WILL pick up `Src/Tests/PipeTest.cpp`). - -**Decision (must-follow):** Place the implementation at `Src/Tests/PipeTest.cpp` and exclude the `Src/Tests` directory from the `Pipe` source glob. Modify the `Pipe` glob (line 65) to exclude the `PipeTests` implementation: - -```cmake -file(GLOB_RECURSE PIPE_SOURCE_FILES CONFIGURE_DEPENDS Src/*.cpp Src/*.c) -list(FILTER PIPE_SOURCE_FILES EXCLUDE REGEX ".*/Src/Tests/.*") -target_sources(Pipe PRIVATE ${PIPE_SOURCE_FILES}) -``` - -Then the `PipeTests` target in this task uses `Src/Tests/PipeTest.cpp`: - -```cmake -add_library(PipeTest STATIC Src/Tests/PipeTest.cpp) -``` - -- [ ] **Step 3: Configure + build (may fail until Task 2 creates the source)** - -Run (from `Extern/Pipe`): -``` -cmake -S . -B Build -cmake --build Build --config Release -``` -Expected: fails only because `Src/Tests/PipeTest.cpp` (and `Include/PipeTest.h`) do not exist yet. This is acceptable mid-plan; the target is created and validated in Task 2. - -- [ ] **Step 4: Commit** - -```bash -git add CMakeLists.txt -git commit -m "build: add PipeTests library target" -``` - ---- - -### Task 2: `PipeTest.h` public header — registration functions - -**Files:** -- Create: `Extern/Pipe/Include/PipeTest.h` - -**Interfaces:** -- Consumes: `Pipe/Core/Log.h` (for error logging), `StringView.h`. -- Produces (used by Tasks 3-6): - - `void Spec(StringView name, TFunction fn)` - - `void Spec(TFunction fn)` (nameless) - - `void Describe(StringView name, TFunction fn)` - - `void It(StringView name, std::function fn)` (owning: stored until run) - - `void XIt(StringView name, std::function fn)` (owning) - - `void BeforeEach(std::function fn)` (owning) - - `void AfterEach(std::function fn)` (owning) - - `struct TestSettings { StringView filter; }` - - `int RunTests(const TestSettings& settings)` - - `int RunTests(int argc, char** argv)` (parses argv, forwards to the above) - -- [ ] **Step 1: Declare the registration API** - -Create `Extern/Pipe/Include/PipeTest.h`: - -```cpp -// Copyright 2015-2026 Piperift. All Rights Reserved. - -#pragma once - -#include "Pipe/Core/StringView.h" - -#include - - -namespace p -{ - /** - * Test framework for Pipe and Rift. - * Imgui-style global context: registration functions act on a current describe. - * Spec opens a first-level describe; Describe/It/BeforeEach/AfterEach attach to - * the current describe as functions are called. Spec calls live at file scope - * and auto-register via static init (like go_bandit); no macros, no manual - * registration calls needed. - */ - - // Self-registering top-level. Spec(name, fn) also opens a first describe named `name`. - void Spec(StringView name, TFunction fn); - // Nameless top-level (like go_bandit); use Describe inside fn. - void Spec(TFunction fn); - - // Nested describe. Only valid inside a Spec; otherwise logs an error and ignores. - void Describe(StringView name, TFunction fn); - // Register a runnable test in the current describe (body stored until RunTests). - void It(StringView name, std::function fn); - // Register a disabled test; never run. - void XIt(StringView name, std::function fn); - // Setup hook attached to the current describe. - void BeforeEach(std::function fn); - // Teardown hook attached to the current describe. - void AfterEach(std::function fn); - - struct TestSettings - { - StringView filter; // empty = run all; else substring match on full test name - }; - - int RunTests(const TestSettings& settings); - int RunTests(int argc, char** argv); -}; // namespace p -``` - -- [ ] **Step 2: Commit** - -```bash -git add Include/PipeTest.h -git commit -m "feat: declare PipeTests registration API" -``` - ---- - -### Task 3: `PipeTests.cpp` — registry, cursor, runner (skip + summary) - -**Files:** -- Create: `Extern/Pipe/Src/Tests/PipeTest.cpp` - -**Interfaces:** -- Consumes: `PipeTest.h`, `Pipe.h`, `Pipe/Core/Log.h`, `PipeStrings.h`, `StringView.h`, `TArray`. -- Produces: implementation of `Spec`, `Describe`, `It`, `XIt`, `BeforeEach`, `AfterEach`, `RunTests`. Matcher `Expect` is a separate task (Task 4); until then `It` bodies cannot assert. - -The runner must support: -- Building a registered tree of groups and tests. -- Running each test, invoking `BeforeEach`/`AfterEach` hooks of the enclosing groups (outer → Test beforeEach first, test body, then AfterEach in reverse-within-group order). -- Skipping `XIt` tests (counted as skipped, not failures). -- Printing pass/fail/skip counts and failed test full names/locations. -- Returning `0` on success, non-zero if any test failed. - -- [ ] **Step 1: Implement the registry data structure** - -```cpp -// Copyright 2015-2026 Piperift. All Rights Reserved. - -#ifndef P_OVERRIDE_NEWDELETE - #define P_OVERRIDE_NEWDELETE 1 -#endif -#if P_OVERRIDE_NEWDELETE - #include "PipeNewDelete.h" -#endif - -#include "PipeTest.h" -#include "Pipe.h" -#include "Pipe/Core/Log.h" -#include "PipeStrings.h" - -#include - - -namespace p -{ - namespace - { - struct TestCase - { - String name; - std::function body; - bool skip = false; - }; - - struct TestGroup - { - String name; - TArray groups; // nested describes - TArray tests; // its - std::function beforeEach; - std::function afterEach; - }; - - // Entire registered suite (treat as a single virtual root group). - struct RegistryState - { - TestGroup root{"", {}, {}, {}, {}}; - - // Pointer into `root.groups` for the currently-adding group. - TestGroup* currentGroup = nullptr; - int failedTests = 0; - int runTests = 0; - int skippedTests = 0; - }; - - // Function-local static: initialized on first use regardless of the - // static-init order of other translation units. - RegistryState& State() - { - static RegistryState state; - return state; - } - - TestGroup*& CurrentGroup() - { - return State().currentGroup; - } - } // namespace -``` - -Note: the registry uses a function-local `static` (`GetTestContext()`). This avoids static-init-order hazards when `Spec` runs at file scope during static init, and keeps all mutable suite state in one lazily-created object. `GetTestContext()`/`CurrentDescribe()` are file-local accessors used by every registration function. (Task 3's code sketches below use the pre-rename identifiers `RegistryState`/`State()`/`TestGroup`/`currentGroup`; read them as `TestContext`/`GetTestContext()`/`TestDescribe`/`currentDescribe`, with `groups` → `describes`.) - -Note: `String` and `TArray` require `PipeStrings.h`/`PipeContainers.h` — included via `Pipe.h`? `Pipe.h` only includes `StringView.h` + `Export.h`. Include `PipeStrings.h` explicitly (done above). Ensure `PipeTests.cpp` links against Pipe (done in Task 1 via `target_link_libraries(PipeTests PUBLIC Pipe)`). - -Because `TestGroup` contains `TArray`, `TArray` must be usable with incomplete types or we must define nested groups via `std::vector`. Since Pipe disallows `std::vector` in headers but this is a `.cpp`, use `TArray`. If `TArray` fails to compile due to incomplete type during the initial member declaration, add a forward `struct TestGroup;` before `TestGroup` and store `TArray`. Prefer keeping the above form; adjust only if the compiler requires it. - -- [ ] **Step 2: Implement registration functions** - -```cpp - void Spec(StringView name, std::function fn) - { - TestGroup group; - group.name = String{name}; - root.groups.Emplace(Move(group)); - TestGroup* groupPtr = &root.groups.Back(); - groupPtr->beforeEach = nullptr; - groupPtr->afterEach = nullptr; - currentGroup = groupPtr; - fn(); - currentGroup = nullptr; - } - - void Spec(std::function fn) - { - currentGroup = &root; - fn(); - currentGroup = nullptr; - } - - void Describe(StringView name, std::function fn) - { - if (!currentGroup) - { - Error("PipeTests: Describe('{}') called outside a Spec. Ignoring.", name); - return; - } - - TestGroup group; - group.name = String{name}; - currentGroup->groups.Emplace(Move(group)); - TestGroup* prevGroup = currentGroup; - currentGroup = ¤tGroup->groups.Back(); - fn(); - currentGroup = prevGroup; - } - - void It(StringView name, std::function fn) - { - if (!currentGroup) - { - Error("PipeTests: It('{}') called outside a Spec. Ignoring.", name); - return; - } - TestCase test; - test.name = String{name}; - test.body = fn; - test.skip = false; - currentGroup->tests.Emplace(Move(test)); - } - - void XIt(StringView name, std::function fn) - { - if (!currentGroup) - { - Error("PipeTests: XIt('{}') called outside a Spec. Ignoring.", name); - return; - } - TestCase test; - test.name = String{name}; - test.body = fn; - test.skip = true; - currentGroup->tests.Emplace(Move(test)); - } - - void BeforeEach(std::function fn) - { - if (!currentGroup) - { - Error("PipeTests: BeforeEach called outside a Spec. Ignoring."); - return; - } - currentGroup->beforeEach = fn; - } - - void AfterEach(std::function fn) - { - if (!currentGroup) - { - Error("PipeTests: AfterEach called outside a Spec. Ignoring."); - return; - } - currentGroup->afterEach = fn; - } -``` - -Note: The nameless `Spec(fn)` sets `currentGroup = &root`, which is a group with no name/beforeEach/afterEach and whose `groups`/`tests` are unused (it acts as a namespace root). Describe/It add into `root.groups`/`root.tests` directly. This matches bandit's `go_bandit` behavior. The `Error(StringView, Args...)` overload exists in `Log.h` (verified). - -Verify the exact `TArray` API names: Pipe's `TArray` uses `Emplace`, `Push`, `Back`, `Size`, `Pop`. `Emplace` and `Back` are used in the code above. If the exact member names differ (e.g. `Add` instead of `Emplace`), adjust to Pipe's actual API shown in `Extern/Pipe/Include/PipeContainers.h` (line 768 `struct TArray`). Double-check `root{"", nullptr, {}, {}, {}, {}}` aggregate init is valid for the struct's member order: `name`, `groups`, `tests`, `beforeEach`, `afterEach`. Reorder the init to match the declared order: - -```cpp -TestGroup root{"", {}, {}, {}, {}}; -``` - -- [ ] **Step 3: Implement the runner** - -```cpp - static String FullName(const TestGroup& group, const TestCase& test) - { - // Build "SpecName.SubGroup.TestName" for reporting. Root has empty name. - String result; - if (!group.name.empty()) - { - result += group.name; - result += "."; - } - result += test.name; - return result; - } - - static void RunTest(const TestCase& test, const TestGroup& group) - { - if (test.skip) - { - ++skippedTests; - return; - } - - ++runTests; - - // Run the enclosing beforeEach hooks (outer groups first is handled by - // recursion in RunGroup; here only the single group-level beforeEach - // applies. For nested groups, RunTest is called with the innermost - // group; BeforeEach hooks of outer groups are collected in RunRecogn. - if (group.beforeEach) - { - group.beforeEach(); - } - - bool passed = true; - try - { - test.body(); - } - catch (...) - { - passed = false; - Error("PipeTests: test failed by exception"); - } - - if (group.afterEach) - { - group.afterEach(); - } - - if (passed) - { - Info(" [PASS] {}", FullName(group, test)); - } - else - { - ++failedTests; - Error(" [FAIL] {}", FullName(group, test)); - } - } -``` - -Note on nested groups & BeforeEach: The above only runs the innermost group's beforeEach/afterEach. To match bandit (which runs BeforeEach of **all** enclosing groups outer→inner, then test, then AfterEach inner→outer), the runner must recurse. Implement `RunGroup` to: for each nested group, call `RunGroup` (which itself runs that group's `beforeEach` and `afterEach` around its tests AND its nested groups); for each test, run it. To run the contained `beforeEach` for nested recursion, structure as: - -```cpp - static void RunGroup(TestGroup& group) - { - for (TestGroup& sub : group.groups) - { - RunGroup(sub); - } - for (TestCase& test : group.tests) - { - // Manual hook application with proper nesting depth handled below. - RunTest(test, group); - } - } -``` - -To correctly nest hooks across levels, thread a stack: pass a `TArray>&` of active beforeEach hooks and a matching afterEach stack. Simplest correct approach that matches bandit semantics: - -```cpp - static void RunNested(TestGroup& group, - TArray>& beforeHooks, - TArray>& afterHooks) - { - if (group.beforeEach) - { - beforeHooks.Emplace(group.beforeEach); - } - if (group.afterEach) - { - afterHooks.Emplace(group.afterEach); - } - - for (TestGroup& sub : group.groups) - { - RunNested(sub, beforeHooks, afterHooks); - } - - for (TestCase& test : group.tests) - { - if (test.skip) - { - ++skippedTests; - continue; - } - ++runTests; - - for (auto& hook : beforeHooks) - { - hook(); - } - - bool passed = true; - try - { - test.body(); - } - catch (...) - { - passed = false; - Error("PipeTests: test failed by exception: {}", FullName(group, test)); - } - - for (sizet i = afterHooks.Size(); i > 0; --i) - { - afterHooks[i - 1](); - } - - if (passed) - { - Info(" [PASS] {}", FullName(group, test)); - } - else - { - ++failedTests; - Error(" [FAIL] {}", FullName(group, test)); - } - } - - if (group.beforeEach) - { - beforeHooks.Pop(); - } - if (group.afterEach) - { - afterHooks.Pop(); - } - } - - int RunTests(int argc, char** argv) - { - (void)argc; // kept for future --filter support - (void)argv; - - Info("PipeTests: {} group(s) registered.", root.groups.Size()); - TArray> beforeHooks; - TArray> afterHooks; - RunNested(root, beforeHooks, afterHooks); - - Info("PipeTests complete: {} run, {} passed, {} failed, {} skipped.", - runTests, runTests - failedTests, failedTests, skippedTests); - - return failedTests == 0 ? 0 : 1; - } -``` - -Use the `RunNested` version (correct nested BeforeEach/AfterEach semantics). Verify `TArray` supports `Emplace(std::function)` (it stores by value; `std::function` is default-constructible and move-assignable — fine). `Pop()` removes last element. - -- [ ] **Step 4: Configure + build** - -Run (from `Extern/Pipe`): -``` -cmake -S . -B Build -cmake --build Build --config Release -``` -Expected: target `PipeTests` builds, `Pipe` library does NOT include `PipeTests.cpp`. If `/Src/Tests/` is still picked up by `Pipe`'s glob, fix the `list(FILTER ...)` exclusion (Task 1) and rebuild. - -- [ ] **Step 5: Commit** - -```bash -git add Src/Tests/PipeTest.cpp -git commit -m "feat: add PipeTests registry and runner" -``` - ---- - -### Task 4: Self-test the framework (small new tests, Bandit untouched) - -**Files:** -- Create: `Extern/Pipe/Tests/PipeTests/PipeTests.spec.cpp` -- Create: `Extern/Pipe/Tests/PipeTests/main.cpp` - -**Interfaces:** -- Consumes: `PipeTest.h`, `Pipe.h`, `p::Expect` (Task 5). To avoid depending on Task 5, implement this task to compile against the header and **defer the actual `Expect` matcher to Task 5**, adding assertions there in step 3. The framework uses no macros (`Spec` at file scope auto-registers); assertions arrive with `Expect` in Task 5. - -- Produces: a second test executable `PipeTestsSelf` registered in CTest, proving the framework runs alongside the untouched Bandit suite. - -- [ ] **Step 1: Create the self-test spec + runner** - -`Extern/Pipe/Tests/PipeTests/main.cpp`: - -```cpp -// Copyright 2015-2026 Piperift. All Rights Reserved. - -// NOTE: PipeNewDelete is deliberately not included here. PipeTests provides the -// replacement operator new/delete (P_OVERRIDE_NEWDELETE) in its own translation unit; -// including it here too would cause duplicate-definition linker errors. - -#include -#include - - -int main(int argc, char* argv[]) -{ - p::Initialize(); - int result = p::RunTests(argc, argv); - p::Shutdown(); - return result; -} -``` - -`Extern/Pipe/Tests/PipeTests/PipeTests.spec.cpp` (`Spec` at file scope auto-registers; no macros): - -```cpp -// Copyright 2015-2026 Piperift. All Rights Reserved. - -#include - -#include - -using namespace p; - -static int beforeEachCount = 0; -static int afterEachCount = 0; -static int topTestResult = 0; - - -Spec("PipeTests", []() -{ - BeforeEach([]() { ++beforeEachCount; }); - AfterEach([]() { ++afterEachCount; }); - - Describe("Basics", []() - { - It("Registers and runs", []() { topTestResult = 42; }); - XIt("Is skipped", []() { topTestResult = -1; }); - }); -}); - - -// NOTE: assertions are added in Task 5 once Expect() exists. -``` - -- [ ] **Step 2: Wire a separate CTest target** - -`Extern/Pipe/Tests/CMakeLists.txt` — add after the existing `PipeTests` executable block, **without modifying** the existing Bandit-based target or its `--reporter=spec`: - -Because the existing `PipeTests/CMakeLists.txt` uses `file(GLOB_RECURSE ...)` and adds its own `PipeTests` executable, adding a second executable in the same glob would collide (two `main.cpp`). Instead, add a **separate subdirectory** `Tests/PipeTests/` with its own `CMakeLists.txt`: - -Create `Extern/Pipe/Tests/PipeTests/CMakeLists.txt`: - -```cmake -add_executable(PipeTestsSelf PipeTests.spec.cpp main.cpp) -pipe_target_define_platform(PipeTestsSelf) -pipe_target_enable_CPP20(PipeTestsSelf) -pipe_target_disable_rtti(PipeTestsSelf PRIVATE) -pipe_target_shared_output_directory(PipeTestsSelf) -target_link_libraries(PipeTestsSelf PUBLIC PipeTests Pipe) -add_test(NAME PipeTestsSelf COMMAND $) -``` - -And in `Extern/Pipe/Tests/CMakeLists.txt`, add `add_subdirectory(PipeTests)` at the end (the parent glob will NOT recurse into `PipeTests` because the existing glob is `GLOB_RECURSE *.cpp *.h *.hpp` which WILL pick up the new `PipeTests.spec.cpp` and `main.cpp` into the Bandit-based `PipeTests` executable — causing duplicate `main()` and redefinition). To prevent that: - -- Change the parent glob to limit scope, OR -- Place the self-test under a path not matched by the parent glob, OR -- The cleanest: **exclude the `PipeTestsSelf` sources from the Bandit glob** by listing them and filtering. Simplest robust approach given CMake: rename the self-test directory so the `GLOB_RECURSE` doesn't include it is not possible (it globs everything). Instead, filter in `Tests/CMakeLists.txt`: - -```cmake -file(GLOB_RECURSE TESTS_SOURCE_FILES CONFIGURE_DEPENDS *.cpp *.h *.hpp) -list(FILTER TESTS_SOURCE_FILES EXCLUDE REGEX ".*/PipeTests/.*") -add_executable(PipeTests ${TESTS_SOURCE_FILES}) -# ... existing target_link_libraries(PipeTests PUBLIC Pipe Bandit) unchanged ... -add_subdirectory(PipeTests) -``` - -This keeps the Bandit-based `PipeTests` exe only on non-`PipeTests/` sources, and adds `PipeTestsSelf` from the subdirectory. Verify the `main()` symbol is only defined once in the Bandit exe. - -- [ ] **Step 3: Configure + build + run** - -Run (from `Extern/Pipe`): -``` -cmake -S . -B Build -cmake --build Build --config Release -ctest --test-dir Build --output-on-failure -``` -Expected: `PipeTestsSelf` appears in CTest output with `0 run, 0 passed` (no assertions yet) and reports the skipped `XIt` count (1 skipped) and 1 pass. The Bandit suite `PipeTests` also still runs (`--reporter=spec`) with its original tests. - -- [ ] **Step 4: Commit** - -```bash -git add Tests/PipeTests -git commit -m "test: add PipeTests self-test suite" -``` - ---- - -### Task 5: `Expect` fluent matcher + extensible formatter - -**Files:** -- Modify: `Extern/Pipe/Include/PipeTest.h` -- Modify: `Extern/Pipe/Src/Tests/PipeTest.cpp` - -**Interfaces:** -- Consumes: `PipeTest.h` registration API (Task 2/3), Pipe `Format`, `StringView`, `Number` concept (`TypeTraits.h`). -- Produces: `Expect(value)` matcher returned by `p::Expect(value)` with methods `ToEqual`, `ToNotEqual`, `ToBeLess`, `ToBeLessOrEqual`, `ToBeGreater`, `ToBeGreaterOrEqual`, `ToBeTrue`, `ToBeFalse`, `ToContain`, `ToNotContain`. Failure prints `file:line` + actual/expected via an extensible `ToString`-style hook (`p::TestString`). - -- [ ] **Step 1: Add the formatter hook and matcher to `PipeTest.h`** - -Append to `PipeTest.h`: - -```cpp - // Extensible value-to-string hook for failure messages. - // Specialize for user types. Default handles numbers and string views. - template - inline String TestString(const T& value); - - namespace details - { - // Format failure message from file:line + description. - P_API void Fail(const char* file, sizet line, StringView message); - } // namespace details -``` - -Implement `TestString` in the header (template): - -```cpp - template - inline String TestString(const T& value) - { - return Format("{}", value); - } - - template<> - inline String TestString(const bool& value) - { - return value ? String{"true"} : String{"false"}; - } - - template<> - inline String TestString(const char& value) - { - return String{value}; - } - - inline String TestString(const StringView value) - { - return String{value}; - } - - inline String TestString(const char* value) - { - return value ? String{value} : String{"(null)"}; - } -``` - -Note: `Format("{}", value)` requires `value` be formattable; Pipe's `STDFormat.h` and `PipeStrings.h` provide `std::format` for arithmetic and `String`/`StringView`/`const char*` (StringView formats as string view). Confirm `StringView` has a `std::formatter`; Pipe formats strings via `std::format` — check `PipeStrings.h`/`Pipe/Core/STDFormat.h` supplies a formatter for `StringView` and `String`. If `Format("{}", StringView)` does not compile, add an overload: - -```cpp - inline String TestString(const String& value) - { - return String{value}; - } -``` - -(Add if needed; Strings.format uses `std::vformat_to` which requires appropriate formatters.) - -Now the matcher class: - -```cpp - // ---- fluent assertion ---- - template - class ExpectValue - { - public: - ExpectValue(const Actual& value, const char* file, sizet line) - : value(value) - , file(file) - , line(line) - {} - - void ToEqual(const Actual& expected) const - { - if (!(value == expected)) - { - details::Fail(file, line, Format( - "Expected {} to equal {}", TestString(value), TestString(expected))); - } - } - - void ToNotEqual(const Actual& expected) const - { - if (!(value != expected)) - { - details::Fail(file, line, Format( - "Expected {} to not equal {}", TestString(value), TestString(expected))); - } - } - - void ToBeLess(const Actual& other) const - { - if (!(value < other)) - { - details::Fail(file, line, Format( - "Expected {} to be less than {}", TestString(value), TestString(other))); - } - } - - void ToBeLessOrEqual(const Actual& other) const - { - if (!(value <= other)) - { - details::Fail(file, line, Format( - "Expected {} to be less or equal to {}", TestString(value), TestString(other))); - } - } - - void ToBeGreater(const Actual& other) const - { - if (!(value > other)) - { - details::Fail(file, line, Format( - "Expected {} to be greater than {}", TestString(value), TestString(other))); - } - } - - void ToBeGreaterOrEqual(const Actual& other) const - { - if (!(value >= other)) - { - details::Fail(file, line, Format( - "Expected {} to be greater or equal to {}", TestString(value), TestString(other))); - } - } - - void ToBeTrue() const - { - if (!value) - { - details::Fail(file, line, "Expected value to be true"); - } - } - - void ToBeFalse() const - { - if (value) - { - details::Fail(file, line, "Expected value to be false"); - } - } - - void ToContain(const StringView sub) const - { - // Actual must be a string-like type. - StringView view{value}; - if (Strings::Find(view, sub) == StringView::npos) - { - details::Fail(file, line, Format( - "Expected {} to contain {}", TestString(value), TestString(sub))); - } - } - - void ToNotContain(const StringView sub) const - { - StringView view{value}; - if (Strings::Find(view, sub) != StringView::npos) - { - details::Fail(file, line, Format( - "Expected {} to not contain {}", TestString(value), TestString(sub))); - } - } - - private: - const Actual& value; - const char* file; - sizet line; - }; -``` - -Note: `ToBeTrue/ToBeFalse` require `value` convertible to bool (works for bool and pointer/integer). For `ToContain`, `StringView view{value}` requires `value` convertible to `StringView` (works for `StringView`, `const char*`, `std::string_view`, `String`). For `Expect(...).ToContain("acid")` with a `String`/`const char*` actual, `StringView view{value}` must be constructible. Confirm `String` is constructible to `StringView` (it exposes a `View` alias and an operator/conversion). If `String` does not implicitly convert, add `StringView{value.c_str(), value.size()}`. - -Finally the entry macro/function: - -```cpp - // Returns a matcher bound to the caller's source location for reporting. - template - ExpectValue Expect(const T& value, const std::source_location loc = std::source_location::current()) - { - return ExpectValue(value, loc); - } -``` - -Note: uses `std::source_location::current()` (C++20) as a default argument — it resolves to the **call site** (the user's `Expect(value)` expression), not the function definition. A default-arg `__LINE__`/`__FILE__` is WRONG on MSVC (it expands at the `Expect` definition in the header), so use `std::source_location`. This keeps the fluent macro-free `Expect(value).ToEqual(4)` usage and reports the correct failing line. - -- [ ] **Step 2: Implement `details::Fail` in the `.cpp`** - -Append to `PipeTests.cpp`: - -```cpp - namespace details - { - void Fail(const char* file, sizet line, StringView message) - { - Error("PipeTests: {}:{}: {}", file, line, message); - } - } // namespace details -``` - -`Error` is the Pipe log function (from `Log.h`, already included). This reports a failure inline; the runner counts it as a failure (Task 3 `RunNested` sets `passed=false` only on exception). **Critical:** `Fail` must mark the current test failed. Currently `RunNested` only flips `passed` on exception. Change the failure tracking: add a global `int currentTestFailed = 0;` plus `bool CurrentTestFailed()` accessor, OR have `Fail` set a global flag checked after `test.body()`. Implement: - -In the anonymous namespace add: -```cpp - int currentTestFailureCount = 0; -``` -In `details::Fail`: -```cpp - void Fail(const char* file, sizet line, StringView message) - { - Error("PipeTests: {}:{}: {}", file, line, message); - ++currentTestFailureCount; - } -``` -In `RunNested`, before running the body reset the count, after body if `currentTestFailureCount > 0` mark failed and reset: - -```cpp - currentTestFailureCount = 0; - bool passed = true; - try - { - test.body(); - } - catch (...) - { - passed = false; - Error("PipeTests: test failed by exception: {}", FullName(group, test)); - } - passed = passed && (currentTestFailureCount == 0); - if (!passed) - { - ++failedTests; - Error(" [FAIL] {}", FullName(group, test)); - } - else - { - Info(" [PASS] {}", FullName(group, test)); - } -``` - -- [ ] **Step 3: Add real assertions to the self-test** - -Update `Extern/Pipe/Tests/PipeTests/PipeTests.spec.cpp` to use `Expect`: - -```cpp -#include -#include - -using namespace p; - -Spec("PipeTests", []() { - Describe("Expect", []() { - It("ToEqual / ToNotEqual", []() { - int value = 4; - Expect(value).ToEqual(4); - Expect(value).ToNotEqual(5); - }); - It("Relational", []() { - int value = 4; - Expect(value).ToBeLess(5); - Expect(value).ToBeLessOrEqual(4); - Expect(value).ToBeGreater(3); - Expect(value).ToBeGreaterOrEqual(4); - }); - It("Booleans", []() { - bool flag = true; - Expect(flag).ToBeTrue(); - Expect(!flag).ToBeFalse(); - }); - It("Strings", []() { - Expect("acidic").ToContain("acid"); - Expect(String{"hello"}).ToNotContain("world"); - }); - It("Equals int", []() { - Expect(4).ToEqual(4); - }); - }); -}); -``` - -(note: `Expect(value).ToBeTrue()` requires `value` be usable in `if (!value)`; bool works.) - -- [ ] **Step 4: Build + run, confirm failure counts** - -Run (from `Extern/Pipe`): -``` -cmake --build Build --config Release -ctest --test-dir Build --output-on-failure -R PipeTestsSelf -``` -Expected: `PipeTestsSelf` runs the `Expect` tests, all pass (except we also want to verify a deliberate failure is counted — optional: temporarily add `Expect(1).ToEqual(2);` to confirm the FAIL path, then remove). - -Verify one failure is caught: temporarily add to a test `Expect(1).ToEqual(2);`, run, confirm `failed` count = 1 and exit non-zero, then remove it and re-run to confirm green. - -- [ ] **Step 5: Commit** - -```bash -git add Include/PipeTest.h Src/Tests/PipeTest.cpp Tests/PipeTests -git commit -m "feat: add Expect fluent matcher" -``` - ---- - -### Task 6: Remove Bandit + migrate existing tests (FINAL — only after Tasks 1-5 pass) - -**Files:** -- Modify: `Extern/Pipe/Tests/CMakeLists.txt` -- Modify: `Extern/Pipe/Tests/main.cpp` -- Modify: `Extern/Pipe/Extern/CMakeLists.txt` (remove `Bandit`) -- Delete: `Extern/Pipe/Extern/Bandit/` (vended dir) -- Modify: all `Extern/Pipe/Tests/**/*.spec.cpp` -- Modify: `Extern/Pipe/Tests/PipeTests/CMakeLists.txt` (remove self-only scope if desired) — optional; keep separate target. -- Modify: `Tests/CMakeLists.txt` (Rift) and `Tests/*.spec.cpp` (Rift) in `D:\Projects\Piperift\rift` - -**Interfaces:** -- Consumes: `PipeTest.h`, `p::RunTests` (Tasks 2-5). -- Produces: Bandit fully removed; both Pipe and Rift suites run on the native framework. - -⚠️ **This task is intentionally LAST. Do not start it until Tasks 1-5 are complete and verified.** - -- [ ] **Step 1: Migrate one reference spec file (Pipe)** - -Convert `Extern/Pipe/Tests/Core/StringView.spec.cpp`: - -Old: -```cpp -#include -#include -#include - -using namespace snowhouse; -using namespace bandit; -using namespace p; - -go_bandit([]() -{ - describe("Strings", []() - { - describe("StringView", []() - { - it("Can assign from literal", [&]() - { - StringView v{"Kiwi"}; - AssertThat(v, Equals("Kiwi")); - AssertThat(v.size(), Equals(4)); - }); - // ... other tests ... - }); - }); -}); -``` - -New: -```cpp -#include -#include -#include - -using namespace p; - - -Spec("Strings", []() -{ - Describe("StringView", []() - { - It("Can assign from literal", []() - { - StringView v{"Kiwi"}; - Expect(v).ToEqual("Kiwi"); - Expect(v.size()).ToEqual(4); - }); - // ... other tests converted similarly ... - }); -}); -``` - -Transform rules (from the spec): -- `#include ` → `#include ` -- `using namespace snowhouse; using namespace bandit;` → remove both; keep `using namespace p;` -- **Top-level:** `go_bandit([](){ describe("G", [](){ ...` → `Spec("G", [](){ ...` wrapped in the file-scope static registrar above (drop the outer `go_bandit` extra nesting and one `describe` level; the top `Spec("Strings", ...)` replaces go_bandit+first describe and auto-registers — no `main.cpp` changes). -- `describe(` → `Describe(` -- `it(` → `It(` (drop the `[&]` → `[]`; lambdas no longer need `&` capture since framework state is global) -- `xit(` → `XIt(` -- `before_each(` → `BeforeEach(` -- `after_each(` → `AfterEach(` -- `AssertThat(v, Equals(x))` → `Expect(v).ToEqual(x)` -- `AssertThat(v, !Equals(x))` → `Expect(v).ToNotEqual(x)` -- `AssertThat(x, Equals(true))` → `Expect(x).ToBeTrue()` -- `AssertThat(x, !Equals(true))` → `Expect(x).ToBeFalse()` -- `AssertThat(x, Equals(false))` → `Expect(x).ToBeFalse()` -- `AssertThat(x, Is().True())` → `Expect(x).ToBeTrue()` -- `AssertThat(x, Is().False())` → `Expect(x).ToBeFalse()` -- `AssertThat(v.size(), Equals(4u))` → `Expect(v.size()).ToEqual(4u)` - -Important: the top-level transform. Bandit files use `go_bandit([](){ describe("Strings", [](){...}) });`. Our `Spec("Strings", [](){...})` handles the `describe` level directly, so replace the pair with a single file-scope `Spec("Strings", fn)` and inside use `Describe`/`It`. For files that use `go_bandit` with a single top describe, keep that one as `Spec` and drop the now-redundant `Describe` wrapper if present. Follow the reference conversion exactly. Each converted file holds its top-level `Spec(...)` at file scope (auto-registers; no wrapper, no `main.cpp` changes). - -- [ ] **Step 2: Build + run the migrated file only (green)** - -Run (from `Extern/Pipe`): -``` -cmake --build Build --config Release -``` -Ensure `StringView.spec.cpp` id not picked up by the Bandit exe twice. The Pipe tests CMake glob (`GLOB_RECURSE *.cpp` in `Tests/CMakeLists.txt`) picks up all `spec.cpp` including the migrated one — but the Bandit exe will FAIL to compile the migrated file (it no longer includes bandit). **Must switch the whole `PipeTests` exe to the new framework now**, not incrementally. Therefore: - -**Decision:** Because `Tests/CMakeLists.txt` globs all `spec.cpp` into one `PipeTests` exe, migration must flip the entire Pipe suite at once (not file-by-file) to keep it compiling. Steps 1-3 migrate ALL Pipe spec files in one pass, then build once. Verify the whole Pipe suite passes via the new framework. - -- [ ] **Step 3: Migrate ALL remaining Pipe spec files** - -Convert every `Extern/Pipe/Tests/**/*.spec.cpp` using the transform rules above. Remove bandit includes and namespaces, map to `Spec/Describe/It/XIt/BeforeEach/AfterEach` and `Expect`. Use `[]` (no `&` capture) for lambda bodies. - -- [ ] **Step 4: Switch PipeTests exe to the new framework** - -`Extern/Pipe/Tests/CMakeLists.txt`: -Keep the suite executable `PipeTests` (no alias); link the framework library: `target_link_libraries(PipeTests PUBLIC Pipe PipeTest)` (framework library is `PipeTest`, alias `Pipe::Test`) -- Remove `--reporter=spec` from `add_test(...)`: - `add_test(NAME PipeTests COMMAND $)` -- Remove the `list(FILTER ...)` exclusion added in Task 4 (restore the plain glob) so all spec files (including migrated ones) build into `PipeTests`. - -`Extern/Pipe/Tests/main.cpp`: replace `int result = bandit::run(argc, argv);` with `int result = p::RunTests(argc, argv);`, and remove `#include `. Specs auto-register at file scope, so `main.cpp` needs no per-file calls. Keep the `p::Initialize`/`p::Shutdown` calls; `PipeNewDelete.h` no longer needs to be included here since `PipeTests` provides the override. - -`Extern/Pipe/Tests/PipeTests/CMakeLists.txt`: keep the `PipeTestsSelf` target for framework self-checks, OR fold the self-test spec files into the main `PipeTests` glob (remove the separate subdirectory). Keep `PipeTestsSelf` as-is for now (harmless), unless the main glob re-includes its files. Since the main glob is `GLOB_RECURSE *.cpp` from `Tests/`, it WILL include `Tests/PipeTests/*.cpp` again → duplicate `main()`. So when restoring the plain glob in step 4, re-apply a filter to EXCLUDE `Tests/PipeTests/` from the main `PipeTests` exe (keep `PipeTestsSelf` as a separate target): - -```cmake -file(GLOB_RECURSE TESTS_SOURCE_FILES CONFIGURE_DEPENDS *.cpp *.h *.hpp) -list(FILTER TESTS_SOURCE_FILES EXCLUDE REGEX ".*/PipeTests/.*") -add_executable(PipeTests ${TESTS_SOURCE_FILES}) -``` - -Keep `add_subdirectory(PipeTests)` for `PipeTestsSelf`. - -- [ ] **Step 5: Build + run full Pipe suite (green)** - -Run (from `Extern/Pipe`): -``` -cmake --build Build --config Release -ctest --test-dir Build --output-on-failure -``` -Expected: `PipeTests` runs all migrated tests with names/locations under the new framework; `PipeTestsSelf` still passes. Bandit no longer referenced. - -- [ ] **Step 6: Remove the Bandit dependency** - -- `Extern/Pipe/Extern/CMakeLists.txt`: remove lines 6-7 (`add_library(Bandit INTERFACE)` + include dir). -- Delete `Extern/Pipe/Extern/Bandit/` directory. -- `git rm -r Extern/Bandit` (from `Extern/Pipe`). - -- [ ] **Step 7: Migrate Rift tests + CMake** - -In `D:\Projects\Piperift\rift`: -- `Tests/CMakeLists.txt`: `target_link_libraries(RiftTests PUBLIC RiftASTLib Bandit)` → `target_link_libraries(RiftTests PUBLIC RiftASTLib Pipe::Test)` (the framework library is `PipeTest`, alias `Pipe::Test`; it is defined unconditionally in `Extern/Pipe/CMakeLists.txt` per Task 1). Rift's `Tests/main.cpp` only swaps `bandit::run` for `p::RunTests` (specs auto-register). -- Convert Rift `Tests/Project.spec.cpp`, `Tests/AST/Statements.spec.cpp`, `Tests/AST/Expressions.spec.cpp`, `Tests/AST/Namespaces.spec.cpp` per the transform rules (uses `before_each`/`after_each` → `BeforeEach`/`AfterEach`, `AssertThat(result, Equals(true))` → `Expect(result).ToBeTrue()`, etc.). Each file holds its `Spec(...)` at file scope; remove `#include ` and `using namespace snowhouse/bandit`. - -- [ ] **Step 8: Full project build + tests + format** - -Run (from `D:\Projects\Piperift\rift`): -``` -cmake --build Build --config Release -cd Build && ctest --output-on-failure -j2 -C Release -``` -and format: `cmake --build Build --target ClangFormat`. - -Expected: all green; no reference to Bandit anywhere in the build. - -- [ ] **Step 9: Commit (Pipe) + Commit (Rift)** - -```bash -# From Extern/Pipe -git add -A -git commit -m "test: replace Bandit with PipeTests framework" - -# From D:\Projects\Piperift\rift (updated submodule pointer + Rift tests + CMake) -git add Extern/Pipe Tests CMakeLists.txt -git commit -m "test: use PipeTests framework in Rift tests" -``` - -Note: the Rift commit must record the new Pipe submodule hash (`git add Extern/Pipe`). - ---- - -## Self-Review - -**Spec coverage:** -- ✅ Native `PipeTests` module in Pipe tree (Task 1-3) -- ✅ Used by both PipeTests and RiftTests (Tasks 4, 6) -- ✅ Mirrors bandit structure `Spec/Describe/It/XIt/BeforeEach/AfterEach` (Tasks 2, 3, 6) -- ✅ `Expect` fluent matcher + extensible formatter (Task 5) -- ✅ Detailed `file:line` + actual/expected (Task 5 `details::Fail`) -- ✅ Bandit removed only at the very end (Task 6), Bandit coexists during dev (Tasks 1-5) -- ✅ No runtime burden on shipped `Pipe` lib (separate target, `Src/Tests/` excluded — Task 1) -- ✅ No `ToThrow` (no exceptions matchers) — confirmed -- ✅ `Describe` misuse = log + ignore (Task 3) -- ✅ `RunTests(TestSettings)` + `RunTests(int, char**)` argv→settings forwarder (Tasks 2, 3) -- ✅ imgui-style global context, macro-free functions; `Spec` at file scope auto-registers (Task 2, 3) - -**Placeholder scan:** All steps carry concrete code. The `Expect` matcher uses `Format("{}", value)` which needs a `StringView` formatter — flagged with an explicit fallback overload if missing. Task adds a note to verify `TArray` member names and add `String` no implicit `StringView` conversion fallback. No TODO/TBD beyond explicit in-task verification notes. - -**Type consistency:** `String`, `StringView`, `sizet`, `i32`, `TFunction` (immediate callbacks) / `std::function` (stored bodies/hooks), `Number`, `TestString`, `TestSettings`, `TestContext`/`TestDescribe`, `ExpectValue`, `details::Fail(loc, message)`, `RunTests(settings)` + `RunTests(int,char**)` used consistently across tasks. \ No newline at end of file From d0d1c0fe000e407f6f9c08498c393d869e21d265 Mon Sep 17 00:00:00 2001 From: muit Date: Sun, 6 Sep 2026 11:51:20 +0200 Subject: [PATCH 24/25] Small changes to testing api --- Include/PipeTest.h | 32 ++---- Src/Tests/PipeTest.cpp | 233 ++++++++++++++++++++++------------------- 2 files changed, 134 insertions(+), 131 deletions(-) diff --git a/Include/PipeTest.h b/Include/PipeTest.h index d42d2f9d..960416dd 100644 --- a/Include/PipeTest.h +++ b/Include/PipeTest.h @@ -172,36 +172,24 @@ namespace p // Counts an assertion. Used to detect tests that ran no expects. void CountAssert(); - // True when both Actual and Expected can be viewed as a StringView (string-ish). - template - struct IsStringBoth : std::false_type - {}; - - template - struct IsStringBoth()}), - decltype(StringView{std::declval()})>> : std::true_type - {}; + // Detect types constructible into a StringView (string-ish values). + template + concept IsStringLike = requires(const T& value) { StringView{value}; }; // Compares two possibly-different types: string-ish values compare by view, // everything else uses operator==. - template::value> - struct ValuesEqual + template + bool ValuesEqual(const A& a, const E& e) { - static bool Eval(const A& a, const E& e) + if constexpr (IsStringLike && IsStringLike) { return StringView{a} == StringView{e}; } - }; - - template - struct ValuesEqual - { - static bool Eval(const A& a, const E& e) + else { return a == e; } - }; + } } // namespace details @@ -218,7 +206,7 @@ namespace p template void ToEqual(const Expected& expected) const { - if (!details::ValuesEqual::Eval(value, expected)) + if (!details::ValuesEqual(value, expected)) { details::Fail(loc, Format("Expected {} to equal {}", TestString(value), TestString(expected))); @@ -228,7 +216,7 @@ namespace p template void ToNotEqual(const Expected& expected) const { - if (details::ValuesEqual::Eval(value, expected)) + if (details::ValuesEqual(value, expected)) { details::Fail(loc, Format("Expected {} to not equal {}", TestString(value), TestString(expected))); diff --git a/Src/Tests/PipeTest.cpp b/Src/Tests/PipeTest.cpp index ebe7a161..91461f49 100644 --- a/Src/Tests/PipeTest.cpp +++ b/Src/Tests/PipeTest.cpp @@ -14,8 +14,13 @@ #include "PipeTest.h" #include "PipeTime.h" +#include + #if P_PLATFORM_WINDOWS + #include #include +#else + #include #endif @@ -44,7 +49,7 @@ namespace p // Entire registered suite (treat as a single virtual root describe). struct TestContext { - TestDescribe root{"", {}, {}, {}, {}}; + TestDescribe root; // Pointer into `root.describes` for the currently-adding describe. TestDescribe* currentDescribe = nullptr; @@ -88,6 +93,32 @@ namespace p { return GetTestContext().currentDescribe; } + + // Appends a new describe to `parent` and returns it. + TestDescribe& AddDescribe(TestDescribe& parent, StringView name) + { + TestDescribe describe; + describe.name = String{name}; + parent.describes.Add(Move(describe)); + return parent.describes.Last(); + } + + // Registers a test in the current describe. `kind` names the API + // (It/XIt) and is used in error messages. + void AddTest(StringView kind, StringView name, std::function fn, bool skip) + { + TestDescribe*& current = CurrentDescribe(); + if (!current) + { + Error("PipeTest: {}('{}') called outside a Spec. Ignoring.", kind, name); + return; + } + TestCase test; + test.name = String{name}; + test.body = Move(fn); + test.skip = skip; + current->tests.Add(Move(test)); + } } // namespace @@ -116,14 +147,8 @@ namespace p void RegisterSpec(StringView name, TFunction fn) { - TestContext& context = GetTestContext(); - TestDescribe describe; - describe.name = String{name}; - describe.beforeEach = nullptr; - describe.afterEach = nullptr; - context.root.describes.Add(Move(describe)); - TestDescribe* describePtr = &context.root.describes.Last(); - context.currentDescribe = describePtr; + TestContext& context = GetTestContext(); + context.currentDescribe = &AddDescribe(context.root, name); fn(); context.currentDescribe = nullptr; } @@ -145,43 +170,20 @@ namespace p return; } - TestDescribe describe; - describe.name = String{name}; - current->describes.Add(Move(describe)); TestDescribe* prevDescribe = current; - current = ¤t->describes.Last(); + current = &AddDescribe(*current, name); fn(); current = prevDescribe; } void It(StringView name, std::function fn) { - TestDescribe*& current = CurrentDescribe(); - if (!current) - { - Error("PipeTest: It('{}') called outside a Spec. Ignoring.", name); - return; - } - TestCase test; - test.name = String{name}; - test.body = fn; - test.skip = false; - current->tests.Add(Move(test)); + AddTest("It", name, Move(fn), false); } void XIt(StringView name, std::function fn) { - TestDescribe*& current = CurrentDescribe(); - if (!current) - { - Error("PipeTest: XIt('{}') called outside a Spec. Ignoring.", name); - return; - } - TestCase test; - test.name = String{name}; - test.body = fn; - test.skip = true; - current->tests.Add(Move(test)); + AddTest("XIt", name, Move(fn), true); } void BeforeEach(std::function fn) @@ -238,31 +240,6 @@ namespace p using Terminal::Red; using Terminal::Yellow; - // Full test name: all enclosing describe names plus the test name, - // e.g. "Containers.BitArray.Copy.Can copy empty". Used for filtering - // and failure reports, so `--only` matches any parent describe too. - static String FullName(StringView testName) - { - TestContext& context = GetTestContext(); - String result; - for (i32 i = 0; i < context.contextStack.Size(); ++i) - { - result += context.contextStack[i]; - result += '.'; - } - result += testName; - return result; - } - - // Whether a test (by full describe+it name) should run given the - // `only`/`skip` substring selection and the skip set. - static bool Matches(StringView fullName, StringView only, StringView skip) - { - const bool included = only.empty() || Strings::Contains(fullName, only); - const bool excluded = !skip.empty() && Strings::Contains(fullName, skip); - return included && !excluded; - } - // Color a string for terminal output, honoring the useColor flag. static String Colored(const char* color, StringView text) { @@ -431,9 +408,22 @@ namespace p }; // ---- Singleline reporter ---- - // bandit's `singleline` reporter: prints a live status line after each test. + // bandit's `singleline` reporter: prints a single self-overwriting + // progress line on real terminals only. On redirected streams no + // per-test progress is printed, so the output stays clean; failure + // details and totals are deferred to TestRunComplete and reported + // exactly once. struct SinglelineReporter : ITestReporter { + SinglelineReporter() + { +#if P_PLATFORM_WINDOWS + isTty = ::_isatty(::_fileno(stdout)); +#else + isTty = isatty(STDOUT_FILENO) != 0; +#endif + } + void ItSucceeded(StringView) override { PrintStatus(); @@ -457,29 +447,58 @@ namespace p void PrintStatus() { TestContext& context = GetTestContext(); - i32 run = context.runTests; - i32 failed = context.failedTests; - i32 passed = run - failed; + const i32 run = context.runTests; if (run <= 0) { Error("Could not find any tests."); return; } + if (!isTty) + { + return; + } + DrawLine(StatusLine(context)); + } - Info("Executed {} tests.", run); - if (failed == 0) + void TestRunComplete() override; + + private: + // bandit's live status line: only includes the succeeded/failed + // counts once something has failed, with the failed count red. + static String StatusLine(const TestContext& context) + { + const i32 run = context.runTests; + if (context.failedTests == 0) { - if (failed <= 0) - {} - Info("{}\n {} failed.", run, passed, Colored(Red, Format("{}", failed))); + return Format("Executed {} tests.", run); } - else + return Format("Executed {} tests. {} succeeded. {}", run, run - context.failedTests, + Colored(Red, Format("{} failed.", context.failedTests))); + } + + // Overwrites the current line in place on a real terminal (console + // API on Windows, carriage-return elsewhere). + void DrawLine(StringView text) + { +#if P_PLATFORM_WINDOWS + const HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); + if (handle != INVALID_HANDLE_VALUE) { - Info("Executed {} tests.", run); + CONSOLE_SCREEN_BUFFER_INFO info; + if (GetConsoleScreenBufferInfo(handle, &info) != 0) + { + const COORD position = {0, info.dwCursorPosition.Y}; + SetConsoleCursorPosition(handle, position); + WriteConsoleA( + handle, text.data(), static_cast(text.size()), nullptr, nullptr); + return; + } } +#endif + std::cout << '\r' << text << std::flush; } - void TestRunComplete() override; + bool isTty = false; }; // ---- Info reporter (verbose with timing support) ---- @@ -672,6 +691,20 @@ namespace p void SinglelineReporter::TestRunComplete() { + TestContext& context = GetTestContext(); + + // Final live line mirrors bandit's singleline reporter: executed/ + // succeeded/failed totals right before the failure summary. + const String line = StatusLine(context); + if (isTty) + { + DrawLine(line); + std::cout << std::endl; + } + else + { + Info("{}", line); + } WriteSummary(); } @@ -706,7 +739,16 @@ namespace p // tests marked skip, are reported as SKIPPED, not hidden. // With break-on-failure, everything after the first failure // is skipped too. - if (test.skip || !Matches(FullName(test.name), only, skip) + String fullName; + for (i32 i = 0; i < context.contextStack.Size(); ++i) + { + fullName += context.contextStack[i]; + fullName += '.'; + } + fullName += test.name; + const bool included = only.empty() || Strings::Contains(fullName, only); + const bool excluded = !skip.empty() && Strings::Contains(fullName, skip); + if (test.skip || !included || excluded || (breakOnFailure && context.encounteredFailure)) { ++context.skippedTests; @@ -745,8 +787,6 @@ namespace p afterHooks[i - 1](); } - String full = FullName(test.name); - if (passed) { if (context.currentTestAssertCount == 0) @@ -765,14 +805,14 @@ namespace p if (unknown) { reporter.ItUnknownError(test.name); - context.failures.Add(full + ":\nUnknown exception\n"); + context.failures.Add(fullName + ":\nUnknown exception\n"); } else { reporter.ItFailed(test.name); String detail = context.currentFailureDetail; - context.failures.Add( - detail.empty() ? (full + ":\n") : (full + ":\n" + detail + "\n")); + context.failures.Add(detail.empty() ? (fullName + ":\n") + : (fullName + ":\n" + detail + "\n")); } } } @@ -921,9 +961,12 @@ namespace p { settings.skip = Strings::RemoveFromStart(arg, StringView{"--skip="}); } - else if (Strings::StartsWith(arg, StringView{"--reporter="})) + else if (Strings::StartsWith(arg, StringView{"-r="}) + || Strings::StartsWith(arg, StringView{"--reporter="})) { - const StringView name = Strings::RemoveFromStart(arg, StringView{"--reporter="}); + StringView name = Strings::RemoveFromStart(arg, StringView{"-r="}); + name = Strings::RemoveFromStart(name, StringView{"--reporter="}); + if (Strings::Equals(name, StringView{"dots"})) { settings.reporter = TTypeId(); @@ -942,35 +985,7 @@ namespace p } else { - Warning("PipeTest: unknown reporter '{}'. Using 'dots'.", name); - } - } - else if (Strings::Equals(arg, StringView{"--reporter"}) - || Strings::Equals(arg, StringView{"-r"})) - { - if (i + 1 < argc) - { - const StringView name{argv[++i]}; - if (Strings::Equals(name, StringView{"dots"})) - { - settings.reporter = TTypeId(); - } - else if (Strings::Equals(name, StringView{"singleline"})) - { - settings.reporter = TTypeId(); - } - else if (Strings::Equals(name, StringView{"spec"})) - { - settings.reporter = TTypeId(); - } - else if (Strings::Equals(name, StringView{"info"})) - { - settings.reporter = TTypeId(); - } - else - { - Warning("PipeTest: unknown reporter '{}'. Using 'spec'.", name); - } + Warning("PipeTest: unknown reporter '{}'. Using default.", name); } } else if (Strings::Equals(arg, StringView{"--report-timing"})) From 972a21df4a685771eacc84839adcde271ce0a6d8 Mon Sep 17 00:00:00 2001 From: muit Date: Mon, 7 Sep 2026 00:16:31 +0200 Subject: [PATCH 25/25] Added custom reporters and documentation --- CMakeLists.txt | 14 -- Docs/PipeTest.md | 234 +++++++++++++++++++++++++++++++++ Include/Pipe/Core/StringView.h | 28 ++++ Include/PipeTest.h | 50 +++++-- Src/{Tests => }/PipeTest.cpp | 101 +++++++------- Tests/CMakeLists.txt | 2 +- 6 files changed, 343 insertions(+), 86 deletions(-) create mode 100644 Docs/PipeTest.md rename Src/{Tests => }/PipeTest.cpp (92%) diff --git a/CMakeLists.txt b/CMakeLists.txt index e3c8be61..75c3021c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,7 +63,6 @@ pipe_target_define_platform(Pipe) target_include_directories(Pipe PUBLIC $) target_include_directories(Pipe PRIVATE $) file(GLOB_RECURSE PIPE_SOURCE_FILES CONFIGURE_DEPENDS Src/*.cpp Src/*.c) -list(FILTER PIPE_SOURCE_FILES EXCLUDE REGEX ".*/Src/Tests/.*") target_sources(Pipe PRIVATE ${PIPE_SOURCE_FILES}) target_compile_definitions(Pipe PUBLIC P_VERSION="${PROJECT_VERSION}") target_compile_definitions(Pipe PRIVATE NOMINMAX) @@ -83,19 +82,6 @@ pipe_target_shared_output_directory(Pipe) pipe_target_disable_rtti(Pipe PRIVATE) -################################################################################ -# PipeTest (test framework library, not part of the runtime Pipe library) - -add_library(PipeTest STATIC Src/Tests/PipeTest.cpp) -add_library(Pipe::Test ALIAS PipeTest) -pipe_target_define_platform(PipeTest) -target_include_directories(PipeTest PUBLIC $) -pipe_target_enable_CPP20(PipeTest) -pipe_target_disable_rtti(PipeTest PRIVATE) -pipe_target_shared_output_directory(PipeTest) -target_link_libraries(PipeTest PUBLIC Pipe) - - ################################################################################ # Pipe Tests (compiled) executable diff --git a/Docs/PipeTest.md b/Docs/PipeTest.md new file mode 100644 index 00000000..dc224011 --- /dev/null +++ b/Docs/PipeTest.md @@ -0,0 +1,234 @@ +--- +title: Pipe Test +--- +**Header:** [``](https://github.com/PipeRift/pipe/blob/main/Include/PipeTest.h)`] +**Namespace:** `p` +**Uses:** [`PipeReflect`](./PipeECS.md) `PipeStrings` `PipeTime` + +## Overview + +`PipeTest` is test framework built into Pipe, used by the library to test itself. +It is heavily inspired by `Bandit`, following similar APIs and outputs to fully support existing IDE tools built for that library. + +```cpp +#include + +using namespace p; + +P_SPEC("Math", []() +{ + BeforeEach([]() { /* runs before each test */ }); + + It("clamps to range", []() + { + Expect(Clamp(5, 0, 3)).ToEqual(3); + }); + + XIt("disabled case", []() + { + // never runs + }); + + AfterEach([]() { /* runs after each test */ }); +}); +``` + +## Philosophy + +A few guiding decisions shape the whole test API. + +- **Macro-free registration:** Registration of tests (`Describe`, `It`, `XIt`, `BeforeEach`, `AfterEach`) are plain functions. The only macro is `P_SPEC`, which it is just syntax sugar to statically register an spec. +- **Immediate global context:** All registration functions live in namespace `p` and operate on a global `TestContext`. +- **Fluent assertions:** `Expect(x)` followed by `ToEqual(y)`, `ToBeTrue()`, `ToContain(sub)`, etc to cover many different testing scenarios. +- **Extensible value formatting:** Failure messages are messaged in a human-readable way, and extensible by the user for new types. +- **Mirror bandit's vocabulary:** `Describe`/`It`/`XIt`/`BeforeEach`/`AfterEach`, `--only`/`--skip`/`--reporter`/`--dry-run`, `Spec`/`Dots`/`Singleline`/`Info` reporters. Migrating existing tests is trivial. +- **No exceptions / no RTTI:** Pipe doesn't require or use RTTI. This is the same with PipeTests. + +## Writing Tests +On any cpp file in our test module, we can define one or more specs. + +```cpp +// Specs register tests inside and optionally adds root Describe("Files.Paths") +P_SPEC("Files.Paths", []() +{ + BeforeEach([]() { /* setup */ }); + + Describe("Join", []() + { + // Describes group all tests inside. They can be nested. + It("joins two segments", []() + { + Expect(Join("a", "b")).ToEqual("a/b"); + }); + + It("joins three segments", []() + { + Expect(Join("a", "b", "c")).ToEqual("a/b/c"); + }); + + XIt("join four segments", []() { /* 'XIt' are disabled tests */ }); + }); + + It("top-level test", []() + { + Expect(true).ToBeTrue(); + Expect(false).ToBeFalse(); + }); + + AfterEach ([]() { /* teardown */ }); +}); +``` + +> [!Note] +> Because registration runs from a constructor (`SpecAutoRegister`), `P_SPEC` can only appear at namespace scope. Inside a function, register the spec directly with `RegisterSpec(name, callback)`. + +## Running Tests + +`PipeTest` exposes two runner entry points: + +```cpp +int RunTests(const TestSettings& settings); +int RunTests(int argc, char** argv); +``` + +The second overload parses CLI args into `TestSettings`. + +In CLI, simply call your test binary. You can use these args: + +| Argument | Effect | +| ------------------------------------- | --------------------------------------------------------------- | +| `--only=` | Run only describes/its whose full name contains `` | +| `--skip=` | Skip describes/its whose full name contains `` | +| `--break-on-failure` | Stop on the first failing test | +| `--dry-run`, `-l`, `--list` | Report the full tree as SKIPPED, run nothing | +| `--report-timing` | Report per-test and total run durations | +| `-r=`, `--reporter=` | Select reporter by (case-insensitive) name — see [Reporter Name Lookup](#Reporter-Name-Lookup) | +| `--colorizer=off`, `--no-color`, `-c` | Disable colorized output | +| `--version` | Print Pipe version and exit | +| `--help` | Print usage and exit | + +## Reporters + +Reporters share the same per-test execution flow but format the run their own way: + +| Reporter | Style | +| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `spec`
(default) | Indented contexts, `- it ... OK` lines, summary block. Compatible with the Bandit VSCode adapter (one tab per indent level — any other indent breaks hierarchy detection). | +| `dots` | One character per test, color-coded, wrapped at a fixed line width. | +| `singleline` | Live overwriting status line on real terminals (`Executed N tests.`). On redirected streams it prints nothing per-test, only the final summary. | +| `info` | Verbose: `begin/end ` lines, per-context totals (`{n} total`, `{n} skipped`, `{n} failed`), `[ PASS ]`/`[ FAIL ]`/`-ERROR->` test lines, and a final failures list. | + +`--report-timing` adds a colored duration suffix (e.g. ` (0.00012s)` / `(0.4us)`) to every test line and a total-time line at the end in the `info` reporter. + +Custom reporters are supported by inheriting `p::ITestReporter`. +They can be called by their name (`--reporter=SpecialReporter` or just `--reporter=Special`): +```cpp +struct SpecialReporter : p::ITestReporter +{ + using Super = p::ITestReporter; + P_STRUCT(SpecialReporter) + + // ... override the reporter callbacks ... +}; +``` + +## Filters & Behavior + +- Tests not matched by `--only`, matched by `--skip`, marked `XIt`, or after a `--break-on-failure` trigger are all reported as **SKIPPED** (bandit semantics). They are not hidden — only excluded from execution. +- Failures are buffered during the run and emitted by the reporter at the end, so they don't interleave with deferred context output. + +--- + +## Reference +### `P_SPEC` Macro +Automatically registers an battery of tests. + +```cpp +P_SPEC("Topic", []() +{ + // body +}); +``` + +```cpp +#define P_SPEC static const p::SpecAutoRegister P_CAT(_pipeSpecReg_, __COUNTER__) +``` +### Registration Functions + +All in namespace `p`. Bodies in `P_SPEC` / `Describe` are stored as `TFunction` (non-owning, called immediately during registration). Test bodies and hooks are stored as `std::function` (owning) because they run later from `RunTests`. + +| Function | Signature | Purpose | +| ------------------ | -------------------------------------------------------------- | ---------------------------------------------------------------------------------- | +| `RegisterSpec` | `(StringView name, TFunction fn)` | Internal: pushes a named first-level describe, runs `fn`, pops. | +| `RegisterSpec` | `(TFunction fn)` | Runs `fn` against the virtual root describe. | +| `SpecAutoRegister` | ctor `(StringView, TFunction)` / `(TFunction)` | Static-init registrar used by `P_SPEC`. | +| `Describe` | `(StringView name, TFunction fn)` | Push a nested describe. Runtime-checked inside a `Spec`; logs + ignores otherwise. | +| `It` | `(StringView name, std::function fn)` | Register a runnable leaf test. | +| `XIt` | `(StringView name, std::function fn)` | Register a leaf test that is never run. | +| `BeforeEach` | `(std::function fn)` | Attach a setup hook to the current describe. | +| `AfterEach` | `(std::function fn)` | Attach a teardown hook to the current describe. | + +### `Expect(value)` — Fluent Matchers + +`Expect(value, loc = std::source_location::current())` returns an `ExpectValue` bound to the caller's `file:line`. The matchers available today: + +| Matcher | Signature | Failure message | +|---------|-----------|-----------------| +| `ToEqual(expected)` | `template void ToEqual(const E&) const` | `Expected {actual} to equal {expected}` | +| `ToNotEqual(expected)` | `template void ToNotEqual(const E&) const` | `Expected {actual} to not equal {expected}` | +| `ToBeLess(other)` | `void ToBeLess(const Actual&) const` | `Expected {actual} to be less than {other}` | +| `ToBeLessOrEqual(other)` | `void ToBeLessOrEqual(const Actual&) const` | `Expected {actual} to be less or equal to {other}` | +| `ToBeGreater(other)` | `void ToBeGreater(const Actual&) const` | `Expected {actual} to be greater than {other}` | +| `ToBeGreaterOrEqual(other)` | `void ToBeGreaterOrEqual(const Actual&) const` | `Expected {actual} to be greater or equal to {other}` | +| `ToBeTrue()` | `void ToBeTrue() const` | `Expected value to be true` | +| `ToBeFalse()` | `void ToBeFalse() const` | `Expected value to be false` | +| `ToContain(sub)` | `void ToContain(StringView) const` | `Expected {actual} to contain {sub}` | +| `ToNotContain(sub)` | `void ToNotContain(StringView) const` | `Expected {actual} to not contain {sub}` | + +- **String-aware equality.** `ToEqual`/`ToNotEqual` treat string-like operands (`StringView`, `const char*`, `std::string`, `p::String`) as strings and compare via `StringView`; everything else uses `operator==`. +- **Containment.** `ToContain`/`ToNotContain` require a `StringView` substring; the actual value is constructed into a `StringView` view for the search. +- **Source location.** The matcher's `file:line` is captured at the call site via `std::source_location` defaults, so failure reports point at the assertion line, not inside the framework. +- **Assertion counter.** Every matcher increments an internal counter. Tests that ran zero asserts are surfaced by reporters as "no assertions" rather than `[ PASS ]`. + +## `TestString` — Custom Value Formatting + +Failure messages format actual/expected values through `String TestString(const T&)`. The default implementation handles two cases: +- **Formattable types** (`std::formatter, char>` exists): `Format("{}", value)`. +- **Non-formattable types** (structs, byte views): a generic placeholder `` so the framework stays compilable for opaque types. + +Predefined specializations and overloads: `bool` (`true`/`false`), `char`, `StringView`, `p::String`, `const char*` (`(null)` for null), `std::string`, and any pointer type (`0xADDR`). + +Extend for your own types by specializing the template: + +```cpp +template<> +inline String p::TestString(const MyType& v) +{ + return Format("MyType({})", v.id); +} +``` + +Prefer `StringView` overloads over `String` so views and literals both work without allocation. + +### `TestSettings` + +```cpp +struct TestSettings +{ + StringView only; // Run only describes/its containing substring. + StringView skip; // Skip describes/its containing substring. + bool dryRun = false; // Report full tree as SKIPPED, run nothing. + bool breakOnFailure = false; // Stop the test run on the first failing test. + bool useColor = true; // Colorized output. + bool reportTiming = false; // Report per-test timing information. + TTypeId reporter; // Reporter id; default = SpecReporter. +}; +``` + +Process exit code: `0` when every test passed, `1` otherwise. + +### Failure Reporting + +- Failures are deferred: per-test assertions only update a per-test buffer (`file:line: msg`); the reporter flushes them at the end of the run. +- Each failed test contributes one entry to the global `failures` list, formatted as `context.test:\n
\n`. +- Failure messages are written to **stdout** (not stderr) so test-tooling parsers that follow bandit's convention (e.g. the Bandit VSCode adapter) keep working. diff --git a/Include/Pipe/Core/StringView.h b/Include/Pipe/Core/StringView.h index 94ff4d99..cb6f3fad 100644 --- a/Include/Pipe/Core/StringView.h +++ b/Include/Pipe/Core/StringView.h @@ -2,6 +2,7 @@ #pragma once +#include "Pipe/Core/Char.h" #include "Pipe/Core/Hash.h" #include "Pipe/Core/Optional.h" #include "PipePlatform.h" @@ -84,6 +85,23 @@ namespace p return str.size() == 1 && str[0] == c; } + template + constexpr bool IEquals(const TStringView str, const TStringView other) + { + return str.size() == other.size() + && std::equal(str.begin(), str.end(), other.begin(), [](CharType a, CharType b) + { + return TCharHelpers::ToLower(a) == TCharHelpers::ToLower(b); + }); + } + + template + constexpr bool IEquals(const TStringView str, const CharType c) + { + return str.size() == 1 + && TCharHelpers::ToLower(str[0]) == TCharHelpers::ToLower(c); + } + template constexpr bool StartsWith( const TStringView str, const TStringView subStr) @@ -228,6 +246,16 @@ namespace p return Equals(str, c); } + P_API inline bool IEquals(const StringView str, const StringView other) + { + return IEquals(str, other); + } + + P_API constexpr bool IEquals(const StringView str, const char c) + { + return IEquals(str, c); + } + P_API constexpr bool StartsWith(const StringView str, const StringView subStr) { return StartsWith(str, subStr); diff --git a/Include/PipeTest.h b/Include/PipeTest.h index 960416dd..f1ca811e 100644 --- a/Include/PipeTest.h +++ b/Include/PipeTest.h @@ -36,8 +36,8 @@ namespace p requires(const T& value) { std::formatter, Char>{}; }; } // namespace details - void RegisterSpec(StringView name, TFunction fn); - void RegisterSpec(TFunction fn); + P_API void RegisterSpec(StringView name, TFunction fn); + P_API void RegisterSpec(TFunction fn); // Self-registering top-level describe. Registers its spec body on // construction (same pattern as TTypeAutoRegister). The body runs @@ -70,20 +70,42 @@ namespace p #define P_SPEC static const p::SpecAutoRegister P_CAT(_pipeSpecReg_, __COUNTER__) // Nested describe. Only valid inside a Spec; otherwise logs an error and ignores. - void Describe(StringView name, TFunction fn); + P_API void Describe(StringView name, TFunction fn); // Register a runnable test in the current describe. // Bodies are stored until RunTests, so std::function (owning) is required here. - void It(StringView name, std::function fn); + P_API void It(StringView name, std::function fn); // Register a disabled test; never run. - void XIt(StringView name, std::function fn); + P_API void XIt(StringView name, std::function fn); // Setup hook attached to the current describe. - void BeforeEach(std::function fn); + P_API void BeforeEach(std::function fn); // Teardown hook attached to the current describe. - void AfterEach(std::function fn); + P_API void AfterEach(std::function fn); - // Reporter interface (defined in Src/Tests/PipeTest.cpp). Forward - // declaration so TestSettings can reference its type id. - struct ITestReporter; + + // Reporter interface. Each reporter formats the run differently. + struct P_API ITestReporter + { + P_STRUCT(ITestReporter) + + virtual ~ITestReporter() = default; + + virtual void TestRunStarting() {} + virtual void TestRunComplete() = 0; + virtual void ContextStarting(StringView) {} + virtual void ContextEnded(StringView) {} + virtual void ItStarting(StringView) {} + virtual void ItSucceeded(StringView) {} + // Test passed but made no assertions (e.g. smoke tests). + virtual void ItSucceededNoAssertions(StringView) {} + virtual void ItFailed(StringView) {} + virtual void ItUnknownError(StringView) {} + virtual void ItSkipped(StringView) {} + }; + + + // Finds a registered reporter type by name (case-insensitive). Matches + // either the full type name ("SpecReporter") or its preffix ("Spec"). + P_API TTypeId FindReporter(StringView name); // Settings for a test run. struct TestSettings @@ -100,8 +122,8 @@ namespace p TTypeId reporter; }; - int RunTests(const TestSettings& settings); - int RunTests(int argc, char** argv); + P_API int RunTests(const TestSettings& settings); + P_API int RunTests(int argc, char** argv); // Extensible value-to-string hook for failure messages. @@ -167,10 +189,10 @@ namespace p namespace details { // Format failure message from source location + description. - void Fail(const std::source_location& loc, StringView message); + P_API void Fail(const std::source_location& loc, StringView message); // Counts an assertion. Used to detect tests that ran no expects. - void CountAssert(); + P_API void CountAssert(); // Detect types constructible into a StringView (string-ish values). template diff --git a/Src/Tests/PipeTest.cpp b/Src/PipeTest.cpp similarity index 92% rename from Src/Tests/PipeTest.cpp rename to Src/PipeTest.cpp index 91461f49..7527c4bd 100644 --- a/Src/Tests/PipeTest.cpp +++ b/Src/PipeTest.cpp @@ -1,17 +1,10 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#ifndef P_OVERRIDE_NEWDELETE - #define P_OVERRIDE_NEWDELETE 1 -#endif -#if P_OVERRIDE_NEWDELETE - #include "PipeNewDelete.h" -#endif +#include "PipeTest.h" -#include "Pipe.h" #include "Pipe/Core/Log.h" #include "Pipe/Memory/OwnPtr.h" #include "PipeStrings.h" -#include "PipeTest.h" #include "PipeTime.h" #include @@ -69,8 +62,6 @@ namespace p // Assertion detail accumulated for the current test (file:line: msg). String currentFailureDetail; - // Id of the selected reporter (matches settings.reporter). - TTypeId reporter{}; bool useColor = true; bool reportTiming = false; @@ -124,12 +115,12 @@ namespace p namespace details { - void CountAssert() + P_API void CountAssert() { ++GetTestContext().currentTestAssertCount; } - void Fail(const std::source_location& loc, StringView message) + P_API void Fail(const std::source_location& loc, StringView message) { // Not printed immediately: failures are reported by the reporter // at the end of the run, so they never interleave with deferred @@ -145,7 +136,7 @@ namespace p } } // namespace details - void RegisterSpec(StringView name, TFunction fn) + P_API void RegisterSpec(StringView name, TFunction fn) { TestContext& context = GetTestContext(); context.currentDescribe = &AddDescribe(context.root, name); @@ -153,7 +144,7 @@ namespace p context.currentDescribe = nullptr; } - void RegisterSpec(TFunction fn) + P_API void RegisterSpec(TFunction fn) { TestContext& context = GetTestContext(); context.currentDescribe = &context.root; @@ -161,7 +152,7 @@ namespace p context.currentDescribe = nullptr; } - void Describe(StringView name, TFunction fn) + P_API void Describe(StringView name, TFunction fn) { TestDescribe*& current = CurrentDescribe(); if (!current) @@ -176,17 +167,17 @@ namespace p current = prevDescribe; } - void It(StringView name, std::function fn) + P_API void It(StringView name, std::function fn) { AddTest("It", name, Move(fn), false); } - void XIt(StringView name, std::function fn) + P_API void XIt(StringView name, std::function fn) { AddTest("XIt", name, Move(fn), true); } - void BeforeEach(std::function fn) + P_API void BeforeEach(std::function fn) { TestDescribe*& current = CurrentDescribe(); if (!current) @@ -197,7 +188,7 @@ namespace p current->beforeEach = fn; } - void AfterEach(std::function fn) + P_API void AfterEach(std::function fn) { TestDescribe*& current = CurrentDescribe(); if (!current) @@ -208,25 +199,25 @@ namespace p current->afterEach = fn; } - // Reporter interface (public so TestSettings can reference its type id). - // Mirrors bandit's reporter callbacks. Each reporter formats the run - // differently; all share the same per-test execution flow in RunNested. - struct ITestReporter + P_API TTypeId FindReporter(StringView name) { - virtual ~ITestReporter() = default; - - virtual void TestRunStarting() {} - virtual void TestRunComplete() = 0; - virtual void ContextStarting(StringView) {} - virtual void ContextEnded(StringView) {} - virtual void ItStarting(StringView) {} - virtual void ItSucceeded(StringView) {} - // Test passed but made no assertions (e.g. smoke tests). - virtual void ItSucceededNoAssertions(StringView) {} - virtual void ItFailed(StringView) {} - virtual void ItUnknownError(StringView) {} - virtual void ItSkipped(StringView) {} - }; + const TypeId baseId = GetTypeId(); + for (const TypeId id : GetRegisteredTypeIds()) + { + if (!IsTypeParentOf(baseId, id)) + { + continue; + } + + // "spec" matches SpecReporter; the full name also matches ("specreporter"). + const StringView typeName = RemoveNamespace(GetTypeName(id)); + if (Strings::IEquals(Strings::RemoveFromEnd(typeName, "Reporter"), name)) + { + return TTypeId{id}; + } + } + return TTypeId{TypeId{}}; // No match: invalid id + } namespace @@ -299,6 +290,9 @@ namespace p // bandit's `spec` reporter: indented contexts, "- it ... OK". struct SpecReporter : ITestReporter { + using Super = ITestReporter; + P_STRUCT(SpecReporter) + i32 indentation = 0; String lastIt; @@ -365,6 +359,9 @@ namespace p // lines (a fresh line every kLineWidth tests) rather than one line each. struct DotsReporter : ITestReporter { + using Super = ITestReporter; + P_STRUCT(DotsReporter) + bool anyResults = false; void ItSucceeded(StringView) override @@ -415,6 +412,9 @@ namespace p // exactly once. struct SinglelineReporter : ITestReporter { + using Super = ITestReporter; + P_STRUCT(SinglelineReporter) + SinglelineReporter() { #if P_PLATFORM_WINDOWS @@ -507,6 +507,9 @@ namespace p // and its own summary. Honors --report-timing on every test line. struct InfoReporter : ITestReporter { + using Super = ITestReporter; + P_STRUCT(InfoReporter) + // One entry per active describe, outermost first. struct ContextInfo { @@ -866,7 +869,7 @@ namespace p } // namespace - int RunTests(const TestSettings& settings) + P_API int RunTests(const TestSettings& settings) { #if P_PLATFORM_WINDOWS // Enable ANSI escape sequences on the Windows console, otherwise @@ -890,7 +893,6 @@ namespace p context.runTests = 0; context.failedTests = 0; context.skippedTests = 0; - context.reporter = settings.reporter; context.useColor = settings.useColor; context.reportTiming = settings.reportTiming; context.contextStack.Clear(); @@ -947,7 +949,7 @@ namespace p return context.failedTests == 0 ? 0 : 1; } - int RunTests(int argc, char** argv) + P_API int RunTests(int argc, char** argv) { TestSettings settings; for (i32 i = 1; i < argc; ++i) @@ -967,23 +969,8 @@ namespace p StringView name = Strings::RemoveFromStart(arg, StringView{"-r="}); name = Strings::RemoveFromStart(name, StringView{"--reporter="}); - if (Strings::Equals(name, StringView{"dots"})) - { - settings.reporter = TTypeId(); - } - else if (Strings::Equals(name, StringView{"singleline"})) - { - settings.reporter = TTypeId(); - } - else if (Strings::Equals(name, StringView{"spec"})) - { - settings.reporter = TTypeId(); - } - else if (Strings::Equals(name, StringView{"info"})) - { - settings.reporter = TTypeId(); - } - else + settings.reporter = FindReporter(name); + if (!settings.reporter) { Warning("PipeTest: unknown reporter '{}'. Using default.", name); } diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index a87397a4..ce189f36 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -8,7 +8,7 @@ pipe_target_enable_CPP20(PipeTests) pipe_target_disable_rtti(PipeTests PRIVATE) pipe_target_define_platform(PipeTests) pipe_target_shared_output_directory(PipeTests) -target_link_libraries(PipeTests PUBLIC Pipe PipeTest) +target_link_libraries(PipeTests PUBLIC Pipe) pipe_add_sanitizers(PipeTests) add_test(NAME PipeTests COMMAND $ --reporter=spec)