diff --git a/tests/test_utils.py b/tests/test_utils.py index 95f92bcba..e96717b6e 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -3,17 +3,35 @@ import pytest import warnings from mock import Mock, call, patch -from thefuck.utils import default_settings, \ - memoize, get_closest, get_all_executables, replace_argument, \ - get_all_matched_commands, is_app, for_app, cache, \ - get_valid_history_without_current, _cache, get_close_matches +from thefuck.utils import ( + default_settings, + memoize, + get_closest, + get_all_executables, + replace_argument, + get_all_matched_commands, + is_app, + for_app, + cache, + get_valid_history_without_current, + _cache, + get_close_matches, +) from thefuck.types import Command -@pytest.mark.parametrize('override, old, new', [ - ({'key': 'val'}, {}, {'key': 'val'}), - ({'key': 'new-val'}, {'key': 'val'}, {'key': 'val'}), - ({'key': 'new-val', 'unset': 'unset'}, {'key': 'val'}, {'key': 'val', 'unset': 'unset'})]) +@pytest.mark.parametrize( + "override, old, new", + [ + ({"key": "val"}, {}, {"key": "val"}), + ({"key": "new-val"}, {"key": "val"}, {"key": "val"}), + ( + {"key": "new-val", "unset": "unset"}, + {"key": "val"}, + {"key": "val", "unset": "unset"}, + ), + ], +) def test_default_settings(settings, override, old, new): settings.clear() settings.update(old) @@ -22,16 +40,16 @@ def test_default_settings(settings, override, old, new): def test_memoize(): - fn = Mock(__name__='fn') + fn = Mock(__name__="fn") memoized = memoize(fn) memoized() memoized() fn.assert_called_once_with() -@pytest.mark.usefixtures('no_memoize') +@pytest.mark.usefixtures("no_memoize") def test_no_memoize(): - fn = Mock(__name__='fn') + fn = Mock(__name__="fn") memoized = memoize(fn) memoized() memoized() @@ -40,134 +58,178 @@ def test_no_memoize(): class TestGetClosest(object): def test_when_can_match(self): - assert 'branch' == get_closest('brnch', ['branch', 'status']) + assert "branch" == get_closest("brnch", ["branch", "status"]) def test_when_cant_match(self): - assert 'status' == get_closest('st', ['status', 'reset']) + assert "status" == get_closest("st", ["status", "reset"]) def test_without_fallback(self): - assert get_closest('st', ['status', 'reset'], - fallback_to_first=False) is None + assert get_closest("st", ["status", "reset"], fallback_to_first=False) is None class TestGetCloseMatches(object): - @patch('thefuck.utils.difflib_get_close_matches') + @patch("thefuck.utils.difflib_get_close_matches") def test_call_with_n(self, difflib_mock): - get_close_matches('', [], 1) + get_close_matches("", [], 1) assert difflib_mock.call_args[0][2] == 1 - @patch('thefuck.utils.difflib_get_close_matches') + @patch("thefuck.utils.difflib_get_close_matches") def test_call_without_n(self, difflib_mock, settings): - get_close_matches('', []) - assert difflib_mock.call_args[0][2] == settings.get('num_close_matches') + get_close_matches("", []) + assert difflib_mock.call_args[0][2] == settings.get("num_close_matches") @pytest.fixture def get_aliases(mocker): - mocker.patch('thefuck.shells.shell.get_aliases', - return_value=['vim', 'apt-get', 'fsck', 'fuck']) + mocker.patch( + "thefuck.shells.shell.get_aliases", + return_value=["vim", "apt-get", "fsck", "fuck"], + ) -@pytest.mark.usefixtures('no_memoize', 'get_aliases') +@pytest.mark.usefixtures("no_memoize", "get_aliases") def test_get_all_executables(): all_callables = get_all_executables() - assert 'vim' in all_callables - assert 'fsck' in all_callables - assert 'fuck' not in all_callables + assert "vim" in all_callables + assert "fsck" in all_callables + assert "fuck" not in all_callables @pytest.fixture def os_environ_pathsep(monkeypatch, path, pathsep): - env = {'PATH': path} - monkeypatch.setattr('os.environ', env) - monkeypatch.setattr('os.pathsep', pathsep) + env = {"PATH": path} + monkeypatch.setattr("os.environ", env) + monkeypatch.setattr("os.pathsep", pathsep) return env -@pytest.mark.usefixtures('no_memoize', 'os_environ_pathsep') -@pytest.mark.parametrize('path, pathsep', [ - ('/foo:/bar:/baz:/foo/bar', ':'), - (r'C:\\foo;C:\\bar;C:\\baz;C:\\foo\\bar', ';')]) +@pytest.mark.usefixtures("no_memoize", "os_environ_pathsep") +@pytest.mark.parametrize( + "path, pathsep", + [("/foo:/bar:/baz:/foo/bar", ":"), (r"C:\\foo;C:\\bar;C:\\baz;C:\\foo\\bar", ";")], +) def test_get_all_executables_pathsep(path, pathsep): - with patch('thefuck.utils.Path') as Path_mock: + with patch("thefuck.utils.Path") as Path_mock: get_all_executables() Path_mock.assert_has_calls([call(p) for p in path.split(pathsep)], True) -@pytest.mark.usefixtures('no_memoize', 'os_environ_pathsep') -@pytest.mark.parametrize('path, pathsep, excluded', [ - ('/foo:/bar:/baz:/foo/bar:/mnt/foo', ':', '/mnt/foo'), - (r'C:\\foo;C:\\bar;C:\\baz;C:\\foo\\bar;Z:\\foo', ';', r'Z:\\foo')]) +@pytest.mark.usefixtures("no_memoize", "os_environ_pathsep") +@pytest.mark.parametrize( + "path, pathsep, excluded", + [ + ("/foo:/bar:/baz:/foo/bar:/mnt/foo", ":", "/mnt/foo"), + (r"C:\\foo;C:\\bar;C:\\baz;C:\\foo\\bar;Z:\\foo", ";", r"Z:\\foo"), + ], +) def test_get_all_executables_exclude_paths(path, pathsep, excluded, settings): settings.init() settings.excluded_search_path_prefixes = [excluded] - with patch('thefuck.utils.Path') as Path_mock: + with patch("thefuck.utils.Path") as Path_mock: get_all_executables() path_list = path.split(pathsep) assert call(path_list[-1]) not in Path_mock.mock_calls assert all(call(p) in Path_mock.mock_calls for p in path_list[:-1]) -@pytest.mark.parametrize('args, result', [ - (('apt-get instol vim', 'instol', 'install'), 'apt-get install vim'), - (('git brnch', 'brnch', 'branch'), 'git branch')]) +@pytest.mark.parametrize( + "args, result", + [ + (("apt-get instol vim", "instol", "install"), "apt-get install vim"), + (("git brnch", "brnch", "branch"), "git branch"), + ], +) def test_replace_argument(args, result): assert replace_argument(*args) == result -@pytest.mark.parametrize('stderr, result', [ - (("git: 'cone' is not a git command. See 'git --help'.\n" - '\n' - 'Did you mean one of these?\n' - '\tclone'), ['clone']), - (("git: 're' is not a git command. See 'git --help'.\n" - '\n' - 'Did you mean one of these?\n' - '\trebase\n' - '\treset\n' - '\tgrep\n' - '\trm'), ['rebase', 'reset', 'grep', 'rm']), - (('tsuru: "target" is not a tsuru command. See "tsuru help".\n' - '\n' - 'Did you mean one of these?\n' - '\tservice-add\n' - '\tservice-bind\n' - '\tservice-doc\n' - '\tservice-info\n' - '\tservice-list\n' - '\tservice-remove\n' - '\tservice-status\n' - '\tservice-unbind'), ['service-add', 'service-bind', 'service-doc', - 'service-info', 'service-list', 'service-remove', - 'service-status', 'service-unbind'])]) +@pytest.mark.parametrize( + "stderr, result", + [ + ( + ( + "git: 'cone' is not a git command. See 'git --help'.\n" + "\n" + "Did you mean one of these?\n" + "\tclone" + ), + ["clone"], + ), + ( + ( + "git: 're' is not a git command. See 'git --help'.\n" + "\n" + "Did you mean one of these?\n" + "\trebase\n" + "\treset\n" + "\tgrep\n" + "\trm" + ), + ["rebase", "reset", "grep", "rm"], + ), + ( + ( + 'tsuru: "target" is not a tsuru command. See "tsuru help".\n' + "\n" + "Did you mean one of these?\n" + "\tservice-add\n" + "\tservice-bind\n" + "\tservice-doc\n" + "\tservice-info\n" + "\tservice-list\n" + "\tservice-remove\n" + "\tservice-status\n" + "\tservice-unbind" + ), + [ + "service-add", + "service-bind", + "service-doc", + "service-info", + "service-list", + "service-remove", + "service-status", + "service-unbind", + ], + ), + ], +) def test_get_all_matched_commands(stderr, result): assert list(get_all_matched_commands(stderr)) == result -@pytest.mark.usefixtures('no_memoize') -@pytest.mark.parametrize('script, names, result', [ - ('/usr/bin/git diff', ['git', 'hub'], True), - ('/bin/hdfs dfs -rm foo', ['hdfs'], True), - ('git diff', ['git', 'hub'], True), - ('hub diff', ['git', 'hub'], True), - ('hg diff', ['git', 'hub'], False)]) +@pytest.mark.usefixtures("no_memoize") +@pytest.mark.parametrize( + "script, names, result", + [ + ("/usr/bin/git diff", ["git", "hub"], True), + ("/bin/hdfs dfs -rm foo", ["hdfs"], True), + ("git diff", ["git", "hub"], True), + ("hub diff", ["git", "hub"], True), + ("hg diff", ["git", "hub"], False), + ], +) def test_is_app(script, names, result): - assert is_app(Command(script, ''), *names) == result - - -@pytest.mark.usefixtures('no_memoize') -@pytest.mark.parametrize('script, names, result', [ - ('/usr/bin/git diff', ['git', 'hub'], True), - ('/bin/hdfs dfs -rm foo', ['hdfs'], True), - ('git diff', ['git', 'hub'], True), - ('hub diff', ['git', 'hub'], True), - ('hg diff', ['git', 'hub'], False)]) + assert is_app(Command(script, ""), *names) == result + + +@pytest.mark.usefixtures("no_memoize") +@pytest.mark.parametrize( + "script, names, result", + [ + ("/usr/bin/git diff", ["git", "hub"], True), + ("/bin/hdfs dfs -rm foo", ["hdfs"], True), + ("git diff", ["git", "hub"], True), + ("hub diff", ["git", "hub"], True), + ("hg diff", ["git", "hub"], False), + ], +) def test_for_app(script, names, result): @for_app(*names) def match(command): return True - assert match(Command(script, '')) == result + assert match(Command(script, "")) == result class TestCache(object): @@ -191,86 +253,93 @@ def get(self, k, v=None): def close(self): return - mocker.patch('thefuck.utils.shelve.open', new_callable=lambda: _Shelve) + mocker.patch("thefuck.utils.shelve.open", new_callable=lambda: _Shelve) return value @pytest.fixture(autouse=True) def enable_cache(self, monkeypatch, shelve): - monkeypatch.setattr('thefuck.utils.cache.disabled', False) + monkeypatch.setattr("thefuck.utils.cache.disabled", False) _cache._init_db() @pytest.fixture(autouse=True) def mtime(self, mocker): - mocker.patch('thefuck.utils.os.path.getmtime', return_value=0) + mocker.patch("thefuck.utils.os.path.getmtime", return_value=0) @pytest.fixture def fn(self): - @cache('~/.bashrc') + @cache("~/.bashrc") def fn(): - return 'test' + return "test" return fn @pytest.fixture def key(self, monkeypatch): - monkeypatch.setattr('thefuck.utils.Cache._get_key', - lambda *_: 'key') - return 'key' + monkeypatch.setattr("thefuck.utils.Cache._get_key", lambda *_: "key") + return "key" def test_with_blank_cache(self, shelve, fn, key): assert shelve == {} - assert fn() == 'test' - assert shelve == {key: {'etag': '0', 'value': 'test'}} + assert fn() == "test" + assert shelve == {key: {"etag": "0", "value": "test"}} def test_with_filled_cache(self, shelve, fn, key): - cache_value = {key: {'etag': '0', 'value': 'new-value'}} + cache_value = {key: {"etag": "0", "value": "new-value"}} shelve.update(cache_value) - assert fn() == 'new-value' + assert fn() == "new-value" assert shelve == cache_value def test_when_etag_changed(self, shelve, fn, key): - shelve.update({key: {'etag': '-1', 'value': 'old-value'}}) - assert fn() == 'test' - assert shelve == {key: {'etag': '0', 'value': 'test'}} + shelve.update({key: {"etag": "-1", "value": "old-value"}}) + assert fn() == "test" + assert shelve == {key: {"etag": "0", "value": "test"}} class TestGetValidHistoryWithoutCurrent(object): @pytest.fixture(autouse=True) def fail_on_warning(self): - warnings.simplefilter('error') + warnings.simplefilter("error") yield warnings.resetwarnings() @pytest.fixture(autouse=True) def history(self, mocker): - mock = mocker.patch('thefuck.shells.shell.get_history') + mock = mocker.patch("thefuck.shells.shell.get_history") # Passing as an argument causes `UnicodeDecodeError` # with newer pytest and python 2.7 - mock.return_value = ['le cat', 'fuck', 'ls cat', - 'diff x', 'nocommand x', u'café ô'] + mock.return_value = [ + "le cat", + "fuck", + "ls cat", + "diff x", + "nocommand x", + "café ô", + ] return mock @pytest.fixture(autouse=True) def alias(self, mocker): - return mocker.patch('thefuck.utils.get_alias', - return_value='fuck') + return mocker.patch("thefuck.utils.get_alias", return_value="fuck") @pytest.fixture(autouse=True) def bins(self, mocker): callables = list() - for name in ['diff', 'ls', 'café']: + for name in ["diff", "ls", "café"]: bin_mock = mocker.Mock(name=name) bin_mock.configure_mock(name=name, is_dir=lambda: False) callables.append(bin_mock) path_mock = mocker.Mock(iterdir=mocker.Mock(return_value=callables)) - return mocker.patch('thefuck.utils.Path', return_value=path_mock) - - @pytest.mark.parametrize('script, result', [ - ('le cat', ['ls cat', 'diff x', u'café ô']), - ('diff x', ['ls cat', u'café ô']), - ('fuck', ['ls cat', 'diff x', u'café ô']), - (u'cafe ô', ['ls cat', 'diff x', u'café ô']), - ]) + return mocker.patch("thefuck.utils.Path", return_value=path_mock) + + @pytest.mark.parametrize( + "script, result", + [ + ("le cat", ["ls cat", "diff x"]), + ("diff x", ["ls cat"]), + ("fuck", ["ls cat", "diff x"]), + ("cafe ô", ["ls cat", "diff x"]), + ], + ) def test_get_valid_history_without_current(self, script, result): - command = Command(script, '') + command = Command(script, "") assert get_valid_history_without_current(command) == result diff --git a/thefuck/conf.py b/thefuck/conf.py index 611ec84b7..d71cef842 100644 --- a/thefuck/conf.py +++ b/thefuck/conf.py @@ -14,7 +14,7 @@ def load_source(name, pathname, _file=None): module_spec.loader.exec_module(module) return module except ImportError: - from imp import load_source + import importlib.util class Settings(dict): @@ -44,23 +44,26 @@ def init(self, args=None): self.update(self._settings_from_args(args)) def _init_settings_file(self): - settings_path = self.user_dir.joinpath('settings.py') + settings_path = self.user_dir.joinpath("settings.py") if not settings_path.is_file(): - with settings_path.open(mode='w') as settings_file: + with settings_path.open(mode="w") as settings_file: settings_file.write(const.SETTINGS_HEADER) for setting in const.DEFAULT_SETTINGS.items(): - settings_file.write(u'# {} = {}\n'.format(*setting)) + settings_file.write("# {} = {}\n".format(*setting)) def _get_user_dir_path(self): """Returns Path object representing the user config resource""" - xdg_config_home = os.environ.get('XDG_CONFIG_HOME', '~/.config') - user_dir = Path(xdg_config_home, 'thefuck').expanduser() - legacy_user_dir = Path('~', '.thefuck').expanduser() + xdg_config_home = os.environ.get("XDG_CONFIG_HOME", "~/.config") + user_dir = Path(xdg_config_home, "thefuck").expanduser() + legacy_user_dir = Path("~", ".thefuck").expanduser() # For backward compatibility use legacy '~/.thefuck' if it exists: if legacy_user_dir.is_dir(): - warn(u'Config path {} is deprecated. Please move to {}'.format( - legacy_user_dir, user_dir)) + warn( + "Config path {} is deprecated. Please move to {}".format( + legacy_user_dir, user_dir + ) + ) return legacy_user_dir else: return user_dir @@ -69,7 +72,7 @@ def _setup_user_dir(self): """Returns user config dir, create it when it doesn't exist.""" user_dir = self._get_user_dir_path() - rules_dir = user_dir.joinpath('rules') + rules_dir = user_dir.joinpath("rules") if not rules_dir.is_dir(): rules_dir.mkdir(parents=True) self.user_dir = user_dir @@ -77,23 +80,28 @@ def _setup_user_dir(self): def _settings_from_file(self): """Loads settings from file.""" settings = load_source( - 'settings', text_type(self.user_dir.joinpath('settings.py'))) - return {key: getattr(settings, key) - for key in const.DEFAULT_SETTINGS.keys() - if hasattr(settings, key)} + "settings", text_type(self.user_dir.joinpath("settings.py")) + ) + return { + key: getattr(settings, key) + for key in const.DEFAULT_SETTINGS.keys() + if hasattr(settings, key) + } def _rules_from_env(self, val): """Transforms rules list from env-string to python.""" - val = val.split(':') - if 'DEFAULT_RULES' in val: - val = const.DEFAULT_RULES + [rule for rule in val if rule != 'DEFAULT_RULES'] + val = val.split(":") + if "DEFAULT_RULES" in val: + val = const.DEFAULT_RULES + [ + rule for rule in val if rule != "DEFAULT_RULES" + ] return val def _priority_from_env(self, val): """Gets priority pairs from env.""" - for part in val.split(':'): + for part in val.split(":"): try: - rule, priority = part.split('=') + rule, priority = part.split("=") yield rule, int(priority) except ValueError: continue @@ -101,26 +109,37 @@ def _priority_from_env(self, val): def _val_from_env(self, env, attr): """Transforms env-strings to python.""" val = os.environ[env] - if attr in ('rules', 'exclude_rules'): + if attr in ("rules", "exclude_rules"): return self._rules_from_env(val) - elif attr == 'priority': + elif attr == "priority": return dict(self._priority_from_env(val)) - elif attr in ('wait_command', 'history_limit', 'wait_slow_command', - 'num_close_matches'): + elif attr in ( + "wait_command", + "history_limit", + "wait_slow_command", + "num_close_matches", + ): return int(val) - elif attr in ('require_confirmation', 'no_colors', 'debug', - 'alter_history', 'instant_mode'): - return val.lower() == 'true' - elif attr in ('slow_commands', 'excluded_search_path_prefixes'): - return val.split(':') + elif attr in ( + "require_confirmation", + "no_colors", + "debug", + "alter_history", + "instant_mode", + ): + return val.lower() == "true" + elif attr in ("slow_commands", "excluded_search_path_prefixes"): + return val.split(":") else: return val def _settings_from_env(self): """Loads settings from env.""" - return {attr: self._val_from_env(env, attr) - for env, attr in const.ENV_TO_ATTR.items() - if env in os.environ} + return { + attr: self._val_from_env(env, attr) + for env, attr in const.ENV_TO_ATTR.items() + if env in os.environ + } def _settings_from_args(self, args): """Loads settings from args.""" @@ -129,11 +148,11 @@ def _settings_from_args(self, args): from_args = {} if args.yes: - from_args['require_confirmation'] = not args.yes + from_args["require_confirmation"] = not args.yes if args.debug: - from_args['debug'] = args.debug + from_args["debug"] = args.debug if args.repeat: - from_args['repeat'] = args.repeat + from_args["repeat"] = args.repeat return from_args