vdi-cloud/tools/tests/test_native_cursor_host.py

107 lines
4.5 KiB
Python

"""Linux host regressions: reject incomplete capture manifests and stale readiness."""
import importlib.util
import hashlib
import io
import json
import os
from pathlib import Path
import sys
import tempfile
import types
import unittest
from unittest.mock import patch
@unittest.skipUnless(sys.platform.startswith('linux'), 'host requires Linux flock')
class HostLeaseTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
configured_source = os.environ.get('VDI_NATIVE_CURSOR_HOST')
source = Path(configured_source) if configured_source else (
Path(__file__).resolve().parents[2] / 'terminal/native-cursor/host.py')
spec = importlib.util.spec_from_file_location('cursor_host', source)
cls.host = importlib.util.module_from_spec(spec)
spec.loader.exec_module(cls.host)
def test_display_restart_cannot_reuse_previous_ready_or_receipt(self):
h = self.host
lease = 'a' * 32
with tempfile.TemporaryDirectory() as directory:
run = Path(directory)
(run / 'ready').write_text('b' * 32)
(run / 'receipt').write_text('nvfbc-v1\n')
calls = []
def systemctl(*args):
calls.append(args)
if args == ('start', h.GUARD):
self.assertFalse((run / 'ready').exists())
self.assertFalse((run / 'receipt').exists())
self.assertEqual((run / 'lease').read_text(), lease)
(run / 'ready').write_text(lease)
return ''
cursor = types.SimpleNamespace(size=(2560, 1440), read=lambda: {})
hello = json.dumps(dict(version=1, lease=lease, size=[2560, 1440]))
stdin = types.SimpleNamespace(buffer=io.BytesIO((hello + '\nstop\n').encode()))
stdout = io.StringIO()
with patch.object(h, 'RUN', run), patch.object(h, 'systemctl', systemctl), \
patch.object(h, 'XCursor', lambda: cursor), patch.object(h.sys, 'stdin', stdin), \
patch.object(h.sys, 'stdout', stdout), \
patch.object(h.select, 'select', lambda *args: ([stdin], [], [])):
h.stream()
self.assertEqual(json.loads(stdout.getvalue())['lease'], lease)
self.assertEqual(calls, [('stop', h.GUARD), ('start', h.GUARD), ('stop', h.GUARD)])
self.assertFalse((run / 'lease').exists())
def test_incomplete_manifest_rejected_before_stopping_capture(self):
h = self.host
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
(root / 'lease').write_text('a' * 32)
(root / 'manifest.json').write_text(json.dumps({'sunshine': '0' * 64}))
with patch.object(h, 'RUN', root), patch.object(h, 'ROOT', root), \
patch.object(h, 'systemctl') as systemctl:
with self.assertRaisesRegex(RuntimeError, 'incomplete pinned capture manifest'):
h.guard()
systemctl.assert_not_called()
def test_software_sprite_guard_stops_capture_before_unhide_on_failure(self):
h = self.host
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
(root / 'lease').write_text('a' * 32)
manifest = {}
for name in ('sunshine', 'capture-no-cursor.so'):
(root / name).write_bytes(name.encode())
manifest[name] = hashlib.sha256(name.encode()).hexdigest()
(root / 'manifest.json').write_text(json.dumps(manifest))
(root / 'hide-server-cursor').touch()
events = []
class CursorOwner:
def __enter__(self):
return self
def hide_server_cursor(self):
events.append('hide')
def __exit__(self, *_):
events.append('restore')
def capture(lease):
events.append('capture')
raise RuntimeError('producer failed')
with patch.object(h, 'ROOT', root), patch.object(h, 'RUN', root), \
patch.object(h, 'XCursor', CursorOwner), \
patch.object(h, 'guard_capture', capture), \
patch.object(h, 'systemctl', lambda *args: events.append(args)):
with self.assertRaisesRegex(RuntimeError, 'producer failed'):
h.guard()
self.assertEqual(events, ['hide', 'capture', ('stop', h.SERVICE), 'restore'])
if __name__ == '__main__':
unittest.main()