tui: fix nested source abandoning the calling script
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>
This commit is contained in:
71
tests/tui/test_source.cpp
Normal file
71
tests/tui/test_source.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#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);
|
||||
}
|
||||
Reference in New Issue
Block a user