1+ import os .path
12import threading
23from copy import deepcopy
34from typing import Callable , Optional
1112from plain2code_events import RenderContextSnapshot
1213from plain2code_state import RunState
1314from plain_modules import PlainModule
14- from render_machine import triggers
15+ from render_machine import failure_attribution , triggers
1516from render_machine .conformance_tests import CONFORMANCE_TESTS_DEFINITION_FILE_NAME , ConformanceTests
1617from render_machine .render_types import (
1718 AcceptanceTestPhase ,
@@ -343,7 +344,7 @@ def _start_regression_phase(self):
343344 ctx .code_changed_during_regression = False
344345
345346 ctx .execution_phase = TestExecutionPhase .RUNNING_REGRESSION
346- ctx .current_testing_frid = None # Will be set by get_first_conformance_tests_running_context
347+ ctx .regression_module_index = None # Will be advanced by _get_next_regression_module
347348
348349 def _get_next_test_to_run (self ):
349350 """Determine which test to run next based on current phase."""
@@ -354,6 +355,78 @@ def _get_next_test_to_run(self):
354355 else :
355356 return self .get_next_conformance_tests_running_context ()
356357
358+ def _module_has_conformance_tests (self , module_name : str ) -> bool :
359+ return len (self .conformance_tests .get_conformance_tests_json (module_name )) > 0
360+
361+ def _get_next_regression_module (self ) -> Optional [str ]:
362+ """Advance to the next module whose whole suite should run during regression.
363+
364+ The regression sequence is every required module (in requires order). When code
365+ changed while fixing a conformance test, the module being rendered is appended so
366+ its suite gets re-verified against the changed code.
367+ """
368+ ctx = self .conformance_tests_running_context
369+
370+ module_names = [module .module_name for module in (self .required_modules or [])]
371+ if ctx .code_changed_during_regression :
372+ module_names = module_names + [self .module_name ]
373+
374+ next_index = 0 if ctx .regression_module_index is None else ctx .regression_module_index + 1
375+ while next_index < len (module_names ):
376+ module_name = module_names [next_index ]
377+ if module_name == self .module_name or self ._module_has_conformance_tests (module_name ):
378+ ctx .regression_module_index = next_index
379+ return module_name
380+ next_index += 1
381+
382+ return None
383+
384+ def _switch_to_module_suite (self , module_name : str ):
385+ """Point the running context at a module so its whole suite is the next run."""
386+ ctx = self .conformance_tests_running_context
387+
388+ if module_name == self .module_name :
389+ ctx .current_testing_module_name = self .module_name
390+ ctx .current_testing_frid = ctx .frid_being_implemented
391+ else :
392+ if not ctx .conformance_tests_json_has_module_populated (module_name ):
393+ ctx .set_conformance_tests_json (
394+ module_name , self .conformance_tests .get_conformance_tests_json (module_name )
395+ )
396+ ctx .current_testing_module_name = module_name
397+ ctx .current_testing_frid = next (iter (ctx .get_conformance_tests_json (module_name )))
398+
399+ self ._setup_test_specifications ()
400+
401+ def route_conformance_failure_to_frid (self , conformance_tests_issue : str ) -> str :
402+ """Attribute a failed whole-suite run to a functionality and scope the evidence to it.
403+
404+ Points the running context at the earliest implicated FRID (the fix loop, memory
405+ creation, and conflict detection all key off current_testing_frid) and returns the
406+ failure evidence for that FRID: its own failure blocks plus a summary note about
407+ other implicated FRIDs. Returns the issue unchanged when no FRID can be identified.
408+ """
409+ ctx = self .conformance_tests_running_context
410+ module_json = ctx .get_conformance_tests_json (ctx .current_testing_module_name )
411+
412+ implicated_frids = failure_attribution .attribute_failures (conformance_tests_issue , module_json )
413+ if not implicated_frids :
414+ return conformance_tests_issue
415+
416+ target_frid = implicated_frids [0 ]
417+ if target_frid != ctx .current_testing_frid :
418+ console .info (
419+ f"Conformance test failure attributed to functionality { target_frid } "
420+ f"in module { ctx .current_testing_module_name } ."
421+ )
422+ ctx .current_testing_frid = target_frid
423+ self ._setup_test_specifications ()
424+
425+ folder_basename = os .path .basename (module_json [target_frid ]["folder_name" ])
426+ evidence = failure_attribution .extract_frid_failure_evidence (conformance_tests_issue , folder_basename )
427+
428+ return evidence + failure_attribution .format_other_frids_note (implicated_frids , target_frid )
429+
357430 def _has_reached_implementation_frid (self ) -> bool :
358431 """Check if regression has reached the FRID being implemented."""
359432 ctx = self .conformance_tests_running_context
@@ -469,32 +542,23 @@ def _handle_current_frid_testing(self):
469542 raise RuntimeError (f"Unexpected acceptance test phase: { ctx .acceptance_test_phase } " )
470543
471544 def _handle_regression_testing (self ):
472- """Handle regression testing of all earlier FRIDs."""
473-
474- # Get next test to run
475- self .conformance_tests_running_context = self ._get_next_test_to_run ()
545+ """Handle regression testing: one whole-suite run per module.
476546
477- # Get reference to the updated context
547+ The module's own suite already ran in full during the current-FRID phase, so
548+ regression only needs the required modules' suites (plus a re-run of the own
549+ module's suite when code changed while fixing a conformance test).
550+ """
478551 ctx = self .conformance_tests_running_context
479552
480- # Set up specs and run test
481- self ._setup_test_specifications ()
553+ next_module = self ._get_next_regression_module ()
482554
483- if ctx .current_conformance_tests_exist ():
484- # Check if this is the implementation FRID (last test to run)
485- if self ._has_reached_implementation_frid ():
486- # Reached implementation FRID - only re-run it if code changed during regression
487- if ctx .code_changed_during_regression :
488- # Code changed - run the implementation FRID again to verify no regression
489- # After it passes, mark as completed on next iteration
490- ctx .execution_phase = TestExecutionPhase .COMPLETED
491- else :
492- # No code changes - skip re-running implementation FRID, mark as completed immediately
493- ctx .execution_phase = TestExecutionPhase .COMPLETED
494- self .machine .dispatch (triggers .MARK_ALL_CONFORMANCE_TESTS_PASSED )
495- return
555+ if next_module is None :
556+ ctx .execution_phase = TestExecutionPhase .COMPLETED
557+ self .machine .dispatch (triggers .MARK_ALL_CONFORMANCE_TESTS_PASSED )
558+ return
496559
497- self .machine .dispatch (triggers .MARK_CONFORMANCE_TESTS_READY )
560+ self ._switch_to_module_suite (next_module )
561+ self .machine .dispatch (triggers .MARK_CONFORMANCE_TESTS_READY )
498562
499563 # ========== Main Conformance Test Orchestration ==========
500564
@@ -534,7 +598,17 @@ def start_conformance_tests_for_frid(self):
534598 return
535599
536600 # ========== STEP 3: Handle Current FRID Testing ==========
537- if self ._should_run_current_frid_tests ():
601+ if ctx .execution_phase == TestExecutionPhase .TESTING_CURRENT_FRID :
602+ # A failed whole-suite run may have re-pointed current_testing_frid at the
603+ # implicated FRID for the fix loop; restore the FRID being implemented before
604+ # continuing the current-FRID phases.
605+ if (
606+ ctx .current_testing_module_name != self .module_name
607+ or ctx .current_testing_frid != ctx .frid_being_implemented
608+ ):
609+ ctx .current_testing_module_name = self .module_name
610+ ctx .current_testing_frid = ctx .frid_being_implemented
611+ self ._setup_test_specifications ()
538612 self ._handle_current_frid_testing ()
539613 return
540614
0 commit comments