Skip to content

Commit 8849063

Browse files
authored
Merge pull request #374 from espressif/change/fork-to-spawn
refactor: always use multiprocess spawn
2 parents 4fdc9fa + 387b877 commit 8849063

File tree

5 files changed

+45
-32
lines changed

5 files changed

+45
-32
lines changed

pytest-embedded/pytest_embedded/dut.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import functools
22
import logging
3-
import multiprocessing
43
import os.path
54
import re
65
from collections.abc import Callable
@@ -10,7 +9,7 @@
109
import pexpect
1110

1211
from .app import App
13-
from .log import PexpectProcess
12+
from .log import MessageQueue, PexpectProcess
1413
from .unity import UNITY_SUMMARY_LINE_REGEX, TestSuite
1514
from .utils import Meta, _InjectMixinCls, remove_asci_color_code, to_bytes, to_list
1615

@@ -29,7 +28,7 @@ class Dut(_InjectMixinCls):
2928
def __init__(
3029
self,
3130
pexpect_proc: PexpectProcess,
32-
msg_queue: multiprocessing.Queue,
31+
msg_queue: MessageQueue,
3332
app: App,
3433
pexpect_logfile: str,
3534
test_case_name: str,

pytest-embedded/pytest_embedded/dut_factory.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,7 @@ def _drop_none_kwargs(kwargs: dict[t.Any, t.Any]):
2828
return {k: v for k, v in kwargs.items() if v is not None}
2929

3030

31-
if sys.platform == 'darwin':
32-
_ctx = multiprocessing.get_context('fork')
33-
else:
34-
_ctx = multiprocessing.get_context()
31+
_ctx = multiprocessing.get_context('spawn')
3532

3633
_stdout = sys.__stdout__
3734

@@ -45,10 +42,6 @@ def _drop_none_kwargs(kwargs: dict[t.Any, t.Any]):
4542
PARAMETRIZED_FIXTURES_CACHE = {}
4643

4744

48-
def msg_queue_gn() -> MessageQueue:
49-
return MessageQueue()
50-
51-
5245
def _listen(q: MessageQueue, filepath: str, with_timestamp: bool = True, count: int = 1, total: int = 1) -> None:
5346
shall_add_prefix = True
5447
while True:
@@ -741,10 +734,15 @@ def create(
741734
"""
742735
layout = []
743736
try:
744-
global PARAMETRIZED_FIXTURES_CACHE
745-
msg_queue = msg_queue_gn()
737+
from .plugin import _MP_MANAGER # avoid circular import
738+
739+
if _MP_MANAGER is None:
740+
raise SystemExit('The _MP_MANAGER is not initialized, please use this function under pytest.')
741+
742+
msg_queue = _MP_MANAGER.MessageQueue()
746743
layout.append(msg_queue)
747744

745+
global PARAMETRIZED_FIXTURES_CACHE
748746
_pexpect_logfile = os.path.join(
749747
PARAMETRIZED_FIXTURES_CACHE['_meta'].logdir, f'custom-dut-{DUT_GLOBAL_INDEX}.txt'
750748
)

pytest-embedded/pytest_embedded/log.py

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
import multiprocessing
44
import os
55
import subprocess
6-
import sys
76
import tempfile
87
import textwrap
98
import uuid
109
from multiprocessing import queues
10+
from multiprocessing.managers import BaseManager
1111
from typing import AnyStr
1212

1313
import pexpect.fdpexpect
@@ -16,10 +16,11 @@
1616

1717
from .utils import Meta, remove_asci_color_code, to_bytes, to_str, utcnow_str
1818

19-
if sys.platform == 'darwin':
20-
_ctx = multiprocessing.get_context('fork')
21-
else:
22-
_ctx = multiprocessing.get_context()
19+
_ctx = multiprocessing.get_context('spawn')
20+
21+
22+
class MessageQueueManager(BaseManager):
23+
pass
2324

2425

2526
class MessageQueue(queues.Queue):
@@ -40,7 +41,7 @@ def put(self, obj, **kwargs):
4041
_b = to_bytes(obj)
4142
try:
4243
super().put(_b, **kwargs)
43-
except: # noqa # queue might be closed
44+
except Exception: # queue might be closed
4445
pass
4546

4647
def write(self, s: AnyStr):
@@ -53,6 +54,9 @@ def isatty(self):
5354
return True
5455

5556

57+
MessageQueueManager.register('MessageQueue', MessageQueue)
58+
59+
5660
class PexpectProcess(pexpect.fdpexpect.fdspawn):
5761
"""
5862
Use a temp file to gather multiple inputs into one output, and do `pexpect.expect()` from one place.
@@ -146,16 +150,16 @@ def live_print_call(*args, msg_queue: MessageQueue | None = None, expect_returnc
146150

147151
class _PopenRedirectProcess(_ctx.Process):
148152
def __init__(self, msg_queue: MessageQueue, logfile: str):
149-
self._q = msg_queue
150-
151-
self.logfile = logfile
152-
153-
super().__init__(target=self._forward_io, daemon=True) # killed by the main process
153+
super().__init__(target=self._forward_io, args=(msg_queue, logfile), daemon=True)
154154

155-
def _forward_io(self) -> None:
156-
with open(self.logfile) as fr:
155+
@staticmethod
156+
def _forward_io(msg_queue, logfile) -> None:
157+
with open(logfile) as fr:
157158
while True:
158-
self._q.put(fr.read())
159+
try:
160+
msg_queue.put(fr.read()) # msg_queue may be closed
161+
except Exception:
162+
break
159163

160164

161165
class DuplicateStdoutPopen(subprocess.Popen):

pytest-embedded/pytest_embedded/plugin.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,14 @@
3636
app_fn,
3737
dut_gn,
3838
gdb_gn,
39-
msg_queue_gn,
4039
openocd_gn,
4140
pexpect_proc_fn,
4241
qemu_gn,
4342
serial_gn,
4443
set_parametrized_fixtures_cache,
4544
wokwi_gn,
4645
)
47-
from .log import MessageQueue, PexpectProcess
46+
from .log import MessageQueue, MessageQueueManager, PexpectProcess
4847
from .unity import JunitMerger, UnityTestReportMode, escape_illegal_xml_chars
4948
from .utils import (
5049
SERVICE_LIB_NAMES,
@@ -300,6 +299,7 @@ def pytest_addoption(parser):
300299
# helpers #
301300
###########
302301
_COUNT = 1
302+
_MP_MANAGER: MessageQueueManager | None = None
303303

304304

305305
def _gte_one_int(v) -> int:
@@ -630,6 +630,19 @@ def port_app_cache() -> dict[str, str]:
630630
return {}
631631

632632

633+
@pytest.fixture(scope='session', autouse=True)
634+
def _mp_manager():
635+
manager = MessageQueueManager()
636+
manager.start()
637+
638+
global _MP_MANAGER
639+
_MP_MANAGER = manager
640+
641+
yield manager
642+
643+
manager.shutdown()
644+
645+
633646
@pytest.fixture
634647
def test_case_tempdir(test_case_name: str, session_tempdir: str) -> str:
635648
"""Function scoped temp dir for pytest-embedded"""
@@ -668,8 +681,8 @@ def _pexpect_logfile(test_case_tempdir, logfile_extension, dut_index, dut_total)
668681

669682
@pytest.fixture
670683
@multi_dut_generator_fixture
671-
def msg_queue() -> MessageQueue: # kwargs passed by `multi_dut_generator_fixture()`
672-
return msg_queue_gn()
684+
def msg_queue(_mp_manager) -> MessageQueue: # kwargs passed by `multi_dut_generator_fixture()`
685+
return _mp_manager.MessageQueue()
673686

674687

675688
@pytest.fixture

pytest-embedded/tests/test_base.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,6 @@ def test_expect_all_failed(dut):
290290
result.assert_outcomes(passed=10)
291291

292292

293-
@pytest.mark.xfail(reason='unstable')
294293
def test_expect_from_timeout(testdir):
295294
testdir.makepyfile(r"""
296295
import threading

0 commit comments

Comments
 (0)