| import importlib | |
| import inspect | |
| import os | |
| import types | |
| import unittest | |
| from _colorize import ANSIColors, get_theme | |
| from _pyrepl.completing_reader import stripcolor | |
| from _pyrepl.fancycompleter import ( | |
| Completer, | |
| colorize_matches, | |
| commonprefix, | |
| _color_for_obj, | |
| ) | |
| 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_colorize_matches_signature(self): | |
| signature = inspect.signature(colorize_matches) | |
| self.assertEqual(list(signature.parameters), ["names", "values", "theme"]) | |
| 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): | |
| theme = get_theme() | |
| match = _color_for_obj('spam', 1, theme) | |
| 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() |