Skip to content

Commit 88c955b

Browse files
authored
Merge pull request #339 from espressif/feat/improve-expecting-functions
Feat/improve expecting functions
2 parents 8a64648 + 7771bff commit 88c955b

File tree

5 files changed

+91
-33
lines changed

5 files changed

+91
-33
lines changed

docs/usages/expecting.rst

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ All of these functions share these possible keyword arguments:
2020

2121
Will raise an exception if the pattern is found in the output, if specified. (Default: None)
2222

23+
- ``return_what_before_match``
24+
25+
Will return the bytes read before the match if specified. (Default: False)
26+
2327
*****************************************
2428
:func:`~pytest_embedded.dut.Dut.expect`
2529
*****************************************
@@ -130,6 +134,40 @@ What's more, argument ``pattern`` could be a list of all supported types.
130134
131135
If you set ``expect_all`` to ``True``, the :func:`~pytest_embedded.dut.Dut.expect` function would return with a list of returned values of each item.
132136

137+
You can also set ``return_what_before_match`` to ``True`` to get the bytes read before the match, instead of the match object.
138+
139+
.. code:: python
140+
141+
import pexpect
142+
143+
def test_expect_before_match(dut):
144+
dut.write('this would be redirected')
145+
146+
res = dut.expect('would', return_what_before_match=True)
147+
assert res == b'this '
148+
149+
res = dut.expect_exact('be ', return_what_before_match=True)
150+
assert res == b' '
151+
152+
res = dut.expect('ected', return_what_before_match=True)
153+
assert res == b'redir'
154+
155+
.. hint::
156+
157+
For better performance when retrieving text before a pattern, use:
158+
159+
.. code:: python
160+
161+
before_str = dut.expect('pattern', return_what_before_match=True).decode('utf-8')
162+
163+
Instead of:
164+
165+
.. code:: python
166+
167+
before_str = dut.expect('(.+)pattern').group(1).decode('utf-8')
168+
169+
The latter performs unnecessary recursive matching of preceding bytes.
170+
133171
***********************************************
134172
:func:`~pytest_embedded.dut.Dut.expect_exact`
135173
***********************************************

pytest-embedded/pytest_embedded/dut.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,17 @@ def write(self, s: AnyStr) -> None:
6767
def _pexpect_func(func) -> Callable[..., Union[Match, AnyStr]]:
6868
@functools.wraps(func)
6969
def wrapper(
70-
self, pattern, *args, expect_all: bool = False, not_matching: List[Union[str, re.Pattern]] = (), **kwargs
70+
self,
71+
pattern,
72+
*args,
73+
expect_all: bool = False,
74+
not_matching: List[Union[str, re.Pattern]] = (),
75+
return_what_before_match: bool = False,
76+
**kwargs,
7177
) -> Union[Union[Match, AnyStr], List[Union[Match, AnyStr]]]:
78+
if return_what_before_match and expect_all:
79+
raise ValueError('`return_what_before_match` and `expect_all` cannot be `True` at the same time.')
80+
7281
patterns = to_list(pattern)
7382
res = []
7483
while patterns:
@@ -101,6 +110,9 @@ def wrapper(
101110
else:
102111
break # one succeeded. leave the loop
103112

113+
if return_what_before_match:
114+
return self.pexpect_proc.before
115+
104116
if len(res) == 1:
105117
return res[0]
106118

@@ -121,6 +133,8 @@ def expect(self, pattern, **kwargs) -> Match:
121133
expect_all (bool): need to match all specified patterns if this flag is `True`.
122134
Otherwise match any of them could pass
123135
not_matching: string, or compiled regex, or a list of string and compiled regex.
136+
return_what_before_match (bool): return the bytes before the matched pattern.
137+
Cannot be specified together with `expect_all`
124138
125139
Returns:
126140
`AnyStr` or `re.Match`
@@ -143,6 +157,8 @@ def expect_exact(self, pattern, **kwargs) -> Match:
143157
expect_all (bool): need to match all specified patterns if this flag is `True`.
144158
Otherwise match any of them could pass
145159
not_matching: string, or compiled regex, or a list of string and compiled regex.
160+
return_what_before_match (bool): return the bytes before the matched pattern.
161+
Cannot be specified together with `expect_all`
146162
147163
Returns:
148164
`AnyStr` or `re.Match`

pytest-embedded/pytest_embedded/dut_factory.py

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -50,45 +50,40 @@ def msg_queue_gn() -> MessageQueue:
5050

5151

5252
def _listen(q: MessageQueue, filepath: str, with_timestamp: bool = True, count: int = 1, total: int = 1) -> None:
53-
_added_prefix = False
53+
shall_add_prefix = True
5454
while True:
55-
msgs = q.get_all()
56-
if not msgs:
55+
msg = q.get()
56+
if not msg:
5757
continue
5858

59-
msg_b = b''.join(msgs)
6059
with open(filepath, 'ab') as fw:
61-
fw.write(msg_b)
60+
fw.write(msg)
6261
fw.flush()
6362

64-
_s = to_str(msg_b)
63+
_s = to_str(msg)
6564
if not _s:
6665
continue
6766

6867
prefix = ''
6968
if total > 1:
70-
source = f'dut-{count}'
71-
else:
72-
source = None
73-
74-
if source:
75-
prefix = f'[{source}] ' + prefix
69+
prefix = f'[dut-{count}] '
7670

7771
if with_timestamp:
7872
prefix = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + ' ' + prefix
7973

80-
if not _added_prefix:
74+
if shall_add_prefix:
8175
_s = prefix + _s
82-
_added_prefix = True
76+
8377
_s = _s.replace('\r\n', '\n') # remove extra \r. since multi-dut \r would mess up the log
84-
_s = _s.replace('\n', '\n' + prefix)
85-
if prefix and _s.endswith(prefix):
86-
_s = _s.rsplit(prefix, maxsplit=1)[0]
87-
_added_prefix = False
78+
if _s.endswith('\n'): # complete line
79+
shall_add_prefix = True
80+
_s = _s[:-1].replace('\n', '\n' + prefix) + '\n'
81+
else:
82+
shall_add_prefix = False
83+
_s = _s.replace('\n', '\n' + prefix)
8884

8985
_stdout.write(_s)
9086
_stdout.flush()
91-
time.sleep(0.05)
9287

9388

9489
def _listener_gn(msg_queue, _pexpect_logfile, with_timestamp, dut_index, dut_total) -> multiprocessing.Process:

pytest-embedded/pytest_embedded/log.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import textwrap
99
import uuid
1010
from multiprocessing import queues
11-
from queue import Empty
1211
from typing import AnyStr, List, Optional, Union
1312

1413
import pexpect.fdpexpect
@@ -28,7 +27,6 @@ def __init__(self, *args, **kwargs):
2827
if 'ctx' not in kwargs:
2928
kwargs['ctx'] = _ctx
3029

31-
self.lock = _ctx.Lock()
3230
super().__init__(*args, **kwargs)
3331

3432
def put(self, obj, **kwargs):
@@ -45,17 +43,6 @@ def put(self, obj, **kwargs):
4543
except: # noqa # queue might be closed
4644
pass
4745

48-
def get_all(self) -> List[bytes]:
49-
res = []
50-
with self.lock:
51-
while True:
52-
try:
53-
res.append(self.get_nowait())
54-
except Empty:
55-
break
56-
57-
return res
58-
5946
def write(self, s: AnyStr):
6047
self.put(s)
6148

pytest-embedded/tests/test_base.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,28 @@ def test_expect_unity_test_output_multi_dut_with_illegal_chars(dut):
496496
assert junit_report[1].find('failure') is not None
497497

498498

499+
def test_expect_before_match(testdir):
500+
testdir.makepyfile(r"""
501+
import pexpect
502+
503+
def test_expect_before_match(dut):
504+
dut.write('this would be redirected')
505+
506+
res = dut.expect('would', return_what_before_match=True)
507+
assert res == b'this '
508+
509+
res = dut.expect_exact('be ', return_what_before_match=True)
510+
assert res == b' '
511+
512+
res = dut.expect('ected', return_what_before_match=True)
513+
assert res == b'redir'
514+
""")
515+
516+
result = testdir.runpytest()
517+
518+
result.assert_outcomes(passed=1)
519+
520+
499521
def test_duplicate_stdout_popen(testdir):
500522
testdir.makepyfile(r"""
501523
import pytest

0 commit comments

Comments
 (0)