import inspect
import keyword
import unittest
from collections import namedtuple
from unittest import mock
try:
import jedi
has_jedi = True
except ImportError:
has_jedi = False
from bpython import autocomplete, inspection
from bpython.line import LinePart
glob_function = "glob.iglob"
class TestSafeEval(unittest.TestCase):
def test_catches_syntax_error(self):
with self.assertRaises(autocomplete.EvaluationError):
autocomplete.safe_eval("1re", {})
class TestFormatters(unittest.TestCase):
def test_filename(self):
completer = autocomplete.FilenameCompletion()
last_part_of_filename = completer.format
self.assertEqual(last_part_of_filename("abc"), "abc")
self.assertEqual(last_part_of_filename("abc/"), "abc/")
self.assertEqual(last_part_of_filename("abc/efg"), "efg")
self.assertEqual(last_part_of_filename("abc/efg/"), "efg/")
self.assertEqual(last_part_of_filename("/abc"), "abc")
self.assertEqual(last_part_of_filename("ab.c/e.f.g/"), "e.f.g/")
def test_attribute(self):
self.assertEqual(autocomplete._after_last_dot("abc.edf"), "edf")
def completer(matches):
mock_completer = autocomplete.BaseCompletionType()
mock_completer.matches = mock.Mock(return_value=matches)
return mock_completer
class TestGetCompleter(unittest.TestCase):
def test_no_completers(self):
self.assertTupleEqual(autocomplete.get_completer([], 0, ""), ([], None))
def test_one_completer_without_matches_returns_empty_list_and_none(self):
a = completer([])
self.assertTupleEqual(
autocomplete.get_completer([a], 0, ""), ([], None)
)
def test_one_completer_returns_matches_and_completer(self):
a = completer(["a"])
self.assertTupleEqual(
autocomplete.get_completer([a], 0, ""), (["a"], a)
)
def test_two_completers_with_matches_returns_first_matches(self):
a = completer(["a"])
b = completer(["b"])
self.assertEqual(autocomplete.get_completer([a, b], 0, ""), (["a"], a))
def test_first_non_none_completer_matches_are_returned(self):
a = completer([])
b = completer(["a"])
self.assertEqual(autocomplete.get_completer([a, b], 0, ""), ([], None))
def test_only_completer_returns_None(self):
a = completer(None)
self.assertEqual(autocomplete.get_completer([a], 0, ""), ([], None))
def test_first_completer_returns_None(self):
a = completer(None)
b = completer(["a"])
self.assertEqual(autocomplete.get_completer([a, b], 0, ""), (["a"], b))
class TestCumulativeCompleter(unittest.TestCase):
def completer(self, matches):
mock_completer = autocomplete.BaseCompletionType()
mock_completer.matches = mock.Mock(return_value=matches)
return mock_completer
def test_no_completers_fails(self):
with self.assertRaises(ValueError):
autocomplete.CumulativeCompleter([])
def test_one_empty_completer_returns_empty(self):
a = self.completer([])
cumulative = autocomplete.CumulativeCompleter([a])
self.assertEqual(cumulative.matches(3, "abc"), set())
def test_one_none_completer_returns_none(self):
a = self.completer(None)
cumulative = autocomplete.CumulativeCompleter([a])
self.assertEqual(cumulative.matches(3, "abc"), None)
def test_two_completers_get_both(self):
a = self.completer(["a"])
b = self.completer(["b"])
cumulative = autocomplete.CumulativeCompleter([a, b])
self.assertEqual(cumulative.matches(3, "abc"), {"a", "b"})
def test_order_completer(self):
a = self.completer(["ax", "ab="])
b = self.completer(["aa"])
cumulative = autocomplete.CumulativeCompleter([a, b])
self.assertEqual(
autocomplete.get_completer([cumulative], 1, "a"),
(["ab=", "aa", "ax"], cumulative),
)
class TestFilenameCompletion(unittest.TestCase):
def setUp(self):
self.completer = autocomplete.FilenameCompletion()
def test_locate_fails_when_not_in_string(self):
self.assertEqual(self.completer.locate(4, "abcd"), None)
def test_locate_succeeds_when_in_string(self):
self.assertEqual(
self.completer.locate(4, "a'bc'd"), LinePart(2, 4, "bc")
)
def test_issue_491(self):
self.assertNotEqual(self.completer.matches(9, '"a[a.l-1]'), None)
@mock.patch(glob_function, new=lambda text: [])
def test_match_returns_none_if_not_in_string(self):
self.assertEqual(self.completer.matches(2, "abcd"), None)
@mock.patch(glob_function, new=lambda text: [])
def test_match_returns_empty_list_when_no_files(self):
self.assertEqual(self.completer.matches(2, '"a'), set())
@mock.patch(glob_function, new=lambda text: ["abcde", "aaaaa"])
@mock.patch("os.path.expanduser", new=lambda text: text)
@mock.patch("os.path.isdir", new=lambda text: False)
@mock.patch("os.path.sep", new="/")
def test_match_returns_files_when_files_exist(self):
self.assertEqual(
sorted(self.completer.matches(2, '"x')), ["aaaaa", "abcde"]
)
@mock.patch(glob_function, new=lambda text: ["abcde", "aaaaa"])
@mock.patch("os.path.expanduser", new=lambda text: text)
@mock.patch("os.path.isdir", new=lambda text: True)
@mock.patch("os.path.sep", new="/")
def test_match_returns_dirs_when_dirs_exist(self):
self.assertEqual(
sorted(self.completer.matches(2, '"x')), ["aaaaa/", "abcde/"]
)
@mock.patch(
glob_function, new=lambda text: ["/expand/ed/abcde", "/expand/ed/aaaaa"]
)
@mock.patch(
"os.path.expanduser", new=lambda text: text.replace("~", "/expand/ed")
)
@mock.patch("os.path.isdir", new=lambda text: False)
@mock.patch("os.path.sep", new="/")
def test_tilde_stays_pretty(self):
self.assertEqual(
sorted(self.completer.matches(4, '"~/a')), ["~/aaaaa", "~/abcde"]
)
@mock.patch("os.path.sep", new="/")
def test_formatting_takes_just_last_part(self):
self.assertEqual(self.completer.format("/hello/there/"), "there/")
self.assertEqual(self.completer.format("/hello/there"), "there")
class MockNumPy:
"""This is a mock numpy object that raises an error when there is an attempt
to convert it to a boolean."""
def __nonzero__(self):
raise ValueError(
"The truth value of an array with more than one "
"element is ambiguous. Use a.any() or a.all()"
)
class TestDictKeyCompletion(unittest.TestCase):
def test_set_of_keys_returned_when_matches_found(self):
com = autocomplete.DictKeyCompletion()
local = {"d": {"ab": 1, "cd": 2}}
self.assertSetEqual(
com.matches(2, "d[", locals_=local), {"'ab']", "'cd']"}
)
def test_none_returned_when_eval_error(self):
com = autocomplete.DictKeyCompletion()
local = {"e": {"ab": 1, "cd": 2}}
self.assertEqual(com.matches(2, "d[", locals_=local), None)
def test_none_returned_when_not_dict_type(self):
com = autocomplete.DictKeyCompletion()
local = {"l": ["ab", "cd"]}
self.assertEqual(com.matches(2, "l[", locals_=local), None)
def test_none_returned_when_no_matches_left(self):
com = autocomplete.DictKeyCompletion()
local = {"d": {"ab": 1, "cd": 2}}
self.assertEqual(com.matches(3, "d[r", locals_=local), None)
def test_obj_that_does_not_allow_conversion_to_bool(self):
com = autocomplete.DictKeyCompletion()
local = {"mNumPy": MockNumPy()}
self.assertEqual(com.matches(7, "mNumPy[", locals_=local), None)
class Foo:
a = 10
def __init__(self):
self.b = 20
def method(self, x):
pass
class Properties(Foo):
@property
def asserts_when_called(self):
raise AssertionError("getter method called")
class Slots:
__slots__ = ["a", "b"]
class OverriddenGetattribute(Foo):
def __getattribute__(self, name):
raise AssertionError("custom get attribute invoked")
class TestAttrCompletion(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.com = autocomplete.AttrCompletion()
def test_att_matches_found_on_instance(self):
self.assertSetEqual(
self.com.matches(2, "a.", locals_={"a": Foo()}),
{"a.method", "a.a", "a.b"},
)
def test_descriptor_attributes_not_run(self):
com = autocomplete.AttrCompletion()
self.assertSetEqual(
com.matches(2, "a.", locals_={"a": Properties()}),
{"a.b", "a.a", "a.method", "a.asserts_when_called"},
)
def test_custom_get_attribute_not_invoked(self):
com = autocomplete.AttrCompletion()
self.assertSetEqual(
com.matches(2, "a.", locals_={"a": OverriddenGetattribute()}),
{"a.b", "a.a", "a.method"},
)
def test_slots_not_crash(self):
com = autocomplete.AttrCompletion()
self.assertSetEqual(
com.matches(2, "A.", locals_={"A": Slots}),
{"A.b", "A.a"},
)
class TestExpressionAttributeCompletion(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.com = autocomplete.ExpressionAttributeCompletion()
def test_att_matches_found_on_instance(self):
self.assertSetEqual(
self.com.matches(5, "a[0].", locals_={"a": [Foo()]}),
{"method", "a", "b"},
)
def test_other_getitem_methods_not_called(self):
class FakeList:
def __getitem__(inner_self, i):
self.fail("possibly side-effecting __getitem_ method called")
self.com.matches(5, "a[0].", locals_={"a": FakeList()})
def test_tuples_complete(self):
self.assertSetEqual(
self.com.matches(5, "a[0].", locals_={"a": (Foo(),)}),
{"method", "a", "b"},
)
@unittest.skip("TODO, subclasses do not complete yet")
def test_list_subclasses_complete(self):
class ListSubclass(list):
pass
self.assertSetEqual(
self.com.matches(5, "a[0].", locals_={"a": ListSubclass([Foo()])}),
{"method", "a", "b"},
)
def test_getitem_not_called_in_list_subclasses_overriding_getitem(self):
class FakeList(list):
def __getitem__(inner_self, i):
self.fail("possibly side-effecting __getitem_ method called")
self.com.matches(5, "a[0].", locals_={"a": FakeList()})
def test_literals_complete(self):
self.assertSetEqual(
self.com.matches(10, "[a][0][0].", locals_={"a": (Foo(),)}),
{"method", "a", "b"},
)
def test_dictionaries_complete(self):
self.assertSetEqual(
self.com.matches(7, 'a["b"].', locals_={"a": {"b": Foo()}}),
{"method", "a", "b"},
)
class TestMagicMethodCompletion(unittest.TestCase):
def test_magic_methods_complete_after_double_underscores(self):
com = autocomplete.MagicMethodCompletion()
block = "class Something(object)\n def __"
self.assertSetEqual(
com.matches(
10,
" def __",
current_block=block,
complete_magic_methods=True,
),
set(autocomplete.MAGIC_METHODS),
)
Completion = namedtuple("Completion", ["name", "complete"])
@unittest.skipUnless(has_jedi, "jedi required")
class TestMultilineJediCompletion(unittest.TestCase):
def test_returns_none_with_single_line(self):
com = autocomplete.MultilineJediCompletion()
self.assertEqual(
com.matches(2, "Va", current_block="Va", history=[]), None
)
def test_returns_non_with_blank_second_line(self):
com = autocomplete.MultilineJediCompletion()
self.assertEqual(
com.matches(
0, "", current_block="class Foo():\n", history=["class Foo():"]
),
None,
)
def matches_from_completions(
self, cursor, line, block, history, completions
):
with mock.patch("bpython.autocomplete.jedi.Script") as Script:
script = Script.return_value
script.complete.return_value = completions
com = autocomplete.MultilineJediCompletion()
return com.matches(
cursor, line, current_block=block, history=history
)
def test_completions_starting_with_different_letters(self):
matches = self.matches_from_completions(
2,
" a",
"class Foo:\n a",
["adsf"],
[Completion("Abc", "bc"), Completion("Cbc", "bc")],
)
self.assertEqual(matches, None)
def test_completions_starting_with_different_cases(self):
matches = self.matches_from_completions(
2,
" a",
"class Foo:\n a",
["adsf"],
[Completion("Abc", "bc"), Completion("ade", "de")],
)
self.assertSetEqual(matches, {"ade"})
def test_issue_544(self):
com = autocomplete.MultilineJediCompletion()
code = "@asyncio.coroutine\ndef"
history = ("import asyncio", "@asyncio.coroutin")
com.matches(3, "def", current_block=code, history=history)
class TestGlobalCompletion(unittest.TestCase):
def setUp(self):
self.com = autocomplete.GlobalCompletion()
def test_function(self):
def function():
pass
self.assertEqual(
self.com.matches(8, "function", locals_={"function": function}),
{"function("},
)
def test_completions_are_unicode(self):
for m in self.com.matches(1, "a", locals_={"abc": 10}):
self.assertIsInstance(m, str)
def test_mock_kwlist(self):
with mock.patch.object(keyword, "kwlist", new=["abcd"]):
self.assertEqual(self.com.matches(3, "abc", locals_={}), None)
def test_mock_kwlist_non_ascii(self):
with mock.patch.object(keyword, "kwlist", new=["abcĂ"]):
self.assertEqual(self.com.matches(3, "abc", locals_={}), None)
class TestParameterNameCompletion(unittest.TestCase):
def test_set_of_params_returns_when_matches_found(self):
def func(apple, apricot, banana, carrot):
pass
argspec = inspection.ArgSpec(*inspect.getfullargspec(func))
funcspec = inspection.FuncProps("func", argspec, False)
com = autocomplete.ParameterNameCompletion()
self.assertSetEqual(
com.matches(1, "a", funcprops=funcspec), {"apple=", "apricot="}
)
self.assertSetEqual(
com.matches(2, "ba", funcprops=funcspec), {"banana="}
)
self.assertSetEqual(
com.matches(3, "car", funcprops=funcspec), {"carrot="}
)
self.assertSetEqual(
com.matches(5, "func(", funcprops=funcspec),
{"apple=", "apricot=", "banana=", "carrot="},
)