1import copy
2import json
3from pathlib import Path
4import subprocess
5import unittest
6
7from crash_recovery import active_text, verify_text
8from document_model import EXPORTER, ordered_pages, view, walk
9
10
11class RecoveryOracleTest(unittest.TestCase):
12 def test_current_page_is_distinguished_from_native_conflict_content(self):
13 source = Path(__file__).resolve().parent.parent / 'corpus/collaboration/round-01/offline/notebook/synthetic.one'
14 model = json.loads(subprocess.check_output([EXPORTER, source]))
15 (sid, _, revision, page), = ordered_pages(model)
16 main = next(node for _, node in walk(revision, page) if node['kind']['type'] == 'RichText')
17 main['kind']['text'] = 'Concurrent edits: current'
18 manifest = revision['nodes'][revision['roots']['1']]
19 self.assertTrue(manifest['spaces'])
20 for conflict in manifest['spaces']:
21 self.assertNotEqual(sid, conflict)
22 _, older = view(model, conflict)
23 node = next(node for node in older['nodes'].values() if node['kind']['type'] == 'RichText')
24 node['kind']['text'] = 'Concurrent edits: older'
25 self.assertEqual(active_text(model), 'Concurrent edits: current')
26
27 def test_accounting_and_corruption_controls(self):
28 logs = {'w0': [
29 {'event': 'intent', 'token': ' [w0:0]', 'replacement': ' [w0:0]', 'before': 'Base', 'range': [4, 4], 'attempt': 1},
30 {'event': 'commit', 'token': ' [w0:0]', 'attempt': 1, 'started_us': 5, 'finished_us': 10},
31 {'event': 'intent', 'token': ' [w0:1]', 'replacement': ' [w0:1]', 'before': 'Base [w0:0]', 'range': [11, 11], 'attempt': 2},
32 {'event': 'commit_error', 'token': ' [w0:1]', 'attempt': 2, 'state': 'Unknown', 'started_us': 18, 'finished_us': 20},
33 {'event': 'read', 'text': 'Base [w0:0]', 'started_us': 15, 'finished_us': 17},
34 ]}
35 self.assertEqual(verify_text('Base', 'Base [w0:0] [w0:1]', logs)['unacknowledged_retained'], [' [w0:1]'])
36 self.assertEqual(verify_text('Base', 'Base [w0:0]', logs)['acknowledged'], 1)
37 for text in ['Base', 'Other [w0:0]', 'Base [w0:0] [w0:0]', 'Base [w0:0] [w0:2]', 'Base [w0:0] [w0:']:
38 with self.assertRaises(AssertionError):
39 verify_text('Base', text, logs)
40 lost_read = copy.deepcopy(logs)
41 lost_read['w0'][-1]['text'] = 'Base'
42 with self.assertRaisesRegex(AssertionError, 'read missed'):
43 verify_text('Base', 'Base [w0:0]', lost_read)
44 committed_error = copy.deepcopy(logs)
45 committed_error['w0'][3]['state'] = 'Committed'
46 with self.assertRaisesRegex(AssertionError, 'acknowledged edit was lost'):
47 verify_text('Base', 'Base [w0:0]', committed_error)
48 rejected = copy.deepcopy(logs)
49 rejected['w0'][3]['state'] = 'NotCommitted'
50 with self.assertRaisesRegex(AssertionError, 'recorded publication'):
51 verify_text('Base', 'Base [w0:0] [w0:1]', rejected)
52 with self.assertRaisesRegex(AssertionError, 'recorded publication'):
53 verify_text('Base', 'Base [w0:1] [w0:0]', logs)
54 future = copy.deepcopy(logs)
55 future['w0'][1]['started_us'] = 18
56 with self.assertRaisesRegex(AssertionError, 'future edit'):
57 verify_text('Base', 'Base [w0:0]', future)