Files
essim/tests/test_nc_origin.cpp
François 63ca17d048 build: split core/ from frontends/; prepare for multiple GUI/TUI targets
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>
2026-06-03 19:33:06 +02:00

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);
}