diff --git a/pre_commit/commands/clean.py b/pre_commit/commands/clean.py index 5f0ba943..e0d307fb 100644 --- a/pre_commit/commands/clean.py +++ b/pre_commit/commands/clean.py @@ -2,11 +2,12 @@ from __future__ import print_function from __future__ import unicode_literals import os.path -import shutil + +from pre_commit.util import rmtree def clean(runner): if os.path.exists(runner.store.directory): - shutil.rmtree(runner.store.directory) + rmtree(runner.store.directory) print('Cleaned {0}.'.format(runner.store.directory)) return 0 diff --git a/pre_commit/five.py b/pre_commit/five.py index a129d1db..647061cf 100644 --- a/pre_commit/five.py +++ b/pre_commit/five.py @@ -6,5 +6,17 @@ PY3 = str is not bytes if PY2: # pragma: no cover (PY2 only) text = unicode # flake8: noqa + + def n(s): + if isinstance(s, bytes): + return s + else: + return s.encode('UTF-8') else: # pragma: no cover (PY3 only) text = str + + def n(s): + if isinstance(s, text): + return s + else: + return s.decode('UTF-8') diff --git a/pre_commit/git.py b/pre_commit/git.py index a2b26527..f16875c0 100644 --- a/pre_commit/git.py +++ b/pre_commit/git.py @@ -16,7 +16,7 @@ logger = logging.getLogger('pre_commit') def get_root(): path = os.getcwd() - while len(path) > 1: + while path != os.path.normpath(os.path.join(path, '../')): if os.path.exists(os.path.join(path, '.git')): return path else: diff --git a/pre_commit/languages/node.py b/pre_commit/languages/node.py index 0a48c5f1..646a8920 100644 --- a/pre_commit/languages/node.py +++ b/pre_commit/languages/node.py @@ -13,7 +13,7 @@ ENVIRONMENT_DIR = 'node_env' class NodeEnv(helpers.Environment): @property def env_prefix(self): - return '. {{prefix}}{0}/bin/activate &&'.format(ENVIRONMENT_DIR) + return ". '{{prefix}}{0}/bin/activate' &&".format(ENVIRONMENT_DIR) @contextlib.contextmanager @@ -37,7 +37,7 @@ def install_environment(repo_cmd_runner, version='default'): repo_cmd_runner.run(cmd) with in_env(repo_cmd_runner) as node_env: - node_env.run('cd {prefix} && npm install -g') + node_env.run("cd '{prefix}' && npm install -g") def run_hook(repo_cmd_runner, hook, file_args): diff --git a/pre_commit/languages/python.py b/pre_commit/languages/python.py index e7ac7787..4423c18b 100644 --- a/pre_commit/languages/python.py +++ b/pre_commit/languages/python.py @@ -1,6 +1,10 @@ from __future__ import unicode_literals import contextlib +import distutils.spawn +import os + +import virtualenv from pre_commit.languages import helpers from pre_commit.util import clean_path_on_failure @@ -12,7 +16,12 @@ ENVIRONMENT_DIR = 'py_env' class PythonEnv(helpers.Environment): @property def env_prefix(self): - return '. {{prefix}}{0}/bin/activate &&'.format(ENVIRONMENT_DIR) + return ". '{{prefix}}{0}activate' &&".format( + virtualenv.path_locations( + ENVIRONMENT_DIR, + )[-1].rstrip(os.sep) + os.sep, + 'activate', + ) @contextlib.contextmanager @@ -20,6 +29,15 @@ def in_env(repo_cmd_runner): yield PythonEnv(repo_cmd_runner) +def norm_version(version): + if os.name == 'nt': # pragma: no cover (windows) + if not distutils.spawn.find_executable(version): + # The default place for python on windows is: + # C:\PythonXX\python.exe + version = r'C:\{0}\python.exe'.format(version.replace('.', '')) + return version + + def install_environment(repo_cmd_runner, version='default'): assert repo_cmd_runner.exists('setup.py') @@ -27,10 +45,10 @@ def install_environment(repo_cmd_runner, version='default'): with clean_path_on_failure(repo_cmd_runner.path(ENVIRONMENT_DIR)): venv_cmd = ['virtualenv', '{{prefix}}{0}'.format(ENVIRONMENT_DIR)] if version != 'default': - venv_cmd.extend(['-p', version]) + venv_cmd.extend(['-p', norm_version(version)]) repo_cmd_runner.run(venv_cmd) with in_env(repo_cmd_runner) as env: - env.run('cd {prefix} && pip install .') + env.run("cd '{prefix}' && pip install .") def run_hook(repo_cmd_runner, hook, file_args): diff --git a/pre_commit/logging_handler.py b/pre_commit/logging_handler.py index c331e396..5dc2d227 100644 --- a/pre_commit/logging_handler.py +++ b/pre_commit/logging_handler.py @@ -31,3 +31,4 @@ class LoggingHandler(logging.Handler): record.getMessage(), ) ) + sys.stdout.flush() diff --git a/pre_commit/make_archives.py b/pre_commit/make_archives.py index 0c447a7e..ff6d3bda 100644 --- a/pre_commit/make_archives.py +++ b/pre_commit/make_archives.py @@ -3,10 +3,11 @@ from __future__ import print_function from __future__ import unicode_literals import os.path -import shutil +from pre_commit import five from pre_commit.util import cmd_output from pre_commit.util import cwd +from pre_commit.util import rmtree from pre_commit.util import tarfile_open from pre_commit.util import tmpdir @@ -50,11 +51,9 @@ def make_archive(name, repo, ref, destdir): # We don't want the '.git' directory # It adds a bunch of size to the archive and we don't use it at # runtime - shutil.rmtree(os.path.join(tempdir, '.git')) + rmtree(os.path.join(tempdir, '.git')) - # XXX: py2.6 derps if filename is unicode while writing - # XXX: str() is used to preserve behavior in py3 - with tarfile_open(str(output_path), 'w|gz') as tf: + with tarfile_open(five.n(output_path), 'w|gz') as tf: tf.add(tempdir, name) return output_path diff --git a/pre_commit/output.py b/pre_commit/output.py index cb8427c4..60c95cab 100644 --- a/pre_commit/output.py +++ b/pre_commit/output.py @@ -8,13 +8,16 @@ from pre_commit import five # TODO: smell: import side-effects -COLS = int( - subprocess.Popen( - ['tput', 'cols'], stdout=subprocess.PIPE, - ).communicate()[0] or - # Default in the case of no terminal - 80 -) +try: + COLS = int( + subprocess.Popen( + ['tput', 'cols'], stdout=subprocess.PIPE, + ).communicate()[0] or + # Default in the case of no terminal + 80 + ) +except OSError: # pragma: no cover (windows) + COLS = 80 def get_hook_message( diff --git a/pre_commit/store.py b/pre_commit/store.py index f004c9be..9d486400 100644 --- a/pre_commit/store.py +++ b/pre_commit/store.py @@ -28,7 +28,7 @@ def _get_default_directory(): """ return os.environ.get( 'PRE_COMMIT_HOME', - os.path.join(os.environ['HOME'], '.pre-commit'), + os.path.join(os.path.expanduser('~'), '.pre-commit'), ) diff --git a/pre_commit/util.py b/pre_commit/util.py index 9f94accc..fd2d0d13 100644 --- a/pre_commit/util.py +++ b/pre_commit/util.py @@ -1,16 +1,20 @@ from __future__ import unicode_literals import contextlib +import errno import functools import os import os.path import shutil +import stat import subprocess import tarfile import tempfile import pkg_resources +from pre_commit import five + @contextlib.contextmanager def cwd(path): @@ -46,7 +50,7 @@ def clean_path_on_failure(path): yield except BaseException: if os.path.exists(path): - shutil.rmtree(path) + rmtree(path) raise @@ -78,7 +82,7 @@ def tmpdir(): try: yield tempdir finally: - shutil.rmtree(tempdir) + rmtree(tempdir) def resource_filename(filename): @@ -135,6 +139,13 @@ def cmd_output(*cmd, **kwargs): if stdin is not None: stdin = stdin.encode('UTF-8') + # py2/py3 on windows are more strict about the types here + cmd = [five.n(arg) for arg in cmd] + kwargs['env'] = dict( + (five.n(key), five.n(value)) + for key, value in kwargs.pop('env', {}).items() + ) or None + popen_kwargs.update(kwargs) proc = __popen(cmd, **popen_kwargs) stdout, stderr = proc.communicate(stdin) @@ -150,3 +161,15 @@ def cmd_output(*cmd, **kwargs): ) return proc.returncode, stdout, stderr + + +def rmtree(path): + """On windows, rmtree fails for readonly dirs.""" + def handle_remove_readonly(func, path, exc): # pragma: no cover (windows) + excvalue = exc[1] + if func in (os.rmdir, os.remove) and excvalue.errno == errno.EACCES: + os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) + func(path) + else: + raise + shutil.rmtree(path, ignore_errors=False, onerror=handle_remove_readonly) diff --git a/testing/resources/system_hook_with_spaces_repo/hooks.yaml b/testing/resources/system_hook_with_spaces_repo/hooks.yaml index dda8fb96..b2c347c1 100644 --- a/testing/resources/system_hook_with_spaces_repo/hooks.yaml +++ b/testing/resources/system_hook_with_spaces_repo/hooks.yaml @@ -1,5 +1,5 @@ - id: system-hook-with-spaces name: System hook with spaces - entry: /usr/bin/python -c 'import sys; print("Hello World")' + entry: bash -c 'echo "Hello World"' language: system files: \.sh$ diff --git a/testing/util.py b/testing/util.py index 2cd1cbc2..d1b8e988 100644 --- a/testing/util.py +++ b/testing/util.py @@ -49,8 +49,27 @@ def is_valid_according_to_schema(obj, schema): return False -def skipif_slowtests_false(func): - return pytest.mark.skipif( - os.environ.get('slowtests') == 'false', - reason='slowtests=false', - )(func) +skipif_slowtests_false = pytest.mark.skipif( + os.environ.get('slowtests') == 'false', + reason='slowtests=false', +) + +xfailif_windows_no_ruby = pytest.mark.xfail( + os.name == 'nt', + reason='Ruby support not yet implemented on windows.', +) + +xfailif_windows_no_node = pytest.mark.xfail( + os.name == 'nt', + reason='Node support not yet implemented on windows.', +) + + +def platform_supports_pcre(): + return cmd_output('grep', '-P', '', os.devnull, retcode=None)[0] == 1 + + +xfailif_no_pcre_support = pytest.mark.xfail( + not platform_supports_pcre(), + reason='grep -P is not supported on this platform', +) diff --git a/tests/commands/clean_test.py b/tests/commands/clean_test.py index 7464f9d7..bdbdc998 100644 --- a/tests/commands/clean_test.py +++ b/tests/commands/clean_test.py @@ -1,9 +1,9 @@ from __future__ import unicode_literals import os.path -import shutil from pre_commit.commands.clean import clean +from pre_commit.util import rmtree def test_clean(runner_with_mocked_store): @@ -14,7 +14,7 @@ def test_clean(runner_with_mocked_store): def test_clean_empty(runner_with_mocked_store): """Make sure clean succeeds when we the directory doesn't exist.""" - shutil.rmtree(runner_with_mocked_store.store.directory) + rmtree(runner_with_mocked_store.store.directory) assert not os.path.exists(runner_with_mocked_store.store.directory) clean(runner_with_mocked_store) assert not os.path.exists(runner_with_mocked_store.store.directory) diff --git a/tests/commands/install_uninstall_test.py b/tests/commands/install_uninstall_test.py index aa2da657..3d3439cf 100644 --- a/tests/commands/install_uninstall_test.py +++ b/tests/commands/install_uninstall_test.py @@ -5,7 +5,6 @@ import io import os import os.path import re -import stat import subprocess import sys @@ -63,8 +62,7 @@ def test_install_pre_commit(tmpdir_factory): pre_push='' ) assert pre_commit_contents == expected_contents - stat_result = os.stat(runner.pre_commit_path) - assert stat_result.st_mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) + assert os.access(runner.pre_commit_path, os.X_OK) ret = install(runner, hook_type='pre-push') assert ret == 0 @@ -120,19 +118,19 @@ def _get_commit_output( # osx does this different :( FILES_CHANGED = ( r'(' - r' 1 file changed, 0 insertions\(\+\), 0 deletions\(-\)\n' + r' 1 file changed, 0 insertions\(\+\), 0 deletions\(-\)\r?\n' r'|' - r' 0 files changed\n' + r' 0 files changed\r?\n' r')' ) NORMAL_PRE_COMMIT_RUN = re.compile( - r'^\[INFO\] Initializing environment for .+\.\n' - r'Bash hook\.+Passed\n' - r'\[master [a-f0-9]{7}\] Commit!\n' + + r'^\[INFO\] Initializing environment for .+\.\r?\n' + r'Bash hook\.+Passed\r?\n' + r'\[master [a-f0-9]{7}\] Commit!\r?\n' + FILES_CHANGED + - r' create mode 100644 foo\n$' + r' create mode 100644 foo\r?\n$' ) @@ -166,7 +164,7 @@ def test_environment_not_sourced(tmpdir_factory): ret, stdout, stderr = cmd_output( 'git', 'commit', '--allow-empty', '-m', 'foo', - env={'HOME': os.environ['HOME']}, + env={'HOME': os.path.expanduser('~')}, retcode=None, ) assert ret == 1 @@ -178,13 +176,13 @@ def test_environment_not_sourced(tmpdir_factory): FAILING_PRE_COMMIT_RUN = re.compile( - r'^\[INFO\] Initializing environment for .+\.\n' - r'Failing hook\.+Failed\n' - r'hookid: failing_hook\n' - r'\n' - r'Fail\n' - r'foo\n' - r'\n$' + r'^\[INFO\] Initializing environment for .+\.\r?\n' + r'Failing hook\.+Failed\r?\n' + r'hookid: failing_hook\r?\n' + r'\r?\n' + r'Fail\r?\n' + r'foo\r?\n' + r'\r?\n$' ) @@ -199,10 +197,10 @@ def test_failing_hooks_returns_nonzero(tmpdir_factory): EXISTING_COMMIT_RUN = re.compile( - r'^legacy hook\n' - r'\[master [a-f0-9]{7}\] Commit!\n' + + r'^legacy hook\r?\n' + r'\[master [a-f0-9]{7}\] Commit!\r?\n' + FILES_CHANGED + - r' create mode 100644 baz\n$' + r' create mode 100644 baz\r?\n$' ) @@ -253,9 +251,9 @@ def test_install_existing_hook_no_overwrite_idempotent(tmpdir_factory): FAIL_OLD_HOOK = re.compile( - r'fail!\n' - r'\[INFO\] Initializing environment for .+\.\n' - r'Bash hook\.+Passed\n' + r'fail!\r?\n' + r'\[INFO\] Initializing environment for .+\.\r?\n' + r'Bash hook\.+Passed\r?\n' ) @@ -363,10 +361,10 @@ def test_uninstall_doesnt_remove_not_our_hooks(tmpdir_factory): PRE_INSTALLED = re.compile( - r'Bash hook\.+Passed\n' - r'\[master [a-f0-9]{7}\] Commit!\n' + + r'Bash hook\.+Passed\r?\n' + r'\[master [a-f0-9]{7}\] Commit!\r?\n' + FILES_CHANGED + - r' create mode 100644 foo\n$' + r' create mode 100644 foo\r?\n$' ) @@ -394,8 +392,10 @@ def test_installed_from_venv(tmpdir_factory): ret, output = _get_commit_output( tmpdir_factory, env_base={ - 'HOME': os.environ['HOME'], + 'HOME': os.path.expanduser('~'), 'TERM': os.environ.get('TERM', ''), + # Windows needs this to import `random` + 'SYSTEMROOT': os.environ.get('SYSTEMROOT', ''), }, ) assert ret == 0 diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py index e67c02bd..a245dfb3 100644 --- a/tests/commands/run_test.py +++ b/tests/commands/run_test.py @@ -275,7 +275,9 @@ def test_stdout_write_bug_py26( ): with cwd(repo_with_failing_hook): # Add bash hook on there again - with io.open('.pre-commit-config.yaml', 'a+') as config_file: + with io.open( + '.pre-commit-config.yaml', 'a+', encoding='UTF-8', + ) as config_file: config_file.write(' args: ["☃"]\n') cmd_output('git', 'add', '.pre-commit-config.yaml') stage_a_file() diff --git a/tests/error_handler_test.py b/tests/error_handler_test.py index 54ae75a4..161b88f8 100644 --- a/tests/error_handler_test.py +++ b/tests/error_handler_test.py @@ -37,13 +37,13 @@ def test_error_handler_fatal_error(mocked_log_and_exit): ) assert re.match( - 'Traceback \(most recent call last\):\n' - ' File ".+/pre_commit/error_handler.py", line \d+, in error_handler\n' - ' yield\n' - ' File ".+/tests/error_handler_test.py", line \d+, ' - 'in test_error_handler_fatal_error\n' - ' raise exc\n' - '(pre_commit\.errors\.)?FatalError: just a test\n', + r'Traceback \(most recent call last\):\n' + r' File ".+pre_commit.error_handler.py", line \d+, in error_handler\n' + r' yield\n' + r' File ".+tests.error_handler_test.py", line \d+, ' + r'in test_error_handler_fatal_error\n' + r' raise exc\n' + r'(pre_commit\.errors\.)?FatalError: just a test\n', mocked_log_and_exit.call_args[0][2], ) @@ -60,13 +60,13 @@ def test_error_handler_uncaught_error(mocked_log_and_exit): mock.ANY, ) assert re.match( - 'Traceback \(most recent call last\):\n' - ' File ".+/pre_commit/error_handler.py", line \d+, in error_handler\n' - ' yield\n' - ' File ".+/tests/error_handler_test.py", line \d+, ' - 'in test_error_handler_uncaught_error\n' - ' raise exc\n' - 'ValueError: another test\n', + r'Traceback \(most recent call last\):\n' + r' File ".+pre_commit.error_handler.py", line \d+, in error_handler\n' + r' yield\n' + r' File ".+tests.error_handler_test.py", line \d+, ' + r'in test_error_handler_uncaught_error\n' + r' raise exc\n' + r'ValueError: another test\n', mocked_log_and_exit.call_args[0][2], ) diff --git a/tests/languages/ruby_test.py b/tests/languages/ruby_test.py index 3ffb4019..9499fcee 100644 --- a/tests/languages/ruby_test.py +++ b/tests/languages/ruby_test.py @@ -3,8 +3,10 @@ from __future__ import unicode_literals import os.path from pre_commit.languages.ruby import _install_rbenv +from testing.util import xfailif_windows_no_ruby +@xfailif_windows_no_ruby def test_install_rbenv(cmd_runner): _install_rbenv(cmd_runner) # Should have created rbenv directory @@ -18,11 +20,12 @@ def test_install_rbenv(cmd_runner): [ 'bash', '-c', - '. {prefix}/rbenv/bin/activate && rbenv --help', + ". '{prefix}rbenv/bin/activate' && rbenv --help", ], ) +@xfailif_windows_no_ruby def test_install_rbenv_with_version(cmd_runner): _install_rbenv(cmd_runner, version='1.9.3p547') @@ -31,6 +34,6 @@ def test_install_rbenv_with_version(cmd_runner): [ 'bash', '-c', - '. {prefix}/rbenv/bin/activate && rbenv install --help', + ". '{prefix}rbenv/bin/activate' && rbenv install --help", ], ) diff --git a/tests/prefixed_command_runner_test.py b/tests/prefixed_command_runner_test.py index a477100b..b3aa6ff6 100644 --- a/tests/prefixed_command_runner_test.py +++ b/tests/prefixed_command_runner_test.py @@ -6,14 +6,22 @@ import subprocess import mock import pytest +from pre_commit import five from pre_commit.prefixed_command_runner import _replace_cmd from pre_commit.prefixed_command_runner import PrefixedCommandRunner from pre_commit.util import CalledProcessError +def norm_slash(input_tup): + return tuple(x.replace('/', os.sep) for x in input_tup) + + def test_CalledProcessError_str(): error = CalledProcessError( - 1, [str('git'), str('status')], 0, (str('stdout'), str('stderr')) + 1, + [five.n('git'), five.n('status')], + 0, + (five.n('stdout'), five.n('stderr')), ) assert str(error) == ( "Command: ['git', 'status']\n" @@ -28,7 +36,7 @@ def test_CalledProcessError_str(): def test_CalledProcessError_str_nooutput(): error = CalledProcessError( - 1, [str('git'), str('status')], 0, (str(''), str('')) + 1, [five.n('git'), five.n('status')], 0, (five.n(''), five.n('')) ) assert str(error) == ( "Command: ['git', 'status']\n" @@ -65,13 +73,15 @@ def test_replace_cmd(input, kwargs, expected_output): @pytest.mark.parametrize(('input', 'expected_prefix'), ( - ('.', './'), - ('foo', 'foo/'), - ('bar/', 'bar/'), - ('foo/bar', 'foo/bar/'), - ('foo/bar/', 'foo/bar/'), + norm_slash(('.', './')), + norm_slash(('foo', 'foo/')), + norm_slash(('bar/', 'bar/')), + norm_slash(('foo/bar', 'foo/bar/')), + norm_slash(('foo/bar/', 'foo/bar/')), )) def test_init_normalizes_path_endings(input, expected_prefix): + input = input.replace('/', os.sep) + expected_prefix = expected_prefix.replace('/', os.sep) instance = PrefixedCommandRunner(input) assert instance.prefix_dir == expected_prefix @@ -82,7 +92,8 @@ def test_run_substitutes_prefix(popen_mock, makedirs_mock): ) ret = instance.run(['{prefix}bar', 'baz'], retcode=None) popen_mock.assert_called_once_with( - ('prefix/bar', 'baz'), + [five.n(os.path.join('prefix', 'bar')), five.n('baz')], + env=None, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, @@ -91,12 +102,12 @@ def test_run_substitutes_prefix(popen_mock, makedirs_mock): PATH_TESTS = ( - ('foo', '', 'foo'), - ('foo', 'bar', 'foo/bar'), - ('foo/bar', '../baz', 'foo/baz'), - ('./', 'bar', 'bar'), - ('./', '', '.'), - ('/tmp/foo', '/tmp/bar', '/tmp/bar'), + norm_slash(('foo', '', 'foo')), + norm_slash(('foo', 'bar', 'foo/bar')), + norm_slash(('foo/bar', '../baz', 'foo/baz')), + norm_slash(('./', 'bar', 'bar')), + norm_slash(('./', '', '.')), + norm_slash(('/tmp/foo', '/tmp/bar', '/tmp/bar')), ) @@ -110,7 +121,7 @@ def test_path(prefix, path_end, expected_output): def test_path_multiple_args(): instance = PrefixedCommandRunner('foo') ret = instance.path('bar', 'baz') - assert ret == 'foo/bar/baz' + assert ret == os.path.join('foo', 'bar', 'baz') @pytest.mark.parametrize( @@ -133,12 +144,13 @@ def test_from_command_runner_preserves_popen(popen_mock, makedirs_mock): second = PrefixedCommandRunner.from_command_runner(first, 'bar') second.run(['foo/bar/baz'], retcode=None) popen_mock.assert_called_once_with( - ('foo/bar/baz',), + [five.n('foo/bar/baz')], + env=None, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) - makedirs_mock.assert_called_once_with('foo/bar/') + makedirs_mock.assert_called_once_with(os.path.join('foo', 'bar') + os.sep) def test_create_path_if_not_exists(in_tmpdir): diff --git a/tests/repository_test.py b/tests/repository_test.py index 2b2fcef9..cde6a762 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -19,6 +19,9 @@ from testing.fixtures import git_dir from testing.fixtures import make_config_from_repo from testing.fixtures import make_repo from testing.util import skipif_slowtests_false +from testing.util import xfailif_no_pcre_support +from testing.util import xfailif_windows_no_node +from testing.util import xfailif_windows_no_ruby def _test_hook_repo( @@ -39,14 +42,14 @@ def _test_hook_repo( ][0] ret = repo.run_hook(hook_dict, args) assert ret[0] == expected_return_code - assert ret[1] == expected + assert ret[1].replace('\r\n', '\n') == expected @pytest.mark.integration def test_python_hook(tmpdir_factory, store): _test_hook_repo( tmpdir_factory, store, 'python_hooks_repo', - 'foo', ['/dev/null'], "['/dev/null']\nHello World\n", + 'foo', [os.devnull], "['{0}']\nHello World\n".format(os.devnull), ) @@ -71,11 +74,14 @@ def test_python_hook_args_with_spaces(tmpdir_factory, store): def test_versioned_python_hook(tmpdir_factory, store): _test_hook_repo( tmpdir_factory, store, 'python3_hooks_repo', - 'python3-hook', ['/dev/null'], "3.3\n['/dev/null']\nHello World\n", + 'python3-hook', + [os.devnull], + "3.3\n['{0}']\nHello World\n".format(os.devnull), ) @skipif_slowtests_false +@xfailif_windows_no_node @pytest.mark.integration def test_run_a_node_hook(tmpdir_factory, store): _test_hook_repo( @@ -85,6 +91,7 @@ def test_run_a_node_hook(tmpdir_factory, store): @skipif_slowtests_false +@xfailif_windows_no_node @pytest.mark.integration def test_run_versioned_node_hook(tmpdir_factory, store): _test_hook_repo( @@ -94,6 +101,7 @@ def test_run_versioned_node_hook(tmpdir_factory, store): @skipif_slowtests_false +@xfailif_windows_no_ruby @pytest.mark.integration def test_run_a_ruby_hook(tmpdir_factory, store): _test_hook_repo( @@ -103,6 +111,7 @@ def test_run_a_ruby_hook(tmpdir_factory, store): @skipif_slowtests_false +@xfailif_windows_no_ruby @pytest.mark.integration def test_run_versioned_ruby_hook(tmpdir_factory, store): _test_hook_repo( @@ -139,6 +148,7 @@ def test_run_hook_with_spaced_args(tmpdir_factory, store): ) +@xfailif_no_pcre_support @pytest.mark.integration def test_pcre_hook_no_match(tmpdir_factory, store): path = git_dir(tmpdir_factory) @@ -160,6 +170,7 @@ def test_pcre_hook_no_match(tmpdir_factory, store): ) +@xfailif_no_pcre_support @pytest.mark.integration def test_pcre_hook_matching(tmpdir_factory, store): path = git_dir(tmpdir_factory) @@ -183,6 +194,7 @@ def test_pcre_hook_matching(tmpdir_factory, store): ) +@xfailif_no_pcre_support @pytest.mark.integration def test_pcre_many_files(tmpdir_factory, store): # This is intended to simulate lots of passing files and one failing file @@ -202,6 +214,14 @@ def test_pcre_many_files(tmpdir_factory, store): ) +def _norm_pwd(path): + # Under windows bash's temp and windows temp is different. + # This normalizes to the bash /tmp + return cmd_output( + 'bash', '-c', "cd '{0}' && pwd".format(path), + )[1].strip() + + @pytest.mark.integration def test_cwd_of_hook(tmpdir_factory, store): # Note: this doubles as a test for `system` hooks @@ -209,7 +229,7 @@ def test_cwd_of_hook(tmpdir_factory, store): with cwd(path): _test_hook_repo( tmpdir_factory, store, 'prints_cwd_repo', - 'prints_cwd', ['-L'], path + '\n', + 'prints_cwd', ['-L'], _norm_pwd(path) + '\n', ) @@ -288,7 +308,9 @@ def test_control_c_control_c_on_install(tmpdir_factory, store): with mock.patch.object( PythonEnv, 'run', side_effect=MyKeyboardInterrupt, ): - with mock.patch.object(shutil, 'rmtree', MyKeyboardInterrupt): + with mock.patch.object( + shutil, 'rmtree', side_effect=MyKeyboardInterrupt, + ): repo.run_hook(hook, []) # Should have made an environment, however this environment is broken! @@ -347,7 +369,7 @@ def test_tags_on_repositories(in_tmpdir, tmpdir_factory, store): ) ret = repo_1.run_hook(repo_1.hooks[0][1], ['-L']) assert ret[0] == 0 - assert ret[1].strip() == in_tmpdir + assert ret[1].strip() == _norm_pwd(in_tmpdir) repo_2 = Repository.create( make_config_from_repo(git_dir_2, sha=tag), store, diff --git a/tests/runner_test.py b/tests/runner_test.py index 249cc2c4..b1a5d5de 100644 --- a/tests/runner_test.py +++ b/tests/runner_test.py @@ -41,8 +41,8 @@ def test_create_changes_to_git_root(tmpdir_factory): def test_config_file_path(): - runner = Runner('foo/bar') - expected_path = os.path.join('foo/bar', C.CONFIG_FILE) + runner = Runner(os.path.join('foo', 'bar')) + expected_path = os.path.join('foo', 'bar', C.CONFIG_FILE) assert runner.config_file_path == expected_path @@ -53,18 +53,18 @@ def test_repositories(tmpdir_factory, mock_out_store_directory): def test_pre_commit_path(): - runner = Runner('foo/bar') - expected_path = os.path.join('foo/bar', '.git/hooks/pre-commit') + runner = Runner(os.path.join('foo', 'bar')) + expected_path = os.path.join('foo', 'bar', '.git', 'hooks', 'pre-commit') assert runner.pre_commit_path == expected_path def test_pre_push_path(): - runner = Runner('foo/bar') - expected_path = os.path.join('foo/bar', '.git/hooks/pre-push') + runner = Runner(os.path.join('foo', 'bar')) + expected_path = os.path.join('foo', 'bar', '.git', 'hooks', 'pre-push') assert runner.pre_push_path == expected_path def test_cmd_runner(mock_out_store_directory): - runner = Runner('foo/bar') + runner = Runner(os.path.join('foo', 'bar')) ret = runner.cmd_runner - assert ret.prefix_dir == os.path.join(mock_out_store_directory) + '/' + assert ret.prefix_dir == os.path.join(mock_out_store_directory) + os.sep diff --git a/tests/store_test.py b/tests/store_test.py index 04107731..7ea6b2e8 100644 --- a/tests/store_test.py +++ b/tests/store_test.py @@ -4,7 +4,6 @@ from __future__ import unicode_literals import io import os import os.path -import shutil import sqlite3 import mock @@ -15,6 +14,7 @@ from pre_commit.store import _get_default_directory from pre_commit.store import Store from pre_commit.util import cmd_output from pre_commit.util import cwd +from pre_commit.util import rmtree from testing.fixtures import git_dir from testing.util import get_head_sha @@ -30,7 +30,7 @@ def test_our_session_fixture_works(): def test_get_default_directory_defaults_to_home(): # Not we use the module level one which is not mocked ret = _get_default_directory() - assert ret == os.path.join(os.environ['HOME'], '.pre-commit') + assert ret == os.path.join(os.path.expanduser('~'), '.pre-commit') def test_uses_environment_variable_when_present(): @@ -61,7 +61,7 @@ def test_store_require_created_does_not_create_twice(store): store.require_created() # We intentionally delete the directory here so we can figure out if it # calls it again. - shutil.rmtree(store.directory) + rmtree(store.directory) assert not os.path.exists(store.directory) # Call require_created, this should not trigger a call to create store.require_created()