Skip to content

Commit e94c8ab

Browse files
More tests
1 parent 331cf14 commit e94c8ab

File tree

2 files changed

+392
-0
lines changed

2 files changed

+392
-0
lines changed
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
"""Test for calibration check bug after calibrating.
2+
3+
Reproduces issue where check_calibration_quality() returns None after calibration.
4+
"""
5+
import unittest
6+
import sys
7+
8+
# Mock hardware before importing SensorManager
9+
class MockI2C:
10+
def __init__(self, bus_id, sda=None, scl=None):
11+
self.bus_id = bus_id
12+
self.sda = sda
13+
self.scl = scl
14+
self.memory = {}
15+
16+
def readfrom_mem(self, addr, reg, nbytes):
17+
if addr not in self.memory:
18+
raise OSError("I2C device not found")
19+
if reg not in self.memory[addr]:
20+
return bytes([0] * nbytes)
21+
return bytes(self.memory[addr][reg])
22+
23+
def writeto_mem(self, addr, reg, data):
24+
if addr not in self.memory:
25+
self.memory[addr] = {}
26+
self.memory[addr][reg] = list(data)
27+
28+
29+
class MockQMI8658:
30+
def __init__(self, i2c_bus, address=0x6B, accel_scale=0b10, gyro_scale=0b100):
31+
self.i2c = i2c_bus
32+
self.address = address
33+
self.accel_scale = accel_scale
34+
self.gyro_scale = gyro_scale
35+
36+
@property
37+
def temperature(self):
38+
return 25.5
39+
40+
@property
41+
def acceleration(self):
42+
return (0.0, 0.0, 1.0) # At rest, Z-axis = 1G
43+
44+
@property
45+
def gyro(self):
46+
return (0.0, 0.0, 0.0) # Stationary
47+
48+
49+
# Mock constants
50+
_QMI8685_PARTID = 0x05
51+
_REG_PARTID = 0x00
52+
_ACCELSCALE_RANGE_8G = 0b10
53+
_GYROSCALE_RANGE_256DPS = 0b100
54+
55+
# Create mock modules
56+
mock_machine = type('module', (), {
57+
'I2C': MockI2C,
58+
'Pin': type('Pin', (), {})
59+
})()
60+
61+
mock_qmi8658 = type('module', (), {
62+
'QMI8658': MockQMI8658,
63+
'_QMI8685_PARTID': _QMI8685_PARTID,
64+
'_REG_PARTID': _REG_PARTID,
65+
'_ACCELSCALE_RANGE_8G': _ACCELSCALE_RANGE_8G,
66+
'_GYROSCALE_RANGE_256DPS': _GYROSCALE_RANGE_256DPS
67+
})()
68+
69+
def _mock_mcu_temperature(*args, **kwargs):
70+
return 42.0
71+
72+
mock_esp32 = type('module', (), {
73+
'mcu_temperature': _mock_mcu_temperature
74+
})()
75+
76+
# Inject mocks
77+
sys.modules['machine'] = mock_machine
78+
sys.modules['mpos.hardware.drivers.qmi8658'] = mock_qmi8658
79+
sys.modules['esp32'] = mock_esp32
80+
81+
try:
82+
import _thread
83+
except ImportError:
84+
mock_thread = type('module', (), {
85+
'allocate_lock': lambda: type('lock', (), {
86+
'acquire': lambda self: None,
87+
'release': lambda self: None
88+
})()
89+
})()
90+
sys.modules['_thread'] = mock_thread
91+
92+
# Now import the module to test
93+
import mpos.sensor_manager as SensorManager
94+
95+
96+
class TestCalibrationCheckBug(unittest.TestCase):
97+
"""Test case for calibration check bug."""
98+
99+
def setUp(self):
100+
"""Set up test fixtures before each test."""
101+
# Reset SensorManager state
102+
SensorManager._initialized = False
103+
SensorManager._imu_driver = None
104+
SensorManager._sensor_list = []
105+
SensorManager._has_mcu_temperature = False
106+
107+
# Create mock I2C bus with QMI8658
108+
self.i2c_bus = MockI2C(0, sda=48, scl=47)
109+
self.i2c_bus.memory[0x6B] = {_REG_PARTID: [_QMI8685_PARTID]}
110+
111+
def test_check_quality_after_calibration(self):
112+
"""Test that check_calibration_quality() works after calibration.
113+
114+
This reproduces the bug where check_calibration_quality() returns
115+
None or shows "--" after performing calibration.
116+
"""
117+
# Initialize
118+
SensorManager.init(self.i2c_bus, address=0x6B)
119+
120+
# Step 1: Check calibration quality BEFORE calibration (should work)
121+
print("\n=== Step 1: Check quality BEFORE calibration ===")
122+
quality_before = SensorManager.check_calibration_quality(samples=10)
123+
self.assertIsNotNone(quality_before, "Quality check BEFORE calibration should return data")
124+
self.assertIn('quality_score', quality_before)
125+
print(f"Quality before: {quality_before['quality_rating']} ({quality_before['quality_score']:.2f})")
126+
127+
# Step 2: Calibrate sensors
128+
print("\n=== Step 2: Calibrate sensors ===")
129+
accel = SensorManager.get_default_sensor(SensorManager.TYPE_ACCELEROMETER)
130+
gyro = SensorManager.get_default_sensor(SensorManager.TYPE_GYROSCOPE)
131+
132+
self.assertIsNotNone(accel, "Accelerometer should be available")
133+
self.assertIsNotNone(gyro, "Gyroscope should be available")
134+
135+
accel_offsets = SensorManager.calibrate_sensor(accel, samples=10)
136+
print(f"Accel offsets: {accel_offsets}")
137+
self.assertIsNotNone(accel_offsets, "Accelerometer calibration should succeed")
138+
139+
gyro_offsets = SensorManager.calibrate_sensor(gyro, samples=10)
140+
print(f"Gyro offsets: {gyro_offsets}")
141+
self.assertIsNotNone(gyro_offsets, "Gyroscope calibration should succeed")
142+
143+
# Step 3: Check calibration quality AFTER calibration (BUG: returns None)
144+
print("\n=== Step 3: Check quality AFTER calibration ===")
145+
quality_after = SensorManager.check_calibration_quality(samples=10)
146+
self.assertIsNotNone(quality_after, "Quality check AFTER calibration should return data (BUG: returns None)")
147+
self.assertIn('quality_score', quality_after)
148+
print(f"Quality after: {quality_after['quality_rating']} ({quality_after['quality_score']:.2f})")
149+
150+
# Verify sensor reads still work
151+
print("\n=== Step 4: Verify sensor reads still work ===")
152+
accel_data = SensorManager.read_sensor(accel)
153+
self.assertIsNotNone(accel_data, "Accelerometer should still be readable")
154+
print(f"Accel data: {accel_data}")
155+
156+
gyro_data = SensorManager.read_sensor(gyro)
157+
self.assertIsNotNone(gyro_data, "Gyroscope should still be readable")
158+
print(f"Gyro data: {gyro_data}")
159+
160+
161+
if __name__ == '__main__':
162+
unittest.main()
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
#!/usr/bin/env python3
2+
"""Automated UI test for IMU calibration bug.
3+
4+
Tests the complete flow:
5+
1. Open Settings → IMU → Check Calibration
6+
2. Verify values are shown
7+
3. Click "Calibrate" → Calibrate IMU
8+
4. Click "Calibrate Now"
9+
5. Go back to Check Calibration
10+
6. BUG: Verify values are shown (not "--")
11+
"""
12+
13+
import sys
14+
import time
15+
16+
# Import graphical test infrastructure
17+
import lvgl as lv
18+
from mpos.ui.testing import (
19+
wait_for_render,
20+
simulate_click,
21+
find_button_with_text,
22+
find_label_with_text,
23+
get_widget_coords,
24+
print_screen_labels,
25+
capture_screenshot
26+
)
27+
28+
def click_button(button_text, timeout=5):
29+
"""Find and click a button with given text."""
30+
start = time.time()
31+
while time.time() - start < timeout:
32+
button = find_button_with_text(lv.screen_active(), button_text)
33+
if button:
34+
coords = get_widget_coords(button)
35+
if coords:
36+
print(f"Clicking button '{button_text}' at ({coords['center_x']}, {coords['center_y']})")
37+
simulate_click(coords['center_x'], coords['center_y'])
38+
wait_for_render(iterations=20)
39+
return True
40+
wait_for_render(iterations=5)
41+
print(f"ERROR: Button '{button_text}' not found after {timeout}s")
42+
return False
43+
44+
def click_label(label_text, timeout=5):
45+
"""Find a label with given text and click on it (or its clickable parent)."""
46+
start = time.time()
47+
while time.time() - start < timeout:
48+
label = find_label_with_text(lv.screen_active(), label_text)
49+
if label:
50+
coords = get_widget_coords(label)
51+
if coords:
52+
print(f"Clicking label '{label_text}' at ({coords['center_x']}, {coords['center_y']})")
53+
simulate_click(coords['center_x'], coords['center_y'])
54+
wait_for_render(iterations=20)
55+
return True
56+
wait_for_render(iterations=5)
57+
print(f"ERROR: Label '{label_text}' not found after {timeout}s")
58+
return False
59+
60+
def find_text_on_screen(text):
61+
"""Check if text is present on screen."""
62+
return find_label_with_text(lv.screen_active(), text) is not None
63+
64+
def main():
65+
print("=== IMU Calibration UI Bug Test ===\n")
66+
67+
# Initialize the OS (boot.py and main.py)
68+
print("Step 1: Initializing MicroPythonOS...")
69+
import mpos.main
70+
wait_for_render(iterations=30)
71+
print("OS initialized\n")
72+
73+
# Step 2: Open Settings app
74+
print("Step 2: Opening Settings app...")
75+
import mpos.apps
76+
77+
# Start Settings app by name
78+
mpos.apps.start_app("com.micropythonos.settings")
79+
wait_for_render(iterations=30)
80+
print("Settings app opened\n")
81+
82+
print("Current screen content:")
83+
print_screen_labels(lv.screen_active())
84+
print()
85+
86+
# Check if we're on the main Settings screen (should see multiple settings options)
87+
# The Settings app shows a list with items like "Calibrate IMU", "Check IMU Calibration", "Theme Color", etc.
88+
on_settings_main = (find_text_on_screen("Calibrate IMU") and
89+
find_text_on_screen("Check IMU Calibration") and
90+
find_text_on_screen("Theme Color"))
91+
92+
# If we're on a sub-screen (like Calibrate IMU or Check IMU Calibration screens),
93+
# we need to go back to Settings main. We can detect this by looking for screen titles.
94+
if not on_settings_main:
95+
print("Step 3: Not on Settings main screen, clicking Back to return...")
96+
if not click_button("Back"):
97+
print("WARNING: Could not find Back button, trying Cancel...")
98+
if not click_button("Cancel"):
99+
print("FAILED: Could not navigate back to Settings main")
100+
return False
101+
wait_for_render(iterations=20)
102+
print("Current screen content:")
103+
print_screen_labels(lv.screen_active())
104+
print()
105+
106+
# Step 4: Click "Check IMU Calibration" (it's a clickable label/container, not a button)
107+
print("Step 4: Clicking 'Check IMU Calibration' menu item...")
108+
if not click_label("Check IMU Calibration"):
109+
print("FAILED: Could not find Check IMU Calibration menu item")
110+
return False
111+
print("Check IMU Calibration opened\n")
112+
113+
# Wait for quality check to complete
114+
time.sleep(0.5)
115+
wait_for_render(iterations=30)
116+
117+
print("Step 5: Checking BEFORE calibration...")
118+
print("Current screen content:")
119+
print_screen_labels(lv.screen_active())
120+
print()
121+
122+
# Capture screenshot before
123+
capture_screenshot("../tests/screenshots/check_imu_before_calib.raw")
124+
125+
# Look for actual values (not "--")
126+
has_values_before = False
127+
widgets = []
128+
from mpos.ui.testing import get_all_widgets_with_text
129+
for widget in get_all_widgets_with_text(lv.screen_active()):
130+
text = widget.get_text()
131+
# Look for patterns like "X: 0.00" or "Quality: Good"
132+
if ":" in text and "--" not in text:
133+
if any(char.isdigit() for char in text):
134+
print(f"Found value: {text}")
135+
has_values_before = True
136+
137+
if not has_values_before:
138+
print("WARNING: No values found before calibration (all showing '--')")
139+
else:
140+
print("GOOD: Values are showing before calibration")
141+
print()
142+
143+
# Step 6: Click "Calibrate" button to go to calibration screen
144+
print("Step 6: Finding 'Calibrate' button...")
145+
calibrate_btn = find_button_with_text(lv.screen_active(), "Calibrate")
146+
if not calibrate_btn:
147+
print("FAILED: Could not find Calibrate button")
148+
return False
149+
150+
print(f"Found Calibrate button: {calibrate_btn}")
151+
print("Manually sending CLICKED event to button...")
152+
# Instead of using simulate_click, manually send the event
153+
calibrate_btn.send_event(lv.EVENT.CLICKED, None)
154+
wait_for_render(iterations=20)
155+
156+
# Wait for navigation to complete (activity transition can take some time)
157+
time.sleep(0.5)
158+
wait_for_render(iterations=50)
159+
print("Calibrate IMU screen should be open now\n")
160+
161+
print("Current screen content:")
162+
print_screen_labels(lv.screen_active())
163+
print()
164+
165+
# Step 7: Click "Calibrate Now" button
166+
print("Step 7: Clicking 'Calibrate Now' button...")
167+
if not click_button("Calibrate Now"):
168+
print("FAILED: Could not find 'Calibrate Now' button")
169+
return False
170+
print("Calibration started...\n")
171+
172+
# Wait for calibration to complete (~2 seconds + UI updates)
173+
time.sleep(3)
174+
wait_for_render(iterations=50)
175+
176+
print("Current screen content after calibration:")
177+
print_screen_labels(lv.screen_active())
178+
print()
179+
180+
# Step 8: Click "Done" to go back
181+
print("Step 8: Clicking 'Done' button...")
182+
if not click_button("Done"):
183+
print("FAILED: Could not find Done button")
184+
return False
185+
print("Going back to Check Calibration\n")
186+
187+
# Wait for screen to load
188+
time.sleep(0.5)
189+
wait_for_render(iterations=30)
190+
191+
# Step 9: Check AFTER calibration (BUG: should show values, not "--")
192+
print("Step 9: Checking AFTER calibration (testing for bug)...")
193+
print("Current screen content:")
194+
print_screen_labels(lv.screen_active())
195+
print()
196+
197+
# Capture screenshot after
198+
capture_screenshot("../tests/screenshots/check_imu_after_calib.raw")
199+
200+
# Look for actual values (not "--")
201+
has_values_after = False
202+
for widget in get_all_widgets_with_text(lv.screen_active()):
203+
text = widget.get_text()
204+
# Look for patterns like "X: 0.00" or "Quality: Good"
205+
if ":" in text and "--" not in text:
206+
if any(char.isdigit() for char in text):
207+
print(f"Found value: {text}")
208+
has_values_after = True
209+
210+
print()
211+
print("="*60)
212+
print("TEST RESULTS:")
213+
print(f" Values shown BEFORE calibration: {has_values_before}")
214+
print(f" Values shown AFTER calibration: {has_values_after}")
215+
216+
if has_values_before and not has_values_after:
217+
print("\n ❌ BUG REPRODUCED: Values disappeared after calibration!")
218+
print(" Expected: Values should still be shown")
219+
print(" Actual: All showing '--'")
220+
return False
221+
elif has_values_after:
222+
print("\n ✅ PASS: Values are showing correctly after calibration")
223+
return True
224+
else:
225+
print("\n ⚠️ WARNING: No values shown before or after (might be desktop mock issue)")
226+
return True
227+
228+
if __name__ == '__main__':
229+
success = main()
230+
sys.exit(0 if success else 1)

0 commit comments

Comments
 (0)