Skip to content

Commit cc850eb

Browse files
committed
Split some test files per individual test
1 parent 407b50b commit cc850eb

File tree

6 files changed

+44
-55
lines changed

6 files changed

+44
-55
lines changed

graalpython/com.oracle.graal.python.test/src/runner.py

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,7 @@ def partition_tests_into_processes(self, suites: list['TestSuite']) -> list[list
449449
def run_tests(self, tests: list['TestSuite']):
450450
serial_suites, parallel_suites = partition_list(
451451
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),
453453
)
454454
parallel_partitions = self.partition_tests_into_processes(parallel_suites)
455455
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
616616

617617
@dataclass
618618
class Config:
619+
configdir: Path = Path('.').resolve()
619620
rootdir: Path = Path('.').resolve()
620621
tags_dir: Path | None = None
621622
run_top_level_functions: bool = False
622623
new_worker_per_file: bool = False
623624
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
624636

625637

626638
@lru_cache
@@ -644,16 +656,19 @@ def config_for_file(test_file: Path) -> Config:
644656
def parse_config(config_path, path):
645657
with open(config_path, 'rb') as f:
646658
config_dict = tomllib.load(f)['tests']
647-
rootdir = config_path.parent.parent.resolve()
648659
tags_dir = None
649660
if config_tags_dir := config_dict.get('tags_dir'):
650661
tags_dir = (path / config_tags_dir).resolve()
651662
return Config(
652-
rootdir=rootdir,
663+
configdir=config_path.parent.resolve(),
664+
rootdir=config_path.parent.parent.resolve(),
653665
tags_dir=tags_dir,
654666
run_top_level_functions=config_dict.get('run_top_level_functions', Config.run_top_level_functions),
655667
new_worker_per_file=config_dict.get('new_worker_per_file', Config.new_worker_per_file),
656668
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+
),
657672
)
658673

659674

@@ -717,7 +732,7 @@ def expand_specifier_paths(specifiers: list[TestSpecifier]) -> list[TestSpecifie
717732
return expanded_specifiers
718733

719734

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:
721736
config = config_for_file(test_file)
722737
saved_path = sys.path[:]
723738
sys.path.insert(0, str(config.rootdir))
@@ -735,6 +750,9 @@ def collect_module(test_file: Path, specifiers: list[TestSpecifier], use_tags=Fa
735750
log(f"Test file {test_file} skipped: {e}")
736751
return
737752
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]
738756
if collected_tests:
739757
return TestSuite(config, test_file, sys.path[:], test_suite, collected_tests, untagged_tests)
740758
finally:
@@ -755,13 +773,24 @@ def collect(all_specifiers: list[TestSpecifier], *, use_tags=False, ignore=None,
755773
s for s in all_specifiers
756774
if not any(path_for_comparison(s.test_file).is_relative_to(i) for i in ignore)
757775
]
776+
specifiers_by_file = group_specifiers_by_file(all_specifiers)
758777
if partial:
759778
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():
762791
if not test_file.exists():
763792
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)
765794
if collected:
766795
to_run.append(collected)
767796
return to_run
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
[tests]
22
run_top_level_functions = true
3+
partial_splits_individual_tests = [
4+
'standalone/test_standalone',
5+
'standalone/test_jbang_integration',
6+
]

graalpython/com.oracle.graal.python.test/src/tests/standalone/test_jbang_integration.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
import unittest
4848

4949
from tests.standalone import util
50-
from tests.util import skip_deselected_test_functions
5150

5251
is_enabled = 'ENABLE_JBANG_INTEGRATION_UNITTESTS' in os.environ and os.environ['ENABLE_JBANG_INTEGRATION_UNITTESTS'] == "true"
5352
MAVEN_REPO_LOCAL_URL = os.environ.get('org.graalvm.maven.downloader.repository')
@@ -415,6 +414,3 @@ def test_two_resource_dirs(self):
415414
out, result = run_cmd(command, cwd=work_dir)
416415
self.assertTrue(result == 1, f"Execution failed with code {result}\n command: {command}\n stdout: {out}")
417416
self.assertTrue("only one //PYTHON_RESOURCES_DIRECTORY comment is allowed" in out, f"Expected text:\nonly one //PYTHON_RESOURCES_DIRECTORY comment is allowed")
418-
419-
420-
skip_deselected_test_functions(globals())

graalpython/com.oracle.graal.python.test/src/tests/standalone/test_standalone.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
import unittest
4343

4444
from tests.standalone import util
45-
from tests.util import skip_deselected_test_functions
4645

4746
is_enabled = 'ENABLE_STANDALONE_UNITTESTS' in os.environ and os.environ['ENABLE_STANDALONE_UNITTESTS'] == "true"
4847

@@ -141,5 +140,3 @@ def test_native_executable_module():
141140
cmd = [target_file]
142141
out, return_code = util.run_cmd(cmd, env)
143142
util.check_ouput("hello standalone world", out)
144-
145-
skip_deselected_test_functions(globals())

graalpython/com.oracle.graal.python.test/src/tests/util.py

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,7 @@
3636
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
3737
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3838
# SOFTWARE.
39-
import os
4039
import sys
41-
import unittest
4240

4341

4442
def storage_to_native(s):
@@ -59,42 +57,3 @@ def assert_raises(err, fn, *args, err_check=None, **kwargs):
5957
else:
6058
assert err_check(e)
6159
assert raised
62-
63-
64-
# XXX we should split the tests in a better way
65-
def skip_deselected_test_functions(globals):
66-
"""
67-
If TAGGED_UNITTEST_PARTIAL is set, this skips tests as appropriate.
68-
"""
69-
envvar = os.environ.get("TAGGED_UNITTEST_PARTIAL", "")
70-
if "/" not in envvar:
71-
return
72-
import types
73-
batch, total = (int(x) for x in envvar.split("/"))
74-
test_functions = []
75-
for g in globals.values():
76-
if isinstance(g, types.FunctionType) and g.__name__.startswith("test_"):
77-
test_functions.append((globals, g))
78-
elif isinstance(g, type) and issubclass(g, unittest.TestCase):
79-
for f in (getattr(g, n) for n in dir(g)):
80-
if isinstance(f, types.FunctionType) and f.__name__.startswith("test_"):
81-
test_functions.append((g, f))
82-
for idx, (owner, test_func) in enumerate(test_functions):
83-
if idx % total != batch - 1:
84-
n = test_func.__name__
85-
if isinstance(owner, type):
86-
removeAttr(owner, n)
87-
else:
88-
del owner[n]
89-
90-
91-
def removeAttr(cls, attr):
92-
if (cls is object):
93-
return
94-
if hasattr(cls, attr):
95-
try:
96-
delattr(cls, attr)
97-
except AttributeError as e:
98-
pass
99-
for b in cls.__bases__:
100-
removeAttr(b, attr)

graalpython/lib-python/3/test/conftest.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,7 @@ serial_tests = [
2828
'test_int',
2929
'test_zipfile',
3030
]
31+
partial_splits_individual_tests = [
32+
'test_multiprocessing_spawn',
33+
'test_multiprocessing_graalpy',
34+
]

0 commit comments

Comments
 (0)