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 71b46354..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) @@ -63,7 +63,9 @@ 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) if(PIPE_ENABLE_ALLOCATION_STACKS) @@ -81,6 +83,19 @@ 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/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/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..18e702b4 --- /dev/null +++ b/Docs/Specs/2026-09-04-pipe-tests-framework-design.md @@ -0,0 +1,182 @@ +# 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. + +**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; + +// 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 */ }); + }); + + It("top-level test", []() { + Expect("acidic").ToContain("acid"); + Expect(flag).ToBeTrue(); + Expect(other).ToBeFalse(); + }); + }); +} +``` + +### Top-level semantics + +- `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). + +### Functions (namespace `p`) + +| Function | Role | +|----------|------| +| `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) | +| `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/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(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 + +`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, 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). + +### Global registration cursor + +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 + +**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 `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 `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`: 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. + +### Transform map (for step 3) + +| Before (bandit) | After (PipeTests) | +|-----------------|-------------------| +| `#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(...)` | +| `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 `PipeTest` target (alias `Pipe::Test`) 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(settings)` + `RunTests(int, char**)` signatures; API names `Spec/Describe/It/XIt/BeforeEach/AfterEach/Expect` in Pipe CamelCase. + +## Out of Scope / Follow-ups + +- 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/Misc/PipeDebug.h b/Include/Misc/PipeDebug.h index a6cb9836..88e563be 100644 --- a/Include/Misc/PipeDebug.h +++ b/Include/Misc/PipeDebug.h @@ -2571,8 +2571,8 @@ namespace p { break; } - const p::Color ac = details::GetArenaColor(va.arena->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/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/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/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 a604bc1e..00000000 --- a/Include/Pipe/Core/TypeId.h +++ /dev/null @@ -1,139 +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>(); - } - - -#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/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/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/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..5a5a3abe 100644 --- a/Include/PipeReflect.h +++ b/Include/PipeReflect.h @@ -5,16 +5,14 @@ #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" -#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 e2f55ef0..99603ae3 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 "PipeType.h" #include diff --git a/Include/PipeTest.h b/Include/PipeTest.h new file mode 100644 index 00000000..960416dd --- /dev/null +++ b/Include/PipeTest.h @@ -0,0 +1,310 @@ +// Copyright 2015-2026 Piperift. All Rights Reserved. + +#pragma once + +#include "Pipe/Core/Function.h" +#include "Pipe/Core/Macros.h" +#include "Pipe/Core/StringView.h" +#include "PipeReflect.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 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. + */ + + namespace details + { + // Detects whether a type can be rendered via std::format. + template + 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 describe. Registers its spec body on + // construction (same pattern as TTypeAutoRegister). The body runs + // immediately during registration, so TFunction (non-owning) is safe. + struct SpecAutoRegister + { + SpecAutoRegister(StringView name, TFunction fn) + { + RegisterSpec(name, fn); + } + SpecAutoRegister(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::SpecAutoRegister P_CAT(_pipeSpecReg_, __COUNTER__) + + // 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 describe. + void BeforeEach(std::function fn); + // Teardown hook attached to the current describe. + void AfterEach(std::function fn); + + // 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 + { + 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. + // 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); + 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) + { + 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<> + 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)"}; + } + + 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); + + // Counts an assertion. Used to detect tests that ran no expects. + void CountAssert(); + + // 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 + bool ValuesEqual(const A& a, const E& e) + { + if constexpr (IsStringLike && IsStringLike) + { + return StringView{a} == StringView{e}; + } + else + { + return a == e; + } + } + } // namespace details + + + // ---- fluent assertion ---- + template + class ExpectValue + { + public: + ExpectValue(const Actual& value, const std::source_location& loc) : value(value), loc(loc) + { + details::CountAssert(); + } + + template + void ToEqual(const Expected& expected) const + { + if (!details::ValuesEqual(value, expected)) + { + details::Fail(loc, + Format("Expected {} to equal {}", TestString(value), TestString(expected))); + } + } + + template + void ToNotEqual(const Expected& expected) const + { + if (details::ValuesEqual(value, expected)) + { + details::Fail(loc, + Format("Expected {} to not equal {}", TestString(value), TestString(expected))); + } + } + + void ToBeLess(const Actual& other) const + { + if (!(value < other)) + { + details::Fail(loc, + Format("Expected {} to be less than {}", TestString(value), TestString(other))); + } + } + + void ToBeLessOrEqual(const Actual& other) const + { + if (!(value <= other)) + { + details::Fail(loc, Format("Expected {} to be less or equal to {}", + TestString(value), TestString(other))); + } + } + + void ToBeGreater(const Actual& other) const + { + if (!(value > other)) + { + details::Fail(loc, Format("Expected {} to be greater than {}", TestString(value), + TestString(other))); + } + } + + void ToBeGreaterOrEqual(const Actual& other) const + { + if (!(value >= other)) + { + details::Fail(loc, Format("Expected {} to be greater or equal to {}", + TestString(value), TestString(other))); + } + } + + void ToBeTrue() const + { + if (!value) + { + details::Fail(loc, "Expected value to be true"); + } + } + + void ToBeFalse() const + { + if (value) + { + details::Fail(loc, "Expected value to be false"); + } + } + + void ToContain(const StringView sub) const + { + StringView view{value}; + if (Strings::Find(view, sub) == StringView::npos) + { + details::Fail( + loc, 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(loc, + Format("Expected {} to not contain {}", TestString(value), TestString(sub))); + } + } + + private: + const Actual& value; + std::source_location loc; + }; + + // 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); + } +}; // namespace p 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/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..0c26690c 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,116 @@ 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>(); + } + + + namespace details + { + P_API bool IsTypeIdCompatible(TypeId parentId, TypeId childId); + } // namespace details + + + // 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(details::IsTypeIdCompatible(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=(details::IsTypeIdCompatible(GetTypeId(), id) ? id : TypeId{}); + return *this; + } + }; + + +#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/Log.cpp b/Src/Core/Log.cpp index 17fc2c92..2d1e2899 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 << "\033[33m" << msg << "\033[0m\n"; + }, + .errorCallback = [](StringView msg) + { + std::cerr << "\033[31m" << msg << "\033[0m\n"; + }}; // clang-format on const Logger* globalLogger = nullptr; 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/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 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/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/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 new file mode 100644 index 00000000..91461f49 --- /dev/null +++ b/Src/Tests/PipeTest.cpp @@ -0,0 +1,1050 @@ +// 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 "Pipe.h" +#include "Pipe/Core/Log.h" +#include "Pipe/Memory/OwnPtr.h" +#include "PipeStrings.h" +#include "PipeTest.h" +#include "PipeTime.h" + +#include + +#if P_PLATFORM_WINDOWS + #include + #include +#else + #include +#endif + + +namespace p +{ + namespace + { + struct TestCase + { + String name; + // Owning: bodies are stored until RunTests runs them. + std::function body; + bool skip = false; + }; + + struct TestDescribe + { + String name; + 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 describe). + struct TestContext + { + 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; + // 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; + + // 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; + // Sum of all executed test bodies this run, in seconds. + double totalDuration = 0.0; + }; + + // Function-local static: initialized on first use regardless of the + // 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() + { + static TestContext context; + return context; + } + + TestDescribe*& CurrentDescribe() + { + 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 + + + namespace details + { + void CountAssert() + { + ++GetTestContext().currentTestAssertCount; + } + + 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 + // 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(); + context.currentDescribe = &AddDescribe(context.root, name); + fn(); + context.currentDescribe = nullptr; + } + + void RegisterSpec(TFunction fn) + { + TestContext& context = GetTestContext(); + context.currentDescribe = &context.root; + fn(); + context.currentDescribe = nullptr; + } + + void Describe(StringView name, TFunction fn) + { + TestDescribe*& current = CurrentDescribe(); + if (!current) + { + Error("PipeTest: Describe('{}') called outside a Spec. Ignoring.", name); + return; + } + + TestDescribe* prevDescribe = current; + current = &AddDescribe(*current, name); + fn(); + current = prevDescribe; + } + + void It(StringView name, std::function fn) + { + AddTest("It", name, Move(fn), false); + } + + void XIt(StringView name, std::function fn) + { + AddTest("XIt", name, Move(fn), true); + } + + void BeforeEach(std::function fn) + { + TestDescribe*& current = CurrentDescribe(); + if (!current) + { + Error("PipeTest: BeforeEach called outside a Spec. Ignoring."); + return; + } + current->beforeEach = fn; + } + + void AfterEach(std::function fn) + { + TestDescribe*& current = CurrentDescribe(); + if (!current) + { + Error("PipeTest: AfterEach called outside a Spec. Ignoring."); + return; + } + 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 + { + // 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; + + // 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))); + } + + // 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 + { + bool anyResults = false; + + void ItSucceeded(StringView) override + { + std::cout << Colored(Green, "."); + anyResults = true; + } + + void ItSucceededNoAssertions(StringView) override + { + 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) + { + std::cout << std::endl; + } + WriteSummary(); + } + }; + + // ---- Singleline reporter ---- + // 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(); + } + + void ItSucceededNoAssertions(StringView) override + { + PrintStatus(); + } + + void ItFailed(StringView) override + { + PrintStatus(); + } + + void ItUnknownError(StringView) override + { + PrintStatus(); + } + + void PrintStatus() + { + TestContext& context = GetTestContext(); + const i32 run = context.runTests; + if (run <= 0) + { + Error("Could not find any tests."); + return; + } + if (!isTty) + { + return; + } + DrawLine(StatusLine(context)); + } + + 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) + { + return Format("Executed {} tests.", run); + } + 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) + { + 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; + } + + bool isTty = false; + }; + + // ---- 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(" (*) {}", 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() + { + 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(); + } + + static void RunNested(TestDescribe& describe, TArray>& beforeHooks, + TArray>& afterHooks, StringView only, StringView skip, + bool breakOnFailure, ITestReporter& reporter) + { + TestContext& context = GetTestContext(); + if (describe.beforeEach) + { + beforeHooks.Add(describe.beforeEach); + } + if (describe.afterEach) + { + 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, only, skip, breakOnFailure, reporter); + } + + for (TestCase& test : describe.tests) + { + // 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. + 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; + reporter.ItSkipped(test.name); + continue; + } + ++context.runTests; + reporter.ItStarting(test.name); + + for (auto& hook : beforeHooks) + { + hook(); + } + + 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; + unknown = true; + } + 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](); + } + + if (passed) + { + if (context.currentTestAssertCount == 0) + { + reporter.ItSucceededNoAssertions(test.name); + } + else + { + reporter.ItSucceeded(test.name); + } + } + else + { + ++context.failedTests; + context.encounteredFailure = true; + if (unknown) + { + reporter.ItUnknownError(test.name); + context.failures.Add(fullName + ":\nUnknown exception\n"); + } + else + { + reporter.ItFailed(test.name); + String detail = context.currentFailureDetail; + context.failures.Add(detail.empty() ? (fullName + ":\n") + : (fullName + ":\n" + detail + "\n")); + } + } + } + + if (!describe.name.empty()) + { + reporter.ContextEnded(describe.name); + context.contextStack.RemoveLast(); + } + + if (describe.beforeEach) + { + beforeHooks.RemoveLast(); + } + if (describe.afterEach) + { + 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; + + // --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) + { + SpecReporter specReporter; + specReporter.TestRunStarting(); + DryRunNested(context.root, specReporter); + specReporter.TestRunComplete(); + return 0; + } + + const TypeId reporterId = settings.reporter; + TOwnPtr reporter; + if (reporterId == GetTypeId()) + { + reporter = MakeOwned(); + } + else if (reporterId == GetTypeId()) + { + reporter = MakeOwned(); + } + else if (reporterId == GetTypeId()) + { + reporter = MakeOwned(); + } + else + { + // Spec (also the fallback for an unset/unknown reporter id). + reporter = MakeOwned(); + } + + reporter->TestRunStarting(); + + const DateTime runStart = DateTime::Now(); + + TArray> beforeHooks; + TArray> afterHooks; + RunNested(context.root, beforeHooks, afterHooks, settings.only, settings.skip, + settings.breakOnFailure, *reporter); + + // 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; + } + + 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{"--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{"-r="}) + || Strings::StartsWith(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(); + } + 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 default.", 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{"--version"})) + { + Info("Pipe version {}", P_VERSION); + return 0; + } + else if (Strings::Equals(arg, StringView{"--help"})) + { + 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::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"})) + { + settings.dryRun = true; + } + else if (Strings::Equals(arg, StringView{"--no-color"}) + || Strings::Equals(arg, StringView{"-c"})) + { + settings.useColor = false; + } + else if (Strings::StartsWith(arg, StringView{"--"})) + { + // Warning("PipeTest: unknown argument '{}'. Ignoring.", arg); + } + } + return RunTests(settings); + } +} // namespace p diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 893e1828..a87397a4 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -3,13 +3,12 @@ file(GLOB_RECURSE TESTS_SOURCE_FILES CONFIGURE_DEPENDS *.cpp *.h *.hpp) 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) +target_link_libraries(PipeTests PUBLIC Pipe PipeTest) pipe_add_sanitizers(PipeTests) -add_test(NAME PipeTests COMMAND $ --reporter=spec) \ No newline at end of file +add_test(NAME PipeTests COMMAND $ --reporter=spec) diff --git a/Tests/Containers/Arrays.spec.cpp b/Tests/Containers/Arrays.spec.cpp index a97fbc81..c454c26f 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,1097 +41,1093 @@ struct CopyType }; -go_bandit([]() +P_SPEC("Containers.Array", []() { - describe("Containers.Array", []() + It("Can initialize", []() { - 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 copy empty", []() { - TArray data1{}; - TArray data2(3); - TArray data3{3, 3, 3, 3}; - 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)); + 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 - AssertThat(target1.Data(), Equals(nullptr)); - AssertThat(target1.Size(), Equals(0)); - AssertThat(target1.Capacity(), Equals(0)); - TArray source2{}; - TArray target2 = source2; // NOLINT - AssertThat(target2.Data(), Equals(nullptr)); - AssertThat(target2.Size(), Equals(0)); - AssertThat(target2.Capacity(), Equals(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; - 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())); - }); + 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; - 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())); - TArray target2; // Copy to a different size - target2 = source; - AssertThat(source.Data(), Equals(source.GetInlineBuffer())); - AssertThat(target2.Data(), Equals(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; - 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())); - }); + 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; - 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())); - }); + 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); - AssertThat(target1.Data(), Equals(nullptr)); - AssertThat(target1.Size(), Equals(0)); - AssertThat(target1.Capacity(), Equals(0)); - TArray source2{}; - TArray target2 = Move(source2); - AssertThat(target2.Data(), Equals(nullptr)); - AssertThat(target2.Size(), Equals(0)); - AssertThat(target2.Capacity(), Equals(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); - 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)); - }); + 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); - 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())); - TArray target2; // Copy to a different size - target2 = Move(source2); - AssertThat(source2.Data(), Equals(nullptr)); - AssertThat(target2.Data(), Equals(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); - AssertThat(source.Size(), Equals(0)); - AssertThat(source.Capacity(), Equals(0)); - AssertThat(target.Size(), Equals(4)); - AssertThat(target.Capacity(), Equals(4)); - - AssertThat(target[0].value, Equals(3)); - AssertThat(target[3].value, Equals(6)); - - AssertThat(source.Data(), Equals(nullptr)); - AssertThat(target.Data(), !Equals(target.GetInlineBuffer())); - AssertThat(target.Data(), Equals(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); - AssertThat(source.Size(), Equals(0)); - AssertThat(source.Capacity(), Equals(0)); - AssertThat(target.Size(), Equals(4)); - AssertThat(target.Capacity(), IsGreaterThanOrEqualTo(4)); - - AssertThat(target[0].value, Equals(3)); - AssertThat(target[3].value, Equals(6)); - - AssertThat(source.Data(), Equals(nullptr)); - AssertThat(target.Data(), !Equals(target.GetInlineBuffer())); - }); - }); + Expect(data1.Data()).ToEqual(nullptr); + Expect(data2.Data()).ToNotEqual(nullptr); + Expect(data3.Data()).ToNotEqual(nullptr); + }); - it("Can access data", [&]() + Describe("Add", []() + { + It("Can add to dynamic", []() { - TArray data1; - TArray data2{1}; - TArray data3{1}; - - AssertThat(data1.Data(), Equals(nullptr)); - AssertThat(data2.Data(), !Equals(nullptr)); - AssertThat(data3.Data(), !Equals(nullptr)); + 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); }); - describe("Add", []() + It("Can add to inline", []() { - 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)); - data.Add(4); - AssertThat(data.Size(), Equals(2)); - AssertThat(data[1], Equals(4)); - }); - - it("Can add to inline", [&]() - { - TArray data; - data.Add(3); - AssertThat(data.Size(), Equals(1)); - AssertThat(data[0], Equals(3)); - data.Add(4); - AssertThat(data.Size(), Equals(2)); - AssertThat(data[1], Equals(4)); - }); + 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 to correct buffers", [&]() - { - TArray data; - data.Add(3); - data.Add(4); - AssertThat(data.Data(), Equals(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())); - }); + 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 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)); - }); + 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); + }); - 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)); - }); + 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 defaulted", [&]() - { - TArray data; - data.Add(); - AssertThat(data[0], Equals(0)); - }); + It("Can add defaulted", []() + { + TArray data; + data.Add(); + 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)); - data.Append(0); - AssertThat(data.Size(), Equals(2)); - data.Append(2); - AssertThat(data.Size(), Equals(4)); - AssertThat(data[2], Equals(0)); - AssertThat(data[3], Equals(0)); - }); + 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 append value", [&]() - { - TArray data; - data.Append(2, 234); - AssertThat(data.Size(), Equals(2)); - AssertThat(data[0], Equals(234)); - AssertThat(data[1], Equals(234)); - data.Append(0, 234); - AssertThat(data.Size(), Equals(2)); - data.Append(2, 235); - AssertThat(data.Size(), Equals(4)); - AssertThat(data[2], Equals(235)); - AssertThat(data[3], Equals(235)); - }); + 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 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)); - data.Append(nullptr, 0); - AssertThat(data.Size(), Equals(2)); - data.Append(buffer2, 2); - AssertThat(data.Size(), Equals(4)); - AssertThat(data[2], Equals(74)); - AssertThat(data[3], Equals(51)); - }); + 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); + }); - 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())); - }); + 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 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())); - }); + 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()); }); + }); - 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)); - data.Assign(0); - AssertThat(data.Size(), Equals(0)); - data.Assign(2); - AssertThat(data.Size(), Equals(2)); - AssertThat(data[0], Equals(0)); - AssertThat(data[1], Equals(0)); - }); + 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 assign value", [&]() - { - TArray data; - data.Assign(2, 234); - AssertThat(data.Size(), Equals(2)); - AssertThat(data[0], Equals(234)); - AssertThat(data[1], Equals(234)); - data.Assign(0, 234); - AssertThat(data.Size(), Equals(0)); - data.Assign(2, 235); - AssertThat(data.Size(), Equals(2)); - AssertThat(data[0], Equals(235)); - AssertThat(data[1], Equals(235)); - }); + 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 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)); - data.Assign(nullptr, 0); - AssertThat(data.Size(), Equals(0)); - data.Assign(buffer2, 2); - AssertThat(data.Size(), Equals(2)); - AssertThat(data[0], Equals(74)); - AssertThat(data[1], Equals(51)); - }); + 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); + }); - 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())); - }); + 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 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())); - }); + 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()); }); + }); - 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)); - - data.Insert(0, 21); - AssertThat(data.Size(), Equals(2)); - AssertThat(data[0], Equals(21)); - }); + 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 insert at end", [&]() - { - TArray data{12, 34}; - data.Insert(2, 12); - AssertThat(data.Size(), Equals(3)); - AssertThat(data[2], Equals(12)); - }); + 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 insert to inline", [&]() - { - TArray data; - data.Insert(0, 12); - data.Insert(0, 21); - AssertThat(data.Size(), Equals(2)); - AssertThat(data[0], Equals(21)); - }); + 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 copied value", [&]() - { - TArray data; - data.Insert(0, 32); // Insert at empty - AssertThat(data.Size(), Equals(1)); - AssertThat(data[0], Equals(32)); - - data.Insert(0, 65); // Insert at start - AssertThat(data.Size(), Equals(2)); - AssertThat(data[0], Equals(65)); - AssertThat(data[1], Equals(32)); - - data.Add(85); - data.Insert(1, 27); // Insert in the middle - AssertThat(data.Size(), Equals(4)); - AssertThat(data[1], Equals(27)); - - data.Insert(4, 43); // Insert in the end - AssertThat(data.Size(), Equals(5)); - AssertThat(data[4], Equals(43)); - }); + 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); + }); - 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)); - - 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)); - - data.Insert(3, 2, 6); // Insert in the middle - AssertThat(data.Size(), Equals(6)); - AssertThat(data[3], Equals(6)); - AssertThat(data[4], Equals(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)); - }); + 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); + }); - 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)); - - 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)); - - 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)); - - 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)); - }); + 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); + }); - 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)); - }); + 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 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)); - }); + 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); + }); - 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)); - - 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)); - - 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)); - - 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)); - }); + 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", [&]() - { - 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)); - - 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)); - - 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)); - - 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)); - }); + 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); }); + }); - describe("Remove", []() + Describe("Remove", []() + { + It("Can remove at index", []() { - it("Can remove at index", []() - { - TArray data{1, 2, 3, 4}; + TArray data{1, 2, 3, 4}; - // Check invalid inputs - AssertThat(data.RemoveAt(-1), Equals(false)); - AssertThat(data.RemoveAt(4), Equals(false)); + // Check invalid inputs + 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", []() - { - TArray data{1, 2, 3, 4, 5, 6, 7, 8}; + 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)); + // Check invalid inputs + 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", []() - { - TArray data{1, 2, 3, 4, 5}; + 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)); + // Check invalid inputs + 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", []() - { - TArray data{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + 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)); + // Check invalid inputs + 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", [&]() - { - 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)); - }); + 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); + }); - 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)); - - TArray dataB{1, 4, 6}; - dataB.RemoveLast(3); - AssertThat(dataB.Size(), Equals(0)); - AssertThat(dataB.Capacity(), Equals(0)); - }); + 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 RemoveIf", [&]() - { - TArray data{1, 4, 5, 6}; + 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)); + 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 RemoveIfSwap", [&]() - { - TArray data{1, 4, 5, 6}; + 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)); + 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 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)); - - TArray data1{34, 1, 5}; - data1.Sort(TGreater{}); - AssertThat(data1[0], Equals(34)); - AssertThat(data1[1], Equals(5)); - AssertThat(data1[2], Equals(1)); - }); + 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", [&]() - { - TArray data{1, 5, 5, 34}; + 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", [&]() - { - TArray data{1, 5, 5, 34}; + 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", [&]() - { - TArray data{1, 2, 3, 4, 5}; + 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)); + 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 tail = data.Slice(3, 100); // Clamped to available elements - AssertThat(tail.Size(), Equals(2)); - AssertThat(tail[0], Equals(4)); - AssertThat(tail[1], Equals(5)); + 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); - auto none = data.Slice(2, 0); // Zero length - AssertThat(none.IsEmpty(), Is().True()); + auto none = data.Slice(2, 0); // Zero length + Expect(none.IsEmpty()).ToBeTrue(); - auto end = data.Slice(5, 2); // Offset clamped to size - AssertThat(end.IsEmpty(), Is().True()); - }); + 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); + }); - it("Can slice views", [&]() + 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 - AssertThat(mid.Size(), Equals(2)); - AssertThat(mid[0], Equals(3)); - AssertThat(mid[1], Equals(4)); - - auto head = view.Slice(0, 3); - AssertThat(head.Size(), Equals(3)); - AssertThat(head[0], Equals(1)); - AssertThat(head[2], Equals(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; - } - - AssertThat(counter, Equals(0)); - TArray data2{}; // Without inline capacity - counter = 0; - for (i32 v : data2) - { - ++counter; - } - AssertThat(counter, Equals(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) - { - AssertThat(v, Equals(mirror[counter])); - ++counter; - } - AssertThat(counter, Equals(3)); - - TArray data2{1, 3, 4}; // Without inline capacity - counter = 0; - for (i32 v : data2) - { - AssertThat(v, Equals(mirror[counter])); - ++counter; - } - AssertThat(counter, Equals(3)); - }); + Expect(v).ToEqual(mirror[counter]); + ++counter; + } + Expect(counter).ToEqual(3); }); }); +}); + +P_SPEC("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}; - - 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)); + 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 - AssertThat(target1.Data(), Equals(nullptr)); - AssertThat(target1.Size(), Equals(0)); - AssertThat(target1.Capacity(), Equals(0)); - BitArray source2{}; - BitArray target2 = source2; // NOLINT - AssertThat(target2.Data(), Equals(nullptr)); - AssertThat(target2.Size(), Equals(0)); - AssertThat(target2.Capacity(), Equals(0)); - }); - - 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)); - }); + 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); - AssertThat(target1.Data(), Equals(nullptr)); - AssertThat(target1.Size(), Equals(0)); - AssertThat(target1.Capacity(), Equals(0)); - BitArray source2{}; - BitArray target2 = Move(source2); - AssertThat(target2.Data(), Equals(nullptr)); - AssertThat(target2.Size(), Equals(0)); - AssertThat(target2.Capacity(), Equals(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); - 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)); - }); + 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 - AssertThat(anded.IsSet(0), Is().True()); - AssertThat(anded.IsSet(1), Is().False()); - AssertThat(anded.IsSet(2), Is().False()); - AssertThat(anded.IsSet(3), Is().False()); - - // 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()); - - // 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()); - - // ~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()); - - // Compound operations - BitArray compound = a; - compound &= b; - AssertThat(compound.IsSet(0), Is().True()); - AssertThat(compound.IsSet(1), Is().False()); - compound |= b; - AssertThat(compound.IsSet(2), Is().True()); - compound ^= b; - AssertThat(compound.IsSet(0), Is().False()); - AssertThat(compound.IsSet(2), Is().False()); - }); + 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; - AssertThat(anded.Size(), Equals(1)); - AssertThat(anded.IsSet(0), Is().False()); - - const BitArray ored = big | small; - AssertThat(ored.Size(), Equals(1)); // Sized to the smallest operand - AssertThat(ored.IsSet(0), Is().True()); - - // 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()); - }); + 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(); }); }); }); diff --git a/Tests/Core/Function.spec.cpp b/Tests/Core/Function.spec.cpp index 19aa9768..b1d1aa7b 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,65 +28,62 @@ struct Foo inline bool Foo::called = false; -go_bandit([]() +P_SPEC("Core.Function", []() { - describe("Core.Function", []() + It("Can create empty", []() { - it("Can create empty", [&]() - { - TFunction func{}; - AssertThat(func.IsBound(), Equals(false)); - AssertThat(bool(func), Equals(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}; - AssertThat(func.IsBound(), Equals(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}; - 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", [&]() - { - 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(); - AssertThat(Foo::called, Equals(true)); + Foo::called = false; + func1(); + Expect(Foo::called).ToEqual(true); - Foo::called = false; - func2(); - AssertThat(Foo::called, Equals(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(); - AssertThat(called, Equals(true)); - }); + called = true; + }; + func(); + Expect(called).ToEqual(true); }); }); diff --git a/Tests/Core/OwnPtr.spec.cpp b/Tests/Core/OwnPtr.spec.cpp index 0eac09a9..103e7e89 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; @@ -38,312 +36,307 @@ struct MockStruct template using PtrBuilder = TestPtrBuilder; - bool bCalledNew = false; - static bool bCalledDelete; + bool bCalledNew = false; + inline static bool bCalledDelete = false; }; -go_bandit([]() +P_SPEC("Core.OwnPtr", []() { - describe("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)); - }); + TOwnPtr ptr; + Expect(ptr.IsValid()).ToEqual(false); + Expect(ptr.Get()).ToEqual(nullptr); + }); - it("Can instantiate", [&]() - { - TOwnPtr ptr = MakeOwned(); - AssertThat(ptr.IsValid(), Equals(true)); - AssertThat(ptr.Get(), Is().Not().EqualTo(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(); - AssertThat(owner.IsValid(), Equals(true)); - owner.Delete(); - AssertThat(owner.IsValid(), Equals(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; - AssertThat(ptr.IsValid(), Equals(true)); - } - AssertThat(ptr.IsValid(), Equals(false)); + auto owner = MakeOwned(); + Expect(owner->bCalledNew).ToEqual(true); }); - describe("Ptr Builder", []() + It("Calls custom delete", []() { - it("Calls custom new", [&]() - { - auto owner = MakeOwned(); - AssertThat(owner->bCalledNew, Equals(true)); - }); - - it("Calls custom delete", [&]() - { - MockStruct::bCalledDelete = false; - auto owner = MakeOwned(); - AssertThat(MockStruct::bCalledDelete, Equals(false)); - owner.Delete(); - AssertThat(MockStruct::bCalledDelete, Equals(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; - AssertThat(ptr.IsValid(), Equals(false)); - AssertThat(ptr.Get(), Equals(nullptr)); - }); - - it("Can initialize from owner", [&]() - { - TOwnPtr owner = MakeOwned(); - TPtr ptr = owner; + TPtr ptr; + Expect(ptr.IsValid()).ToEqual(false); + Expect(ptr.Get()).ToEqual(nullptr); + }); - AssertThat(ptr.IsValid(), Equals(true)); - AssertThat(ptr.Get(), Is().Not().EqualTo(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(ptr.IsValid()).ToEqual(true); + Expect(ptr.Get()).ToNotEqual(nullptr); + }); - AssertThat(ptr2.IsValid(), Equals(true)); - AssertThat(ptr.Get(), Equals(raw)); - AssertThat(ptr2.Get(), Equals(raw)); - }); + 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); + }); - 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); - 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", [&]() - { - TOwnPtr owner = MakeOwned(); - TPtr ptr = owner; - owner.Delete(); + 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)); - - AssertThat(owner != owner, Equals(false)); - AssertThat(owner != owner2, Equals(true)); - AssertThat(ownerEmpty != ownerEmpty, Equals(false)); - AssertThat(owner != ownerEmpty, Equals(true)); - }); + 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("Owner can equal Weak", [&]() - { - auto owner = MakeOwned(); - auto owner2 = MakeOwned(); - auto weak = owner.AsPtr(); - TOwnPtr ownerEmpty; - TPtr weakEmpty; - - AssertThat(owner == weak, Equals(true)); - AssertThat(owner2 == weak, Equals(false)); - AssertThat(ownerEmpty == weak, Equals(false)); - AssertThat(ownerEmpty == weakEmpty, Equals(true)); - - AssertThat(owner != weak, Equals(false)); - AssertThat(owner2 != weak, Equals(true)); - AssertThat(ownerEmpty != weak, Equals(true)); - AssertThat(ownerEmpty != weakEmpty, Equals(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; - - AssertThat(weak == weak, Equals(true)); - AssertThat(weak2 == weak, Equals(false)); - AssertThat(weakEmpty == weak, Equals(false)); - AssertThat(weakEmpty == weakEmpty, Equals(true)); - - AssertThat(weak != weak, Equals(false)); - AssertThat(weak2 != weak, Equals(true)); - AssertThat(weakEmpty != weak, Equals(true)); - AssertThat(weakEmpty != weakEmpty, Equals(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; - - AssertThat(weak == owner, Equals(true)); - AssertThat(weak2 == owner, Equals(false)); - AssertThat(weakEmpty == owner, Equals(false)); - AssertThat(weakEmpty == ownerEmpty, Equals(true)); - - AssertThat(weak != owner, Equals(false)); - AssertThat(weak2 != owner, Equals(true)); - AssertThat(weakEmpty != owner, Equals(true)); - AssertThat(weakEmpty != ownerEmpty, Equals(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", []() + Describe("Counter", []() + { + It("Adds weaks", []() { - it("Adds weaks", [&]() - { - auto owner = MakeOwned(); - const auto* counter = owner.GetCounter(); - AssertThat(counter->weakCount, Equals(0u)); + auto owner = MakeOwned(); + const auto* counter = owner.GetCounter(); + Expect(counter->weakCount).ToEqual(0u); - auto weak = owner.AsPtr(); - AssertThat(counter->weakCount, Equals(1u)); - }); + auto weak = owner.AsPtr(); + Expect(counter->weakCount).ToEqual(1u); + }); - it("Removes weaks", [&]() + It("Removes weaks", []() + { + auto owner = MakeOwned(); + const auto* counter = owner.GetCounter(); { - auto owner = MakeOwned(); - const auto* counter = owner.GetCounter(); - { - auto weak = owner.AsPtr(); - AssertThat(counter->weakCount, Equals(1u)); - } - AssertThat(counter->weakCount, Equals(0u)); - }); + auto weak = owner.AsPtr(); + Expect(counter->weakCount).ToEqual(1u); + } + Expect(counter->weakCount).ToEqual(0u); + }); - it("Removes with owner release", [&]() - { - auto owner = MakeOwned(); - AssertThat(owner.GetCounter(), Is().Not().EqualTo(nullptr)); + It("Removes with owner release", []() + { + auto owner = MakeOwned(); + Expect(owner.GetCounter()).ToNotEqual(nullptr); - owner.Delete(); - AssertThat(owner.GetCounter(), Equals(nullptr)); - }); + owner.Delete(); + Expect(owner.GetCounter()).ToEqual(nullptr); + }); - it("Removes with no weakCount left", [&]() - { - auto owner = MakeOwned(); - auto weak = owner.AsPtr(); - AssertThat(weak.GetCounter(), Is().Not().EqualTo(nullptr)); + It("Removes with no weakCount left", []() + { + auto owner = MakeOwned(); + auto weak = owner.AsPtr(); + Expect(weak.GetCounter()).ToNotEqual(nullptr); - owner.Delete(); - AssertThat(weak.GetCounter(), Is().Not().EqualTo(nullptr)); + owner.Delete(); + Expect(weak.GetCounter()).ToNotEqual(nullptr); - weak.Reset(); - AssertThat(owner.GetCounter(), Equals(nullptr)); - }); + weak.Reset(); + Expect(owner.GetCounter()).ToEqual(nullptr); }); + }); - it("Can detect custom PtrBuilders", [&]() - { - AssertThat(p::HasCustomPtrBuilder::value, Equals(false)); - AssertThat(p::HasCustomPtrBuilder::value, Equals(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(); - AssertThat(typedPtr.IsValid(), Equals(true)); + TOwnPtr typedPtr = MakeOwned(); + Expect(typedPtr.IsValid()).ToEqual(true); - EmptyStruct* data = typedPtr.Get(); + 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)); - }); + 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(); - AssertThat(ptr.IsValid(), Equals(true)); - auto* data = ptr.Get(); - - TOwnPtr typedPtr = Move(ptr); - AssertThat(ptr.IsValid(), Equals(false)); - AssertThat(typedPtr.IsValid(), Equals(true)); - AssertThat(typedPtr.Get(), Equals(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(); - AssertThat(ptr1.IsValid(), Equals(true)); - AssertThat(ptr1.GetId(), Equals(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())); - - AssertThat(ptr2.IsValid(), Equals(true)); - AssertThat(ptr2.Get(), Equals(data)); - AssertThat(ptr2.GetId(), Equals(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(); - AssertThat(ptr.Get(), !Equals(nullptr)); - AssertThat(ptr.Get(), Equals(nullptr)); - }); + It("Cant retrive invalid types", []() + { + OwnPtr ptr = MakeOwned(); + 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..3071b8f2 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,85 +25,82 @@ struct Dummy }; -go_bandit([]() +P_SPEC("ECS.PageBuffer", []() { - describe("ECS.PageBuffer", []() + It("Can reserve", []() { - it("Can reserve", [&]() - { - TPageBuffer buffer{GetCurrentArena()}; - - AssertThat(buffer.GetPagesSize(), Equals(0)); - AssertThat(buffer.Capacity(), Equals(0)); - - buffer.Reserve(2); - AssertThat(buffer.GetPagesSize(), Equals(1)); - AssertThat(buffer.Capacity(), Equals(2)); - - buffer.Reserve(6); - AssertThat(buffer.GetPagesSize(), Equals(3)); - AssertThat(buffer.Capacity(), Equals(6)); - }); - - it("Can shrink", [&]() - { - TPageBuffer buffer{GetCurrentArena()}; - buffer.Reserve(7); - AssertThat(buffer.GetPagesSize(), Equals(4)); - - buffer.Shrink(4); - AssertThat(buffer.GetPagesSize(), Equals(2)); - AssertThat(buffer.Capacity(), Equals(4)); - }); - - it("Can insert", [&]() - { - TPageBuffer buffer{GetCurrentArena()}; - buffer.Reserve(4); - - buffer.Insert(0); - AssertThat(buffer[0].created, Equals(true)); - AssertThat(buffer[0].destroyed, Equals(false)); - - buffer.Insert(3); - AssertThat(buffer[3].created, Equals(true)); - AssertThat(buffer[3].destroyed, Equals(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 - // AssertThat(buffer[0].destroyed, Equals(true)); - - buffer.RemoveAt(3); - // Temporarily disabled due to GCC only test fail - // AssertThat(buffer[3].destroyed, Equals(true)); - }); - - 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)); - - - 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)); - }); + 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); }); }); diff --git a/Tests/Core/PlatformProcess.spec.cpp b/Tests/Core/PlatformProcess.spec.cpp index 88a051b0..9f77e44d 100644 --- a/Tests/Core/PlatformProcess.spec.cpp +++ b/Tests/Core/PlatformProcess.spec.cpp @@ -3,26 +3,21 @@ #include "Pipe/Core/Log.h" #include "Pipe/Core/Subprocess.h" -#include #include +#include -using namespace snowhouse; -using namespace bandit; using namespace p; -go_bandit([]() +P_SPEC("Core.Subprocess", []() { - describe("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)); +#if 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 1fd37138..b2a796b0 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,76 @@ struct TypeOfSize }; -go_bandit([]() +P_SPEC("Core.Set", []() { - describe("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)); - - AssertThat(data3[2], Equals(2)); - AssertThat(data3[3], Equals(3)); - AssertThat(data3[4], Equals(4)); - AssertThat(data3[5], Equals(5)); - }); - - 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)); - - 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)); - }); - - it("Can move", [&]() - { - TSet data1{4, 3}; - AssertThat(data1.Size(), Equals(2)); - - TSet data2{Move(data1)}; - AssertThat(data1.Size(), Equals(0)); - AssertThat(data2.Size(), Equals(2)); - - TSet data3{4, 3}; - TSet data4; - AssertThat(data3.Size(), Equals(2)); - AssertThat(data4.Size(), Equals(0)); - - data4 = Move(data3); - AssertThat(data3.Size(), Equals(0)); - AssertThat(data4.Size(), Equals(2)); - AssertThat(data4[3], Equals(3)); - AssertThat(data4[4], Equals(4)); - }); - - it("Can access data", [&]() - { - TSet data1; - TSet data2{1, 5}; - - AssertThat(data1.Size(), Equals(0)); - AssertThat(data2.Size(), IsGreaterThanOrEqualTo(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)); - }); + 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 793fca38..4a98c691 100644 --- a/Tests/Core/SpinLock.spec.cpp +++ b/Tests/Core/SpinLock.spec.cpp @@ -1,187 +1,182 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include +#include #include #include #include -using namespace snowhouse; -using namespace bandit; using namespace p; -go_bandit([]() +P_SPEC("Core.SpinLock", []() { - describe("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); - 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", [&]() - { - 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(); + } - 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); + SharedSpinLock lock; + ExclusiveScopedLock writer(lock); - AssertThat(lock.TryLockExclusive(), Is().False()); - }); + 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); - AssertThat(lock.TryLockShared(), Is().False()); - }); + 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); - AssertThat(lock.TryLockExclusive(), Is().False()); - }); + 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. - AssertThat(lock.TryLockShared(), Is().True()); - lock.UnlockShared(); + // Readers coexist: shared still acquirable. + Expect(lock.TryLockShared()).ToBeTrue(); + lock.UnlockShared(); - AssertThat(lock.TryLockExclusive(), Is().False()); - }); + Expect(lock.TryLockExclusive()).ToBeFalse(); + }); - it("Writers exclude each other", [&]() - { - SharedSpinLock lock; + It("Writers exclude each other", []() + { + SharedSpinLock lock; - ExclusiveScopedLock w1(lock); - AssertThat(lock.TryLockExclusive(), Is().False()); - }); + 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(); + } - 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; + + 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(); + } - 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..f6c05d8d 100644 --- a/Tests/Core/String.spec.cpp +++ b/Tests/Core/String.spec.cpp @@ -1,975 +1,971 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include #include #include +#include #include #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([]() + +P_SPEC("Strings", []() { - describe("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)); - // 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')); - }); - - it("Can construct from literal", [&]() - { - String v{"Kiwi"}; - AssertThat(v, Equals("Kiwi")); - AssertThat(v.size(), Equals(4u)); - }); + 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 with count", [&]() - { - String v{"KiwiApple", 4}; - AssertThat(v, Equals("Kiwi")); - AssertThat(v.size(), Equals(4u)); - }); + It("Can construct from literal", []() + { + String v{"Kiwi"}; + Expect(v).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4u); + }); - it("Can construct from count and char", [&]() - { - String v(5, 'x'); - AssertThat(v, Equals("xxxxx")); - AssertThat(v.size(), Equals(5u)); - }); + It("Can construct from literal with count", []() + { + String v{"KiwiApple", 4}; + Expect(v).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4u); + }); - it("Can construct from string view", [&]() - { - StringView str{"Kiwi"}; - String v{str}; - AssertThat(v, Equals("Kiwi")); - AssertThat(v.size(), Equals(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 string view with pos and count", [&]() - { - StringView str{"KiwiApple"}; - String v{str, 4, 5}; - AssertThat(v, Equals("Apple")); - }); + 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 substring", [&]() - { - String str{"KiwiApple"}; - String v{str, 4}; - AssertThat(v, Equals("Apple")); - String v2{str, 4, 3}; - AssertThat(v2, Equals("App")); - }); + 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 iterators", [&]() - { - std::string_view sv = "Kiwi"; - String v{sv.begin(), sv.end()}; - AssertThat(v, Equals("Kiwi")); - }); + 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 initializer list", [&]() - { - String v{'K', 'i', 'w', 'i'}; - AssertThat(v, Equals("Kiwi")); - }); + It("Can construct from iterators", []() + { + std::string_view sv = "Kiwi"; + String v{sv.begin(), sv.end()}; + Expect(v).ToEqual("Kiwi"); + }); - it("Can copy construct", [&]() - { - String v{"Kiwi"}; - String v2{v}; - AssertThat(v2, Equals("Kiwi")); - AssertThat(v, Equals("Kiwi")); - }); + It("Can construct from initializer list", []() + { + String v{'K', 'i', 'w', 'i'}; + Expect(v).ToEqual("Kiwi"); + }); - it("Can move construct", [&]() - { - String v{"Kiwi"}; - String v2{Move(v)}; - AssertThat(v2, Equals("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')); - }); + It("Can copy construct", []() + { + String v{"Kiwi"}; + String v2{v}; + Expect(v2).ToEqual("Kiwi"); + Expect(v).ToEqual("Kiwi"); }); - describe("Assignment", []() + It("Can move construct", []() { - it("Can assign from literal", [&]() - { - String v; - v = "Kiwi"; - AssertThat(v, Equals("Kiwi")); - }); + 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 copy assign", [&]() - { - String vKiwi{"Kiwi"}; - String vApple{"Apple"}; - String vCopy = vKiwi; - AssertThat(vCopy, Equals("Kiwi")); - vCopy = vApple; - AssertThat(vCopy, Equals("Apple")); - AssertThat(vCopy, Equals(vApple)); - }); + Describe("Assignment", []() + { + It("Can assign from literal", []() + { + String v; + v = "Kiwi"; + Expect(v).ToEqual("Kiwi"); + }); - it("Can move assign", [&]() - { - String vKiwi{"Kiwi"}; - String vApple{"Apple"}; - String vMove = Move(vKiwi); - AssertThat(vKiwi.size(), Equals(0u)); - AssertThat(vMove, Equals("Kiwi")); - vMove = Move(vApple); - AssertThat(vApple.size(), Equals(0u)); - AssertThat(vMove, Equals("Apple")); - }); + 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 assign char", [&]() - { - String v; - v = 'x'; - AssertThat(v, Equals("x")); - }); + 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 initializer list", [&]() - { - String v; - v = {'K', 'i', 'w', 'i'}; - AssertThat(v, Equals("Kiwi")); - }); + It("Can assign char", []() + { + String v; + v = 'x'; + Expect(v).ToEqual("x"); + }); - it("Can assign string view", [&]() - { - String v; - StringView sv{"Kiwi"}; - v = sv; - AssertThat(v, Equals("Kiwi")); - }); + It("Can assign initializer list", []() + { + String v; + v = {'K', 'i', 'w', 'i'}; + Expect(v).ToEqual("Kiwi"); + }); - it("Can assign", [&]() - { - String v; - v.assign("Kiwi"); - AssertThat(v, Equals("Kiwi")); - v.assign("KiwiApple", 4); - AssertThat(v, Equals("Kiwi")); - v.assign(3, 'x'); - AssertThat(v, Equals("xxx")); - String other{"Apple"}; - v.assign(other); - AssertThat(v, Equals("Apple")); - v.assign(other, 2, 2); - AssertThat(v, Equals("pl")); - StringView sv{"KiwiApple"}; - v.assign(sv, 4, 5); - AssertThat(v, Equals("Apple")); - v.assign({'a', 'b', 'c'}); - AssertThat(v, Equals("abc")); - }); + It("Can assign string view", []() + { + String v; + StringView sv{"Kiwi"}; + v = sv; + Expect(v).ToEqual("Kiwi"); + }); - it("Can self assign", [&]() - { - String v{"Kiwi"}; - const String& ref = v; - v = ref; - AssertThat(v, Equals("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 self assign substrings", [&]() - { - String v{longText}; - v.assign(v.c_str() + 10); - AssertThat(v, Equals("ABCDEFGHIJ0123456789ABC")); - }); + It("Can self assign", []() + { + String v{"Kiwi"}; + const String& ref = v; + v = ref; + Expect(v).ToEqual("Kiwi"); + }); - it("Can self assign substrings with count", [&]() - { - String v{longText}; - v.assign(v.c_str() + 5, 10); - AssertThat(v, Equals("56789ABCDE")); - }); + It("Can self assign substrings", []() + { + String v{longText}; + v.assign(v.c_str() + 10); + Expect(v).ToEqual("ABCDEFGHIJ0123456789ABC"); }); - describe("Element access", []() + It("Can self assign substrings with count", []() { - it("Can index", [&]() - { - String v{"Kiwi"}; - AssertThat(v[0], Equals('K')); - AssertThat(v[3], Equals('i')); - v[0] = 'k'; - AssertThat(v, Equals("kiwi")); - // pos == size() returns reference to null char - AssertThat(v[4], Equals('\0')); - }); + String v{longText}; + v.assign(v.c_str() + 5, 10); + Expect(v).ToEqual("56789ABCDE"); + }); + }); - it("Can access at", [&]() - { - String v{"Kiwi"}; - AssertThat(v.at(0), Equals('K')); - AssertThat(v.at(3), Equals('i')); - v.at(0) = 'k'; - AssertThat(v, Equals("kiwi")); - }); + Describe("Element access", []() + { + 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 front and back", [&]() - { - String v{"Kiwi"}; - AssertThat(v.front(), Equals('K')); - AssertThat(v.back(), Equals('i')); - v.front() = 'P'; - v.back() = 's'; - AssertThat(v, Equals("Piws")); - }); + 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 retrieve data", [&]() - { - String v{"Kiwi"}; - AssertThat(v.data(), Equals("Kiwi")); - AssertThat(v.size(), Equals(4u)); - AssertThat(strlen(v.data()), Equals(4u)); - }); + 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 convert to string view", [&]() - { - String v{"Kiwi"}; - StringView sv = v; - AssertThat(sv.size(), Equals(4u)); - AssertThat(sv, Equals(StringView{"Kiwi"})); - StringView wsv{v}; - AssertThat(wsv, Equals(StringView{"Kiwi"})); - }); + It("Can retrieve data", []() + { + String v{"Kiwi"}; + Expect(v.data()).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4u); + Expect(strlen(v.data())).ToEqual(4u); }); - describe("Iterators", []() + It("Can convert to string view", []() { - it("Can iterate", [&]() - { - String v{"Kiwi"}; - u32 i = 0; - for (char c : v) - { - AssertThat(c, Equals("Kiwi"[i])); - ++i; - } - AssertThat(i, Equals(4u)); - }); + 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 iterate const", [&]() - { - const String v{"Kiwi"}; - u32 i = 0; - for (char c : v) - { - AssertThat(c, Equals("Kiwi"[i])); - ++i; - } - AssertThat(i, Equals(4u)); - }); + Describe("Iterators", []() + { + 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 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')); - ++it; - AssertThat(*it, Equals('i')); - it += 2; - AssertThat(*it, Equals('i')); - --it; - AssertThat(*it, Equals('w')); - AssertThat(it == v.begin() + 2, Is().True()); - AssertThat(it != v.begin(), Is().True()); - }); + 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 reverse", [&]() - { - String v{"Kiwi"}; - u32 i = 0; - for (auto rit = v.rbegin(); rit != v.rend(); ++rit) - { - AssertThat(*rit, Equals("Kiwi"[3 - i])); - ++i; - } - AssertThat(i, Equals(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 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')); - }); + 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 mutate through iterators", [&]() - { - String v{"Kiwi"}; - std::transform(v.begin(), v.end(), v.begin(), [](char c) - { - return char(c + 1); - }); - AssertThat(v, Equals("Ljxj")); - }); + 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'); }); - describe("Capacity", []() + It("Can mutate through iterators", []() { - it("Can query size and length", [&]() + String v{"Kiwi"}; + std::transform(v.begin(), v.end(), v.begin(), [](char c) { - String v{"Kiwi"}; - AssertThat(v.size(), Equals(4u)); - AssertThat(v.length(), Equals(4u)); - AssertThat(v.empty(), Is().False()); + return char(c + 1); }); + Expect(v).ToEqual("Ljxj"); + }); + }); - 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()); - }); + Describe("Capacity", []() + { + It("Can query size and length", []() + { + String v{"Kiwi"}; + Expect(v.size()).ToEqual(4u); + Expect(v.length()).ToEqual(4u); + Expect(v.empty()).ToBeFalse(); + }); - it("Can reserve", [&]() - { - String v; - v.reserve(100); - AssertThat(v.capacity() >= 100u, Is().True()); - AssertThat(v.size(), Equals(0u)); - v = "Kiwi"; - AssertThat(v, Equals("Kiwi")); - AssertThat(v.capacity() >= 100u, Is().True()); - }); + 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 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()); - }); + 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("Has max size", [&]() - { - String v; - // Lengths are stored internally as i32 - AssertThat(v.max_size(), Equals(sizet(Limits::Max() - 1))); - }); + 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(); }); - describe("Modifiers", []() + It("Has max size", []() { - 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')); - }); + String v; + // Lengths are stored internally as i32 + Expect(v.max_size()).ToEqual(sizet(Limits::Max() - 1)); + }); + }); - 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')); - v.pop_back(); - AssertThat(v, Equals("Kiw")); - v.pop_back(); - v.pop_back(); - v.pop_back(); - AssertThat(v, Equals("")); - AssertThat(v.empty(), Is().True()); - }); + Describe("Modifiers", []() + { + 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 append", [&]() - { - String v{"Kiwi"}; - v.append("Apple"); - AssertThat(v, Equals("KiwiApple")); - v.append("Orange", 3); - AssertThat(v, Equals("KiwiAppleOra")); - v.append(3, '-'); - AssertThat(v, Equals("KiwiAppleOra---")); - String other{"End"}; - v.append(other); - AssertThat(v, Equals("KiwiAppleOra---End")); - v.append(other, 1, 2); - AssertThat(v, Equals("KiwiAppleOra---Endnd")); - StringView sv{"View"}; - v.append(sv); - AssertThat(v, Equals("KiwiAppleOra---EndndView")); - v.append(sv, 2, 2); - AssertThat(v, Equals("KiwiAppleOra---EndndViewew")); - v.append({'!', '?'}); - AssertThat(v, Equals("KiwiAppleOra---EndndViewew!?")); - }); + 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 with operator+=", [&]() - { - String v{"Kiwi"}; - v += "Apple"; - AssertThat(v, Equals("KiwiApple")); - v += '!'; - AssertThat(v, Equals("KiwiApple!")); - String other{"End"}; - v += other; - AssertThat(v, Equals("KiwiApple!End")); - v += StringView{"View"}; - AssertThat(v, Equals("KiwiApple!EndView")); - v += {'a', 'b'}; - AssertThat(v, Equals("KiwiApple!EndViewab")); - }); + 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 insert", [&]() - { - String v{"KiwiApple"}; - v.insert(4, "Orange"); - AssertThat(v, Equals("KiwiOrangeApple")); - v.insert(0, "-"); - AssertThat(v, Equals("-KiwiOrangeApple")); - v.insert(v.size(), "!"); - AssertThat(v, Equals("-KiwiOrangeApple!")); - v.insert(0, 3, '='); - AssertThat(v, Equals("===-KiwiOrangeApple!")); - String other{"XX"}; - v.insert(3, other); - AssertThat(v, Equals("===XX-KiwiOrangeApple!")); - StringView sv{"YY"}; - v.insert(5, sv); - AssertThat(v, Equals("===XXYY-KiwiOrangeApple!")); - v.insert(0, 2, 'Z'); - AssertThat(v, Equals("ZZ===XXYY-KiwiOrangeApple!")); - }); + 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 with iterator", [&]() - { - String v{"Kiwi"}; - auto it = v.insert(v.begin() + 2, '-'); - AssertThat(*it, Equals('-')); - AssertThat(v, Equals("Ki-wi")); - v.insert(v.end(), 3, '!'); - AssertThat(v, Equals("Ki-wi!!!")); - String other{"AB"}; - v.insert(v.begin(), other.begin(), other.end()); - AssertThat(v, Equals("ABKi-wi!!!")); - v.insert(v.begin() + 2, {'x', 'y'}); - AssertThat(v, Equals("ABxyKi-wi!!!")); - }); + 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 erase", [&]() - { - String v{"KiwiApple"}; - v.erase(4, 5); - AssertThat(v, Equals("Kiwi")); - v.erase(2); - AssertThat(v, Equals("Ki")); - v.erase(0, 1); - AssertThat(v, Equals("i")); - v.erase(0, 10); - AssertThat(v, Equals("")); - }); + 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 with iterator", [&]() - { - String v{"Kiwi"}; - auto it = v.erase(v.begin()); - AssertThat(*it, Equals('i')); - AssertThat(v, Equals("iwi")); - v.erase(v.begin() + 1, v.end()); - AssertThat(v, Equals("i")); - }); + 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 replace", [&]() - { - String v{"KiwiApple"}; - v.replace(0, 4, "Orange"); - AssertThat(v, Equals("OrangeApple")); - v.replace(0, 6, "X"); - AssertThat(v, Equals("XApple")); - v.replace(v.size() - 3, 3, "Z"); - AssertThat(v, Equals("XApZ")); - String other{"Kiwi"}; - v.replace(0, 4, other); - AssertThat(v, Equals("Kiwi")); - StringView sv{"Two"}; - v.replace(0, 4, sv); - AssertThat(v, Equals("Two")); - v.replace(0, 3, 2, 'y'); - AssertThat(v, Equals("yy")); - }); + 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 with iterators", [&]() - { - String v{"KiwiApple"}; - v.replace(v.begin(), v.begin() + 4, "Orange"); - AssertThat(v, Equals("OrangeApple")); - }); + 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 resize", [&]() - { - String v{"Kiwi"}; - v.resize(2); - AssertThat(v, Equals("Ki")); - v.resize(4); - AssertThat(v.size(), Equals(4u)); - AssertThat(v[2], Equals('\0')); - AssertThat(v[3], Equals('\0')); - v.resize(6, 'x'); - AssertThat(v[4], Equals('x')); - AssertThat(v[5], Equals('x')); - AssertThat(v.size(), Equals(6u)); - }); + It("Can replace with iterators", []() + { + String v{"KiwiApple"}; + v.replace(v.begin(), v.begin() + 4, "Orange"); + Expect(v).ToEqual("OrangeApple"); + }); - it("Can swap", [&]() - { - String a{"Kiwi"}; - String b{"Apple"}; - a.swap(b); - AssertThat(a, Equals("Apple")); - AssertThat(b, Equals("Kiwi")); - }); + 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 append from self", [&]() - { - String v{longText}; - v.append(v.c_str()); - AssertThat(v, Equals(std::string{longText} + std::string{longText})); - }); + It("Can swap", []() + { + String a{"Kiwi"}; + String b{"Apple"}; + a.swap(b); + Expect(a).ToEqual("Apple"); + Expect(b).ToEqual("Kiwi"); + }); - 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)})); - }); + It("Can append from self", []() + { + String v{longText}; + v.append(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()); - AssertThat(v, Equals(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 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)})); - }); + 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 replace with self", [&]() - { - String v{longText}; - v.replace(0, 4, v.c_str()); - AssertThat(v, Equals(std::string{longText} + 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 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)})); - }); + 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)}); }); - describe("Operations", []() + It("Can replace self substring with count", []() { - 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")); - }); + 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 copy out", [&]() - { - String v{"KiwiApple"}; - char buffer[16]{}; - const auto count = v.copy(buffer, 4, 4); - AssertThat(count, Equals(4u)); - AssertThat(buffer, Equals("Appl")); - buffer[count] = '\0'; - }); + Describe("Operations", []() + { + 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 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)); - }); + 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 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()); - }); + 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 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()); - }); + 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 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)); - }); + 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 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)); - }); + 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 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)); - }); + 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 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)); - }); + 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 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)); - }); + 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 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)); - }); + 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("Has npos", [&]() - { - AssertThat(String::npos, Equals(sizet(-1))); - AssertThat(StringView::npos, Equals(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); }); - describe("Operators", []() + It("Has npos", []() { - 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(String::npos).ToEqual(sizet(-1)); + Expect(StringView::npos).ToEqual(String::npos); + }); + }); - it("Can chain concatenate", [&]() - { - String a{"Kiwi"}; - String result = a + " " + "Apple" + '!'; - AssertThat(result, Equals("Kiwi Apple!")); - }); + Describe("Operators", []() + { + 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 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 chain concatenate", []() + { + String a{"Kiwi"}; + String result = a + " " + "Apple" + '!'; + Expect(result).ToEqual("Kiwi Apple!"); + }); - 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()); - }); + 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(); }); - describe("Memory", []() + It("Can three-way compare", []() { - 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')); - }); + 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 reuse capacity", [&]() - { - String v; - v.reserve(1000); - const auto cap = v.capacity(); - for (u32 i = 0; i < 100; ++i) - { - v.assign("KiwiAppleOrangeBanana"); - v.clear(); - } - AssertThat(v.capacity(), Equals(cap)); - }); + Describe("Memory", []() + { + 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("Is valid after move assignment", [&]() + It("Can reuse capacity", []() + { + String v; + v.reserve(1000); + const auto cap = v.capacity(); + for (u32 i = 0; i < 100; ++i) { - String a{"Kiwi"}; - String b; - b = Move(a); - AssertThat(b, Equals("Kiwi")); - a = "Reused"; - AssertThat(a, Equals("Reused")); - }); + v.assign("KiwiAppleOrangeBanana"); + v.clear(); + } + Expect(v.capacity()).ToEqual(cap); }); - describe("Format & Hash", []() + It("Is valid after move assignment", []() { - it("Can be formatted", [&]() - { - String v{"Kiwi"}; - AssertThat(std::format("{}", v), Equals("Kiwi")); - AssertThat(Format("{}-{}", v, 5), Equals("Kiwi-5")); - String out; - FormatTo(out, "{}!", v); - AssertThat(out, Equals("Kiwi!")); - }); + String a{"Kiwi"}; + String b; + b = Move(a); + Expect(b).ToEqual("Kiwi"); + a = "Reused"; + Expect(a).ToEqual("Reused"); + }); + }); - it("Can be hashed", [&]() - { - String v{"Kiwi"}; - AssertThat(GetHash(v), Equals(GetStringHash("Kiwi"))); - AssertThat(GetHash(StringView{"Kiwi"}), Equals(GetHash(v))); - }); + Describe("Format & Hash", []() + { + 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!"); }); - describe("Arena", []() + It("Can be hashed", []() { - const char* longText = "This string is long enough to exceed the inline capacity"; + String v{"Kiwi"}; + Expect(GetHash(v)).ToEqual(GetStringHash("Kiwi")); + Expect(GetHash(StringView{"Kiwi"})).ToEqual(GetHash(v)); + }); + }); - 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))); - // Short strings still use the inline buffer - v = "Kiwi"; - AssertThat(v, Equals("Kiwi")); - AssertThat(v.capacity() <= 32u, Is().True()); - }); + Describe("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(); + }); - 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))); - // Long strings must allocate on the arena, not the current arena - AssertThat(v.capacity() >= v.size(), Is().True()); - }); + 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'}; - AssertThat(v.size(), Equals(64u)); - AssertThat(&v.GetArena(), Equals(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{longText}; - String v{arena, original}; - AssertThat(v, Equals(original)); - AssertThat(&v.GetArena(), Equals(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(longText); - 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()); - }); + 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); - AssertThat(v, Equals("Apple")); - Strings::RemoveFromStart(v, 100); - AssertThat(v.empty(), Is().True()); - }); + 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); - AssertThat(v, Equals("Kiwi")); - Strings::RemoveFromEnd(v, StringView{"wi"}); - AssertThat(v, Equals("Ki")); - Strings::RemoveFromEnd(v, 100); - AssertThat(v.empty(), Is().True()); - }); + 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!"}; - AssertThat(Strings::RemoveCharFromEnd(v, '!'), Is().True()); - AssertThat(v, Equals("Kiwi")); - AssertThat(Strings::RemoveCharFromEnd(v, '!'), Is().False()); - AssertThat(v, Equals("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", [&]() - { - 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")); - }); + 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); - AssertThat(u.size(), Equals(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); - AssertThat(utf16result.size(), Equals(4u)); - AssertThat(utf16result[2] == 0xd834, Is().True()); - AssertThat(utf16result[3] == 0xdd1e, Is().True()); - }); - it("Convert u32 to u8", [&]() - { - TString utf32string = {0x448, 0x65E5, 0x10346}; - TString utf8result = Strings::Convert>(utf32string); - AssertThat(utf8result.size(), Equals(9u)); - }); - it("Convert u8 to u32", [&]() - { - TString twochars = "\xe6\x97\xa5\xd1\x88"; - TString utf32result = Strings::Convert>(twochars); - AssertThat(utf32result.size(), Equals(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 1e54e70d..6249b228 100644 --- a/Tests/Core/StringView.spec.cpp +++ b/Tests/Core/StringView.spec.cpp @@ -1,115 +1,110 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include #include +#include -using namespace snowhouse; -using namespace bandit; using namespace p; -go_bandit([]() +P_SPEC("Strings", []() { - describe("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)); - }); + StringView v{"Kiwi"}; + Expect(v).ToEqual("Kiwi"); + Expect(v.size()).ToEqual(4); + }); - it("Can assign from string", [&]() - { - String str{"Kiwi"}; - StringView v{str}; - AssertThat(v, Equals("Kiwi")); - AssertThat(v.size(), Equals(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{" "}; - AssertThat(str.empty(), Equals(true)); - AssertThat((u8*)str.data(), Equals(nullptr)); - AssertThat(str2.empty(), Equals(false)); - AssertThat((u8*)str2.data(), !Equals(nullptr)); - str2 = str; - AssertThat(str2.empty(), Equals(true)); - AssertThat((u8*)str2.data(), Equals(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"}; - AssertThat(v.data(), Equals("Kiwi")); - AssertThat(v.size(), Equals(4)); - StringView v2{}; - AssertThat((u8*)v2.data(), Equals(nullptr)); - AssertThat(v2.size(), Equals(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"}; - AssertThat(vKiwi, Equals(vKiwi2)); - AssertThat(vKiwi, !Equals(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; - AssertThat(vCopy, Equals("Kiwi")); - AssertThat(vCopy, Equals(vKiwi)); - AssertThat(vCopy, !Equals(vApple)); - vCopy = vApple; - AssertThat(vCopy, Equals("Apple")); - AssertThat(vCopy, !Equals(vKiwi)); - AssertThat(vCopy, Equals(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); - AssertThat(vMove, Equals("Kiwi")); - vMove = Move(vApple); - AssertThat(vMove, Equals("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 - 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)); - // 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' + // 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 - 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)); - }); + // 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 0cb43801..d23c6d90 100644 --- a/Tests/Core/Tag.spec.cpp +++ b/Tests/Core/Tag.spec.cpp @@ -1,109 +1,104 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include +#include -using namespace snowhouse; -using namespace bandit; using namespace p; -go_bandit([]() +P_SPEC("Core.Tag", []() { - describe("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)); - tag2 = tag; - AssertThat(p::GetHash(tag2), Equals(0)); - AssertThat(tag2.IsNone(), Equals(true)); - }); - it("Can assign from literal", [&]() - { - Tag tag{"Kiwi"}; - AssertThat(tag.AsString(), Equals("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}; - AssertThat(tag.AsString(), Equals("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"}; - AssertThat(tag.AsString(), Equals("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"}; - AssertThat(tagKiwi, Equals(tagKiwi2)); - AssertThat(tagKiwi, !Equals(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"}; - AssertThat(tagKiwi.AsString().data(), Equals(tagKiwi2.AsString().data())); - AssertThat(tagKiwi.AsString().data(), !Equals(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{}; - AssertThat(tagValid.IsNone(), Equals(false)); - AssertThat(tagValid, !Equals(Tag::None())); - AssertThat(tagInvalid.IsNone(), Equals(true)); - AssertThat(tagInvalid, Equals(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"}; - AssertThat(p::GetHash(tagKiwi), Equals(p::GetHash(tagKiwi2))); - AssertThat(tagKiwi.GetStringHash(), Equals(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; - AssertThat(tagCopy.AsString(), Equals("Kiwi")); - AssertThat(tagCopy, Equals(tagKiwi)); - AssertThat(tagCopy, !Equals(tagApple)); - tagCopy = tagApple; - AssertThat(tagCopy.AsString(), Equals("Apple")); - AssertThat(tagCopy, !Equals(tagKiwi)); - AssertThat(tagCopy, Equals(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); - AssertThat(tagKiwi, Equals(Tag::None())); - AssertThat(tagMove.AsString(), Equals("Kiwi")); - tagMove = Move(tagApple); - AssertThat(tagApple, Equals(Tag::None())); - AssertThat(tagMove.AsString(), Equals("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 58dc705f..8132f12f 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,250 +44,246 @@ struct TestComponent u32 TestComponent::destructed = 0; -go_bandit([]() +P_SPEC("ECS.Components", []() { - describe("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)); - - ctx.Add(id); - AssertThat(ctx.Has(id), Is().True()); - AssertThat(ctx.TryGet(id), Equals(nullptr)); - - ctx.Add(id); - AssertThat(ctx.Has(id), Is().True()); - AssertThat(ctx.TryGet(id), !Equals(nullptr)); - }); - - 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)); - - NonEmptyComponent::destructed = 0; - ctx.Remove(id); - AssertThat(ctx.Has(id), Is().False()); - AssertThat(ctx.TryGet(id), Equals(nullptr)); - AssertThat(NonEmptyComponent::destructed, Equals(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); - AssertThat(data, !Equals(nullptr)); - AssertThat(data->a, Equals(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); - 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)); - - // Repeat in different order - ctx.AddN(ids, NonEmptyComponent{2}); - - 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)); - }); - - 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); - AssertThat(ctx.IsValid(id), Is().False()); + 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); + }); - AssertThat(ctx.Has(id), Is().False()); - AssertThat(ctx.TryGet(id), Equals(nullptr)); - AssertThat(ctx.Has(id), Is().False()); - AssertThat(ctx.TryGet(id), Equals(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)", [&]() + for (Id id : ids) { - IdContext ctx; - Id id = AddId(ctx); - ctx.Add(id); - - RmId(ctx, id); - AssertThat(ctx.IsValid(id), Is().False()); - - AssertThat(ctx.Has(id), Is().True()); - AssertThat(ctx.TryGet(id), Equals(nullptr)); - AssertThat(ctx.Has(id), Is().True()); - AssertThat(ctx.TryGet(id), !Equals(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)); - }); - - 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)); - }); - - it("Can copy registry", []() - { - 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}; - AssertThat(ctxb.Has(id), Is().True()); - AssertThat(ctxb.Has(id), Is().True()); - AssertThat(ctxb.TryGet(id), !Equals(nullptr)); + It("Components are removed after node is deleted", []() + { + IdContext ctx; + Id id = AddId(ctx); + ctx.Add(id); - // Holds component values - AssertThat(ctxb.Has(id2), Is().True()); - AssertThat(ctxb.Get(id2).a, Equals(2)); - }); + RmId(ctx, id, p::RmIdFlags::Instant); + Expect(ctx.IsValid(id)).ToBeFalse(); - 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.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); + }); - id = AddId(ctx); - AssertThat(ctx.Has(id), Is().False()); - AssertThat(ctx.Has(id), Is().False()); + It("Can copy registry", []() + { + IdContext ctxa; - ctx.Add(id); - AssertThat(ctx.Has(id), Is().True()); - AssertThat(ctx.Has(id), Is().True()); - }); + Id id = AddId(ctxa); + ctxa.Add(id); + Id id2 = AddId(ctxa); + ctxa.AddN(id2, NonEmptyComponent{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]); - AssertThat( - NonEmptyComponent::destructed, Equals(4)); // 3 + 1 (passed by value on Add()) - AssertThat(TestComponent::destructed, Equals(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)); - }); - - 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()); - - AssertThat(ctx.Has(id), Is().False()); - AssertThat(ctx.TryGet(id), Equals(nullptr)); - AssertThat(ctx.Has(id), Is().False()); - AssertThat(ctx.TryGet(id), Equals(nullptr)); - }); - - 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()); - - AssertThat(ctx.Has(id), Is().True()); - AssertThat(ctx.TryGet(id), Equals(nullptr)); - AssertThat(ctx.Has(id), Is().True()); - AssertThat(ctx.TryGet(id), !Equals(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)); - }); - - 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); - AssertThat(ctx.Has(id), Is().False()); - AssertThat(ctx.Has(id), Is().True()); - AssertThat(ctx.TryGet(id), !Equals(nullptr)); - }); - - 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)); - }); + 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); }); }); diff --git a/Tests/ECS/ECS.spec.cpp b/Tests/ECS/ECS.spec.cpp index 9574dfe3..28f5495b 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,58 +15,55 @@ struct ECSTypeB {}; -go_bandit([]() +P_SPEC("ECS", []() { - describe("ECS", []() + It("Can copy context", []() { - it("Can copy context", [&]() - { - static IdContext* ctxPtr = nullptr; - - IdContext origin; - Id id = AddId(origin); - - ctxPtr = &origin; - 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)); - - ctxPtr = ⌖ - target.Add(id); - AssertThat(target.Has(id), Equals(true)); - }); - - it("Can move context", [&]() - { - static IdContext* ctxPtr = nullptr; - - IdContext origin; - Id id = AddId(origin); - - ctxPtr = &origin; - origin.Add(id); - AssertThat(origin.Has(id), Equals(true)); - - IdContext target{Move(origin)}; - AssertThat(origin.IsValid(id), Equals(false)); - - AssertThat(target.IsValid(id), Equals(true)); - AssertThat(target.Has(id), Equals(true)); - - ctxPtr = ⌖ - target.Add(id); - AssertThat(target.Has(id), Equals(true)); - }); - - it("Can assure pool", [&]() - { - IdContext origin; - TPool& pool = origin.AssurePool(); - AssertThat(pool.Size(), Equals(0)); - }); + 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 b8453116..b3c87a10 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,206 +23,208 @@ go_bandit([]() Id id3; Id id4; Id id5; - describe("ECS.Filtering", [&]() +} // namespace + + +P_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", []() { - before_each([&]() + 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); - AssertThat(typeIds.Contains(id1), Is().True()); - AssertThat(typeIds.Contains(id2), Is().True()); - AssertThat(typeIds.Contains(id3), Is().False()); - - TArray type2Ids = FindAllIdsWith(access); - AssertThat(type2Ids.Contains(id1), Is().False()); - AssertThat(type2Ids.Contains(id2), Is().True()); - AssertThat(type2Ids.Contains(id3), Is().True()); - }); - - 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()); - - TArray type2Ids = FindAllIdsWithAny(access); - AssertThat(type2Ids.Contains(id1), Is().True()); - AssertThat(type2Ids.Contains(id2), Is().True()); - AssertThat(type2Ids.Contains(id3), Is().True()); - }); - - 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 - - TArray ids = FindAllIdsWith(access); - AssertThat(ids.Contains(NoId), Is().False()); - AssertThat(ids.Size(), Equals(1)); - }); - - 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 - - FlushDeferredRemovals(ctx); - - TArray ids = FindAllIdsWith(access); - AssertThat(ids.Contains(NoId), Is().False()); - AssertThat(ids.Size(), Equals(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); - AssertThat(typeIds.Contains(id1), Is().True()); - AssertThat(typeIds.Contains(id2), Is().False()); - AssertThat(typeIds.Contains(id3), Is().False()); - }); - - 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()); - }); - - 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()); - }); + 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); - AssertThat(typeIds.Contains(id1), Is().True()); - AssertThat(typeIds.Contains(id2), Is().True()); - AssertThat(typeIds.Contains(id3), Is().False()); - }); - - 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()); - }); + 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); - 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()); - }); - - 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()); - }); + 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); - AssertThat(ids1.Contains(id1), Is().True()); + TIdScope access{ctx}; + TArray typeIds = FindAllIdsWithAny(access); - TArray ids2 = FindAllIdsWithAny(ctx); - AssertThat(ids2.Contains(id1), Is().True()); + 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); - AssertThat(ids3.Contains(id1), Is().True()); + It("Removes ids containing multiple component", []() + { + TIdScope access{ctx}; + TArray typeIds = FindAllIdsWithAny(access); - TArray ids4 = FindAllIdsWithAny(ctx); - ExcludeIdsWithout(ctx, ids4); - AssertThat(ids4.Contains(id1), Is().False()); + 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}; + + 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(); + }); - TArray ids1 = FindAllIdsWith(ctx); - AssertThat(ids1.Contains(id1), Is().True()); - 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)); - - TArray ids3 = FindAllIdsWith(ctx); - AssertThat(ids3.Contains(id1), Is().True()); - AssertThat(ids3.Contains(id2), Is().True()); + 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 5e0cfe01..edf24193 100644 --- a/Tests/ECS/Hierarchy.spec.cpp +++ b/Tests/ECS/Hierarchy.spec.cpp @@ -1,508 +1,486 @@ // 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([]() +P_SPEC("ECS.Hierarchy", []() { - describe("ECS.Hierarchy", []() + BeforeEach([]() { - IdContext ctx; - Id root; - Id child1; - Id child2; - Id child3; - Id grandchild; + ctx = {}; + root = AddId(ctx); + child1 = AddId(ctx); + child2 = AddId(ctx); + child3 = AddId(ctx); + grandchild = AddId(ctx); + }); - before_each([&]() + Describe("AttachId", []() + { + 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); - - 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)); - }); - - 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)); - }); + 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); - 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)); - }); + 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([]() { - before_each([&]() - { - 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); - 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", [&]() - { - DetachIdParent({ctx}, child1, false); + 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", [&]() - { - DetachIdParent({ctx}, {child1, child2}, false); + 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", []() + { + BeforeEach([]() { - before_each([&]() - { - 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); - 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", [&]() - { - DetachIdChildren({ctx}, root, 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", []() + { + BeforeEach([]() { - before_each([&]() - { - 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); - AssertThat(children, !Equals(nullptr)); - AssertThat(children->Size(), Equals(2)); - AssertThat(children->Contains(child1), Is().True()); - AssertThat(children->Contains(child2), Is().True()); - }); + 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); - AssertThat(outChildren.Size(), Equals(3)); - AssertThat(outChildren.Contains(grandchild), Is().True()); - }); + 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", [&]() - { - AssertThat(GetIdChildren({ctx}, child2), Equals(nullptr)); - }); + It("Returns null for entities without CParent component", []() + { + Expect(GetIdChildren({ctx}, child2)).ToEqual(nullptr); }); + }); - describe("GetAllIdChildren", [&]() + Describe("GetAllIdChildren", []() + { + BeforeEach([]() { - before_each([&]() - { - 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); - AssertThat(outChildren.Size(), Equals(2)); - AssertThat(outChildren.Contains(grandchild), Is().True()); - }); + 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); - AssertThat(outChildren.Size(), Equals(1)); - AssertThat(outChildren.Contains(grandchild), Is().False()); - }); + 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([]() { - before_each([&]() - { - AttachId({ctx}, root, child1); - AttachId({ctx}, child1, grandchild); - }); + AttachId({ctx}, root, child1); + AttachId({ctx}, child1, grandchild); + }); - it("Returns parent Id for child entities", [&]() - { - AssertThat(GetIdParent({ctx}, child1), Equals(root)); - AssertThat(GetIdParent({ctx}, grandchild), Equals(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); - AssertThat(outParents.Size(), Equals(2)); - AssertThat(outParents.Contains(root), Is().True()); - AssertThat(outParents.Contains(child1), Is().True()); - }); + 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", [&]() - { - AssertThat(GetIdParent({ctx}, root), Equals(NoId)); - }); + It("Returns NoId for root entities without parent", []() + { + Expect(GetIdParent({ctx}, root)).ToEqual(NoId); + }); - it("Returns NoId for entities without CChild component", [&]() - { - AssertThat(GetIdParent({ctx}, child2), Equals(NoId)); - }); + It("Returns NoId for entities without CChild component", []() + { + Expect(GetIdParent({ctx}, child2)).ToEqual(NoId); }); + }); - describe("GetAllIdParents", [&]() + Describe("GetAllIdParents", []() + { + BeforeEach([]() { - before_each([&]() - { - 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); - AssertThat(outParents.Size(), Equals(2)); - AssertThat(outParents[0], Equals(child1)); - AssertThat(outParents[1], Equals(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); - AssertThat(outParents.IsEmpty(), Is().True()); - }); + It("Returns empty when entity has no CChild component", []() + { + TArray outParents; + GetAllIdParents({ctx}, child2, outParents); + Expect(outParents.IsEmpty()).ToBeTrue(); }); + }); - describe("FindIdParent", [&]() + Describe("FindIdParent", []() + { + BeforeEach([]() { - before_each([&]() - { - 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) { - AssertThat(FindIdParent({ctx}, grandchild, - [&](Id id) - { - return id == root; - }), - Equals(root)); - }); + return id == root; + })).ToEqual(root); + }); - it("Finds immediate parent matching predicate", [&]() + It("Finds immediate parent matching predicate", []() + { + Expect(FindIdParent({ctx}, grandchild, [&](Id id) { - AssertThat(FindIdParent({ctx}, grandchild, - [&](Id id) - { - return id == child1; - }), - Equals(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) { - AssertThat(IsNone(FindIdParent({ctx}, grandchild, - [](Id) - { - return false; - })), - Is().True()); - }); + 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; - }); - AssertThat(outParents.Size(), Equals(1)); - AssertThat(outParents.Contains(intermediate), Is().True()); - }); + 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; - }); - AssertThat(outParents.IsEmpty(), Is().True()); + 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); - AssertThat(roots.IsEmpty(), Is().True()); + 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); - AssertThat(roots.Size(), Equals(1)); - AssertThat(roots.Contains(root), Is().True()); - }); + 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); - AssertThat(roots.Size(), Equals(2)); - AssertThat(roots.Contains(root), Is().True()); - AssertThat(roots.Contains(root2), Is().True()); - }); + 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); - AssertThat(roots.Size(), Equals(1)); - AssertThat(roots.Contains(root), Is().True()); - AssertThat(roots.Contains(child1), Is().False()); - }); + 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", []() { - before_each([&]() - { - 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); - AssertThat(roots.Size(), Equals(1)); - AssertThat(roots.Contains(root), Is().True()); - }); + 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); - AssertThat(roots.Size(), Equals(2)); - AssertThat(roots.Contains(root), Is().True()); - AssertThat(roots.Contains(root2), Is().True()); - }); + 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); - AssertThat(roots.Size(), Equals(1)); - AssertThat(roots.Contains(root), Is().True()); - }); + 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); - AssertThat(roots.IsEmpty(), Is().True()); - }); + 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); - AssertThat(roots.IsEmpty(), Is().True()); - }); + 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", []() { - before_each([&]() - { - 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", [&]() - { - AssertThat(FixParentIdLinks({ctx}, root), Is().False()); - }); + 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(); + }); - AssertThat(FixParentIdLinks({ctx}, root), Is().True()); - AssertThat(ctx.Get(child1).parent, Equals(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); - AssertThat(ctx.Has(child1), Is().False()); + Expect(FixParentIdLinks({ctx}, root)).ToBeTrue(); + Expect(ctx.Get(child1).parent).ToEqual(root); + }); - AssertThat(FixParentIdLinks({ctx}, root), Is().True()); - AssertThat(ctx.Has(child1), Is().True()); - AssertThat(ctx.Get(child1).parent, Equals(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([]() { - before_each([&]() - { - AttachId({ctx}, root, child1); - }); + AttachId({ctx}, root, child1); + }); - it("Returns true when all parent-child links are consistent", [&]() - { - AssertThat(ValidateParentIdLinks({ctx}, root), Is().True()); - }); + 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; - AssertThat(ValidateParentIdLinks({ctx}, root), Is().False()); - }); + 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); - 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..8250c787 100644 --- a/Tests/ECS/IdRegistry.spec.cpp +++ b/Tests/ECS/IdRegistry.spec.cpp @@ -1,178 +1,159 @@ // 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 + +P_SPEC("ECS.IdRegistry", []() { - template<> - struct Stringizer + It("Can create one id", []() { - static std::string ToString(Id id) - { - std::stringstream stream; - stream << "Id(" << id.value << ")"; - return stream.str(); - } - }; -} // namespace snowhouse + 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); + }); -go_bandit([]() -{ - describe("ECS.IdRegistry", []() + It("Can create two and remove first", []() { - it("Can create one id", [&]() - { - IdRegistry ids; - AssertThat(ids.Size(), Equals(0)); - Id id = ids.Create(); - AssertThat(id, !Equals(NoId)); - AssertThat(ids.IsValid(id), Is().True()); - AssertThat(ids.Size(), Equals(1)); - }); - - 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)); - }); - - 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)); - }); - - 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)); - }); - - 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)); - }); - - 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)); - }); - - 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)); - }); - - it("Removed id index gets reused", [&]() - { - IdRegistry ids; - ids.Create(); - Id id = ids.Create(); - ids.Create(); - AssertThat(ids.RemoveInstant(id), Is().True()); - Id id2 = ids.Create(); - AssertThat(id2.GetIndex(), Equals(id.GetIndex())); - Id id3 = ids.Create(); - AssertThat(id3.GetIndex(), !Equals(id.GetIndex())); - }); - - 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()); - Id id2 = ids.Create(); - AssertThat(id2.GetIndex(), !Equals(id.GetIndex())); - ids.FlushDeferredRemovals(); - Id id3 = ids.Create(); - AssertThat(id3.GetIndex(), Equals(id.GetIndex())); - Id id4 = ids.Create(); - AssertThat(id4.GetIndex(), !Equals(id.GetIndex())); - }); - - it("Can create many ids", [&]() + 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; - AssertThat(ids.Size(), Equals(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); - AssertThat(ids.Size(), Equals(3)); - for (i32 i = 0; i < list.Size(); ++i) - { - AssertThat(list[i].GetIndex(), Equals(i)); - AssertThat(ids.IsValid(list[i]), Is().True()); - } - }); + 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); - AssertThat(ids.Size(), Equals(3)); + Expect(ids.IsValid(list[i])).ToBeFalse(); + } + }); - AssertThat(ids.RemoveInstant(list), Is().True()); - AssertThat(ids.Size(), Equals(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) - { - AssertThat(ids.IsValid(list[i]), Is().False()); - } - }); + 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); - AssertThat(ids.Size(), Equals(3)); - - AssertThat(ids.Remove(list), Is().True()); - AssertThat(ids.Size(), Equals(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..85433b39 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,118 @@ struct ScopeTypeC }; -go_bandit([]() +P_SPEC("ECS.IdScopes", []() { - describe("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())); - }); - - 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()); - - id = AddId(ctx); - AssertThat(scope.Has(id), Is().False()); - AssertThat(scopeConst.Has(id), Is().False()); - - ctx.Add(id); - AssertThat(scope.Has(id), Is().True()); - AssertThat(scopeConst.Has(id), Is().True()); - - TIdScope scope2{ctx}; - ctx.Add(id); - AssertThat(scope2.Has(id), Is().True()); - }); - - it("Can initialize superset", [&]() - { - IdContext ctx; - TPool& typePool = ctx.AssurePool(); - - TIdScope> scope1{ctx}; - TIdScope> superset1{scope1}; - AssertThat(superset1.GetPool(), Equals(&typePool)); - - TIdScope> scope2{ctx}; - TIdScope superset2{scope2}; - AssertThat(superset2.GetPool(), Equals(&typePool)); - - TIdScope> scope3{ctx}; - TIdScope superset3{scope3}; - AssertThat(superset1.GetPool(), Equals(&typePool)); - }); - - it("Can mark modify", [&]() - { - IdContext ctx; - Id id = AddId(ctx); - TIdScope>> scope1{ctx}; - AssertThat(scope1.Has>(id), Is().False()); - scope1.Modify(id); - AssertThat(scope1.Has>(id), Is().True()); - AssertThat(scope1.IsModified(id), Is().True()); - - scope1.Remove>(id); - AssertThat(scope1.Has>(id), Is().False()); - AssertThat(scope1.IsModified(id), Is().False()); - - scope1.Modify(id); - AssertThat(scope1.Has>(id), Is().True()); - AssertThat(scope1.IsModified(id), Is().True()); - }); - - 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()); - - scope.Add(id); // Type B should be auto modified - AssertThat(scope.IsModified(id), Is().True()); - scope.Add(id); // Type B should not be auto modified - AssertThat(scope.IsModified(id), Is().False()); - - scope.ClearPool>(); - AssertThat(scope.IsModified(id), Is().False()); - - scope.Has(id); // Has should never mark modify - AssertThat(scope.IsModified(id), Is().False()); - - scope.Get(id); - AssertThat(scope.IsModified(id), Is().False()); - scope.Get(id); - AssertThat(scope.IsModified(id), Is().True()); - scope.Add(id); // Type B should not be auto modified - AssertThat(scope.IsModified(id), Is().False()); - - scope.ClearPool>(); - - scope.Remove(id); - AssertThat(scope.Has(id), Is().False()); - AssertThat(scope.IsModified(id), Is().True()); - - scope.Remove(id); // Type B should not be auto modified - AssertThat(scope.Has(id), Is().False()); - AssertThat(scope.IsModified(id), Is().False()); - }); + 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(); }); }); }); diff --git a/Tests/ECS/Statics.spec.cpp b/Tests/ECS/Statics.spec.cpp index 9199d56b..49f51d61 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,69 @@ struct StaticTypeThree }; -go_bandit([]() +P_SPEC("ECS.Statics", []() { - describe("ECS.Statics", []() + It("Can set an static", []() { - it("Can set an static", [&]() - { - IdContext ctx; - AssertThat(ctx.HasStatic(), Equals(false)); - auto& var = ctx.SetStatic({4}); - AssertThat(var.i, Equals(4)); - AssertThat(ctx.HasStatic(), Equals(true)); - AssertThat(ctx.HasStatic(), Equals(false)); - }); - it("Can set two statics", [&]() - { - IdContext ctx; - AssertThat(ctx.HasStatic(), Equals(false)); - AssertThat(ctx.HasStatic(), Equals(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)); - }); - it("Can replace an static", [&]() - { - IdContext ctx; - AssertThat(ctx.HasStatic(), Equals(false)); - ctx.SetStatic({4}); - ctx.SetStatic({2}); - AssertThat(ctx.GetStatic().i, Equals(2)); - AssertThat(ctx.HasStatic(), Equals(true)); - }); - it("Can get or set an static", [&]() - { - IdContext ctx; - // Can set - AssertThat(ctx.GetOrSetStatic({4}).i, Equals(4)); - // Can get - AssertThat(ctx.GetOrSetStatic({10}).i, Equals(4)); - }); - it("Can remove an static", [&]() - { - IdContext ctx; - ctx.SetStatic(); - AssertThat(ctx.HasStatic(), Equals(true)); - AssertThat(ctx.RemoveStatic(), Is().True()); - AssertThat(ctx.HasStatic(), Equals(false)); + 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); - AssertThat(ctx.RemoveStatic(), Is().False()); - }); + Expect(ctx.RemoveStatic()).ToBeFalse(); + }); - it("Can get statics", [&]() - { - IdContext ctx; - ctx.SetStatic({4}); - ctx.SetStatic({2}); - AssertThat(ctx.GetStatic().i, Equals(4)); - AssertThat(ctx.GetStatic().i, Equals(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}); - AssertThat(ctx.GetStatic().i, Equals(14)); + ctx.SetStatic({14}); + Expect(ctx.GetStatic().i).ToEqual(14); - ctx.RemoveStatic(); - AssertThat(ctx.TryGetStatic(), Is().Null()); - }); + ctx.RemoveStatic(); + Expect(ctx.TryGetStatic()).ToEqual(nullptr); }); }); diff --git a/Tests/Files/Paths.spec.cpp b/Tests/Files/Paths.spec.cpp index 17416441..1b571edf 100644 --- a/Tests/Files/Paths.spec.cpp +++ b/Tests/Files/Paths.spec.cpp @@ -1,217 +1,212 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include #include +#include -using namespace snowhouse; -using namespace bandit; +using namespace p; -go_bandit([]() +P_SPEC("Files.Paths", []() { - describe("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:\\")); + Expect(p::GetRootPathName("F:\\SomeFolder\\AnotherFolder")).ToEqual("F:"); + Expect(p::GetRootPath("F:\\SomeFolder\\AnotherFolder")).ToEqual("F:\\"); #elif P_PLATFORM_LINUX - AssertThat(p::GetRootPathName("/var/SomeFolder/AnotherFolder"), Equals("")); - AssertThat(p::GetRootPath("/var/SomeFolder/AnotherFolder"), Equals("/")); + Expect(p::GetRootPathName("/var/SomeFolder/AnotherFolder")).ToEqual(""); + Expect(p::GetRootPath("/var/SomeFolder/AnotherFolder")).ToEqual("/"); #endif - AssertThat(p::GetRootPathName("/AnotherFolder"), Equals("")); - AssertThat(p::GetRootPath("/AnotherFolder"), Equals("/")); - }); + 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")); + Expect(p::GetRelativePath("F:\\SomeFolder\\AnotherFolder")) + .ToEqual("SomeFolder\\AnotherFolder"); #endif - AssertThat(p::GetRelativePath("/var/SomeFolder/AnotherFolder"), - Equals("var/SomeFolder/AnotherFolder")); - AssertThat(p::GetRelativePath("/SomeFolder/AnotherFolder"), - Equals("SomeFolder/AnotherFolder")); - }); - - it("Can check absolute path", [&]() - { - AssertThat(p::IsAbsolutePath("//host"), Equals(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 - AssertThat(p::IsAbsolutePath("F:\\SomeFolder\\AnotherFolder"), Equals(true)); + Expect(p::IsAbsolutePath("F:\\SomeFolder\\AnotherFolder")).ToEqual(true); #elif P_PLATFORM_LINUX - AssertThat(p::IsAbsolutePath("/var/SomeFolder/AnotherFolder"), Equals(true)); + Expect(p::IsAbsolutePath("/var/SomeFolder/AnotherFolder")).ToEqual(true); #endif - AssertThat(p::IsAbsolutePath("Executable.exe"), Equals(false)); - AssertThat(p::IsAbsolutePath("SomeFolder/AnotherFolder"), Equals(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 - AssertThat(p::IsRelativePath("F:\\SomeFolder\\AnotherFolder"), Equals(false)); + Expect(p::IsRelativePath("F:\\SomeFolder\\AnotherFolder")).ToEqual(false); #elif P_PLATFORM_LINUX - AssertThat(p::IsRelativePath("/var/SomeFolder/AnotherFolder"), Equals(false)); + Expect(p::IsRelativePath("/var/SomeFolder/AnotherFolder")).ToEqual(false); #endif - AssertThat(p::IsRelativePath("Executable.exe"), Equals(true)); - AssertThat(p::IsRelativePath("SomeFolder/AnotherFolder"), Equals(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 - AssertThat(p::GetParentPath("F:\\SomeFolder\\AnotherFolder"), Equals("F:\\SomeFolder")); + Expect(p::GetParentPath("F:\\SomeFolder\\AnotherFolder")).ToEqual("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")); - }); - - it("Executable path is not empty", [&]() - { - AssertThat(p::PlatformPaths::GetExecutablePath(), !Equals("")); - }); - - 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 - 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("")); + 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 - 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("")); + 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 - AssertThat(p::GetExtension("AnotherFolder.lib"), Equals(".lib")); - AssertThat(p::GetExtension("AnotherFolder"), Equals("")); - }); + 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)); + 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 - 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)); + 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 - AssertThat(p::HasExtension("AnotherFolder.lib"), Equals(true)); - AssertThat(p::HasExtension("AnotherFolder"), Equals(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"); - AssertThat(path, Equals(p::String{"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"); - AssertThat(path, Equals(p::String{"/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"); - AssertThat(path, Equals(p::String{"AnotherFolder.txt"})); - path = "AnotherFolder."; - p::ReplaceExtension(path, ".txt"); - AssertThat(path, Equals(p::String{"AnotherFolder.txt"})); - path = "AnotherFolder.lib"; - p::ReplaceExtension(path, ".txt"); - AssertThat(path, Equals(p::String{"AnotherFolder.txt"})); - path = "AnotherFolder"; - p::ReplaceExtension(path, "txt"); - AssertThat(path, Equals(p::String{"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 - 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("")); + 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 - 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("")); + 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 - AssertThat(p::GetStem("AnotherFolder.lib"), Equals("AnotherFolder")); - AssertThat(p::GetStem("AnotherFolder"), Equals("AnotherFolder")); - AssertThat(p::GetStem(""), Equals("")); - }); + 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)); + 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 - 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)); + 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 - AssertThat(p::HasStem("AnotherFolder.lib"), Equals(true)); - AssertThat(p::HasStem("AnotherFolder"), Equals(true)); - AssertThat(p::HasStem(""), Equals(false)); - }); - - - 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"})); + 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 - 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"})); + 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 - 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"})); + 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..be9c5110 100644 --- a/Tests/Math/Color.spec.cpp +++ b/Tests/Math/Color.spec.cpp @@ -1,176 +1,134 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include +#include -using namespace snowhouse; -using namespace bandit; using namespace p; -namespace snowhouse +P_SPEC("Math.Color", []() { - template<> - struct Stringizer + Describe("Helpers", []() { - static std::string ToString(u8 a) + It("Can make from rgba", []() { - std::stringstream stream; - stream << u32(a); - return stream.str(); - } - }; - template<> - struct Stringizer - { - static std::string ToString(const Color& a) + 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", []() { - std::stringstream stream; - stream << "Color(" << u32(a.r) << ", " << u32(a.g) << ", " << u32(a.b) << ", " - << u32(a.a) << ")"; - return stream.str(); - } - }; - - template<> - struct Stringizer + 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", []() { - static std::string ToString(const LinearColor& a) + It("Can Shade", []() { - std::stringstream stream; - stream << "LinearColor(" << a.r << ", " << a.g << ", " << a.b << ", " << a.a << ")"; - return stream.str(); - } - }; -} // namespace snowhouse + 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); + }); -go_bandit([]() -{ - describe("Math.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", []() + { + Expect(std::abs(LinearColor::Black().Translucency(0.5f).Tint(1.0f).a - 0.5f)) + .ToBeLessOrEqual(0.01f); + }); + }); + Describe("Color", []() { - describe("Helpers", [&]() + 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", []() { - 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)); - }); - 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)); - - 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)); - }); - - 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)); - - 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)); - - 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)); - - 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)); - }); - - 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::White().Translucency(127).Shade(1.0f).a).ToEqual(127); }); - describe("LinearColor", [&]() + + It("Can Tint", []() { - it("Can Shade", [&]() - { - AssertThat(LinearColor::White().Shade(1.0f), Equals(LinearColor::Black())); - AssertThat(LinearColor::White().Shade(0.5f), Equals(LinearColor::Gray())); - constexpr LinearColor color{Color::FromHex(0x80ced7)}; - AssertThat(color.Shade(0.5f), Equals(LinearColor{Color::FromHex(0x40676B)})); - }); - - it("Shade doesn't change alpha", [&]() - { - AssertThat(LinearColor::White().Translucency(0.5f).Shade(1.0f).a, - EqualsWithDelta(0.5f, 0.01f)); - }); - - 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))); - }); - - it("Tint doesn't change alpha", [&]() - { - AssertThat(LinearColor::Black().Translucency(0.5f).Tint(1.0f).a, - EqualsWithDelta(0.5f, 0.01f)); - }); + 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)); }); - describe("Color", [&]() + + It("Tint doesn't change alpha", []() + { + Expect(Color::Black().Translucency(127).Tint(1.0f).a).ToEqual(127); + }); + + It("Can convert to linear", []() { - 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))); - }); - - it("Shade doesn't change alpha", [&]() - { - AssertThat(Color::White().Translucency(127).Shade(1.0f).a, Equals(127)); - }); - - 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))); - }); - - it("Tint doesn't change alpha", [&]() - { - AssertThat(Color::Black().Translucency(127).Tint(1.0f).a, Equals(127)); - }); - - 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..c13f5264 100644 --- a/Tests/Math/Math.spec.cpp +++ b/Tests/Math/Math.spec.cpp @@ -1,361 +1,360 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include #include #include #include +#include #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 + + +P_SPEC("Math.Math", []() +{ + Describe("Binary Search", []() { - describe("Binary Search", []() + It("LowerBound", [=]() { - TArray bottomUp{23, 34, 50, 100, 120}; - TArray topDown{120, 100, 50, 34, 23}; - - 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", [&]() - { - AssertThat(bottomUp.UpperBound(34), Equals(2)); - AssertThat(bottomUp.UpperBound(100), Equals(4)); + It("UpperBound", [=]() + { + 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", [&]() - { - 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)); + 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); - 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}; + TArray bottomUp{23, 34, 50, 50, 100, 120}; - it("Find first item", [&]() - { - auto i4 = bottomUp.FindSortedMax(23, false); - AssertThat(i4, Equals(NO_INDEX)); + It("Find first item", [=]() + { + auto i4 = bottomUp.FindSortedMax(23, false); + Expect(i4).ToEqual(NO_INDEX); - auto i5 = bottomUp.FindSortedMax(23, true); - AssertThat(i5, Equals(0)); + auto i5 = bottomUp.FindSortedMax(23, true); + Expect(i5).ToEqual(0); - auto i6 = bottomUp.FindSortedMax(22, true); - AssertThat(i6, Equals(NO_INDEX)); - }); + auto i6 = bottomUp.FindSortedMax(22, true); + Expect(i6).ToEqual(NO_INDEX); + }); - it("Find any item", [&]() - { - auto i1 = bottomUp.FindSortedMax(34, true); - AssertThat(i1, Equals(1)); + It("Find any item", [=]() + { + auto i1 = bottomUp.FindSortedMax(34, true); + Expect(i1).ToEqual(1); - auto i2 = bottomUp.FindSortedMax(33, true); - AssertThat(i2, Equals(0)); + auto i2 = bottomUp.FindSortedMax(33, true); + Expect(i2).ToEqual(0); - auto i3 = bottomUp.FindSortedMax(34, false); - AssertThat(i3, Equals(0)); - }); + auto i3 = bottomUp.FindSortedMax(34, false); + Expect(i3).ToEqual(0); + }); - it("Find last item", [&]() - { - auto i4 = bottomUp.FindSortedMax(120, false); - AssertThat(i4, Equals(4)); + It("Find last item", [=]() + { + auto i4 = bottomUp.FindSortedMax(120, false); + Expect(i4).ToEqual(4); - auto i5 = bottomUp.FindSortedMax(120, true); - AssertThat(i5, Equals(5)); + auto i5 = bottomUp.FindSortedMax(120, true); + Expect(i5).ToEqual(5); - auto i6 = bottomUp.FindSortedMax(121, true); - AssertThat(i6, Equals(5)); + auto i6 = bottomUp.FindSortedMax(121, true); + Expect(i6).ToEqual(5); - auto i7 = bottomUp.FindSortedMax(100, false); - AssertThat(i7, Equals(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); - AssertThat(i4, Equals(0)); + It("Find first item", [=]() + { + auto i4 = topDown.FindSortedMax(120, true); + Expect(i4).ToEqual(0); - auto i5 = topDown.FindSortedMax(120, false); - AssertThat(i5, Equals(1)); + auto i5 = topDown.FindSortedMax(120, false); + Expect(i5).ToEqual(1); - auto i6 = topDown.FindSortedMax(121, true); - AssertThat(i6, Equals(0)); - }); + auto i6 = topDown.FindSortedMax(121, true); + Expect(i6).ToEqual(0); + }); - it("Find any item", [&]() - { - auto i1 = topDown.FindSortedMax(34, true); - AssertThat(i1, Equals(4)); + It("Find any item", [=]() + { + auto i1 = topDown.FindSortedMax(34, true); + Expect(i1).ToEqual(4); - auto i2 = topDown.FindSortedMax(33, true); - AssertThat(i2, Equals(5)); + auto i2 = topDown.FindSortedMax(33, true); + Expect(i2).ToEqual(5); - auto i3 = topDown.FindSortedMax(34, false); - AssertThat(i3, Equals(5)); - }); + auto i3 = topDown.FindSortedMax(34, false); + Expect(i3).ToEqual(5); + }); - it("Find last item", [&]() - { - auto i4 = topDown.FindSortedMax(23, false); - AssertThat(i4, Equals(NO_INDEX)); + It("Find last item", [=]() + { + auto i4 = topDown.FindSortedMax(23, false); + Expect(i4).ToEqual(NO_INDEX); - auto i5 = topDown.FindSortedMax(23, true); - AssertThat(i5, Equals(5)); + auto i5 = topDown.FindSortedMax(23, true); + Expect(i5).ToEqual(5); - auto i6 = topDown.FindSortedMax(22, true); - AssertThat(i6, Equals(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); - AssertThat(i1, Equals(NO_INDEX)); + It("Doesnt find smaller", [=]() + { + auto i1 = allEqual.FindSortedMax(9, false); + Expect(i1).ToEqual(NO_INDEX); - auto i2 = allEqual.FindSortedMax(10, false); - AssertThat(i2, Equals(NO_INDEX)); - }); + auto i2 = allEqual.FindSortedMax(10, false); + Expect(i2).ToEqual(NO_INDEX); + }); - it("Finds smaller", [&]() - { - auto i1 = allEqual.FindSortedMax(10, true); - AssertThat(i1, Equals(0)); + It("Finds smaller", [=]() + { + auto i1 = allEqual.FindSortedMax(10, true); + Expect(i1).ToEqual(0); - auto i2 = allEqual.FindSortedMax(11, false); - AssertThat(i2, Equals(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); - AssertThat(i1, Equals(0)); + It("Find first item", [=]() + { + auto i1 = bottomUp.FindSortedMin(23, true); + Expect(i1).ToEqual(0); - auto i2 = bottomUp.FindSortedMin(20, true); - AssertThat(i2, Equals(0)); + auto i2 = bottomUp.FindSortedMin(20, true); + Expect(i2).ToEqual(0); - auto i3 = bottomUp.FindSortedMin(23, false); - AssertThat(i3, Equals(1)); - }); + auto i3 = bottomUp.FindSortedMin(23, false); + Expect(i3).ToEqual(1); + }); - it("Find any item", [&]() - { - auto i1 = bottomUp.FindSortedMin(33, false); - AssertThat(i1, Equals(1)); + It("Find any item", [=]() + { + auto i1 = bottomUp.FindSortedMin(33, false); + Expect(i1).ToEqual(1); - auto i2 = bottomUp.FindSortedMin(34, true); - AssertThat(i2, Equals(1)); + auto i2 = bottomUp.FindSortedMin(34, true); + Expect(i2).ToEqual(1); - auto i3 = bottomUp.FindSortedMin(34, false); - AssertThat(i3, Equals(2)); - }); + auto i3 = bottomUp.FindSortedMin(34, false); + Expect(i3).ToEqual(2); + }); - it("Find last item", [&]() - { - auto i1 = bottomUp.FindSortedMin(100, false); - AssertThat(i1, Equals(5)); + It("Find last item", [=]() + { + auto i1 = bottomUp.FindSortedMin(100, false); + Expect(i1).ToEqual(5); - auto i2 = bottomUp.FindSortedMin(120, false); - AssertThat(i2, Equals(NO_INDEX)); + auto i2 = bottomUp.FindSortedMin(120, false); + Expect(i2).ToEqual(NO_INDEX); - auto i3 = bottomUp.FindSortedMin(120, true); - AssertThat(i3, Equals(5)); + auto i3 = bottomUp.FindSortedMin(120, true); + Expect(i3).ToEqual(5); - auto i4 = bottomUp.FindSortedMin(121, true); - AssertThat(i4, Equals(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); - AssertThat(i4, Equals(0)); + It("Find first item", [=]() + { + auto i4 = topDown.FindSortedMin(120, true); + Expect(i4).ToEqual(0); - auto i5 = topDown.FindSortedMin(120, false); - AssertThat(i5, Equals(NO_INDEX)); + auto i5 = topDown.FindSortedMin(120, false); + Expect(i5).ToEqual(NO_INDEX); - auto i6 = topDown.FindSortedMin(121, true); - AssertThat(i6, Equals(NO_INDEX)); - }); + auto i6 = topDown.FindSortedMin(121, true); + Expect(i6).ToEqual(NO_INDEX); + }); - it("Find any item", [&]() - { - auto i1 = topDown.FindSortedMin(34, true); - AssertThat(i1, Equals(4)); + It("Find any item", [=]() + { + auto i1 = topDown.FindSortedMin(34, true); + Expect(i1).ToEqual(4); - auto i2 = topDown.FindSortedMin(33, true); - AssertThat(i2, Equals(4)); + auto i2 = topDown.FindSortedMin(33, true); + Expect(i2).ToEqual(4); - auto i3 = topDown.FindSortedMin(34, false); - AssertThat(i3, Equals(3)); - }); + auto i3 = topDown.FindSortedMin(34, false); + Expect(i3).ToEqual(3); + }); - it("Find last item", [&]() - { - auto i4 = topDown.FindSortedMin(23, false); - AssertThat(i4, Equals(4)); + It("Find last item", [=]() + { + auto i4 = topDown.FindSortedMin(23, false); + Expect(i4).ToEqual(4); - auto i5 = topDown.FindSortedMin(23, true); - AssertThat(i5, Equals(5)); + auto i5 = topDown.FindSortedMin(23, true); + Expect(i5).ToEqual(5); - auto i6 = topDown.FindSortedMin(22, true); - AssertThat(i6, Equals(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); - AssertThat(i1, Equals(NO_INDEX)); + It("Doesnt find bigger", [=]() + { + auto i1 = allEqual.FindSortedMin(11, false); + Expect(i1).ToEqual(NO_INDEX); - auto i2 = allEqual.FindSortedMin(10, false); - AssertThat(i2, Equals(NO_INDEX)); - }); + auto i2 = allEqual.FindSortedMin(10, false); + Expect(i2).ToEqual(NO_INDEX); + }); - it("Finds bigger", [&]() - { - auto i1 = allEqual.FindSortedMin(10, true); - AssertThat(i1, Equals(0)); + It("Finds bigger", [=]() + { + auto i1 = allEqual.FindSortedMin(10, true); + Expect(i1).ToEqual(0); - auto i2 = allEqual.FindSortedMin(9, false); - AssertThat(i2, Equals(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 Infinite", [&]() + It("Can check NAN", [=]() + { + Expect(IsNAN(0.0)).ToEqual(false); + Expect(IsNAN(Limits::QuietNaN())).ToEqual(true); + }); + + Describe("Roundings", []() + { + It("Can Floor", [=]() { - 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(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(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(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", [=]() { - AssertThat(IsNAN(0.0), Equals(false)); - AssertThat(IsNAN(Limits::QuietNaN()), Equals(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", [&]() - { - 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)); - - 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)); - }); - 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)); - - 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(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", [&]() - { - 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)); - - 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)); - }); + 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 4c1fb542..85dab325 100644 --- a/Tests/Math/Vector.spec.cpp +++ b/Tests/Math/Vector.spec.cpp @@ -1,73 +1,68 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include +#include #include -using namespace snowhouse; -using namespace bandit; using namespace p; -go_bandit([]() +P_SPEC("Math.Vector", []() { - describe("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)); - 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)); - 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)); - 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)); - 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)); - 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)); - 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)); - 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)); - }); + 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(); - AssertThat(anglea, Equals(90.f)); - float angleb = p::v2{0.f, -1.f}.Angle(); - AssertThat(angleb, Equals(-90.f)); - float anglec = p::v2{1.f, 0.f}.Angle(); - AssertThat(anglec, Equals(0.f)); - float angled = p::v2{-1.f, 0.f}.Angle(); - AssertThat(angled, Equals(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", [&]() - { - AssertThat(p::v2::FromAngle(0.f).Angle(), Equals(0)); - AssertThat(p::v2::FromAngle(90.f).Angle(), Equals(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 4f93ef4f..d12cb796 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,283 +14,280 @@ struct TypeOfSize }; -go_bandit([]() +P_SPEC("Memory.BestFitArena", []() { - describe("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)); - }); - - 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()); - }); - - 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>(); - AssertThat(p, Is().EqualTo(blockPtr)); - - void* p2 = arena.Alloc(4); - new (p2) TypeOfSize<4>(); - AssertThat(p2, Is().EqualTo(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>(); - AssertThat(p, Is().Not().Null()); - AssertThat(arena.Contains(p), Is().True()); - - // Another 16 bytes - void* p2 = arena.Alloc(6); - new (p2) TypeOfSize<6>(); - AssertThat(p2, Is().Not().Null()); - AssertThat(arena.Contains(p2), Is().True()); - - // No more space, return null - void* p3 = arena.Alloc(8); // 8 bytes - AssertThat(p3, Is().Null()); - }); - - 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>(); - AssertThat(GetAlignmentPadding(p, 8), Is().EqualTo(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)); - - // 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)); - }); - - 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>(); - AssertThat(p, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(32)); + It("Can allocate", []() + { + BestFitArena arena{1024}; + arena.GetStats()->detectLeaks = false; - arena.Free(p, 32); - AssertThat(arena.GetFreeSize(), Equals(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>(); - AssertThat(p, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(48)); + const auto* blockPtr = static_cast(*arena.GetBlock()); - void* p2 = arena.Alloc(16); - new (p2) TypeOfSize<16>(); - AssertThat(p2, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(32)); + void* p = arena.Alloc(4); + new (p) TypeOfSize<4>(); + Expect(p).ToEqual(blockPtr); - arena.Free(p2, 16); - AssertThat(arena.GetFreeSize(), Equals(48)); + void* p2 = arena.Alloc(4); + new (p2) TypeOfSize<4>(); + Expect(p2).ToEqual(blockPtr + 4); + }); - arena.Free(p, 16); - AssertThat(arena.GetFreeSize(), Equals(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>(); - AssertThat(p, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(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)); - - void* p3 = arena.Alloc(2); - new (p3) TypeOfSize<2>(); - AssertThat(p3, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(0)); - // No space left, no free slots - AssertThat(arena.GetFreeSlots().Size(), Equals(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)); - }); - - 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>(); - AssertThat(p, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(55)); + It("Can free", []() + { + BestFitArena arena{64}; + arena.GetStats()->detectLeaks = false; - 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)); + 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>(); - AssertThat(p3, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(0)); + arena.Free(p, 32); + Expect(arena.GetFreeSize()).ToEqual(64); + }); - // No space left, no free slots - AssertThat(arena.GetFreeSlots().Size(), Equals(0)); + It("Can free multiple", []() + { + BestFitArena arena{64}; + arena.GetStats()->detectLeaks = false; - arena.Free(p, 9); - AssertThat(arena.GetFreeSlots().Size(), Equals(1)); + void* p = arena.Alloc(16); + new (p) TypeOfSize<16>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(48); - arena.Free(p3, 5); - AssertThat(arena.GetFreeSlots().Size(), Equals(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 - AssertThat(arena.GetFreeSlots().Size(), Equals(1)); - AssertThat(arena.GetFreeSlots()[0].size, Equals(64)); + arena.Free(p2, 16); + Expect(arena.GetFreeSize()).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())); - }); + 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>(); - AssertThat(p, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(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>(); - AssertThat(p2, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(0)); - AssertThat(arena.GetFreeSlots().Size(), Equals(0)); + void* p = arena.Alloc(9); + new (p) TypeOfSize<9>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(55); - arena.Free(p, 39); - AssertThat(arena.GetFreeSlots().Size(), Equals(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 - AssertThat(arena.GetFreeSlots().Size(), Equals(1)); - AssertThat(arena.GetFreeSlots()[0].size, Equals(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 - AssertThat(arena.GetFreeSlots()[0].start, Equals(arena.GetBlock().data)); - AssertThat(arena.GetFreeSlots()[0].End(), Equals(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>(); - AssertThat(p, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(24)); + arena.Free(p3, 5); + Expect(arena.GetFreeSlots().Size()).ToEqual(2); - 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)); + 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); - AssertThat(arena.GetFreeSlots().Size(), Equals(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 - AssertThat(arena.GetFreeSlots().Size(), Equals(1)); - AssertThat(arena.GetFreeSlots()[0].size, Equals(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 - AssertThat(arena.GetFreeSlots()[0].start, Equals(arena.GetBlock().data)); - AssertThat(arena.GetFreeSlots()[0].End(), Equals(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>(); - AssertThat(arena.GetFreeSize(), Equals(120)); - - void* p2 = arena.Alloc(8, 64); - new (p2) TypeOfSize<8>(); - AssertThat(p2, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(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)); - - // 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())); - - // 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..6cd6605f 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,300 +13,292 @@ struct TypeOfSize p::u8 data[size]{0}; // Fill data for debugging }; -go_bandit([]() +P_SPEC("Memory.BigBestFitArena", []() { - describe("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)); - }); - - it("Can allocate", [&]() - { - BigBestFitArena arena{1024}; - arena.GetStats()->detectLeaks = false; + BigBestFitArena 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(4); - new (p) TypeOfSize<4>(); - AssertThat(p, Is().Not().Null()); - AssertThat(arena.Contains(p), Is().True()); - }); + It("Can allocate", []() + { + BigBestFitArena arena{1024}; + arena.GetStats()->detectLeaks = false; - it("Allocates at correct addresses", [&]() - { - 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(); + }); - const auto* blockPtr = static_cast(*arena.GetBlock()); + It("Allocates at correct addresses", []() + { + BigBestFitArena arena{1024}; + arena.GetStats()->detectLeaks = false; - void* p = arena.Alloc(4); - new (p) TypeOfSize<4>(); - const void* expectedP = blockPtr + p::GetAlignmentPaddingWithHeader(blockPtr, 8, 8); - AssertThat(p, Is().EqualTo(expectedP)); + const auto* blockPtr = static_cast(*arena.GetBlock()); - 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)); - }); + void* p = arena.Alloc(4); + new (p) TypeOfSize<4>(); + const void* expectedP = blockPtr + p::GetAlignmentPaddingWithHeader(blockPtr, 8, 8); + Expect(p).ToEqual(expectedP); - 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>(); - AssertThat(p, Is().Not().Null()); - AssertThat(arena.Contains(p), Is().True()); - - // Another 16 bytes - void* p2 = arena.Alloc(4); - new (p2) TypeOfSize<4>(); - AssertThat(p2, Is().Not().Null()); - AssertThat(arena.Contains(p2), Is().True()); - - // No more space, return null - void* p3 = arena.Alloc(8); // 8 bytes - AssertThat(p3, Is().Null()); - }); - - it("Allocates with alignment", [&]() - { - BigBestFitArena arena{1024}; - arena.GetStats()->detectLeaks = false; + 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* b = arena.Alloc(1); - new (b) TypeOfSize<1>(); + 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); + }); - // 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)); + 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 0 (last ptr is aligned) - void* p2 = arena.Alloc(4, 16); - new (p2) TypeOfSize<4>(); - AssertThat(p::GetAlignmentPadding(p2, 16), Is().EqualTo(0)); + It("Can free", []() + { + BigBestFitArena arena{64}; + arena.GetStats()->detectLeaks = false; - // 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)); - }); + void* p = arena.Alloc(32); + new (p) TypeOfSize<32>(); + Expect(p).ToNotEqual(nullptr); + Expect(arena.GetFreeSize()).ToEqual(24); - it("Can free", [&]() - { - BigBestFitArena arena{64}; - arena.GetStats()->detectLeaks = false; + arena.Free(p, 32); + Expect(arena.GetFreeSize()).ToEqual(64); + }); - void* p = arena.Alloc(32); - new (p) TypeOfSize<32>(); - AssertThat(p, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(24)); + It("Can free multiple", []() + { + BigBestFitArena arena{64}; + arena.GetStats()->detectLeaks = false; - arena.Free(p, 32); - AssertThat(arena.GetFreeSize(), Equals(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>(); - AssertThat(p, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(40)); + arena.Free(p2, 16); + Expect(arena.GetFreeSize()).ToEqual(40); - void* p2 = arena.Alloc(16); - new (p2) TypeOfSize<16>(); - AssertThat(p2, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(16)); + arena.Free(p, 16); + Expect(arena.GetFreeSize()).ToEqual(64); + }); - arena.Free(p2, 16); - AssertThat(arena.GetFreeSize(), Equals(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); - AssertThat(arena.GetFreeSize(), Equals(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>(); - AssertThat(p, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(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)); - - void* p3 = arena.Alloc(8); - new (p3) TypeOfSize<8>(); - AssertThat(p3, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(0)); - // No space left, no free slots - AssertThat(arena.GetFreeSlots().Size(), Equals(0)); - - arena.Free(p2, 16); - AssertThat(arena.GetFreeSize(), Equals(24)); - AssertThat(arena.GetFreeSlots().Size(), Equals(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)); - }); - - 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)); - - 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)); - - void* p3 = arena.Alloc(8); - new (p3) TypeOfSize<8>(); - AssertThat(p3, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(0)); - - // No space left, no free slots - AssertThat(arena.GetFreeSlots().Size(), Equals(0)); - - arena.Free(p, 16); - AssertThat(arena.GetFreeSlots().Size(), Equals(1)); - - arena.Free(p3, 8); - AssertThat(arena.GetFreeSlots().Size(), Equals(2)); - - arena.Free(p2, 16); // Slots previous and next are merged - AssertThat(arena.GetFreeSlots().Size(), Equals(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()))); - }); - - 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)); - - 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)); - - arena.Free(p, 16); - AssertThat(arena.GetFreeSlots().Size(), Equals(1)); - - arena.Free(p2, 16); // Slot is expanded from the front - AssertThat(arena.GetFreeSlots().Size(), Equals(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()))); - }); - - 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)); - - 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)); - - arena.Free(p2, 16); - AssertThat(arena.GetFreeSlots().Size(), Equals(1)); - - arena.Free(p, 16); // Slot is expanded from the back - AssertThat(arena.GetFreeSlots().Size(), Equals(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()))); - }); - - it("Ensures a big alignment leaves a gap", [&]() + 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", []() + { + 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>(); - AssertThat(arena.GetFreeSize(), Equals(112)); - - void* p2 = arena.Alloc(8, 64); - new (p2) TypeOfSize<8>(); - AssertThat(p2, Is().Not().Null()); - AssertThat(arena.GetFreeSize(), Equals(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)); - - // 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()))); - - // 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))); - } - }); + 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 c0c41835..39b5a2d0 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,196 +53,193 @@ struct MoveType }; -go_bandit([]() +P_SPEC("Memory.Operations", []() { - describe("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)); - - 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)); - - u32 u32Values[2]{34, 45}; // Assign simulated garbage - ConstructItems(u32Values, 2); - AssertThat(u32Values[0], Is().EqualTo(0)); - AssertThat(u32Values[1], Is().EqualTo(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)); - - 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)); - - 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); - 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)); - }); - - 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)); - - 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)); - - 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)); - - 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)); - - 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)); - - BoolsType boolsValues[2]{ - {.value1 = false, .value2 = true}, - {.value1 = false, .value2 = true} - }; // 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)); - }); - - 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)); - - 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)); - - 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)); - - 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)); - - 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)); - - BoolsType boolsValues[2]{ - {false, true}, - {false, true} - }; // Assign simulated garbage - BoolsType srcBoolsValues[2]{ - {true, false}, - {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)); - - 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)); - }); - - 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)); - - 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)); - - 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)); - - 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)); - - 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)); - - 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); - 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)); - - 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)); - }); + // 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 27edffad..d7c36243 100644 --- a/Tests/Memory/MemoryStats.spec.cpp +++ b/Tests/Memory/MemoryStats.spec.cpp @@ -1,15 +1,13 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include +#include #include #include #include -using namespace snowhouse; -using namespace bandit; using namespace p; @@ -24,622 +22,619 @@ static const MemoryStatsEvent* LiveFind(const MemoryStats& s, void* ptr) } -go_bandit([]() +P_SPEC("Memory.MemoryStats", []() { - describe("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)); - }); + 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(); - 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)); - }); + 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(); - AssertThat(s.used, Is().EqualTo(0)); - AssertThat(LiveCount(s), Is().EqualTo(0)); - // totalAllocated is cumulative alloc bytes ever. - AssertThat(s.totalAllocated, Is().EqualTo(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(); - AssertThat(s.used, Is().EqualTo(16 + 32 + 64)); - AssertThat(s.totalAllocated, Is().EqualTo(16 + 32 + 64)); - AssertThat(LiveCount(s), Is().EqualTo(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", [&]() + It("Tracks many adds and frees", []() + { + MemoryStats s; + s.detectLeaks = false; + const sizet N = 100; + TArray buf(N * 16); + + 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); + }); - AssertThat(s.used, Is().EqualTo((N / 2) * 16)); - AssertThat(s.totalAllocated, Is().EqualTo(N * 16)); - AssertThat(LiveCount(s), Is().EqualTo(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. - AssertThat(s.used, Is().EqualTo(0)); - AssertThat(LiveCount(s), Is().EqualTo(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(); - AssertThat(s.used, Is().EqualTo(0)); - AssertThat(LiveCount(s), Is().EqualTo(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. - 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)); - }); + 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(); - AssertThat(LiveCount(s), Is().EqualTo(1)); - AssertThat(s.used, Is().EqualTo(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(); - AssertThat(s.used, Is().EqualTo(0)); - AssertThat(LiveCount(s), Is().EqualTo(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); + }); - AssertThat(LiveCount(s), Is().EqualTo(2)); - AssertThat(LiveFind(s, (void*)0x1000)->GetSize(), Is().EqualTo(64)); - AssertThat(LiveFind(s, (void*)0x2000)->GetSize(), Is().EqualTo(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(); - AssertThat(LiveCount(s), Is().EqualTo(2)); - AssertThat(s.used, Is().EqualTo(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(); - - 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)); - }); + 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(); - AssertThat(LiveCount(s), Is().EqualTo(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(); - AssertThat(s.used, Is().EqualTo(32)); - AssertThat(s.totalAllocated, Is().EqualTo(32)); - AssertThat(LiveCount(s), Is().EqualTo(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(); - 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)); + 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(); - AssertThat(LiveCount(s), Is().EqualTo(0)); - AssertThat(s.used, Is().EqualTo(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(); - 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)); - - // 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)); - }); + 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(); - 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)); - }); - 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(); - AssertThat(s.used, Is().EqualTo(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. - AssertThat(s.used, Is().EqualTo(64)); - AssertThat(LiveCount(s), Is().EqualTo(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(); - AssertThat(s.used, Is().EqualTo(96)); - - s.Reset(); - AssertThat(s.used, Is().EqualTo(0)); - AssertThat(s.totalAllocated, Is().EqualTo(0)); - AssertThat(LiveCount(s), Is().EqualTo(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(); - AssertThat(s.used, Is().EqualTo(96)); - AssertThat(LiveCount(s), Is().EqualTo(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(); - AssertThat(s.used, Is().EqualTo(64)); - AssertThat(LiveCount(s), Is().EqualTo(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(); - AssertThat(s.used, Is().EqualTo(N * 8)); - AssertThat(s.totalAllocated, Is().EqualTo(N * 8)); - AssertThat(LiveCount(s), Is().EqualTo(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(); - AssertThat(s.used, Is().EqualTo((N / 2) * 8)); - AssertThat(s.totalAllocated, Is().EqualTo(N * 8)); - AssertThat(LiveCount(s), Is().EqualTo(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(); - AssertThat(LiveCount(s), Is().EqualTo(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(); - AssertThat(LiveCount(s), Is().EqualTo(N / 2)); - AssertThat(s.used, Is().EqualTo((N / 2) * 8)); }); - }); + start.store(true, std::memory_order_release); + producer.join(); + consumer.join(); - 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}; + Expect(LiveCount(s)).ToEqual(N); + Expect(s.used).ToEqual(N * 8); - 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); - }); - - 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(); - - AssertThat(LiveCount(s), Is().EqualTo(N)); - AssertThat(s.used, Is().EqualTo(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(); - - AssertThat(LiveCount(s), Is().EqualTo(N)); - AssertThat(s.used, Is().EqualTo(N * 8)); - AssertThat(s.totalAllocated, Is().EqualTo(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(); - 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(); - }); + // 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(); + }); + + start.store(true, std::memory_order_release); + for (auto& t : producers) + { + t.join(); + } + consumer.join(); - // s.used reflects the net remaining live set. - AssertThat(s.used, Is().EqualTo(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(); }); }); -}); \ No newline at end of file +}); diff --git a/Tests/Memory/MonoLinearArena.spec.cpp b/Tests/Memory/MonoLinearArena.spec.cpp index bea7e19f..db7b2944 100644 --- a/Tests/Memory/MonoLinearArena.spec.cpp +++ b/Tests/Memory/MonoLinearArena.spec.cpp @@ -1,147 +1,142 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include #include +#include -using namespace snowhouse; -using namespace bandit; using namespace p; -go_bandit([]() +P_SPEC("Memory.MonoLinearArena", []() { - describe("Memory.MonoLinearArena", []() + It("Reserves a block on construction", []() { - it("Reserves a block on construction", [&]() - { - MonoLinearArena arena{1024}; - - AssertThat(arena.GetAvailableMemory(), Is().EqualTo(1024)); - arena.GetStats()->CollectStats(); - AssertThat(arena.GetStats()->used, Is().EqualTo(0)); - }); - - it("Can allocate outside the block", [&]() - { - MonoLinearArena arena{256}; - - AssertThat(arena.GetAvailableMemory(), Is().EqualTo(256)); - void* p = arena.Alloc(512); - AssertThat(arena.GetAvailableMemory(), Is().EqualTo(256)); - arena.Free(p, 512); - }); - - it("Can free from outside the block", [&]() - { - MonoLinearArena arena{256}; - - void* p = arena.Alloc(512); - AssertThat(arena.GetAvailableMemory(), Is().EqualTo(256)); - arena.Free(p, 512); - AssertThat(arena.GetAvailableMemory(), Is().EqualTo(256)); - }); - - it("Can free active block", [&]() - { - MonoLinearArena arena{1024}; - arena.Release(); - - TArray blocks; - arena.GetBlocks(blocks); - AssertThat(blocks.Size(), Equals(1)); - }); - - it("Can allocate", [&]() - { - MonoLinearArena arena{1024}; - void* p = arena.Alloc(sizeof(float)); - AssertThat(p, Is().Not().Null()); - arena.GetStats()->CollectStats(); - AssertThat(arena.GetStats()->used, Is().EqualTo(4)); - AssertThat(arena.GetAvailableMemory(), Is().EqualTo(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); - AssertThat(p::GetAlignmentPadding(p1, 8), Is().EqualTo(0)); - - // When padding is 0 (last ptr is aligned) - void* p2 = arena.Alloc(sizeof(float), 16); - AssertThat(p::GetAlignmentPadding(p2, 16), Is().EqualTo(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)); - AssertThat(p, Is().Not().Null()); - arena.GetStats()->CollectStats(); - AssertThat(arena.GetStats()->used, Is().EqualTo(4)); - // Buffer size will be as small as the type (4 bytes) - AssertThat(arena.GetAvailableMemory(), Is().EqualTo(1024)); - - arena.Free(p, sizeof(float)); - }); - - it("Can free block after Free", [&]() - { - MonoLinearArena arena{1024}; - void* p = arena.Alloc(256); - arena.GetStats()->CollectStats(); - AssertThat(arena.GetStats()->used, Is().EqualTo(256)); - arena.Free(p, 256); - arena.GetStats()->CollectStats(); - AssertThat(arena.GetStats()->used, Is().EqualTo(0)); - }); - - it("Allocates at correct addresses", [&]() - { - MonoLinearArena arena{1024}; - - TArray blocks; - arena.GetBlocks(blocks); - - void* p1 = arena.Alloc(sizeof(float)); - AssertThat(p1, Is().EqualTo(blocks[0].data)); - void* p2 = arena.Alloc(sizeof(float), alignof(float)); - AssertThat(p2, Is().EqualTo((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 - AssertThat(arena.GetStats()->used, Is().EqualTo(12)); - AssertThat(arena.GetAvailableMemory(), Is().EqualTo(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)); - - AssertThat(arena.GetStats()->used, Is().EqualTo(8)); - AssertThat(arena.GetAvailableMemory(), Is().EqualTo(16)); - });*/ + 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/PipeTest.spec.cpp b/Tests/PipeTest.spec.cpp new file mode 100644 index 00000000..69cc532f --- /dev/null +++ b/Tests/PipeTest.spec.cpp @@ -0,0 +1,81 @@ +// Copyright 2015-2026 Piperift. All Rights Reserved. + +#include +#include + +#include + +using namespace p; + +static int beforeEachCount = 0; +static int afterEachCount = 0; +static int topTestResult = 0; + + +P_SPEC("PipeTest", []() +{ + BeforeEach([]() + { + ++beforeEachCount; + }); + AfterEach([]() + { + ++afterEachCount; + }); + + Describe("Basics", []() + { + It("Registers and runs", []() + { + topTestResult = 42; + }); + XIt("Is skipped", []() + { + topTestResult = -1; + }); + }); + + Describe("Expect", []() + { + It("Relational", []() + { + int value = 4; + Expect(value).ToBeLess(5); + Expect(value).ToBeLessOrEqual(4); + Expect(value).ToBeGreater(3); + Expect(value).ToBeGreaterOrEqual(4); + }); + 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("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(); + }); + }); +}); diff --git a/Tests/PipeTime.spec.cpp b/Tests/PipeTime.spec.cpp new file mode 100644 index 00000000..c824f97f --- /dev/null +++ b/Tests/PipeTime.spec.cpp @@ -0,0 +1,28 @@ +// Copyright 2015-2026 Piperift. All Rights Reserved. + +#include +#include + + +using namespace p; + + +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/Reflect/MacroReflection.spec.cpp b/Tests/Reflect/MacroReflection.spec.cpp new file mode 100644 index 00000000..6b32e93a --- /dev/null +++ b/Tests/Reflect/MacroReflection.spec.cpp @@ -0,0 +1,39 @@ +// Copyright 2015-2026 Piperift. All Rights Reserved. + +#include +#include +#include + + +using namespace p; + + +struct TestStruct +{ + P_STRUCT(TestStruct) + + P_PROP(value0) + bool value0 = true; + + P_PROP(value1) + p::TArray value1 = true; +}; + + +P_SPEC("Reflection.Macros", []() +{ + It("Can get property names", []() + { + p::TypeId testStructType = p::RegisterTypeId(); + + Expect(p::HasTypeFlags(testStructType, p::TF_Struct)).ToEqual(true); + + 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"); + }); +}); diff --git a/Tests/Reflect/Object.spec.cpp b/Tests/Reflect/Object.spec.cpp new file mode 100644 index 00000000..1c089f89 --- /dev/null +++ b/Tests/Reflect/Object.spec.cpp @@ -0,0 +1,46 @@ +// Copyright 2015-2026 Piperift. All Rights Reserved. + +#include +#include + + +using namespace p; + + +class TestObject : public p::Object +{ +public: + using Super = p::Object; + P_CLASS(TestObject); + + bool bConstructed = false; + + TestObject() + { + bConstructed = true; + } +}; + + +P_SPEC("Reflection.Object", []() +{ + Describe("Pointers", []() + { + 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()); + }); + }); +}); diff --git a/Tests/Reflect/Traits.spec.cpp b/Tests/Reflect/Traits.spec.cpp new file mode 100644 index 00000000..7cad0b02 --- /dev/null +++ b/Tests/Reflect/Traits.spec.cpp @@ -0,0 +1,102 @@ +// Copyright 2015-2026 Piperift. All Rights Reserved. + +#include +#include +#include +#include + + +using namespace p; + + +struct TestNotSerializable +{}; + +struct TestSerializable +{ + void ReadProperties(p::Reader& r) {} + void WriteProperties(p::Writer& w) const {} +}; + +struct TestWithSuper : public TestNotSerializable +{ + using Super = TestNotSerializable; +}; + +struct TestExternal : public TestNotSerializable +{}; + +void Read(p::Reader& r, TestExternal& v) {} +void Write(p::Writer& r, const TestExternal& v) {} + +namespace p +{ + struct TestExternal2 : public TestNotSerializable + {}; + + void Read(Reader& r, TestExternal2& v) {} + void Write(Writer& r, const TestExternal2& v) {} +} // namespace p + + +P_SPEC("Reflection.Traits", []() +{ + Describe("Read/Write 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(); + }); + }); + + Describe("Read/Write external", []() + { + 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 in namespace", []() + { + It("Can check for read properties", []() + { + 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(); + }); +}); diff --git a/Tests/Reflection/MacroReflection.spec.cpp b/Tests/Reflection/MacroReflection.spec.cpp deleted file mode 100644 index 8722146c..00000000 --- a/Tests/Reflection/MacroReflection.spec.cpp +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2015-2026 Piperift. All Rights Reserved. - -#include -#include -#include - - -using namespace snowhouse; -using namespace bandit; - - -struct TestStruct -{ - P_STRUCT(TestStruct) - - P_PROP(value0) - bool value0 = true; - - P_PROP(value1) - p::TArray value1 = true; -}; - - -go_bandit([]() -{ - describe("Reflection.Macros", []() - { - it("Can get property names", [&]() - { - p::TypeId testStructType = p::RegisterTypeId(); - - AssertThat(p::HasTypeFlags(testStructType, p::TF_Struct), Equals(true)); - - auto properties = p::GetTypeProperties(testStructType); - AssertThat(properties.Size(), Equals(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")); - }); - }); -}); diff --git a/Tests/Reflection/Object.spec.cpp b/Tests/Reflection/Object.spec.cpp deleted file mode 100644 index a51e1376..00000000 --- a/Tests/Reflection/Object.spec.cpp +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2015-2026 Piperift. All Rights Reserved. - -#include -#include - - -using namespace snowhouse; -using namespace bandit; - - -class TestObject : public p::Object -{ -public: - using Super = p::Object; - P_CLASS(TestObject); - - bool bConstructed = false; - - TestObject() - { - bConstructed = true; - } -}; - - -go_bandit([]() -{ - describe("Reflection.Object", []() - { - describe("Pointers", []() - { - it("Can create object", [&]() - { - auto owner = p::MakeOwned(); - - AssertThat(owner.Get(), Is().Not().EqualTo(nullptr)); - AssertThat(owner->bConstructed, Equals(true)); - }); - - 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())); - }); - }); - }); -}); diff --git a/Tests/Reflection/Traits.spec.cpp b/Tests/Reflection/Traits.spec.cpp deleted file mode 100644 index 8e1b2eeb..00000000 --- a/Tests/Reflection/Traits.spec.cpp +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2015-2026 Piperift. All Rights Reserved. - -#include -#include -#include -#include - - -using namespace snowhouse; -using namespace bandit; - - -struct TestNotSerializable -{}; - -struct TestSerializable -{ - void ReadProperties(p::Reader& r) {} - void WriteProperties(p::Writer& w) const {} -}; - -struct TestWithSuper : public TestNotSerializable -{ - using Super = TestNotSerializable; -}; - -struct TestExternal : public TestNotSerializable -{}; - -void Read(p::Reader& r, TestExternal& v) {} -void Write(p::Writer& r, const TestExternal& v) {} - -namespace p -{ - struct TestExternal2 : public TestNotSerializable - {}; - - void Read(Reader& r, TestExternal2& v) {} - void Write(Writer& r, const TestExternal2& v) {} -} // namespace p - - -go_bandit([]() -{ - describe("Reflection.Traits", []() - { - describe("Read/Write 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()); - }); - - 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()); - }); - }); - - describe("Read/Write external", []() - { - it("Can check for read properties", [&]() - { - AssertThat(p::Readable, Is().False()); - AssertThat(p::Readable, Is().True()); - }); - - it("Can check for write properties", [&]() - { - AssertThat(p::Writable, Is().False()); - AssertThat(p::Writable, Is().True()); - }); - }); - - describe("Read/Write external in namespace", []() - { - it("Can check for read properties", [&]() - { - AssertThat(p::Readable, Is().True()); - }); - - it("Can check for write properties", [&]() - { - AssertThat(p::Writable, Is().True()); - }); - }); - - it("Can check super", []() - { - AssertThat(p::HasSuper(), Is().False()); - AssertThat(p::HasSuper(), Is().True()); - }); - - it("Can build type on Arrays", []() - { - AssertThat(p::CanBuildType>(), Is().True()); - AssertThat(p::HasExternalBuildType>(), Is().True()); - }); - }); -}); diff --git a/Tests/Reflection/TypeId.spec.cpp b/Tests/Reflection/TypeId.spec.cpp deleted file mode 100644 index 1a71f8c2..00000000 --- a/Tests/Reflection/TypeId.spec.cpp +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2015-2026 Piperift. All Rights Reserved. - -#include -#include - - -using namespace snowhouse; -using namespace bandit; -using namespace p; - -struct One -{}; - - -go_bandit([]() -{ - describe("Reflection.TypeId", []() - { - it("Ids can be valid and invalid", [&]() - { - static constexpr TypeId id = GetTypeId(); - AssertThat(id.IsValid(), Equals(true)); - - static constexpr TypeId noId{}; - AssertThat(noId.IsValid(), Equals(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); - - // Check that no id matches the other - for (u32 i = 0; i < numIds; ++i) - { - for (u32 e = i + 1; e < numIds; ++e) - { - AssertThat(ids[i], !Equals(ids[e])); - } - } - }); - }); -}); diff --git a/Tests/Reflection/TypeName.spec.cpp b/Tests/Reflection/TypeName.spec.cpp deleted file mode 100644 index 6b947ffb..00000000 --- a/Tests/Reflection/TypeName.spec.cpp +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2015-2026 Piperift. All Rights Reserved. - -#include -#include -#include -#include -#include -#include -#include - - -using namespace snowhouse; -using namespace bandit; -using namespace p; - - -struct AnStruct -{}; - -class AClass -{}; - -namespace Space -{ - struct Other - {}; -} // namespace Space - - -go_bandit([]() -{ - describe("Reflection.TypeName", []() - { - 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")); - }); - - it("Can get Native type names", [&]() - { - AssertThat(GetTypeName(), Equals("bool")); - AssertThat(GetTypeName(), Equals("float")); - AssertThat(GetTypeName(), Equals("double")); - }); - - it("Can get Class names", [&]() - { - AssertThat(GetTypeName(), Equals("AClass")); - }); - - it("Can get Struct names", [&]() - { - AssertThat(GetTypeName(), Equals("AnStruct")); - }); - - it("Can get names with namespaces", [&]() - { - AssertThat(GetTypeName(), Equals("Space::Other")); - }); - - describe("Containers", []() - { - it("Can get TArray names", [&]() - { - AssertThat(GetTypeName>(), Equals("TArray")); - AssertThat(GetFullTypeName>(), Equals("TArray")); - AssertThat(GetFullTypeName>(false), Equals("TArray")); - }); - - it("Can get TMap names", [&]() - { - auto name = GetTypeName>(); - AssertThat(name, Equals("TMap")); - - auto fullName = GetFullTypeName>(); - AssertThat(fullName, Equals("TMap")); - - - auto namespaceName = GetFullTypeName>(); - AssertThat(namespaceName, Equals("TMap")); - auto noNamespaceName = GetFullTypeName>(false); - AssertThat(noNamespaceName, Equals("TMap")); - }); - }); - }); -}); diff --git a/Tests/Serialization/Binary.spec.cpp b/Tests/Serialization/Binary.spec.cpp deleted file mode 100644 index 0ab52a0d..00000000 --- a/Tests/Serialization/Binary.spec.cpp +++ /dev/null @@ -1,420 +0,0 @@ -// Copyright 2015-2026 Piperift. All Rights Reserved. - -#include -#include - - -using namespace snowhouse; -using namespace bandit; -using namespace p; - - -go_bandit([]() -{ - describe("Serialization.Binary", []() - { - describe("Reader", [&]() - { - it("Can create a reader", [&]() - { - BinaryFormatReader reader{TArray{}}; - AssertThat(reader.IsValid(), Equals(false)); - - BinaryFormatReader reader2{TArray{255}}; - AssertThat(reader2.IsValid(), Equals(true)); - }); - - it("Can read from object value", [&]() - { - TArray data{255}; - BinaryFormatReader reader{data}; - Reader ct = reader; - ct.BeginObject(); - u8 value = 0; - ct.Next(value); - AssertThat(value, Equals(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); - AssertThat(size, Equals(1)); - u8 value = 0; - ct.Next(value); - AssertThat(value, Equals(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")) - { - static const StringView expected[]{"Miguel", "Juan"}; - u32 size; - ct.BeginArray(size); - for (u32 i = 0; i < size; ++i) - { - StringView name; - ct.Next(name); - AssertThat(name, Equals(expected[i])); - } - ct.Leave(); - } - }); - - 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); - AssertThat(value, Equals(true)); - ct.Next("b", value); - AssertThat(value, Equals(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); - AssertThat(value, Equals(0)); - ct.Next("b", value); - AssertThat(value, Equals(127)); - ct.Next("b", value); - AssertThat(value, Equals(-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); - AssertThat(value, Equals(0)); - ct.Next("b", value); - AssertThat(value, Equals(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); - AssertThat(value, Equals(0)); - ct.Next("b", value); - AssertThat(value, Equals(Limits::Lowest())); - ct.Next("c", value); - AssertThat(value, Equals(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); - AssertThat(value, Equals(0)); - ct.Next("b", value); - AssertThat(value, Equals(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); - AssertThat(value, Equals(0)); - ct.Next("b", value); - AssertThat(value, Equals(Limits::Lowest())); - ct.Next("c", value); - AssertThat(value, Equals(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); - AssertThat(value, Equals(0)); - ct.Next("b", value); - AssertThat(value, Equals(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); - AssertThat(value, Equals(0)); - ct.Next("b", value); - AssertThat(value, Equals(Limits::Lowest())); - ct.Next("c", value); - AssertThat(value, Equals(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); - AssertThat(value, Equals(0)); - ct.Next("b", value); - AssertThat(value, Equals(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); - AssertThat(value, Equals(-1.4f)); - ct.Next("b", value); - AssertThat(value, Equals(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); - AssertThat(value, Equals(-1.4)); - ct.Next("b", value); - AssertThat(value, Equals(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); - AssertThat(string, Equals("yes")); - }); - }); - }); - - describe("Writer", [&]() - { - it("Can create a writer", [&]() - { - BinaryFormatWriter writer{}; - AssertThat(writer.IsValid(), Equals(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'}; - AssertThat(writer.GetData(), Equals(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}; - AssertThat(writer.GetData(), Equals(TView{expected})); - }); - - describe("Types", []() - { - it("Can write bool values", [&]() - { - BinaryFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("a", true); - ct.Next("b", false); - TArray expected{1, 0}; - AssertThat(writer.GetData(), Equals(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}; - AssertThat(writer.GetData(), Equals(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}; - AssertThat(writer.GetData(), Equals(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}; - AssertThat(writer.GetData(), Equals(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}; - AssertThat(writer.GetData(), Equals(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}; - AssertThat(writer.GetData(), Equals(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}; - AssertThat(writer.GetData(), Equals(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}; - AssertThat(writer.GetData(), Equals(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}; - AssertThat(writer.GetData(), Equals(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}; - AssertThat(writer.GetData(), Equals(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}; - AssertThat(writer.GetData(), Equals(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'}; - AssertThat(writer.GetData(), Equals(TView(expected))); - }); - }); - }); - }); -}); diff --git a/Tests/Serialization/Json.spec.cpp b/Tests/Serialization/Json.spec.cpp deleted file mode 100644 index 372fedaf..00000000 --- a/Tests/Serialization/Json.spec.cpp +++ /dev/null @@ -1,435 +0,0 @@ -// Copyright 2015-2026 Piperift. All Rights Reserved. - -#include -#include - - -using namespace snowhouse; -using namespace bandit; -using namespace p; - - -go_bandit([]() -{ - describe("Serialization.Json", []() - { - describe("Reader", [&]() - { - it("Can create a reader", [&]() - { - JsonFormatReader reader{"{}"}; - AssertThat(reader.IsValid(), Is().True()); - }); - - it("Can read from object value", [&]() - { - String data{"{\"name\": \"Miguel\"}"}; - JsonFormatReader reader{data}; - - Reader& ct = reader; - ct.BeginObject(); - String name; - ct.Next("name", name); - - AssertThat(name.data(), Equals("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); - AssertThat(name.data(), Equals("Miguel")); - - ct.Next(name); - AssertThat(name.data(), Equals("Juan")); - - ct.Leave(); - } - }); - - it("Can iterate arrays", [&]() - { - String data{"{\"players\": [\"Miguel\", \"Juan\"]}"}; - JsonFormatReader 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); - AssertThat(name, Equals(expected[i])); - } - ct.Leave(); - } - }); - - it("Can check types", [&]() - { - String data{"{\"players\": [\"Miguel\", \"Juan\"]}"}; - JsonFormatReader reader{data}; - - Reader& ct = reader; - AssertThat(reader.IsObject(), Equals(true)); - ct.BeginObject(); - if (ct.EnterNext("players")) - { - AssertThat(reader.IsArray(), Equals(true)); - ct.Leave(); - } - }); - - it("Can find multiple keys", [&]() - { - String data{"{\"one\": \"Miguel\", \"other\": \"Juan\"}"}; - JsonFormatReader reader{data}; - - Reader& ct = reader; - AssertThat(reader.IsObject(), Equals(true)); - ct.BeginObject(); - StringView name; - ct.Next("one", name); - AssertThat(name, Equals("Miguel")); - - ct.Next("other", name); - AssertThat(name, Equals("Juan")); - }); - - it("Can find multiple unordered keys", [&]() - { - String data{"{\"one\": \"Miguel\", \"other\": \"Juan\"}"}; - JsonFormatReader reader{data}; - - Reader& ct = reader; - AssertThat(reader.IsObject(), Equals(true)); - ct.BeginObject(); - StringView name; - ct.Next("other", name); - AssertThat(name, Equals("Juan")); - - ct.Next("one", name); - AssertThat(name, Equals("Miguel")); - }); - - describe("Types", []() - { - 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)); - - JsonFormatReader reader2{"{\"alive\": false}"}; - ct = reader2; - ct.BeginObject(); - bool value2 = true; - ct.Next("alive", value2); - AssertThat(value2, Equals(false)); - }); - - 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)); - - JsonFormatReader reader2{"{\"alive\": -1.344}"}; - ct = reader2; - ct.BeginObject(); - i8 value2 = 0; - ct.Next("alive", value2); - AssertThat(value2, Equals(-1)); - }); - - 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)); - - JsonFormatReader reader2{"{\"alive\": 1.344}"}; - ct = reader2; - ct.BeginObject(); - u8 value2 = 0; - ct.Next("alive", value2); - AssertThat(value2, Equals(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); - AssertThat(value, Equals(Limits::Max())); - ct.Next("b", value); - AssertThat(value, Equals(Limits::Lowest())); - ct.Next("c", value); - AssertThat(value, Equals(Limits::Max())); - ct.Next("d", value); - AssertThat(value, Equals(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); - AssertThat(value, Equals(Limits::Max())); - ct.Next("b", value); - AssertThat(value, Equals(Limits::Lowest())); - ct.Next("c", value); - AssertThat(value, Equals(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); - AssertThat(value, Equals(Limits::Max())); - ct.Next("b", value); - AssertThat(value, Equals(Limits::Lowest())); - ct.Next("c", value); - AssertThat(value, Equals(Limits::Max())); - ct.Next("d", value); - AssertThat(value, Equals(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); - AssertThat(value, Equals(Limits::Max())); - ct.Next("b", value); - AssertThat(value, Equals(Limits::Lowest())); - ct.Next("c", value); - AssertThat(value, Equals(0)); - }); - - 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)); - - JsonFormatReader reader2{"{\"alive\": 4}"}; - ct = reader2; - ct.BeginObject(); - float value2 = 0.f; - ct.Next("alive", value2); - AssertThat(value2, Equals(4.f)); - }); - - it("Can read StringView values", [&]() - { - JsonFormatReader reader{"{\"alive\": \"yes\"}"}; - Reader& ct = reader; - ct.BeginObject(); - StringView value; - ct.Next("alive", value); - AssertThat(value, Equals("yes")); - }); - }); - }); - - describe("Writer", [&]() - { - it("Can create a writer", [&]() - { - JsonFormatWriter writer{}; - AssertThat(writer.IsValid(), Equals(true)); - }); - - 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\"}")); - }); - - 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) - { - ct.Next(expected[i]); - } - ct.Leave(); - } - AssertThat(writer.ToString(false), Equals("{\"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"}); - AssertThat( - writer.ToString(false), Equals("{\"one\":\"Miguel\",\"other\":\"Juan\"}")); - }); - - describe("Types", []() - { - it("Can write bool values", [&]() - { - JsonFormatWriter writer{}; - Writer& ct = writer; - ct.BeginObject(); - ct.Next("alive", true); - AssertThat(writer.ToString(false), Equals("{\"alive\":true}")); - - JsonFormatWriter writer2{}; - ct = writer2; - ct.BeginObject(); - ct.Next("alive", false); - AssertThat(writer2.ToString(false), Equals("{\"alive\":false}")); - }); - - it("Can write i8 values", [&]() - { - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("alive", i8(-3)); - AssertThat(writer.ToString(false), Equals("{\"alive\":-3}")); - }); - - it("Can write u8 values", [&]() - { - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("alive", u8(3)); - AssertThat(writer.ToString(false), Equals("{\"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()); - AssertThat( - writer.ToString(false), Equals("{\"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()); - AssertThat(writer.ToString(false), Equals("{\"a\":3000,\"b\":65535,\"c\":0}")); - }); - - it("Can write u32 values", [&]() - { - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("alive", u32(35533)); - AssertThat(writer.ToString(false), Equals("{\"alive\":35533}")); - }); - - 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}")); - - JsonFormatWriter writer2{}; - ct = writer2; - ct.BeginObject(); - ct.Next("alive", i32(-35533)); - AssertThat(writer2.ToString(false), Equals("{\"alive\":-35533}")); - }); - - 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)); - - JsonFormatWriter writer2{}; - ct = writer2; - ct.BeginObject(); - ct.Next("alive", 4.f); - AssertThat(writer2.ToString(false), Equals("{\"alive\":4.0}")); - }); - - it("Can write StringView values", [&]() - { - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("alive", StringView{"yes"}); - AssertThat(writer.ToString(false), Equals("{\"alive\":\"yes\"}")); - }); - }); - }); - }); -}); diff --git a/Tests/Serialization/Serialization.spec.cpp b/Tests/Serialization/Serialization.spec.cpp deleted file mode 100644 index b42b5337..00000000 --- a/Tests/Serialization/Serialization.spec.cpp +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright 2015-2026 Piperift. All Rights Reserved. - -#include -#include - - -using namespace snowhouse; -using namespace bandit; -using namespace p; - - -struct SerTypeA -{ - bool value = false; -}; -void Read(Reader& ct, SerTypeA& val) -{ - ct.BeginObject(); - ct.Next("value", val.value); -} -void Write(Writer& ct, const SerTypeA& val) -{ - ct.BeginObject(); - ct.Next("value", val.value); -} - - -struct SerTypeB -{ - bool value = false; -}; -template<> -struct p::TFlags : public p::DefaultTFlags -{ - enum - { - HasSingleSerialize = true - }; -}; - -void Serialize(ReadWriter& ct, SerTypeB& val) -{ - ct.BeginObject(); - ct.Next("value", val.value); -} - - -struct SerTypeC -{ - bool value = false; - - void Read(Reader& ct) - { - ct.BeginObject(); - ct.Next("value", value); - } - void Write(Writer& ct) const - { - ct.BeginObject(); - ct.Next("value", value); - } -}; -template<> -struct p::TFlags : public p::DefaultTFlags -{ - enum - { - HasMemberSerialize = true - }; -}; - - -struct SerTypeD -{ - bool value = false; - - void Serialize(ReadWriter& ct) - { - ct.BeginObject(); - ct.Next("value", value); - } -}; -template<> -struct p::TFlags : public p::DefaultTFlags -{ - enum - { - HasMemberSerialize = true, - HasSingleSerialize = true - }; -}; - - -go_bandit([]() -{ - describe("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); - AssertThat(val.value, Equals(true)); - }); - - it("Can use custom Write()", [&]() - { - SerTypeA val{}; - val.value = true; - - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("type", val); - AssertThat(writer.ToString(false), Equals("{\"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); - AssertThat(val.value, Equals(true)); - }); - - it("Can use Serialize() instead of Write()", [&]() - { - SerTypeB val{}; - val.value = true; - - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("type", val); - AssertThat(writer.ToString(false), Equals("{\"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); - AssertThat(val.value, Equals(true)); - }); - - it("Can use custom Write()", [&]() - { - SerTypeC val{}; - val.value = true; - - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("type", val); - AssertThat(writer.ToString(false), Equals("{\"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); - AssertThat(val.value, Equals(true)); - }); - - it("Can use Serialize() instead of Write()", [&]() - { - SerTypeD val{}; - val.value = true; - - JsonFormatWriter writer{}; - Writer ct = writer; - ct.BeginObject(); - ct.Next("type", val); - AssertThat(writer.ToString(false), Equals("{\"type\":{\"value\":true}}")); - }); - }); - }); -}); diff --git a/Tests/Serialize/Binary.spec.cpp b/Tests/Serialize/Binary.spec.cpp new file mode 100644 index 00000000..f26ed08b --- /dev/null +++ b/Tests/Serialize/Binary.spec.cpp @@ -0,0 +1,413 @@ +// Copyright 2015-2026 Piperift. All Rights Reserved. + +#include +#include + + +using namespace p; + + +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", []() + { + 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")) + { + 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(); + } + }); + + 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); + }); + + 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", []() + { + 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{}; + 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/Serialize/Json.spec.cpp b/Tests/Serialize/Json.spec.cpp new file mode 100644 index 00000000..d73d6b9c --- /dev/null +++ b/Tests/Serialize/Json.spec.cpp @@ -0,0 +1,428 @@ +// Copyright 2015-2026 Piperift. All Rights Reserved. + +#include +#include + + +using namespace p; + + +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", []() + { + String data{"{\"players\": [\"Miguel\", \"Juan\"]}"}; + JsonFormatReader 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(); + } + }); + + 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(); + 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"); + }); + }); + }); + + 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) + { + 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("Types", []() + { + 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/Serialize/Serialization.spec.cpp b/Tests/Serialize/Serialization.spec.cpp new file mode 100644 index 00000000..37a7a082 --- /dev/null +++ b/Tests/Serialize/Serialization.spec.cpp @@ -0,0 +1,191 @@ +// Copyright 2015-2026 Piperift. All Rights Reserved. + +#include +#include + + +using namespace p; + + +struct SerTypeA +{ + bool value = false; +}; +void Read(Reader& ct, SerTypeA& val) +{ + ct.BeginObject(); + ct.Next("value", val.value); +} +void Write(Writer& ct, const SerTypeA& val) +{ + ct.BeginObject(); + ct.Next("value", val.value); +} + + +struct SerTypeB +{ + bool value = false; +}; +template<> +struct p::TFlags : public p::DefaultTFlags +{ + enum + { + HasSingleSerialize = true + }; +}; + +void Serialize(ReadWriter& ct, SerTypeB& val) +{ + ct.BeginObject(); + ct.Next("value", val.value); +} + + +struct SerTypeC +{ + bool value = false; + + void Read(Reader& ct) + { + ct.BeginObject(); + ct.Next("value", value); + } + void Write(Writer& ct) const + { + ct.BeginObject(); + ct.Next("value", value); + } +}; +template<> +struct p::TFlags : public p::DefaultTFlags +{ + enum + { + HasMemberSerialize = true + }; +}; + + +struct SerTypeD +{ + bool value = false; + + void Serialize(ReadWriter& ct) + { + ct.BeginObject(); + ct.Next("value", value); + } +}; +template<> +struct p::TFlags : public p::DefaultTFlags +{ + enum + { + HasMemberSerialize = true, + HasSingleSerialize = true + }; +}; + + +P_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()", []() + { + 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/Time.spec.cpp b/Tests/Time.spec.cpp deleted file mode 100644 index a87f68b6..00000000 --- a/Tests/Time.spec.cpp +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2015-2026 Piperift. All Rights Reserved. - -#include -#include - - -using namespace snowhouse; -using namespace bandit; -using namespace p; - - -go_bandit([]() -{ - describe("Time.DateTime", []() - { - it("Can get day of year", [&]() - { - DateTime time1{2024, 1, 1}; - AssertThat(time1.GetDayOfYear(), Equals(1)); - DateTime time11{2024, 1, 30}; - AssertThat(time11.GetDayOfYear(), Equals(30)); - DateTime time12{2024, 1, 31}; - AssertThat(time12.GetDayOfYear(), Equals(31)); - - DateTime time2{2024, 2, 1}; - AssertThat(time2.GetDayOfYear(), Equals(32)); - DateTime time3{2024, 3, 1}; - AssertThat(time3.GetDayOfYear(), Equals(60)); - DateTime time4{2024, 12, 31}; - AssertThat(time4.GetDayOfYear(), Equals(365)); - }); - }); -}); 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/Type/TypeId.spec.cpp b/Tests/Type/TypeId.spec.cpp new file mode 100644 index 00000000..8dc27003 --- /dev/null +++ b/Tests/Type/TypeId.spec.cpp @@ -0,0 +1,39 @@ +// Copyright 2015-2026 Piperift. All Rights Reserved. + +#include +#include + + +using namespace p; + +struct One +{}; + + +P_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("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) + { + for (u32 e = i + 1; e < numIds; ++e) + { + Expect(ids[i]).ToNotEqual(ids[e]); + } + } + }); +}); diff --git a/Tests/Type/TypeName.spec.cpp b/Tests/Type/TypeName.spec.cpp new file mode 100644 index 00000000..c58178fa --- /dev/null +++ b/Tests/Type/TypeName.spec.cpp @@ -0,0 +1,91 @@ +// Copyright 2015-2026 Piperift. All Rights Reserved. + +#include +#include +#include +#include +#include +#include +#include + + +using namespace p; + + +struct AnStruct +{}; + +class AClass +{}; + +namespace Space +{ + struct Other + {}; +} // namespace Space + + +P_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"); + }); + + 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/main.cpp b/Tests/main.cpp index 12f6f87d..f9916c8d 100644 --- a/Tests/main.cpp +++ b/Tests/main.cpp @@ -1,11 +1,14 @@ // Copyright 2015-2026 Piperift. All Rights Reserved. -#include -// Override as first include +// 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 + +#include // namespace backward @@ -20,7 +23,9 @@ int main(int argc, char* argv[]) // 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); + + // Specs auto-register at file scope via static init; just run them. + int result = p::RunTests(argc, argv); p::Shutdown(); return result; }