Release Preview 20 shared desktop, Android and Sargo updates
Restore Gaia settings and media workflows, extend native network adapters, and align the APK payload. Generate French/English changelogs and website entries from the simplified JSON source. Validation: shared JS and Tundra unit tests; desktop workflows and persistence; signed APK payload parity; Sargo RAM and installed startup with boot readback. Android runtime qualification is recorded separately.
This commit is contained in:
parent
be2cdce823
commit
1311bed5e9
170 changed files with 19698 additions and 2495 deletions
|
|
@ -1,4 +1,7 @@
|
|||
import importlib.util
|
||||
import json
|
||||
import socket
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
from types import SimpleNamespace
|
||||
|
|
@ -12,6 +15,107 @@ spec.loader.exec_module(radio)
|
|||
|
||||
|
||||
class RadioControlTests(unittest.TestCase):
|
||||
def test_message_state_accepts_modem_hex_case_but_not_other_objects(self):
|
||||
inbox = SimpleNamespace(outgoing_state=lambda path: 'sent')
|
||||
spec = SimpleNamespace(loader=SimpleNamespace(exec_module=lambda module: None))
|
||||
with patch.object(radio.importlib.util, 'spec_from_file_location', return_value=spec), \
|
||||
patch.object(radio.importlib.util, 'module_from_spec', return_value=inbox):
|
||||
for path in ['/ril_0/message_18DE1E64E8B16EA9E5E57366556C971B822E2614',
|
||||
'/ril_0/message_ab12']:
|
||||
self.assertEqual(radio.request({'version':1, 'operation':'messageState',
|
||||
'path':path}), {'state':'sent'})
|
||||
for path in ['/ril_0/voicecall01', '/ril_0/message_Z', '/ril_0/message_1/other']:
|
||||
with self.assertRaisesRegex(ValueError, 'INVALID_SMS_PATH'):
|
||||
radio.request({'version':1, 'operation':'messageState', 'path':path})
|
||||
|
||||
def test_slow_settings_transaction_does_not_block_call_controls(self):
|
||||
import threading
|
||||
started, release = threading.Event(), threading.Event()
|
||||
worker = radio.SettingsWorker()
|
||||
settings_server, settings_client = socket.socketpair()
|
||||
voice_server, voice_client = socket.socketpair()
|
||||
def request(value):
|
||||
if value['operation'] == 'mobileSettings':
|
||||
started.set()
|
||||
if not release.wait(2): raise RuntimeError('test settings timeout')
|
||||
return {'saved':True}
|
||||
return {'calls':[]}
|
||||
try:
|
||||
with patch.object(radio,'request',side_effect=request), settings_client, voice_server, voice_client:
|
||||
settings_client.sendall(b'{"version":1,"operation":"mobileSettings","action":"editApn"}\n')
|
||||
self.assertTrue(radio.handle_connection(settings_server,worker))
|
||||
self.assertTrue(started.wait(1))
|
||||
voice_client.sendall(b'{"version":1,"operation":"calls"}\n')
|
||||
self.assertFalse(radio.handle_connection(voice_server,worker))
|
||||
voice_client.settimeout(1)
|
||||
self.assertEqual(json.loads(voice_client.recv(4096))['result'],{'calls':[]})
|
||||
release.set()
|
||||
settings_client.settimeout(1)
|
||||
self.assertTrue(json.loads(settings_client.recv(4096))['result']['saved'])
|
||||
finally:
|
||||
release.set(); worker.close(); settings_server.close()
|
||||
|
||||
def test_stale_queued_settings_are_not_applied(self):
|
||||
worker=radio.SettingsWorker()
|
||||
server, client=socket.socketpair()
|
||||
worker.slots.acquire()
|
||||
try:
|
||||
with client, patch.object(radio.time,'monotonic',return_value=20), patch.object(radio,'request') as request:
|
||||
worker.run(server,{'operation':'mobileSettings'},0)
|
||||
self.assertEqual(json.loads(client.recv(4096)),{'ok':False,'error':'RADIO_BUSY'})
|
||||
request.assert_not_called()
|
||||
finally:worker.close()
|
||||
|
||||
def test_full_settings_queue_is_bounded(self):
|
||||
worker=radio.SettingsWorker()
|
||||
server, client=socket.socketpair()
|
||||
try:
|
||||
for _ in range(8):self.assertTrue(worker.slots.acquire(blocking=False))
|
||||
with server, client:
|
||||
client.sendall(b'{"version":1,"operation":"mobileSettings","action":"sim"}\n')
|
||||
self.assertFalse(radio.handle_connection(server,worker))
|
||||
self.assertEqual(json.loads(client.recv(4096)),{'ok':False,'error':'RADIO_BUSY'})
|
||||
finally:
|
||||
for _ in range(8):worker.slots.release()
|
||||
worker.close()
|
||||
|
||||
def test_closed_client_does_not_crash_broker_or_repeat_sms(self):
|
||||
server, client = socket.socketpair()
|
||||
with server:
|
||||
client.sendall(b'{"version":1,"operation":"sms"}\n')
|
||||
client.close()
|
||||
with patch.object(radio, 'request', return_value={'state':'pending'}) as request:
|
||||
radio.handle_connection(server)
|
||||
request.assert_called_once()
|
||||
|
||||
def test_incomplete_request_cannot_dial(self):
|
||||
server, client = socket.socketpair()
|
||||
with server, client:
|
||||
client.sendall(b'{"version":1,"operation":"dial"}')
|
||||
client.shutdown(socket.SHUT_WR)
|
||||
with patch.object(radio, 'request') as request:
|
||||
radio.handle_connection(server)
|
||||
request.assert_not_called()
|
||||
|
||||
def test_modem_timeout_does_not_disclose_sms_arguments(self):
|
||||
server, client = socket.socketpair()
|
||||
with server, client:
|
||||
client.sendall(b'{"version":1,"operation":"sms"}\n')
|
||||
error = subprocess.TimeoutExpired(['busctl', 'private-number', 'private-body'], 15)
|
||||
with patch.object(radio, 'request', side_effect=error):
|
||||
radio.handle_connection(server)
|
||||
response = json.loads(client.recv(4096))
|
||||
self.assertEqual(response, {'ok':False, 'error':'RADIO_TIMEOUT'})
|
||||
|
||||
def test_slow_client_timeout_does_not_escape_handler(self):
|
||||
from unittest.mock import Mock
|
||||
conn = Mock()
|
||||
conn.recv.side_effect = socket.timeout()
|
||||
with patch.object(radio, 'request') as request:
|
||||
radio.handle_connection(conn)
|
||||
request.assert_not_called()
|
||||
conn.sendall.assert_not_called()
|
||||
|
||||
def test_void_dbus_success_is_not_an_invalid_reply(self):
|
||||
with patch.object(radio.subprocess, 'run', return_value=SimpleNamespace(
|
||||
returncode=0, stdout='', stderr='')):
|
||||
|
|
|
|||
Loading…
Reference in a new issue