From 1a6d24c67cb3a83b2efcb156ad1b540554d4d070 Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Wed, 20 Aug 2025 09:32:27 -0500 Subject: [PATCH] Fix a test failure caused by an internal docutils `open()` call docutils 0.22 introduced an RST stylesheet feature that must be able to open files during RST-to-HTML rendering. The original design of the `test_cli_explicit_format` test patched `pathlib.Path.open()` so it would always return the same open file instance...which was always closed on first use. This caused a failure when docutils tried to open `minimal.css`: ``` ValueError: I/O operation on closed file. ``` This change introduces a more complex mocking strategy that documents and meets the current technical requirements. --- tests/test_cli.py | 47 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index dddf5c3..3e8c928 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -46,12 +46,47 @@ def test_cli_invalid_format(): def test_cli_explicit_format(input_file): fmt = input_file.suffix.lstrip(".") - with input_file.open() as fp, \ - mock.patch("pathlib.Path.open", return_value=fp), \ - mock.patch("builtins.print") as print_: - main(["-f", fmt, "no-file.invalid"]) - print_.assert_called_once() - (result,), _ = print_.call_args + + # The explicit-format tests present a number of technical challenges. + # + # 1. The filename must not have a recognized extension + # to ensure that the renderer is honoring the `-f` argument. + # Therefore, patching is used so the input filename can be faked. + # + # 2. docutils must be able to open stylesheet files during RST-to-HTML rendering. + # Therefore, patching must be limited to readme-renderer's usage. + # + # The strategy used here is to patch `pathlib.Path.open()` until it is first called, + # since the first call occurs when readme-renderer reads the input file's content. + # After that, the patch is disabled. However, this presents additional challenges: + # + # 3. On Python <=3.11, `patch.__exit__()` cannot be called more than once. + # 4. `patch.stop()` cannot sufficiently undo a patch if `.__enter__()` was called. + # + # Therefore, the patch cannot be used as a context manager. + # It must be manually started and stopped. + + fp = input_file.open() + + def stop_open_patch_after_one_call(): + open_patch.stop() + return fp + + open_patch = mock.patch( + "pathlib.Path.open", + side_effect=stop_open_patch_after_one_call, + ) + + open_patch.start() + + try: + with fp, mock.patch("builtins.print") as print_: + main(["-f", fmt, "no-file.invalid"]) + print_.assert_called_once() + (result,), _ = print_.call_args + finally: + # Stop the patch regardless of the test result. + open_patch.stop() with input_file.with_suffix(".html").open() as fp: assert result.strip() == fp.read().strip()