# -*- coding: utf-8 -*- 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.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"}, ), ], ) def test_default_settings(settings, override, old, new): settings.clear() settings.update(old) default_settings(override)(lambda _: _)(None) assert settings == new def test_memoize(): fn = Mock(__name__="fn") memoized = memoize(fn) memoized() memoized() fn.assert_called_once_with() @pytest.mark.usefixtures("no_memoize") def test_no_memoize(): fn = Mock(__name__="fn") memoized = memoize(fn) memoized() memoized() assert fn.call_count == 2 class TestGetClosest(object): def test_when_can_match(self): assert "branch" == get_closest("brnch", ["branch", "status"]) def test_when_cant_match(self): assert "status" == get_closest("st", ["status", "reset"]) def test_without_fallback(self): assert get_closest("st", ["status", "reset"], fallback_to_first=False) is None class TestGetCloseMatches(object): @patch("thefuck.utils.difflib_get_close_matches") def test_call_with_n(self, difflib_mock): get_close_matches("", [], 1) assert difflib_mock.call_args[0][2] == 1 @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") @pytest.fixture def get_aliases(mocker): mocker.patch( "thefuck.shells.shell.get_aliases", return_value=["vim", "apt-get", "fsck", "fuck"], ) @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 @pytest.fixture def os_environ_pathsep(monkeypatch, path, 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", ";")], ) def test_get_all_executables_pathsep(path, pathsep): 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"), ], ) 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: 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"), ], ) 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", ], ), ], ) 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), ], ) 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), ], ) def test_for_app(script, names, result): @for_app(*names) def match(command): return True assert match(Command(script, "")) == result class TestCache(object): @pytest.fixture def shelve(self, mocker): value = {} class _Shelve(object): def __init__(self, path): pass def __setitem__(self, k, v): value[k] = v def __getitem__(self, k): return value[k] def get(self, k, v=None): return value.get(k, v) def close(self): return 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) _cache._init_db() @pytest.fixture(autouse=True) def mtime(self, mocker): mocker.patch("thefuck.utils.os.path.getmtime", return_value=0) @pytest.fixture def fn(self): @cache("~/.bashrc") def fn(): return "test" return fn @pytest.fixture def key(self, monkeypatch): 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"}} def test_with_filled_cache(self, shelve, fn, key): cache_value = {key: {"etag": "0", "value": "new-value"}} shelve.update(cache_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"}} class TestGetValidHistoryWithoutCurrent(object): @pytest.fixture(autouse=True) def fail_on_warning(self): warnings.simplefilter("error") yield warnings.resetwarnings() @pytest.fixture(autouse=True) def history(self, mocker): 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", "café ô", ] return mock @pytest.fixture(autouse=True) def alias(self, mocker): 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é"]: 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"]), ("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, "") assert get_valid_history_without_current(command) == result