Files
essim/tests/test_load.cpp
François 4ef110ab70 Harden ImportBase: open read-only and fail fast on an unreadable file.
ImportBase opened the input with a default std::fstream (in|out), which had
two consequences: a missing file silently produced an empty module (no error),
and a present-but-read-only file failed to open and also loaded as empty. Open
the stream read-only (std::ios::in) instead, and expose is_open().

System::Load now builds the importer first, checks is_open(), and throws
"cannot open file: <path>" before creating the module — so a failed load
surfaces as `load failed: …` and leaves no empty module behind. A read-only
but present file now loads correctly.

Flip the test that pinned the old silent-empty behaviour to assert the clean
failure (error + no module created).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:24:11 +02:00

64 lines
2.2 KiB
C++

#include <doctest/doctest.h>
#include "core/app/load.hpp"
#include "core/domain/modules.hpp"
#include "core/domain/system.hpp"
#include <cstdio>
#include <fstream>
#include <string>
// app::load_module is pure core: import a module, drop singleton signals, infer
// signal types, return counts or an error — no Print/dialog/FTXUI. The parse
// helper import_type_from_name is likewise UI-free.
TEST_CASE("import_type_from_name maps names case-insensitively") {
ImportType t;
CHECK(app::import_type_from_name("mentor", t));
CHECK(t == ImportType::IMPORT_MENTOR);
CHECK(app::import_type_from_name("ALTIUM", t));
CHECK(t == ImportType::IMPORT_ALTIUM);
CHECK(app::import_type_from_name("Ods", t));
CHECK(t == ImportType::IMPORT_ODS);
CHECK_FALSE(app::import_type_from_name("kicad", t));
CHECK_FALSE(app::import_type_from_name("", t));
}
TEST_CASE("load_module imports, drops singletons and reports counts") {
// Minimal Mentor netlist: two parts; NETA/NETB span both parts (2 pins
// each, kept), LONELY sits on one pin only (dropped as a singleton).
const char *path = "test_load_in.net";
{
std::ofstream f(path);
f << "COMP: 'C1' 'J1'\n"
" Explicit Pin: '1' 'x' 'NETA'\n"
" Explicit Pin: '2' 'x' 'NETB'\n"
" Explicit Pin: '3' 'x' 'LONELY'\n"
"COMP: 'C2' 'J2'\n"
" Explicit Pin: '1' 'x' 'NETA'\n"
" Explicit Pin: '2' 'x' 'NETB'\n";
}
System sys;
app::LoadResult r = app::load_module(&sys, "M", path, ImportType::IMPORT_MENTOR);
CHECK(r.ok);
CHECK(r.error.empty());
CHECK(r.parts == 2);
CHECK(r.signals == 2); // NETA, NETB — LONELY dropped
CHECK(r.dropped == 1); // LONELY
std::remove(path);
}
TEST_CASE("load_module fails cleanly on a missing file") {
// ImportBase opens read-only and System::Load checks is_open(), so a missing
// file is a clean error — and no empty module is left in the system.
System sys;
app::LoadResult r = app::load_module(
&sys, "M", "/nonexistent-dir-xyz/nope.net", ImportType::IMPORT_MENTOR);
CHECK_FALSE(r.ok);
CHECK(r.error.find("cannot open") != std::string::npos);
CHECK(sys.modules()->size() == 0);
}