From 7ac6502e620f0c1774fc13289398efceb371b27d Mon Sep 17 00:00:00 2001 From: Thomas Farstrike Date: Tue, 17 Mar 2026 21:19:33 +0100 Subject: [PATCH] Add testing mocks --- CHANGELOG.md | 4 + tests/mocks.py | 350 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 354 insertions(+) create mode 100644 tests/mocks.py diff --git a/CHANGELOG.md b/CHANGELOG.md index fbb99a84..3229c38c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ 0.9.0 ===== +Builtin Apps: +- Hotspot Settings +- WebServer Settings + Frameworks: - AudioManager: add support for multiple speakers and microphones - AudioManager: add support for ADC-based microphone (adc_mic) diff --git a/tests/mocks.py b/tests/mocks.py new file mode 100644 index 00000000..1965d729 --- /dev/null +++ b/tests/mocks.py @@ -0,0 +1,350 @@ +class MockSharedPreferences: + """Mock SharedPreferences for testing.""" + + _all_data = {} # Class-level storage + + def __init__(self, app_id, filename=None): + self.app_id = app_id + self.filename = filename + if app_id not in MockSharedPreferences._all_data: + MockSharedPreferences._all_data[app_id] = {} + + def _get_value(self, key, default): + return MockSharedPreferences._all_data.get(self.app_id, {}).get(key, default) + + def get_dict(self, key): + return self._get_value(key, {}) + + def get_list(self, key, default=None): + return self._get_value(key, default) + + def get_bool(self, key, default=False): + value = self._get_value(key, default) + return bool(value) + + def get_string(self, key, default=""): + value = self._get_value(key, default) + return value if value is not None else default + + def get_int(self, key, default=0): + value = self._get_value(key, default) + try: + return int(value) + except (TypeError, ValueError): + return default + + def edit(self): + return MockEditor(self) + + @classmethod + def reset_all(cls): + cls._all_data = {} + + +class MockEditor: + """Mock editor for SharedPreferences.""" + + def __init__(self, prefs): + self.prefs = prefs + self.pending = {} + + def put_dict(self, key, value): + self.pending[key] = value + + def put_list(self, key, value): + self.pending[key] = value + + def put_bool(self, key, value): + self.pending[key] = bool(value) + + def put_string(self, key, value): + self.pending[key] = value + + def put_int(self, key, value): + self.pending[key] = int(value) + + def commit(self): + if self.prefs.app_id not in MockSharedPreferences._all_data: + MockSharedPreferences._all_data[self.prefs.app_id] = {} + MockSharedPreferences._all_data[self.prefs.app_id].update(self.pending) + + +class MockMpos: + """Mock mpos module with config and time.""" + + class config: + @staticmethod + def SharedPreferences(app_id): + return MockSharedPreferences(app_id) + + class time: + @staticmethod + def sync_time(): + pass # No-op for testing + + +class HotspotMockNetwork: + """Mock network module with AP/STA support for hotspot tests.""" + + STA_IF = 0 + AP_IF = 1 + + AUTH_OPEN = 0 + AUTH_WPA_PSK = 1 + AUTH_WPA2_PSK = 2 + AUTH_WPA_WPA2_PSK = 3 + + class MockWLAN: + def __init__(self, interface): + self.interface = interface + self._active = False + self._connected = False + self._config = {} + self._scan_results = [] + self._ifconfig = ("0.0.0.0", "0.0.0.0", "0.0.0.0", "0.0.0.0") + + def active(self, is_active=None): + if is_active is None: + return self._active + self._active = is_active + return None + + def isconnected(self): + return self._connected + + def connect(self, ssid, password): + self._connected = True + self._config["essid"] = ssid + + def disconnect(self): + self._connected = False + + def config(self, *args, **kwargs): + if kwargs: + self._config.update(kwargs) + return None + if args: + return self._config.get(args[0]) + return self._config + + def ifconfig(self, cfg=None): + if cfg is None: + return self._ifconfig + self._ifconfig = cfg + return None + + def ipconfig(self, key=None): + config = self.ifconfig() + mapping = { + "addr4": config[0], + "netmask4": config[1], + "gw4": config[2], + "dns4": config[3], + } + if key is None: + return mapping + return mapping.get(key) + + def scan(self): + return self._scan_results + + def __init__(self): + self._wlan_instances = {} + + def WLAN(self, interface): + if interface not in self._wlan_instances: + self._wlan_instances[interface] = self.MockWLAN(interface) + return self._wlan_instances[interface] + + +class MockADC: + """Mock ADC for testing.""" + + ATTN_11DB = 3 + + def __init__(self, pin): + self.pin = pin + self._atten = None + self._read_value = 2048 + + def atten(self, value): + self._atten = value + + def read(self): + return self._read_value + + def set_read_value(self, value): + """Test helper to set ADC reading.""" + self._read_value = value + + +class MockPin: + """Mock Pin for testing.""" + + def __init__(self, pin_num): + self.pin_num = pin_num + + +class MockMachineADC: + """Mock machine module with ADC/Pin.""" + + ADC = MockADC + Pin = MockPin + + +class MockWifiService: + """Mock WifiService for testing.""" + + wifi_busy = False + _connected = False + _temporarily_disabled = False + + @classmethod + def is_connected(cls): + return cls._connected + + @classmethod + def disconnect(cls): + cls._connected = False + + @classmethod + def temporarily_disable(cls): + """Temporarily disable WiFi and return whether it was connected.""" + if cls.wifi_busy: + raise RuntimeError("Cannot disable WiFi: WifiService is already busy") + was_connected = cls._connected + cls.wifi_busy = True + cls._connected = False + cls._temporarily_disabled = True + return was_connected + + @classmethod + def temporarily_enable(cls, was_connected): + """Re-enable WiFi and reconnect if it was connected before.""" + cls.wifi_busy = False + cls._temporarily_disabled = False + if was_connected: + cls._connected = True + + @classmethod + def reset(cls): + """Test helper to reset state.""" + cls.wifi_busy = False + cls._connected = False + cls._temporarily_disabled = False + + +class MockI2C: + """Mock I2C bus for testing.""" + + def __init__(self, bus_id, sda=None, scl=None): + self.bus_id = bus_id + self.sda = sda + self.scl = scl + self.memory = {} + + def readfrom_mem(self, addr, reg, nbytes): + """Read from memory (simulates I2C read).""" + if addr not in self.memory: + raise OSError("I2C device not found") + if reg not in self.memory[addr]: + return bytes([0] * nbytes) + return bytes(self.memory[addr][reg]) + + def writeto_mem(self, addr, reg, data): + """Write to memory (simulates I2C write).""" + if addr not in self.memory: + self.memory[addr] = {} + self.memory[addr][reg] = list(data) + + +class MockQMI8658: + """Mock QMI8658 IMU sensor.""" + + def __init__(self, i2c_bus, address=0x6B, accel_scale=0b10, gyro_scale=0b100): + self.i2c = i2c_bus + self.address = address + self.accel_scale = accel_scale + self.gyro_scale = gyro_scale + + @property + def temperature(self): + """Return mock temperature.""" + return 25.5 + + @property + def acceleration(self): + """Return mock acceleration (in G).""" + return (0.0, 0.0, 1.0) + + @property + def gyro(self): + """Return mock gyroscope (in deg/s).""" + return (0.0, 0.0, 0.0) + + +class MockWsenIsds: + """Mock WSEN_ISDS IMU sensor.""" + + def __init__(self, i2c, address=0x6B, acc_range="8g", acc_data_rate="104Hz", + gyro_range="500dps", gyro_data_rate="104Hz"): + self.i2c = i2c + self.address = address + self.acc_range = acc_range + self.gyro_range = gyro_range + self.acc_sensitivity = 0.244 + self.gyro_sensitivity = 17.5 + self.acc_offset_x = 0 + self.acc_offset_y = 0 + self.acc_offset_z = 0 + self.gyro_offset_x = 0 + self.gyro_offset_y = 0 + self.gyro_offset_z = 0 + + def get_chip_id(self): + """Return WHO_AM_I value.""" + return 0x6A + + def _read_raw_accelerations(self): + """Return mock acceleration (in mg).""" + return (0.0, 0.0, 1000.0) + + def read_angular_velocities(self): + """Return mock gyroscope (in mdps).""" + return (0.0, 0.0, 0.0) + + def acc_calibrate(self, samples=None): + """Mock calibration.""" + pass + + def gyro_calibrate(self, samples=None): + """Mock calibration.""" + pass + + +def make_machine_i2c_module(i2c_cls, pin_cls=None): + if pin_cls is None: + pin_cls = type("Pin", (), {}) + return type("module", (), {"I2C": i2c_cls, "Pin": pin_cls})() + + +def make_machine_timer_module(timer_cls): + return type("module", (), {"Timer": timer_cls})() + + +def make_usocket_module(socket_cls): + class MockUsocket: + """Mock usocket module.""" + + AF_INET = socket_cls.AF_INET + SOCK_STREAM = socket_cls.SOCK_STREAM + + @staticmethod + def socket(af, sock_type): + return socket_cls(af, sock_type) + + return MockUsocket + + +def make_config_module(shared_prefs_cls): + return type("module", (), {"SharedPreferences": shared_prefs_cls})()