esp-test-utils 0.2.3__tar.gz → 0.3.1__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 (120) hide show
  1. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/.gitignore +1 -0
  2. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/.gitlab-ci.yml +71 -4
  3. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/CHANGELOG.md +16 -0
  4. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/PKG-INFO +3 -1
  5. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esp_test_utils.egg-info/PKG-INFO +3 -1
  6. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esp_test_utils.egg-info/SOURCES.txt +3 -0
  7. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esp_test_utils.egg-info/requires.txt +2 -0
  8. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/adapter/dut/dut_base.py +2 -2
  9. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/adapter/dut/wrapper.py +6 -1
  10. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/adapter/port/base_port.py +45 -20
  11. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/adapter/port/serial_port.py +60 -18
  12. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/adapter/port/shell_port.py +100 -11
  13. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/common/compat_typing.py +9 -3
  14. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/common/data_monitor.py +2 -2
  15. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/common/decorators.py +20 -0
  16. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/common/shell.py +18 -0
  17. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/config/env_config.py +4 -2
  18. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/db/runners.py +4 -1
  19. esp_test_utils-0.3.1/esptest/devices/switch.py +544 -0
  20. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/interface/port.py +6 -5
  21. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/iperf_utility/iperf_results.py +27 -24
  22. esp_test_utils-0.3.1/esptest/iperf_utility/line_chart.py +244 -0
  23. esp_test_utils-0.3.1/esptest/network/mac.py +31 -0
  24. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/network/netif.py +35 -11
  25. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/scripts/downbin.py +10 -1
  26. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/scripts/list_ports.py +20 -0
  27. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/tools/copy_bin.py +2 -2
  28. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/tools/download_bin.py +41 -9
  29. esp_test_utils-0.3.1/esptest/tools/uart_monitor.py +324 -0
  30. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/utility/parse_bin_path.py +26 -6
  31. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/pyproject.toml +6 -1
  32. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/tests/adapter/test_Dut.py +83 -1
  33. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/tests/adapter/test_shell_port.py +41 -11
  34. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/tests/basic/test_decorators.py +25 -1
  35. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/tests/basic/test_network.py +39 -3
  36. esp_test_utils-0.3.1/tests/devices/test_switch.py +217 -0
  37. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/tests/esp_console/test_WifiCmd.py +56 -1
  38. esp_test_utils-0.3.1/tests/iperf_utility/test_chart.py +101 -0
  39. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/tests/iperf_utility/test_iperf_results.py +8 -0
  40. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/tests/test_common.py +7 -2
  41. esp_test_utils-0.2.3/esptest/iperf_utility/line_chart.py +0 -86
  42. esp_test_utils-0.2.3/esptest/network/mac.py +0 -5
  43. esp_test_utils-0.2.3/tests/iperf_utility/test_chart.py +0 -46
  44. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/.github/.gitkeep +0 -0
  45. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/.github/workflows/pypi-publish.yml +0 -0
  46. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/.pre-commit-config.yaml +0 -0
  47. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/CONTRIBUTING.md +0 -0
  48. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/LICENSE +0 -0
  49. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/README.md +0 -0
  50. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/docs/Makefile +0 -0
  51. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/docs/conf.py +0 -0
  52. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/docs/index.rst +0 -0
  53. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/docs/make.bat +0 -0
  54. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esp_test_utils.egg-info/dependency_links.txt +0 -0
  55. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esp_test_utils.egg-info/entry_points.txt +0 -0
  56. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esp_test_utils.egg-info/top_level.txt +0 -0
  57. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/__init__.py +0 -0
  58. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/__main__.py +0 -0
  59. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/adapter/__init__.py +0 -0
  60. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/adapter/dut/__init__.py +0 -0
  61. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/adapter/dut/create_dut.py +0 -0
  62. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/adapter/dut/esp_dut.py +0 -0
  63. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/adapter/dut/esp_mixin.py +0 -0
  64. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/adapter/dut/esp_port.py +0 -0
  65. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/adapter/dut/mac_mixin.py +0 -0
  66. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/adapter/port/__init__.py +0 -0
  67. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/all.py +0 -0
  68. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/common/__init__.py +0 -0
  69. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/common/encoding.py +0 -0
  70. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/common/generator.py +0 -0
  71. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/common/timestamp.py +0 -0
  72. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/config/__init__.py +0 -0
  73. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/config/default_config.py +0 -0
  74. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/db/__init__.py +0 -0
  75. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/devices/__init__.py +0 -0
  76. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/devices/attenuator.py +0 -0
  77. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/devices/esp_serial.py +0 -0
  78. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/devices/serial_dut.py +0 -0
  79. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/devices/serial_tools.py +0 -0
  80. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/env/__init__.py +0 -0
  81. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/env/base_env.py +0 -0
  82. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/env/wifi_env.py +0 -0
  83. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/esp_console/__init__.py +0 -0
  84. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/esp_console/wifi_cmd.py +0 -0
  85. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/interface/__init__.py +0 -0
  86. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/interface/dut.py +0 -0
  87. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/iperf_utility/__init__.py +0 -0
  88. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/iperf_utility/iperf_test.py +0 -0
  89. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/iperf_utility/iperf_test.test.py +0 -0
  90. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/logger/__init__.py +0 -0
  91. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/logger/logger.py +0 -0
  92. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/network/__init__.py +0 -0
  93. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/network/nic.py +0 -0
  94. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/scripts/monitor.py +0 -0
  95. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/scripts/set_att.py +0 -0
  96. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/tools/http_download.py +0 -0
  97. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/tools/pip_check.py +0 -0
  98. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/esptest/utility/gen_esp32part.py +0 -0
  99. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/example/jap_test.py +0 -0
  100. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/example/restart_test.py +0 -0
  101. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/setup.cfg +0 -0
  102. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/tests/__init__.py +0 -0
  103. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/tests/conftest.py +0 -0
  104. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/tests/db/test_db_runners.py +0 -0
  105. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/tests/esp_console/_files/wifi_cmd_connected_1.log +0 -0
  106. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/tests/esp_console/_files/wifi_cmd_connected_2.log +0 -0
  107. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/tests/esp_console/conftest.py +0 -0
  108. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/tests/iperf_utility/_files/dut_iperf_rx1.log +0 -0
  109. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/tests/iperf_utility/_files/dut_iperf_rx2.log +0 -0
  110. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/tests/iperf_utility/_files/pc_iperf_rx.log +0 -0
  111. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/tests/iperf_utility/_files/pc_iperf_rx2.log +0 -0
  112. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/tests/iperf_utility/test_iperf_util.py +0 -0
  113. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/tests/test_EnvConfig.py +0 -0
  114. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/tests/test_import.py +0 -0
  115. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/tests/tools/test_download_file.py +0 -0
  116. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/tests/tools/test_pip_check.py +0 -0
  117. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/tests/utility/_files/test-bin.zip +0 -0
  118. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/tests/utility/_files/test-get-baud/ESP32AT-V4.1.1.0/sdkconfig +0 -0
  119. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/tests/utility/test_parse_bin_path.py +0 -0
  120. {esp_test_utils-0.2.3 → esp_test_utils-0.3.1}/tools/ci/check_dev_version.py +0 -0
@@ -8,6 +8,7 @@ __pycache__/
8
8
  htmlcov/
9
9
  /.coverage
10
10
  /.coverage.*
11
+ /coverage.xml
11
12
  gl-codequality.json
12
13
 
13
14
  /build/
@@ -30,20 +30,87 @@ pre-commit-check:
30
30
  # ------------------------------------------------------------------------------------------------------
31
31
  # Pytest
32
32
  # ------------------------------------------------------------------------------------------------------
33
- pytest-check:
33
+ pytest-linux:
34
34
  stage: test
35
35
  needs: []
36
+ image: ${IMG}
36
37
  before_script:
37
38
  - pip install -e '.[test]'
38
39
  script:
39
- - pytest --junitxml=reports/junit.xml --cov=esptest --cov-report=term --cov-report xml:reports/coverage.xml
40
- coverage: '/TOTAL.*\s+(\d+)\%/'
40
+ # - pytest --junitxml=reports/junit.xml --cov=esptest --cov-report=term --cov-report xml:reports/coverage.xml
41
+ - if [ "$IMG" = "python:3.11-bookworm" ]; then pip install pyecharts; fi # test with pyecharts installed
42
+ - export COVERAGE_FILE=.coverage.${CI_JOB_ID}
43
+ - pytest --junitxml=reports/junit.xml --cov=esptest --cov-report=term
44
+ # coverage: '/TOTAL.*\s+(\d+)\%/'
41
45
  artifacts:
42
46
  paths:
43
- - reports/
47
+ # - reports/
44
48
  - '.coverage*'
45
49
  reports:
46
50
  junit: reports/junit.xml
51
+ expire_in: 2 days
52
+ parallel:
53
+ matrix:
54
+ - IMG: "python:3.7-bullseye"
55
+ - IMG: "python:3.11-bookworm"
56
+ - IMG: "python:3.14-trixie"
57
+ tags:
58
+ - host_test
59
+
60
+ pytest-win32:
61
+ stage: test
62
+ needs:
63
+ - job: pytest-linux
64
+ artifacts: false
65
+ before_script:
66
+ - pymanager exec -V:$env:PYTHON_VER -m venv venv
67
+ - 'venv\Scripts\Activate.ps1'
68
+ - python -V
69
+ - pip install '.[test]'
70
+ script:
71
+ # - pytest --junitxml=reports/junit.xml --cov=esptest --cov-report=term --cov-report xml:reports/coverage.xml
72
+ - $env:COVERAGE_FILE = ".coverage.$env:CI_JOB_ID"
73
+ - pytest --junitxml=reports/junit.xml --cov=esptest --cov-report=term
74
+ # coverage: '/TOTAL.*\s+(\d+)\%/'
75
+ artifacts:
76
+ paths:
77
+ # - reports/
78
+ - '.coverage*'
79
+ reports:
80
+ junit: reports/junit.xml
81
+ expire_in: 2 days
82
+ parallel:
83
+ matrix:
84
+ - PYTHON_VER: "3.7"
85
+ - PYTHON_VER: "3.11"
86
+ - PYTHON_VER: "3.14"
87
+ tags:
88
+ - windows
89
+ - pymanager
90
+
91
+ pytest-coverage:
92
+ stage: test
93
+ needs:
94
+ - job: pytest-linux
95
+ artifacts: true
96
+ - job: pytest-win32
97
+ artifacts: false # Do not merge coverage from win32
98
+ before_script:
99
+ - pip install coverage
100
+ script:
101
+ - coverage combine .coverage*
102
+ - coverage report
103
+ - coverage xml
104
+ - coverage html
105
+ coverage: '/TOTAL.*\s+(\d+)\%/'
106
+ artifacts:
107
+ paths:
108
+ - htmlcov/
109
+ - '.coverage'
110
+ - coverage.xml
111
+ # reports:
112
+ # coverage: 'coverage.xml'
113
+ expire_in: 1 week
47
114
  tags:
48
115
  - host_test
49
116
 
@@ -1,3 +1,19 @@
1
+ ## v0.3.1 (2026-01-16)
2
+
3
+
4
+ - feat: add diff values for pyecharts
5
+ - feat: add secure boot check
6
+ - fix: pass pytest on windows
7
+
8
+ ## v0.3.0 (2025-12-10)
9
+
10
+
11
+ - feat: esp-listports support monitor mode
12
+ - feat: add more logs to H3CSwitch
13
+ - feat: esp-downbin support argument --force-no-stub
14
+ - feat: add h3c switch device control
15
+ - feat: add decorator timeit
16
+
1
17
  ## v0.2.3 (2025-11-14)
2
18
 
3
19
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: esp-test-utils
3
- Version: 0.2.3
3
+ Version: 0.3.1
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.2.3
3
+ Version: 0.3.1
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
@@ -52,6 +52,7 @@ esptest/devices/attenuator.py
52
52
  esptest/devices/esp_serial.py
53
53
  esptest/devices/serial_dut.py
54
54
  esptest/devices/serial_tools.py
55
+ esptest/devices/switch.py
55
56
  esptest/env/__init__.py
56
57
  esptest/env/base_env.py
57
58
  esptest/env/wifi_env.py
@@ -79,6 +80,7 @@ esptest/tools/copy_bin.py
79
80
  esptest/tools/download_bin.py
80
81
  esptest/tools/http_download.py
81
82
  esptest/tools/pip_check.py
83
+ esptest/tools/uart_monitor.py
82
84
  esptest/utility/gen_esp32part.py
83
85
  esptest/utility/parse_bin_path.py
84
86
  example/jap_test.py
@@ -93,6 +95,7 @@ tests/adapter/test_shell_port.py
93
95
  tests/basic/test_decorators.py
94
96
  tests/basic/test_network.py
95
97
  tests/db/test_db_runners.py
98
+ tests/devices/test_switch.py
96
99
  tests/esp_console/conftest.py
97
100
  tests/esp_console/test_WifiCmd.py
98
101
  tests/esp_console/_files/wifi_cmd_connected_1.log
@@ -3,8 +3,10 @@ pyserial
3
3
  PyYAML
4
4
  pexpect
5
5
  pyusb
6
+ pyudev
6
7
  esptool
7
8
  packaging
9
+ rich
8
10
  sqlalchemy
9
11
 
10
12
  [:python_version < "3.11"]
@@ -258,9 +258,9 @@ class DutBase(VariablesMixin, DutInterface): # pylint: disable=too-many-public-
258
258
  @overload
259
259
  def expect(self, pattern: bytes, timeout: float = 30) -> None: ...
260
260
  @overload
261
- def expect(self, pattern: re.Pattern[str], timeout: float = 30) -> re.Match[str]: ...
261
+ def expect(self, pattern: 're.Pattern[str]', timeout: float = 30) -> 're.Match[str]': ...
262
262
  @overload
263
- def expect(self, pattern: re.Pattern[bytes], timeout: float = 30) -> re.Match[bytes]: ...
263
+ def expect(self, pattern: 're.Pattern[bytes]', timeout: float = 30) -> 're.Match[bytes]': ...
264
264
 
265
265
  def expect(self, pattern, timeout=30): # type: ignore
266
266
  if self._base_port_proxy:
@@ -6,7 +6,7 @@ from ...common.generator import get_next_index
6
6
  from ...interface.dut import DutInterface
7
7
  from ...logger import get_logger
8
8
  from ..port.base_port import RawPort
9
- from ..port.serial_port import SerialExt, SerialPort
9
+ from ..port.serial_port import SerialExt, SerialPort, serial_add_mixin
10
10
  from .create_dut import create_dut
11
11
  from .dut_base import DutBase, DutConfig
12
12
  from .esp_dut import EspDut
@@ -62,6 +62,11 @@ def dut_wrapper(dut, name='', log_file='', wrap_cls=None): # type: ignore
62
62
  dut.__class__ = SerialExt
63
63
  dut_config = DutConfig(opened_port=dut, name=_name, log_file=log_file)
64
64
  wrap_dut = wrap_cls(dut_config=dut_config)
65
+ elif isinstance(dut, serial.SerialBase):
66
+ _name = name or dut.port.split('/')[-1]
67
+ dut.__class__ = serial_add_mixin(dut.__class__)
68
+ dut_config = DutConfig(opened_port=dut, name=_name, log_file=log_file)
69
+ wrap_dut = wrap_cls(dut_config=dut_config)
65
70
  elif isinstance(dut, RawPort):
66
71
  _name = name
67
72
  if not _name:
@@ -5,13 +5,12 @@ import logging
5
5
  import os
6
6
  import queue
7
7
  import re
8
+ import sys
8
9
  import threading
9
10
  import time
10
11
  from dataclasses import dataclass
11
12
  from typing import overload
12
13
 
13
- import pexpect.spawnbase
14
-
15
14
  import esptest.common.compat_typing as t
16
15
 
17
16
  from ...common import timestamp_str, to_bytes, to_str
@@ -19,6 +18,18 @@ from ...common.decorators import deprecated
19
18
  from ...interface.port import PortInterface
20
19
  from ...logger import get_logger
21
20
 
21
+ if sys.platform == 'win32':
22
+ import pexpect
23
+ from pexpect.exceptions import ExceptionPexpect
24
+ from pexpect.spawnbase import SpawnBase
25
+ # from wexpect import SpawnPipe as SpawnBase
26
+ # from wexpect import ExceptionPexpect
27
+ else:
28
+ import pexpect
29
+ from pexpect.exceptions import ExceptionPexpect
30
+ from pexpect.spawnbase import SpawnBase
31
+
32
+
22
33
  logger = get_logger('port')
23
34
  NEVER_MATCHED_MAGIC_STRING = 'o6K,Q.(w+~yr~N9R'
24
35
 
@@ -26,6 +37,13 @@ NEVER_MATCHED_MAGIC_STRING = 'o6K,Q.(w+~yr~N9R'
26
37
  class ExpectTimeout(TimeoutError):
27
38
  """raise same ExpectTimeout rather than different Exception from different framework"""
28
39
 
40
+ def __init__(self, message: str, data_in_buffer: t.Union[str, bytes] = b'') -> None:
41
+ super().__init__(message)
42
+ self.data_in_buffer: t.Union[str, bytes] = data_in_buffer
43
+
44
+ def __str__(self) -> str:
45
+ return f'{super().__str__()}\n data_in_buffer={repr(self.data_in_buffer)}'
46
+
29
47
 
30
48
  class RawPort(metaclass=abc.ABCMeta):
31
49
  """Define a minimum Dut class, the dut objects should at least support these methods
@@ -74,7 +92,7 @@ class SpawnConfig:
74
92
  # TODO: monitors
75
93
 
76
94
 
77
- class PortSpawn(pexpect.spawnbase.SpawnBase, t.Generic[T]):
95
+ class PortSpawn(SpawnBase, t.Generic[T]):
78
96
  """Create a new class for pexpect with port read()/write() method.
79
97
 
80
98
  There's some reason that we can not use pyserial with pexpect.fdpexpect directly:
@@ -261,6 +279,27 @@ class PortSpawn(pexpect.spawnbase.SpawnBase, t.Generic[T]):
261
279
  self._line_cache = b''
262
280
 
263
281
 
282
+ def handle_expect_timeout(func: t.Callable) -> t.Callable:
283
+ """Raise same type exception ExpectTimeout for ports from different frameworks"""
284
+
285
+ @functools.wraps(func)
286
+ def wrap(obj: 'BasePort', *args, **kwargs): # type: ignore
287
+ try:
288
+ result = func(obj, *args, **kwargs)
289
+ except obj.expect_timeout_exceptions as e:
290
+ data_in_buffer = ''
291
+ try:
292
+ if obj._pexpect_spawn: # pylint: disable=protected-access
293
+ data_in_buffer = obj._pexpect_spawn.before # pylint: disable=protected-access
294
+ except AttributeError:
295
+ pass # ignore
296
+ obj.logger.debug(f'ExpectTimeout: {str(e)}, data_in_buffer={repr(data_in_buffer)}')
297
+ raise ExpectTimeout(str(e), data_in_buffer=data_in_buffer) from e
298
+ return result
299
+
300
+ return wrap
301
+
302
+
264
303
  class BasePort(PortInterface, t.Generic[T]):
265
304
  """A class to simply port methods for all devices / shell / sockets to similar usage
266
305
 
@@ -271,7 +310,7 @@ class BasePort(PortInterface, t.Generic[T]):
271
310
 
272
311
  EXPECT_TIMEOUT_EXCEPTIONS: t.Tuple[t.Type[Exception], ...] = (
273
312
  TimeoutError,
274
- pexpect.exceptions.ExceptionPexpect,
313
+ ExceptionPexpect,
275
314
  )
276
315
  INIT_START_REDIRECT_THREAD: bool = True
277
316
  PEXPECT_DEFAULT_TIMEOUT: float = 30
@@ -399,20 +438,6 @@ class BasePort(PortInterface, t.Generic[T]):
399
438
  if stopped:
400
439
  self.start_redirect_thread()
401
440
 
402
- @staticmethod
403
- def handle_expect_timeout(func: t.Callable) -> t.Callable:
404
- """Raise same type exception ExpectTimeout for ports from different frameworks"""
405
-
406
- @functools.wraps(func)
407
- def wrap(self, *args, **kwargs): # type: ignore
408
- try:
409
- result = func(self, *args, **kwargs)
410
- except self.expect_timeout_exceptions as e:
411
- raise ExpectTimeout(str(e)) from e
412
- return result
413
-
414
- return wrap
415
-
416
441
  def write(self, data: t.AnyStr) -> None:
417
442
  if self._pexpect_spawn:
418
443
  return self._pexpect_spawn.write(data)
@@ -434,9 +459,9 @@ class BasePort(PortInterface, t.Generic[T]):
434
459
  @overload
435
460
  def expect(self, pattern: bytes, timeout: float = 30) -> None: ...
436
461
  @overload
437
- def expect(self, pattern: re.Pattern[str], timeout: float = 30) -> re.Match[str]: ...
462
+ def expect(self, pattern: 're.Pattern[str]', timeout: float = 30) -> 're.Match[str]': ...
438
463
  @overload
439
- def expect(self, pattern: re.Pattern[bytes], timeout: float = 30) -> re.Match[bytes]: ...
464
+ def expect(self, pattern: 're.Pattern[bytes]', timeout: float = 30) -> 're.Match[bytes]': ...
440
465
 
441
466
  @handle_expect_timeout
442
467
  def expect(self, pattern, timeout=PEXPECT_DEFAULT_TIMEOUT): # type: ignore
@@ -1,28 +1,43 @@
1
1
  import time
2
- from typing import TYPE_CHECKING, Any, AnyStr, Dict, Optional, TypeAlias
2
+ from typing import TYPE_CHECKING
3
3
 
4
4
  import serial
5
- from serial import Serial
5
+ from serial import Serial, SerialBase
6
+
7
+ import esptest.common.compat_typing as t
6
8
 
7
9
  from ...common import to_bytes
8
10
  from ...logger import get_logger
9
11
  from .base_port import BasePort
10
12
 
11
13
  if TYPE_CHECKING:
12
- MixinBase: TypeAlias = 'BasePort'
14
+ MixinBase: t.TypeAlias = 'BasePort'
13
15
  else:
14
16
  MixinBase = object
15
17
 
16
18
  logger = get_logger('ser_port')
17
19
 
18
20
 
19
- class SerialExt(Serial):
20
- """Add RawPort methods to serial.Serial"""
21
+ class SerialBaseProtocol(t.Protocol):
22
+ @property
23
+ def port(self) -> t.Optional[str]: ...
24
+
25
+ @property
26
+ def baudrate(self) -> t.Optional[int]: ...
27
+
28
+ @property
29
+ def timeout(self) -> t.Optional[float]: ...
30
+
31
+ def read(self, size: int = 1) -> bytes: ...
32
+
33
+ def write(self, data: t.AnyStr) -> int: ...
21
34
 
35
+
36
+ class SerMixin(SerialBaseProtocol):
22
37
  @property
23
38
  def read_timeout(self) -> float:
24
39
  # For PortSpawn
25
- return super().timeout or 0.001 # type: ignore
40
+ return self.timeout or 0.001 # type: ignore
26
41
 
27
42
  def read_bytes(self, timeout: float = 0.001) -> bytes:
28
43
  # For PortSpawn
@@ -30,25 +45,52 @@ class SerialExt(Serial):
30
45
  assert self.timeout >= 0.001
31
46
  if timeout > self.timeout:
32
47
  time.sleep(timeout - self.timeout)
33
- return super().read(1024) # type: ignore
48
+ return self.read(1024) # type: ignore
34
49
 
35
- def write_bytes(self, data: AnyStr) -> None:
50
+ def write_bytes(self, data: t.AnyStr) -> int:
36
51
  # For PortSpawn
37
- super().write(to_bytes(data))
52
+ self.write(to_bytes(data))
53
+ return len(to_bytes(data))
38
54
 
39
55
  def __str__(self) -> str:
40
- """SerialPort<device=xxx,baudrate=xxx,timeout=xxx>"""
41
- return f'SerialPort<device={self.port},baudrate={self.baudrate},timeout={self.timeout}>'
56
+ """SerialExt<device=xxx,baudrate=xxx,timeout=xxx>"""
57
+ return f'SerialExt<device={self.port},baudrate={self.baudrate},timeout={self.timeout}>'
58
+
59
+
60
+ class SerialExt(Serial, SerMixin):
61
+ """Add RawPort methods to serial.Serial"""
62
+
63
+
64
+ def serial_add_mixin(cls: t.Type[t.Any]) -> t.Type[t.Any]:
65
+ """动态为类添加 SerMixin"""
66
+ # 创建一个新的类,继承自原始类和 SerMixin
67
+ # 基类顺序与 SerialExt(Serial, SerMixin) 保持一致
68
+ return type(f'{cls.__name__}Ext', (cls, SerMixin), {})
42
69
 
43
70
 
44
71
  class SerialPortMixin(MixinBase):
45
72
  """Add RawPort methods to serial.Serial"""
46
73
 
47
- def __init__(self, raw_port: Any, name: str, log_file: str = '') -> None:
48
- if isinstance(raw_port, Serial):
74
+ @staticmethod
75
+ def _add_mixin_by_type(raw_port: t.Any) -> None:
76
+ """根据原始类型添加对应的 mixin"""
77
+ if raw_port is None:
78
+ return
79
+ original_type = type(raw_port)
80
+ # If the original type already includes SerMixin, do nothing.
81
+ # This prevents repeatedly nesting mixin classes when the serial
82
+ # object is reassigned and _add_mixin_by_type is called multiple times.
83
+ if issubclass(original_type, SerMixin):
84
+ return
85
+ if issubclass(original_type, Serial):
49
86
  raw_port.__class__ = SerialExt
87
+ elif issubclass(original_type, SerialBase):
88
+ raw_port.__class__ = serial_add_mixin(original_type)
89
+
90
+ def __init__(self, raw_port: t.Any, name: str, log_file: str = '') -> None:
91
+ self._add_mixin_by_type(raw_port)
50
92
  super().__init__(raw_port, name, log_file)
51
- self._serial_config: Dict[str, Any] = {}
93
+ self._serial_config: t.Dict[str, t.Any] = {}
52
94
 
53
95
  def start_redirect_thread(self) -> None:
54
96
  if not self.serial:
@@ -72,12 +114,12 @@ class SerialPortMixin(MixinBase):
72
114
  super().start_redirect_thread()
73
115
 
74
116
  @property
75
- def serial(self) -> Optional[SerialExt]:
117
+ def serial(self) -> t.Optional[SerialExt]:
76
118
  """Get Current serial instance."""
77
119
  return self._raw_port # type: ignore
78
120
 
79
121
  @serial.setter
80
- def serial(self, serial_instance: Optional[Serial]) -> None:
122
+ def serial(self, serial_instance: t.Optional[Serial]) -> None:
81
123
  """Set serial instance, will close and clean up the old serial resources"""
82
124
  if self._raw_port:
83
125
  # Close pexpect proc
@@ -87,7 +129,7 @@ class SerialPortMixin(MixinBase):
87
129
  # self._port.close()
88
130
  if serial_instance:
89
131
  self._raw_port = serial_instance
90
- self._raw_port.__class__ = SerialExt
132
+ self._add_mixin_by_type(serial_instance)
91
133
  self.start_redirect_thread()
92
134
 
93
135
  def close(self) -> None:
@@ -107,7 +149,7 @@ class SerialPort(SerialPortMixin, BasePort):
107
149
  This class using serial with pexpect.
108
150
  """
109
151
 
110
- def __init__(self, dut: Serial, name: str, log_file: str = '', **kwargs: Any) -> None:
152
+ def __init__(self, dut: Serial, name: str, log_file: str = '', **kwargs: t.Any) -> None:
111
153
  if not dut:
112
154
  self.INIT_START_REDIRECT_THREAD = False # pylint: disable=invalid-name
113
155
  super().__init__(dut, name, log_file, **kwargs)