diff --git a/tests/test_conf.py b/tests/test_conf.py index 608a49eb..c5a8333b 100644 --- a/tests/test_conf.py +++ b/tests/test_conf.py @@ -1,89 +1,100 @@ +import pytest import six -from mock import patch, Mock +from mock import Mock from thefuck import conf from tests.utils import Rule -def test_default(): - assert Rule('test', enabled_by_default=True) in conf.DEFAULT_RULES - assert Rule('test', enabled_by_default=False) not in conf.DEFAULT_RULES - assert Rule('test', enabled_by_default=False) in (conf.DEFAULT_RULES + ['test']) +@pytest.mark.parametrize('enabled, rules, result', [ + (True, conf.DEFAULT_RULES, True), + (False, conf.DEFAULT_RULES, False), + (False, conf.DEFAULT_RULES + ['test'], True)]) +def test_default(enabled, rules, result): + assert (Rule('test', enabled_by_default=enabled) in rules) == result -def test_settings_defaults(): - with patch('thefuck.conf.load_source', return_value=object()), \ - patch('thefuck.conf.os.environ', new_callable=lambda: {}): - for key, val in conf.DEFAULT_SETTINGS.items(): - assert getattr(conf.get_settings(Mock()), key) == val +@pytest.fixture +def load_source(monkeypatch): + mock = Mock() + monkeypatch.setattr('thefuck.conf.load_source', mock) + return mock -def test_settings_from_file(): - with patch('thefuck.conf.load_source', return_value=Mock(rules=['test'], - wait_command=10, - require_confirmation=True, - no_colors=True)), \ - patch('thefuck.conf.os.environ', new_callable=lambda: {}): +@pytest.fixture +def environ(monkeypatch): + data = {} + monkeypatch.setattr('thefuck.conf.os.environ', data) + return data + + +@pytest.mark.usefixture('environ') +def test_settings_defaults(load_source): + load_source.return_value = object() + for key, val in conf.DEFAULT_SETTINGS.items(): + assert getattr(conf.get_settings(Mock()), key) == val + + +@pytest.mark.usefixture('environ') +class TestSettingsFromFile(object): + def test_from_file(self, load_source): + load_source.return_value = Mock(rules=['test'], + wait_command=10, + require_confirmation=True, + no_colors=True) settings = conf.get_settings(Mock()) assert settings.rules == ['test'] assert settings.wait_command == 10 assert settings.require_confirmation is True assert settings.no_colors is True - -def test_settings_from_file_with_DEFAULT(): - with patch('thefuck.conf.load_source', return_value=Mock(rules=conf.DEFAULT_RULES + ['test'], - wait_command=10, - require_confirmation=True, - no_colors=True)), \ - patch('thefuck.conf.os.environ', new_callable=lambda: {}): + def test_from_file_with_DEFAULT(self, load_source): + load_source.return_value = Mock(rules=conf.DEFAULT_RULES + ['test'], + wait_command=10, + require_confirmation=True, + no_colors=True) settings = conf.get_settings(Mock()) assert settings.rules == conf.DEFAULT_RULES + ['test'] -def test_settings_from_env(): - with patch('thefuck.conf.load_source', return_value=Mock(rules=['test'], - wait_command=10)), \ - patch('thefuck.conf.os.environ', - new_callable=lambda: {'THEFUCK_RULES': 'bash:lisp', - 'THEFUCK_WAIT_COMMAND': '55', - 'THEFUCK_REQUIRE_CONFIRMATION': 'true', - 'THEFUCK_NO_COLORS': 'false'}): +@pytest.mark.usefixture('load_source') +class TestSettingsFromEnv(object): + def test_from_env(self, environ): + environ.update({'THEFUCK_RULES': 'bash:lisp', + 'THEFUCK_WAIT_COMMAND': '55', + 'THEFUCK_REQUIRE_CONFIRMATION': 'true', + 'THEFUCK_NO_COLORS': 'false'}) settings = conf.get_settings(Mock()) assert settings.rules == ['bash', 'lisp'] assert settings.wait_command == 55 assert settings.require_confirmation is True assert settings.no_colors is False - -def test_settings_from_env_with_DEFAULT(): - with patch('thefuck.conf.load_source', return_value=Mock()), \ - patch('thefuck.conf.os.environ', new_callable=lambda: {'THEFUCK_RULES': 'DEFAULT_RULES:bash:lisp'}): + def test_from_env_with_DEFAULT(self, environ): + environ.update({'THEFUCK_RULES': 'DEFAULT_RULES:bash:lisp'}) settings = conf.get_settings(Mock()) assert settings.rules == conf.DEFAULT_RULES + ['bash', 'lisp'] -def test_initialize_settings_file_ignore_if_exists(): - settings_path_mock = Mock(is_file=Mock(return_value=True), open=Mock()) - user_dir_mock = Mock(joinpath=Mock(return_value=settings_path_mock)) - conf.initialize_settings_file(user_dir_mock) - assert settings_path_mock.is_file.call_count == 1 - assert not settings_path_mock.open.called +class TestInitializeSettingsFile(object): + def test_ignore_if_exists(self): + settings_path_mock = Mock(is_file=Mock(return_value=True), open=Mock()) + user_dir_mock = Mock(joinpath=Mock(return_value=settings_path_mock)) + conf.initialize_settings_file(user_dir_mock) + assert settings_path_mock.is_file.call_count == 1 + assert not settings_path_mock.open.called - -def test_initialize_settings_file_create_if_exists_not(): - settings_file = six.StringIO() - settings_path_mock = Mock( - is_file=Mock(return_value=False), - open=Mock(return_value=Mock( - __exit__=lambda *args: None, __enter__=lambda *args: settings_file - )), - ) - user_dir_mock = Mock(joinpath=Mock(return_value=settings_path_mock)) - conf.initialize_settings_file(user_dir_mock) - settings_file_contents = settings_file.getvalue() - assert settings_path_mock.is_file.call_count == 1 - assert settings_path_mock.open.call_count == 1 - assert conf.SETTINGS_HEADER in settings_file_contents - for setting in conf.DEFAULT_SETTINGS.items(): - assert '# {} = {}\n'.format(*setting) in settings_file_contents - settings_file.close() + def test_create_if_doesnt_exists(self): + settings_file = six.StringIO() + settings_path_mock = Mock( + is_file=Mock(return_value=False), + open=Mock(return_value=Mock( + __exit__=lambda *args: None, __enter__=lambda *args: settings_file))) + user_dir_mock = Mock(joinpath=Mock(return_value=settings_path_mock)) + conf.initialize_settings_file(user_dir_mock) + settings_file_contents = settings_file.getvalue() + assert settings_path_mock.is_file.call_count == 1 + assert settings_path_mock.open.call_count == 1 + assert conf.SETTINGS_HEADER in settings_file_contents + for setting in conf.DEFAULT_SETTINGS.items(): + assert '# {} = {}\n'.format(*setting) in settings_file_contents + settings_file.close() diff --git a/tests/test_history.py b/tests/test_history.py index 37687608..5e76c60a 100644 --- a/tests/test_history.py +++ b/tests/test_history.py @@ -1,32 +1,30 @@ import pytest -from mock import patch, Mock +from mock import Mock from thefuck.history import History -@pytest.fixture -def process(monkeypatch): - Process = Mock() - Process.return_value.parent.return_value.pid = 1 - monkeypatch.setattr('thefuck.history.Process', Process) - - -@pytest.fixture -def db(monkeypatch): - class DBMock(dict): - def __init__(self): - super(DBMock, self).__init__() - self.sync = Mock() - - def __call__(self, *args, **kwargs): - return self - - db = DBMock() - monkeypatch.setattr('thefuck.history.shelve.open', db) - return db - - -@pytest.mark.usefixtures('process') class TestHistory(object): + @pytest.fixture(autouse=True) + def process(self, monkeypatch): + Process = Mock() + Process.return_value.parent.return_value.pid = 1 + monkeypatch.setattr('thefuck.history.Process', Process) + return Process + + @pytest.fixture(autouse=True) + def db(self, monkeypatch): + class DBMock(dict): + def __init__(self): + super(DBMock, self).__init__() + self.sync = Mock() + + def __call__(self, *args, **kwargs): + return self + + db = DBMock() + monkeypatch.setattr('thefuck.history.shelve.open', db) + return db + def test_set(self, db): history = History() history.update(last_script='ls', @@ -39,6 +37,6 @@ class TestHistory(object): db['1-last_script'] = 'cd ..' assert history.last_script == 'cd ..' - def test_get_without_value(self, db): + def test_get_without_value(self): history = History() assert history.last_script is None diff --git a/tests/test_main.py b/tests/test_main.py index bb3710e2..8e730f08 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,51 +1,56 @@ +import pytest from subprocess import PIPE from pathlib import PosixPath, Path -from mock import patch, Mock +from mock import Mock from thefuck import main, conf, types from tests.utils import Rule, Command -def test_load_rule(): +def test_load_rule(monkeypatch): match = object() get_new_command = object() - with patch('thefuck.main.load_source', - return_value=Mock( - match=match, - get_new_command=get_new_command, - enabled_by_default=True)) as load_source: - assert main.load_rule(Path('/rules/bash.py')) \ - == Rule('bash', match, get_new_command) - load_source.assert_called_once_with('bash', '/rules/bash.py') + load_source = Mock() + load_source.return_value = Mock(match=match, + get_new_command=get_new_command, + enabled_by_default=True) + monkeypatch.setattr('thefuck.main.load_source', load_source) + assert main.load_rule(Path('/rules/bash.py')) \ + == Rule('bash', match, get_new_command) + load_source.assert_called_once_with('bash', '/rules/bash.py') -def test_get_rules(): - with patch('thefuck.main.Path.glob') as glob, \ - patch('thefuck.main.load_source', - lambda x, _: Mock(match=x, get_new_command=x, - enabled_by_default=True)): - glob.return_value = [PosixPath('bash.py'), PosixPath('lisp.py')] - assert list(main.get_rules( - Path('~'), - Mock(rules=conf.DEFAULT_RULES))) \ - == [Rule('bash', 'bash', 'bash'), - Rule('lisp', 'lisp', 'lisp'), - Rule('bash', 'bash', 'bash'), - Rule('lisp', 'lisp', 'lisp')] - assert list(main.get_rules( - Path('~'), - Mock(rules=types.RulesNamesList(['bash'])))) \ - == [Rule('bash', 'bash', 'bash'), - Rule('bash', 'bash', 'bash')] +@pytest.mark.parametrize('conf_rules, rules', [ + (conf.DEFAULT_RULES, [Rule('bash', 'bash', 'bash'), + Rule('lisp', 'lisp', 'lisp'), + Rule('bash', 'bash', 'bash'), + Rule('lisp', 'lisp', 'lisp')]), + (types.RulesNamesList(['bash']), [Rule('bash', 'bash', 'bash'), + Rule('bash', 'bash', 'bash')])]) +def test_get_rules(monkeypatch, conf_rules, rules): + monkeypatch.setattr( + 'thefuck.main.Path.glob', + lambda *_: [PosixPath('bash.py'), PosixPath('lisp.py')]) + monkeypatch.setattr('thefuck.main.load_source', + lambda x, _: Mock(match=x, get_new_command=x, + enabled_by_default=True)) + assert list(main.get_rules(Path('~'), Mock(rules=conf_rules))) == rules -def test_get_command(): - with patch('thefuck.main.Popen') as Popen, \ - patch('thefuck.main.os.environ', - new_callable=lambda: {}), \ - patch('thefuck.main.wait_output', - return_value=True): +class TestGetCommand(object): + @pytest.fixture(autouse=True) + def Popen(self, monkeypatch): + Popen = Mock() Popen.return_value.stdout.read.return_value = b'stdout' Popen.return_value.stderr.read.return_value = b'stderr' + monkeypatch.setattr('thefuck.main.Popen', Popen) + return Popen + + @pytest.fixture(autouse=True) + def prepare(self, monkeypatch): + monkeypatch.setattr('thefuck.main.os.environ', {}) + monkeypatch.setattr('thefuck.main.wait_output', lambda *_: True) + + def test_get_command_calls(self, Popen): assert main.get_command(Mock(), Mock(), ['thefuck', 'apt-get', 'search', 'vim']) \ == Command('apt-get search vim', 'stdout', 'stderr') @@ -54,81 +59,113 @@ def test_get_command(): stdout=PIPE, stderr=PIPE, env={'LANG': 'C'}) - assert main.get_command(Mock(), Mock(), ['']) is None - # When command is `fuck`: - assert main.get_command( - Mock(), - Mock(last_script='ls', last_fixed_script='ls -la'), - ['thefuck', 'fuck']).script == 'ls -la' - # When command equals to last command: - assert main.get_command( - Mock(), - Mock(last_script='ls', last_fixed_script='ls -la'), - ['thefuck', 'ls']).script == 'ls -la' - # When last command is `fuck` and no last fixed script: - assert main.get_command( - Mock(), - Mock(last_script='ls', last_fixed_script=''), - ['thefuck', 'ls']).script == 'ls' + + @pytest.mark.parametrize('history, args, result', [ + (Mock(), [''], None), + (Mock(last_script='ls', last_fixed_script='ls -la'), + ['thefuck', 'fuck'], 'ls -la'), + (Mock(last_script='ls', last_fixed_script='ls -la'), + ['thefuck', 'ls'], 'ls -la'), + (Mock(last_script='ls', last_fixed_script=''), + ['thefuck', 'ls'], 'ls'), + (Mock(last_script='ls', last_fixed_script=''), + ['thefuck', 'fuck'], 'ls')]) + def test_get_command_script(self, history, args, result): + if result: + assert main.get_command(Mock(), history, args).script == result + else: + assert main.get_command(Mock(), history, args) is None -def test_get_matched_rule(capsys): - rules = [Rule('', lambda x, _: x.script == 'cd ..'), - Rule('', lambda *_: False), - Rule('rule', Mock(side_effect=OSError('Denied')))] - assert main.get_matched_rule(Command('ls'), - rules, Mock(no_colors=True)) is None - assert main.get_matched_rule(Command('cd ..'), - rules, Mock(no_colors=True)) == rules[0] - assert capsys.readouterr()[1].split('\n')[0] \ - == '[WARN] Rule rule:' +class TestGetMatchedRule(object): + @pytest.fixture + def rules(self): + return [Rule('', lambda x, _: x.script == 'cd ..'), + Rule('', lambda *_: False), + Rule('rule', Mock(side_effect=OSError('Denied')))] + + def test_no_match(self): + assert main.get_matched_rule( + Command('ls'), [Rule('', lambda *_: False)], + Mock(no_colors=True)) is None + + def test_match(self): + rule = Rule('', lambda x, _: x.script == 'cd ..') + assert main.get_matched_rule( + Command('cd ..'), [rule], Mock(no_colors=True)) == rule + + def test_when_rule_failed(self, capsys): + main.get_matched_rule( + Command('ls'), [Rule('test', Mock(side_effect=OSError('Denied')))], + Mock(no_colors=True)) + assert capsys.readouterr()[1].split('\n')[0] == '[WARN] Rule test:' -def test_run_rule(capsys): - with patch('thefuck.main.confirm', return_value=True): +class TestRunRule(object): + @pytest.fixture(autouse=True) + def confirm(self, monkeypatch): + mock = Mock(return_value=True) + monkeypatch.setattr('thefuck.main.confirm', mock) + return mock + + def test_run_rule(self, capsys): main.run_rule(Rule(get_new_command=lambda *_: 'new-command'), Command(), Mock(), None) assert capsys.readouterr() == ('new-command\n', '') - # With side effect: + + def test_run_rule_with_side_effect(self, capsys): side_effect = Mock() settings = Mock() - command = Mock(script='ls') - history = Mock() + command = Command() main.run_rule(Rule(get_new_command=lambda *_: 'new-command', side_effect=side_effect), - command, history, settings) + command, Mock(), settings) assert capsys.readouterr() == ('new-command\n', '') side_effect.assert_called_once_with(command, settings) - # Ensure that history updated: + + def test_hisotry_updated(self): + history = Mock() + main.run_rule(Rule(get_new_command=lambda *_: 'ls -lah'), + Command('ls'), history, None) history.update.assert_called_once_with(last_script='ls', - last_fixed_script='new-command') - with patch('thefuck.main.confirm', return_value=False): + last_fixed_script='ls -lah') + + def test_when_not_comfirmed(self, capsys, confirm): + confirm.return_value = False main.run_rule(Rule(get_new_command=lambda *_: 'new-command'), Command(), Mock(), None) assert capsys.readouterr() == ('', '') -def test_confirm(capsys): - # When confirmation not required: - assert main.confirm('command', None, Mock(require_confirmation=False)) - assert capsys.readouterr() == ('', 'command\n') - # With side effect and without confirmation: - assert main.confirm('command', Mock(), Mock(require_confirmation=False)) - assert capsys.readouterr() == ('', 'command*\n') - # When confirmation required and confirmed: - with patch('thefuck.main.sys.stdin.read', return_value='\n'): - assert main.confirm( - 'command', None, Mock(require_confirmation=True, - no_colors=True)) +class TestConfirm(object): + @pytest.fixture + def stdin(self, monkeypatch): + mock = Mock(return_value='\n') + monkeypatch.setattr('sys.stdin.read', mock) + return mock + + def test_when_not_required(self, capsys): + assert main.confirm('command', None, Mock(require_confirmation=False)) + assert capsys.readouterr() == ('', 'command\n') + + def test_with_side_effect_and_without_confirmation(self, capsys): + assert main.confirm('command', Mock(), Mock(require_confirmation=False)) + assert capsys.readouterr() == ('', 'command*\n') + + # `stdin` fixture should be applied after `capsys` + def test_when_confirmation_required_and_confirmed(self, capsys, stdin): + assert main.confirm('command', None, Mock(require_confirmation=True, + no_colors=True)) assert capsys.readouterr() == ('', 'command [enter/ctrl+c]') - # With side effect: - assert main.confirm( - 'command', Mock(), Mock(require_confirmation=True, - no_colors=True)) + + # `stdin` fixture should be applied after `capsys` + def test_when_confirmation_required_and_confirmed_with_side_effect(self, capsys, stdin): + assert main.confirm('command', Mock(), Mock(require_confirmation=True, + no_colors=True)) assert capsys.readouterr() == ('', 'command* [enter/ctrl+c]') - # When confirmation required and ctrl+c: - with patch('thefuck.main.sys.stdin.read', side_effect=KeyboardInterrupt): - assert not main.confirm('command', None, - Mock(require_confirmation=True, - no_colors=True)) + + def test_when_confirmation_required_and_aborted(self, capsys, stdin): + stdin.side_effect = KeyboardInterrupt + assert not main.confirm('command', None, Mock(require_confirmation=True, + no_colors=True)) assert capsys.readouterr() == ('', 'command [enter/ctrl+c]Aborted\n') diff --git a/tests/test_utils.py b/tests/test_utils.py index 08766a9f..24d6b196 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,26 +1,26 @@ +import pytest from mock import Mock from thefuck.utils import sudo_support, wrap_settings from thefuck.types import Settings from tests.utils import Command -def test_wrap_settings(): +@pytest.mark.parametrize('override, old, new', [ + ({'key': 'val'}, {}, {'key': 'val'}), + ({'key': 'new-val'}, {'key': 'val'}, {'key': 'new-val'})]) +def test_wrap_settings(override, old, new): fn = lambda _, settings: settings - assert wrap_settings({'key': 'val'})(fn)(None, Settings({})) \ - == {'key': 'val'} - assert wrap_settings({'key': 'new-val'})(fn)( - None, Settings({'key': 'val'})) == {'key': 'new-val'} + assert wrap_settings(override)(fn)(None, Settings(old)) == new -def test_sudo_support(): - fn = Mock(return_value=True, __name__='') - assert sudo_support(fn)(Command('sudo ls'), None) - fn.assert_called_once_with(Command('ls'), None) - - fn.return_value = False - assert not sudo_support(fn)(Command('sudo ls'), None) - - fn.return_value = 'pwd' - assert sudo_support(fn)(Command('sudo ls'), None) == 'sudo pwd' - - assert sudo_support(fn)(Command('ls'), None) == 'pwd' +@pytest.mark.parametrize('return_value, command, called, result', [ + ('ls -lah', 'sudo ls', 'ls', 'sudo ls -lah'), + ('ls -lah', 'ls', 'ls', 'ls -lah'), + (True, 'sudo ls', 'ls', True), + (True, 'ls', 'ls', True), + (False, 'sudo ls', 'ls', False), + (False, 'ls', 'ls', False)]) +def test_sudo_support(return_value, command, called, result): + fn = Mock(return_value=return_value, __name__='') + assert sudo_support(fn)(Command(command), None) == result + fn.assert_called_once_with(Command(called), None)