diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7d91954a4..bb01aabfe 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -25,12 +25,6 @@ jobs: fail-fast: false matrix: include: - - tox_env: py27-m_ans-ans2.10 - - tox_env: py27-m_ans-ans4 - - - tox_env: py36-m_ans-ans2.10 - - tox_env: py36-m_ans-ans4 - - tox_env: py27-m_mtg - tox_env: py36-m_mtg @@ -86,34 +80,6 @@ jobs: fail-fast: false matrix: include: - - tox_env: py311-m_ans-ans2.10 - python_version: '3.11' - - tox_env: py311-m_ans-ans3 - python_version: '3.11' - - tox_env: py311-m_ans-ans4 - python_version: '3.11' - - tox_env: py311-m_ans-ans5 - python_version: '3.11' - - tox_env: py313-m_ans-ans6 - python_version: '3.13' - - tox_env: py313-m_ans-ans7 - python_version: '3.13' - - tox_env: py313-m_ans-ans8 - python_version: '3.13' - - tox_env: py314-m_ans-ans9 - python_version: '3.14' - - tox_env: py314-m_ans-ans10 - python_version: '3.14' - - tox_env: py314-m_ans-ans11 - python_version: '3.14' - - tox_env: py314-m_ans-ans12 - python_version: '3.14' - - tox_env: py314-m_ans-ans13 - python_version: '3.14' - - - tox_env: py314-m_ans-ans13-s_lin - python_version: '3.14' - - tox_env: py314-m_mtg python_version: '3.14' @@ -161,11 +127,6 @@ jobs: fail-fast: false matrix: include: - - tox_env: py314-m_lcl-ans13 - python_version: '3.14' - - tox_env: py314-m_lcl-ans13-s_lin - python_version: '3.14' - - tox_env: py314-m_mtg python_version: '3.14' diff --git a/mitogen/parent.py b/mitogen/parent.py index 6e30b1c62..66e7a8796 100644 --- a/mitogen/parent.py +++ b/mitogen/parent.py @@ -1415,7 +1415,6 @@ def __repr__(self): # W: write side of interpreter stdin. # r: read side of core_src FD. # w: write side of core_src FD. - # C: the decompressed core source. # Final os.close(STDOUT_FILENO) to avoid --py-debug build corrupting stream with # "[1234 refs]" during exit. @@ -1437,8 +1436,17 @@ def _first_stage(): os.environ['ARGV0']=sys.executable os.execl(sys.executable,sys.executable+'(mitogen:%s)'%sys.argv[2]) os.write(1,'MITO000\n'.encode()) + # Read `len(compressed preamble)` bytes sent by our Mitogen parent. + # `select()` handles non-blocking stdin (e.g. sudo + log_output). + # `C` accumulates compressed bytes. + # `c` supports detecting EOF (`.read(...) -> b''`). C=''.encode() - while int(sys.argv[3])-len(C)and select.select([0],[],[]):C+=os.read(0,int(sys.argv[3])-len(C)) + c=1 + while c and int(sys.argv[3])-len(C)and select.select([0],[],[]): + c=os.read(0,int(sys.argv[3])-len(C)) + # Raises `TypeError` if non-blocking read returns `None` + C+=c + # Raises `zlib.error` if compressed preamble is truncated or invalid C=zlib.decompress(C) f=os.fdopen(W,'wb',0) f.write(C) @@ -1463,8 +1471,9 @@ def get_python_argv(self): return [self.options.python_path] def get_boot_command(self): - source = inspect.getsource(self._first_stage) - source = textwrap.dedent('\n'.join(source.strip().split('\n')[2:])) + lines = inspect.getsourcelines(self._first_stage)[0][2:] + # Remove line comments, leading indentation, trailing newline + source = textwrap.dedent(''.join(s for s in lines if '#' not in s))[:-1] source = source.replace(' ', ' ') compressor = zlib.compressobj( zlib.Z_BEST_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS, diff --git a/tests/first_stage_test.py b/tests/first_stage_test.py index 2576ec14d..4f3d77465 100644 --- a/tests/first_stage_test.py +++ b/tests/first_stage_test.py @@ -50,3 +50,30 @@ def test_valid_syntax(self): ) finally: fp.close() + + def test_stage(self): + options = mitogen.parent.Options(max_message_size=123) + conn = mitogen.parent.Connection(options, self.router) + conn.context = mitogen.core.Context(None, 123) + + proc = subprocess.Popen( + args=conn.get_boot_command(), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + stdin=subprocess.PIPE, + ) + stdout, stderr = proc.communicate(conn.get_preamble()) + + # proc is killed by SIGTERM as the broker kills itself + self.assertEqual(-15, proc.returncode) + expected_stdout_prefix = b("%s\n%s\n%s\n") % ( + mitogen.parent.BootstrapProtocol.EC0_MARKER, + mitogen.parent.BootstrapProtocol.EC1_MARKER, + mitogen.parent.BootstrapProtocol.EC2_MARKER, + ) + self.assertTrue( + stdout.startswith(expected_stdout_prefix), + "%r not at start of %r" % (expected_stdout_prefix, stdout), + ) + self.assertIn(b'shutting down', stdout) + self.assertEqual(b(""), stderr)