Skip to content

Commit a62f081

Browse files
committed
fixed test_mpy_integration.py - problem with connecting to wifi
1 parent ba93f3a commit a62f081

2 files changed

Lines changed: 75 additions & 62 deletions

File tree

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -538,8 +538,13 @@ Requires [mpytool](https://github.com/cortexm/mpytool) and `uhttp-client`.
538538

539539
```bash
540540
# Install dependencies
541-
../.venv/bin/pip install -e ../mpytool uhttp-client
541+
../.venv/bin/pip install uhttp-client mpytool
542+
```
543+
544+
uhttp-client - for other integration tests
545+
mpytool - for mpy_integration tests
542546

547+
```bash
543548
# Run tests
544549
MPY_TEST_PORT=/dev/ttyUSB0 ../.venv/bin/python -m unittest tests.test_mpy_integration -v
545550
```

tests/test_mpy_integration.py

Lines changed: 69 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
import json
2626
import os
27+
import socket
2728
import time
2829
import unittest
2930
from pathlib import Path
@@ -91,73 +92,58 @@ def requires_device(cls):
9192
return cls
9293

9394

94-
# Minimum free RAM for server tests (server.py is ~44KB)
95-
MIN_RAM_FOR_SERVER = 200000
96-
97-
9895
class MpyServerTestCase(unittest.TestCase):
9996
"""Base class for MicroPython server tests"""
10097

10198
mpy = None
10299
conn = None
103100
esp32_ip = None
104101
server_running = False
105-
_server_uploaded = False
102+
_mount_handler = None
106103

107104
@classmethod
108105
def setUpClass(cls):
109106
import mpytool
107+
from mpytool.mpy_cross import MpyCross
110108

111109
cls.conn = mpytool.ConnSerial(port=PORT, baudrate=115200)
112110
cls.mpy = mpytool.Mpy(cls.conn)
113-
cls.mpy.stop()
114111

115-
# Check RAM before uploading large server module
116-
free_mem = cls.get_free_memory()
117-
if free_mem < MIN_RAM_FOR_SERVER:
118-
raise unittest.SkipTest(
119-
f"Not enough RAM for server: {free_mem} < {MIN_RAM_FOR_SERVER}")
112+
# Soft reset to clear any previous state
113+
cls.mpy.stop()
114+
try:
115+
cls.conn.write(b'\x03\x03\x04') # Ctrl-C twice + Ctrl-D
116+
time.sleep(2)
117+
cls.conn.read_all()
118+
except Exception:
119+
pass
120+
cls.mpy.stop()
120121

121-
# Upload server module once
122-
if not cls._server_uploaded:
123-
cls._upload_server()
124-
cls._server_uploaded = True
122+
# Mount server module with mpy-cross compilation
123+
server_dir = Path(__file__).parent.parent / 'uhttp'
124+
mpy_cross = MpyCross()
125+
mpy_cross.init(cls.mpy.platform())
126+
cls._mount_handler = cls.mpy.mount(
127+
str(server_dir), mount_point='/lib/uhttp', mpy_cross=mpy_cross)
125128

126129
# Connect WiFi and get IP
127130
cls.esp32_ip = cls._connect_wifi()
128131

129-
# Start server on ESP32
132+
# Start server and wait for it to be ready
130133
cls._start_server()
131-
132-
@classmethod
133-
def get_free_memory(cls):
134-
"""Get free memory on device"""
135-
result = cls.mpy.comm.exec_raw_paste(
136-
"import gc; gc.collect(); print(gc.mem_free())", timeout=5)
137-
return int(result.strip())
134+
cls._wait_for_server()
138135

139136
@classmethod
140137
def tearDownClass(cls):
141-
cls._stop_server()
138+
if cls.server_running:
139+
try:
140+
cls.mpy.stop()
141+
except Exception:
142+
pass
143+
cls.server_running = False
142144
if cls.conn:
143145
cls.conn.close()
144146

145-
@classmethod
146-
def _upload_server(cls):
147-
"""Upload server.py to ESP32"""
148-
server_file = Path(__file__).parent.parent / 'uhttp' / 'server.py'
149-
# Create /lib/uhttp directory
150-
try:
151-
cls.mpy.mkdir('/lib')
152-
except Exception:
153-
pass
154-
try:
155-
cls.mpy.mkdir('/lib/uhttp')
156-
except Exception:
157-
pass
158-
# Upload server.py
159-
cls.mpy.put(server_file.read_bytes(), '/lib/uhttp/server.py')
160-
161147
@classmethod
162148
def _connect_wifi(cls):
163149
"""Connect ESP32 to WiFi and return IP address"""
@@ -192,20 +178,30 @@ def _connect_wifi(cls):
192178

193179
@classmethod
194180
def _start_server(cls):
195-
"""Start HTTP server on ESP32 (fire-and-forget)"""
181+
"""Start HTTP server on ESP32"""
182+
# Verify import works (also ensures mount is ready)
183+
test_result = cls.mpy.comm.exec(
184+
"import sys; sys.path.insert(0, '/lib'); "
185+
"from uhttp.server import HttpServer; print('OK')",
186+
timeout=10
187+
).decode('utf-8')
188+
if 'OK' not in test_result:
189+
raise RuntimeError(f"Failed to import uhttp.server: {test_result}")
190+
191+
# Start server (fire-and-forget)
196192
code = f"""
197193
import sys
198194
sys.path.insert(0, '/lib')
199-
200195
from uhttp.server import HttpServer
201196
202197
server = HttpServer(port={ESP32_SERVER_PORT})
203-
print('SERVER_STARTED')
204198
205199
while True:
206200
client = server.wait(timeout=1)
207201
if client:
208-
if client.path == '/json':
202+
if client.path == '/health':
203+
client.respond({{'status': 'ok'}})
204+
elif client.path == '/json':
209205
client.respond({{'method': client.method, 'path': client.path}})
210206
elif client.path == '/echo':
211207
client.respond({{'data': client.data, 'method': client.method}})
@@ -218,37 +214,49 @@ def _start_server(cls):
218214
else:
219215
client.respond({{'path': client.path, 'method': client.method}})
220216
"""
221-
# Start server with timeout=0 (fire-and-forget)
222217
cls.mpy.comm.exec(code, timeout=0)
223218
cls.server_running = True
224-
time.sleep(1) # Give server time to start
219+
time.sleep(0.5)
225220

226221
@classmethod
227-
def _stop_server(cls):
228-
"""Stop HTTP server on ESP32"""
229-
if cls.server_running:
222+
def _wait_for_server(cls, max_attempts=10):
223+
"""Wait for server to respond to health check"""
224+
for _ in range(max_attempts):
230225
try:
231-
cls.mpy.stop()
232-
time.sleep(0.5)
233-
except Exception:
226+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
227+
sock.settimeout(2)
228+
sock.connect((cls.esp32_ip, ESP32_SERVER_PORT))
229+
sock.sendall(b'GET /health HTTP/1.0\r\nHost: test\r\n\r\n')
230+
if b'200' in sock.recv(1024):
231+
sock.close()
232+
return
233+
sock.close()
234+
except (OSError, socket.timeout):
234235
pass
235-
cls.server_running = False
236+
time.sleep(1)
237+
raise RuntimeError(
238+
f"Server not ready on {cls.esp32_ip}:{ESP32_SERVER_PORT}")
236239

237240

238241
@requires_device
239242
class TestHTTPServer(MpyServerTestCase):
240243
"""Test HTTP server basic functionality"""
241244

242-
def _request(self, method, path, **kwargs):
245+
def _request(self, method, path, retries=2, **kwargs):
243246
"""Make HTTP request to ESP32 server"""
244-
from uhttp.client import HttpClient
247+
from uhttp.client import HttpClient, HttpConnectionError, HttpTimeoutError
245248
url = f"http://{self.esp32_ip}:{ESP32_SERVER_PORT}"
246-
client = HttpClient(url)
247-
try:
248-
response = getattr(client, method.lower())(path, **kwargs).wait()
249-
return response
250-
finally:
251-
client.close()
249+
last_error = None
250+
for _ in range(retries):
251+
client = HttpClient(url, timeout=10)
252+
try:
253+
return getattr(client, method.lower())(path, **kwargs).wait()
254+
except (HttpConnectionError, HttpTimeoutError) as e:
255+
last_error = e
256+
time.sleep(0.5)
257+
finally:
258+
client.close()
259+
raise last_error
252260

253261
def test_get_request(self):
254262
"""Test basic GET request"""

0 commit comments

Comments
 (0)