@@ -374,6 +374,111 @@ def f():
374374 assert segments == []
375375
376376
377+ def test_extract_generates_segments_without_blocks_when_only_segment_gate_met () -> None :
378+ lines = ["def f():" ]
379+ for i in range (12 ):
380+ lines .append (f" x{ i } = { i } " )
381+ lines .append ("" )
382+ lines .append ("" )
383+ src = "\n " .join (lines )
384+
385+ units , blocks , segments = extract_units_from_source (
386+ source = src ,
387+ filepath = "x.py" ,
388+ module_name = "mod" ,
389+ cfg = NormalizationConfig (),
390+ min_loc = 1 ,
391+ min_stmt = 1 ,
392+ )
393+
394+ assert units
395+ assert blocks == []
396+ assert segments
397+
398+
399+ def test_extract_generates_blocks_without_segments_when_only_block_gate_met () -> None :
400+ lines = ["def f():" ]
401+ for i in range (10 ):
402+ lines .append (f" x{ i } = { i } " )
403+ lines .append ("" )
404+ lines .append ("" )
405+ lines .append ("" )
406+ lines .append ("" )
407+ src = "\n " .join (lines )
408+
409+ units , blocks , segments = extract_units_from_source (
410+ source = src ,
411+ filepath = "x.py" ,
412+ module_name = "mod" ,
413+ cfg = NormalizationConfig (),
414+ min_loc = 1 ,
415+ min_stmt = 1 ,
416+ )
417+
418+ assert units
419+ assert blocks
420+ assert segments == []
421+
422+
423+ def test_extract_handles_non_list_function_body_for_hash_reuse (
424+ monkeypatch : pytest .MonkeyPatch ,
425+ ) -> None :
426+ lines = ["def f():" ]
427+ for i in range (12 ):
428+ lines .append (f" x{ i } = { i } " )
429+ lines .append ("" )
430+ lines .append ("" )
431+ tree = ast .parse ("\n " .join (lines ))
432+ func = tree .body [0 ]
433+ assert isinstance (func , ast .FunctionDef )
434+ func .body = tuple (func .body ) # type: ignore[assignment]
435+
436+ captured_hashes : dict [str , object ] = {}
437+
438+ def _fake_parse (_source : str , _timeout_s : int ) -> ast .AST :
439+ return tree
440+
441+ def _fake_fingerprint (
442+ _node : ast .FunctionDef | ast .AsyncFunctionDef ,
443+ _cfg : NormalizationConfig ,
444+ _qualname : str ,
445+ ) -> str :
446+ return "f" * 40
447+
448+ def _fake_extract_segments (
449+ _node : ast .FunctionDef | ast .AsyncFunctionDef ,
450+ filepath : str ,
451+ qualname : str ,
452+ cfg : NormalizationConfig ,
453+ window_size : int = 6 ,
454+ max_segments : int = 60 ,
455+ * ,
456+ precomputed_hashes : list [str ] | None = None ,
457+ ) -> list [object ]:
458+ del filepath , qualname , cfg , window_size , max_segments
459+ captured_hashes ["value" ] = precomputed_hashes
460+ return []
461+
462+ monkeypatch .setattr (extractor , "_parse_with_limits" , _fake_parse )
463+ monkeypatch .setattr (extractor , "_stmt_count" , lambda _node : 12 )
464+ monkeypatch .setattr (extractor , "get_cfg_fingerprint" , _fake_fingerprint )
465+ monkeypatch .setattr (extractor , "extract_segments" , _fake_extract_segments )
466+
467+ units , blocks , segments = extract_units_from_source (
468+ source = "def f():\n pass\n " ,
469+ filepath = "x.py" ,
470+ module_name = "mod" ,
471+ cfg = NormalizationConfig (),
472+ min_loc = 1 ,
473+ min_stmt = 1 ,
474+ )
475+
476+ assert len (units ) == 1
477+ assert blocks == []
478+ assert segments == []
479+ assert captured_hashes ["value" ] is None
480+
481+
377482def test_extract_skips_invalid_positions (monkeypatch : pytest .MonkeyPatch ) -> None :
378483 tree = ast .parse (
379484 """
0 commit comments