rust-analyzer/crates/ra-salsa/tests/on_demand_inputs.rs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

148 lines
5.4 KiB
Rust
Raw Normal View History

2024-02-07 15:29:46 +00:00
//! Test that "on-demand" input pattern works.
//!
//! On-demand inputs are inputs computed lazily on the fly. They are simulated
//! via a b query with zero inputs, which uses `add_synthetic_read` to
//! tweak durability and `invalidate` to clear the input.
2024-02-07 15:53:27 +00:00
#![allow(clippy::disallowed_types, clippy::type_complexity)]
2024-02-07 15:29:46 +00:00
use std::{cell::RefCell, collections::HashMap, rc::Rc};
2024-10-04 19:48:11 +00:00
use ra_salsa::{Database as _, Durability, EventKind};
2024-02-07 15:29:46 +00:00
2024-10-04 19:48:11 +00:00
#[ra_salsa::query_group(QueryGroupStorage)]
trait QueryGroup: ra_salsa::Database + AsRef<HashMap<u32, u32>> {
2024-02-07 15:29:46 +00:00
fn a(&self, x: u32) -> u32;
fn b(&self, x: u32) -> u32;
fn c(&self, x: u32) -> u32;
}
fn a(db: &dyn QueryGroup, x: u32) -> u32 {
2024-02-07 15:30:00 +00:00
let durability = if x % 2 == 0 { Durability::LOW } else { Durability::HIGH };
2024-02-07 15:29:46 +00:00
db.salsa_runtime().report_synthetic_read(durability);
let external_state: &HashMap<u32, u32> = db.as_ref();
external_state[&x]
}
fn b(db: &dyn QueryGroup, x: u32) -> u32 {
db.a(x)
}
fn c(db: &dyn QueryGroup, x: u32) -> u32 {
db.b(x)
}
2024-10-04 19:48:11 +00:00
#[ra_salsa::database(QueryGroupStorage)]
2024-02-07 15:29:46 +00:00
#[derive(Default)]
struct Database {
2024-10-04 19:48:11 +00:00
storage: ra_salsa::Storage<Self>,
2024-02-07 15:29:46 +00:00
external_state: HashMap<u32, u32>,
2024-10-04 19:48:11 +00:00
on_event: Option<Box<dyn Fn(&Database, ra_salsa::Event)>>,
2024-02-07 15:29:46 +00:00
}
2024-10-04 19:48:11 +00:00
impl ra_salsa::Database for Database {
fn salsa_event(&self, event: ra_salsa::Event) {
2024-02-07 15:29:46 +00:00
if let Some(cb) = &self.on_event {
cb(self, event)
}
}
}
impl AsRef<HashMap<u32, u32>> for Database {
fn as_ref(&self) -> &HashMap<u32, u32> {
&self.external_state
}
}
#[test]
fn on_demand_input_works() {
let mut db = Database::default();
db.external_state.insert(1, 10);
assert_eq!(db.b(1), 10);
assert_eq!(db.a(1), 10);
// We changed external state, but haven't signaled about this yet,
// so we expect to see the old answer
db.external_state.insert(1, 92);
assert_eq!(db.b(1), 10);
assert_eq!(db.a(1), 10);
AQuery.in_db_mut(&mut db).invalidate(&1);
assert_eq!(db.b(1), 92);
assert_eq!(db.a(1), 92);
// Downstream queries should also be rerun if we call `a` first.
db.external_state.insert(1, 50);
AQuery.in_db_mut(&mut db).invalidate(&1);
assert_eq!(db.a(1), 50);
assert_eq!(db.b(1), 50);
}
#[test]
fn on_demand_input_durability() {
let mut db = Database::default();
let events = Rc::new(RefCell::new(vec![]));
db.on_event = Some(Box::new({
let events = events.clone();
move |db, event| {
if let EventKind::WillCheckCancellation = event.kind {
// these events are not interesting
} else {
events.borrow_mut().push(format!("{:?}", event.debug(db)))
}
}
}));
events.replace(vec![]);
db.external_state.insert(1, 10);
db.external_state.insert(2, 20);
assert_eq!(db.b(1), 10);
assert_eq!(db.b(2), 20);
2024-02-07 15:30:20 +00:00
expect_test::expect![[r#"
RefCell {
value: [
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::BQuery::b(1) } }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::AQuery::a(1) } }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::BQuery::b(2) } }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::AQuery::a(2) } }",
2024-02-07 15:30:20 +00:00
],
}
"#]].assert_debug_eq(&events);
2024-02-07 15:29:46 +00:00
db.synthetic_write(Durability::LOW);
2024-02-07 15:29:46 +00:00
events.replace(vec![]);
assert_eq!(db.c(1), 10);
assert_eq!(db.c(2), 20);
// Re-execute `a(2)` because that has low durability, but not `a(1)`
2024-02-07 15:30:20 +00:00
expect_test::expect![[r#"
RefCell {
value: [
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::CQuery::c(1) } }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: on_demand_inputs::BQuery::b(1) } }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::CQuery::c(2) } }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::AQuery::a(2) } }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: on_demand_inputs::BQuery::b(2) } }",
2024-02-07 15:30:20 +00:00
],
}
"#]].assert_debug_eq(&events);
2024-02-07 15:29:46 +00:00
db.synthetic_write(Durability::HIGH);
2024-02-07 15:29:46 +00:00
events.replace(vec![]);
assert_eq!(db.c(1), 10);
assert_eq!(db.c(2), 20);
// Re-execute both `a(1)` and `a(2)`, but we don't re-execute any `b` queries as the
// result didn't actually change.
2024-02-07 15:30:20 +00:00
expect_test::expect![[r#"
RefCell {
value: [
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::AQuery::a(1) } }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: on_demand_inputs::CQuery::c(1) } }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: WillExecute { database_key: on_demand_inputs::AQuery::a(2) } }",
"Event { runtime_id: RuntimeId { counter: 0 }, kind: DidValidateMemoizedValue { database_key: on_demand_inputs::CQuery::c(2) } }",
2024-02-07 15:30:20 +00:00
],
}
"#]].assert_debug_eq(&events);
2024-02-07 15:29:46 +00:00
}