| import importlib |
| import os |
| import types |
| import unittest |
| |
| from _colorize import ANSIColors, get_theme |
| from _pyrepl.completing_reader import stripcolor |
| from _pyrepl.fancycompleter import Completer, commonprefix |
| from test.support.import_helper import ready_to_import |
| |
| class MockPatch: |
| def __init__(self): |
| self.original_values = {} |
| |
| def setattr(self, obj, name, value): |
| if obj not in self.original_values: |
| self.original_values[obj] = {} |
| if name not in self.original_values[obj]: |
| self.original_values[obj][name] = getattr(obj, name) |
| setattr(obj, name, value) |
| |
| def restore_all(self): |
| for obj, attrs in self.original_values.items(): |
| for name, value in attrs.items(): |
| setattr(obj, name, value) |
| |
| class FancyCompleterTests(unittest.TestCase): |
| def setUp(self): |
| self.mock_patch = MockPatch() |
| |
| def tearDown(self): |
| self.mock_patch.restore_all() |
| |
| def test_commonprefix(self): |
| self.assertEqual(commonprefix(['isalpha', 'isdigit', 'foo']), '') |
| self.assertEqual(commonprefix(['isalpha', 'isdigit']), 'is') |
| self.assertEqual(commonprefix([]), '') |
| |
| def test_complete_attribute(self): |
| compl = Completer({'a': None}, use_colors=False) |
| self.assertEqual(compl.attr_matches('a.'), ['a.__']) |
| matches = compl.attr_matches('a.__') |
| self.assertNotIn('__class__', matches) |
| self.assertIn('a.__class__', matches) |
| match = compl.attr_matches('a.__class') |
| self.assertEqual(len(match), 1) |
| self.assertTrue(match[0].startswith('a.__class__')) |
| |
| def test_complete_attribute_prefix(self): |
| class C(object): |
| attr = 1 |
| _attr = 2 |
| __attr__attr = 3 |
| compl = Completer({'a': C}, use_colors=False) |
| self.assertEqual(compl.attr_matches('a.'), ['a.attr', 'a.mro']) |
| self.assertEqual( |
| compl.attr_matches('a._'), |
| ['a._C__attr__attr', 'a._attr'], |
| ) |
| matches = compl.attr_matches('a.__') |
| self.assertNotIn('__class__', matches) |
| self.assertIn('a.__class__', matches) |
| match = compl.attr_matches('a.__class') |
| self.assertEqual(len(match), 1) |
| self.assertTrue(match[0].startswith('a.__class__')) |
| |
| compl = Completer({'a': None}, use_colors=False) |
| self.assertEqual(compl.attr_matches('a._'), ['a.__']) |
| |
| def test_complete_attribute_colored(self): |
| theme = get_theme() |
| compl = Completer({'a': 42}, use_colors=True) |
| matches = compl.attr_matches('a.__') |
| self.assertGreater(len(matches), 2) |
| expected_color = theme.fancycompleter.type |
| expected_part = f'{expected_color}a.__class__{ANSIColors.RESET}' |
| for match in matches: |
| if expected_part in match: |
| break |
| else: |
| self.assertFalse(True, matches) |
| self.assertNotIn(' ', matches) |
| |
| def test_preserves_callable_postfix_for_single_attribute_match(self): |
| compl = Completer({'os': os}, use_colors=False) |
| self.assertEqual(compl.attr_matches('os.getpid'), ['os.getpid()']) |
| |
| def test_property_method_not_called(self): |
| class Foo: |
| property_called = False |
| |
| @property |
| def bar(self): |
| self.property_called = True |
| return 1 |
| |
| foo = Foo() |
| compl = Completer({'foo': foo}, use_colors=False) |
| self.assertEqual(compl.attr_matches('foo.b'), ['foo.bar']) |
| self.assertFalse(foo.property_called) |
| |
| def test_excessive_getattr(self): |
| class Foo: |
| calls = 0 |
| bar = '' |
| |
| def __getattribute__(self, name): |
| if name == 'bar': |
| self.calls += 1 |
| return None |
| return super().__getattribute__(name) |
| |
| foo = Foo() |
| compl = Completer({'foo': foo}, use_colors=False) |
| self.assertEqual(compl.complete('foo.b', 0), 'foo.bar') |
| self.assertEqual(foo.calls, 1) |
| |
| def test_uncreated_attr(self): |
| class Foo: |
| __slots__ = ('bar',) |
| |
| compl = Completer({'foo': Foo()}, use_colors=False) |
| self.assertEqual(compl.complete('foo.', 0), 'foo.bar') |
| |
| def test_module_attributes_do_not_reify_lazy_imports(self): |
| with ready_to_import("test_pyrepl_lazy_mod", "lazy import json\n") as (name, _): |
| module = importlib.import_module(name) |
| self.assertIs(type(module.__dict__["json"]), types.LazyImportType) |
| |
| compl = Completer({name: module}, use_colors=False) |
| self.assertEqual(compl.attr_matches(f"{name}.j"), [f"{name}.json"]) |
| self.assertIs(type(module.__dict__["json"]), types.LazyImportType) |
| |
| def test_complete_colored_single_match(self): |
| """No coloring, via commonprefix.""" |
| compl = Completer({'foobar': 42}, use_colors=True) |
| matches = compl.global_matches('foob') |
| self.assertEqual(matches, ['foobar']) |
| |
| def test_does_not_color_single_match(self): |
| class obj: |
| msgs = [] |
| |
| compl = Completer({'obj': obj}, use_colors=True) |
| matches = compl.attr_matches('obj.msgs') |
| self.assertEqual(matches, ['obj.msgs']) |
| |
| def test_complete_global(self): |
| compl = Completer({'foobar': 1, 'foobazzz': 2}, use_colors=False) |
| self.assertEqual(compl.global_matches('foo'), ['fooba']) |
| matches = compl.global_matches('fooba') |
| self.assertEqual(set(matches), set(['foobar', 'foobazzz'])) |
| self.assertEqual(compl.global_matches('foobaz'), ['foobazzz']) |
| self.assertEqual(compl.global_matches('nothing'), []) |
| |
| def test_complete_global_colored(self): |
| theme = get_theme() |
| compl = Completer({'foobar': 1, 'foobazzz': 2}, use_colors=True) |
| self.assertEqual(compl.global_matches('foo'), ['fooba']) |
| matches = compl.global_matches('fooba') |
| |
| int_color = theme.fancycompleter.int |
| self.assertEqual(matches, [ |
| f'{int_color}foobar{ANSIColors.RESET}', |
| f'{int_color}foobazzz{ANSIColors.RESET}', |
| ]) |
| self.assertEqual(compl.global_matches('foobaz'), ['foobazzz']) |
| self.assertEqual(compl.global_matches('nothing'), []) |
| |
| def test_colorized_match_is_stripped(self): |
| compl = Completer({'a': 42}, use_colors=True) |
| match = compl._color_for_obj('spam', 1) |
| self.assertEqual(stripcolor(match), 'spam') |
| |
| def test_complete_with_indexer(self): |
| compl = Completer({'lst': [None, 2, 3]}, use_colors=False) |
| self.assertEqual(compl.attr_matches('lst[0].'), ['lst[0].__']) |
| matches = compl.attr_matches('lst[0].__') |
| self.assertNotIn('__class__', matches) |
| self.assertIn('lst[0].__class__', matches) |
| match = compl.attr_matches('lst[0].__class') |
| self.assertEqual(len(match), 1) |
| self.assertTrue(match[0].startswith('lst[0].__class__')) |
| |
| def test_autocomplete(self): |
| class A: |
| aaa = None |
| abc_1 = None |
| abc_2 = None |
| abc_3 = None |
| bbb = None |
| compl = Completer({'A': A}, use_colors=False) |
| # |
| # In this case, we want to display all attributes which start with |
| # 'a'. |
| matches = compl.attr_matches('A.a') |
| self.assertEqual( |
| sorted(matches), |
| ['A.aaa', 'A.abc_1', 'A.abc_2', 'A.abc_3'], |
| ) |
| # |
| # If there is an actual common prefix, we return just it, so that readline |
| # will insert it into place |
| matches = compl.attr_matches('A.ab') |
| self.assertEqual(matches, ['A.abc_']) |
| # |
| # Finally, at the next tab, we display again all the completions |
| # available for this common prefix. |
| matches = compl.attr_matches('A.abc_') |
| self.assertEqual( |
| sorted(matches), |
| ['A.abc_1', 'A.abc_2', 'A.abc_3'], |
| ) |
| |
| def test_complete_exception(self): |
| compl = Completer({}, use_colors=False) |
| self.assertEqual(compl.attr_matches('xxx.'), []) |
| |
| def test_complete_invalid_attr(self): |
| compl = Completer({'str': str}, use_colors=False) |
| self.assertEqual(compl.attr_matches('str.xx'), []) |
| |
| def test_complete_function_skipped(self): |
| compl = Completer({'str': str}, use_colors=False) |
| self.assertEqual(compl.attr_matches('str.split().'), []) |
| |
| def test_unicode_in___dir__(self): |
| class Foo(object): |
| def __dir__(self): |
| return ['hello', 'world'] |
| |
| compl = Completer({'a': Foo()}, use_colors=False) |
| matches = compl.attr_matches('a.') |
| self.assertEqual(matches, ['a.hello', 'a.world']) |
| self.assertIs(type(matches[0]), str) |
| |
| |
| if __name__ == "__main__": |
| unittest.main() |