feat(rtos): 首次发布手机真机调试桥与S2探针
这个提交包含在:
当前提交
2f0f4b9702
59 个文件已更改
+4303
没有与搜索条件相匹配的文件
@@ -0,0 +1,117 @@
|
||||
"""Exercise the PC client against a real local HTTP socket, without Android."""
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import threading
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
|
||||
spec = importlib.util.spec_from_file_location('bridge', Path(__file__).resolve().parents[1] / 'bridge.py')
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
def log_message(self, *_): pass
|
||||
|
||||
def handle_request(self):
|
||||
length = int(self.headers.get('Content-Length', 0))
|
||||
data = self.rfile.read(length)
|
||||
self.server.requests.append((self.command, self.path, dict(self.headers), data))
|
||||
if self.path == '/reject':
|
||||
status, result = 401, {'error': 'Unauthorized'}
|
||||
elif self.command == 'PUT':
|
||||
status, result = 200, {'file': self.path.split('/')[-1], 'sha256': self.headers['X-SHA256']}
|
||||
elif self.path == '/command':
|
||||
status, result = 202, {'id': 'test-job'}
|
||||
elif self.path == '/jobs/test-job':
|
||||
status, result = 200, self.server.job_result
|
||||
else:
|
||||
status, result = 200, {'state': 'bound'}
|
||||
body = json.dumps(result).encode()
|
||||
self.send_response(status)
|
||||
self.send_header('Content-Length', str(len(body)))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
|
||||
do_GET = do_POST = do_PUT = handle_request
|
||||
|
||||
|
||||
class ClientTests(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.server = ThreadingHTTPServer(('127.0.0.1', 0), Handler)
|
||||
cls.server.requests = []
|
||||
threading.Thread(target=cls.server.serve_forever, daemon=True).start()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.server.shutdown()
|
||||
cls.server.server_close()
|
||||
|
||||
def setUp(self):
|
||||
self.client = module.Bridge(port=self.server.server_port)
|
||||
self.client.token = 'test-token'
|
||||
self.server.requests.clear()
|
||||
|
||||
def test_unicode_message_survives_http(self):
|
||||
self.server.job_result = {'state': 'success', 'result': {'code': 200}}
|
||||
self.client.command({'command': 'message', 'message': 'S2 真机 🙂'})
|
||||
method, path, headers, body = self.server.requests[0]
|
||||
self.assertEqual(json.loads(body)['message'], 'S2 真机 🙂')
|
||||
self.assertEqual(headers['Authorization'], 'Bearer test-token')
|
||||
self.assertEqual((method, path), ('POST', '/command'))
|
||||
|
||||
def test_watch_failure_is_preserved(self):
|
||||
self.server.job_result = {'state': 'failed', 'result': {'code': 5005, 'msg': 'invalid package'}}
|
||||
result = self.client.command({'command': 'install', 'file': 'test.pkg'})
|
||||
self.assertEqual(result['state'], 'failed')
|
||||
self.assertEqual(result['result']['code'], 5005)
|
||||
|
||||
def test_timeout_keeps_job_for_inspection(self):
|
||||
result = self.client.command({'command': 'info'}, timeout=0)
|
||||
self.assertEqual(result['state'], 'timeout')
|
||||
self.assertEqual(result['id'], 'test-job')
|
||||
|
||||
def test_binary_upload_hash_and_safe_name(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
file = Path(tmp) / '中文 package.pkg'
|
||||
data = bytes(range(256)) * 32
|
||||
file.write_bytes(data)
|
||||
name = self.client.upload(file)
|
||||
request = self.server.requests[0]
|
||||
self.assertEqual(request[3], data)
|
||||
self.assertRegex(name, r'^[a-f0-9]{24}\.pkg$')
|
||||
self.assertEqual(request[2]['X-Sha256'], module.hashlib.sha256(data).hexdigest())
|
||||
|
||||
def test_rejected_request_does_not_leak_token(self):
|
||||
with self.assertRaises(RuntimeError) as ctx:
|
||||
self.client.request('GET', '/reject')
|
||||
self.assertIn('401', str(ctx.exception))
|
||||
self.assertNotIn('test-token', str(ctx.exception))
|
||||
|
||||
def test_empty_upload_rejected_before_network(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
file = Path(tmp) / 'empty.pkg'
|
||||
file.touch()
|
||||
with self.assertRaises(ValueError): self.client.upload(file)
|
||||
self.assertEqual(self.server.requests, [])
|
||||
|
||||
def test_wait_link_allows_health_to_reconnect(self):
|
||||
offline = {'state': 'success', 'result': {'connected': False}}
|
||||
online = {'state': 'success', 'result': {'connected': True}}
|
||||
with patch.object(self.client, 'command', side_effect=[offline, online]) as command, patch.object(module.time, 'sleep'):
|
||||
self.assertEqual(self.client.wait_link(5), online)
|
||||
self.assertEqual(command.call_count, 2)
|
||||
self.assertTrue(all(call.args[0] == {'command': 'link'} for call in command.call_args_list))
|
||||
|
||||
def test_wait_link_does_not_repeat_failed_calls(self):
|
||||
failed = {'state': 'failed', 'result': 'disconnected'}
|
||||
with patch.object(self.client, 'command', return_value=failed) as command:
|
||||
self.assertEqual(self.client.wait_link(5), failed)
|
||||
command.assert_called_once()
|
||||
|
||||
|
||||
if __name__ == '__main__': unittest.main()
|
||||
在新工单中引用
屏蔽一个用户