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>
64 lines
2.2 KiB
C++
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);
|
|
}
|