pytest-embedded 1.2.5__tar.gz → 1.3.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: 1.2
2
2
  Name: pytest-embedded
3
- Version: 1.2.5
3
+ Version: 1.3.0
4
4
  Summary: pytest embedded plugin
5
5
  Home-page: https://docs.espressif.com/projects/pytest-embedded/en/latest/
6
6
  Author: Fu Hanxi
@@ -9,10 +9,10 @@ import pexpect
9
9
  from .app import App
10
10
  from .log import PexpectProcess
11
11
  from .unity import UNITY_SUMMARY_LINE_REGEX, TestSuite
12
- from .utils import Meta, remove_asci_color_code, to_bytes, to_list
12
+ from .utils import Meta, _InjectMixinCls, remove_asci_color_code, to_bytes, to_list
13
13
 
14
14
 
15
- class Dut:
15
+ class Dut(_InjectMixinCls):
16
16
  """
17
17
  Device under test (DUT) base class
18
18
 
@@ -160,7 +160,7 @@ class Dut:
160
160
  - Would raise TIMEOUT exception at the end of the test if any unity test case execution took longer
161
161
  than timeout value
162
162
 
163
- Warnings:
163
+ Warning:
164
164
  - All unity test cases record would be missed if the final report block is uncaught.
165
165
  """
166
166
  self.expect(UNITY_SUMMARY_LINE_REGEX, timeout=timeout)
@@ -174,3 +174,25 @@ class Dut:
174
174
  log = remove_asci_color_code(log)
175
175
 
176
176
  self.testsuite.add_unity_test_cases(log)
177
+
178
+ @_InjectMixinCls.require_services('idf')
179
+ def run_all_single_board_cases(
180
+ self,
181
+ group: Optional[str] = None,
182
+ reset: bool = False,
183
+ timeout: float = 30,
184
+ run_ignore_cases: bool = False,
185
+ ):
186
+ """
187
+ Run all multi_stage cases
188
+
189
+ Args:
190
+ group: test case group
191
+ reset: whether to perform a hardware reset before running a case
192
+ timeout: timeout. (Default: 30 seconds)
193
+ run_ignore_cases: run ignored test cases or not
194
+
195
+ Warning:
196
+ requires enable service `idf`
197
+ """
198
+ pass
@@ -216,7 +216,7 @@ def pytest_addoption(parser):
216
216
  )
217
217
  qemu_group.addoption(
218
218
  '--qemu-cli-args',
219
- help='QEMU cli default arguments. (Default: "-nographic -no-reboot -machine esp32")',
219
+ help='QEMU cli default arguments. (Default: "-nographic -machine esp32")',
220
220
  )
221
221
  qemu_group.addoption(
222
222
  '--qemu-extra-args',
@@ -227,6 +227,15 @@ def pytest_addoption(parser):
227
227
  help='y/yes/true for True and n/no/false for False. '
228
228
  'Set to True to disable auto regenerate image. (Default: False)',
229
229
  )
230
+ qemu_group.addoption(
231
+ '--encrypt',
232
+ help='y/yes/true for True and n/no/false for False. '
233
+ 'Set to True for pre-encryption workflow (Default: False)',
234
+ )
235
+ qemu_group.addoption(
236
+ '--keyfile',
237
+ help='Flash Encryption (pre-encrypted workflow) key path. (Default: None)',
238
+ )
230
239
 
231
240
 
232
241
  ###########
@@ -898,6 +907,20 @@ def skip_regenerate_image(request: FixtureRequest) -> t.Optional[str]:
898
907
  return _request_param_or_config_option_or_default(request, 'skip_regenerate_image', None)
899
908
 
900
909
 
910
+ @pytest.fixture
911
+ @multi_dut_argument
912
+ def encrypt(request: FixtureRequest) -> t.Optional[str]:
913
+ """Enable parametrization for the same cli option"""
914
+ return _request_param_or_config_option_or_default(request, 'encrypt', None)
915
+
916
+
917
+ @pytest.fixture
918
+ @multi_dut_argument
919
+ def keyfile(request: FixtureRequest) -> t.Optional[str]:
920
+ """Enable parametrization for the same cli option"""
921
+ return _request_param_or_config_option_or_default(request, 'keyfile', None)
922
+
923
+
901
924
  ####################
902
925
  # Private Fixtures #
903
926
  ####################
@@ -924,6 +947,7 @@ def _services(embedded_services: t.Optional[str]) -> t.List[str]:
924
947
  @dataclass
925
948
  class ClassCliOptions:
926
949
  classes: t.Dict[str, type]
950
+ mixins: t.Dict[str, t.List[type]]
927
951
  kwargs: t.Dict[str, t.Dict[str, t.Any]]
928
952
 
929
953
 
@@ -958,6 +982,8 @@ def _fixture_classes_and_options(
958
982
  qemu_cli_args,
959
983
  qemu_extra_args,
960
984
  skip_regenerate_image,
985
+ encrypt,
986
+ keyfile,
961
987
  # pre-initialized fixtures
962
988
  dut_index,
963
989
  _pexpect_logfile,
@@ -985,6 +1011,7 @@ def _fixture_classes_and_options(
985
1011
  }
986
1012
  """
987
1013
  classes: t.Dict[str, type] = {}
1014
+ mixins: t.Dict[str, t.List[type]] = defaultdict(list)
988
1015
  kwargs: t.Dict[str, t.Dict[str, t.Any]] = defaultdict(dict)
989
1016
 
990
1017
  for fixture in FIXTURES_SERVICES.keys():
@@ -1001,6 +1028,8 @@ def _fixture_classes_and_options(
1001
1028
  'part_tool': part_tool,
1002
1029
  'qemu_image_path': qemu_image_path,
1003
1030
  'skip_regenerate_image': skip_regenerate_image,
1031
+ 'encrypt': encrypt,
1032
+ 'keyfile': keyfile,
1004
1033
  }
1005
1034
  )
1006
1035
  else:
@@ -1099,16 +1128,23 @@ def _fixture_classes_and_options(
1099
1128
  }
1100
1129
  elif fixture == 'qemu':
1101
1130
  if 'qemu' in _services:
1102
- from pytest_embedded_qemu import DEFAULT_IMAGE_FN, Qemu
1131
+ from pytest_embedded_qemu import (
1132
+ DEFAULT_IMAGE_FN,
1133
+ ENCRYPTED_IMAGE_FN,
1134
+ Qemu,
1135
+ )
1103
1136
 
1104
1137
  classes[fixture] = Qemu
1105
1138
  kwargs[fixture] = {
1106
1139
  'msg_queue': msg_queue,
1107
1140
  'qemu_image_path': qemu_image_path
1108
- or os.path.join(app_path or '', build_dir or 'build', DEFAULT_IMAGE_FN),
1141
+ or os.path.join(
1142
+ app_path or '', build_dir or 'build', ENCRYPTED_IMAGE_FN if encrypt else DEFAULT_IMAGE_FN
1143
+ ),
1109
1144
  'qemu_prog_path': qemu_prog_path,
1110
1145
  'qemu_cli_args': qemu_cli_args,
1111
1146
  'qemu_extra_args': qemu_extra_args,
1147
+ 'app': None,
1112
1148
  'meta': _meta,
1113
1149
  }
1114
1150
  elif fixture == 'dut':
@@ -1130,6 +1166,11 @@ def _fixture_classes_and_options(
1130
1166
  'qemu': None,
1131
1167
  }
1132
1168
  )
1169
+
1170
+ if 'idf' in _services:
1171
+ from pytest_embedded_idf.unity_tester import IdfUnityDutMixin
1172
+
1173
+ mixins[fixture].append(IdfUnityDutMixin)
1133
1174
  elif 'jtag' in _services:
1134
1175
  if 'idf' in _services:
1135
1176
  from pytest_embedded_idf import IdfDut
@@ -1169,7 +1210,7 @@ def _fixture_classes_and_options(
1169
1210
  }
1170
1211
  )
1171
1212
 
1172
- return ClassCliOptions(classes, kwargs)
1213
+ return ClassCliOptions(classes, mixins, kwargs)
1173
1214
 
1174
1215
 
1175
1216
  ####################
@@ -1268,6 +1309,7 @@ def dut(
1268
1309
  return cls(**kwargs)
1269
1310
 
1270
1311
  cls = _fixture_classes_and_options.classes['dut']
1312
+ mixins = _fixture_classes_and_options.mixins['dut']
1271
1313
  kwargs = _fixture_classes_and_options.kwargs['dut']
1272
1314
 
1273
1315
  for k, v in kwargs.items():
@@ -1283,7 +1325,7 @@ def dut(
1283
1325
  elif k == 'qemu':
1284
1326
  kwargs[k] = qemu
1285
1327
 
1286
- return cls(**_drop_none_kwargs(kwargs))
1328
+ return cls(**_drop_none_kwargs(kwargs), mixins=mixins)
1287
1329
 
1288
1330
 
1289
1331
  @pytest.fixture
@@ -33,6 +33,12 @@ FIXTURES_SERVICES = {
33
33
 
34
34
  _T = t.TypeVar('_T')
35
35
 
36
+ _MIXIN_REQUIRED_SERVICES = {
37
+ 'IdfUnityMixin': ['idf'],
38
+ }
39
+
40
+ _MIXIN_REQUIRED_SERVICES_KEY = '_based_on_services'
41
+
36
42
 
37
43
  #######################
38
44
  # Errors and Warnings #
@@ -54,6 +60,16 @@ class PackageNotInstalledError(SystemExit):
54
60
  )
55
61
 
56
62
 
63
+ class RequireServiceError(SystemExit):
64
+ def __init__(self, func_name: str, services: t.Union[str, t.List[str]]) -> None:
65
+ services_str = ','.join(to_list(services))
66
+ super().__init__(
67
+ f'function {func_name} requires enabling one of the service(s) {services_str}. '
68
+ f'Please enable by passing CLI options "--embedded-services {services_str}". '
69
+ f'For more details, please refer to "pytest --help".'
70
+ )
71
+
72
+
57
73
  #####################
58
74
  # Utility Functions #
59
75
  #####################
@@ -245,3 +261,80 @@ def lazy_load(
245
261
  raise AttributeError('Attribute %s not found in module %s', object_name, base_module.__name__)
246
262
 
247
263
  return __getattr__
264
+
265
+
266
+ class _InjectMixinMeta(type):
267
+ def __call__(cls, *args, **kwargs):
268
+ try:
269
+ mixins = kwargs.pop('mixins', None)
270
+ if mixins:
271
+ mixins = to_list(mixins)
272
+ name = cls.__name__ + 'With' + 'And'.join([m.__name__ for m in mixins])
273
+ cls = type(name, tuple([*mixins, cls]), {})
274
+
275
+ # users should only know concept "services", not mixins
276
+ _based_on_services = set()
277
+ for m in mixins:
278
+ if m.__name__ not in _MIXIN_REQUIRED_SERVICES:
279
+ continue
280
+
281
+ _based_on_services.update(to_list(_MIXIN_REQUIRED_SERVICES[m.__name__]))
282
+ kwargs[_MIXIN_REQUIRED_SERVICES_KEY] = sorted(_based_on_services)
283
+ except KeyError:
284
+ pass
285
+ return type.__call__(cls, *args, **kwargs)
286
+
287
+
288
+ class _InjectMixinCls(metaclass=_InjectMixinMeta):
289
+ """
290
+ This class provide a check function `require_services()` to check if the function is injected by
291
+ enabling one of the required services.
292
+
293
+ The benefits are:
294
+ - provide the autocompletion for the functions provided by the mixins
295
+ - check the requirement at runtime
296
+
297
+ Examples:
298
+
299
+ class IdfUnityMixin:
300
+ def foo(self):
301
+ print('foo from MixinOne')
302
+
303
+
304
+ class Test(_InjectMixinCls):
305
+ def __init__(self, **kwargs):
306
+ for k, v in kwargs.items():
307
+ setattr(self, k, v)
308
+
309
+ @_InjectMixinCls.require_services('idf')
310
+ def foo(self):
311
+ pass
312
+
313
+
314
+ # mro(): TestWithIdfUnityMixin, IdfUnityMixin, Test, object
315
+ s1 = Test(mixins=IdfUnityMixin)
316
+ # mro(): Test, object
317
+ s2 = Test()
318
+ s1.foo() # foo from IdfUnityMixin
319
+ s2.foo() # function foo requires enabling one of the service(s) idf.
320
+ """
321
+
322
+ def require_services(*services):
323
+ def decorator(func):
324
+ def wrapped(self, *args, **kwargs):
325
+ based = False
326
+ for service in services:
327
+ if hasattr(self, _MIXIN_REQUIRED_SERVICES_KEY) and service in getattr(
328
+ self, _MIXIN_REQUIRED_SERVICES_KEY
329
+ ):
330
+ based = True
331
+ break
332
+
333
+ if based:
334
+ return func(self, *args, **kwargs)
335
+ else:
336
+ raise RequireServiceError(func.__name__, services)
337
+
338
+ return wrapped
339
+
340
+ return decorator
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 1.2
2
2
  Name: pytest-embedded
3
- Version: 1.2.5
3
+ Version: 1.3.0
4
4
  Summary: pytest embedded plugin
5
5
  Home-page: https://docs.espressif.com/projects/pytest-embedded/en/latest/
6
6
  Author: Fu Hanxi