Files
ARCADIA-Studio/harness/experiments/text-workspace-native3-20260913/helpers/gate.py
T
sorokin.oleg.edu 2df7f580aa Make the Text workspace compact and preserve editing context
Replace the oversized header and reference rail with stable editor slots and a retained supporting drawer. Bind diagnostic, result and menu actions to their original publication and visit; preserve effective selections on navigation and dismiss Actions, Find and the drawer one surface per Escape.

Retain exact Windows source captures, eight final gates (871 desktop passes), independent reviews, native selection/rename/recovery trials and the original failures. All 526 tested inputs and staged evidence were byte-checked before commit. Combined preview integration and packaging remain with issue 27; Linux remains paused.
2026-09-12 22:00:36 -07:00

114 lines
9.4 KiB
Python

"""Capture exact working inputs and run one serial Windows acceptance attempt."""
import datetime
import hashlib
import json
import os
from pathlib import Path
import re
import shutil
import subprocess
import sys
import time
import zipfile
ROOT = Path(__file__).resolve().parents[2]
BASE = Path(__file__).resolve().parent
label = sys.argv[1]
if not re.fullmatch(r'attempt-[0-9]+', label):
raise RuntimeError('Invalid attempt label')
DEST = BASE / label
DEST.mkdir()
if os.environ.get('CARGO_BUILD_JOBS') != '1':
raise RuntimeError('One Windows compiler job required')
SCOPES = ['Cargo.toml', 'Cargo.lock', 'crates', 'build', '.cargo', 'docs', 'examples', 'harness/fixtures']
def now():
return datetime.datetime.now(datetime.timezone.utc).isoformat()
def sha(path):
return hashlib.sha256(path.read_bytes()).hexdigest()
def names():
data = subprocess.check_output(['git', 'ls-files', '-z', '--cached', '--others', '--exclude-standard', '--', *SCOPES], cwd=ROOT)
return sorted(set(p.decode() for p in data.split(b'\0') if p))
def write(name, value):
with (DEST / name).open('x', encoding='utf-8', newline='\n') as output:
json.dump(value, output, indent=2)
output.write('\n')
inputs = []
with zipfile.ZipFile(DEST / 'inputs.zip', 'x', zipfile.ZIP_DEFLATED) as archive:
for name in names():
raw = (ROOT / name).read_bytes()
archive.writestr(name, raw)
inputs.append({'path': name, 'bytes': len(raw), 'sha256': hashlib.sha256(raw).hexdigest()})
def verify():
return names() == [row['path'] for row in inputs] and all(sha(ROOT / row['path']) == row['sha256'] for row in inputs)
write('inputs.json', {'atUtc': now(), 'baseCommit': subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=ROOT).decode().strip(), 'archiveSha256': sha(DEST / 'inputs.zip'), 'files': inputs})
write('environment.json', {'atUtc': now(), 'versions': {name: subprocess.check_output(command, cwd=ROOT).decode() for name, command in [('rustc', ['rustc', '-vV']), ('cargo', ['cargo', '-V'])]}, 'variables': {key: os.environ.get(key) for key in ['RUSTUP_HOME', 'CARGO_HOME', 'RUSTUP_TOOLCHAIN', 'CARGO_TARGET_DIR', 'CARGO_BUILD_JOBS', 'CARGO_TARGET_X86_64_PC_WINDOWS_GNULLVM_LINKER']}})
gates = [
('escape', ['cargo', 'test', '--locked', '--offline', '-p', 'oa-desktop', '--bin', 'arcadia-studio', 'escape', '--', '--nocapture', '--test-threads=1']),
('clippy', ['cargo', 'clippy', '--locked', '--offline', '-p', 'oa-desktop', '--all-targets', '--', '-D', 'warnings']),
('membership', ['cargo', 'test', '--locked', '--offline', '-p', 'oa-desktop', '--bin', 'arcadia-studio', '--', '--list']),
('focused', ['cargo', 'test', '--locked', '--offline', '-p', 'oa-desktop', '--bin', 'arcadia-studio', 'text_workspace', '--', '--nocapture', '--test-threads=1']),
('selection', ['cargo', 'test', '--locked', '--offline', '-p', 'oa-desktop', '--bin', 'arcadia-studio', 'selection', '--', '--nocapture', '--test-threads=1']),
('tests', ['cargo', 'test', '--locked', '--offline', '-p', 'oa-desktop', '--bin', 'arcadia-studio', '--', '--test-threads=1']),
('timing', ['cargo', 'test', '--locked', '--offline', '-p', 'oa-desktop', '--bin', 'arcadia-studio', 'app::source_rename::timing_tests::accepted_release1_prepare_commit_sync_and_confirm_timing', '--', '--ignored', '--nocapture', '--test-threads=1']),
('gui', ['cargo', 'build', '--locked', '--offline', '-p', 'oa-desktop', '--bin', 'arcadia-studio', '--message-format=json-render-diagnostics']),
]
for name, command in gates:
if not verify():
raise RuntimeError('Source inputs changed before gate')
print(json.dumps({'starting': name, 'command': command}), flush=True)
start, clock = now(), time.perf_counter()
with (DEST / (name + '-stdout.log')).open('xb') as out, (DEST / (name + '-stderr.log')).open('xb') as err:
result = subprocess.run(command, cwd=ROOT, stdout=out, stderr=err)
receipt = {'command': command, 'started': start, 'finished': now(), 'seconds': time.perf_counter() - clock, 'exitCode': result.returncode, 'inputsUnchanged': verify(), 'stdoutSha256': sha(DEST / (name + '-stdout.log')), 'stderrSha256': sha(DEST / (name + '-stderr.log'))}
if name == 'membership' and result.returncode == 0:
listed = {line.removesuffix(': test') for line in (DEST / 'membership-stdout.log').read_text(encoding='utf-8').splitlines() if line.endswith(': test')}
required = {
'app::source_symbols::tests::escape_hides_results_and_cancels_navigation_but_retains_latest_display',
'app::source_symbols::tests::captured_find_and_modal_escape_use_one_actual_subscription_and_preserve_pending_work',
'app::source_symbols::tests::popup_escape_has_no_duplicate_bridge_and_dismissal_preserves_underlying_find_and_drawer',
'app::text_workspace::tests::document_reset_preserves_popup_token_sequence_and_drops_old_menu_authority',
'app::text_workspace::tests::stale_retained_group_can_expand_without_navigating_or_applying_drafts',
'app::text_workspace::lifecycle_tests::saved_new_destination_republishes_current_warning_and_refuses_old_primary_and_related',
'app::text_workspace::lifecycle_tests::invalid_repair_open_clears_prior_presentation_and_publishes_current_problems_preserving_worker',
'views::text_workspace::tests::highlighted_sensor_editor_retains_real_scrolled_buffer_across_panel_rebuilds',
'views::text_workspace::tests::drawer_clamps_to_actual_center_allocation_including_oversize_requests',
'diagnostic_input::tests::same_publication_new_visit_cancels_armed_rows_without_a_hidden_tree_frame',
'app::source_symbols::tests::retained_choices_cannot_replay_after_hide_or_references_problems_references',
'app::text_workspace::tests::queued_group_toggle_cannot_mutate_a_reopened_or_rotated_visit',
'views::text_workspace::summary_visibility_tests::all_reference_exceptions_fit_allocated_width_with_wide_alias_and_large_counts',
'app::text_workspace::focus_selection_tests::word_and_line_selection_survive_find_close_and_escape_then_replace_exact_bytes',
'app::text_workspace::focus_selection_tests::mounted_direct_typing_control_and_old_cursor_roundtrip_discriminate_selection_loss',
'app::text_workspace::focus_selection_tests::stale_focus_ticket_and_later_raw_input_cannot_steal_focus_or_change_selected_bytes',
'app::text_workspace::focus_selection_tests::explicit_cursor_navigation_still_moves_and_selects_requested_line',
'app::source_symbols::tests::effective_selection_survives_file_roundtrip_and_definition_back',
'app::source_symbols::tests::effective_selection_is_untouched_by_noop_missing_invalid_or_cancelled_navigation',
'app::source_symbols::tests::changed_active_origin_back_keeps_current_effective_selection',
'app::source_symbols::tests::source_creation_preserves_outgoing_selection_and_undisplayed_entry_cursor',
'source_navigation::selection::tests::unicode_normal_ranges_preserve_forward_and_reverse_direction',
'source_navigation::selection::tests::repeated_unicode_words_capture_the_selected_occurrence_without_searching',
'source_navigation::selection::tests::line_selection_expands_past_its_interior_unicode_caret',
'source_navigation::selection::tests::multiline_ranges_use_copied_lf_and_utf8_columns_for_physical_crlf_and_mixed_endings',
'source_navigation::selection::tests::expanded_word_and_line_modes_capture_multiline_ranges_in_both_directions',
'source_navigation::selection::tests::trailing_newline_selection_restores_endpoint_on_the_empty_last_line',
'source_navigation::selection::tests::absent_and_empty_selection_do_not_move_the_departing_caret',
}
receipt['testMembership'] = {'listed': len(listed), 'required': sorted(required), 'missing': sorted(required - listed), 'diagnosticGuardCount': sum(name.startswith('diagnostic_input::tests::') for name in listed)}
if required - listed or receipt['testMembership']['diagnosticGuardCount'] != 20:
write(name + '-result.json', receipt)
raise RuntimeError('New Text workspace test membership differs; shared-target cache output is not accepted')
if name == 'gui' and result.returncode == 0:
records = [json.loads(line) for line in (DEST / 'gui-stdout.log').read_text(encoding='utf-8').splitlines() if line.startswith('{')]
artifacts = [record for record in records if record.get('reason') == 'compiler-artifact' and record.get('target', {}).get('name') == 'arcadia-studio' and record.get('executable')]
if len(artifacts) != 1:
raise RuntimeError('Expected exactly one desktop artifact')
binary = Path(artifacts[0]['executable'])
shutil.copyfile(binary, DEST / 'arcadia-studio.exe')
if sha(binary) != sha(DEST / 'arcadia-studio.exe'):
raise RuntimeError('Desktop preservation differs')
receipt['binary'] = {'path': str(binary), 'preserved': str(DEST / 'arcadia-studio.exe'), 'sha256': sha(binary), 'bytes': binary.stat().st_size, 'artifact': artifacts[0]}
write(name + '-result.json', receipt)
print(json.dumps(receipt), flush=True)
if result.returncode or not receipt['inputsUnchanged']:
raise SystemExit(result.returncode or 1)
write('complete.json', {'atUtc': now(), 'inputsUnchanged': verify(), 'gates': [name for name, _ in gates], 'scope': 'Windows headless tests/lint/timing/build; no native acceptance'})