diff --git a/src/xinterpreter.cpp b/src/xinterpreter.cpp index 1c3a7a34..b1913461 100644 --- a/src/xinterpreter.cpp +++ b/src/xinterpreter.cpp @@ -236,6 +236,45 @@ namespace xcpp return "unknown"; } + class SilentStreamRedirectRAII + { + public: + + explicit SilentStreamRedirectRAII(bool silent) + : m_silent(silent) + , m_old_cout(silent ? std::cout.rdbuf() : nullptr) + , m_old_cerr(silent ? std::cerr.rdbuf() : nullptr) + { + if (m_silent) + { + std::cout.rdbuf(&m_null); + std::cerr.rdbuf(&m_null); + } + } + + ~SilentStreamRedirectRAII() noexcept + { + if (m_silent) + { + std::cout.rdbuf(m_old_cout); + std::cerr.rdbuf(m_old_cerr); + } + } + + SilentStreamRedirectRAII(const SilentStreamRedirectRAII&) = delete; + SilentStreamRedirectRAII& operator=(const SilentStreamRedirectRAII&) = delete; + SilentStreamRedirectRAII(SilentStreamRedirectRAII&&) = delete; + SilentStreamRedirectRAII& operator=(SilentStreamRedirectRAII&&) = delete; + + private: + + bool m_silent; + xnull m_null; + + std::streambuf* m_old_cout; + std::streambuf* m_old_cerr; + }; + interpreter::interpreter(int argc, const char* const* argv) : xmagics() , p_cout_strbuf(nullptr) @@ -288,16 +327,7 @@ namespace xcpp // If silent is set to true, temporarily dismiss all std::cerr and // std::cout outputs resulting from `process_code`. - - auto cout_strbuf = std::cout.rdbuf(); - auto cerr_strbuf = std::cerr.rdbuf(); - - if (config.silent) - { - auto null = xnull(); - std::cout.rdbuf(&null); - std::cerr.rdbuf(&null); - } + SilentStreamRedirectRAII silent_guard(config.silent); std::string err; @@ -331,13 +361,6 @@ namespace xcpp std::cout << std::flush; std::cerr << std::flush; - // Reset non-silent output buffers - if (config.silent) - { - std::cout.rdbuf(cout_strbuf); - std::cerr.rdbuf(cerr_strbuf); - } - // Depending of error level, publish execution result or execution // error, and compose execute_reply message. if (errorlevel) diff --git a/test/test_interpreter.cpp b/test/test_interpreter.cpp index 4b19ae26..a1a11f57 100644 --- a/test/test_interpreter.cpp +++ b/test/test_interpreter.cpp @@ -1229,3 +1229,41 @@ TEST_SUITE("mime_bundle_repr") REQUIRE(res == expected); } } + +#if !defined(__EMSCRIPTEN__) +// TODO: Currently any test added to this file will fail for the wasm build saying memory access out of bounds. +TEST_CASE("Silent mode restores std::cout and std::cerr buffers") +{ + std::vector Args = {}; + xcpp::interpreter interpreter((int)Args.size(), Args.data()); + + auto* cout_before = std::cout.rdbuf(); + auto* cerr_before = std::cerr.rdbuf(); + + xeus::execute_request_config config; + config.silent = true; + config.store_history = false; + config.allow_stdin = false; + + nl::json header = nl::json::object(); + xeus::xrequest_context::guid_list id = {}; + xeus::xrequest_context context(header, id); + + std::promise promise; + auto callback = [&promise](nl::json result) { promise.set_value(result); }; + + std::string code = R"( + #include + std::cout << "hidden stdout\n"; + std::cerr << "hidden stderr\n"; + )"; + + interpreter.execute_request(context, callback, code, config, nl::json::object()); + + auto reply = promise.get_future().get(); + REQUIRE(reply["status"] == "ok"); + + REQUIRE(std::cout.rdbuf() == cout_before); + REQUIRE(std::cerr.rdbuf() == cerr_before); +} +#endif \ No newline at end of file