Skip to content

Commit b705ade

Browse files
committed
Exit the sim after the desired number of code.py runs
Make UART PTY reliable by waiting for all bytes to be read from the subsidiary PTY.
1 parent a4a3827 commit b705ade

10 files changed

Lines changed: 154 additions & 134 deletions

File tree

ports/zephyr-cp/supervisor/port.c

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@
1717
#include <zephyr/kernel.h>
1818
#include <zephyr/sys/reboot.h>
1919

20+
#if defined(CONFIG_ARCH_POSIX)
21+
#include <limits.h>
22+
#include <stdlib.h>
23+
24+
#include "cmdline.h"
25+
#include "posix_board_if.h"
26+
#include "posix_native_task.h"
27+
#endif
28+
2029
#include "lib/tlsf/tlsf.h"
2130
#include <zephyr/device.h>
2231

@@ -37,6 +46,32 @@ static K_EVENT_DEFINE(main_needed);
3746

3847
static struct k_timer tick_timer;
3948

49+
#if defined(CONFIG_ARCH_POSIX)
50+
// Number of VM runs before exiting.
51+
// <= 0 means run forever.
52+
// INT32_MAX means option was not provided.
53+
static int32_t native_sim_vm_runs = INT32_MAX;
54+
static uint32_t native_sim_reset_port_count = 0;
55+
56+
static struct args_struct_t native_sim_reset_port_args[] = {
57+
{
58+
.option = "vm-runs",
59+
.name = "count",
60+
.type = 'i',
61+
.dest = &native_sim_vm_runs,
62+
.descript = "Exit native_sim after this many VM runs. "
63+
"Example: --vm-runs=2"
64+
},
65+
ARG_TABLE_ENDMARKER
66+
};
67+
68+
static void native_sim_register_cmdline_opts(void) {
69+
native_add_command_line_opts(native_sim_reset_port_args);
70+
}
71+
72+
NATIVE_TASK(native_sim_register_cmdline_opts, PRE_BOOT_1, 0);
73+
#endif
74+
4075
static void _tick_function(struct k_timer *timer_id) {
4176
supervisor_tick();
4277
}
@@ -61,6 +96,16 @@ void reset_port(void) {
6196
#if CIRCUITPY_BLEIO
6297
bleio_reset();
6398
#endif
99+
100+
#if defined(CONFIG_ARCH_POSIX)
101+
native_sim_reset_port_count++;
102+
if (native_sim_vm_runs != INT32_MAX &&
103+
native_sim_vm_runs > 0 &&
104+
native_sim_reset_port_count >= (uint32_t)(native_sim_vm_runs + 1)) {
105+
printk("posix: exiting after %d VM runs\n", native_sim_vm_runs);
106+
posix_exit(0);
107+
}
108+
#endif
64109
}
65110

66111
void reset_to_bootloader(void) {

ports/zephyr-cp/tests/__init__.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import serial
22
import subprocess
33
import threading
4+
import time
45

56

67
class StdSerial:
@@ -25,6 +26,12 @@ def close(self):
2526
self.stdin.close()
2627
self.stdout.close()
2728

29+
@property
30+
def in_waiting(self):
31+
if self.stdout is None:
32+
return 0
33+
return len(self.stdout.peek())
34+
2835

2936
class SerialSaver:
3037
"""Capture serial output in a background thread so output isn't missed."""
@@ -58,6 +65,13 @@ def _reader_loop(self):
5865
with self._cv:
5966
self.all_output += text
6067
self._cv.notify_all()
68+
in_waiting = 0
69+
try:
70+
in_waiting = self.serial.in_waiting
71+
except OSError:
72+
pass
73+
if in_waiting > 0:
74+
self.all_output += self.serial.read().decode("utf-8", errors="replace")
6175

6276
def wait_for(self, text, timeout=10):
6377
with self._cv:
@@ -79,11 +93,11 @@ def close(self):
7993
return
8094

8195
self._stop.set()
96+
self._reader.join(timeout=1.0)
8297
try:
8398
self.serial.close()
8499
except Exception:
85100
pass
86-
self._reader.join(timeout=1.0)
87101
self.serial = None
88102

89103
def write(self, text):
@@ -131,6 +145,7 @@ def shutdown(self):
131145
self.debug_serial.close()
132146

133147
def wait_until_done(self):
134-
self._proc.wait(timeout=self._timeout)
135-
self.serial.close()
136-
self.debug_serial.close()
148+
start_time = time.monotonic()
149+
while self._proc.poll() is None and time.monotonic() - start_time < self._timeout:
150+
time.sleep(0.01)
151+
self.shutdown()

ports/zephyr-cp/tests/bsim/conftest.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def shutdown(self) -> None:
8686
@pytest.fixture
8787
def bsim_phy(request, bsim_phy_binary, native_sim_env, sim_id):
8888
duration_marker = request.node.get_closest_marker("duration")
89-
sim_length = float(duration_marker.args[0]) if duration_marker else 20.0
89+
duration = float(duration_marker.args[0]) if duration_marker else 20.0
9090

9191
devices = 1
9292
if "circuitpython2" in request.fixturenames or "zephyr_sample" in request.fixturenames:
@@ -97,12 +97,7 @@ def bsim_phy(request, bsim_phy_binary, native_sim_env, sim_id):
9797
sample_device_id = int(sample_marker.kwargs.get("device_id", 1))
9898
devices = max(devices, sample_device_id + 1)
9999

100-
bsim_marker = request.node.get_closest_marker("bsim")
101-
if bsim_marker is not None:
102-
devices = int(bsim_marker.kwargs.get("devices", devices))
103-
sim_length = float(bsim_marker.kwargs.get("sim_length", sim_length))
104-
105-
sim_length_us = int(sim_length * 1e6)
100+
sim_length_us = int(duration * 1e6)
106101
cmd = [
107102
"stdbuf",
108103
"-oL",
@@ -136,7 +131,7 @@ def bsim_phy(request, bsim_phy_binary, native_sim_env, sim_id):
136131
raise RuntimeError("bsim PHY exited immediately")
137132
# Assume bsim is running
138133

139-
phy = BsimPhyInstance(proc, phy_output, timeout=sim_length)
134+
phy = BsimPhyInstance(proc, phy_output, timeout=duration)
140135
yield phy
141136
phy.shutdown()
142137

@@ -195,7 +190,12 @@ def zephyr_sample(request, bsim_phy, native_sim_env, sim_id):
195190
if not binary.exists():
196191
pytest.skip(f"Zephyr sample binary not found: {binary}")
197192

198-
cmd = [str(binary), f"-s={sim_id}", f"-d={device_id}"]
193+
cmd = [
194+
str(binary),
195+
f"-s={sim_id}",
196+
f"-d={device_id}",
197+
"-disconnect_on_exit=1",
198+
]
199199
logger.info("Running: %s", " ".join(cmd))
200200
proc = subprocess.Popen(
201201
cmd,

ports/zephyr-cp/tests/bsim/test_bsim_basics.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,13 @@
1414

1515
@pytest.mark.circuitpy_drive({"code.py": BSIM_CODE})
1616
@pytest.mark.circuitpy_drive({"code.py": BSIM_CODE})
17+
@pytest.mark.duration(3)
1718
def test_bsim_dual_instance_connect(bsim_phy, circuitpython1, circuitpython2):
1819
"""Run two bsim instances on the same sim id and verify UART output."""
19-
print("in the test")
2020

21-
# Wait for both devices to produce their expected output before
22-
# tearing down the simulation.
23-
circuitpython1.serial.wait_for("bsim ready")
24-
circuitpython2.serial.wait_for("bsim ready")
25-
26-
bsim_phy.finish_sim()
21+
# Wait for both devices to complete before checking output.
22+
circuitpython1.wait_until_done()
23+
circuitpython2.wait_until_done()
2724

2825
output0 = circuitpython1.serial.all_output
2926
output1 = circuitpython2.serial.all_output

ports/zephyr-cp/tests/bsim/test_bsim_ble_advertising.py

Lines changed: 20 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
"""BLE advertising tests for nrf5340bsim."""
55

66
import logging
7-
import time
87

98
import pytest
109

@@ -39,8 +38,9 @@
3938
print("adv run start")
4039
adapter.start_advertising(advertisement, connectable=False)
4140
print("adv running")
42-
while True:
43-
time.sleep(0.2)
41+
time.sleep(10)
42+
adapter.stop_advertising()
43+
print("adv run done")
4444
"""
4545

4646

@@ -50,54 +50,34 @@ def test_bsim_advertise_and_scan(bsim_phy, circuitpython, zephyr_sample):
5050
"""Advertise from CircuitPython and verify Zephyr observer sees traffic."""
5151
observer = zephyr_sample
5252

53-
start_time = time.time()
54-
while time.time() - start_time < 12.0:
55-
observer_output = observer.serial.all_output
56-
adv_ready = "adv started" in circuitpython.serial.all_output
57-
if (
58-
"Device found:" in observer_output
59-
and "AD data len 10" in observer_output
60-
and adv_ready
61-
):
62-
break
63-
time.sleep(0.05)
53+
circuitpython.wait_until_done()
6454

55+
cp_output = circuitpython.serial.all_output
6556
observer_output = observer.serial.all_output
66-
assert "adv start" in circuitpython.serial.all_output
67-
assert "adv started" in circuitpython.serial.all_output
57+
assert "adv start" in cp_output
58+
assert "adv started" in cp_output
59+
assert "adv stop" in cp_output
6860
assert "Device found:" in observer_output
6961
assert "AD data len 10" in observer_output
7062

7163

72-
@pytest.mark.zephyr_sample("bluetooth/observer", timeout=20.0)
64+
@pytest.mark.zephyr_sample("bluetooth/observer")
65+
@pytest.mark.code_py_runs(2)
66+
@pytest.mark.duration(25)
7367
@pytest.mark.circuitpy_drive({"code.py": BSIM_ADV_INTERRUPT_RELOAD_CODE})
7468
def test_bsim_advertise_ctrl_c_reload(bsim_phy, circuitpython, zephyr_sample):
7569
"""Ensure advertising resumes after Ctrl-C and a reload."""
7670
observer = zephyr_sample
7771

78-
start_time = time.time()
79-
sent_ctrl_c = False
80-
sent_reload = False
81-
observer_count_before = 0
82-
83-
while time.time() - start_time < 22.0:
84-
cp_output = circuitpython.serial.all_output
85-
observer_output = observer.serial.all_output
86-
device_found_count = observer_output.count("Device found:")
87-
88-
if not sent_ctrl_c and "adv running" in cp_output and device_found_count > 0:
89-
circuitpython.serial.write("\x03")
90-
sent_ctrl_c = True
91-
observer_count_before = device_found_count
92-
93-
if sent_ctrl_c and not sent_reload and "KeyboardInterrupt" in cp_output:
94-
circuitpython.serial.write("\x04")
95-
sent_reload = True
72+
circuitpython.serial.wait_for("adv running")
73+
observer.serial.wait_for("Device found:")
74+
observer_count_before = observer.serial.all_output.count("Device found:")
9675

97-
if sent_reload and cp_output.count("adv running") >= 2:
98-
break
76+
circuitpython.serial.write("\x03")
77+
circuitpython.serial.wait_for("KeyboardInterrupt")
9978

100-
time.sleep(0.05)
79+
circuitpython.serial.write("\x04")
80+
circuitpython.wait_until_done()
10181

10282
cp_output = circuitpython.serial.all_output
10383
observer_output = observer.serial.all_output
@@ -107,5 +87,6 @@ def test_bsim_advertise_ctrl_c_reload(bsim_phy, circuitpython, zephyr_sample):
10787
assert "adv run start" in cp_output
10888
assert "KeyboardInterrupt" in cp_output
10989
assert cp_output.count("adv running") >= 2
110-
assert observer_output.count("Device found:") >= observer_count_before
90+
assert cp_output.count("adv run done") >= 1
91+
assert observer_output.count("Device found:") >= observer_count_before + 1
11192
assert "Already advertising" not in cp_output

ports/zephyr-cp/tests/bsim/test_bsim_ble_name.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33

44
"""BLE name tests for nrf5340bsim."""
55

6-
import time
7-
86
import pytest
97

108
pytestmark = pytest.mark.circuitpython_board("native_nrf5340bsim")
@@ -22,10 +20,6 @@
2220
@pytest.mark.circuitpy_drive({"code.py": BSIM_NAME_CODE})
2321
def test_bsim_set_name(bsim_phy, circuitpython):
2422
"""Set the BLE name and read it back on bsim."""
25-
start_time = time.time()
26-
while time.time() - start_time < 3.0:
27-
if "name CPNAME" in circuitpython.serial.all_output:
28-
break
29-
time.sleep(0.05)
23+
circuitpython.wait_until_done()
3024

3125
assert "name CPNAME" in circuitpython.serial.all_output

0 commit comments

Comments
 (0)