|
1 | 1 | import contextlib
|
| 2 | +import json |
2 | 3 | import os
|
| 4 | +import os.path |
3 | 5 | import sys
|
4 | 6 | import threading
|
5 | 7 | from textwrap import dedent
|
|
9 | 11 | from test import support
|
10 | 12 | from test.support import import_helper
|
11 | 13 | from test.support import threading_helper
|
| 14 | +from test.support import os_helper |
12 | 15 | _interpreters = import_helper.import_module('_xxsubinterpreters')
|
13 | 16 | _channels = import_helper.import_module('_xxinterpchannels')
|
14 | 17 | from test.support import interpreters
|
@@ -488,6 +491,154 @@ def task():
|
488 | 491 | pass
|
489 | 492 |
|
490 | 493 |
|
| 494 | +class StartupTests(TestBase): |
| 495 | + |
| 496 | + # We want to ensure the initial state of subinterpreters |
| 497 | + # matches expectations. |
| 498 | + |
| 499 | + _subtest_count = 0 |
| 500 | + |
| 501 | + @contextlib.contextmanager |
| 502 | + def subTest(self, *args): |
| 503 | + with super().subTest(*args) as ctx: |
| 504 | + self._subtest_count += 1 |
| 505 | + try: |
| 506 | + yield ctx |
| 507 | + finally: |
| 508 | + if self._debugged_in_subtest: |
| 509 | + if self._subtest_count == 1: |
| 510 | + # The first subtest adds a leading newline, so we |
| 511 | + # compensate here by not printing a trailing newline. |
| 512 | + print('### end subtest debug ###', end='') |
| 513 | + else: |
| 514 | + print('### end subtest debug ###') |
| 515 | + self._debugged_in_subtest = False |
| 516 | + |
| 517 | + def debug(self, msg, *, header=None): |
| 518 | + if header: |
| 519 | + self._debug(f'--- {header} ---') |
| 520 | + if msg: |
| 521 | + if msg.endswith(os.linesep): |
| 522 | + self._debug(msg[:-len(os.linesep)]) |
| 523 | + else: |
| 524 | + self._debug(msg) |
| 525 | + self._debug('<no newline>') |
| 526 | + self._debug('------') |
| 527 | + else: |
| 528 | + self._debug(msg) |
| 529 | + |
| 530 | + _debugged = False |
| 531 | + _debugged_in_subtest = False |
| 532 | + def _debug(self, msg): |
| 533 | + if not self._debugged: |
| 534 | + print() |
| 535 | + self._debugged = True |
| 536 | + if self._subtest is not None: |
| 537 | + if True: |
| 538 | + if not self._debugged_in_subtest: |
| 539 | + self._debugged_in_subtest = True |
| 540 | + print('### start subtest debug ###') |
| 541 | + print(msg) |
| 542 | + else: |
| 543 | + print(msg) |
| 544 | + |
| 545 | + def create_temp_dir(self): |
| 546 | + import tempfile |
| 547 | + tmp = tempfile.mkdtemp(prefix='test_interpreters_') |
| 548 | + tmp = os.path.realpath(tmp) |
| 549 | + self.addCleanup(os_helper.rmtree, tmp) |
| 550 | + return tmp |
| 551 | + |
| 552 | + def write_script(self, *path, text): |
| 553 | + filename = os.path.join(*path) |
| 554 | + dirname = os.path.dirname(filename) |
| 555 | + if dirname: |
| 556 | + os.makedirs(dirname, exist_ok=True) |
| 557 | + with open(filename, 'w', encoding='utf-8') as outfile: |
| 558 | + outfile.write(dedent(text)) |
| 559 | + return filename |
| 560 | + |
| 561 | + @support.requires_subprocess() |
| 562 | + def run_python(self, argv, *, cwd=None): |
| 563 | + # This method is inspired by |
| 564 | + # EmbeddingTestsMixin.run_embedded_interpreter() in test_embed.py. |
| 565 | + import shlex |
| 566 | + import subprocess |
| 567 | + if isinstance(argv, str): |
| 568 | + argv = shlex.split(argv) |
| 569 | + argv = [sys.executable, *argv] |
| 570 | + try: |
| 571 | + proc = subprocess.run( |
| 572 | + argv, |
| 573 | + cwd=cwd, |
| 574 | + capture_output=True, |
| 575 | + text=True, |
| 576 | + ) |
| 577 | + except Exception as exc: |
| 578 | + self.debug(f'# cmd: {shlex.join(argv)}') |
| 579 | + if isinstance(exc, FileNotFoundError) and not exc.filename: |
| 580 | + if os.path.exists(argv[0]): |
| 581 | + exists = 'exists' |
| 582 | + else: |
| 583 | + exists = 'does not exist' |
| 584 | + self.debug(f'{argv[0]} {exists}') |
| 585 | + raise # re-raise |
| 586 | + assert proc.stderr == '' or proc.returncode != 0, proc.stderr |
| 587 | + if proc.returncode != 0 and support.verbose: |
| 588 | + self.debug(f'# python3 {shlex.join(argv[1:])} failed:') |
| 589 | + self.debug(proc.stdout, header='stdout') |
| 590 | + self.debug(proc.stderr, header='stderr') |
| 591 | + self.assertEqual(proc.returncode, 0) |
| 592 | + self.assertEqual(proc.stderr, '') |
| 593 | + return proc.stdout |
| 594 | + |
| 595 | + def test_sys_path_0(self): |
| 596 | + # The main interpreter's sys.path[0] should be used by subinterpreters. |
| 597 | + script = ''' |
| 598 | + import sys |
| 599 | + from test.support import interpreters |
| 600 | +
|
| 601 | + orig = sys.path[0] |
| 602 | +
|
| 603 | + interp = interpreters.create() |
| 604 | + interp.run(f"""if True: |
| 605 | + import json |
| 606 | + import sys |
| 607 | + print(json.dumps({{ |
| 608 | + 'main': {orig!r}, |
| 609 | + 'sub': sys.path[0], |
| 610 | + }}, indent=4), flush=True) |
| 611 | + """) |
| 612 | + ''' |
| 613 | + # <tmp>/ |
| 614 | + # pkg/ |
| 615 | + # __init__.py |
| 616 | + # __main__.py |
| 617 | + # script.py |
| 618 | + # script.py |
| 619 | + cwd = self.create_temp_dir() |
| 620 | + self.write_script(cwd, 'pkg', '__init__.py', text='') |
| 621 | + self.write_script(cwd, 'pkg', '__main__.py', text=script) |
| 622 | + self.write_script(cwd, 'pkg', 'script.py', text=script) |
| 623 | + self.write_script(cwd, 'script.py', text=script) |
| 624 | + |
| 625 | + cases = [ |
| 626 | + ('script.py', cwd), |
| 627 | + ('-m script', cwd), |
| 628 | + ('-m pkg', cwd), |
| 629 | + ('-m pkg.script', cwd), |
| 630 | + ('-c "import script"', ''), |
| 631 | + ] |
| 632 | + for argv, expected in cases: |
| 633 | + with self.subTest(f'python3 {argv}'): |
| 634 | + out = self.run_python(argv, cwd=cwd) |
| 635 | + data = json.loads(out) |
| 636 | + sp0_main, sp0_sub = data['main'], data['sub'] |
| 637 | + self.assertEqual(sp0_sub, sp0_main) |
| 638 | + self.assertEqual(sp0_sub, expected) |
| 639 | + # XXX Also check them all with the -P cmdline flag? |
| 640 | + |
| 641 | + |
491 | 642 | class FinalizationTests(TestBase):
|
492 | 643 |
|
493 | 644 | def test_gh_109793(self):
|
|
0 commit comments