From e25ab34e328c09b3098b598346ddda06ab7b40b5 Mon Sep 17 00:00:00 2001 From: Noorhteen Raja NJ Date: Fri, 3 Sep 2021 00:45:20 +0530 Subject: [PATCH] Add test for readline shell (#4447) * fix: ReadlineShell pass stdin/out to Cmd's constructor * test: add test for readline shell * chore: increase required coverage * test: * fix: failing ci pipeline * fix: failing ci test --- .coveragerc | 6 +++--- tests/conftest.py | 16 ++++++++++++++++ tests/test_readline_shell.py | 21 +++++++++++++++++++++ xonsh/base_shell.py | 11 +++++++++-- xonsh/readline_shell.py | 20 ++++++++++++-------- 5 files changed, 61 insertions(+), 13 deletions(-) diff --git a/.coveragerc b/.coveragerc index a5f5bd88f..52bf449bc 100644 --- a/.coveragerc +++ b/.coveragerc @@ -27,7 +27,7 @@ exclude_lines = skip_covered = true skip_empty = true show_missing = true -sort = Cover +sort = Miss -# something is better than nothing :) -fail_under = 55 +# keep improving ;) +fail_under = 60 diff --git a/tests/conftest.py b/tests/conftest.py index 17e2eb700..96f560674 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -183,6 +183,22 @@ def ptk_shell(xonsh_execer): inp.close() +@pytest.fixture +def readline_shell(xonsh_execer, tmpdir, mocker): + from xonsh.readline_shell import ReadlineShell + + inp_path = tmpdir / "in" + inp = inp_path.open("w+") + out_path = tmpdir / "out" + out = out_path.open("w+") + + shell = ReadlineShell(execer=xonsh_execer, ctx={}, stdin=inp, stdout=out) + mocker.patch.object(shell, "_load_remaining_input_into_queue") + yield shell + inp.close() + out.close() + + def pytest_configure(config): """Abort test run if --flake8 requested, since it would hang on parser_test.py""" if config.getoption("--flake8", ""): diff --git a/tests/test_readline_shell.py b/tests/test_readline_shell.py index d122b4f43..896185310 100644 --- a/tests/test_readline_shell.py +++ b/tests/test_readline_shell.py @@ -23,3 +23,24 @@ def test_render_completions(prefix, completion, prefix_len, readline_completion) assert _render_completions({completion}, prefix, prefix_len) == [ readline_completion ] + + +@pytest.mark.parametrize( + "line, exp", + [ + [repr("hello"), "hello"], + ["2 * 3", "6"], + ], +) +def test_rl_prompt_cmdloop(line, exp, readline_shell, capsys): + shell = readline_shell + shell.use_rawinput = False + shell.stdin.write(f"{line}\nexit\n") # note: terminate with '\n' + shell.stdin.seek(0) + shell.cmdloop() + # xonsh, doesn't write all its output to shell.stdout + # so capture sys.stdout + out, err = capsys.readouterr() + + # sometimes the output has ansii color codes + assert exp in out.strip() diff --git a/xonsh/base_shell.py b/xonsh/base_shell.py index f6785db35..bc0267f3c 100644 --- a/xonsh/base_shell.py +++ b/xonsh/base_shell.py @@ -299,11 +299,18 @@ class Tee: return s -class BaseShell(object): +class BaseShell: """The xonsh shell.""" def __init__(self, execer, ctx, **kwargs): - super().__init__() + """ + + Notes + ----- + classes inheriting multiple base classes should call them explicitly + as done for ``ReadlineShell`` + """ + self.execer = execer self.ctx = ctx self.completer = Completer() if kwargs.get("completer", True) else None diff --git a/xonsh/readline_shell.py b/xonsh/readline_shell.py index 3328155e4..e63d54bf5 100644 --- a/xonsh/readline_shell.py +++ b/xonsh/readline_shell.py @@ -320,7 +320,11 @@ class ReadlineShell(BaseShell, cmd.Cmd): """The readline based xonsh shell.""" def __init__(self, completekey="tab", stdin=None, stdout=None, **kwargs): - super().__init__(completekey=completekey, stdin=stdin, stdout=stdout, **kwargs) + BaseShell.__init__(self, **kwargs) + # super() doesn't pass the stdin/stdout to Cmd's init method correctly. + # so calling it explicitly + cmd.Cmd.__init__(self, completekey=completekey, stdin=stdin, stdout=stdout) + setup_readline() self._current_indent = "" self._current_prompt = "" @@ -533,16 +537,16 @@ class ReadlineShell(BaseShell, cmd.Cmd): (C) Python Software Foundation, 2015. """ self.preloop() - if self.use_rawinput and self.completekey: - try: - import readline + try: + import readline + if self.use_rawinput and self.completekey: self.old_completer = readline.get_completer() readline.set_completer(self.complete) readline.parse_and_bind(self.completekey + ": complete") - have_readline = True - except ImportError: - have_readline = False + have_readline = True + except ImportError: + have_readline = False try: if intro is not None: self.intro = intro @@ -606,7 +610,7 @@ class ReadlineShell(BaseShell, cmd.Cmd): try: self._cmdloop(intro=intro) except (KeyboardInterrupt, SystemExit): - print() # Gives a newline + print(file=self.stdout) # Gives a newline fix_readline_state_after_ctrl_c() self.reset_buffer() intro = None