3
3
import traceback
4
4
import types
5
5
import pathlib
6
+ from dataclasses import dataclass
7
+
6
8
import pytest
7
9
import typing
8
10
from enum import Enum
@@ -31,6 +33,20 @@ class FenceSyntax(Enum):
31
33
superfences = "superfences"
32
34
33
35
36
+ @dataclass
37
+ class FenceTest :
38
+ code_block : str
39
+ fixture_names : typing .List [str ]
40
+ start_line : int
41
+
42
+
43
+ @dataclass
44
+ class ObjectTest :
45
+ intra_object_index : int
46
+ object_name : str
47
+ fence_test : FenceTest
48
+
49
+
34
50
class MarkdownInlinePythonItem (pytest .Item ):
35
51
def __init__ (
36
52
self ,
@@ -161,11 +177,11 @@ def reportinfo(self):
161
177
return self .name , 0 , self .nodeid
162
178
163
179
164
- def extract_code_blocks (
180
+ def extract_fence_tests (
165
181
markdown_string : str ,
166
182
markdown_type : str = "md" ,
167
183
fence_syntax : FenceSyntax = FenceSyntax .default ,
168
- ) -> typing .Generator [typing . Tuple [ str , typing . List [ str ], int ] , None , None ]:
184
+ ) -> typing .Generator [FenceTest , None , None ]:
169
185
import markdown_it
170
186
171
187
mi = markdown_it .MarkdownIt (config = "commonmark" )
@@ -176,7 +192,7 @@ def extract_code_blocks(
176
192
if block .type != "fence" or not block .map :
177
193
continue
178
194
179
- startline = block .map [0 ] + 1 # skip the info line when counting
195
+ start_line = block .map [0 ] + 1 # skip the info line when counting
180
196
if fence_syntax == FenceSyntax .superfences :
181
197
code_info = parse_superfences_block_info (block .info )
182
198
else :
@@ -204,12 +220,12 @@ def extract_code_blocks(
204
220
205
221
if "continuation" in code_options :
206
222
code_block = prev + code_block
207
- startline = - 1 # this disables proper line numbers, TODO: adjust line numbers *per snippet*
223
+ start_line = - 1 # this disables proper line numbers, TODO: adjust line numbers *per snippet*
208
224
209
225
fixture_names = [
210
226
f [len ("fixture:" ) :] for f in code_options if f .startswith ("fixture:" )
211
227
]
212
- yield code_block , fixture_names , startline
228
+ yield FenceTest ( code_block , fixture_names , start_line )
213
229
prev = code_block
214
230
215
231
@@ -268,43 +284,36 @@ def collect(self):
268
284
self .path , root = self .config .rootpath , consider_namespace_packages = True
269
285
)
270
286
else :
271
- # but unsupported before 8.1...
287
+ # but unsupported before pytest 8.1...
272
288
module = import_path (self .path , root = self .config .rootpath )
273
289
274
- for code_block_idx , obj , (
275
- test_code ,
276
- fixture_names ,
277
- start_line ,
278
- ) in self .find_object_tests_recursive (module .__name__ , module ):
279
- obj_name = (
280
- getattr (obj , "__qualname__" , None )
281
- or getattr (obj , "__name__" , None )
282
- or "<Unnamed obj>"
283
- )
290
+ for object_test in self .find_object_tests_recursive (module .__name__ , module ):
291
+ fence_test = object_test .fence_test
284
292
yield MarkdownInlinePythonItem .from_parent (
285
293
self ,
286
- name = f"{ obj_name } [CodeBlock#{ code_block_idx + 1 } ][rel.line:{ start_line } ]" ,
287
- code = test_code ,
288
- fixture_names = fixture_names ,
289
- start_line = start_line ,
294
+ name = f"{ object_test . object_name } [CodeBlock#{ object_test . intra_object_index + 1 } ][rel.line:{ fence_test . start_line } ]" ,
295
+ code = fence_test . code_block ,
296
+ fixture_names = fence_test . fixture_names ,
297
+ start_line = fence_test . start_line ,
290
298
fake_line_numbers = True , # TODO: figure out where docstrings are in file to offset line numbers properly
291
299
)
292
300
293
301
def find_object_tests_recursive (
294
302
self , module_name : str , object : typing .Any
295
- ) -> typing .Generator [
296
- typing .Tuple [int , typing .Any , typing .Tuple [str , typing .List [str ], int ]],
297
- None ,
298
- None ,
299
- ]:
303
+ ) -> typing .Generator [ObjectTest , None , None ]:
300
304
docstr = inspect .getdoc (object )
301
305
302
306
if docstr :
307
+ obj_name = (
308
+ getattr (object , "__qualname__" , None )
309
+ or getattr (object , "__name__" , None )
310
+ or "<Unnamed obj>"
311
+ )
303
312
fence_syntax = FenceSyntax (self .config .option .markdowndocs_syntax )
304
- for i , code_block in enumerate (
305
- extract_code_blocks (docstr , fence_syntax = fence_syntax )
313
+ for i , fence_test in enumerate (
314
+ extract_fence_tests (docstr , fence_syntax = fence_syntax )
306
315
):
307
- yield i , object , code_block
316
+ yield ObjectTest ( i , obj_name , fence_test )
308
317
309
318
for member_name , member in inspect .getmembers (object ):
310
319
if member_name .startswith ("_" ):
@@ -323,20 +332,20 @@ def collect(self):
323
332
markdown_content = self .path .read_text ("utf8" )
324
333
fence_syntax = FenceSyntax (self .config .option .markdowndocs_syntax )
325
334
326
- for i , ( code_block , fixture_names , start_line ) in enumerate (
327
- extract_code_blocks (
335
+ for i , fence_test in enumerate (
336
+ extract_fence_tests (
328
337
markdown_content ,
329
338
markdown_type = self .path .suffix .replace ("." , "" ),
330
339
fence_syntax = fence_syntax ,
331
340
)
332
341
):
333
342
yield MarkdownInlinePythonItem .from_parent (
334
343
self ,
335
- name = f"[CodeBlock#{ i + 1 } ][line:{ start_line } ]" ,
336
- code = code_block ,
337
- fixture_names = fixture_names ,
338
- start_line = start_line ,
339
- fake_line_numbers = start_line == - 1 ,
344
+ name = f"[CodeBlock#{ i + 1 } ][line:{ fence_test . start_line } ]" ,
345
+ code = fence_test . code_block ,
346
+ fixture_names = fence_test . fixture_names ,
347
+ start_line = fence_test . start_line ,
348
+ fake_line_numbers = fence_test . start_line == - 1 ,
340
349
)
341
350
342
351
0 commit comments