@@ -392,27 +392,51 @@ def combine_all_coverage() -> None:
392392 """Combine all coverage files (unit tests + container coverage) and generate reports.
393393
394394 This is called at the end of the test session to merge:
395- - pytest-cov coverage from unit tests (host)
396- - Container coverage from e2e tests (Docker)
395+ - pytest-cov coverage from tests/.coverage.* (host pytest workers )
396+ - Container coverage from coverage_reports/.coverage.* (Docker containers )
397397 """
398- if not COVERAGE_OUTPUT_DIR .exists ():
399- return
398+ tests_dir = Path (__file__ ).parent
399+
400+ # Collect coverage files from both locations:
401+ # 1. tests/.coverage.* - pytest-cov worker files (host unit/e2e tests)
402+ # 2. coverage_reports/.coverage.* - container coverage files (e2e Docker)
403+ coverage_files : list [Path ] = list (tests_dir .glob (".coverage*" ))
404+ if COVERAGE_OUTPUT_DIR .exists ():
405+ coverage_files .extend (COVERAGE_OUTPUT_DIR .glob (".coverage*" ))
400406
401- coverage_files = list (COVERAGE_OUTPUT_DIR .glob (".coverage*" ))
402407 if not coverage_files :
403408 print ("[Coverage] No coverage data found to combine" )
404409 return
405410
406- print (f"[Coverage] Found { len (coverage_files )} coverage files to combine" )
411+ print (f"[Coverage] Found { len (coverage_files )} coverage files to combine:" )
412+ for cf in coverage_files :
413+ print (f" - { cf } " )
414+
415+ # Copy all files to coverage_reports for combination
416+ COVERAGE_OUTPUT_DIR .mkdir (parents = True , exist_ok = True )
417+ for cov_file in coverage_files :
418+ if cov_file .parent != COVERAGE_OUTPUT_DIR :
419+ dest = COVERAGE_OUTPUT_DIR / cov_file .name
420+ try :
421+ shutil .copy (cov_file , dest )
422+ print (
423+ f"[Coverage] Copied { cov_file .name } to { COVERAGE_OUTPUT_DIR .name } /"
424+ )
425+ except Exception as e :
426+ print (f"[Coverage] Warning: Failed to copy { cov_file .name } : { e } " )
427+
428+ # Use pyproject.toml from tests/ directory for coverage config
429+ # This contains the path mapping for container -> host paths
430+ rcfile = str (tests_dir / "pyproject.toml" )
407431
408432 orig_dir = os .getcwd ()
409433 try :
410434 os .chdir (COVERAGE_OUTPUT_DIR )
411435
412- # Combine all coverage files
436+ # Combine all coverage files with explicit config
413437 try :
414438 result = subprocess .run (
415- ["coverage" , "combine" , "--keep" ],
439+ ["coverage" , "combine" , "--keep" , f"--rcfile= { rcfile } " ],
416440 check = False ,
417441 capture_output = True ,
418442 text = True ,
@@ -423,7 +447,7 @@ def combine_all_coverage() -> None:
423447 if result .returncode != 0 :
424448 # Try without --keep for older coverage versions
425449 result = subprocess .run (
426- ["coverage" , "combine" ],
450+ ["coverage" , "combine" , f"--rcfile= { rcfile } " ],
427451 check = False ,
428452 capture_output = True ,
429453 text = True ,
@@ -434,21 +458,21 @@ def combine_all_coverage() -> None:
434458
435459 # Generate HTML report
436460 subprocess .run (
437- ["coverage" , "html" , "-d" , "htmlcov" ],
461+ ["coverage" , "html" , "-d" , "htmlcov" , f"--rcfile= { rcfile } " ],
438462 check = False ,
439463 capture_output = True ,
440464 )
441465
442466 # Generate XML report (Cobertura format)
443467 subprocess .run (
444- ["coverage" , "xml" , "-o" , "coverage.xml" ],
468+ ["coverage" , "xml" , "-o" , "coverage.xml" , f"--rcfile= { rcfile } " ],
445469 check = False ,
446470 capture_output = True ,
447471 )
448472
449473 # Print summary report
450474 result = subprocess .run (
451- ["coverage" , "report" ],
475+ ["coverage" , "report" , f"--rcfile= { rcfile } " ],
452476 check = False ,
453477 capture_output = True ,
454478 text = True ,
@@ -828,6 +852,16 @@ def pytest_sessionstart(session: Session) -> None:
828852 # Also clean any legacy resources without timestamps
829853 cleanup_docker_resources_by_prefix ("ref-ressource-" )
830854
855+ # Clean up stale coverage files to prevent SQLite race conditions
856+ # pytest-cov will write to tests/.coverage.* with unique suffixes per worker
857+ tests_dir = Path (__file__ ).parent
858+ for coverage_file in tests_dir .glob (".coverage*" ):
859+ try :
860+ coverage_file .unlink ()
861+ print (f"[REF E2E] Removed stale coverage file: { coverage_file .name } " )
862+ except Exception as e :
863+ print (f"[REF E2E] Warning: Failed to remove { coverage_file .name } : { e } " )
864+
831865 # Prune unused Docker networks to avoid IP pool exhaustion
832866 print ("[REF E2E] Pruning unused Docker networks..." )
833867 try :
@@ -841,6 +875,14 @@ def pytest_sessionstart(session: Session) -> None:
841875
842876 COVERAGE_OUTPUT_DIR .mkdir (parents = True , exist_ok = True )
843877
878+ # Also clean container coverage files from previous runs
879+ for coverage_file in COVERAGE_OUTPUT_DIR .glob (".coverage*" ):
880+ try :
881+ coverage_file .unlink ()
882+ print (f"[REF E2E] Removed stale container coverage: { coverage_file .name } " )
883+ except Exception as e :
884+ print (f"[REF E2E] Warning: Failed to remove { coverage_file .name } : { e } " )
885+
844886
845887def pytest_sessionfinish (session : Session , exitstatus : int ) -> None :
846888 """
0 commit comments