Dotfiles_ArchAP/tests/session_v2/test_path_stability.py

106 lines
6.5 KiB
Python

"""Refus à la frontière Guard sur le lot exact de quatre fenêtres du ticket."""
import tempfile
import unittest
from unittest.mock import Mock, patch
from common import *
from test_prewarm import Sway
from path_stability_runtime import four_slots, UUID, UUID2
from session_v2.executor import Executor
from session_v2.recovery import Recovery, focus_proof
from session_v2.ipc import workspace, walk
from session_v2.errors import Failure
class PathTests(unittest.TestCase):
def fixture(self, directory):
sway = Sway()
sway.add('kitty', 7, focus=False)
sway.add('firefox', 8, focus=False)
sway.add('firefox', 10)
provider = Mock(resolve=lambda *args: {UUID: 8, UUID2: 10}, capture=lambda *args: {UUID: 8, UUID2: 10})
engine = Executor(sway, Catalog(), provider, Path(directory), '/interdit', process_identity=lambda pid: 123)
return sway, engine
@staticmethod
def unary(sway):
ws = workspace(sway.value, '1')
ws['nodes'] = [dict(id=90+i, type='con', layout='splitv' if i % 2 else 'splith', nodes=[n])
for i, n in enumerate(ws['nodes'])]
def test_guard_refuse_les_divergences_avant_tout_ipc(self):
for mode in ('ordre', 'pid', 'starttime', 'remplacement', 'extra', 'workspace', 'output',
'floating', 'focus-tiers', 'limusic-duplique'):
with self.subTest(mode=mode), tempfile.TemporaryDirectory() as directory:
sway, engine = self.fixture(directory)
# Le tiers appartient à la baseline, sans occuper un workspace cible.
if mode == 'focus-tiers':
sway.value['nodes'][0]['nodes'].append(dict(id=50, type='workspace', name='tiers', nodes=[]))
sway.add('tiers', 51, 'tiers', focus=False)
prepared, proof = engine._observe_prepared(), focus_proof(sway)
anchors = Mock(owned=lambda n: False, close=lambda: None)
def guard(recorded, *args):
self.unary(sway)
ws = workspace(sway.value, '1')
first = next(n for n in walk(ws) if n['id'] == 8)
if mode == 'ordre': ws['nodes'].reverse()
if mode == 'pid': first['pid'] = os.getppid()
if mode == 'starttime': engine.process_identity = lambda pid: 456
if mode == 'remplacement': first['id'] = 21
if mode == 'extra': sway.add('firefox', 21, focus=False)
if mode == 'workspace':
moved = ws['nodes'].pop(1)
workspace(sway.value, '10')['nodes'].append(moved)
if mode == 'output': sway.value['nodes'][0]['name'] = 'AUTRE'
if mode == 'floating': first['floating'] = 'user_on'
if mode == 'focus-tiers':
for node in walk(sway.value): node['focused'] = node['id'] == 51
if mode == 'limusic-duplique': sway.add('limusic-app', 22, '10', focus=False)
return Mock(ready={'helpers_suspended': 0}, finish=lambda: {'helpers_resumed': 0, 'anchors_cleaned': 0})
with patch('session_v2.executor.Anchors', return_value=anchors), \
patch('session_v2.executor.Guard', side_effect=guard), patch.object(Recovery, 'recover'):
with self.assertRaises(Failure) as failure:
engine._apply(four_slots(), 1, -1, proof, prepared)
expected = {'remplacement': 'APPLICATION_MEMBERSHIP_CHANGED', 'extra': 'APPLICATION_MEMBERSHIP_CHANGED',
'limusic-duplique': 'APPLICATION_MEMBERSHIP_CHANGED', 'focus-tiers': 'FOCUS_RECOVERY_NOT_PROVEN'}
self.assertEqual(failure.exception.code, expected.get(mode, 'TARGET_OBSERVATION_CHANGED'))
self.assertEqual(sway.commands, [], 'Aucun déplacement autorisé après divergence')
anchors.build.assert_not_called()
def test_changement_apres_capture_canonique_refuse_avant_deplacement(self):
for mode in ('chemin', 'pid', 'starttime', 'remplacement', 'extra', 'focus', 'pending'):
with self.subTest(mode=mode), tempfile.TemporaryDirectory() as directory:
sway, engine = self.fixture(directory)
prepared, proof = engine._observe_prepared(), focus_proof(sway)
anchors = Mock(owned=lambda n: False, close=lambda: None)
guard = Mock(ready={'helpers_suspended': 0}, finish=lambda: {'helpers_resumed': 0, 'anchors_cleaned': 0})
admit = Recovery.admit
def changed(recovery, node, observed=None):
# La dernière fenêtre change après la capture du lot. Les
# premières admissions restent possibles, aucun move ne l'est.
if node['id'] == 10:
target = next(n for n in walk(sway.value) if n['id'] == 10)
if mode == 'chemin': self.unary(sway)
if mode == 'pid': target['pid'] += 1
if mode == 'starttime': recovery.start = lambda pid: 456
if mode == 'remplacement': target['id'] = 21
if mode == 'extra': sway.add('firefox', 21, focus=False)
if mode == 'focus':
for n in walk(sway.value): n['focused'] = n['id'] == 5
result = admit(recovery, node, observed)
if mode == 'pending' and node['id'] == 10:
recovery.data['windows'][0]['pending'] = True
return result
with patch('session_v2.executor.Anchors', return_value=anchors), \
patch('session_v2.executor.Guard', return_value=guard), \
patch.object(Recovery, 'admit', changed), patch.object(Recovery, 'recover'):
with self.assertRaises(Failure) as failure:
engine._apply(four_slots(), 1, -1, proof, prepared)
expected = {'pid': 'TARGET_IDENTITY_CHANGED', 'remplacement': 'TARGET_IDENTITY_CHANGED',
'extra': 'APPLICATION_MEMBERSHIP_CHANGED', 'focus': 'FOCUS_RECOVERY_NOT_PROVEN',
'pending': 'RECOVERY_STATE_CHANGED'}
self.assertEqual(failure.exception.code, expected.get(mode, 'TARGET_OBSERVATION_CHANGED'))
self.assertEqual(sway.commands, [])
anchors.build.assert_not_called()
if __name__ == '__main__': unittest.main()