File: test_unbuffered_stdio.py

package info (click to toggle)
pyinstaller 6.18.0%2Bds-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 11,824 kB
  • sloc: python: 41,828; ansic: 12,123; makefile: 171; sh: 131; xml: 19
file content (80 lines) | stat: -rw-r--r-- 2,828 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#-----------------------------------------------------------------------------
# Copyright (c) 2021-2023, PyInstaller Development Team.
#
# Distributed under the terms of the GNU General Public License (version 2
# or later) with exception for distributing the bootloader.
#
# The full license is in the file COPYING.txt, distributed with this software.
#
# SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
#-----------------------------------------------------------------------------
"""
Test for unbuffered stdio (stdout/stderr) mode.
"""

import os
import asyncio

import pytest

from PyInstaller.compat import is_win


@pytest.mark.parametrize('stream_mode', ['binary', 'text'])
@pytest.mark.parametrize('output_stream', ['stdout', 'stderr'])
def test_unbuffered_stdio(tmp_path, output_stream, stream_mode, pyi_builder_spec):
    # Freeze the test program; test_spec() builds the app and runs it, so explicitly set the number of
    # stars to 0 for this run.
    pyi_builder_spec.test_spec('pyi_unbuffered_output.spec', app_args=['--num-stars', '0'])

    # Path to the frozen executable
    executable = os.path.join(tmp_path, 'dist', 'pyi_unbuffered_output', 'pyi_unbuffered_output')

    # Expected number of stars
    EXPECTED_STARS = 5

    # Run the test program via asyncio.SubprocessProtocol and monitor the output.
    class SubprocessDotCounter(asyncio.SubprocessProtocol):
        def __init__(self, loop, output='stdout'):
            self.count = 0
            self.loop = loop
            # Select stdout vs stderr
            assert output in {'stdout', 'stderr'}
            self.out_fd = 1 if output == 'stdout' else 2

        def pipe_data_received(self, fd, data):
            if fd == self.out_fd:
                # Treat any data batch that does not end with the * as irregularity
                if not data.endswith(b'*'):
                    return
                self.count += data.count(b'*')

        def connection_lost(self, exc):
            self.loop.stop()  # end loop.run_forever()

    # Create event loop
    if is_win:
        loop = asyncio.ProactorEventLoop()  # for subprocess' pipes on Windows
    else:
        loop = asyncio.SelectorEventLoop()
    asyncio.set_event_loop(loop)

    counter_proto = SubprocessDotCounter(loop, output=output_stream)

    # Run
    try:
        proc = loop.subprocess_exec(
            lambda: counter_proto,
            executable,
            "--num-stars", str(EXPECTED_STARS),
            "--output-stream", output_stream,
            "--stream-mode", stream_mode
        )  # yapf: disable
        transport, _ = loop.run_until_complete(proc)
        loop.run_forever()
    finally:
        loop.close()
        transport.close()

    # Check the number of received stars
    assert counter_proto.count == EXPECTED_STARS