Skip to content

Commit 3bee328

Browse files
committed
Add separate workers mode for jacoco
1 parent ee0ed06 commit 3bee328

File tree

2 files changed

+44
-31
lines changed

2 files changed

+44
-31
lines changed

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

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -415,10 +415,11 @@ def interrupt_process(process: subprocess.Popen):
415415

416416

417417
class ParallelTestRunner(TestRunner):
418-
def __init__(self, *, num_processes, subprocess_args, **kwargs):
418+
def __init__(self, *, num_processes, subprocess_args, separate_workers, **kwargs):
419419
super().__init__(**kwargs)
420420
self.num_processes = num_processes
421421
self.subprocess_args = subprocess_args
422+
self.separate_workers = separate_workers
422423
self.stop_event = threading.Event()
423424
self.crashes = []
424425
self.last_out_pos = 0
@@ -432,28 +433,35 @@ def report_result(self, result: TestResult):
432433
def tests_failed(self):
433434
return super().tests_failed() or bool(self.crashes)
434435

436+
def partition_tests_into_processes(self, suites: list['TestSuite']) -> list[list[TestId]]:
437+
if self.separate_workers:
438+
per_file_suites = suites
439+
unpartitioned = []
440+
else:
441+
per_file_suites, unpartitioned = partition_list(suites, lambda suite: suite.config.new_worker_per_file)
442+
partitions = [suite.collected_tests for suite in per_file_suites]
443+
per_partition = int(math.ceil(len(unpartitioned) / max(1, self.num_processes)))
444+
while unpartitioned:
445+
partitions.append([test for suite in unpartitioned[:per_partition] for test in suite.collected_tests])
446+
unpartitioned = unpartitioned[per_partition:]
447+
return partitions
448+
435449
def run_tests(self, tests: list['TestSuite']):
450+
serial_suites, parallel_suites = partition_list(
451+
tests,
452+
lambda suite: suite.test_file.name.removesuffix('.py') in suite.config.serial_tests,
453+
)
454+
parallel_partitions = self.partition_tests_into_processes(parallel_suites)
455+
serial_partitions = self.partition_tests_into_processes(serial_suites)
456+
436457
start_time = time.time()
437-
if tests:
438-
serial_suites, unpartitioned = partition_list(
439-
tests,
440-
lambda suite: suite.test_file.name.removesuffix('.py') in suite.config.serial_tests,
441-
)
442-
per_file_suites, unpartitioned = partition_list(
443-
unpartitioned,
444-
lambda suite: suite.config.new_worker_per_file,
445-
)
446-
partitions = [suite.collected_tests for suite in per_file_suites]
447-
per_partition = int(math.ceil(len(unpartitioned) / self.num_processes))
448-
while unpartitioned:
449-
partitions.append([test for suite in unpartitioned[:per_partition] for test in suite.collected_tests])
450-
unpartitioned = unpartitioned[per_partition:]
451-
452-
num_processes = max(1, min(self.num_processes, len(partitions)))
458+
if parallel_partitions:
459+
num_processes = max(1, min(self.num_processes, len(parallel_partitions)))
453460
with concurrent.futures.ThreadPoolExecutor(num_processes) as executor:
454-
self.run_partitions_in_subprocesses(executor, partitions)
455-
for serial_suite in serial_suites:
456-
self.run_partitions_in_subprocesses(executor, [serial_suite.collected_tests])
461+
self.run_partitions_in_subprocesses(executor, parallel_partitions)
462+
if serial_partitions:
463+
with concurrent.futures.ThreadPoolExecutor(1) as executor:
464+
self.run_partitions_in_subprocesses(executor, serial_partitions)
457465

458466
self.total_duration = time.time() - start_time
459467
self.display_summary()
@@ -794,6 +802,8 @@ def main():
794802
help="Interpret test file names relative to tagged test directory")
795803
parser.add_argument('-n', '--num-processes', type=int,
796804
help="Run tests in N subprocess workers. Adds crash recovery, output capture and timeout handling")
805+
parser.add_argument('--separate-workers', action='store_true',
806+
help="Create a new worker process for each test file (when -n is specified). Default for tagged unit tests")
797807
parser.add_argument('--ignore', type=Path, action='append', default=[],
798808
help="Ignore path during collection (multi-allowed)")
799809
parser.add_argument('-f', '--failfast', action='store_true',
@@ -865,17 +875,18 @@ def main():
865875
if not tests:
866876
sys.exit("No tests matched\n")
867877

868-
runner_args = {
869-
'failfast': args.failfast,
870-
'report_durations': args.durations,
871-
}
878+
runner_args = dict(
879+
failfast=args.failfast,
880+
report_durations=args.durations,
881+
)
872882
if not args.num_processes:
873883
runner = TestRunner(**runner_args)
874884
else:
875885
runner = ParallelTestRunner(
876886
**runner_args,
877887
num_processes=args.num_processes,
878888
subprocess_args=args.subprocess_args,
889+
separate_workers=args.separate_workers,
879890
)
880891

881892
runner.run_tests(tests)

mx.graalpython/mx_graalpython.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1199,10 +1199,12 @@ def run_python_unittests(python_binary, args=None, paths=None, aot_compatible=Fa
11991199
for file in exclude:
12001200
args += ['--ignore', file]
12011201

1202-
if is_collecting_coverage():
1203-
if mx_gate.get_jacoco_agent_args():
1204-
with open(python_binary, "r") as f:
1205-
assert f.read(9) == "#!/bin/sh"
1202+
if is_collecting_coverage() and mx_gate.get_jacoco_agent_args():
1203+
# jacoco only dumps the data on exit, and when we run all our unittests
1204+
# at once it generates so much data we run out of heap space
1205+
args.append('--separate-workers')
1206+
with open(python_binary, "r") as f:
1207+
assert f.read(9) == "#!/bin/sh"
12061208

12071209
if report:
12081210
reportfile = None
@@ -1211,12 +1213,12 @@ def run_python_unittests(python_binary, args=None, paths=None, aot_compatible=Fa
12111213
reportfile = os.path.abspath(tempfile.mktemp(prefix="test-report-", suffix=".json"))
12121214
args += ["--mx-report", reportfile]
12131215

1214-
if paths:
1216+
if paths is not None:
12151217
args += paths
12161218
else:
12171219
args.append('.')
12181220

1219-
mx.logv(" ".join([python_binary] + args))
1221+
mx.logv(shlex.join([python_binary] + args))
12201222
if lock:
12211223
lock.release()
12221224
result = mx.run([python_binary] + args, nonZeroIsFatal=nonZeroIsFatal, env=env, cwd=cwd, out=out, err=err, timeout=timeout)
@@ -2683,7 +2685,7 @@ def python_coverage(args):
26832685
if os.environ.get('CI'):
26842686
mx.run(['coverage-uploader.py', '--associated-repos', f.name])
26852687
else:
2686-
mx.run(['genhtml', '--output-directory', 'coverage-html', '--source-directory', os.path.abspath(os.path.join(SUITE.dir, '..')), '--quiet'])
2688+
mx.run(['genhtml', '--output-directory', 'coverage-html', '--source-directory', os.path.abspath(os.path.join(SUITE.dir, '..')), '--quiet', 'coverage/lcov.info'])
26872689
print('Generated html report in ./coverage-html')
26882690

26892691

0 commit comments

Comments
 (0)