esp-test-utils 0.1.0__py3-none-any.whl
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/__init__.py +1 -0
- esp_test_utils/__main__.py +20 -0
- esp_test_utils/adapter/__init__.py +0 -0
- esp_test_utils/adapter/base_port.py +452 -0
- esp_test_utils/adapter/dut/__init__.py +2 -0
- esp_test_utils/adapter/dut/dut_base.py +118 -0
- esp_test_utils/adapter/dut/serial_dut.py +107 -0
- esp_test_utils/adapter/dut/wrapper.py +63 -0
- esp_test_utils/basic/__init__.py +3 -0
- esp_test_utils/basic/decorators.py +101 -0
- esp_test_utils/basic/encoding.py +38 -0
- esp_test_utils/basic/network.py +193 -0
- esp_test_utils/basic/timestamp.py +17 -0
- esp_test_utils/basic/utils.py +5 -0
- esp_test_utils/config/__init__.py +1 -0
- esp_test_utils/config/default_config.py +2 -0
- esp_test_utils/config/env_config.py +130 -0
- esp_test_utils/devices/__init__.py +2 -0
- esp_test_utils/devices/attenuator.py +287 -0
- esp_test_utils/devices/serial_dut.py +28 -0
- esp_test_utils/devices/serial_tools.py +39 -0
- esp_test_utils/env/__init__.py +0 -0
- esp_test_utils/env/base_env.py +44 -0
- esp_test_utils/env/wifi_env.py +8 -0
- esp_test_utils/esp_console/__init__.py +1 -0
- esp_test_utils/esp_console/wifi_cmd.py +251 -0
- esp_test_utils/iperf_utility/__init__.py +10 -0
- esp_test_utils/iperf_utility/iperf_results.py +192 -0
- esp_test_utils/iperf_utility/iperf_test.py +137 -0
- esp_test_utils/iperf_utility/iperf_test.test.py +19 -0
- esp_test_utils/iperf_utility/line_chart.py +91 -0
- esp_test_utils/logger/__init__.py +1 -0
- esp_test_utils/logger/logger.py +10 -0
- esp_test_utils/network/__init__.py +0 -0
- esp_test_utils/tools/copy_bin.py +91 -0
- esp_test_utils/tools/set_att.py +25 -0
- esp_test_utils-0.1.0.dist-info/LICENSE +201 -0
- esp_test_utils-0.1.0.dist-info/METADATA +273 -0
- esp_test_utils-0.1.0.dist-info/RECORD +42 -0
- esp_test_utils-0.1.0.dist-info/WHEEL +5 -0
- esp_test_utils-0.1.0.dist-info/entry_points.txt +2 -0
- esp_test_utils-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .adapter.dut import dut_wrapper # noqa: F401
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def main() -> None:
|
|
5
|
+
"""main function, show python version and module version"""
|
|
6
|
+
print(f'Current python version: {sys.version}')
|
|
7
|
+
assert sys.version_info.major == 3, 'Only support python3'
|
|
8
|
+
if sys.version_info.minor < 8:
|
|
9
|
+
import pkg_resources
|
|
10
|
+
|
|
11
|
+
package_ver = pkg_resources.get_distribution('esp_test_utils').version
|
|
12
|
+
else:
|
|
13
|
+
from importlib.metadata import version
|
|
14
|
+
|
|
15
|
+
package_ver = version('esp_test_utils')
|
|
16
|
+
print(f'Installed esp_test_utils version: {package_ver}')
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
if __name__ == '__main__':
|
|
20
|
+
main()
|
|
File without changes
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import functools
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import queue
|
|
6
|
+
import re
|
|
7
|
+
import threading
|
|
8
|
+
import time
|
|
9
|
+
from typing import AnyStr
|
|
10
|
+
from typing import Callable
|
|
11
|
+
from typing import Generic
|
|
12
|
+
from typing import Optional
|
|
13
|
+
from typing import overload
|
|
14
|
+
from typing import Tuple
|
|
15
|
+
from typing import Type
|
|
16
|
+
from typing import TypeVar
|
|
17
|
+
from typing import Union
|
|
18
|
+
|
|
19
|
+
import pexpect.spawnbase
|
|
20
|
+
|
|
21
|
+
from ..basic import generate_timestamp
|
|
22
|
+
from ..basic import to_bytes
|
|
23
|
+
from ..basic import to_str
|
|
24
|
+
from ..logger import get_logger
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
from typing import Self
|
|
28
|
+
except ImportError:
|
|
29
|
+
# ignore type hints: Self
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
LOGGER = get_logger('SerialDut')
|
|
33
|
+
NEVER_MATCHED_MAGIC_STRING = 'o6K,Q.(w+~yr~N9R'
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ExpectTimeout(TimeoutError):
|
|
37
|
+
"""raise same ExpectTimeout rather than different Exception from different framework"""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class RawPort(metaclass=abc.ABCMeta):
|
|
41
|
+
"""Define a minimum Dut class, the dut objects should at least support these methods
|
|
42
|
+
|
|
43
|
+
the dut should at least support these attributes:
|
|
44
|
+
- attribute name with type str
|
|
45
|
+
- method: write_bytes() with parameters: data[bytes]
|
|
46
|
+
- method: read_bytes() with parameters: timeout[float]
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def __subclasshook__(cls, subclass: object) -> bool:
|
|
51
|
+
if not hasattr(subclass, 'read_bytes') or not callable(subclass.read_bytes):
|
|
52
|
+
return False
|
|
53
|
+
if not hasattr(subclass, 'write_bytes') or not callable(subclass.write_bytes):
|
|
54
|
+
return False
|
|
55
|
+
return True
|
|
56
|
+
|
|
57
|
+
def write_bytes(self, data: bytes) -> None:
|
|
58
|
+
"""write bytes"""
|
|
59
|
+
raise NotImplementedError('Port class should implement this method')
|
|
60
|
+
|
|
61
|
+
def read_bytes(self, timeout: float = 0) -> bytes:
|
|
62
|
+
"""blocking read bytes"""
|
|
63
|
+
raise NotImplementedError('Port class should implement this method')
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
T = TypeVar('T', bound=RawPort)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class PortSpawn(pexpect.spawnbase.SpawnBase, Generic[T]):
|
|
70
|
+
"""Create a new class for pexpect with port read()/write() method.
|
|
71
|
+
|
|
72
|
+
There's some reason that we can not use pyserial with pexpect.fdpexpect directly:
|
|
73
|
+
- pyserial do not support fileno in windows.
|
|
74
|
+
- Pexpect only read from serial during expect() method.
|
|
75
|
+
- Can not read more than 4K data at once, the data may be lost if it is not read in time:
|
|
76
|
+
- https://stackoverflow.com/questions/2415074/serial-port-not-able-to-write-big-chunk-of-data
|
|
77
|
+
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
DEFAULT_READ_INTERVAL = 0.005
|
|
81
|
+
|
|
82
|
+
def __init__(
|
|
83
|
+
self,
|
|
84
|
+
port: T,
|
|
85
|
+
name: str = '',
|
|
86
|
+
log_file: Optional[str] = None,
|
|
87
|
+
timeout: float = 30,
|
|
88
|
+
logger: logging.Logger = LOGGER,
|
|
89
|
+
) -> None:
|
|
90
|
+
"""PortSpawn for pexpect
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
port (RawPort): port instance with read() method.
|
|
94
|
+
log_file (str, optional): log file path for saving serial output logs. Defaults to None.
|
|
95
|
+
timeout (int, optional): pexpect default timeout. Defaults to 30.
|
|
96
|
+
logger (logging.Logger): Specific port logger for logging.
|
|
97
|
+
"""
|
|
98
|
+
super().__init__(timeout=timeout)
|
|
99
|
+
assert isinstance(port, RawPort)
|
|
100
|
+
self.name = name
|
|
101
|
+
self._port = port
|
|
102
|
+
if not self.name and hasattr(self.port, 'name'):
|
|
103
|
+
assert isinstance(self.port.name, str)
|
|
104
|
+
self.name = self.port.name
|
|
105
|
+
self.logger = logger
|
|
106
|
+
# Save serial logs to file
|
|
107
|
+
self.log_file = log_file
|
|
108
|
+
|
|
109
|
+
self._data_cache = b''
|
|
110
|
+
self._line_cache = b''
|
|
111
|
+
self._last_write_log_time = time.time()
|
|
112
|
+
# Create a new thread to read data from serial port
|
|
113
|
+
self._read_queue: queue.Queue = queue.Queue()
|
|
114
|
+
self._read_thread_stop_event = threading.Event()
|
|
115
|
+
self._read_thread = threading.Thread(target=self._read_incoming, name=f'Spawn_{self.name}')
|
|
116
|
+
self._read_thread.daemon = True
|
|
117
|
+
self._read_thread.start()
|
|
118
|
+
self.receive_callback: Optional[Callable[[str, AnyStr], None]] = None
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def port(self) -> T:
|
|
122
|
+
return self._port
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def read_timeout(self) -> float:
|
|
126
|
+
if hasattr(self.port, 'read_timeout'):
|
|
127
|
+
_timeout = self.port.read_timeout
|
|
128
|
+
assert isinstance(_timeout, float)
|
|
129
|
+
assert _timeout > 0
|
|
130
|
+
return _timeout
|
|
131
|
+
return self.DEFAULT_READ_INTERVAL
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def data_cache(self) -> str:
|
|
135
|
+
return self._data_cache.decode('utf-8', errors='replace')
|
|
136
|
+
|
|
137
|
+
def _write_port_log(self, data: bytes) -> None:
|
|
138
|
+
"""Write serial outputs to log file"""
|
|
139
|
+
data_to_write = b''
|
|
140
|
+
if data:
|
|
141
|
+
self._line_cache += data
|
|
142
|
+
if self._line_cache.endswith(b'\n'):
|
|
143
|
+
data_to_write = self._line_cache
|
|
144
|
+
self._line_cache = b''
|
|
145
|
+
elif b'\n' in self._line_cache:
|
|
146
|
+
_index = self._line_cache.rfind(b'\n') + 1
|
|
147
|
+
data_to_write = self._line_cache[:_index]
|
|
148
|
+
self._line_cache = self._line_cache[_index:]
|
|
149
|
+
if not data_to_write and self._line_cache and time.time() - self._last_write_log_time > self.read_timeout * 5:
|
|
150
|
+
# No new data for a long time, flush line cache
|
|
151
|
+
# Default timeout is serial.timeout * 5, depends on read timeout of serial instance
|
|
152
|
+
# Minimum serial timeout is 1ms, 5 ms should be enough for most lines.
|
|
153
|
+
data_to_write = self._line_cache
|
|
154
|
+
self._line_cache = b''
|
|
155
|
+
|
|
156
|
+
if data_to_write:
|
|
157
|
+
self._last_write_log_time = time.time()
|
|
158
|
+
if self.log_file:
|
|
159
|
+
with open(self.log_file, 'ab+') as f:
|
|
160
|
+
_time_info = f'\n[{generate_timestamp()}]\n'.encode()
|
|
161
|
+
f.write(_time_info)
|
|
162
|
+
f.write(data_to_write)
|
|
163
|
+
else:
|
|
164
|
+
self.logger.debug(f'[{self.name}]: {to_str(data_to_write)}')
|
|
165
|
+
|
|
166
|
+
def _read_incoming(self) -> None:
|
|
167
|
+
"""Running in a thread to read serial output and save to data cache."""
|
|
168
|
+
self.logger.debug(f'Start serial {self.name} read thread.')
|
|
169
|
+
assert isinstance(self.read_timeout, float)
|
|
170
|
+
assert self.read_timeout > 0
|
|
171
|
+
while True:
|
|
172
|
+
if self._read_thread_stop_event.is_set():
|
|
173
|
+
# Stop the thread when spawn stop.
|
|
174
|
+
self.logger.debug(f'Stop port {self.name} read thread.')
|
|
175
|
+
return
|
|
176
|
+
new_data = b''
|
|
177
|
+
try:
|
|
178
|
+
# some port instances do not support changing read timeout, therefore use default timeout of the
|
|
179
|
+
new_data = self.port.read_bytes(timeout=self.read_timeout)
|
|
180
|
+
except Exception as e: # pylint: disable=W0718
|
|
181
|
+
self._log(to_bytes(f'PortRead {type(e)}: {str(e)}'), 'read')
|
|
182
|
+
self._write_port_log(to_bytes(f'SerialException: {str(e)}'))
|
|
183
|
+
self.logger.exception(f'{self.name} reading thread stopped {type(e)}: {str(e)}')
|
|
184
|
+
return
|
|
185
|
+
if new_data:
|
|
186
|
+
self._read_queue.put(new_data)
|
|
187
|
+
if self.receive_callback and callable(self.receive_callback):
|
|
188
|
+
# https://stackoverflow.com/questions/69732212/pylint-self-xxx-is-not-callable
|
|
189
|
+
self.receive_callback(self.name, new_data) # pylint: disable=E1102
|
|
190
|
+
# the last line may be cached, to make the file more readable after adding timestamp
|
|
191
|
+
# always check need write to file or not whether there's new data
|
|
192
|
+
self._write_port_log(new_data)
|
|
193
|
+
|
|
194
|
+
def write(self, data: AnyStr) -> None:
|
|
195
|
+
self.port.write_bytes(to_bytes(data))
|
|
196
|
+
|
|
197
|
+
def read_nonblocking(self, size: int = 1, timeout: Optional[Union[int, float]] = None) -> bytes:
|
|
198
|
+
"""This method was used during expect(), reads data from serial output data cache.
|
|
199
|
+
|
|
200
|
+
If the data cache is not empty, it will return immediately. Otherwise, waiting for new data.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
size (int, optional): maximum size of returning data. Defaults to 1.
|
|
204
|
+
timeout (Union[int, float] | None, optional): maximum block time waiting for new data.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
bytes: new serial output data.
|
|
208
|
+
"""
|
|
209
|
+
if timeout is None:
|
|
210
|
+
timeout = self.timeout
|
|
211
|
+
assert timeout is not None
|
|
212
|
+
t0 = time.time()
|
|
213
|
+
# Read out all cache from queue first
|
|
214
|
+
while True:
|
|
215
|
+
try:
|
|
216
|
+
_new_data = self._read_queue.get(timeout=0)
|
|
217
|
+
self._data_cache += _new_data
|
|
218
|
+
except queue.Empty:
|
|
219
|
+
break
|
|
220
|
+
self.logger.debug(self._data_cache)
|
|
221
|
+
# Waiting for more data until timeout if there's no data cache.
|
|
222
|
+
# Any new data should be returned immediately.
|
|
223
|
+
time_left = t0 + timeout - time.time()
|
|
224
|
+
while not self._data_cache and time_left > 0:
|
|
225
|
+
try:
|
|
226
|
+
_new_data = self._read_queue.get(timeout=time_left)
|
|
227
|
+
self._data_cache += _new_data
|
|
228
|
+
except queue.Empty:
|
|
229
|
+
break
|
|
230
|
+
time_left = t0 + timeout - time.time()
|
|
231
|
+
# Returned data should not more than given size.
|
|
232
|
+
if self._data_cache:
|
|
233
|
+
ret_data = self._data_cache[:size]
|
|
234
|
+
self._data_cache = self._data_cache[size:]
|
|
235
|
+
else:
|
|
236
|
+
ret_data = b''
|
|
237
|
+
# _log here to be same with pexpect SpawnBase
|
|
238
|
+
self._log(ret_data, 'read') # type: ignore
|
|
239
|
+
return ret_data
|
|
240
|
+
|
|
241
|
+
def stop(self) -> None:
|
|
242
|
+
"""Stop and clean up"""
|
|
243
|
+
self.logger.debug(f'Stopping SerialSpawn {self.name}')
|
|
244
|
+
self._read_thread_stop_event.set()
|
|
245
|
+
self._read_thread.join()
|
|
246
|
+
self._read_queue.empty()
|
|
247
|
+
self.receive_callback = None
|
|
248
|
+
self._data_cache = b''
|
|
249
|
+
self._line_cache = b''
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class BasePort(Generic[T]):
|
|
253
|
+
"""A class to simply port methods for all devices / shell / sockets to similar usage
|
|
254
|
+
|
|
255
|
+
- Create receive thread and pexpect spawn process for data read/expect
|
|
256
|
+
- Redefine
|
|
257
|
+
|
|
258
|
+
"""
|
|
259
|
+
|
|
260
|
+
EXPECT_TIMEOUT_EXCEPTIONS: Tuple[Type[Exception], ...] = (
|
|
261
|
+
TimeoutError,
|
|
262
|
+
pexpect.exceptions.ExceptionPexpect,
|
|
263
|
+
)
|
|
264
|
+
INIT_START_PEXPECT_PROC: bool = True
|
|
265
|
+
DISABLE_PEXPECT_PROC: bool = False
|
|
266
|
+
PEXPECT_DEFAULT_TIMEOUT: float = 30
|
|
267
|
+
|
|
268
|
+
def __init__(
|
|
269
|
+
self,
|
|
270
|
+
port: T,
|
|
271
|
+
name: str = '',
|
|
272
|
+
log_file: str = '',
|
|
273
|
+
logger: Optional[logging.Logger] = None,
|
|
274
|
+
) -> None:
|
|
275
|
+
if port:
|
|
276
|
+
assert isinstance(port, RawPort)
|
|
277
|
+
self._port = port
|
|
278
|
+
self._name = name
|
|
279
|
+
self._log_file = log_file
|
|
280
|
+
self.expect_timeout_exceptions = self.EXPECT_TIMEOUT_EXCEPTIONS
|
|
281
|
+
self.logger = logger or LOGGER
|
|
282
|
+
self.timeout = self.PEXPECT_DEFAULT_TIMEOUT
|
|
283
|
+
|
|
284
|
+
self._pexpect_proc: Optional[PortSpawn] = None
|
|
285
|
+
if self.INIT_START_PEXPECT_PROC:
|
|
286
|
+
self.start_pexpect_proc()
|
|
287
|
+
|
|
288
|
+
@property
|
|
289
|
+
def port(self) -> T:
|
|
290
|
+
return self._port # type: ignore
|
|
291
|
+
|
|
292
|
+
@property
|
|
293
|
+
def name(self) -> str:
|
|
294
|
+
return self._name
|
|
295
|
+
|
|
296
|
+
@name.setter
|
|
297
|
+
def name(self, value: str) -> None:
|
|
298
|
+
self._name = value
|
|
299
|
+
if self.spawn:
|
|
300
|
+
self.spawn.name = value
|
|
301
|
+
|
|
302
|
+
def _init_log_file(self) -> None:
|
|
303
|
+
if self.log_file:
|
|
304
|
+
os.makedirs(os.path.dirname(self.log_file), exist_ok=True)
|
|
305
|
+
with open(self.log_file, 'ab+') as f:
|
|
306
|
+
f.write(f'--------- Saving {self.name}:{self.port} logs to this file --------\n'.encode())
|
|
307
|
+
else:
|
|
308
|
+
self.logger.debug(f'do not save {self.name}:{self.port} logs to file')
|
|
309
|
+
|
|
310
|
+
@property
|
|
311
|
+
def log_file(self) -> str:
|
|
312
|
+
"""Get Current dut log file."""
|
|
313
|
+
if not self._log_file:
|
|
314
|
+
return ''
|
|
315
|
+
return os.path.abspath(self._log_file)
|
|
316
|
+
|
|
317
|
+
@log_file.setter
|
|
318
|
+
def log_file(self, new_log_file: str) -> None:
|
|
319
|
+
"""Set Current dut log file."""
|
|
320
|
+
if new_log_file == self._log_file:
|
|
321
|
+
return
|
|
322
|
+
if self._pexpect_proc:
|
|
323
|
+
self._pexpect_proc.serial_log_file = new_log_file
|
|
324
|
+
self._log_file = new_log_file
|
|
325
|
+
|
|
326
|
+
@property
|
|
327
|
+
def spawn(self) -> Optional[PortSpawn]:
|
|
328
|
+
"""Allow the use of pexpect spawn enhancements, if pexpect process is available"""
|
|
329
|
+
return self._pexpect_proc
|
|
330
|
+
|
|
331
|
+
def start_pexpect_proc(self) -> None:
|
|
332
|
+
if self.DISABLE_PEXPECT_PROC:
|
|
333
|
+
return
|
|
334
|
+
if self._pexpect_proc:
|
|
335
|
+
return
|
|
336
|
+
self._init_log_file()
|
|
337
|
+
self._pexpect_proc = PortSpawn(self.port, self.name, self.log_file, self.PEXPECT_DEFAULT_TIMEOUT, self.logger)
|
|
338
|
+
|
|
339
|
+
@staticmethod
|
|
340
|
+
def _handle_expect_timeout(func: Callable) -> Callable:
|
|
341
|
+
"""Raise same type exception ExpectTimeout for ports from different frameworks"""
|
|
342
|
+
|
|
343
|
+
@functools.wraps(func)
|
|
344
|
+
def wrap(self, *args, **kwargs): # type: ignore
|
|
345
|
+
try:
|
|
346
|
+
result = func(self, *args, **kwargs)
|
|
347
|
+
except self.expect_timeout_exceptions as e:
|
|
348
|
+
raise ExpectTimeout(str(e)) from e
|
|
349
|
+
return result
|
|
350
|
+
|
|
351
|
+
return wrap
|
|
352
|
+
|
|
353
|
+
def write(self, data: AnyStr) -> None:
|
|
354
|
+
if self._pexpect_proc:
|
|
355
|
+
return self._pexpect_proc.write(data)
|
|
356
|
+
raise NotImplementedError()
|
|
357
|
+
|
|
358
|
+
def write_line(self, data: AnyStr, end: str = '\n') -> None:
|
|
359
|
+
return self.write(to_bytes(data, end))
|
|
360
|
+
|
|
361
|
+
@_handle_expect_timeout
|
|
362
|
+
def expect_exact(self, pattern: Union[str, bytes], timeout: float) -> None:
|
|
363
|
+
"""this is similar to expect(), but only uses plain string/bytes matching"""
|
|
364
|
+
if self.spawn:
|
|
365
|
+
pexpect_pattern = to_bytes(pattern)
|
|
366
|
+
self.spawn.expect_exact(pexpect_pattern, timeout=timeout)
|
|
367
|
+
raise NotImplementedError()
|
|
368
|
+
|
|
369
|
+
@overload
|
|
370
|
+
def expect(self, pattern: str, timeout: float = 30) -> None: ...
|
|
371
|
+
@overload
|
|
372
|
+
def expect(self, pattern: bytes, timeout: float = 30) -> None: ...
|
|
373
|
+
@overload
|
|
374
|
+
def expect(self, pattern: re.Pattern[str], timeout: float = 30) -> re.Match[str]: ...
|
|
375
|
+
@overload
|
|
376
|
+
def expect(self, pattern: re.Pattern[bytes], timeout: float = 30) -> re.Match[bytes]: ...
|
|
377
|
+
|
|
378
|
+
@_handle_expect_timeout
|
|
379
|
+
def expect(self, pattern, timeout=PEXPECT_DEFAULT_TIMEOUT): # type: ignore
|
|
380
|
+
"""This seeks through the stream until a pattern is matched.
|
|
381
|
+
|
|
382
|
+
This expect() method is different with the one in pexpect.
|
|
383
|
+
This method only accepts pattern type str/bytes or re.Pattern. Does not accept list, EOF or TIMEOUT.
|
|
384
|
+
If the pattern type is str or bytes, this method is similar to expect_exact(), but returning None.
|
|
385
|
+
If the pattern type is re.Pattern, this method will return a re.Match object if the pattern is matched.
|
|
386
|
+
Can read all output data by pattern=re.compile('.+', re.DOTALL)
|
|
387
|
+
|
|
388
|
+
Args:
|
|
389
|
+
pattern (Union[str, bytes, re.Pattern]): pattern to match
|
|
390
|
+
timeout (int, optional): seconds of waiting for new data if match failed. Defaults to 30s.
|
|
391
|
+
|
|
392
|
+
Returns:
|
|
393
|
+
Optional[re.Match]: match result if the input pattern is re.Pattern
|
|
394
|
+
"""
|
|
395
|
+
if self._pexpect_proc:
|
|
396
|
+
if isinstance(pattern, (bytes, str)):
|
|
397
|
+
self._pexpect_proc.expect_exact(pattern, timeout=timeout)
|
|
398
|
+
return None
|
|
399
|
+
|
|
400
|
+
assert isinstance(pattern, re.Pattern)
|
|
401
|
+
if isinstance(pattern.pattern, str):
|
|
402
|
+
# re-compile regex pattern using bytes, with same flags
|
|
403
|
+
re_flags = pattern.flags & (re.DOTALL | re.MULTILINE | re.IGNORECASE)
|
|
404
|
+
pexpect_pattern = re.compile(to_bytes(pattern.pattern), re_flags)
|
|
405
|
+
else:
|
|
406
|
+
pexpect_pattern = pattern
|
|
407
|
+
self._pexpect_proc.expect(pexpect_pattern, timeout=timeout)
|
|
408
|
+
match = self._pexpect_proc.match
|
|
409
|
+
if isinstance(pattern.pattern, str) and isinstance(match, re.Match):
|
|
410
|
+
# convert the match result into string
|
|
411
|
+
match = pattern.match(to_str(match.group(0)))
|
|
412
|
+
return match # type: ignore
|
|
413
|
+
raise NotImplementedError()
|
|
414
|
+
|
|
415
|
+
@property
|
|
416
|
+
def data_cache(self) -> str:
|
|
417
|
+
return self.read_all_data(flush=False)
|
|
418
|
+
|
|
419
|
+
def flush_data(self) -> str:
|
|
420
|
+
return self.read_all_data(flush=True)
|
|
421
|
+
|
|
422
|
+
def read_all_data(self, flush: bool = True) -> str:
|
|
423
|
+
return to_str(self.read_all_bytes(flush))
|
|
424
|
+
|
|
425
|
+
def read_all_bytes(self, flush: bool = False) -> bytes:
|
|
426
|
+
"""Read out all data from dut, return immediately.
|
|
427
|
+
|
|
428
|
+
Returns:
|
|
429
|
+
bytes: all data read from dut
|
|
430
|
+
"""
|
|
431
|
+
buffer = b''
|
|
432
|
+
if flush:
|
|
433
|
+
match = self.expect(re.compile(b'.*', re.DOTALL), timeout=0)
|
|
434
|
+
assert match
|
|
435
|
+
buffer = match.group(0)
|
|
436
|
+
else:
|
|
437
|
+
# flush spawn buffer
|
|
438
|
+
assert self._pexpect_proc
|
|
439
|
+
self._pexpect_proc.expect_exact(pexpect.TIMEOUT, timeout=0)
|
|
440
|
+
buffer = to_bytes(self._pexpect_proc.buffer)
|
|
441
|
+
assert isinstance(buffer, bytes)
|
|
442
|
+
return buffer
|
|
443
|
+
|
|
444
|
+
def close(self) -> None:
|
|
445
|
+
if self._pexpect_proc:
|
|
446
|
+
self._pexpect_proc.stop()
|
|
447
|
+
|
|
448
|
+
def __enter__(self) -> 'Self':
|
|
449
|
+
return self
|
|
450
|
+
|
|
451
|
+
def __exit__(self, exc_type, exc_value, trace) -> None: # type: ignore
|
|
452
|
+
self.close()
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from typing import AnyStr
|
|
3
|
+
from typing import Dict
|
|
4
|
+
from typing import Union
|
|
5
|
+
|
|
6
|
+
from ...basic.utils import mac_offset
|
|
7
|
+
from ..base_port import BasePort
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DutMacMixin:
|
|
11
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
12
|
+
super().__init__(*args, **kwargs)
|
|
13
|
+
self._device_mac: str = '00:00:00:00:00:00'
|
|
14
|
+
self._sta_mac: str = ''
|
|
15
|
+
self._ap_mac: str = ''
|
|
16
|
+
self._bt_mac: str = ''
|
|
17
|
+
self._eth_mac: str = ''
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def mac(self) -> str:
|
|
21
|
+
return self._device_mac
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def device_mac(self) -> str:
|
|
25
|
+
return self._device_mac
|
|
26
|
+
|
|
27
|
+
@device_mac.setter
|
|
28
|
+
def device_mac(self, value: str) -> None:
|
|
29
|
+
self._device_mac = value
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def sta_mac(self) -> str:
|
|
33
|
+
return self._sta_mac or self._device_mac
|
|
34
|
+
|
|
35
|
+
@sta_mac.setter
|
|
36
|
+
def sta_mac(self, value: str) -> None:
|
|
37
|
+
self._sta_mac = value
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def ap_mac(self) -> str:
|
|
41
|
+
return self._ap_mac or mac_offset(self._device_mac, 1)
|
|
42
|
+
|
|
43
|
+
@ap_mac.setter
|
|
44
|
+
def ap_mac(self, value: str) -> None:
|
|
45
|
+
self._ap_mac = value
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def bt_mac(self) -> str:
|
|
49
|
+
return self._bt_mac or mac_offset(self._device_mac, 2)
|
|
50
|
+
|
|
51
|
+
@bt_mac.setter
|
|
52
|
+
def bt_mac(self, value: str) -> None:
|
|
53
|
+
self._bt_mac = value
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def eth_mac(self) -> str:
|
|
57
|
+
# do not generate eth mac from device mac
|
|
58
|
+
return self._eth_mac # or mac_offset(self._device_mac, 3)
|
|
59
|
+
|
|
60
|
+
@eth_mac.setter
|
|
61
|
+
def eth_mac(self, value: str) -> None:
|
|
62
|
+
self._eth_mac = value
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class DutPort(DutMacMixin, BasePort):
|
|
66
|
+
"""Add dut related methods to Port"""
|
|
67
|
+
|
|
68
|
+
def __init__(self, dut: Any, name: str, log_file: str = '') -> None:
|
|
69
|
+
super().__init__(dut, name, log_file)
|
|
70
|
+
|
|
71
|
+
def write_line(self, data: AnyStr, end: str = '\r\n') -> None:
|
|
72
|
+
"""Use \\r\\n as default ending"""
|
|
73
|
+
return super().write_line(data, end)
|
|
74
|
+
|
|
75
|
+
def stop_receive_thread(self) -> None:
|
|
76
|
+
raise NotImplementedError()
|
|
77
|
+
|
|
78
|
+
def start_receive_thread(self) -> None:
|
|
79
|
+
self.start_pexpect_proc()
|
|
80
|
+
|
|
81
|
+
# Attributes needed by bin path
|
|
82
|
+
@property
|
|
83
|
+
def bin_path(self) -> str:
|
|
84
|
+
raise NotImplementedError()
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def sdkconfig(self) -> Dict[str, Any]:
|
|
88
|
+
raise NotImplementedError()
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def target(self) -> str:
|
|
92
|
+
raise NotImplementedError()
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def partition_table(self) -> Dict[str, Any]:
|
|
96
|
+
raise NotImplementedError()
|
|
97
|
+
|
|
98
|
+
# Serial Specific
|
|
99
|
+
def reconfigure(self) -> bool:
|
|
100
|
+
raise NotImplementedError()
|
|
101
|
+
|
|
102
|
+
def hard_reset(self) -> None:
|
|
103
|
+
raise NotImplementedError()
|
|
104
|
+
|
|
105
|
+
# EspTool Specific
|
|
106
|
+
def flash(self, bin_path: str = '') -> None:
|
|
107
|
+
raise NotImplementedError()
|
|
108
|
+
|
|
109
|
+
def flash_partition(self, part: Union[int, str], bin_path: str = '') -> None:
|
|
110
|
+
raise NotImplementedError()
|
|
111
|
+
|
|
112
|
+
def flash_nvs(self, bin_path: str = '') -> None:
|
|
113
|
+
raise NotImplementedError()
|
|
114
|
+
|
|
115
|
+
def dump_flash(self, part: Union[int, str], bin_path: str, size: int = 0) -> None:
|
|
116
|
+
raise NotImplementedError()
|
|
117
|
+
|
|
118
|
+
# More extra methods may be implemented
|