125 lines
4.6 KiB
Python
125 lines
4.6 KiB
Python
import importlib.util
|
|
from importlib.machinery import SourceFileLoader
|
|
import json
|
|
import os
|
|
import socket
|
|
import stat
|
|
import sys
|
|
import tempfile
|
|
import threading
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
|
|
SCRIPT = Path(__file__).parents[2] / "bin/.local/bin/labfy-removable-media"
|
|
SPEC = importlib.util.spec_from_loader(
|
|
"labfy_removable_media_ipc",
|
|
SourceFileLoader("labfy_removable_media_ipc", str(SCRIPT)),
|
|
)
|
|
assert SPEC and SPEC.loader
|
|
media = importlib.util.module_from_spec(SPEC)
|
|
sys.modules[SPEC.name] = media
|
|
SPEC.loader.exec_module(media)
|
|
|
|
|
|
class IpcTests(unittest.TestCase):
|
|
def test_request_allowlist_and_shape(self):
|
|
self.assertEqual(media.validate_request({"operation": "list"}), ("list", None))
|
|
self.assertEqual(
|
|
media.validate_request({"operation": "mount", "runtime_id": "mtp:nonce:1"}),
|
|
("mount", "mtp:nonce:1"),
|
|
)
|
|
with self.assertRaises(media.RequestError):
|
|
media.validate_request({"operation": "format", "runtime_id": "/tmp/x"})
|
|
with self.assertRaises(media.RequestError):
|
|
media.validate_request({"operation": "list", "extra": True})
|
|
with self.assertRaises(media.RequestError):
|
|
media.validate_request({"operation": "mount", "runtime_id": "sdb1"})
|
|
|
|
def test_socket_mode_and_json_round_trip(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
path = Path(directory) / "media.sock"
|
|
seen = []
|
|
server = media.JsonSocketServer(
|
|
path, lambda request: seen.append(request) or {"ok": True}
|
|
)
|
|
server.start()
|
|
self.assertEqual(stat.S_IMODE(path.stat().st_mode), 0o600)
|
|
client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
client.connect(str(path))
|
|
thread = threading.Thread(
|
|
target=lambda: server._serve_connection(server.socket.accept()[0])
|
|
)
|
|
thread.start()
|
|
client.sendall(b'{"operation":"list"}\n')
|
|
response = json.loads(client.makefile("rb").readline())
|
|
client.close()
|
|
thread.join(2)
|
|
server.socket.close()
|
|
self.assertFalse(thread.is_alive())
|
|
self.assertEqual(response, {"ok": True})
|
|
self.assertEqual(seen, [{"operation": "list"}])
|
|
|
|
def test_oversized_request_is_rejected_without_calling_handler(self):
|
|
called = []
|
|
server = media.JsonSocketServer(Path("unused"), lambda request: called.append(request))
|
|
server_side, client_side = socket.socketpair()
|
|
thread = threading.Thread(target=server._serve_connection, args=(server_side,))
|
|
thread.start()
|
|
client_side.sendall(b"x" * (media.MAX_REQUEST + 1))
|
|
response = json.loads(client_side.makefile("rb").readline())
|
|
thread.join(2)
|
|
client_side.close()
|
|
self.assertFalse(response["ok"])
|
|
self.assertIn("volumineuse", response["error"])
|
|
self.assertEqual(called, [])
|
|
|
|
def test_dispatcher_waits_for_async_completion(self):
|
|
pending = []
|
|
|
|
class GLib:
|
|
@staticmethod
|
|
def idle_add(callback):
|
|
callback()
|
|
|
|
dispatcher = media.GLibDispatcher(
|
|
GLib, lambda request, done: pending.append((request, done)) or (lambda: None)
|
|
)
|
|
result = []
|
|
thread = threading.Thread(
|
|
target=lambda: result.append(dispatcher({"operation": "list"}))
|
|
)
|
|
thread.start()
|
|
while not pending:
|
|
threading.Event().wait(0.001)
|
|
pending[0][1]({"ok": True})
|
|
thread.join(2)
|
|
|
|
self.assertFalse(thread.is_alive())
|
|
self.assertEqual(result, [{"ok": True}])
|
|
|
|
def test_slow_incomplete_request_times_out_without_calling_handler(self):
|
|
called = []
|
|
server = media.JsonSocketServer(Path("unused"), lambda request: called.append(request))
|
|
server_side, client_side = socket.socketpair()
|
|
previous_timeout = media.READ_TIMEOUT
|
|
media.READ_TIMEOUT = 0.05
|
|
try:
|
|
thread = threading.Thread(
|
|
target=server._serve_connection, args=(server_side,)
|
|
)
|
|
thread.start()
|
|
client_side.sendall(b'{"operation":')
|
|
response = json.loads(client_side.makefile("rb").readline())
|
|
thread.join(2)
|
|
finally:
|
|
media.READ_TIMEOUT = previous_timeout
|
|
client_side.close()
|
|
self.assertFalse(thread.is_alive())
|
|
self.assertFalse(response["ok"])
|
|
self.assertIn("Délai de lecture", response["error"])
|
|
self.assertEqual(called, [])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|