Skip to content

Commit cee834b

Browse files
committed
better error handling when Store is readonly
1 parent 0e851bd commit cee834b

File tree

2 files changed

+39
-3
lines changed

2 files changed

+39
-3
lines changed

pre_commit/error_handler.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,17 @@ class FatalError(RuntimeError):
1818
def _log_and_exit(msg: str, exc: BaseException, formatted: str) -> None:
1919
error_msg = f'{msg}: {type(exc).__name__}: '.encode() + force_bytes(exc)
2020
output.write_line_b(error_msg)
21-
log_path = os.path.join(Store().directory, 'pre-commit.log')
22-
output.write_line(f'Check the log at {log_path}')
2321

24-
with open(log_path, 'wb') as log:
22+
storedir = Store().directory
23+
log_path = os.path.join(storedir, 'pre-commit.log')
24+
with contextlib.ExitStack() as ctx:
25+
if os.access(storedir, os.W_OK):
26+
output.write_line(f'Check the log at {log_path}')
27+
log = ctx.enter_context(open(log_path, 'wb'))
28+
else: # pragma: win32 no cover
29+
output.write_line(f'Failed to write to log at {log_path}')
30+
log = sys.stdout.buffer
31+
2532
_log_line = functools.partial(output.write_line, stream=log)
2633
_log_line_b = functools.partial(output.write_line_b, stream=log)
2734

tests/error_handler_test.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import os.path
22
import re
3+
import stat
34
import sys
45
from unittest import mock
56

67
import pytest
78

89
from pre_commit import error_handler
10+
from pre_commit.store import Store
911
from pre_commit.util import CalledProcessError
1012
from testing.util import cmd_output_mocked_pre_commit_home
13+
from testing.util import xfailif_windows
1114

1215

1316
@pytest.fixture
@@ -168,3 +171,29 @@ def test_error_handler_no_tty(tempdir_factory):
168171
out_lines = out.splitlines()
169172
assert out_lines[-2] == 'An unexpected error has occurred: ValueError: ☃'
170173
assert out_lines[-1] == f'Check the log at {log_file}'
174+
175+
176+
@xfailif_windows # pragma: win32 no cover
177+
def test_error_handler_read_only_filesystem(mock_store_dir, cap_out, capsys):
178+
# a better scenario would be if even the Store crash would be handled
179+
# but realistically we're only targetting systems where the Store has
180+
# already been set up
181+
Store()
182+
183+
write = (stat.S_IWGRP | stat.S_IWOTH | stat.S_IWUSR)
184+
os.chmod(mock_store_dir, os.stat(mock_store_dir).st_mode & ~write)
185+
186+
with pytest.raises(SystemExit):
187+
with error_handler.error_handler():
188+
raise ValueError('ohai')
189+
190+
output = cap_out.get()
191+
assert output.startswith(
192+
'An unexpected error has occurred: ValueError: ohai\n'
193+
'Failed to write to log at ',
194+
)
195+
196+
# our cap_out mock is imperfect so the rest of the output goes to capsys
197+
out, _ = capsys.readouterr()
198+
# the things that normally go to the log file will end up here
199+
assert '### version information' in out

0 commit comments

Comments
 (0)