//! Manual timing through the real Document and App paths. No disk writes, //! executor polling, layout, GPU work or first-paint measurement occurs here. //! Register as a test child of app::source_rename to use its actual worker helpers. use super::*; use crate::source_workspace::Workspace; use oa_core::source_project::SourceBundle; use std::{ collections::BTreeMap, path::PathBuf, time::{Duration, Instant}, }; #[cfg(debug_assertions)] #[path = "source_rename_layout_timing_tests.rs"] mod layout_timing_tests; const OLD: &str = "oa_restore_work"; const NEW: &str = "oa_restore_pressure"; const DECLARATIONS: &str = "oa/activities/activity_oa_restore_work.arcadia"; const ORIGIN: &str = "traceability.arcadia"; struct Fixture { bundle: SourceBundle, project: Project, expected_bundle: SourceBundle, expected_project: Project, target: Id, cursor: text_editor::Cursor, } impl Fixture { fn load() -> Self { // The optional entry selects a parent-pinned copy of this exact fixture, // not an arbitrary performance generator or a mutable GUI destination. let entry = std::env::var_os("ARCADIA_RENAME_TIMING_ENTRY") .map(PathBuf::from) .unwrap_or_else(|| { PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("../../examples/release1/project.arcadia") }); let loaded = oa_core::source_files::load_project(&entry, LIMITS).unwrap(); let bundle = loaded.reachable_bundle(); assert_eq!( bundle.files.len(), 30, "use the preserved release1 walkthrough" ); let document = Document::from_bundle(bundle.clone(), LIMITS).unwrap(); assert_eq!( document.bundle(), &bundle, "fixture must need no stabilization/migration" ); assert!(!document.dirty()); let project = document.project().clone(); let target = Id::parse_str("1b91f8a7-0bdb-514e-b1cd-955658e09927").unwrap(); assert_eq!(project.elements[&target].alias, OLD); assert!(!project.identities().iter().any(|(_, alias)| *alias == NEW)); let mut expected_project = project.clone(); expected_project.elements.get_mut(&target).unwrap().alias = NEW.into(); // Independent literal source oracle, authored before running the rename. // Do not replace the alias substring in filenames or the trace's alias. let mut expected_bundle = bundle.clone(); let declarations = expected_bundle.files.get_mut(DECLARATIONS).unwrap(); assert_eq!( declarations.matches("oa_activity oa_restore_work ").count(), 1 ); assert_eq!(declarations.matches("parent @oa_restore_work;").count(), 4); *declarations = declarations .replace( "oa_activity oa_restore_work ", "oa_activity oa_restore_pressure ", ) .replace("parent @oa_restore_work;", "parent @oa_restore_pressure;"); let traces = expected_bundle.files.get_mut(ORIGIN).unwrap(); assert_eq!(traces.matches("oa @oa_restore_work;").count(), 1); *traces = traces.replace("oa @oa_restore_work;", "oa @oa_restore_pressure;"); let source = &bundle.files[ORIGIN]; let start = source.rfind("@oa_restore_work;").unwrap() + 1; let position = |offset| { let prefix = &source[..offset]; text_editor::Position { line: prefix.bytes().filter(|byte| *byte == b'\n').count(), column: prefix .rsplit_once('\n') .map_or(prefix.len(), |(_, tail)| tail.len()), } }; let cursor = text_editor::Cursor { position: position(start), selection: Some(position(start + OLD.len())), }; assert!(nav::exact_cursor(source, cursor)); println!( "{}", serde_json::json!({ "record": "fixture", "entry": entry, "reachable_files": bundle.files.len(), "source_bytes": bundle.files.values().map(String::len).sum::(), "identities": project.identities().len(), "active_file": ORIGIN, "active_file_bytes": source.len(), "old": OLD, "new": NEW, "warmups": 1, "measured_trials": 5, "debug_assertions": cfg!(debug_assertions), "scope": "real worker helpers, guarded Document commit, App sync and Confirm update; no task polling or rendering" }) ); Self { bundle, project, expected_bundle, expected_project, target, cursor, } } fn app(&self) -> App { let mut app = App::with_project(Project::empty()); app.document = Document::from_bundle(self.bundle.clone(), LIMITS).unwrap(); app.source_workspace = Workspace::new(app.document.bundle(), true); app.source_workspace.active = ORIGIN.into(); app.selected = app.project().id; app.page = Page::Text; app.sync(); app.source_editor.move_to(self.cursor); assert_eq!(app.project(), &self.project); assert_eq!(app.document.bundle(), &self.bundle); assert_eq!(app.document.revision(), 0); assert!(!app.document.dirty() && !app.document.can_undo() && !app.document.can_redo()); assert!(app.rename_admission().is_ok()); app } fn check_candidate(&self, prepared: &PreparedAliasRename) { assert!(prepared.changed()); assert_eq!(prepared.before().bundle, self.bundle); assert_eq!(prepared.before().decoded.project, self.project); assert_eq!(prepared.after().bundle, self.expected_bundle); assert_eq!(prepared.after().decoded.project, self.expected_project); } fn check_adopted(&self, app: &mut App, candidate_address: *const CompiledSourceProject) { assert!( std::ptr::eq(app.document.compiled(), candidate_address), "adopt exact candidate allocation" ); assert_eq!(app.project(), &self.expected_project); assert_eq!(app.document.bundle(), &self.expected_bundle); assert_eq!(app.source_workspace.bundle(), self.expected_bundle); assert_eq!(app.source_editor.text(), self.expected_bundle.files[ORIGIN]); assert_eq!(app.document.revision(), 1); assert!(app.document.dirty() && app.document.can_undo() && !app.document.can_redo()); assert!(app.document.undo()); assert_eq!(app.project(), &self.project); assert_eq!(app.document.bundle(), &self.bundle); assert!( !app.document.dirty(), "live known saved baseline remains authoritative" ); assert!( !app.document.can_undo(), "exactly one preceding model transaction" ); assert!(app.document.redo()); assert_eq!(app.project(), &self.expected_project); assert_eq!(app.document.bundle(), &self.expected_bundle); assert!(std::ptr::eq(app.document.compiled(), candidate_address)); } } #[derive(Default)] struct Samples(BTreeMap<&'static str, Vec>); impl Samples { fn time(&mut self, phase: &'static str, trial: usize, action: impl FnOnce() -> T) -> T { let started = Instant::now(); let result = action(); let elapsed = started.elapsed(); // Printing, assertions and dropping the returned value are outside this // interval. Full Confirm still includes production's own local drops. println!( "{}", serde_json::json!({ "record": "timing", "phase": phase, "trial": trial, "warmup": trial == 0, "elapsed_ns": elapsed.as_nanos().to_string(), "elapsed_ms": elapsed.as_secs_f64() * 1000.0 }) ); if trial != 0 { self.0.entry(phase).or_default().push(elapsed); } result } fn report(mut self) { for (phase, values) in &mut self.0 { assert_eq!(values.len(), 5); values.sort(); println!( "{}", serde_json::json!({ "record": "summary", "phase": phase, "samples": values.len(), "min_ms": values[0].as_secs_f64() * 1000.0, "median_ms": values[2].as_secs_f64() * 1000.0, "max_ms": values[4].as_secs_f64() * 1000.0 }) ); } } } fn resolve(app: &App, context: &Context) -> Target { let snapshot = app .with_source_input(|input| query::admit_owned(input, query::Limits::default())) .unwrap() .capture() .unwrap(); let offset = nav::cursor_offset(app.source_workspace.text(), context.begin.cursor).unwrap(); resolve_target(&snapshot, context, offset).unwrap() } fn active_job(app: &App) -> Job { let form = app.source_rename.form.as_ref().unwrap(); let id = form.pending.unwrap(); assert_eq!(app.source_rename.in_flight, Some(id)); Job { id, invocation: form.invocation, generation: form.generation, context: form.context.clone(), } } fn separate_phases(fixture: &Fixture, samples: &mut Samples, trial: usize) { let mut app = fixture.app(); let target = resolve(&app, &app.rename_context()); assert_eq!(target.id, fixture.target); let request = samples .time("document_request_mint", trial, || { app.document.alias_rename_request(target.id, NEW.into()) }) .unwrap(); let prepared = samples .time("worker_document_prepare", trial, || request.prepare()) .unwrap(); fixture.check_candidate(&prepared); let approval = samples .time("worker_preview_and_origin", trial, || { prepare_approval(prepared, &target, ORIGIN) }) .unwrap(); assert_eq!(approval.preview.files().len(), 2); assert_eq!(approval.preview.changed_line_count(), 6); let candidate_address = approval.prepared.after() as *const CompiledSourceProject; // Same-destination save completion is a legitimate live update after Preview. app.document.mark_saved(app.document.snapshot()); let changed = samples .time("document_guarded_commit", trial, || { app.document.commit_alias_rename(&approval.prepared) }) .unwrap(); assert!(changed); samples.time("app_sync", trial, || app.sync()); fixture.check_adopted(&mut app, candidate_address); } fn whole_confirm(fixture: &Fixture, samples: &mut Samples, trial: usize) { let mut app = fixture.app(); let begin = app.source_rename_message(); // These Tasks are deliberately not polled: deterministic worker helpers // supply the same results through the real readiness messages below. drop(app.update(begin)); let target_job = active_job(&app); let target = Arc::new(resolve(&app, &target_job.context)); assert_eq!(target.id, fixture.target); drop(app.update(Message::SourceRenameReady(Completion { job: target_job, payload: Payload::Target(Ok(target.clone())), }))); let invocation = app.source_rename.form.as_ref().unwrap().invocation; drop(app.update(Message::SourceRename(Event::Input(invocation, NEW.into())))); let generation = app.source_rename.form.as_ref().unwrap().generation; drop(app.update(Message::SourceRename(Event::Preview( invocation, generation, )))); let preview_job = active_job(&app); let prepared = app .document .alias_rename_request(target.id, NEW.into()) .unwrap() .prepare() .unwrap(); fixture.check_candidate(&prepared); let approval = Arc::new(prepare_approval(prepared, &target, ORIGIN).unwrap()); drop(target); let candidate_address = approval.prepared.after() as *const CompiledSourceProject; let expected_cursor = approval.cursor; let approval_id = approval.id; drop(app.update(Message::SourceRenameReady(Completion { job: preview_job, payload: Payload::Preview(Ok(approval)), }))); assert_eq!( app.source_rename .form .as_ref() .unwrap() .approval .as_ref() .unwrap() .id, approval_id ); app.document.mark_saved(app.document.snapshot()); let confirm = Message::SourceRename(Event::Confirm(invocation, generation, approval_id)); let task = samples.time("app_confirm_update", trial, || app.update(confirm)); samples.time("confirm_returned_task_drop_without_polling", trial, || { drop(task) }); assert!(!app.source_rename.open()); assert!(app.source_rename.in_flight.is_none()); assert!(app.source_rename_undo_message().is_some()); assert_eq!(app.source_editor.cursor(), expected_cursor); fixture.check_adopted(&mut app, candidate_address); } #[test] #[ignore = "manual Windows dev/debug rename latency trial; run alone with --ignored --nocapture --test-threads=1"] fn accepted_release1_prepare_commit_sync_and_confirm_timing() { let fixture = Fixture::load(); let mut samples = Samples::default(); for trial in 0..=5 { println!( "{}", serde_json::json!({ "record": "order", "trial": trial, "warmup": trial == 0, "separate_phases_first": trial % 2 == 0 }) ); if trial % 2 == 0 { separate_phases(&fixture, &mut samples, trial); whole_confirm(&fixture, &mut samples, trial); } else { whole_confirm(&fixture, &mut samples, trial); separate_phases(&fixture, &mut samples, trial); } } samples.report(); }