129 lines
6.2 KiB
Python
129 lines
6.2 KiB
Python
"""Optional private desktops never change normal installation/readiness."""
|
|
import contextlib
|
|
import io
|
|
import json
|
|
import re
|
|
from pathlib import Path
|
|
import sys
|
|
import tempfile
|
|
from types import SimpleNamespace
|
|
import unittest
|
|
from unittest.mock import patch, Mock
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'packaging'))
|
|
from build_expanded import package_relationships
|
|
from build_profiles import PROFILES, target_profile
|
|
from framebridge_expanded import cli, config, display_context as dc, runtime
|
|
|
|
|
|
class DesktopPackageRelationships(unittest.TestCase):
|
|
def test_no_automatic_virtual_desktop_in_any_target(self):
|
|
for target in PROFILES:
|
|
with self.subTest(target=target):
|
|
fields = package_relationships(target_profile(target))
|
|
def packages(value):
|
|
return {re.match(r'[a-z0-9+.-]+', item.strip()).group()
|
|
for item in re.split(r'[,|]', value) if item.strip()}
|
|
required = packages(fields['Depends'])
|
|
recommended = packages(fields.get('Recommends', ''))
|
|
self.assertTrue({'openbox', 'xvfb'}.isdisjoint(required | recommended))
|
|
self.assertEqual({'openbox', 'xvfb'}, set(fields['Suggests'].split(', ')))
|
|
self.assertIn('xauth', required)
|
|
self.assertIn('tmux', required)
|
|
|
|
|
|
class OptionalPrivateDesktop(unittest.TestCase):
|
|
def setUp(self):
|
|
self.temporary = tempfile.TemporaryDirectory(prefix='fb-optional-desktop-')
|
|
self.addCleanup(self.temporary.cleanup)
|
|
self.state = Path(self.temporary.name)
|
|
config.save_config(self.state, config.default_config())
|
|
self.env = {'PATH': '/fixture/bin'}
|
|
dc.bind_text(self.env, self.state)
|
|
|
|
@contextlib.contextmanager
|
|
def prerequisites(self, missing, physical=False):
|
|
def which(name, path=None):
|
|
return None if name in missing else '/fixture/bin/' + name
|
|
compatible = {'ok': True, 'profile_id': 'debian13-amd64-py313',
|
|
'issues': [], 'warnings': []}
|
|
context = {'kind': 'physical-x11', 'physical': True} if physical else {'kind': 'text', 'physical': False}
|
|
with patch.object(runtime, 'environment', return_value=dict(self.env)), \
|
|
patch.object(runtime.shutil, 'which', side_effect=which), \
|
|
patch.object(runtime, '_display_ok', return_value=physical), \
|
|
patch.object(dc, 'describe', return_value=context), \
|
|
patch('framebridge_expanded.network.inspect', return_value={}), \
|
|
patch('framebridge_expanded.compatibility.assess', return_value=compatible):
|
|
yield
|
|
|
|
def mode(self, value):
|
|
settings = config.load_config(self.state)
|
|
settings['headless'] = value
|
|
config.save_config(self.state, settings)
|
|
|
|
def test_normal_text_mode_ready_without_optional_tools(self):
|
|
with self.prerequisites({'openbox', 'Xvfb'}):
|
|
result = runtime.doctor(self.state)
|
|
self.assertTrue(result['ok'])
|
|
self.assertEqual([], result['missing_commands'])
|
|
self.assertTrue(result['capabilities']['fbexec'])
|
|
self.assertTrue(result['capabilities']['fbterm'])
|
|
self.assertFalse(result['capabilities']['private_desktop'])
|
|
self.assertEqual(['Xvfb', 'openbox'], result['optional_missing_commands']['private_desktop'])
|
|
self.assertEqual(dc.VIRTUAL_INSTALL_COMMAND, result['optional_install_commands']['private_desktop'])
|
|
|
|
def test_existing_physical_x11_does_not_require_virtual_tools(self):
|
|
self.mode('no')
|
|
with self.prerequisites({'openbox', 'Xvfb'}, physical=True):
|
|
result = runtime.doctor(self.state)
|
|
self.assertTrue(result['ok'])
|
|
self.assertTrue(result['capabilities']['graphical_cua'])
|
|
self.assertFalse(result['capabilities']['private_desktop'])
|
|
self.assertEqual([], result['missing_commands'])
|
|
|
|
def test_explicit_virtual_start_fails_before_spawning_with_install_hint(self):
|
|
self.mode('yes')
|
|
with self.prerequisites({'openbox'}), \
|
|
patch.object(runtime, 'status', return_value={'running': False}), \
|
|
patch.object(runtime, '_spawn') as spawn:
|
|
result = runtime.start(self.state)
|
|
self.assertFalse(result['ok'])
|
|
self.assertIn('openbox', result['doctor']['missing_commands'])
|
|
self.assertIn(dc.VIRTUAL_INSTALL_COMMAND, result['error'])
|
|
spawn.assert_not_called()
|
|
|
|
def test_manager_rechecks_missing_tool_before_creating_any_child(self):
|
|
self.mode('yes')
|
|
with self.prerequisites({'openbox'}), \
|
|
patch.object(runtime.signal, 'signal'), \
|
|
patch.object(runtime, '_spawn') as spawn:
|
|
with self.assertRaisesRegex(RuntimeError, 'Private virtual desktop tools are missing: openbox'):
|
|
runtime._manager(self.state)
|
|
spawn.assert_not_called()
|
|
error = json.loads((self.state/'last-error.json').read_text())['error']
|
|
self.assertIn(dc.VIRTUAL_INSTALL_COMMAND, error)
|
|
|
|
def test_virtual_tools_available_with_no_optional_install_hint(self):
|
|
with self.prerequisites(set()):
|
|
result = runtime.doctor(self.state)
|
|
self.assertTrue(result['capabilities']['private_desktop'])
|
|
self.assertIsNone(result['optional_install_commands']['private_desktop'])
|
|
|
|
def test_human_doctor_explains_optional_install_without_failing(self):
|
|
fake_runtime = SimpleNamespace(doctor=Mock(return_value={
|
|
'ok': True, 'display': 'text tools', 'missing_commands': [], 'issues': [],
|
|
'platform_compatibility': {'ok': True},
|
|
'optional_missing_commands': {'private_desktop': ['openbox']},
|
|
'optional_install_commands': {'private_desktop': dc.VIRTUAL_INSTALL_COMMAND}}))
|
|
stdout = io.StringIO()
|
|
with patch.object(cli, '_module', return_value=fake_runtime), \
|
|
contextlib.redirect_stdout(stdout):
|
|
code = cli.main(['--state-dir', str(self.state), 'doctor'])
|
|
self.assertEqual(0, code)
|
|
self.assertIn('Optional private desktop tools missing: openbox', stdout.getvalue())
|
|
self.assertIn(dc.VIRTUAL_INSTALL_COMMAND, stdout.getvalue())
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|