@@ -449,7 +449,7 @@ def partition_tests_into_processes(self, suites: list['TestSuite']) -> list[list
449
449
def run_tests (self , tests : list ['TestSuite' ]):
450
450
serial_suites , parallel_suites = partition_list (
451
451
tests ,
452
- lambda suite : suite .test_file . name . removesuffix ( '.py' ) in suite .config . serial_tests ,
452
+ lambda suite : suite .config . is_serial_test ( suite .test_file ) ,
453
453
)
454
454
parallel_partitions = self .partition_tests_into_processes (parallel_suites )
455
455
serial_partitions = self .partition_tests_into_processes (serial_suites )
@@ -616,11 +616,23 @@ def filter_tree(test_file: Path, test_suite: unittest.TestSuite, specifiers: lis
616
616
617
617
@dataclass
618
618
class Config :
619
+ configdir : Path = Path ('.' ).resolve ()
619
620
rootdir : Path = Path ('.' ).resolve ()
620
621
tags_dir : Path | None = None
621
622
run_top_level_functions : bool = False
622
623
new_worker_per_file : bool = False
623
624
serial_tests : frozenset [str ] = frozenset ()
625
+ partial_splits_individual_tests : frozenset [str ] = frozenset ()
626
+
627
+ def is_serial_test (self , test_file : Path ):
628
+ resolved = test_file .resolve ().relative_to (self .configdir )
629
+ name = str (resolved ).removesuffix ('.py' )
630
+ return name in self .serial_tests
631
+
632
+ def is_partial_splits_individual_tests (self , test_file : Path ):
633
+ resolved = test_file .resolve ().relative_to (self .configdir )
634
+ name = str (resolved ).removesuffix ('.py' )
635
+ return name in self .partial_splits_individual_tests
624
636
625
637
626
638
@lru_cache
@@ -644,16 +656,19 @@ def config_for_file(test_file: Path) -> Config:
644
656
def parse_config (config_path , path ):
645
657
with open (config_path , 'rb' ) as f :
646
658
config_dict = tomllib .load (f )['tests' ]
647
- rootdir = config_path .parent .parent .resolve ()
648
659
tags_dir = None
649
660
if config_tags_dir := config_dict .get ('tags_dir' ):
650
661
tags_dir = (path / config_tags_dir ).resolve ()
651
662
return Config (
652
- rootdir = rootdir ,
663
+ configdir = config_path .parent .resolve (),
664
+ rootdir = config_path .parent .parent .resolve (),
653
665
tags_dir = tags_dir ,
654
666
run_top_level_functions = config_dict .get ('run_top_level_functions' , Config .run_top_level_functions ),
655
667
new_worker_per_file = config_dict .get ('new_worker_per_file' , Config .new_worker_per_file ),
656
668
serial_tests = frozenset (config_dict .get ('serial_tests' , Config .serial_tests )),
669
+ partial_splits_individual_tests = frozenset (
670
+ config_dict .get ('partial_splits_individual_tests' , Config .partial_splits_individual_tests )
671
+ ),
657
672
)
658
673
659
674
@@ -717,7 +732,7 @@ def expand_specifier_paths(specifiers: list[TestSpecifier]) -> list[TestSpecifie
717
732
return expanded_specifiers
718
733
719
734
720
- def collect_module (test_file : Path , specifiers : list [TestSpecifier ], use_tags = False ) -> TestSuite | None :
735
+ def collect_module (test_file : Path , specifiers : list [TestSpecifier ], use_tags = False , partial = None ) -> TestSuite | None :
721
736
config = config_for_file (test_file )
722
737
saved_path = sys .path [:]
723
738
sys .path .insert (0 , str (config .rootdir ))
@@ -735,6 +750,9 @@ def collect_module(test_file: Path, specifiers: list[TestSpecifier], use_tags=Fa
735
750
log (f"Test file { test_file } skipped: { e } " )
736
751
return
737
752
collected_tests , untagged_tests = filter_tree (test_file , test_suite , specifiers , tags )
753
+ if partial and config .is_partial_splits_individual_tests (test_file ):
754
+ selected , total = partial
755
+ collected_tests = collected_tests [selected ::total ]
738
756
if collected_tests :
739
757
return TestSuite (config , test_file , sys .path [:], test_suite , collected_tests , untagged_tests )
740
758
finally :
@@ -755,13 +773,24 @@ def collect(all_specifiers: list[TestSpecifier], *, use_tags=False, ignore=None,
755
773
s for s in all_specifiers
756
774
if not any (path_for_comparison (s .test_file ).is_relative_to (i ) for i in ignore )
757
775
]
776
+ specifiers_by_file = group_specifiers_by_file (all_specifiers )
758
777
if partial :
759
778
selected , total = partial
760
- all_specifiers = all_specifiers [selected ::total ]
761
- for test_file , specifiers in group_specifiers_by_file (all_specifiers ).items ():
779
+ to_split = []
780
+ partial_files = set ()
781
+ # Always keep files that are split per-test
782
+ for test_file in specifiers_by_file :
783
+ config = config_for_file (test_file )
784
+ if config .is_partial_splits_individual_tests (test_file ):
785
+ partial_files .add (test_file )
786
+ else :
787
+ to_split .append (test_file )
788
+ partial_files |= set (to_split [selected ::total ])
789
+ specifiers_by_file = {f : s for f , s in specifiers_by_file .items () if f in partial_files }
790
+ for test_file , specifiers in specifiers_by_file .items ():
762
791
if not test_file .exists ():
763
792
sys .exit (f"File does not exist: { test_file } " )
764
- collected = collect_module (test_file , specifiers , use_tags = use_tags )
793
+ collected = collect_module (test_file , specifiers , use_tags = use_tags , partial = partial )
765
794
if collected :
766
795
to_run .append (collected )
767
796
return to_run
0 commit comments