Files
ARCADIA-Studio/crates/oa-desktop/src/app/source_rename_timing_tests.rs
T

348 lines
14 KiB
Rust

//! 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::<usize>(),
"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<Duration>>);
impl Samples {
fn time<T>(&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();
}