1
0
mirror of https://github.com/nvbn/thefuck.git synced 2025-01-18 12:06:04 +00:00

fixed import imp crash

This commit is contained in:
909 2024-12-21 11:20:25 +08:00
parent c7e7e1d884
commit f3af4c30da
2 changed files with 235 additions and 147 deletions

View File

@ -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
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)])
@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 @@ class TestCache(object):
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)
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é ô']),
])
@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

View File

@ -14,7 +14,7 @@ try:
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 @@ class Settings(dict):
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 @@ class Settings(dict):
"""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 @@ class Settings(dict):
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 @@ class Settings(dict):
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 @@ class Settings(dict):
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