esp-test-utils 0.2.2__tar.gz → 0.2.3__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.
Files changed (114) hide show
  1. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/CHANGELOG.md +7 -0
  2. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/PKG-INFO +1 -1
  3. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esp_test_utils.egg-info/PKG-INFO +1 -1
  4. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esp_test_utils.egg-info/SOURCES.txt +3 -1
  5. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/adapter/dut/esp_mixin.py +5 -3
  6. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/adapter/port/base_port.py +22 -7
  7. esp_test_utils-0.2.3/esptest/adapter/port/shell_port.py +191 -0
  8. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/config/env_config.py +1 -4
  9. esp_test_utils-0.2.3/tests/adapter/test_shell_port.py +94 -0
  10. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/tests/test_EnvConfig.py +24 -0
  11. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/.github/.gitkeep +0 -0
  12. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/.github/workflows/pypi-publish.yml +0 -0
  13. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/.gitignore +0 -0
  14. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/.gitlab-ci.yml +0 -0
  15. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/.pre-commit-config.yaml +0 -0
  16. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/CONTRIBUTING.md +0 -0
  17. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/LICENSE +0 -0
  18. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/README.md +0 -0
  19. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/docs/Makefile +0 -0
  20. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/docs/conf.py +0 -0
  21. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/docs/index.rst +0 -0
  22. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/docs/make.bat +0 -0
  23. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esp_test_utils.egg-info/dependency_links.txt +0 -0
  24. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esp_test_utils.egg-info/entry_points.txt +0 -0
  25. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esp_test_utils.egg-info/requires.txt +0 -0
  26. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esp_test_utils.egg-info/top_level.txt +0 -0
  27. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/__init__.py +0 -0
  28. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/__main__.py +0 -0
  29. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/adapter/__init__.py +0 -0
  30. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/adapter/dut/__init__.py +0 -0
  31. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/adapter/dut/create_dut.py +0 -0
  32. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/adapter/dut/dut_base.py +0 -0
  33. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/adapter/dut/esp_dut.py +0 -0
  34. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/adapter/dut/esp_port.py +0 -0
  35. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/adapter/dut/mac_mixin.py +0 -0
  36. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/adapter/dut/wrapper.py +0 -0
  37. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/adapter/port/__init__.py +0 -0
  38. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/adapter/port/serial_port.py +0 -0
  39. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/all.py +0 -0
  40. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/common/__init__.py +0 -0
  41. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/common/compat_typing.py +0 -0
  42. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/common/data_monitor.py +0 -0
  43. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/common/decorators.py +0 -0
  44. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/common/encoding.py +0 -0
  45. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/common/generator.py +0 -0
  46. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/common/shell.py +0 -0
  47. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/common/timestamp.py +0 -0
  48. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/config/__init__.py +0 -0
  49. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/config/default_config.py +0 -0
  50. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/db/__init__.py +0 -0
  51. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/db/runners.py +0 -0
  52. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/devices/__init__.py +0 -0
  53. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/devices/attenuator.py +0 -0
  54. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/devices/esp_serial.py +0 -0
  55. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/devices/serial_dut.py +0 -0
  56. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/devices/serial_tools.py +0 -0
  57. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/env/__init__.py +0 -0
  58. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/env/base_env.py +0 -0
  59. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/env/wifi_env.py +0 -0
  60. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/esp_console/__init__.py +0 -0
  61. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/esp_console/wifi_cmd.py +0 -0
  62. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/interface/__init__.py +0 -0
  63. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/interface/dut.py +0 -0
  64. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/interface/port.py +0 -0
  65. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/iperf_utility/__init__.py +0 -0
  66. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/iperf_utility/iperf_results.py +0 -0
  67. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/iperf_utility/iperf_test.py +0 -0
  68. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/iperf_utility/iperf_test.test.py +0 -0
  69. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/iperf_utility/line_chart.py +0 -0
  70. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/logger/__init__.py +0 -0
  71. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/logger/logger.py +0 -0
  72. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/network/__init__.py +0 -0
  73. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/network/mac.py +0 -0
  74. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/network/netif.py +0 -0
  75. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/network/nic.py +0 -0
  76. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/scripts/downbin.py +0 -0
  77. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/scripts/list_ports.py +0 -0
  78. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/scripts/monitor.py +0 -0
  79. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/scripts/set_att.py +0 -0
  80. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/tools/copy_bin.py +0 -0
  81. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/tools/download_bin.py +0 -0
  82. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/tools/http_download.py +0 -0
  83. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/tools/pip_check.py +0 -0
  84. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/utility/gen_esp32part.py +0 -0
  85. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/esptest/utility/parse_bin_path.py +0 -0
  86. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/example/jap_test.py +0 -0
  87. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/example/restart_test.py +0 -0
  88. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/pyproject.toml +0 -0
  89. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/setup.cfg +0 -0
  90. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/tests/__init__.py +0 -0
  91. {esp_test_utils-0.2.2/tests → esp_test_utils-0.2.3/tests/adapter}/test_Dut.py +0 -0
  92. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/tests/basic/test_decorators.py +0 -0
  93. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/tests/basic/test_network.py +0 -0
  94. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/tests/conftest.py +0 -0
  95. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/tests/db/test_db_runners.py +0 -0
  96. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/tests/esp_console/_files/wifi_cmd_connected_1.log +0 -0
  97. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/tests/esp_console/_files/wifi_cmd_connected_2.log +0 -0
  98. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/tests/esp_console/conftest.py +0 -0
  99. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/tests/esp_console/test_WifiCmd.py +0 -0
  100. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/tests/iperf_utility/_files/dut_iperf_rx1.log +0 -0
  101. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/tests/iperf_utility/_files/dut_iperf_rx2.log +0 -0
  102. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/tests/iperf_utility/_files/pc_iperf_rx.log +0 -0
  103. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/tests/iperf_utility/_files/pc_iperf_rx2.log +0 -0
  104. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/tests/iperf_utility/test_chart.py +0 -0
  105. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/tests/iperf_utility/test_iperf_results.py +0 -0
  106. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/tests/iperf_utility/test_iperf_util.py +0 -0
  107. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/tests/test_common.py +0 -0
  108. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/tests/test_import.py +0 -0
  109. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/tests/tools/test_download_file.py +0 -0
  110. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/tests/tools/test_pip_check.py +0 -0
  111. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/tests/utility/_files/test-bin.zip +0 -0
  112. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/tests/utility/_files/test-get-baud/ESP32AT-V4.1.1.0/sdkconfig +0 -0
  113. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/tests/utility/test_parse_bin_path.py +0 -0
  114. {esp_test_utils-0.2.2 → esp_test_utils-0.2.3}/tools/ci/check_dev_version.py +0 -0
@@ -1,3 +1,10 @@
1
+ ## v0.2.3 (2025-11-14)
2
+
3
+
4
+ - feat: add shell port support
5
+ - fix: Fix esptool connect to given port
6
+ - fix: log of env config search dirs
7
+
1
8
  ## v0.2.2 (2025-10-29)
2
9
 
3
10
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: esp-test-utils
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: ESP Test Utils
5
5
  Author-email: Chen Yudong <chenyudong@espressif.com>
6
6
  License: Apache License
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: esp-test-utils
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: ESP Test Utils
5
5
  Author-email: Chen Yudong <chenyudong@espressif.com>
6
6
  License: Apache License
@@ -33,6 +33,7 @@ esptest/adapter/dut/wrapper.py
33
33
  esptest/adapter/port/__init__.py
34
34
  esptest/adapter/port/base_port.py
35
35
  esptest/adapter/port/serial_port.py
36
+ esptest/adapter/port/shell_port.py
36
37
  esptest/common/__init__.py
37
38
  esptest/common/compat_typing.py
38
39
  esptest/common/data_monitor.py
@@ -84,10 +85,11 @@ example/jap_test.py
84
85
  example/restart_test.py
85
86
  tests/__init__.py
86
87
  tests/conftest.py
87
- tests/test_Dut.py
88
88
  tests/test_EnvConfig.py
89
89
  tests/test_common.py
90
90
  tests/test_import.py
91
+ tests/adapter/test_Dut.py
92
+ tests/adapter/test_shell_port.py
91
93
  tests/basic/test_decorators.py
92
94
  tests/basic/test_network.py
93
95
  tests/db/test_db_runners.py
@@ -51,15 +51,17 @@ class EspSerial:
51
51
 
52
52
  class EspMixin(BaseProtocol):
53
53
  def _esptool_open_port(self, port: str, initial_baud: int, **kwargs: t.Any) -> esptool.ESPLoader:
54
- ports = [p.device for p in get_all_serial_ports()]
55
54
  port = compute_serial_port(port) if port else ''
55
+ serial_list = [port] if port else [p.device for p in get_all_serial_ports()]
56
+ # esptool.get_default_connected_device always detect_chip from serial_list
56
57
  esp = esptool.get_default_connected_device(
57
- ports,
58
- port,
58
+ serial_list,
59
+ port=port or None, # type: ignore
59
60
  connect_attempts=3,
60
61
  initial_baud=initial_baud,
61
62
  chip=kwargs.get('chip', 'auto'),
62
63
  )
64
+ assert esp, f'Failed to connect to {port}'
63
65
  return esp
64
66
 
65
67
  def _esptool_path(self, use_esptool: str = '') -> str:
@@ -31,9 +31,12 @@ class RawPort(metaclass=abc.ABCMeta):
31
31
  """Define a minimum Dut class, the dut objects should at least support these methods
32
32
 
33
33
  the dut should at least support these attributes:
34
- - attribute name with type str
35
34
  - method: write_bytes() with parameters: data[bytes]
36
35
  - method: read_bytes() with parameters: timeout[float]
36
+
37
+ optional attribute & method:
38
+ - attribute: name with type str
39
+ - attribute: read_timeout with type float
37
40
  """
38
41
 
39
42
  @classmethod
@@ -243,7 +246,11 @@ class PortSpawn(pexpect.spawnbase.SpawnBase, t.Generic[T]):
243
246
  self._log(ret_data, 'read') # type: ignore
244
247
  return ret_data
245
248
 
249
+ @deprecated('Should use close() for Spawn')
246
250
  def stop(self) -> None:
251
+ self.close()
252
+
253
+ def close(self) -> None:
247
254
  """Stop and clean up"""
248
255
  self.logger.debug(f'Stopping SerialSpawn {self.name}')
249
256
  self._read_thread_stop_event.set()
@@ -359,7 +366,7 @@ class BasePort(PortInterface, t.Generic[T]):
359
366
  if new_log_file == self._log_file:
360
367
  return
361
368
  if self._pexpect_spawn:
362
- self._pexpect_spawn.serial_log_file = new_log_file
369
+ self._pexpect_spawn.log_file = new_log_file
363
370
  self._log_file = new_log_file
364
371
 
365
372
  @property
@@ -381,7 +388,7 @@ class BasePort(PortInterface, t.Generic[T]):
381
388
  if not self._pexpect_spawn:
382
389
  return False
383
390
  self._init_log_file()
384
- self._pexpect_spawn.stop()
391
+ self._pexpect_spawn.close()
385
392
  self._pexpect_spawn = None
386
393
  return True
387
394
 
@@ -486,9 +493,13 @@ class BasePort(PortInterface, t.Generic[T]):
486
493
  """
487
494
  buffer = b''
488
495
  if flush:
489
- match = self.expect(re.compile(b'.*', re.DOTALL), timeout=0)
490
- assert match
491
- buffer = match.group(0)
496
+ # pexpect may return empty bytes if b'(.*)' is used
497
+ try:
498
+ match = self.expect(re.compile(b'(.+)', re.DOTALL), timeout=0)
499
+ assert match
500
+ buffer = match.group(0)
501
+ except TimeoutError:
502
+ pass
492
503
  else:
493
504
  # flush spawn buffer
494
505
  assert self._pexpect_spawn
@@ -499,7 +510,11 @@ class BasePort(PortInterface, t.Generic[T]):
499
510
 
500
511
  def close(self) -> None:
501
512
  if self._close_redirect_thread_when_exit and self._pexpect_spawn:
502
- self._pexpect_spawn.stop()
513
+ self._pexpect_spawn.close()
514
+ if self.raw_port:
515
+ if hasattr(self.raw_port, 'close'):
516
+ assert callable(self.raw_port.close) # type: ignore
517
+ self.raw_port.close() # type: ignore
503
518
 
504
519
  def __enter__(self) -> 't.Self':
505
520
  return self
@@ -0,0 +1,191 @@
1
+ import os
2
+ import subprocess
3
+ import time
4
+
5
+ import pexpect
6
+ import psutil
7
+
8
+ import esptest.common.compat_typing as t
9
+
10
+ from ...logger import get_logger
11
+ from .base_port import BasePort, RawPort
12
+
13
+ logger = get_logger('shell_port')
14
+
15
+
16
+ class ShellRaw(RawPort):
17
+ """A subprocess Raw Port class that supports shell read, write
18
+
19
+ is a subclass of RawPort
20
+ """
21
+
22
+ def __init__(self, cmd: str = '/bin/bash', env: t.Optional[t.Dict[str, str]] = None) -> None:
23
+ self.env = env or os.environ.copy()
24
+ self.env['PYTHONUNBUFFERED'] = 'true' # for python scripts, disable output buffering
25
+ self.cmd = cmd
26
+ self.proc: t.Optional[subprocess.Popen] = None
27
+ self.read_timeout = 0.002 # default read_timeout
28
+ self.open()
29
+
30
+ def open(self) -> None:
31
+ if not self.proc:
32
+ self.proc = subprocess.Popen( # pylint: disable=consider-using-with
33
+ self.cmd,
34
+ shell=True,
35
+ env=self.env,
36
+ stdin=subprocess.PIPE,
37
+ stdout=subprocess.PIPE,
38
+ stderr=subprocess.STDOUT,
39
+ )
40
+ # Set stdout to non-blocking
41
+ os.set_blocking(self.proc.stdout.fileno(), False) # type: ignore
42
+
43
+ def close(self) -> None:
44
+ """Close subprocess."""
45
+ if self.proc:
46
+ if self.proc.pid:
47
+ try:
48
+ proc = psutil.Process(self.proc.pid)
49
+ for child in proc.children(recursive=True):
50
+ child.kill()
51
+ proc.kill()
52
+ time.sleep(0.01)
53
+ except psutil.Error:
54
+ pass
55
+ # # Unix / Linux - does not work
56
+ # try:
57
+ # os.killpg(self.proc.pid, signal.SIGTERM) # send SIGTERM to all in the group
58
+ # os.killpg(self.proc.pid, signal.SIGKILL) # send SIGTERM to all in the group
59
+ # except ProcessLookupError:
60
+ # pass
61
+ self.proc.terminate()
62
+ self.proc.kill()
63
+ self.proc.wait()
64
+ logger.info(f'shell command [{self.cmd}] was killed')
65
+ self.proc = None
66
+
67
+ def write_bytes(self, data: bytes) -> None:
68
+ """Write bytes to subprocess stdin."""
69
+ if self.proc:
70
+ self.proc.stdin.write(data) # type: ignore
71
+ self.proc.stdin.flush() # type: ignore
72
+ return
73
+ raise ValueError('Subprocess not initialized.')
74
+
75
+ def read_bytes(self, timeout: float = 0) -> bytes:
76
+ """blocking read bytes"""
77
+ data = self.read_bytes_nonblocking()
78
+ if not data and timeout > 0:
79
+ time.sleep(timeout) # blocking read
80
+ data = self.read_bytes_nonblocking()
81
+ if data:
82
+ logger.debug(f'[{self.cmd}] read_bytes timeout={timeout}, data={str(data)}')
83
+ return data
84
+
85
+ def read_bytes_nonblocking(self, size: int = -1) -> bytes:
86
+ """non-blocking read bytes"""
87
+ if self.proc:
88
+ self.proc.stdout.flush() # type: ignore
89
+ return self.proc.stdout.read(size) # type: ignore
90
+ return b''
91
+
92
+
93
+ class ShellPort(BasePort[ShellRaw]):
94
+ """A combined port class that supports shell read, write, expect"""
95
+
96
+ def __init__(
97
+ self,
98
+ cmd: str = '/bin/bash',
99
+ env: t.Optional[dict[str, str]] = None,
100
+ name: str = '',
101
+ log_file: str = '',
102
+ **kwargs: t.Any,
103
+ ) -> None:
104
+ raw_port = ShellRaw(cmd=cmd, env=env)
105
+ super().__init__(raw_port, name, log_file, **kwargs)
106
+
107
+
108
+ class InvalidRaw(RawPort):
109
+ """A invalid Raw Port class that always raise NotImplementedError to pass type check"""
110
+
111
+ def write_bytes(self, data: bytes) -> None:
112
+ """Write bytes to subprocess stdin."""
113
+ raise NotImplementedError('Invalid Raw Port.')
114
+
115
+ def read_bytes(self, timeout: float = 0) -> bytes:
116
+ """blocking read bytes"""
117
+ raise NotImplementedError('Invalid Raw Port.')
118
+
119
+
120
+ class PexpectPort(BasePort[InvalidRaw]):
121
+ """A pexpect Port class that supports shell read, write, expect
122
+
123
+ based on pexpect.spawn but use different expect method
124
+ """
125
+
126
+ def __init__(
127
+ self,
128
+ cmd: str = '/bin/bash',
129
+ name: str = '',
130
+ log_file: str = '',
131
+ **kwargs: t.Any,
132
+ ) -> None:
133
+ self._cmd = cmd
134
+ raw_port = InvalidRaw()
135
+ self._pexpect_spawn: t.Optional[pexpect.spawn] = None # change type
136
+ self.log_file_f = open(log_file, 'wb') if log_file else None # pylint: disable=consider-using-with
137
+ super().__init__(raw_port, name, log_file, **kwargs)
138
+
139
+ @property
140
+ def log_file(self) -> str:
141
+ """Get Current dut log file."""
142
+ if not self._log_file:
143
+ return ''
144
+ return os.path.abspath(self._log_file)
145
+
146
+ @log_file.setter
147
+ def log_file(self, new_log_file: str) -> None:
148
+ """Set Current dut log file."""
149
+ if new_log_file == self._log_file:
150
+ return
151
+ if self.log_file_f:
152
+ if self._pexpect_spawn:
153
+ self._pexpect_spawn.logfile = None # type: ignore
154
+ self.log_file_f.close()
155
+ if self._pexpect_spawn:
156
+ self.log_file_f = open(new_log_file, 'wb') if new_log_file else None # pylint: disable=consider-using-with
157
+ self._pexpect_spawn.logfile = self.log_file_f # type: ignore
158
+ self._log_file = new_log_file
159
+
160
+ @property
161
+ def spawn(self) -> t.Optional[pexpect.spawn]: # type: ignore
162
+ """Allow the use of pexpect spawn enhancements, if pexpect process is available"""
163
+ return self._pexpect_spawn
164
+
165
+ def start_redirect_thread(self) -> None:
166
+ """Start a new thread to read data from port and save to data cache."""
167
+ if self._pexpect_spawn:
168
+ return
169
+ self._init_log_file()
170
+ env = os.environ.copy()
171
+ env['PYTHONUNBUFFERED'] = 'true' # for python scripts, disable output buffering
172
+ self._pexpect_spawn = pexpect.spawn(self._cmd, maxread=8192, echo=False, env=env) # type: ignore
173
+ self._pexpect_spawn.logfile = self.log_file_f # type: ignore
174
+ # self._pexpect_spawn.delaybeforesend = 0.001
175
+
176
+ def stop_redirect_thread(self) -> bool:
177
+ """Stop the redirect thread and pexpect process."""
178
+ if not self._pexpect_spawn:
179
+ return False
180
+ self._init_log_file()
181
+ self._pexpect_spawn.close()
182
+ self._pexpect_spawn = None # type: ignore
183
+ return True
184
+
185
+ def close(self) -> None:
186
+ """Close pexpect process."""
187
+ super().close()
188
+ self.stop_redirect_thread()
189
+ if self.log_file_f:
190
+ self.log_file_f.close()
191
+ self.log_file_f = None
@@ -113,10 +113,7 @@ class EnvConfig:
113
113
  continue
114
114
  config_file = os.path.join(_dir, cls.ENV_CONFIG_FILE_BASE_NAME)
115
115
  if not config_file:
116
- _msg = (
117
- 'Can not find env config file from:\n ',
118
- ' \n'.join(cls._search_dirs()),
119
- )
116
+ _msg = 'Can not find env config file from:\n ' + ' \n'.join(cls._search_dirs())
120
117
  logging.warning(_msg)
121
118
  if not cls.ALLOW_INPUT:
122
119
  raise FileNotFoundError(f'Could not find config file: {cls.ENV_CONFIG_FILE_BASE_NAME}')
@@ -0,0 +1,94 @@
1
+ import random
2
+ import re
3
+ import subprocess
4
+ import time
5
+ from pathlib import Path
6
+
7
+ import pytest
8
+
9
+ from esptest.adapter.port.shell_port import PexpectPort, ShellPort, ShellRaw
10
+
11
+
12
+ def test_shell_raw_open_close() -> None:
13
+ ran_int = random.randint(12345678, 87654321)
14
+ raw_port = ShellRaw(cmd=f'sleep {ran_int}')
15
+ assert raw_port.proc is not None
16
+ output = subprocess.check_output(f'ps -ef | grep {ran_int}', shell=True).decode('utf-8')
17
+ assert f'sleep {ran_int}' in output
18
+ raw_port.close()
19
+ output = subprocess.check_output(f'ps -ef | grep {ran_int}', shell=True).decode('utf-8')
20
+ assert f'sleep {ran_int}' not in output
21
+
22
+
23
+ def test_shell_port_open_close() -> None:
24
+ ran_int = random.randint(12345678, 87654321)
25
+ # close by close method
26
+ port = ShellPort(cmd=f'sleep {ran_int}')
27
+ assert isinstance(port.raw_port, ShellRaw)
28
+ output = subprocess.check_output(f'ps -ef | grep {ran_int}', shell=True).decode('utf-8')
29
+ assert f'sleep {ran_int}' in output
30
+ port.close()
31
+ output = subprocess.check_output(f'ps -ef | grep {ran_int}', shell=True).decode('utf-8')
32
+ assert f'sleep {ran_int}' not in output
33
+ # close by with statement
34
+ with ShellPort(cmd=f'sleep {ran_int}') as port:
35
+ assert isinstance(port.raw_port, ShellRaw)
36
+ output = subprocess.check_output(f'ps -ef | grep {ran_int}', shell=True).decode('utf-8')
37
+ assert f'sleep {ran_int}' in output
38
+ output = subprocess.check_output(f'ps -ef | grep {ran_int}', shell=True).decode('utf-8')
39
+ assert f'sleep {ran_int}' not in output
40
+
41
+
42
+ def test_shell_port_read_write() -> None:
43
+ with ShellPort(cmd='/bin/bash') as port:
44
+ port.write_line('echo hello')
45
+ time.sleep(0.1) # wait for the receive thread
46
+ assert 'hello' in port.read_all_data()
47
+ port.write_line('sleep 0.1 && echo world')
48
+ assert 'world' not in port.read_all_data()
49
+ match = port.expect(re.compile('world'))
50
+ assert match.group(0) == 'world'
51
+
52
+
53
+ def test_shell_port_logfile(tmp_path: Path) -> None:
54
+ log_file = tmp_path / 'shell_port1.log'
55
+ with ShellPort(cmd='/bin/bash', log_file=str(log_file)) as port:
56
+ port.write_line('echo hello')
57
+ time.sleep(0.1) # wait for the receive thread
58
+ with open(str(log_file), 'r') as f:
59
+ assert 'hello' in f.read()
60
+ port.log_file = str(tmp_path / 'shell_port2.log')
61
+ port.write_line('echo world')
62
+ time.sleep(0.1) # wait for the receive thread
63
+ with open(str(tmp_path / 'shell_port2.log'), 'r') as f:
64
+ assert 'world' in f.read()
65
+
66
+
67
+ def test_pexpect_spawn_port_read_write() -> None:
68
+ with PexpectPort(cmd='/bin/bash') as port:
69
+ port.write_line('echo hello')
70
+ time.sleep(0.5) # wait for the receive thread
71
+ assert 'hello' in port.read_all_data()
72
+ port.write_line('sleep 0.1 && echo world')
73
+ assert 'world' not in port.read_all_data()
74
+ match = port.expect(re.compile('world'))
75
+ assert match.group(0) == 'world'
76
+
77
+
78
+ def test_pexpect_spawn_port_logfile(tmp_path: Path) -> None:
79
+ log_file = tmp_path / 'shell_port1.log'
80
+ with PexpectPort(cmd='/bin/bash', log_file=str(log_file)) as port:
81
+ port.write_line('echo hello')
82
+ time.sleep(0.5) # wait for the receive thread
83
+ with open(str(log_file), 'r') as f:
84
+ assert 'hello' in f.read()
85
+ port.log_file = str(tmp_path / 'shell_port2.log')
86
+ port.write_line('echo world')
87
+ time.sleep(0.1) # wait for the receive thread
88
+ with open(str(tmp_path / 'shell_port2.log'), 'r') as f:
89
+ assert 'world' in f.read()
90
+
91
+
92
+ if __name__ == '__main__':
93
+ # Breakpoints do not work with coverage, disable coverage for debugging
94
+ pytest.main([__file__, '--no-cov', '--log-cli-level=DEBUG'])
@@ -65,6 +65,30 @@ def test_env_config_get_var(tmp_path: Path) -> None:
65
65
  env_config.get_variable('dut_port')
66
66
 
67
67
 
68
+ def test_env_config_from_shell_env(tmp_path: Path) -> None:
69
+ # Test Get variable from console
70
+ config_file = tmp_path / 'not_exist_config.yml'
71
+ env = {
72
+ 'TEST_ENV_CONFIG_FILE': str(config_file),
73
+ }
74
+ with reload_envconfig(env):
75
+ try:
76
+ os.environ.pop('RUNNER_WIFI_SSID')
77
+ except KeyError:
78
+ pass
79
+ try:
80
+ os.environ.pop('RUNNER_AP_SSID')
81
+ except KeyError:
82
+ pass
83
+ env_config = EnvConfig()
84
+ env_config.ALLOW_INPUT = False
85
+ with pytest.raises(ValueError):
86
+ var = env_config.get_variable('ap_ssid')
87
+ os.environ['RUNNER_AP_SSID'] = 'ssid_from_env'
88
+ var = env_config.get_variable('ap_ssid')
89
+ assert var == 'ssid_from_env'
90
+
91
+
68
92
  def test_env_config_from_console(tmp_path, monkeypatch): # type: ignore
69
93
  # Test Get variable from console
70
94
  config_file = tmp_path / 'not_exist_config.yml'
File without changes
File without changes
File without changes