Expecting Functions
In testing, most of the work involves expecting a certain string or pattern and then making assertions. This is supported by the functions expect(), expect_exact(), and expect_unity_test_output().
All of these functions accept the following keyword arguments:
timeout: Sets the timeout in seconds for this expect statement (default: 30s). Throws apexpect.TIMEOUTexception if the specified value is exceeded.expect_all: Matches all specified patterns if set toTrue(default:False).not_matching: Raises an exception if the specified pattern is found in the output (default:None).return_what_before_match: Returns the bytes read before the match if specified (default:False).
expect()
The pattern can be a str, bytes, or a compiled regex with bytes.
If the pattern is a str or bytes, it will be converted to a compiled regex with bytes before the function is run.
import re
def test_basic_expect(dut):
dut.write('this would be redirected')
dut.expect(b'this')
dut.expect('would')
dut.expect('[be]{2}')
dut.expect(re.compile(b'redirected'))
If the expect call is successful, the return value will be a re.Match object.
def test_expect_return_value(redirect, dut):
# Use the `redirect` fixture to write `sys.stdout` to the DUT
with redirect():
print('this would be redirected')
res = dut.expect('this (would) be ([cdeirt]+)')
assert res.group() == b'this would be redirected'
assert res.group(1) == b'would'
assert res.group(2).decode('utf-8') == 'redirected'
You can get the bytes read before a timeout by expecting a pexpect.TIMEOUT object.
import time
import threading
import pexpect
def test_expect_from_eof(dut):
def write_bytes():
for _ in range(5):
dut.write('1')
time.sleep(2)
write_thread = threading.Thread(target=write_bytes, daemon=True)
write_thread.start()
res = dut.expect(pexpect.TIMEOUT, timeout=3)
assert res == b'11'
You can also get all bytes in the pexpect process buffer by expecting a pexpect.EOF object.
import pexpect
def test_expect_from_eof_current_buffer(dut):
dut.write('this would be redirected')
dut.expect('this')
# Close the pexpect process to generate an EOF
dut.pexpect_proc.terminate()
res = dut.expect(pexpect.EOF, timeout=None)
assert res == b' would be redirected'
Note
The pexpect process only reads from the process into its buffer when running expect functions. If you expect pexpect.EOF as the first statement, it will return an empty byte string.
import pexpect
def test_expect_from_eof_at_first(dut):
dut.write("this would be redirected")
# Close the pexpect process to generate an EOF
dut.pexpect_proc.terminate()
res = dut.expect(pexpect.EOF, timeout=None)
assert res == b""
Additionally, the pattern argument can be a list of any of the supported types.
import re
def test_expect_from_list(dut):
dut.write("this would be redirected")
pattern_list = [
"this",
b"would",
"[be]+",
re.compile(b"redirected"),
]
for _ in range(4):
dut.expect(pattern_list)
If you set expect_all to True, the expect() function will return a list of the returned values for each item.
You can also set return_what_before_match to True to get the bytes read before the match, instead of the match object.
import pexpect
def test_expect_before_match(dut):
dut.write('this would be redirected')
res = dut.expect('would', return_what_before_match=True)
assert res == b'this '
res = dut.expect_exact('be ', return_what_before_match=True)
assert res == b' '
res = dut.expect('ected', return_what_before_match=True)
assert res == b'redir'
Hint
For better performance when retrieving text before a pattern, use:
before_str = dut.expect('pattern', return_what_before_match=True).decode('utf-8')
Instead of:
before_str = dut.expect('(.+)pattern').group(1).decode('utf-8')
The latter performs unnecessary recursive matching of preceding bytes.
expect_exact()
The pattern can be a str or bytes.
If the pattern is a str, it will be converted to bytes before the function is run.
def test_expect_exact(dut):
dut.write('this would be redirected')
dut.expect_exact('this would')
dut.expect_exact(b'be redirected')
As with the expect() function, the pattern argument can be a list of any of the supported types.
def test_expect_exact_from_list(dut):
dut.write('this would be redirected')
pattern_list = [
'this would',
b'be redirected',
]
for _ in range(2):
dut.expect_exact(pattern_list)
expect_unity_test_output()
Unity Test is a C test framework.
This function parses the output as Unity test output. The default timeout is 60 seconds.
When the test script finishes, the DUT object will raise an AssertionError if any Unity test case has a “FAIL” result.
Additionally, it will dump a JUnit report to a temporary folder and merge it with the main report if you use the pytest --junitxml feature.
import inspect
import pytest
def test_expect_unity_test_output_basic(dut):
dut.write(inspect.cleandoc('''
foo.c:100:test_case:FAIL:Expected 2 was 1
foo.c:101:test_case_2:FAIL:Expected 1 was 2
-------------------
2 Tests 2 Failures 0 Ignored
FAIL
'''))
with pytest.raises(AssertionError):
dut.expect_unity_test_output()
assert len(dut.testsuite.testcases) == 2
assert dut.testsuite.attrs['failures'] == 2
assert dut.testsuite.testcases[0].attrs['message'] == 'Expected 2 was 1'
assert dut.testsuite.testcases[1].attrs['message'] == 'Expected 1 was 2'
It also supports Unity fixtures.
import inspect
import pytest
def test_expect_unity_test_output_fixture(dut):
dut.write(inspect.cleandoc('''
TEST(group, test_case)foo.c:100::FAIL:Expected 2 was 1
TEST(group, test_case_2)foo.c:101::FAIL:Expected 1 was 2
-------------------
2 Tests 2 Failures 0 Ignored
FAIL
'''))
with pytest.raises(AssertionError):
dut.expect_unity_test_output()
assert len(dut.testsuite.testcases) == 2
assert dut.testsuite.attrs['failures'] == 2
assert dut.testsuite.testcases[0].attrs['message'] == 'Expected 2 was 1'
assert dut.testsuite.testcases[1].attrs['message'] == 'Expected 1 was 2'