|
| 1 | +import os |
| 2 | +import sys |
| 3 | +import types |
| 4 | + |
| 5 | + |
| 6 | +def _ensure_module(name): |
| 7 | + mod = sys.modules.get(name) |
| 8 | + if mod is None: |
| 9 | + mod = types.ModuleType(name) |
| 10 | + sys.modules[name] = mod |
| 11 | + return mod |
| 12 | + |
| 13 | + |
| 14 | +def _ensure_submodule(package, name, attrs): |
| 15 | + full_name = f"{package}.{name}" |
| 16 | + module = _ensure_module(full_name) |
| 17 | + for attr, value in attrs.items(): |
| 18 | + if not hasattr(module, attr): |
| 19 | + setattr(module, attr, value) |
| 20 | + parent = _ensure_module(package) |
| 21 | + if not hasattr(parent, "__path__"): |
| 22 | + parent.__path__ = [] |
| 23 | + setattr(parent, name, module) |
| 24 | + return module |
| 25 | + |
| 26 | + |
| 27 | +def setup_native_stubs(): |
| 28 | + if sys.implementation.name == 'micropython': |
| 29 | + return |
| 30 | + |
| 31 | + if not hasattr(os, "ilistdir"): |
| 32 | + def _ilistdir(path): |
| 33 | + for entry in os.scandir(path): |
| 34 | + mode = 0x4000 if entry.is_dir() else 0x8000 |
| 35 | + yield (entry.name, mode, 0, 0) |
| 36 | + os.ilistdir = _ilistdir |
| 37 | + |
| 38 | + pyb = _ensure_module("pyb") |
| 39 | + if not hasattr(pyb, "SDCard"): |
| 40 | + class _DummySDCard: |
| 41 | + def __init__(self, *args, **kwargs): |
| 42 | + pass |
| 43 | + |
| 44 | + def present(self): |
| 45 | + return True |
| 46 | + |
| 47 | + def power(self, value): |
| 48 | + pass |
| 49 | + |
| 50 | + class _DummyLED: |
| 51 | + def __init__(self, *args, **kwargs): |
| 52 | + pass |
| 53 | + |
| 54 | + def on(self): |
| 55 | + pass |
| 56 | + |
| 57 | + def off(self): |
| 58 | + pass |
| 59 | + |
| 60 | + pyb.SDCard = _DummySDCard |
| 61 | + pyb.LED = _DummyLED |
| 62 | + pyb.usb_mode = lambda *args, **kwargs: None |
| 63 | + pyb.UART = lambda *args, **kwargs: None |
| 64 | + pyb.USB_VCP = lambda *args, **kwargs: None |
| 65 | + |
| 66 | + lvgl = _ensure_module("lvgl") |
| 67 | + if not hasattr(lvgl, "SYMBOL"): |
| 68 | + class _Symbol: |
| 69 | + EDIT = "[edit]" |
| 70 | + TRASH = "[trash]" |
| 71 | + |
| 72 | + def __getattr__(self, name): |
| 73 | + return f"[{name.lower()}]" |
| 74 | + |
| 75 | + lvgl.SYMBOL = _Symbol() |
| 76 | + |
| 77 | + display = _ensure_module("display") |
| 78 | + if not hasattr(display, "Screen"): |
| 79 | + display.Screen = type("Screen", (), {}) |
| 80 | + |
| 81 | + gui = _ensure_module("gui") |
| 82 | + if not hasattr(gui, "__path__"): |
| 83 | + gui.__path__ = [] |
| 84 | + |
| 85 | + screens = _ensure_module("gui.screens") |
| 86 | + if not hasattr(screens, "__path__"): |
| 87 | + screens.__path__ = [] |
| 88 | + for _name in [ |
| 89 | + "Menu", |
| 90 | + "InputScreen", |
| 91 | + "Prompt", |
| 92 | + "TransactionScreen", |
| 93 | + "WalletScreen", |
| 94 | + "ConfirmWalletScreen", |
| 95 | + "QRAlert", |
| 96 | + "Alert", |
| 97 | + "PinScreen", |
| 98 | + "DerivationScreen", |
| 99 | + "NumericScreen", |
| 100 | + "MnemonicScreen", |
| 101 | + "NewMnemonicScreen", |
| 102 | + "RecoverMnemonicScreen", |
| 103 | + "Progress", |
| 104 | + "DevSettings", |
| 105 | + ]: |
| 106 | + if not hasattr(screens, _name): |
| 107 | + setattr(screens, _name, type(_name, (), {})) |
| 108 | + |
| 109 | + _ensure_submodule("gui.screens", "mnemonic", { |
| 110 | + "ExportMnemonicScreen": type("ExportMnemonicScreen", (), {}), |
| 111 | + }) |
| 112 | + _ensure_submodule("gui.screens", "settings", { |
| 113 | + "HostSettings": type("HostSettings", (), {}), |
| 114 | + }) |
| 115 | + _ensure_submodule("gui.screens", "screen", { |
| 116 | + "Screen": type("Screen", (), {}), |
| 117 | + }) |
| 118 | + _ensure_submodule("gui.screens", "qralert", { |
| 119 | + "QRAlert": type("QRAlert", (), {}), |
| 120 | + }) |
| 121 | + |
| 122 | + common = _ensure_module("gui.common") |
| 123 | + if not hasattr(common, "HOR_RES"): |
| 124 | + common.HOR_RES = 480 |
| 125 | + if not hasattr(common, "styles"): |
| 126 | + common.styles = types.SimpleNamespace() |
| 127 | + for _name in [ |
| 128 | + "add_label", |
| 129 | + "add_button", |
| 130 | + "add_button_pair", |
| 131 | + "align_button_pair", |
| 132 | + "format_addr", |
| 133 | + ]: |
| 134 | + if not hasattr(common, _name): |
| 135 | + setattr(common, _name, lambda *args, **kwargs: None) |
| 136 | + |
| 137 | + decorators = _ensure_module("gui.decorators") |
| 138 | + if not hasattr(decorators, "on_release"): |
| 139 | + decorators.on_release = lambda func: func |
| 140 | + |
| 141 | + ucryptolib = _ensure_module("ucryptolib") |
| 142 | + if not hasattr(ucryptolib, "aes"): |
| 143 | + class _DummyAES: |
| 144 | + def __init__(self, key, mode, iv): |
| 145 | + self.key = key |
| 146 | + self.mode = mode |
| 147 | + self.iv = iv |
| 148 | + |
| 149 | + def encrypt(self, data): |
| 150 | + return data |
| 151 | + |
| 152 | + def decrypt(self, data): |
| 153 | + return data |
| 154 | + |
| 155 | + ucryptolib.aes = lambda key, mode, iv: _DummyAES(key, mode, iv) |
| 156 | + |
| 157 | + bcur = _ensure_module("bcur") |
| 158 | + if not hasattr(bcur, "bcur_decode_stream"): |
| 159 | + bcur.bcur_decode_stream = lambda stream: stream |
| 160 | + |
| 161 | + secp256k1 = _ensure_module("secp256k1") |
| 162 | + if not hasattr(secp256k1, "EC_UNCOMPRESSED"): |
| 163 | + secp256k1.EC_UNCOMPRESSED = 0 |
| 164 | + secp256k1.ec_pubkey_parse = lambda data: data |
| 165 | + secp256k1.ec_pubkey_create = lambda secret: secret |
| 166 | + secp256k1.ec_pubkey_serialize = lambda pub, flag=0: b"\x04" + bytes(64) |
| 167 | + secp256k1.ec_pubkey_tweak_mul = lambda pub, secret: None |
| 168 | + secp256k1.ecdsa_signature_parse_der = lambda raw: raw |
| 169 | + secp256k1.ecdsa_signature_normalize = lambda sig: sig |
| 170 | + secp256k1.ecdsa_verify = lambda sig, msg, pub: True |
| 171 | + secp256k1.ecdsa_sign_recoverable = lambda msghash, secret: bytes(65) |
| 172 | + |
| 173 | + from app import BaseApp |
| 174 | + |
| 175 | + if not hasattr(BaseApp, "_native_original_get_prefix"): |
| 176 | + BaseApp._native_original_get_prefix = BaseApp.get_prefix |
| 177 | + |
| 178 | + def _native_get_prefix(self, stream): |
| 179 | + pos = stream.tell() |
| 180 | + prefix = BaseApp._native_original_get_prefix(self, stream) |
| 181 | + if prefix is not None: |
| 182 | + prefixes = getattr(self, 'prefixes', None) |
| 183 | + if prefixes and prefix not in prefixes: |
| 184 | + stream.seek(pos) |
| 185 | + return None |
| 186 | + return prefix |
| 187 | + |
| 188 | + BaseApp.get_prefix = _native_get_prefix |
| 189 | + |
| 190 | + try: |
| 191 | + from apps.wallets.wallet import Wallet as _Wallet |
| 192 | + except ModuleNotFoundError as exc: |
| 193 | + if exc.name == "embit": |
| 194 | + raise ModuleNotFoundError( |
| 195 | + "Native test suite requires the 'embit' package. " |
| 196 | + "Install it with 'pip install -r test/integration/requirements.txt'." |
| 197 | + ) from exc |
| 198 | + raise |
| 199 | + |
| 200 | + if not hasattr(_Wallet, '_native_original_from_descriptor'): |
| 201 | + _Wallet._native_original_from_descriptor = _Wallet.from_descriptor |
| 202 | + |
| 203 | + def _native_from_descriptor(cls, desc: str, path): |
| 204 | + desc = desc.split('#')[0].replace(' ', '') |
| 205 | + descriptor = cls.DescriptorClass.from_string(desc) |
| 206 | + return cls(descriptor, path) |
| 207 | + |
| 208 | + _Wallet.from_descriptor = classmethod(_native_from_descriptor) |
0 commit comments