| 1 | """Verify local intent acknowledgements separately from the remote publication chain.""" |
| 2 | import re |
| 3 | |
| 4 | |
| 5 | def tokens(text): |
| 6 | assert text.startswith('Concurrent edits:'), 'Unexpected append prefix' |
| 7 | tail = text[len('Concurrent edits:'):] |
| 8 | found = re.findall(r' \[w[0-9]+:[0-9]+\]', tail) |
| 9 | assert ''.join(found) == tail and len(set(found)) == len(found), 'Malformed or duplicated append history' |
| 10 | return found |
| 11 | |
| 12 | |
| 13 | def publication_links(logs, operations, partial=False): |
| 14 | local = {} |
| 15 | for actor, events in logs.items(): |
| 16 | assert events and events[0]['event'] == 'ready', 'Missing client start' |
| 17 | assert partial or events[-1]['event'] == 'done', 'Incomplete client log' |
| 18 | if not actor.startswith('w'): continue |
| 19 | assert events[0].get('offline') is True, 'Expected an offline writer' |
| 20 | edits = [event for event in events if event['event'] == 'local_commit'] |
| 21 | assert [event['operation'] for event in edits] == list(range(len(edits))), 'Missing or duplicate local acknowledgement' |
| 22 | assert len(edits) <= operations and (partial or len(edits) == operations), 'Local operation count differs' |
| 23 | assert [event['id'] for event in edits] == sorted(event['id'] for event in edits), 'Local IDs went backwards' |
| 24 | for event in edits: |
| 25 | token = f' [{actor}:{event["operation"]}]' |
| 26 | assert event['token'] == token and token not in local, 'Local token differs from its operation' |
| 27 | assert event['started_us'] <= event['finished_us'], 'Invalid local acknowledgement interval' |
| 28 | prior = tokens(event['before']) |
| 29 | own = [item for item in prior if item.startswith(f' [{actor}:')] |
| 30 | assert own == [f' [{actor}:{i}]' for i in range(event['operation'])], 'Local view lost or invented its own edit' |
| 31 | local[token] = event |
| 32 | for event in local.values(): |
| 33 | for token in tokens(event['before']): |
| 34 | assert token in local and local[token]['started_us'] <= event['finished_us'], 'Local view invented a future token' |
| 35 | links = {} |
| 36 | seen_revisions = set() |
| 37 | for actor, events in logs.items(): |
| 38 | if not actor.startswith('w'): continue |
| 39 | # A save replaces the newest pending save of its page, so consecutive operations |
| 40 | # can share one intent and one publication. |
| 41 | edits = {} |
| 42 | for event in events: |
| 43 | if event['event'] == 'local_commit': edits.setdefault(event['id'], []).append(event) |
| 44 | def once(rows): |
| 45 | # The client reports a coalesced intent's receipt once per operation. |
| 46 | found, seen = {}, {} |
| 47 | for row in rows: |
| 48 | assert found.setdefault(row['id'], row)['revision'] == row['revision'], 'One intent has two receipts' |
| 49 | seen[row['id']] = seen.get(row['id'], 0) + 1 |
| 50 | assert all(count == len(edits.get(id, [])) for id, count in seen.items()), 'Duplicate remote receipt' |
| 51 | return list(found.values()) |
| 52 | receipts = once(event for event in events if event['event'] == 'remote_receipt') |
| 53 | assert set(event['id'] for event in receipts) <= set(edits), 'Receipt lacks a local intent' |
| 54 | assert partial or len(receipts) == len(edits), 'Local success lacks remote acknowledgement' |
| 55 | reopened = once(event for event in events if event['event'] == 'reopened_receipt') |
| 56 | if not partial: |
| 57 | assert [(event['id'], event['revision']) for event in reopened] == [(event['id'], event['revision']) for event in receipts], 'Receipt changed across reopen' |
| 58 | for receipt in receipts: |
| 59 | group = edits[receipt['id']] |
| 60 | intent = {**group[0], 'token': ''.join(event['token'] for event in group)} |
| 61 | attempts = [event for event in events if event['event'] == 'remote_attempt' and event['revision'] == receipt['revision']] |
| 62 | assert len(attempts) == 1, 'Receipt does not identify one publication attempt' |
| 63 | attempt, = attempts |
| 64 | assert attempt['state'] in ('Committed', 'Unknown'), 'Receipt identifies a proven-unpublished attempt' |
| 65 | if attempt['state'] == 'Unknown': |
| 66 | assert all(field in attempt and field in intent for field in ('space', 'object')), 'Uncertain target identity is missing' |
| 67 | assert attempt['space'] == intent['space'] and attempt['object'] == intent['object'], 'Confirmation identifies another target' |
| 68 | confirmed = [event for event in events if event['event'] == 'remote_confirm' |
| 69 | and event['state'] == 'Committed' |
| 70 | and receipt['revision'] in event.get('revisions', {}).get(intent['space'], []) |
| 71 | and event.get('text', '').startswith(attempt['after']) |
| 72 | and attempt['finished_us'] <= event['started_us'] <= event['finished_us'] <= receipt['at_us']] |
| 73 | assert confirmed, 'Uncertain publication lacks a successful retained-revision confirmation' |
| 74 | assert not any(event['event'] == 'remote_attempt' and event['started_us'] >= attempt['finished_us'] |
| 75 | and event['after'] == event['before'] + intent['token'] for event in events), 'An uncertain intent was replayed' |
| 76 | assert receipt['revision'] not in seen_revisions, 'A revision was acknowledged twice' |
| 77 | seen_revisions.add(receipt['revision']) |
| 78 | assert intent['started_us'] <= attempt['started_us'] <= attempt['finished_us'] <= receipt['at_us'], 'Receipt precedes its publication' |
| 79 | assert attempt['after'] == attempt['before'] + intent['token'], 'Remote publication differs from local intent' |
| 80 | tokens(attempt['after']) |
| 81 | assert attempt['before'] not in links, 'Remote publications branched from the same content' |
| 82 | event = {**attempt, 'event': 'commit', 'operation': group[-1]['operation'], 'operations': len(group), 'token': intent['token'], 'finished_us': receipt['at_us']} |
| 83 | links[attempt['before']] = event, attempt['after'] |
| 84 | return links |