From: Kurt McKee <contactme@kurtmckee.org>
Date: Wed, 20 Aug 2025 09:32:27 -0500
Subject: 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.

Origin: https://github.com/pypa/readme_renderer/pull/332
---
 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()
