Cai
2026-08-28 2c699fc08247494faf054f9c1f5b90a47a105ec1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
from __future__ import annotations
 
from pathlib import Path
import unittest
 
from hibor_fast_collection.adb import AdbClient, UiNode
from hibor_fast_collection.models import ContractError, ErrorCode
from hibor_fast_collection.process import ProcessResult
from hibor_fast_collection.ui import HiborUiDriver, UiConfig
 
 
def result(exit_code: int = 0, *, stdout: bytes = b"", stderr: bytes = b"") -> ProcessResult:
    return ProcessResult(
        (), True, 1, exit_code, False, False, True, False,
        stdout, stderr, False, False, None, None, 1, None,
    )
 
 
class ScriptedSupervisor:
    def __init__(self, responses: list[ProcessResult]):
        self.responses = list(responses)
        self.calls: list[tuple[str, ...]] = []
 
    def run(self, argv, *, timeout_ms, **kwargs):
        self.calls.append(tuple(argv))
        if not self.responses:
            raise AssertionError(f"unexpected command: {argv!r}")
        return self.responses.pop(0)
 
 
def client(responses: list[ProcessResult]) -> tuple[AdbClient, ScriptedSupervisor]:
    supervisor = ScriptedSupervisor(responses)
    return AdbClient(Path("adb.exe"), supervisor, serial="emulator-test"), supervisor
 
 
class AdbInputCompatibilityTests(unittest.TestCase):
    def test_safe_ascii_uses_generic_input_without_clipboard_or_paste(self):
        adb, supervisor = client([result(), result(), result()])
 
        adb.input_text("603179")
 
        commands = [call[3:] for call in supervisor.calls]
        self.assertEqual(commands[-1], ("shell", "input", "text", "603179"))
        self.assertFalse(any("memu_clip_board_base64" in call for call in supervisor.calls))
        self.assertFalse(any(call[-2:] == ("keyevent", "279") for call in supervisor.calls))
 
    def test_memu_service_failure_never_pastes(self):
        adb, supervisor = client([
            result(stdout=b"com.microvirt.memuime/.MemuIME\n"),
            result(), result(), result(exit_code=1, stderr=b"service unavailable"),
        ])
 
        with self.assertRaisesRegex(ContractError, "Memu clipboard failed"):
            adb.input_text("\u56fd\u74f7\u6750\u6599")
 
        self.assertTrue(any("memu_clip_board_base64" in call for call in supervisor.calls))
        self.assertFalse(any(call[-2:] == ("keyevent", "279") for call in supervisor.calls))
 
    def test_monkey_missing_falls_back_to_exported_welcome_activity(self):
        adb, supervisor = client([
            result(exit_code=127, stderr=b"/system/bin/sh: monkey: not found"),
            result(stdout=b"Starting: Intent { cmp=cn.com.hibor/.WelcomeActivity }\nStatus: ok\n"),
        ])
 
        adb.start_package()
 
        self.assertEqual(
            supervisor.calls[-1][3:],
            ("shell", "am", "start", "-W", "-n", "cn.com.hibor/.WelcomeActivity"),
        )
 
    def test_unsupported_non_ascii_or_sensitive_input_fails_before_edit_or_paste(self):
        for value in ("\u4e09\u73af\u96c6\u56e2", "603179;paste"):
            with self.subTest(value=value):
                adb, supervisor = client([result(stdout=b"com.android.inputmethod.pinyin/.InputService\n")])
                with self.assertRaisesRegex(ContractError, "MemuIME unsupported") as raised:
                    adb.input_text(value)
                self.assertEqual(raised.exception.code, ErrorCode.UI_ANCHOR_DRIFT)
                self.assertEqual(len(supervisor.calls), 1)
                self.assertEqual(supervisor.calls[0][3:], ("shell", "ime", "list", "-s"))
 
    def test_ui_requires_exact_character_for_character_readback(self):
        field = UiNode("", "search-input", "android.widget.EditText", (0, 0, 10, 10), True)
        wrong = UiNode("603178", "search-input", "android.widget.EditText", (0, 0, 10, 10), True)
 
        class UiAdb:
            def __init__(self):
                self.snapshots = [(field,), (wrong,)]
 
            def ui_nodes(self):
                return self.snapshots.pop(0)
 
            def tap(self, *args):
                pass
 
            def input_text(self, value):
                self.value = value
 
        config = UiConfig((), "search-input", ("\u641c\u7d22",), "", (), "", "", "", "", (0, 0, 0, 0, 1))
        driver = HiborUiDriver(UiAdb(), config)
 
        with self.assertRaisesRegex(ContractError, "text mismatch"):
            driver.search("603179")
 
 
if __name__ == "__main__":
    unittest.main()