okami.mom/albedo

git clone https://git.nkp.pet/okami.mom/albedo

Aly Raffauftest(python): cover trace lifetime and daemon persistence75f8b53

main
5.8 KiB182 linesraw
1"""Catch premature trace release and observation after nested execution.
2
3The daemon cannot refuse trace writes or gate cells.finish acknowledgements,
4so these checks drive the real kernel through its owner channel.
5"""
6
7import unittest
8
9from remote_kernel_test import OwnerChannel
10
11INSPECT = """
12import __main__ as kernel
13import asyncio, sys
14
15def trace_state(id):
16    trace = kernel.ARCHIVES[id].trace
17    return {'sealed': trace.sealed, 'before': len(trace.before),
18            'activities': len(trace.activities), 'truncated': trace.truncated}
19
20async def release_late():
21    late_gate.set()
22    await late_task
23    return trace_state('child')
24"""
25EDIT = "open('note.txt', 'w').write('edited\\n')\nopen('note.txt').read()\n"
26
27
28class CellTracesTest(unittest.TestCase):
29    def setUp(self):
30        self.channel = OwnerChannel([])
31        self.addCleanup(self.channel.close)
32        self.assertEqual(self.channel.recv()["type"], "ready")
33        self.execute("setup", INSPECT)
34
35    def execute(self, id, code):
36        self.channel.send({"type": "execute", "id": id, "code": code})
37        trace = self.channel.wait_for(
38            lambda frame: frame["type"] == "trace" and frame["id"] == id
39        )
40        self.assertFalse(
41            any(
42                frame["type"] == "done" and frame["id"] == id
43                for frame in self.channel.buffered
44            )
45        )
46        done = self.channel.wait_for(
47            lambda frame: frame["type"] == "done" and frame["id"] == id
48        )
49        return trace["trace"], done
50
51    def state(self, id):
52        reply = self.channel.invoke("inspect-" + id, name="trace_state", args=[id])
53        self.assertTrue(reply["ok"], reply)
54        return reply["value"]
55
56    def acknowledge(self, call, value=None):
57        self.channel.send(
58            {"type": "reply", "id": call["id"], "value": {"ok": True, "value": value}}
59        )
60
61    def test_nested_seals_before_ack_and_late_task_cannot_refill(self):
62        self.channel.send(
63            {"type": "execute", "id": "parent", "code": "await cells.run('saved')"}
64        )
65        prepare = self.channel.wait_for(
66            lambda frame: frame.get("method") == "cells.prepare"
67        )
68        self.acknowledge(
69            prepare,
70            {
71                "id": "child",
72                "source": EDIT
73                + """
74late_gate = asyncio.Event()
75async def late_edit():
76    await late_gate.wait()
77    open('note.txt', 'w').write('late\\n')
78    sys.audit('open')
79    kernel.albedo_trace.note('read', 'late')
80late_task = asyncio.create_task(late_edit())
81""",
82            },
83        )
84        started = self.channel.wait_for(
85            lambda frame: frame.get("method") == "cells.started"
86        )
87        self.acknowledge(started)
88        frame = self.channel.wait_for(
89            lambda frame: frame["type"] == "trace" and frame["id"] == "child"
90        )
91        self.assertFalse(
92            any(
93                frame.get("method") == "cells.finish" for frame in self.channel.buffered
94            )
95        )
96        finish = self.channel.wait_for(
97            lambda frame: frame.get("method") == "cells.finish"
98        )
99        expected = {"sealed": True, "before": 0, "activities": 0, "truncated": False}
100        self.assertEqual(self.state("child"), expected)
101        reply = self.channel.invoke("late", name="release_late")
102        self.assertTrue(reply["ok"], reply)
103        self.assertEqual(reply["value"], expected)
104        self.assertIn("+edited", frame["trace"]["changes"][0]["diff"])
105        self.acknowledge(finish)
106        self.channel.wait_for(
107            lambda frame: frame["type"] == "done" and frame["id"] == "parent"
108        )
109        self.assertEqual(self.state("child"), expected)
110
111    def test_delivery_failure_keeps_sealed_buffers_and_propagates(self):
112        _, done = self.execute(
113            "failures",
114            """
115def fail_delivery(finalization=False):
116    capture = kernel.Capture('undelivered')
117    capture.trace.writing('missing.txt')
118    capture.trace.activity('read', 'missing.txt')
119    original_send = kernel.send
120    original_snapshot = kernel.albedo_trace.snapshot
121    def fail(*args):
122        raise OSError('forced failure')
123    try:
124        if finalization:
125            kernel.albedo_trace.snapshot = fail
126        else:
127            kernel.send = fail
128        try:
129            kernel.deliver_trace(capture)
130        except OSError:
131            pass
132        else:
133            raise AssertionError('failure did not propagate')
134    finally:
135        kernel.send = original_send
136        kernel.albedo_trace.snapshot = original_snapshot
137    trace = capture.trace
138    assert trace.sealed and trace.before and trace.activities
139    trace.writing('late.txt')
140    trace.activity('read', 'late.txt')
141    trace.renamed('missing.txt', 'late.txt')
142    assert len(trace.before) == len(trace.activities) == 1
143    try:
144        trace.finish()
145    except RuntimeError:
146        pass
147    else:
148        raise AssertionError('finish allowed twice')
149fail_delivery()
150fail_delivery(True)
151""",
152        )
153        self.assertEqual(done["status"], "ok", done)
154
155    def test_payload_is_detached_and_release_is_idempotent(self):
156        _, done = self.execute(
157            "detached",
158            """
159trace = kernel.albedo_trace.Trace()
160try:
161    trace.release()
162except RuntimeError:
163    pass
164else:
165    raise AssertionError('released an observing trace')
166trace.truncated = True
167trace.activity('read', 'original')
168trace.writing('missing.txt')
169payload = trace.finish()
170trace.activities[('read', 'original')]['target'] = 'mutated'
171trace.release()
172trace.release()
173assert payload['activities'] == [{'kind': 'read', 'target': 'original'}]
174assert not trace.before and not trace.activities
175assert payload['truncated'] and trace.truncated
176""",
177        )
178        self.assertEqual(done["status"], "ok", done)
179
180
181if __name__ == "__main__":
182    unittest.main()