|
29 | 29 | logger = logging.getLogger("pytest-markdown-docs") |
30 | 30 |
|
31 | 31 | MARKER_NAME = "markdown-docs" |
| 32 | +_PYTHON_FENCE_LANGUAGES = frozenset(("py", "python", "python3")) |
32 | 33 |
|
33 | 34 |
|
34 | 35 | class FenceSyntax(Enum): |
@@ -60,6 +61,15 @@ def _get_asyncio_runner(fixture_request): |
60 | 61 | return None |
61 | 62 |
|
62 | 63 |
|
| 64 | +def _runner_name_for_language_from_config(config): |
| 65 | + def runner_name_for_language(language: str) -> typing.Optional[str]: |
| 66 | + return config.hook.pytest_markdown_docs_runner_name_for_language( |
| 67 | + language=language |
| 68 | + ) |
| 69 | + |
| 70 | + return runner_name_for_language |
| 71 | + |
| 72 | + |
63 | 73 | class MarkdownInlinePythonItem(pytest.Item): |
64 | 74 | def __init__( |
65 | 75 | self, |
@@ -173,6 +183,9 @@ def extract_fence_tests( |
173 | 183 | source_path: pathlib.Path, |
174 | 184 | markdown_type: str = "md", |
175 | 185 | fence_syntax: FenceSyntax = FenceSyntax.default, |
| 186 | + runner_name_for_language: typing.Optional[ |
| 187 | + typing.Callable[[str], typing.Optional[str]] |
| 188 | + ] = None, |
176 | 189 | ) -> typing.Generator[FenceTestDefinition, None, None]: |
177 | 190 | tokens = markdown_it_parser.parse(markdown_string) |
178 | 191 |
|
@@ -203,27 +216,31 @@ def extract_fence_tests( |
203 | 216 | if i >= 2 and is_mdx_comment(tokens[i - 2]): |
204 | 217 | code_options |= extract_options_from_mdx_comment(tokens[i - 2].content) |
205 | 218 |
|
206 | | - if lang in ("py", "python", "python3") and "notest" not in code_options: |
207 | | - start_line = ( |
208 | | - start_line_offset + block.map[0] + 1 |
209 | | - ) # actual code starts on +1 from the "info" line |
210 | | - if "continuation" not in code_options: |
211 | | - prev = "" |
212 | | - |
213 | | - add_blank_lines = start_line - prev.count("\n") |
214 | | - code_block = prev + ("\n" * add_blank_lines) + block.content |
215 | | - |
216 | | - fixture_names = get_prefixed_strings(code_options, "fixture:") |
| 219 | + if lang is not None and "notest" not in code_options: |
217 | 220 | runner_names = get_prefixed_strings(code_options, "runner:") |
218 | 221 | if len(runner_names) == 0: |
219 | 222 | runner_name = None |
| 223 | + if runner_name_for_language is not None: |
| 224 | + runner_name = runner_name_for_language(lang) |
| 225 | + if runner_name is None and lang not in _PYTHON_FENCE_LANGUAGES: |
| 226 | + continue |
220 | 227 | elif len(runner_names) > 1: |
221 | 228 | raise Exception( |
222 | 229 | f"Multiple runners are not supported, use a single one instead: {runner_names}" |
223 | 230 | ) |
224 | 231 | else: |
225 | 232 | runner_name = runner_names[0] |
226 | 233 |
|
| 234 | + start_line = ( |
| 235 | + start_line_offset + block.map[0] + 1 |
| 236 | + ) # actual code starts on +1 from the "info" line |
| 237 | + if "continuation" not in code_options: |
| 238 | + prev = "" |
| 239 | + |
| 240 | + add_blank_lines = start_line - prev.count("\n") |
| 241 | + code_block = prev + ("\n" * add_blank_lines) + block.content |
| 242 | + |
| 243 | + fixture_names = get_prefixed_strings(code_options, "fixture:") |
227 | 244 | retry_counts = get_prefixed_strings(code_options, "retry:") |
228 | 245 | if len(retry_counts) == 0: |
229 | 246 | max_retries = 0 |
@@ -389,6 +406,9 @@ def find_object_tests_recursive( |
389 | 406 | docstring_offset, |
390 | 407 | source_path=self.path, |
391 | 408 | fence_syntax=fence_syntax, |
| 409 | + runner_name_for_language=_runner_name_for_language_from_config( |
| 410 | + self.config |
| 411 | + ), |
392 | 412 | ) |
393 | 413 | ): |
394 | 414 | found_test = ObjectTestDefinition(i, obj_name, fence_test) |
@@ -420,6 +440,9 @@ def collect(self): |
420 | 440 | start_line_offset=0, |
421 | 441 | markdown_type=self.path.suffix.replace(".", ""), |
422 | 442 | fence_syntax=fence_syntax, |
| 443 | + runner_name_for_language=_runner_name_for_language_from_config( |
| 444 | + self.config |
| 445 | + ), |
423 | 446 | ) |
424 | 447 | ): |
425 | 448 | yield MarkdownInlinePythonItem.from_parent( |
|
0 commit comments