| // Copyright 2019 The Fuchsia Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "repl.h" | |
| #include <stdlib.h> | |
| #include <zircon/syscalls.h> | |
| #include <array> | |
| #include <fstream> | |
| #include <queue> | |
| #include <string> | |
| #include <gtest/gtest.h> | |
| #include "li.h" | |
| #include "src/developer/shell/josh/lib/runtime.h" | |
| #include "src/lib/line_input/line_input.h" | |
| #include "third_party/quickjs/quickjs-libc.h" | |
| #include "third_party/quickjs/quickjs.h" | |
| extern "C" const uint8_t qjsc_repl_init[]; | |
| extern "C" const uint32_t qjsc_repl_init_size; | |
| namespace shell::repl { | |
| class TestRepl : public Repl { | |
| public: | |
| TestRepl(JSContext* ctx, const std::string& prompt, bool ev) | |
| : Repl(ctx, prompt, [this](const std::string& s) { HandleLine(s); }), eval_(ev) { | |
| ChangeOutput(&shell_out_); | |
| } | |
| std::queue<std::string> GetListFullLines() { | |
| std::queue<std::string> res(full_lines_); | |
| std::queue<std::string>().swap(full_lines_); | |
| return res; | |
| } | |
| std::queue<std::string> GetListFullCmds() { | |
| std::queue res(full_cmds_); | |
| std::queue<std::string>().swap(full_cmds_); | |
| return res; | |
| } | |
| std::string PublicOpenSymbols(const std::string& cmd) { return OpenSymbols(cmd); } | |
| std::string GetOutput() { return shell_out_.str(); } | |
| protected: | |
| void HandleLine(const std::string& line) override { | |
| full_lines_.push(line); | |
| Repl::HandleLine(line); | |
| } | |
| void EvalCmd(const std::string& cmd) override { | |
| full_cmds_.push(cmd); | |
| if (eval_) { | |
| Repl::EvalCmd(cmd); | |
| } else { | |
| ShowPrompt(); // no evaluation, which makes this call | |
| } | |
| } | |
| private: | |
| std::queue<std::string> full_lines_; | |
| std::queue<std::string> full_cmds_; | |
| bool eval_ = false; | |
| std::ostringstream shell_out_; | |
| }; | |
| TEST(Repl, Sanity) { | |
| std::ostringstream captured_output; | |
| // set up the repl | |
| shell::Runtime rt; | |
| ASSERT_NE(rt.Get(), nullptr); | |
| shell::Context ctx(&rt); | |
| ASSERT_NE(ctx.Get(), nullptr); | |
| ASSERT_NE(0, ctx.InitStd()); | |
| ASSERT_NE(0, ctx.InitBuiltins("/pkg/data/fidling", "/pkg/data/lib")); | |
| JSContext* ctx_ptr = ctx.Get(); | |
| js_std_add_helpers(ctx_ptr, 0, nullptr); | |
| ASSERT_NE(shell::li::LiModuleInit(ctx_ptr, "li_internal"), nullptr); | |
| js_std_eval_binary(ctx_ptr, qjsc_repl_init, qjsc_repl_init_size, 0); | |
| // uninstall repl read handler and get a pointer to the repl | |
| std::string script = "os.setReadHandler(std.in.fileno(), null); repl.repl;"; | |
| JSValue res = | |
| JS_Eval(ctx_ptr, script.c_str(), script.length(), "<evalScript>", JS_EVAL_TYPE_GLOBAL); | |
| repl::Repl* repl = li::GetRepl(ctx_ptr, res); | |
| repl->ChangeOutput(&captured_output); | |
| std::string test_string = "print(4);\n"; | |
| repl->FeedInput(reinterpret_cast<unsigned char*>(test_string.data()), test_string.size()); | |
| std::string actual = captured_output.str(); | |
| std::string expected("undefined\n"); | |
| ASSERT_STREQ(expected.c_str(), actual.c_str()); | |
| std::string test_string2 = | |
| "ccc = new Promise(function(resolve, reject){os.setTimeout(() => resolve('d'), 1);});\n"; | |
| repl->FeedInput(reinterpret_cast<unsigned char*>(test_string2.data()), test_string2.size()); | |
| js_std_loop(ctx_ptr); | |
| std::string actual2 = captured_output.str(); | |
| std::string expected2("undefined\nd\n"); | |
| ASSERT_STREQ(expected2.c_str(), actual2.c_str()); | |
| } | |
| TEST(Repl, SpecialCharacters) { | |
| JSContext* ctx_ptr = nullptr; | |
| TestRepl repl(ctx_ptr, "li >", false); | |
| std::string test_string = "pr\x1b[Dint(3)\n"; | |
| std::string expected = "pint(3)r"; | |
| repl.FeedInput(reinterpret_cast<unsigned char*>(test_string.data()), test_string.size()); | |
| std::queue<std::string> res_lines = repl.GetListFullLines(); | |
| std::queue<std::string> res_cmds = repl.GetListFullCmds(); | |
| EXPECT_EQ(res_lines.size(), 1); | |
| EXPECT_EQ(res_cmds.size(), 1); | |
| EXPECT_STREQ(res_lines.front().c_str(), expected.c_str()); | |
| EXPECT_STREQ(res_cmds.front().c_str(), expected.c_str()); | |
| } | |
| TEST(Repl, MultipleLines) { | |
| JSContext* ctx_ptr = nullptr; | |
| TestRepl repl(ctx_ptr, "li >", false); | |
| constexpr int kNumLines = 4; | |
| std::array<std::string, kNumLines> test_string1 = {"function (\n", "a){\n", "\tprint(a)\n", | |
| "};\n"}; | |
| std::string cur_cmd; | |
| std::string expected = "function (a){print(a)};"; | |
| std::array<std::string, kNumLines> expected_open_symbols = {"(", "{", "{", ""}; | |
| for (int i = 0; i < kNumLines; i++) { | |
| repl.FeedInput(reinterpret_cast<unsigned char*>(test_string1[i].data()), | |
| test_string1[i].size()); | |
| std::queue<std::string> res_lines = repl.GetListFullLines(); | |
| ASSERT_EQ(res_lines.size(), 1); | |
| cur_cmd += res_lines.front(); | |
| EXPECT_STREQ(repl.PublicOpenSymbols(cur_cmd).c_str(), expected_open_symbols[i].c_str()); | |
| } | |
| std::queue<std::string> res_cmds = repl.GetListFullCmds(); | |
| EXPECT_EQ(res_cmds.size(), 1); | |
| EXPECT_STREQ(res_cmds.front().c_str(), expected.c_str()); | |
| constexpr int kNumLines2 = 5; | |
| std::array<std::string, kNumLines2> test_string2 = {"regex = /\n", "[abc\n", "/\n", "]\n", | |
| "/;\n"}; | |
| std::array<std::string, kNumLines2> expected_lines = {"regex = /", "[abc", "/", "]", "/;"}; | |
| std::string expected2 = "regex = /[abc/]/;"; | |
| std::array<std::string, kNumLines2> expected_open_symbols2 = {"/", "/[", "/[", "/", ""}; | |
| cur_cmd = ""; | |
| for (int i = 0; i < kNumLines2; i++) { | |
| repl.FeedInput(reinterpret_cast<unsigned char*>(test_string2[i].data()), | |
| test_string2[i].size()); | |
| std::queue<std::string> res_lines = repl.GetListFullLines(); | |
| ASSERT_EQ(res_lines.size(), 1); | |
| cur_cmd += res_lines.front(); | |
| EXPECT_STREQ(repl.PublicOpenSymbols(cur_cmd).c_str(), expected_open_symbols2[i].c_str()); | |
| } | |
| res_cmds = repl.GetListFullCmds(); | |
| EXPECT_EQ(res_cmds.size(), 1); | |
| EXPECT_STREQ(res_cmds.front().c_str(), expected2.c_str()); | |
| } | |
| TEST(Repl, TabCompletionAndHistory) { | |
| std::ostringstream captured_output; | |
| // set up the repl | |
| shell::Runtime rt; | |
| ASSERT_NE(rt.Get(), nullptr); | |
| shell::Context ctx(&rt); | |
| ASSERT_NE(ctx.Get(), nullptr); | |
| ASSERT_NE(0, ctx.InitStd()); | |
| ASSERT_NE(0, ctx.InitBuiltins("/pkg/data/fidling", "/pkg/data/lib")); | |
| JSContext* ctx_ptr = ctx.Get(); | |
| js_std_add_helpers(ctx_ptr, 0, nullptr); | |
| ASSERT_NE(shell::li::LiModuleInit(ctx_ptr, "li_internal"), nullptr); | |
| js_std_eval_binary(ctx_ptr, qjsc_repl_init, qjsc_repl_init_size, 0); | |
| // uninstall repl read handler and get a pointer to the repl | |
| std::string script = "os.setReadHandler(std.in.fileno(), null); repl.repl;"; | |
| JSValue res = | |
| JS_Eval(ctx_ptr, script.c_str(), script.length(), "<evalScript>", JS_EVAL_TYPE_GLOBAL); | |
| auto repl = reinterpret_cast<repl::Repl*>(li::GetRepl(ctx_ptr, res)); | |
| repl->ChangeOutput(&captured_output); | |
| std::string test_string = "a = \"dddd\";\n"; | |
| repl->FeedInput(reinterpret_cast<unsigned char*>(test_string.data()), test_string.size()); | |
| std::string test_string2 = "b =a.le\t;\n"; | |
| repl->FeedInput(reinterpret_cast<unsigned char*>(test_string2.data()), test_string2.size()); | |
| std::string actual = captured_output.str(); | |
| std::string expected("dddd\n4\n"); | |
| ASSERT_STREQ(expected.c_str(), actual.c_str()); | |
| // testing history, repeats the previous command | |
| std::string test_string3 = "\x1b[A\n"; | |
| repl->FeedInput(reinterpret_cast<unsigned char*>(test_string3.data()), test_string3.size()); | |
| std::string actual2 = captured_output.str(); | |
| std::string expected2("dddd\n4\n4\n"); | |
| ASSERT_STREQ(expected2.c_str(), actual2.c_str()); | |
| } | |
| } // namespace shell::repl |