Tui::Source kept the script position in single member fields (loading_lines/idx/...), so a sourced line that was itself `source inner` overwrote them: when the inner file finished, the outer script's remaining lines were silently dropped. The state is now a stack of SourceFrames — the stack is the call chain. A nested source just pushes a frame (the running driver, ticker thread or headless drain, picks it up next line) and the caller's frame resumes when it pops. Each frame still prints its own "source: <file> (N line(s))" summary; an interactive-line abort clears the whole chain; depth capped at 32 like the core script engine. The Computing modal shows the top frame. Regression-tested headless via BootDispatch (tests/tui/test_source.cpp): nested-then-continue, and self-recursion hitting the depth guard. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
72 lines
2.0 KiB
C++
72 lines
2.0 KiB
C++
#include <doctest/doctest.h>
|
|
|
|
#include "frontends/tui/tui.hpp"
|
|
|
|
#include <cstdio>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <string>
|
|
|
|
// Tui::Source nesting — regression for the bug where a nested `source`
|
|
// overwrote the single loading state, so the CALLING script's remaining
|
|
// lines never ran. Headless path (no screen): BootDispatch drains
|
|
// synchronously, exactly like `essim --batch --source`.
|
|
|
|
namespace {
|
|
|
|
std::string run_boot(const std::string &cmd) {
|
|
Tui t;
|
|
t.BootDispatch(cmd);
|
|
std::ostringstream oss;
|
|
t.DumpOutput(oss);
|
|
return oss.str();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("source: lines after a nested source still run") {
|
|
const char *inner = "test_src_inner.essim";
|
|
const char *outer = "test_src_outer.essim";
|
|
{
|
|
std::ofstream f(inner);
|
|
f << "new\n";
|
|
}
|
|
{
|
|
std::ofstream f(outer);
|
|
f << "source " << inner << "\n"
|
|
"verify\n";
|
|
}
|
|
|
|
std::string out = run_boot(std::string("source ") + outer);
|
|
|
|
// The inner script ran and was summarised…
|
|
CHECK(out.find("system created.") != std::string::npos);
|
|
CHECK(out.find(std::string("source: ") + inner) != std::string::npos);
|
|
// …and the OUTER script kept going after it: verify executed…
|
|
CHECK(out.find("verify: 0 local mismatch(es)") != std::string::npos);
|
|
// …and the outer summary counts its 2 effective lines.
|
|
CHECK(out.find(std::string("source: ") + outer + " (2 line(s))")
|
|
!= std::string::npos);
|
|
|
|
std::remove(inner);
|
|
std::remove(outer);
|
|
}
|
|
|
|
TEST_CASE("source: self-recursion stops at the depth guard") {
|
|
const char *loop = "test_src_loop.essim";
|
|
{
|
|
std::ofstream f(loop);
|
|
f << "source " << loop << "\n";
|
|
}
|
|
|
|
std::string out = run_boot(std::string("source ") + loop);
|
|
|
|
CHECK(out.find("source: nesting too deep, skipping")
|
|
!= std::string::npos);
|
|
// Every frame still closes with its own summary.
|
|
CHECK(out.find(std::string("source: ") + loop + " (1 line(s))")
|
|
!= std::string::npos);
|
|
|
|
std::remove(loop);
|
|
}
|