Skip to content

Commit a8ab85d

Browse files
authored
[mypyc] Improve access to generated C on test failures and document this (#18476)
Now the generated C files for the first mypyc run test failure in a pytest session will be copied to the `.mypyc_test_output` directory, and this will be indicated in the test output. This is a convenience feature to help in the common scenario where all test failures have the same root cause, so any single output is sufficient for debugging. Document this and `--mypyc-showc`, which allows showing generated C for every test failure. The latter is too verbose to be enabled by default.
1 parent b20eefd commit a8ab85d

File tree

5 files changed

+45
-2
lines changed

5 files changed

+45
-2
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ venv/
1717
test-data/packages/.pip_lock
1818
dmypy.json
1919
.dmypy.json
20+
/.mypyc_test_output
2021

2122
# Packages
2223
*.egg

mypy/test/config.py

+3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
# It is also hard-coded in numerous places, so don't change it.
1919
test_temp_dir = "tmp"
2020

21+
# Mypyc tests may write intermediate files (e.g. generated C) here on failure
22+
mypyc_output_dir = os.path.join(PREFIX, ".mypyc_test_output")
23+
2124
# The PEP 561 tests do a bunch of pip installs which, even though they operate
2225
# on distinct temporary virtual environments, run into race conditions on shared
2326
# file-system state. To make this work reliably in parallel mode, we'll use a

mypy/test/data.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import pytest
2121

2222
from mypy import defaults
23-
from mypy.test.config import PREFIX, test_data_prefix, test_temp_dir
23+
from mypy.test.config import PREFIX, mypyc_output_dir, test_data_prefix, test_temp_dir
2424

2525
root_dir = os.path.normpath(PREFIX)
2626

@@ -586,6 +586,13 @@ def fix_cobertura_filename(line: str) -> str:
586586
##
587587

588588

589+
def pytest_sessionstart(session: Any) -> None:
590+
# Clean up directory where mypyc tests write intermediate files on failure
591+
# to avoid any confusion between test runs
592+
if os.path.isdir(mypyc_output_dir):
593+
shutil.rmtree(mypyc_output_dir)
594+
595+
589596
# This function name is special to pytest. See
590597
# https://docs.pytest.org/en/latest/reference.html#initialization-hooks
591598
def pytest_addoption(parser: Any) -> None:

mypyc/doc/dev-intro.md

+16
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,22 @@ Compiled native functions have the prefix `CPyDef_`, while wrapper
296296
functions used for calling functions from interpreted Python code have
297297
the `CPyPy_` prefix.
298298

299+
When running a test, the first test failure will copy generated C code
300+
into the `.mypyc_test_output` directory. You will see something like
301+
this in the test output:
302+
303+
```
304+
...
305+
---------------------------- Captured stderr call -----------------------------
306+
307+
Generated files: /Users/me/src/mypy/.mypyc_test_output (for first failure only)
308+
309+
...
310+
```
311+
312+
You can also run pytest with `--mypyc-showc` to display C code on every
313+
test failure.
314+
299315
## Other Important Limitations
300316

301317
All of these limitations will likely be fixed in the future:

mypyc/test/test_run.py

+17-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from mypy import build
1818
from mypy.errors import CompileError
1919
from mypy.options import Options
20-
from mypy.test.config import test_temp_dir
20+
from mypy.test.config import mypyc_output_dir, test_temp_dir
2121
from mypy.test.data import DataDrivenTestCase
2222
from mypy.test.helpers import assert_module_equivalence, perform_file_operations
2323
from mypyc.build import construct_groups
@@ -281,6 +281,7 @@ def run_case_step(self, testcase: DataDrivenTestCase, incremental_step: int) ->
281281
if not run_setup(setup_file, ["build_ext", "--inplace"]):
282282
if testcase.config.getoption("--mypyc-showc"):
283283
show_c(cfiles)
284+
copy_output_files(mypyc_output_dir)
284285
assert False, "Compilation failed"
285286

286287
# Assert that an output file got created
@@ -344,6 +345,7 @@ def run_case_step(self, testcase: DataDrivenTestCase, incremental_step: int) ->
344345
)
345346
print("hint: You may need to build a debug version of Python first and use it")
346347
print('hint: See also "Debuggging Segfaults" in mypyc/doc/dev-intro.md')
348+
copy_output_files(mypyc_output_dir)
347349

348350
# Verify output.
349351
if bench:
@@ -457,3 +459,17 @@ def fix_native_line_number(message: str, fnam: str, delta: int) -> str:
457459
message,
458460
)
459461
return message
462+
463+
464+
def copy_output_files(target_dir: str) -> None:
465+
try:
466+
os.mkdir(target_dir)
467+
except OSError:
468+
# Only copy data for the first failure, to avoid excessive output in case
469+
# many tests fail
470+
return
471+
472+
for fnam in glob.glob("build/*.[ch]"):
473+
shutil.copy(fnam, target_dir)
474+
475+
sys.stderr.write(f"\nGenerated files: {target_dir} (for first failure only)\n\n")

0 commit comments

Comments
 (0)