esp-test-utils 0.2.2__tar.gz → 0.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.
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/CHANGELOG.md +16 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/PKG-INFO +3 -1
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esp_test_utils.egg-info/PKG-INFO +3 -1
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esp_test_utils.egg-info/SOURCES.txt +6 -1
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esp_test_utils.egg-info/requires.txt +2 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/adapter/dut/esp_mixin.py +5 -3
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/adapter/port/base_port.py +35 -8
- esp_test_utils-0.3.0/esptest/adapter/port/shell_port.py +191 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/common/decorators.py +20 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/config/env_config.py +1 -4
- esp_test_utils-0.3.0/esptest/devices/switch.py +544 -0
- esp_test_utils-0.3.0/esptest/network/mac.py +31 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/scripts/downbin.py +10 -1
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/scripts/list_ports.py +20 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/tools/download_bin.py +33 -6
- esp_test_utils-0.3.0/esptest/tools/uart_monitor.py +324 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/pyproject.toml +5 -0
- {esp_test_utils-0.2.2/tests → esp_test_utils-0.3.0/tests/adapter}/test_Dut.py +21 -0
- esp_test_utils-0.3.0/tests/adapter/test_shell_port.py +94 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/tests/basic/test_decorators.py +25 -1
- esp_test_utils-0.3.0/tests/devices/test_switch.py +217 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/tests/test_EnvConfig.py +24 -0
- esp_test_utils-0.2.2/esptest/network/mac.py +0 -5
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/.github/.gitkeep +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/.github/workflows/pypi-publish.yml +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/.gitignore +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/.gitlab-ci.yml +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/.pre-commit-config.yaml +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/CONTRIBUTING.md +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/LICENSE +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/README.md +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/docs/Makefile +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/docs/conf.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/docs/index.rst +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/docs/make.bat +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esp_test_utils.egg-info/dependency_links.txt +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esp_test_utils.egg-info/entry_points.txt +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esp_test_utils.egg-info/top_level.txt +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/__init__.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/__main__.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/adapter/__init__.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/adapter/dut/__init__.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/adapter/dut/create_dut.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/adapter/dut/dut_base.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/adapter/dut/esp_dut.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/adapter/dut/esp_port.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/adapter/dut/mac_mixin.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/adapter/dut/wrapper.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/adapter/port/__init__.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/adapter/port/serial_port.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/all.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/common/__init__.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/common/compat_typing.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/common/data_monitor.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/common/encoding.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/common/generator.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/common/shell.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/common/timestamp.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/config/__init__.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/config/default_config.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/db/__init__.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/db/runners.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/devices/__init__.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/devices/attenuator.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/devices/esp_serial.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/devices/serial_dut.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/devices/serial_tools.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/env/__init__.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/env/base_env.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/env/wifi_env.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/esp_console/__init__.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/esp_console/wifi_cmd.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/interface/__init__.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/interface/dut.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/interface/port.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/iperf_utility/__init__.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/iperf_utility/iperf_results.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/iperf_utility/iperf_test.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/iperf_utility/iperf_test.test.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/iperf_utility/line_chart.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/logger/__init__.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/logger/logger.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/network/__init__.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/network/netif.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/network/nic.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/scripts/monitor.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/scripts/set_att.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/tools/copy_bin.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/tools/http_download.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/tools/pip_check.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/utility/gen_esp32part.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/esptest/utility/parse_bin_path.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/example/jap_test.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/example/restart_test.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/setup.cfg +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/tests/__init__.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/tests/basic/test_network.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/tests/conftest.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/tests/db/test_db_runners.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/tests/esp_console/_files/wifi_cmd_connected_1.log +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/tests/esp_console/_files/wifi_cmd_connected_2.log +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/tests/esp_console/conftest.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/tests/esp_console/test_WifiCmd.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/tests/iperf_utility/_files/dut_iperf_rx1.log +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/tests/iperf_utility/_files/dut_iperf_rx2.log +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/tests/iperf_utility/_files/pc_iperf_rx.log +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/tests/iperf_utility/_files/pc_iperf_rx2.log +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/tests/iperf_utility/test_chart.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/tests/iperf_utility/test_iperf_results.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/tests/iperf_utility/test_iperf_util.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/tests/test_common.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/tests/test_import.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/tests/tools/test_download_file.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/tests/tools/test_pip_check.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/tests/utility/_files/test-bin.zip +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/tests/utility/_files/test-get-baud/ESP32AT-V4.1.1.0/sdkconfig +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/tests/utility/test_parse_bin_path.py +0 -0
- {esp_test_utils-0.2.2 → esp_test_utils-0.3.0}/tools/ci/check_dev_version.py +0 -0
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
## v0.3.0 (2025-12-10)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
- feat: esp-listports support monitor mode
|
|
5
|
+
- feat: add more logs to H3CSwitch
|
|
6
|
+
- feat: esp-downbin support argument --force-no-stub
|
|
7
|
+
- feat: add h3c switch device control
|
|
8
|
+
- feat: add decorator timeit
|
|
9
|
+
|
|
10
|
+
## v0.2.3 (2025-11-14)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
- feat: add shell port support
|
|
14
|
+
- fix: Fix esptool connect to given port
|
|
15
|
+
- fix: log of env config search dirs
|
|
16
|
+
|
|
1
17
|
## v0.2.2 (2025-10-29)
|
|
2
18
|
|
|
3
19
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: esp-test-utils
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: ESP Test Utils
|
|
5
5
|
Author-email: Chen Yudong <chenyudong@espressif.com>
|
|
6
6
|
License: Apache License
|
|
@@ -228,8 +228,10 @@ Requires-Dist: pyserial
|
|
|
228
228
|
Requires-Dist: PyYAML
|
|
229
229
|
Requires-Dist: pexpect
|
|
230
230
|
Requires-Dist: pyusb
|
|
231
|
+
Requires-Dist: pyudev
|
|
231
232
|
Requires-Dist: esptool
|
|
232
233
|
Requires-Dist: packaging
|
|
234
|
+
Requires-Dist: rich
|
|
233
235
|
Requires-Dist: sqlalchemy
|
|
234
236
|
Requires-Dist: typing_extensions; python_version < "3.11"
|
|
235
237
|
Provides-Extra: idfci
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: esp-test-utils
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: ESP Test Utils
|
|
5
5
|
Author-email: Chen Yudong <chenyudong@espressif.com>
|
|
6
6
|
License: Apache License
|
|
@@ -228,8 +228,10 @@ Requires-Dist: pyserial
|
|
|
228
228
|
Requires-Dist: PyYAML
|
|
229
229
|
Requires-Dist: pexpect
|
|
230
230
|
Requires-Dist: pyusb
|
|
231
|
+
Requires-Dist: pyudev
|
|
231
232
|
Requires-Dist: esptool
|
|
232
233
|
Requires-Dist: packaging
|
|
234
|
+
Requires-Dist: rich
|
|
233
235
|
Requires-Dist: sqlalchemy
|
|
234
236
|
Requires-Dist: typing_extensions; python_version < "3.11"
|
|
235
237
|
Provides-Extra: idfci
|
|
@@ -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
|
|
@@ -51,6 +52,7 @@ esptest/devices/attenuator.py
|
|
|
51
52
|
esptest/devices/esp_serial.py
|
|
52
53
|
esptest/devices/serial_dut.py
|
|
53
54
|
esptest/devices/serial_tools.py
|
|
55
|
+
esptest/devices/switch.py
|
|
54
56
|
esptest/env/__init__.py
|
|
55
57
|
esptest/env/base_env.py
|
|
56
58
|
esptest/env/wifi_env.py
|
|
@@ -78,19 +80,22 @@ esptest/tools/copy_bin.py
|
|
|
78
80
|
esptest/tools/download_bin.py
|
|
79
81
|
esptest/tools/http_download.py
|
|
80
82
|
esptest/tools/pip_check.py
|
|
83
|
+
esptest/tools/uart_monitor.py
|
|
81
84
|
esptest/utility/gen_esp32part.py
|
|
82
85
|
esptest/utility/parse_bin_path.py
|
|
83
86
|
example/jap_test.py
|
|
84
87
|
example/restart_test.py
|
|
85
88
|
tests/__init__.py
|
|
86
89
|
tests/conftest.py
|
|
87
|
-
tests/test_Dut.py
|
|
88
90
|
tests/test_EnvConfig.py
|
|
89
91
|
tests/test_common.py
|
|
90
92
|
tests/test_import.py
|
|
93
|
+
tests/adapter/test_Dut.py
|
|
94
|
+
tests/adapter/test_shell_port.py
|
|
91
95
|
tests/basic/test_decorators.py
|
|
92
96
|
tests/basic/test_network.py
|
|
93
97
|
tests/db/test_db_runners.py
|
|
98
|
+
tests/devices/test_switch.py
|
|
94
99
|
tests/esp_console/conftest.py
|
|
95
100
|
tests/esp_console/test_WifiCmd.py
|
|
96
101
|
tests/esp_console/_files/wifi_cmd_connected_1.log
|
|
@@ -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
|
-
|
|
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:
|
|
@@ -26,14 +26,24 @@ NEVER_MATCHED_MAGIC_STRING = 'o6K,Q.(w+~yr~N9R'
|
|
|
26
26
|
class ExpectTimeout(TimeoutError):
|
|
27
27
|
"""raise same ExpectTimeout rather than different Exception from different framework"""
|
|
28
28
|
|
|
29
|
+
def __init__(self, message: str, data_in_buffer: t.Union[str, bytes] = b'') -> None:
|
|
30
|
+
super().__init__(message)
|
|
31
|
+
self.data_in_buffer: t.Union[str, bytes] = data_in_buffer
|
|
32
|
+
|
|
33
|
+
def __str__(self) -> str:
|
|
34
|
+
return f'{super().__str__()}\n data_in_buffer={repr(self.data_in_buffer)}'
|
|
35
|
+
|
|
29
36
|
|
|
30
37
|
class RawPort(metaclass=abc.ABCMeta):
|
|
31
38
|
"""Define a minimum Dut class, the dut objects should at least support these methods
|
|
32
39
|
|
|
33
40
|
the dut should at least support these attributes:
|
|
34
|
-
- attribute name with type str
|
|
35
41
|
- method: write_bytes() with parameters: data[bytes]
|
|
36
42
|
- method: read_bytes() with parameters: timeout[float]
|
|
43
|
+
|
|
44
|
+
optional attribute & method:
|
|
45
|
+
- attribute: name with type str
|
|
46
|
+
- attribute: read_timeout with type float
|
|
37
47
|
"""
|
|
38
48
|
|
|
39
49
|
@classmethod
|
|
@@ -243,7 +253,11 @@ class PortSpawn(pexpect.spawnbase.SpawnBase, t.Generic[T]):
|
|
|
243
253
|
self._log(ret_data, 'read') # type: ignore
|
|
244
254
|
return ret_data
|
|
245
255
|
|
|
256
|
+
@deprecated('Should use close() for Spawn')
|
|
246
257
|
def stop(self) -> None:
|
|
258
|
+
self.close()
|
|
259
|
+
|
|
260
|
+
def close(self) -> None:
|
|
247
261
|
"""Stop and clean up"""
|
|
248
262
|
self.logger.debug(f'Stopping SerialSpawn {self.name}')
|
|
249
263
|
self._read_thread_stop_event.set()
|
|
@@ -359,7 +373,7 @@ class BasePort(PortInterface, t.Generic[T]):
|
|
|
359
373
|
if new_log_file == self._log_file:
|
|
360
374
|
return
|
|
361
375
|
if self._pexpect_spawn:
|
|
362
|
-
self._pexpect_spawn.
|
|
376
|
+
self._pexpect_spawn.log_file = new_log_file
|
|
363
377
|
self._log_file = new_log_file
|
|
364
378
|
|
|
365
379
|
@property
|
|
@@ -381,7 +395,7 @@ class BasePort(PortInterface, t.Generic[T]):
|
|
|
381
395
|
if not self._pexpect_spawn:
|
|
382
396
|
return False
|
|
383
397
|
self._init_log_file()
|
|
384
|
-
self._pexpect_spawn.
|
|
398
|
+
self._pexpect_spawn.close()
|
|
385
399
|
self._pexpect_spawn = None
|
|
386
400
|
return True
|
|
387
401
|
|
|
@@ -401,7 +415,12 @@ class BasePort(PortInterface, t.Generic[T]):
|
|
|
401
415
|
try:
|
|
402
416
|
result = func(self, *args, **kwargs)
|
|
403
417
|
except self.expect_timeout_exceptions as e:
|
|
404
|
-
|
|
418
|
+
try:
|
|
419
|
+
data_in_buffer = self._pexpect_spawn.before # pylint: disable=protected-access
|
|
420
|
+
except AttributeError:
|
|
421
|
+
data_in_buffer = ''
|
|
422
|
+
self.logger.debug(f'ExpectTimeout: {str(e)}, data_in_buffer={repr(data_in_buffer)}')
|
|
423
|
+
raise ExpectTimeout(str(e), data_in_buffer=data_in_buffer) from e
|
|
405
424
|
return result
|
|
406
425
|
|
|
407
426
|
return wrap
|
|
@@ -486,9 +505,13 @@ class BasePort(PortInterface, t.Generic[T]):
|
|
|
486
505
|
"""
|
|
487
506
|
buffer = b''
|
|
488
507
|
if flush:
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
508
|
+
# pexpect may return empty bytes if b'(.*)' is used
|
|
509
|
+
try:
|
|
510
|
+
match = self.expect(re.compile(b'(.+)', re.DOTALL), timeout=0)
|
|
511
|
+
assert match
|
|
512
|
+
buffer = match.group(0)
|
|
513
|
+
except TimeoutError:
|
|
514
|
+
pass
|
|
492
515
|
else:
|
|
493
516
|
# flush spawn buffer
|
|
494
517
|
assert self._pexpect_spawn
|
|
@@ -499,7 +522,11 @@ class BasePort(PortInterface, t.Generic[T]):
|
|
|
499
522
|
|
|
500
523
|
def close(self) -> None:
|
|
501
524
|
if self._close_redirect_thread_when_exit and self._pexpect_spawn:
|
|
502
|
-
self._pexpect_spawn.
|
|
525
|
+
self._pexpect_spawn.close()
|
|
526
|
+
if self.raw_port:
|
|
527
|
+
if hasattr(self.raw_port, 'close'):
|
|
528
|
+
assert callable(self.raw_port.close) # type: ignore
|
|
529
|
+
self.raw_port.close() # type: ignore
|
|
503
530
|
|
|
504
531
|
def __enter__(self) -> 't.Self':
|
|
505
532
|
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
|
|
@@ -109,3 +109,23 @@ def suppress_stdout() -> t.Callable[[GenericFunc], GenericFunc]:
|
|
|
109
109
|
return t.cast(GenericFunc, wrapper)
|
|
110
110
|
|
|
111
111
|
return decorator
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def timeit(
|
|
115
|
+
print_func: t.Callable[[str], None] = logger.critical,
|
|
116
|
+
format_str: str = 'Func {func_name} time used: {time_used:.2f} s',
|
|
117
|
+
) -> t.Callable[[GenericFunc], GenericFunc]:
|
|
118
|
+
"""Show time used when method is called"""
|
|
119
|
+
|
|
120
|
+
def decorator(func: GenericFunc) -> GenericFunc:
|
|
121
|
+
@wraps(func)
|
|
122
|
+
def wrapper(*args: t.Any, **kwargs: t.Any) -> t.Any:
|
|
123
|
+
start_time = time.perf_counter()
|
|
124
|
+
ret = func(*args, **kwargs)
|
|
125
|
+
end_time = time.perf_counter()
|
|
126
|
+
print_func(format_str.format(func_name=func.__name__, time_used=end_time - start_time))
|
|
127
|
+
return ret
|
|
128
|
+
|
|
129
|
+
return t.cast(GenericFunc, wrapper)
|
|
130
|
+
|
|
131
|
+
return decorator
|
|
@@ -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}')
|