Preserve loaded draft bytes and model history, reject stale workspace and input events, and keep captured Escape under modal priority. Verified 181 focused Windows tests and strict desktop Clippy on exact inputs4; earlier full737 scope and component/native preparation evidence are archived separately. Native navigation acceptance remains pending.
99 lines
4.7 KiB
Python
99 lines
4.7 KiB
Python
from pathlib import Path
|
|
import hashlib
|
|
import json
|
|
import shutil
|
|
|
|
ROOT = Path(__file__).resolve().parent
|
|
REPO = ROOT.parent.parent
|
|
|
|
|
|
def write_source(relative, text):
|
|
path = ROOT / relative
|
|
assert not path.exists(), path
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_bytes(text.replace('\r\n', '\n').replace('\n', '\r\n').encode('utf-8'))
|
|
|
|
|
|
files = {
|
|
'project.arcadia': '''arcadia 7;
|
|
project navigation_trial "Navigation — 救援" id "9e8f0000-0000-4000-8000-000000000001" {
|
|
description "Prose @assess must not be a lexical use.";
|
|
include "救援/assess.arcadia";
|
|
include "救援/confirm.arcadia";
|
|
include "links.arcadia";
|
|
}
|
|
''',
|
|
'救援/assess.arcadia': '''// Comment @assess must not be a lexical use.
|
|
oa_activity assess "Assess 救援 request" id "9e8f0000-0000-4000-8000-000000000002" {
|
|
description "Keep @assess as prose, not a use.";
|
|
}
|
|
''',
|
|
'救援/confirm.arcadia': '''oa_activity confirm "Confirm 救援 response" id "9e8f0000-0000-4000-8000-000000000003" {}
|
|
''',
|
|
'links.arcadia': '''interaction handoff "Assessment handoff" id "9e8f0000-0000-4000-8000-000000000004" {
|
|
description "救援 @assess is prose"; source @assess; target @confirm;
|
|
// Another comment @assess is excluded.
|
|
}
|
|
''',
|
|
}
|
|
for name, text in files.items():
|
|
write_source('valid-baseline/' + name, text)
|
|
shutil.copytree(ROOT / 'valid-baseline', ROOT / 'valid-work')
|
|
for case in ['ambiguous-open', 'missing-include-open']:
|
|
for name, text in files.items():
|
|
if case == 'ambiguous-open' and name == '救援/confirm.arcadia':
|
|
text += 'oa_activity assess "Duplicate draft assessment" id "9e8f0000-0000-4000-8000-000000000005" {}\n'
|
|
if case == 'missing-include-open' and name == 'project.arcadia':
|
|
text = text.replace(' include "links.arcadia";', ' include "links.arcadia";\n include "missing/not-loaded.arcadia";')
|
|
write_source(case + '/' + name, text)
|
|
|
|
# A realistic existing teaching model, copied without changing source bytes.
|
|
shutil.copytree(REPO / 'examples/release1', ROOT / 'release1-baseline')
|
|
shutil.copytree(ROOT / 'release1-baseline', ROOT / 'release1-work')
|
|
|
|
|
|
def position(raw, offset):
|
|
line_start = raw.rfind(b'\n', 0, offset) + 1
|
|
return {'byte_offset': offset, 'line_1based': raw.count(b'\n', 0, offset) + 1,
|
|
'utf8_byte_column_1based': offset - line_start + 1}
|
|
|
|
|
|
locations = []
|
|
for folder in ['valid-baseline', 'ambiguous-open', 'missing-include-open']:
|
|
for path in sorted((ROOT / folder).rglob('*.arcadia')):
|
|
raw = path.read_bytes()
|
|
text = raw.decode('utf-8')
|
|
assert b'\n' not in raw.replace(b'\r\n', b'')
|
|
for alias in ['assess', 'confirm']:
|
|
token = alias.encode('ascii')
|
|
start = 0
|
|
while (found := raw.find(token, start)) != -1:
|
|
start = found + len(token)
|
|
line_start = raw.rfind(b'\n', 0, found) + 1
|
|
prefix = raw[line_start:found]
|
|
# Explicit fixture expectations, independent of the application lexer.
|
|
if prefix == b'oa_activity ':
|
|
kind = 'declaration'
|
|
elif prefix.endswith(b'source @') or prefix.endswith(b'target @'):
|
|
kind = 'lexical-use'
|
|
elif found > 0 and raw[found-1:found] == b'@':
|
|
kind = 'excluded-comment-or-string'
|
|
else:
|
|
continue
|
|
locations.append({'fixture': folder, 'path': path.relative_to(ROOT / folder).as_posix(),
|
|
'alias': alias, 'kind': kind, 'span': [found, start],
|
|
**position(raw, found)})
|
|
expected = {'encoding': 'UTF-8 without BOM; every authored fixture newline CRLF',
|
|
'valid_totals': {'assess': {'declarations': 1, 'lexical_uses': 1},
|
|
'confirm': {'declarations': 1, 'lexical_uses': 1}},
|
|
'ambiguous_totals': {'assess': {'declarations': 2, 'lexical_uses': 1}},
|
|
'locations': locations}
|
|
(ROOT / 'expected-locations.json').write_text(json.dumps(expected, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
|
|
manifest = [{'path': p.relative_to(ROOT).as_posix(), 'bytes': p.stat().st_size,
|
|
'sha256': hashlib.sha256(p.read_bytes()).hexdigest()}
|
|
for folder in ['valid-baseline', 'valid-work', 'ambiguous-open', 'missing-include-open', 'release1-baseline', 'release1-work']
|
|
for p in sorted((ROOT / folder).rglob('*')) if p.is_file()]
|
|
(ROOT / 'fixture-manifest.json').write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
|
|
print(json.dumps({'files': len(manifest), 'bytes': sum(p['bytes'] for p in manifest),
|
|
'expected_locations': len(locations)}))
|