pytest-embedded 2.0.0a0__tar.gz → 2.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytest-embedded
3
- Version: 2.0.0a0
3
+ Version: 2.1.0
4
4
  Summary: A pytest plugin that designed for embedded testing.
5
5
  Author-email: Fu Hanxi <fuhanxi@espressif.com>
6
6
  Requires-Python: >=3.10
@@ -6,4 +6,4 @@ from .dut_factory import DutFactory
6
6
 
7
7
  __all__ = ['App', 'Dut', 'DutFactory']
8
8
 
9
- __version__ = '2.0.0a0'
9
+ __version__ = '2.1.0'
@@ -1,6 +1,5 @@
1
1
  import functools
2
2
  import logging
3
- import multiprocessing
4
3
  import os.path
5
4
  import re
6
5
  from collections.abc import Callable
@@ -10,7 +9,7 @@ from typing import AnyStr
10
9
  import pexpect
11
10
 
12
11
  from .app import App
13
- from .log import PexpectProcess
12
+ from .log import MessageQueue, PexpectProcess
14
13
  from .unity import UNITY_SUMMARY_LINE_REGEX, TestSuite
15
14
  from .utils import Meta, _InjectMixinCls, remove_asci_color_code, to_bytes, to_list
16
15
 
@@ -29,7 +28,7 @@ class Dut(_InjectMixinCls):
29
28
  def __init__(
30
29
  self,
31
30
  pexpect_proc: PexpectProcess,
32
- msg_queue: multiprocessing.Queue,
31
+ msg_queue: MessageQueue,
33
32
  app: App,
34
33
  pexpect_logfile: str,
35
34
  test_case_name: str,
@@ -28,10 +28,7 @@ def _drop_none_kwargs(kwargs: dict[t.Any, t.Any]):
28
28
  return {k: v for k, v in kwargs.items() if v is not None}
29
29
 
30
30
 
31
- if sys.platform == 'darwin':
32
- _ctx = multiprocessing.get_context('fork')
33
- else:
34
- _ctx = multiprocessing.get_context()
31
+ _ctx = multiprocessing.get_context('spawn')
35
32
 
36
33
  _stdout = sys.__stdout__
37
34
 
@@ -45,10 +42,6 @@ DUT_GLOBAL_INDEX = 0
45
42
  PARAMETRIZED_FIXTURES_CACHE = {}
46
43
 
47
44
 
48
- def msg_queue_gn() -> MessageQueue:
49
- return MessageQueue()
50
-
51
-
52
45
  def _listen(q: MessageQueue, filepath: str, with_timestamp: bool = True, count: int = 1, total: int = 1) -> None:
53
46
  shall_add_prefix = True
54
47
  while True:
@@ -145,6 +138,7 @@ def _fixture_classes_and_options_fn(
145
138
  qemu_prog_path,
146
139
  qemu_cli_args,
147
140
  qemu_extra_args,
141
+ qemu_efuse_path,
148
142
  wokwi_diagram,
149
143
  skip_regenerate_image,
150
144
  encrypt,
@@ -179,6 +173,7 @@ def _fixture_classes_and_options_fn(
179
173
  'encrypt': encrypt,
180
174
  'keyfile': keyfile,
181
175
  'qemu_prog_path': qemu_prog_path,
176
+ 'qemu_efuse_path': qemu_efuse_path,
182
177
  }
183
178
  )
184
179
  else:
@@ -310,6 +305,7 @@ def _fixture_classes_and_options_fn(
310
305
  'qemu_prog_path': qemu_prog_path,
311
306
  'qemu_cli_args': qemu_cli_args,
312
307
  'qemu_extra_args': qemu_extra_args,
308
+ 'qemu_efuse_path': qemu_efuse_path,
313
309
  'app': None,
314
310
  'meta': _meta,
315
311
  'dut_index': dut_index,
@@ -681,6 +677,7 @@ class DutFactory:
681
677
  qemu_prog_path: str | None = None,
682
678
  qemu_cli_args: str | None = None,
683
679
  qemu_extra_args: str | None = None,
680
+ qemu_efuse_path: str | None = None,
684
681
  wokwi_diagram: str | None = None,
685
682
  skip_regenerate_image: bool | None = None,
686
683
  encrypt: bool | None = None,
@@ -727,6 +724,7 @@ class DutFactory:
727
724
  qemu_prog_path: QEMU program path.
728
725
  qemu_cli_args: QEMU CLI arguments.
729
726
  qemu_extra_args: Additional QEMU arguments.
727
+ qemu_efuse_path: Efuse binary path.
730
728
  wokwi_diagram: Wokwi diagram path.
731
729
  skip_regenerate_image: Skip image regeneration flag.
732
730
  encrypt: Encryption flag.
@@ -741,10 +739,15 @@ class DutFactory:
741
739
  """
742
740
  layout = []
743
741
  try:
744
- global PARAMETRIZED_FIXTURES_CACHE
745
- msg_queue = msg_queue_gn()
742
+ from .plugin import _MP_MANAGER # avoid circular import
743
+
744
+ if _MP_MANAGER is None:
745
+ raise SystemExit('The _MP_MANAGER is not initialized, please use this function under pytest.')
746
+
747
+ msg_queue = _MP_MANAGER.MessageQueue()
746
748
  layout.append(msg_queue)
747
749
 
750
+ global PARAMETRIZED_FIXTURES_CACHE
748
751
  _pexpect_logfile = os.path.join(
749
752
  PARAMETRIZED_FIXTURES_CACHE['_meta'].logdir, f'custom-dut-{DUT_GLOBAL_INDEX}.txt'
750
753
  )
@@ -789,6 +792,7 @@ class DutFactory:
789
792
  'qemu_prog_path': qemu_prog_path,
790
793
  'qemu_cli_args': qemu_cli_args,
791
794
  'qemu_extra_args': qemu_extra_args,
795
+ 'qemu_efuse_path': qemu_efuse_path,
792
796
  'wokwi_diagram': wokwi_diagram,
793
797
  'skip_regenerate_image': skip_regenerate_image,
794
798
  'encrypt': encrypt,
@@ -3,11 +3,11 @@ import logging
3
3
  import multiprocessing
4
4
  import os
5
5
  import subprocess
6
- import sys
7
6
  import tempfile
8
7
  import textwrap
9
8
  import uuid
10
9
  from multiprocessing import queues
10
+ from multiprocessing.managers import BaseManager
11
11
  from typing import AnyStr
12
12
 
13
13
  import pexpect.fdpexpect
@@ -16,10 +16,11 @@ from pexpect.utils import poll_ignore_interrupts, select_ignore_interrupts
16
16
 
17
17
  from .utils import Meta, remove_asci_color_code, to_bytes, to_str, utcnow_str
18
18
 
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
23
24
 
24
25
 
25
26
  class MessageQueue(queues.Queue):
@@ -40,7 +41,7 @@ class MessageQueue(queues.Queue):
40
41
  _b = to_bytes(obj)
41
42
  try:
42
43
  super().put(_b, **kwargs)
43
- except: # noqa # queue might be closed
44
+ except Exception: # queue might be closed
44
45
  pass
45
46
 
46
47
  def write(self, s: AnyStr):
@@ -53,6 +54,9 @@ class MessageQueue(queues.Queue):
53
54
  return True
54
55
 
55
56
 
57
+ MessageQueueManager.register('MessageQueue', MessageQueue)
58
+
59
+
56
60
  class PexpectProcess(pexpect.fdpexpect.fdspawn):
57
61
  """
58
62
  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
146
150
 
147
151
  class _PopenRedirectProcess(_ctx.Process):
148
152
  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)
154
154
 
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:
157
158
  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
159
163
 
160
164
 
161
165
  class DuplicateStdoutPopen(subprocess.Popen):
@@ -36,7 +36,6 @@ from .dut_factory import (
36
36
  app_fn,
37
37
  dut_gn,
38
38
  gdb_gn,
39
- msg_queue_gn,
40
39
  openocd_gn,
41
40
  pexpect_proc_fn,
42
41
  qemu_gn,
@@ -44,7 +43,7 @@ from .dut_factory import (
44
43
  set_parametrized_fixtures_cache,
45
44
  wokwi_gn,
46
45
  )
47
- from .log import MessageQueue, PexpectProcess
46
+ from .log import MessageQueue, MessageQueueManager, PexpectProcess
48
47
  from .unity import JunitMerger, UnityTestReportMode, escape_illegal_xml_chars
49
48
  from .utils import (
50
49
  SERVICE_LIB_NAMES,
@@ -275,6 +274,10 @@ def pytest_addoption(parser):
275
274
  '--qemu-extra-args',
276
275
  help='QEMU cli extra arguments, will append to the argument list. (Default: None)',
277
276
  )
277
+ qemu_group.addoption(
278
+ '--qemu-efuse-path',
279
+ help='This option makes it possible to use efuse in QEMU when it is set up.',
280
+ )
278
281
  qemu_group.addoption(
279
282
  '--skip-regenerate-image',
280
283
  help='y/yes/true for True and n/no/false for False. '
@@ -300,6 +303,7 @@ def pytest_addoption(parser):
300
303
  # helpers #
301
304
  ###########
302
305
  _COUNT = 1
306
+ _MP_MANAGER: MessageQueueManager | None = None
303
307
 
304
308
 
305
309
  def _gte_one_int(v) -> int:
@@ -630,6 +634,19 @@ def port_app_cache() -> dict[str, str]:
630
634
  return {}
631
635
 
632
636
 
637
+ @pytest.fixture(scope='session', autouse=True)
638
+ def _mp_manager():
639
+ manager = MessageQueueManager()
640
+ manager.start()
641
+
642
+ global _MP_MANAGER
643
+ _MP_MANAGER = manager
644
+
645
+ yield manager
646
+
647
+ manager.shutdown()
648
+
649
+
633
650
  @pytest.fixture
634
651
  def test_case_tempdir(test_case_name: str, session_tempdir: str) -> str:
635
652
  """Function scoped temp dir for pytest-embedded"""
@@ -668,8 +685,8 @@ def _pexpect_logfile(test_case_tempdir, logfile_extension, dut_index, dut_total)
668
685
 
669
686
  @pytest.fixture
670
687
  @multi_dut_generator_fixture
671
- def msg_queue() -> MessageQueue: # kwargs passed by `multi_dut_generator_fixture()`
672
- return msg_queue_gn()
688
+ def msg_queue(_mp_manager) -> MessageQueue: # kwargs passed by `multi_dut_generator_fixture()`
689
+ return _mp_manager.MessageQueue()
673
690
 
674
691
 
675
692
  @pytest.fixture
@@ -950,6 +967,13 @@ def qemu_extra_args(request: FixtureRequest) -> str | None:
950
967
  return _request_param_or_config_option_or_default(request, 'qemu_extra_args', None)
951
968
 
952
969
 
970
+ @pytest.fixture
971
+ @multi_dut_argument
972
+ def qemu_efuse_path(request: FixtureRequest) -> str | None:
973
+ """Enable parametrization for the same cli option"""
974
+ return _request_param_or_config_option_or_default(request, 'qemu_efuse_path', None)
975
+
976
+
953
977
  @pytest.fixture
954
978
  @multi_dut_argument
955
979
  def skip_regenerate_image(request: FixtureRequest) -> str | None:
@@ -1037,6 +1061,7 @@ def parametrize_fixtures(
1037
1061
  qemu_prog_path,
1038
1062
  qemu_cli_args,
1039
1063
  qemu_extra_args,
1064
+ qemu_efuse_path,
1040
1065
  wokwi_diagram,
1041
1066
  skip_regenerate_image,
1042
1067
  encrypt,
@@ -290,7 +290,6 @@ def test_expect(testdir):
290
290
  result.assert_outcomes(passed=10)
291
291
 
292
292
 
293
- @pytest.mark.xfail(reason='unstable')
294
293
  def test_expect_from_timeout(testdir):
295
294
  testdir.makepyfile(r"""
296
295
  import threading