// test_terminal_panel_smoke.cpp — smoke test para terminal_panel. // // Prueba real del PTY en Linux: spawn "echo hello && exit 0", // espera output, verifica que el scrollback contiene "hello". // // En Windows: test skipped (ConPTY require DISPLAY y proceso vivo — CI). // En Linux sin forkpty: verifica que el build es correcto al menos. #define CATCH_CONFIG_MAIN #include "catch_amalgamated.hpp" #include "viz/terminal_panel/terminal_panel.h" #include #include #include #ifdef _WIN32 // En Windows en CI, skipeamos el smoke del proceso real. TEST_CASE("smoke: spawn echo hello and exit, scrollback contains hello", "[terminal_panel][smoke]") { SKIP("Smoke PTY test skipped on Windows CI"); } #else // Helper: concatena todas las celdas del scrollback como texto plano. static std::string scrollback_text(fn_term::TerminalPanel& p) { std::lock_guard lk(p.buf_mutex); std::string result; for (const auto& line : p.lines) { for (const auto& cell : line) { if (cell.ch >= 0x20 && cell.ch < 0x7F) result += static_cast(cell.ch); } result += '\n'; } return result; } TEST_CASE("smoke: spawn echo hello and exit, scrollback contains hello", "[terminal_panel][smoke]") { fn_term::TerminalPanel term; term.shell = "/bin/bash"; term.scrollback_lines = 100; fn_term::open(term); REQUIRE(term.is_open()); // Enviar el comando y esperar a que el proceso salga. fn_term::send(term, "echo hello && exit 0\n"); // Esperar máximo 2 segundos a que el proceso termine. auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); while (!term.process_exited.load() && std::chrono::steady_clock::now() < deadline) { std::this_thread::sleep_for(std::chrono::milliseconds(20)); } // Dar 100ms adicionales para que el reader thread procese el último output. std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::string text = scrollback_text(term); fn_term::close(term); INFO("scrollback: " << text); REQUIRE(text.find("hello") != std::string::npos); } TEST_CASE("smoke: process exits cleanly", "[terminal_panel][smoke]") { fn_term::TerminalPanel term; term.shell = "/bin/bash"; term.scrollback_lines = 50; fn_term::open(term); REQUIRE(term.is_open()); fn_term::send(term, "exit 0\n"); auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); while (!term.process_exited.load() && std::chrono::steady_clock::now() < deadline) { std::this_thread::sleep_for(std::chrono::milliseconds(20)); } REQUIRE(term.process_exited.load()); REQUIRE(term.exit_code == 0); fn_term::close(term); } TEST_CASE("smoke: readonly panel ignores send", "[terminal_panel][smoke]") { fn_term::TerminalPanel term; term.shell = "/bin/bash"; term.readonly = true; term.scrollback_lines = 50; fn_term::open(term); REQUIRE(term.is_open()); // send() no debe hacer nada (readonly). fn_term::send(term, "echo should_not_appear\n"); std::this_thread::sleep_for(std::chrono::milliseconds(200)); std::string text = scrollback_text(term); fn_term::close(term); // "should_not_appear" no debería estar en el scrollback porque send es no-op. INFO("scrollback: " << text); REQUIRE(text.find("should_not_appear") == std::string::npos); } #endif // !_WIN32