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>
126 lines
4.2 KiB
C++
126 lines
4.2 KiB
C++
#include <doctest/doctest.h>
|
|
|
|
#include "core/domain/modules.hpp"
|
|
#include "core/domain/parts.hpp"
|
|
#include "core/domain/persist.hpp"
|
|
#include "core/domain/pins.hpp"
|
|
#include "core/domain/signals.hpp"
|
|
#include "core/domain/system.hpp"
|
|
|
|
#include <filesystem>
|
|
#include <memory>
|
|
#include <string>
|
|
|
|
TEST_CASE("nc_origin_tag round-trips with from_tag for tagged variants") {
|
|
NcOrigin o;
|
|
REQUIRE(nc_origin_from_tag("U", o));
|
|
CHECK(o == NcOrigin::ImportedUnconnected);
|
|
REQUIRE(nc_origin_from_tag("D", o));
|
|
CHECK(o == NcOrigin::DroppedSingleton);
|
|
|
|
CHECK(std::string(nc_origin_tag(NcOrigin::ImportedUnconnected)) == "U");
|
|
CHECK(std::string(nc_origin_tag(NcOrigin::DroppedSingleton)) == "D");
|
|
CHECK(std::string(nc_origin_tag(NcOrigin::None)) == "");
|
|
}
|
|
|
|
TEST_CASE("nc_origin_from_tag rejects unknown / empty tags") {
|
|
NcOrigin o = NcOrigin::None;
|
|
CHECK(!nc_origin_from_tag("", o));
|
|
CHECK(!nc_origin_from_tag("X", o));
|
|
CHECK(!nc_origin_from_tag("Unknown", o));
|
|
}
|
|
|
|
TEST_CASE("drop_singleton_signals detaches size-1 signals and tags pins") {
|
|
Module mod("M");
|
|
Part *prt = new Part("U1");
|
|
mod.add(prt);
|
|
|
|
auto add_pin = [&](const std::string &pin_name, const std::string &sig) {
|
|
Pin *p = new Pin(pin_name);
|
|
prt->add(p);
|
|
Signal *s = mod.signals->merge(sig);
|
|
s->add(p);
|
|
p->connect(s);
|
|
return p;
|
|
};
|
|
|
|
// Two-pin signal: should survive.
|
|
Pin *pa = add_pin("A1", "BUS_X");
|
|
Pin *pb = add_pin("A2", "BUS_X");
|
|
// Singleton: should be dropped, pin tagged DroppedSingleton.
|
|
Pin *po = add_pin("A3", "ORPHAN");
|
|
|
|
REQUIRE(mod.signals->size() == 2);
|
|
int dropped = drop_singleton_signals(mod.signals);
|
|
CHECK(dropped == 1);
|
|
CHECK(mod.signals->size() == 1); // BUS_X kept
|
|
CHECK(mod.signals->exists("BUS_X"));
|
|
CHECK(!mod.signals->exists("ORPHAN"));
|
|
|
|
CHECK(pa->signal() != nullptr); // unchanged
|
|
CHECK(pb->signal() != nullptr);
|
|
CHECK(po->signal() == nullptr); // detached
|
|
CHECK(po->nc_origin == NcOrigin::DroppedSingleton);
|
|
CHECK(pa->nc_origin == NcOrigin::None);
|
|
}
|
|
|
|
TEST_CASE("drop_singleton_signals is a no-op on a clean module") {
|
|
Module mod("M");
|
|
Part *prt = new Part("U1");
|
|
mod.add(prt);
|
|
auto *p1 = new Pin("A1"); prt->add(p1);
|
|
auto *p2 = new Pin("A2"); prt->add(p2);
|
|
Signal *s = mod.signals->merge("MULTIPIN");
|
|
s->add(p1); p1->connect(s);
|
|
s->add(p2); p2->connect(s);
|
|
|
|
CHECK(drop_singleton_signals(mod.signals) == 0);
|
|
CHECK(mod.signals->size() == 1);
|
|
}
|
|
|
|
TEST_CASE("persist round-trip preserves nc_origin tags") {
|
|
auto sys = std::make_unique<System>();
|
|
Module *mod = sys->modules()->merge("M");
|
|
Part *prt = new Part("U1");
|
|
mod->add(prt);
|
|
|
|
auto *connected = new Pin("A1");
|
|
prt->add(connected);
|
|
Signal *s = mod->signals->merge("SIG");
|
|
s->add(connected); connected->connect(s);
|
|
|
|
auto *imported_nc = new Pin("A2");
|
|
imported_nc->nc_origin = NcOrigin::ImportedUnconnected;
|
|
prt->add(imported_nc);
|
|
|
|
auto *dropped = new Pin("A3");
|
|
dropped->nc_origin = NcOrigin::DroppedSingleton;
|
|
prt->add(dropped);
|
|
|
|
// A pin with no signal and no origin (e.g. an old snapshot or a
|
|
// FillIdentityNCs-style materialisation): tag stays None on restore.
|
|
auto *bare_nc = new Pin("A4");
|
|
prt->add(bare_nc);
|
|
|
|
std::string path =
|
|
(std::filesystem::temp_directory_path() / "essim_nc_origin.txt").string();
|
|
std::string err;
|
|
REQUIRE(save_system(sys.get(), path, err));
|
|
|
|
std::unique_ptr<System> restored(restore_system(path, err));
|
|
REQUIRE(restored);
|
|
std::filesystem::remove(path);
|
|
|
|
Part *rp = restored->modules()->get("M")->get("U1");
|
|
CHECK(rp->get("A1")->nc_origin == NcOrigin::None);
|
|
CHECK(rp->get("A2")->nc_origin == NcOrigin::ImportedUnconnected);
|
|
CHECK(rp->get("A3")->nc_origin == NcOrigin::DroppedSingleton);
|
|
CHECK(rp->get("A4")->nc_origin == NcOrigin::None);
|
|
}
|
|
|
|
TEST_CASE("next_signal_type cycles Power → Gnd → Other → Power") {
|
|
CHECK(next_signal_type(SignalType::Power) == SignalType::GndShield);
|
|
CHECK(next_signal_type(SignalType::GndShield) == SignalType::Other);
|
|
CHECK(next_signal_type(SignalType::Other) == SignalType::Power);
|
|
}
|