# encoding: utf-8
from itertools import islice
from six.moves import range
import collections
import inspect
import os
import shutil
import socket
import sys
import tempfile
from bpython._py3compat import py3
from bpython import config, repl, cli, autocomplete
from bpython.test import MagicIterMock, mock, FixLanguageTestCase as TestCase
from bpython.test import unittest, TEST_CONFIG
pypy = "PyPy" in sys.version
def setup_config(conf):
config_struct = config.Struct()
config.loadini(config_struct, TEST_CONFIG)
if conf is not None and "autocomplete_mode" in conf:
config_struct.autocomplete_mode = conf["autocomplete_mode"]
return config_struct
class FakeHistory(repl.History):
def __init__(self):
pass
def reset(self):
pass
class FakeRepl(repl.Repl):
def __init__(self, conf=None):
repl.Repl.__init__(self, repl.Interpreter(), setup_config(conf))
self.current_line = ""
self.cursor_offset = 0
class FakeCliRepl(cli.CLIRepl, FakeRepl):
def __init__(self):
self.s = ""
self.cpos = 0
self.rl_history = FakeHistory()
class TestMatchesIterator(unittest.TestCase):
def setUp(self):
self.matches = ["bobby", "bobbies", "bobberina"]
self.matches_iterator = repl.MatchesIterator()
self.matches_iterator.current_word = "bob"
self.matches_iterator.orig_line = "bob"
self.matches_iterator.orig_cursor_offset = len("bob")
self.matches_iterator.matches = self.matches
def test_next(self):
self.assertEqual(next(self.matches_iterator), self.matches[0])
for x in range(len(self.matches) - 1):
next(self.matches_iterator)
self.assertEqual(next(self.matches_iterator), self.matches[0])
self.assertEqual(next(self.matches_iterator), self.matches[1])
self.assertNotEqual(next(self.matches_iterator), self.matches[1])
def test_previous(self):
self.assertEqual(self.matches_iterator.previous(), self.matches[2])
for x in range(len(self.matches) - 1):
self.matches_iterator.previous()
self.assertNotEqual(self.matches_iterator.previous(), self.matches[0])
self.assertEqual(self.matches_iterator.previous(), self.matches[1])
self.assertEqual(self.matches_iterator.previous(), self.matches[0])
def test_nonzero(self):
"""self.matches_iterator should be False at start,
then True once we active a match.
"""
self.assertFalse(self.matches_iterator)
next(self.matches_iterator)
self.assertTrue(self.matches_iterator)
def test_iter(self):
slice = islice(self.matches_iterator, 0, 9)
self.assertEqual(list(slice), self.matches * 3)
def test_current(self):
with self.assertRaises(ValueError):
self.matches_iterator.current()
next(self.matches_iterator)
self.assertEqual(self.matches_iterator.current(), self.matches[0])
def test_update(self):
slice = islice(self.matches_iterator, 0, 3)
self.assertEqual(list(slice), self.matches)
newmatches = ["string", "str", "set"]
completer = mock.Mock()
completer.locate.return_value = (0, 1, "s")
self.matches_iterator.update(1, "s", newmatches, completer)
newslice = islice(newmatches, 0, 3)
self.assertNotEqual(list(slice), self.matches)
self.assertEqual(list(newslice), newmatches)
def test_cur_line(self):
completer = mock.Mock()
completer.locate.return_value = (
0,
self.matches_iterator.orig_cursor_offset,
self.matches_iterator.orig_line,
)
self.matches_iterator.completer = completer
with self.assertRaises(ValueError):
self.matches_iterator.cur_line()
self.assertEqual(next(self.matches_iterator), self.matches[0])
self.assertEqual(
self.matches_iterator.cur_line(),
(len(self.matches[0]), self.matches[0]),
)
def test_is_cseq(self):
self.assertTrue(self.matches_iterator.is_cseq())
class TestArgspec(unittest.TestCase):
def setUp(self):
self.repl = FakeRepl()
self.repl.push("def spam(a, b, c):\n", False)
self.repl.push(" pass\n", False)
self.repl.push("\n", False)
self.repl.push("class Spam(object):\n", False)
self.repl.push(" def spam(self, a, b, c):\n", False)
self.repl.push(" pass\n", False)
self.repl.push("\n", False)
self.repl.push("class SpammitySpam(object):\n", False)
self.repl.push(" def __init__(self, a, b, c):\n", False)
self.repl.push(" pass\n", False)
self.repl.push("\n", False)
self.repl.push("class WonderfulSpam(object):\n", False)
self.repl.push(" def __new__(self, a, b, c):\n", False)
self.repl.push(" pass\n", False)
self.repl.push("\n", False)
self.repl.push("o = Spam()\n", False)
self.repl.push("\n", False)
def set_input_line(self, line):
"""Set current input line of the test REPL."""
self.repl.current_line = line
self.repl.cursor_offset = len(line)
def test_func_name(self):
for (line, expected_name) in [
("spam(", "spam"),
("spam(map([]", "map"),
("spam((), ", "spam"),
]:
self.set_input_line(line)
self.assertTrue(self.repl.get_args())
self.assertEqual(self.repl.current_func.__name__, expected_name)
def test_func_name_method_issue_479(self):
for (line, expected_name) in [
("o.spam(", "spam"),
("o.spam(map([]", "map"),
("o.spam((), ", "spam"),
]:
self.set_input_line(line)
self.assertTrue(self.repl.get_args())
self.assertEqual(self.repl.current_func.__name__, expected_name)
def test_syntax_error_parens(self):
for line in ["spam(]", "spam([)", "spam())"]:
self.set_input_line(line)
# Should not explode
self.repl.get_args()
def test_kw_arg_position(self):
self.set_input_line("spam(a=0")
self.assertTrue(self.repl.get_args())
self.assertEqual(self.repl.arg_pos, "a")
self.set_input_line("spam(1, b=1")
self.assertTrue(self.repl.get_args())
self.assertEqual(self.repl.arg_pos, "b")
self.set_input_line("spam(1, c=2")
self.assertTrue(self.repl.get_args())
self.assertEqual(self.repl.arg_pos, "c")
def test_lambda_position(self):
self.set_input_line("spam(lambda a, b: 1, ")
self.assertTrue(self.repl.get_args())
self.assertTrue(self.repl.funcprops)
# Argument position
self.assertEqual(self.repl.arg_pos, 1)
def test_issue127(self):
self.set_input_line("x=range(")
self.assertTrue(self.repl.get_args())
self.assertEqual(self.repl.current_func.__name__, "range")
self.set_input_line("{x:range(")
self.assertTrue(self.repl.get_args())
self.assertEqual(self.repl.current_func.__name__, "range")
self.set_input_line("foo(1, 2, x,range(")
self.assertEqual(self.repl.current_func.__name__, "range")
self.set_input_line("(x,range(")
self.assertEqual(self.repl.current_func.__name__, "range")
def test_nonexistent_name(self):
self.set_input_line("spamspamspam(")
self.assertFalse(self.repl.get_args())
def test_issue572(self):
self.set_input_line("SpammitySpam(")
self.assertTrue(self.repl.get_args())
self.set_input_line("WonderfulSpam(")
self.assertTrue(self.repl.get_args())
@unittest.skipIf(pypy, "pypy pydoc doesn't have this")
def test_issue583(self):
self.repl = FakeRepl()
self.repl.push("a = 1.2\n", False)
self.set_input_line("a.is_integer(")
self.repl.set_docstring()
self.assertIsNot(self.repl.docstring, None)
def test_methods_of_expressions(self):
self.set_input_line("'a'.capitalize(")
self.assertTrue(self.repl.get_args())
self.set_input_line("(1 + 1.1).as_integer_ratio(")
self.assertTrue(self.repl.get_args())
class TestArgspecInternal(unittest.TestCase):
def test_function_expressions(self):
te = self.assertTupleEqual
fa = lambda line: repl.Repl._funcname_and_argnum(line)
for line, (func, argnum) in [
("spam(", ("spam", 0)),
("spam((), ", ("spam", 1)),
("spam.eggs((), ", ("spam.eggs", 1)),
("spam[abc].eggs((), ", ("spam[abc].eggs", 1)),
("spam[0].eggs((), ", ("spam[0].eggs", 1)),
("spam[a + b]eggs((), ", ("spam[a + b]eggs", 1)),
("spam().eggs((), ", ("spam().eggs", 1)),
("spam(1, 2).eggs((), ", ("spam(1, 2).eggs", 1)),
("spam(1, f(1)).eggs((), ", ("spam(1, f(1)).eggs", 1)),
("[0].eggs((), ", ("[0].eggs", 1)),
("[0][0]((), {}).eggs((), ", ("[0][0]((), {}).eggs", 1)),
("a + spam[0].eggs((), ", ("spam[0].eggs", 1)),
("spam(", ("spam", 0)),
("spam(map([]", ("map", 0)),
("spam((), ", ("spam", 1)),
]:
te(fa(line), (func, argnum))
class TestGetSource(unittest.TestCase):
def setUp(self):
self.repl = FakeRepl()
def set_input_line(self, line):
"""Set current input line of the test REPL."""
self.repl.current_line = line
self.repl.cursor_offset = len(line)
def assert_get_source_error_for_current_function(self, func, msg):
self.repl.current_func = func
with self.assertRaises(repl.SourceNotFound):
self.repl.get_source_of_current_name()
try:
self.repl.get_source_of_current_name()
except repl.SourceNotFound as e:
self.assertEqual(e.args[0], msg)
else:
self.fail("Should have raised SourceNotFound")
def test_current_function(self):
self.set_input_line("INPUTLINE")
self.repl.current_func = inspect.getsource
self.assertIn(
"text of the source code", self.repl.get_source_of_current_name()
)
self.assert_get_source_error_for_current_function(
[], "No source code found for INPUTLINE"
)
self.assert_get_source_error_for_current_function(
list.pop, "No source code found for INPUTLINE"
)
@unittest.skipIf(pypy, "different errors for PyPy")
def test_current_function_cpython(self):
self.set_input_line("INPUTLINE")
self.assert_get_source_error_for_current_function(
collections.defaultdict.copy, "No source code found for INPUTLINE"
)
self.assert_get_source_error_for_current_function(
collections.defaultdict, "could not find class definition"
)
def test_current_line(self):
self.repl.interp.locals["a"] = socket.socket
self.set_input_line("a")
self.assertIn("dup(self)", self.repl.get_source_of_current_name())
# TODO add tests for various failures without using current function
class TestEditConfig(TestCase):
def setUp(self):
self.repl = FakeRepl()
self.repl.interact.confirm = lambda msg: True
self.repl.interact.notify = lambda msg: None
self.repl.config.editor = "true"
def test_create_config(self):
tmp_dir = tempfile.mkdtemp()
try:
config_path = os.path.join(tmp_dir, "newdir", "config")
self.repl.config.config_path = config_path
self.repl.edit_config()
self.assertTrue(os.path.exists(config_path))
finally:
shutil.rmtree(tmp_dir)
self.assertFalse(os.path.exists(config_path))
class TestRepl(unittest.TestCase):
def set_input_line(self, line):
"""Set current input line of the test REPL."""
self.repl.current_line = line
self.repl.cursor_offset = len(line)
def setUp(self):
self.repl = FakeRepl()
def test_current_string(self):
self.set_input_line('a = "2"')
# TODO factor cpos out of repl.Repl
self.repl.cpos = 0
self.assertEqual(self.repl.current_string(), '"2"')
self.set_input_line('a = "2" + 2')
self.assertEqual(self.repl.current_string(), "")
def test_push(self):
self.repl = FakeRepl()
self.repl.push("foobar = 2")
self.assertEqual(self.repl.interp.locals["foobar"], 2)
# COMPLETE TESTS
# 1. Global tests
def test_simple_global_complete(self):
self.repl = FakeRepl({"autocomplete_mode": autocomplete.SIMPLE})
self.set_input_line("d")
self.assertTrue(self.repl.complete())
self.assertTrue(hasattr(self.repl.matches_iter, "matches"))
self.assertEqual(
self.repl.matches_iter.matches,
["def", "del", "delattr(", "dict(", "dir(", "divmod("],
)
def test_substring_global_complete(self):
self.repl = FakeRepl({"autocomplete_mode": autocomplete.SUBSTRING})
self.set_input_line("time")
self.assertTrue(self.repl.complete())
self.assertTrue(hasattr(self.repl.matches_iter, "matches"))
self.assertEqual(
self.repl.matches_iter.matches, ["RuntimeError(", "RuntimeWarning("]
)
def test_fuzzy_global_complete(self):
self.repl = FakeRepl({"autocomplete_mode": autocomplete.FUZZY})
self.set_input_line("doc")
self.assertTrue(self.repl.complete())
self.assertTrue(hasattr(self.repl.matches_iter, "matches"))
self.assertEqual(
self.repl.matches_iter.matches,
["UnboundLocalError(", "__doc__"]
if not py3
else ["ChildProcessError(", "UnboundLocalError(", "__doc__"],
)
# 2. Attribute tests
def test_simple_attribute_complete(self):
self.repl = FakeRepl({"autocomplete_mode": autocomplete.SIMPLE})
self.set_input_line("Foo.b")
code = "class Foo():\n\tdef bar(self):\n\t\tpass\n"
for line in code.split("\n"):
self.repl.push(line)
self.assertTrue(self.repl.complete())
self.assertTrue(hasattr(self.repl.matches_iter, "matches"))
self.assertEqual(self.repl.matches_iter.matches, ["Foo.bar"])
def test_substring_attribute_complete(self):
self.repl = FakeRepl({"autocomplete_mode": autocomplete.SUBSTRING})
self.set_input_line("Foo.az")
code = "class Foo():\n\tdef baz(self):\n\t\tpass\n"
for line in code.split("\n"):
self.repl.push(line)
self.assertTrue(self.repl.complete())
self.assertTrue(hasattr(self.repl.matches_iter, "matches"))
self.assertEqual(self.repl.matches_iter.matches, ["Foo.baz"])
def test_fuzzy_attribute_complete(self):
self.repl = FakeRepl({"autocomplete_mode": autocomplete.FUZZY})
self.set_input_line("Foo.br")
code = "class Foo():\n\tdef bar(self):\n\t\tpass\n"
for line in code.split("\n"):
self.repl.push(line)
self.assertTrue(self.repl.complete())
self.assertTrue(hasattr(self.repl.matches_iter, "matches"))
self.assertEqual(self.repl.matches_iter.matches, ["Foo.bar"])
# 3. Edge cases
def test_updating_namespace_complete(self):
self.repl = FakeRepl({"autocomplete_mode": autocomplete.SIMPLE})
self.set_input_line("foo")
self.repl.push("foobar = 2")
self.assertTrue(self.repl.complete())
self.assertTrue(hasattr(self.repl.matches_iter, "matches"))
self.assertEqual(self.repl.matches_iter.matches, ["foobar"])
def test_file_should_not_appear_in_complete(self):
self.repl = FakeRepl({"autocomplete_mode": autocomplete.SIMPLE})
self.set_input_line("_")
self.assertTrue(self.repl.complete())
self.assertTrue(hasattr(self.repl.matches_iter, "matches"))
self.assertNotIn("__file__", self.repl.matches_iter.matches)
# 4. Parameter names
def test_paremeter_name_completion(self):
self.repl = FakeRepl({"autocomplete_mode": autocomplete.SIMPLE})
self.set_input_line("foo(ab")
code = "def foo(abc=1, abd=2, xyz=3):\n\tpass\n"
for line in code.split("\n"):
self.repl.push(line)
self.assertTrue(self.repl.complete())
self.assertTrue(hasattr(self.repl.matches_iter, "matches"))
self.assertEqual(
self.repl.matches_iter.matches, ["abc=", "abd=", "abs("]
)
class TestCliRepl(unittest.TestCase):
def setUp(self):
self.repl = FakeCliRepl()
def test_atbol(self):
self.assertTrue(self.repl.atbol())
self.repl.s = "\t\t"
self.assertTrue(self.repl.atbol())
self.repl.s = "\t\tnot an empty line"
self.assertFalse(self.repl.atbol())
def test_addstr(self):
self.repl.complete = mock.Mock(True)
self.repl.s = "foo"
self.repl.addstr("bar")
self.assertEqual(self.repl.s, "foobar")
self.repl.cpos = 3
self.repl.addstr("buzz")
self.assertEqual(self.repl.s, "foobuzzbar")
class TestCliReplTab(unittest.TestCase):
def setUp(self):
self.repl = FakeCliRepl()
# 3 Types of tab complete
def test_simple_tab_complete(self):
self.repl.matches_iter = MagicIterMock()
if py3:
self.repl.matches_iter.__bool__.return_value = False
else:
self.repl.matches_iter.__nonzero__.return_value = False
self.repl.complete = mock.Mock()
self.repl.print_line = mock.Mock()
self.repl.matches_iter.is_cseq.return_value = False
self.repl.show_list = mock.Mock()
self.repl.funcprops = mock.Mock()
self.repl.arg_pos = mock.Mock()
self.repl.matches_iter.cur_line.return_value = (None, "foobar")
self.repl.s = "foo"
self.repl.tab()
self.assertTrue(self.repl.complete.called)
self.repl.complete.assert_called_with(tab=True)
self.assertEqual(self.repl.s, "foobar")
@unittest.skip("disabled while non-simple completion is disabled")
def test_substring_tab_complete(self):
self.repl.s = "bar"
self.repl.config.autocomplete_mode = autocomplete.FUZZY
self.repl.tab()
self.assertEqual(self.repl.s, "foobar")
self.repl.tab()
self.assertEqual(self.repl.s, "foofoobar")
@unittest.skip("disabled while non-simple completion is disabled")
def test_fuzzy_tab_complete(self):
self.repl.s = "br"
self.repl.config.autocomplete_mode = autocomplete.FUZZY
self.repl.tab()
self.assertEqual(self.repl.s, "foobar")
# Edge Cases
def test_normal_tab(self):
"""make sure pressing the tab key will
still in some cases add a tab"""
self.repl.s = ""
self.repl.config = mock.Mock()
self.repl.config.tab_length = 4
self.repl.complete = mock.Mock()
self.repl.print_line = mock.Mock()
self.repl.tab()
self.assertEqual(self.repl.s, " ")
def test_back_parameter(self):
self.repl.matches_iter = mock.Mock()
self.repl.matches_iter.matches = True
self.repl.matches_iter.previous.return_value = "previtem"
self.repl.matches_iter.is_cseq.return_value = False
self.repl.show_list = mock.Mock()
self.repl.funcprops = mock.Mock()
self.repl.arg_pos = mock.Mock()
self.repl.matches_iter.cur_line.return_value = (None, "previtem")
self.repl.print_line = mock.Mock()
self.repl.s = "foo"
self.repl.cpos = 0
self.repl.tab(back=True)
self.assertTrue(self.repl.matches_iter.previous.called)
self.assertTrue(self.repl.s, "previtem")
# Attribute Tests
@unittest.skip("disabled while non-simple completion is disabled")
def test_fuzzy_attribute_tab_complete(self):
"""Test fuzzy attribute with no text"""
self.repl.s = "Foo."
self.repl.config.autocomplete_mode = autocomplete.FUZZY
self.repl.tab()
self.assertEqual(self.repl.s, "Foo.foobar")
@unittest.skip("disabled while non-simple completion is disabled")
def test_fuzzy_attribute_tab_complete2(self):
"""Test fuzzy attribute with some text"""
self.repl.s = "Foo.br"
self.repl.config.autocomplete_mode = autocomplete.FUZZY
self.repl.tab()
self.assertEqual(self.repl.s, "Foo.foobar")
# Expand Tests
def test_simple_expand(self):
self.repl.s = "f"
self.cpos = 0
self.repl.matches_iter = mock.Mock()
self.repl.matches_iter.is_cseq.return_value = True
self.repl.matches_iter.substitute_cseq.return_value = (3, "foo")
self.repl.print_line = mock.Mock()
self.repl.tab()
self.assertEqual(self.repl.s, "foo")
@unittest.skip("disabled while non-simple completion is disabled")
def test_substring_expand_forward(self):
self.repl.config.autocomplete_mode = autocomplete.SUBSTRING
self.repl.s = "ba"
self.repl.tab()
self.assertEqual(self.repl.s, "bar")
@unittest.skip("disabled while non-simple completion is disabled")
def test_fuzzy_expand(self):
pass
if __name__ == "__main__":
unittest.main()