Reorganise the tree into business vs frontend as separate directories:
src/core/{domain,imports,app} (was system/, imports/, app/)
src/frontends/tui/ (was tui/ + main.cpp)
tests/tui/ (the FTXUI-coupled helper test)
All cross-dir #include paths rewritten; same-dir includes untouched.
CMake: essim_core is the frontend-agnostic business library — links libzip,
pugixml and bsdl, NO GUI toolkit. Each frontend is a self-contained
src/frontends/<name>/ (own CMakeLists, toolkit, main.cpp) that links
essim_core, selected with -DESSIM_FRONTEND=<name> (default tui; 'none' = core +
tests only, no toolkit fetched). FTXUI moved into the tui frontend. Tests are
split: essim_tests links essim_core (no FTXUI), essim_tui_tests links essim_tui.
Verified: default tui build green (ctest 2/2); ESSIM_FRONTEND=none builds the
core + tests with FTXUI never fetched and no `essim` binary.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
103 lines
3.5 KiB
C++
103 lines
3.5 KiB
C++
#include <doctest/doctest.h>
|
|
|
|
#include "core/domain/parts.hpp"
|
|
#include "core/domain/pin_name.hpp"
|
|
#include "core/domain/pins.hpp"
|
|
#include "core/domain/transform.hpp"
|
|
|
|
#include <memory>
|
|
|
|
TEST_CASE("canonical_pin_name: zero-pads pure digit suffix to 3") {
|
|
CHECK(canonical_pin_name("A1") == "A001");
|
|
CHECK(canonical_pin_name("A001") == "A001");
|
|
CHECK(canonical_pin_name("AB12") == "AB012");
|
|
CHECK(canonical_pin_name("12") == "012");
|
|
CHECK(canonical_pin_name("J1") == "J001");
|
|
CHECK(canonical_pin_name("J999") == "J999");
|
|
CHECK(canonical_pin_name("J1000") == "J1000");
|
|
}
|
|
|
|
TEST_CASE("canonical_pin_name: leaves names without digit suffix unchanged") {
|
|
CHECK(canonical_pin_name("") == "");
|
|
CHECK(canonical_pin_name("VCC") == "VCC");
|
|
CHECK(canonical_pin_name("GND") == "GND");
|
|
}
|
|
|
|
TEST_CASE("canonical_pin_name: leaves mixed suffixes unchanged") {
|
|
CHECK(canonical_pin_name("A1B") == "A1B");
|
|
CHECK(canonical_pin_name("P3.3V") == "P3.3V");
|
|
CHECK(canonical_pin_name("D+") == "D+");
|
|
}
|
|
|
|
TEST_CASE("IdentityTransform pairs A1 ↔ A001 and reports compatible") {
|
|
auto a = std::make_unique<Part>("U_a");
|
|
auto b = std::make_unique<Part>("U_b");
|
|
Pin *a1 = new Pin("A1");
|
|
Pin *a2 = new Pin("A2");
|
|
Pin *b1 = new Pin("A001");
|
|
Pin *b2 = new Pin("A002");
|
|
a->add(a1); a->add(a2);
|
|
b->add(b1); b->add(b2);
|
|
|
|
CHECK(CheckIdentityCompatible(a.get(), b.get()) == "");
|
|
|
|
IdentityTransform t;
|
|
auto wires = t.apply(a.get(), b.get());
|
|
CHECK(wires.size() == 2);
|
|
// Build a quick set for order-independent verification.
|
|
bool saw_a1 = false, saw_a2 = false;
|
|
for (auto &w : wires) {
|
|
if (w.first == a1) { CHECK(w.second == b1); saw_a1 = true; }
|
|
if (w.first == a2) { CHECK(w.second == b2); saw_a2 = true; }
|
|
}
|
|
CHECK(saw_a1);
|
|
CHECK(saw_a2);
|
|
}
|
|
|
|
TEST_CASE("CheckIdentityCompatible accepts subset, surfaces orphans as info") {
|
|
auto a = std::make_unique<Part>("U_a");
|
|
auto b = std::make_unique<Part>("U_b");
|
|
a->add(new Pin("A1"));
|
|
a->add(new Pin("A2"));
|
|
b->add(new Pin("A001")); // canonical match for A1
|
|
// b is a strict subset of a — accept, info mentions A2 (orphan on a).
|
|
std::string info;
|
|
std::string err = CheckIdentityCompatible(a.get(), b.get(), &info);
|
|
CHECK(err.empty());
|
|
CHECK(!info.empty());
|
|
CHECK(info.find("A2") != std::string::npos);
|
|
CHECK(info.find("U_a") != std::string::npos);
|
|
}
|
|
|
|
TEST_CASE("FillIdentityNCs materialises the missing side") {
|
|
auto a = std::make_unique<Part>("U_a");
|
|
auto b = std::make_unique<Part>("U_b");
|
|
a->add(new Pin("A1"));
|
|
a->add(new Pin("A2"));
|
|
a->add(new Pin("A3"));
|
|
b->add(new Pin("A001"));
|
|
// b is a strict subset; A2/A3 missing → 2 created on b.
|
|
int added = FillIdentityNCs(a.get(), b.get());
|
|
CHECK(added == 2);
|
|
CHECK(b->size() == 3);
|
|
CHECK(b->exists("A2"));
|
|
CHECK(b->exists("A3"));
|
|
// Idempotent.
|
|
CHECK(FillIdentityNCs(a.get(), b.get()) == 0);
|
|
// Materialised pins are NC.
|
|
CHECK(b->get("A2")->signal() == nullptr);
|
|
}
|
|
|
|
TEST_CASE("CheckIdentityCompatible refuses bidirectional mismatch") {
|
|
auto a = std::make_unique<Part>("U_a");
|
|
auto b = std::make_unique<Part>("U_b");
|
|
a->add(new Pin("A1"));
|
|
a->add(new Pin("X9")); // only on a
|
|
b->add(new Pin("A001")); // canonical match
|
|
b->add(new Pin("Y7")); // only on b
|
|
std::string err = CheckIdentityCompatible(a.get(), b.get());
|
|
CHECK(!err.empty());
|
|
CHECK(err.find("X9") != std::string::npos);
|
|
CHECK(err.find("Y7") != std::string::npos);
|
|
}
|