|
42 | 42 | import json
|
43 | 43 | import math
|
44 | 44 | import multiprocessing
|
45 |
| -import multiprocessing.connection |
46 | 45 | import os
|
47 | 46 | import pickle
|
48 | 47 | import re
|
@@ -515,22 +514,19 @@ def run_partitions_in_subprocesses(self, executor, partitions: list[list[TestId]
|
515 | 514 |
|
516 | 515 | def run_in_subprocess_and_watch(self, tests: list[TestId]):
|
517 | 516 | # noinspection PyUnresolvedReferences
|
518 |
| - use_pipe = not IS_GRAALPY or __graalpython__.posix_module_backend() == 'native' |
519 |
| - use_pipe = False |
| 517 | + use_pipe = sys.platform != 'win32' and (not IS_GRAALPY or __graalpython__.posix_module_backend() == 'native') |
520 | 518 | remaining_tests = tests
|
521 | 519 | last_started_test: TestId | None = None
|
522 | 520 | last_started_time: float | None = None
|
523 |
| - with ( |
524 |
| - tempfile.NamedTemporaryFile(prefix='graalpytest-in-', mode='w+') as tests_file, |
525 |
| - tempfile.NamedTemporaryFile(prefix='graalpytest-out-', mode='w+') as out_file, |
526 |
| - ): |
| 521 | + with tempfile.TemporaryDirectory(prefix='graalpytest-') as tmp_dir: |
| 522 | + tmp_dir = Path(tmp_dir) |
527 | 523 | env = os.environ.copy()
|
528 | 524 | env['IN_PROCESS'] = '1'
|
529 | 525 |
|
530 | 526 | if use_pipe:
|
531 | 527 | pipe, child_pipe = multiprocessing.Pipe()
|
532 | 528 | else:
|
533 |
| - result_file = out_file.name + '.result' |
| 529 | + result_file = tmp_dir / 'result' |
534 | 530 |
|
535 | 531 | def process_event(event):
|
536 | 532 | nonlocal remaining_tests, last_started_test, last_started_time, last_out_pos
|
@@ -560,86 +556,91 @@ def process_event(event):
|
560 | 556 | last_out_pos = event['out_pos']
|
561 | 557 |
|
562 | 558 | while remaining_tests and not self.stop_event.is_set():
|
563 |
| - last_out_pos = out_file.tell() |
564 |
| - cmd = [ |
565 |
| - sys.executable, |
566 |
| - '-u', |
567 |
| - *self.subprocess_args, |
568 |
| - __file__, |
569 |
| - '--tests-file', tests_file.name, |
570 |
| - ] |
571 |
| - if use_pipe: |
572 |
| - cmd += ['--pipe-fd', str(child_pipe.fileno())] |
573 |
| - else: |
574 |
| - cmd += ['--result-file', result_file] |
575 |
| - if self.failfast: |
576 |
| - cmd.append('--failfast') |
577 |
| - # We communicate the tests through a temp file to avoid running into too long commandlines on windows |
578 |
| - tests_file.seek(0) |
579 |
| - tests_file.truncate() |
580 |
| - tests_file.write('\n'.join(map(str, remaining_tests))) |
581 |
| - tests_file.flush() |
582 |
| - process = subprocess.Popen( |
583 |
| - cmd, |
584 |
| - stdout=out_file, |
585 |
| - stderr=out_file, |
586 |
| - env=env, |
587 |
| - pass_fds=[child_pipe.fileno()] if use_pipe else [], |
588 |
| - ) |
589 |
| - |
590 |
| - timed_out = False |
591 |
| - |
592 |
| - if use_pipe: |
593 |
| - while process.poll() is None: |
| 559 | + with ( |
| 560 | + open(tmp_dir / 'out', 'w+') as out_file, |
| 561 | + open(tmp_dir / 'tests', 'w+') as tests_file, |
| 562 | + ): |
| 563 | + last_out_pos = 0 |
| 564 | + cmd = [ |
| 565 | + sys.executable, |
| 566 | + '-u', |
| 567 | + *self.subprocess_args, |
| 568 | + __file__, |
| 569 | + '--tests-file', str(tests_file.name), |
| 570 | + ] |
| 571 | + if use_pipe: |
| 572 | + cmd += ['--pipe-fd', str(child_pipe.fileno())] |
| 573 | + else: |
| 574 | + cmd += ['--result-file', str(result_file)] |
| 575 | + if self.failfast: |
| 576 | + cmd.append('--failfast') |
| 577 | + # We communicate the tests through a temp file to avoid running into too long commandlines on windows |
| 578 | + tests_file.seek(0) |
| 579 | + tests_file.truncate() |
| 580 | + tests_file.write('\n'.join(map(str, remaining_tests))) |
| 581 | + tests_file.flush() |
| 582 | + popen_kwargs: dict = dict( |
| 583 | + stdout=out_file, |
| 584 | + stderr=out_file, |
| 585 | + env=env, |
| 586 | + ) |
| 587 | + if use_pipe: |
| 588 | + popen_kwargs.update(pass_fds=[child_pipe.fileno()]) |
| 589 | + process = subprocess.Popen(cmd, **popen_kwargs) |
| 590 | + |
| 591 | + timed_out = False |
| 592 | + |
| 593 | + if use_pipe: |
| 594 | + while process.poll() is None: |
| 595 | + while pipe.poll(0.1): |
| 596 | + process_event(pipe.recv()) |
| 597 | + if self.stop_event.is_set(): |
| 598 | + interrupt_process(process) |
| 599 | + break |
| 600 | + if last_started_time is not None and time.time() - last_started_time >= self.default_test_timeout: |
| 601 | + interrupt_process(process) |
| 602 | + timed_out = True |
| 603 | + # Drain the pipe |
| 604 | + while pipe.poll(0.1): |
| 605 | + pipe.recv() |
| 606 | + break |
| 607 | + |
| 608 | + returncode = process.wait() |
| 609 | + if self.stop_event.is_set(): |
| 610 | + return |
| 611 | + if use_pipe: |
594 | 612 | while pipe.poll(0.1):
|
595 | 613 | process_event(pipe.recv())
|
596 |
| - if self.stop_event.is_set(): |
597 |
| - interrupt_process(process) |
598 |
| - break |
599 |
| - if last_started_time is not None and time.time() - last_started_time >= self.default_test_timeout: |
600 |
| - interrupt_process(process) |
601 |
| - timed_out = True |
602 |
| - # Drain the pipe |
603 |
| - while pipe.poll(0.1): |
604 |
| - pipe.recv() |
605 |
| - break |
606 |
| - |
607 |
| - returncode = process.wait() |
608 |
| - if self.stop_event.is_set(): |
609 |
| - return |
610 |
| - if use_pipe: |
611 |
| - while pipe.poll(0.1): |
612 |
| - process_event(pipe.recv()) |
613 |
| - else: |
614 |
| - with open(result_file, 'rb') as f: |
615 |
| - for file_event in pickle.load(f): |
616 |
| - process_event(file_event) |
617 |
| - |
618 |
| - if returncode != 0 or timed_out: |
619 |
| - out_file.seek(last_out_pos) |
620 |
| - output = out_file.read() |
621 |
| - if last_started_test: |
622 |
| - if timed_out: |
623 |
| - message = "Timed out" |
624 |
| - elif returncode >= 0: |
625 |
| - message = f"Test process exitted with code {returncode}" |
626 |
| - else: |
627 |
| - try: |
628 |
| - signal_name = signal.Signals(-returncode).name |
629 |
| - except ValueError: |
630 |
| - signal_name = str(-returncode) |
631 |
| - message = f"Test process killed by signal {signal_name}" |
632 |
| - self.report_result(TestResult( |
633 |
| - test_id=last_started_test, |
634 |
| - status=TestStatus.ERROR, |
635 |
| - param=message, |
636 |
| - output=output, |
637 |
| - )) |
638 |
| - continue |
639 | 614 | else:
|
640 |
| - # Crashed outside of tests, don't retry |
641 |
| - self.crashes.append(output or 'Runner subprocess crashed') |
642 |
| - return |
| 615 | + with open(result_file, 'rb') as f: |
| 616 | + for file_event in pickle.load(f): |
| 617 | + process_event(file_event) |
| 618 | + |
| 619 | + if returncode != 0 or timed_out: |
| 620 | + out_file.seek(last_out_pos) |
| 621 | + output = out_file.read() |
| 622 | + if last_started_test: |
| 623 | + if timed_out: |
| 624 | + message = "Timed out" |
| 625 | + elif returncode >= 0: |
| 626 | + message = f"Test process exitted with code {returncode}" |
| 627 | + else: |
| 628 | + try: |
| 629 | + signal_name = signal.Signals(-returncode).name |
| 630 | + except ValueError: |
| 631 | + signal_name = str(-returncode) |
| 632 | + message = f"Test process killed by signal {signal_name}" |
| 633 | + self.report_result(TestResult( |
| 634 | + test_id=last_started_test, |
| 635 | + status=TestStatus.ERROR, |
| 636 | + param=message, |
| 637 | + output=output, |
| 638 | + )) |
| 639 | + continue |
| 640 | + else: |
| 641 | + # Crashed outside of tests, don't retry |
| 642 | + self.crashes.append(output or 'Runner subprocess crashed') |
| 643 | + return |
643 | 644 |
|
644 | 645 |
|
645 | 646 | def filter_tree(test_file: Path, test_suite: unittest.TestSuite, specifiers: list[TestSpecifier],
|
@@ -900,6 +901,7 @@ def in_process():
|
900 | 901 |
|
901 | 902 | data = []
|
902 | 903 | if args.pipe_fd:
|
| 904 | + import multiprocessing.connection |
903 | 905 | conn = multiprocessing.connection.Connection(args.pipe_fd)
|
904 | 906 |
|
905 | 907 | def result_factory(suite):
|
|
0 commit comments