#![cfg(feature = "inventory")]
mod common;
use common::{ExecuteValidateLoggerDatabase, LogDatabase};
use expect_test::expect;
use salsa::{Database as Db, DatabaseImpl as DbImpl, Durability, Setter};
#[cfg(not(miri))]
use test_log::test;
#[derive(Clone, Copy, Debug, PartialEq, Eq, salsa::Update)]
enum Value {
N(u8),
OutOfBounds,
TooManyIterations,
}
impl Value {
fn to_value(self) -> Option<u8> {
if let Self::N(val) = self {
Some(val)
} else {
None
}
}
}
#[salsa::input]
struct Inputs {
#[returns(ref)]
inputs: Vec<Input>,
}
impl Inputs {
fn values(self, db: &dyn Db) -> impl Iterator<Item = Value> + use<'_> {
self.inputs(db).iter().map(|input| input.eval(db))
}
}
#[derive(Clone)]
enum Input {
Value(Value),
UntrackedRead(Value),
MinIterate(Inputs),
MaxIterate(Inputs),
MinPanic(Inputs),
MaxPanic(Inputs),
Successor(Box<Input>),
SuccessorOrZero(Box<Input>),
}
impl Input {
fn eval(&self, db: &dyn Db) -> Value {
match *self {
Self::Value(value) => value,
Self::UntrackedRead(value) => {
db.report_untracked_read();
value
}
Self::MinIterate(inputs) => min_iterate(db, inputs),
Self::MaxIterate(inputs) => max_iterate(db, inputs),
Self::MinPanic(inputs) => min_panic(db, inputs),
Self::MaxPanic(inputs) => max_panic(db, inputs),
Self::Successor(ref input) => match input.eval(db) {
Value::N(num) => Value::N(num + 1),
other => other,
},
Self::SuccessorOrZero(ref input) => match input.eval(db) {
Value::N(num) => Value::N(num + 1),
_ => Value::N(0),
},
}
}
#[track_caller]
fn assert(&self, db: &dyn Db, expected: Value) {
assert_eq!(self.eval(db), expected)
}
#[track_caller]
fn assert_value(&self, db: &dyn Db, expected: u8) {
self.assert(db, Value::N(expected))
}
#[track_caller]
fn assert_bounds(&self, db: &dyn Db) {
self.assert(db, Value::OutOfBounds)
}
#[track_caller]
fn assert_count(&self, db: &dyn Db) {
self.assert(db, Value::TooManyIterations)
}
}
const MIN_VALUE: u8 = 10;
const MAX_VALUE: u8 = 245;
const MAX_ITERATIONS: u32 = 3;
fn cycle_recover(
_db: &dyn Db,
cycle: &salsa::Cycle,
last_provisional_value: &Value,
value: Value,
_inputs: Inputs,
) -> Value {
if &value == last_provisional_value {
value
} else if value
.to_value()
.is_some_and(|val| val <= MIN_VALUE || val >= MAX_VALUE)
{
Value::OutOfBounds
} else if cycle.iteration() > MAX_ITERATIONS {
Value::TooManyIterations
} else {
value
}
}
fn fold_values<F>(values: impl IntoIterator<Item = Value>, op: F) -> Value
where
F: Fn(u8, u8) -> u8,
{
values
.into_iter()
.fold(None, |accum, elem| {
let Some(accum) = accum else {
return Some(elem);
};
match (accum, elem) {
(Value::TooManyIterations, _) | (_, Value::TooManyIterations) => {
Some(Value::TooManyIterations)
}
(Value::OutOfBounds, _) | (_, Value::OutOfBounds) => Some(Value::OutOfBounds),
(Value::N(val1), Value::N(val2)) => Some(Value::N(op(val1, val2))),
}
})
.expect("inputs should not be empty")
}
#[salsa::tracked(cycle_fn=cycle_recover, cycle_initial=min_initial)]
fn min_iterate<'db>(db: &'db dyn Db, inputs: Inputs) -> Value {
fold_values(inputs.values(db), u8::min)
}
fn min_initial(_db: &dyn Db, _id: salsa::Id, _inputs: Inputs) -> Value {
Value::N(255)
}
#[salsa::tracked(cycle_fn=cycle_recover, cycle_initial=max_initial)]
fn max_iterate<'db>(db: &'db dyn Db, inputs: Inputs) -> Value {
fold_values(inputs.values(db), u8::max)
}
fn max_initial(_db: &dyn Db, _id: salsa::Id, _inputs: Inputs) -> Value {
Value::N(0)
}
#[salsa::tracked]
fn min_panic<'db>(db: &'db dyn Db, inputs: Inputs) -> Value {
fold_values(inputs.values(db), u8::min)
}
#[salsa::tracked]
fn max_panic<'db>(db: &'db dyn Db, inputs: Inputs) -> Value {
fold_values(inputs.values(db), u8::max)
}
fn untracked(num: u8) -> Input {
Input::UntrackedRead(Value::N(num))
}
fn value(num: u8) -> Input {
Input::Value(Value::N(num))
}
#[test]
#[should_panic(expected = "dependency graph cycle")]
fn self_panic() {
let mut db = DbImpl::new();
let a_in = Inputs::new(&db, vec![]);
let a = Input::MinPanic(a_in);
a_in.set_inputs(&mut db).to(vec![a.clone()]);
a.eval(&db);
}
#[test]
#[should_panic(expected = "dependency graph cycle")]
fn self_untracked_panic() {
let mut db = DbImpl::new();
let a_in = Inputs::new(&db, vec![]);
let a = Input::MinPanic(a_in);
a_in.set_inputs(&mut db).to(vec![untracked(10), a.clone()]);
a.eval(&db);
}
#[test]
fn self_converge_initial_value() {
let mut db = DbImpl::new();
let a_in = Inputs::new(&db, vec![]);
let a = Input::MinIterate(a_in);
a_in.set_inputs(&mut db).to(vec![a.clone()]);
a.assert_value(&db, 255);
}
#[test]
fn two_mixed_converge_initial_value() {
let mut db = DbImpl::new();
let a_in = Inputs::new(&db, vec![]);
let b_in = Inputs::new(&db, vec![]);
let a = Input::MinIterate(a_in);
let b = Input::MinPanic(b_in);
a_in.set_inputs(&mut db).to(vec![b]);
b_in.set_inputs(&mut db).to(vec![a.clone()]);
a.assert_value(&db, 255);
}
#[test]
#[should_panic(expected = "dependency graph cycle")]
fn two_mixed_panic() {
let mut db = DbImpl::new();
let a_in = Inputs::new(&db, vec![]);
let b_in = Inputs::new(&db, vec![]);
let a = Input::MinPanic(b_in);
let b = Input::MinIterate(a_in);
a_in.set_inputs(&mut db).to(vec![b]);
b_in.set_inputs(&mut db).to(vec![a.clone()]);
a.eval(&db);
}
#[test]
fn two_iterate_converge_initial_value() {
let mut db = DbImpl::new();
let a_in = Inputs::new(&db, vec![]);
let b_in = Inputs::new(&db, vec![]);
let a = Input::MinIterate(a_in);
let b = Input::MaxIterate(b_in);
a_in.set_inputs(&mut db).to(vec![b.clone()]);
b_in.set_inputs(&mut db).to(vec![a.clone()]);
a.assert_value(&db, 255);
b.assert_value(&db, 255);
}
#[test]
fn two_iterate_converge_initial_value_2() {
let mut db = DbImpl::new();
let a_in = Inputs::new(&db, vec![]);
let b_in = Inputs::new(&db, vec![]);
let a = Input::MaxIterate(a_in);
let b = Input::MinIterate(b_in);
a_in.set_inputs(&mut db).to(vec![b.clone()]);
b_in.set_inputs(&mut db).to(vec![a.clone()]);
a.assert_value(&db, 0);
b.assert_value(&db, 0);
}
#[test]
fn two_indirect_iterate_converge_initial_value() {
let mut db = DbImpl::new();
let a_in = Inputs::new(&db, vec![]);
let b_in = Inputs::new(&db, vec![]);
let c_in = Inputs::new(&db, vec![]);
let a = Input::MinPanic(a_in);
let b = Input::MinIterate(b_in);
let c = Input::MaxPanic(c_in);
a_in.set_inputs(&mut db).to(vec![b.clone()]);
b_in.set_inputs(&mut db).to(vec![c]);
c_in.set_inputs(&mut db).to(vec![b]);
a.assert_value(&db, 255);
}
#[test]
#[should_panic(expected = "dependency graph cycle")]
fn two_indirect_panic() {
let mut db = DbImpl::new();
let a_in = Inputs::new(&db, vec![]);
let b_in = Inputs::new(&db, vec![]);
let c_in = Inputs::new(&db, vec![]);
let a = Input::MinPanic(a_in);
let b = Input::MinPanic(b_in);
let c = Input::MaxIterate(c_in);
a_in.set_inputs(&mut db).to(vec![b.clone()]);
b_in.set_inputs(&mut db).to(vec![c]);
c_in.set_inputs(&mut db).to(vec![b]);
a.eval(&db);
}
#[test]
fn two_converge() {
let mut db = DbImpl::new();
let a_in = Inputs::new(&db, vec![]);
let b_in = Inputs::new(&db, vec![]);
let c_in = Inputs::new(&db, vec![]);
let a = Input::MinPanic(a_in);
let b = Input::MinIterate(b_in);
let c = Input::MaxPanic(c_in);
a_in.set_inputs(&mut db).to(vec![b.clone()]);
b_in.set_inputs(&mut db).to(vec![value(200), c]);
c_in.set_inputs(&mut db).to(vec![b]);
a.assert_value(&db, 200);
}
#[test]
fn two_fallback_count() {
let mut db = DbImpl::new();
let a_in = Inputs::new(&db, vec![]);
let b_in = Inputs::new(&db, vec![]);
let c_in = Inputs::new(&db, vec![]);
let a = Input::MaxPanic(a_in);
let b = Input::MaxIterate(b_in);
let c = Input::MaxPanic(c_in);
a_in.set_inputs(&mut db).to(vec![b.clone()]);
b_in.set_inputs(&mut db).to(vec![value(20), c]);
c_in.set_inputs(&mut db)
.to(vec![Input::Successor(Box::new(b))]);
a.assert_count(&db);
}
#[test]
fn two_fallback_diverge() {
let mut db = DbImpl::new();
let a_in = Inputs::new(&db, vec![]);
let b_in = Inputs::new(&db, vec![]);
let c_in = Inputs::new(&db, vec![]);
let a = Input::MaxPanic(a_in);
let b = Input::MaxIterate(b_in);
let c = Input::MaxPanic(c_in);
a_in.set_inputs(&mut db).to(vec![b.clone()]);
b_in.set_inputs(&mut db).to(vec![value(20), c.clone()]);
c_in.set_inputs(&mut db)
.to(vec![Input::SuccessorOrZero(Box::new(b))]);
a.assert_count(&db);
}
#[test]
fn two_fallback_value() {
let mut db = DbImpl::new();
let a_in = Inputs::new(&db, vec![]);
let b_in = Inputs::new(&db, vec![]);
let c_in = Inputs::new(&db, vec![]);
let a = Input::MaxPanic(a_in);
let b = Input::MaxIterate(b_in);
let c = Input::MaxPanic(c_in);
a_in.set_inputs(&mut db).to(vec![b.clone()]);
b_in.set_inputs(&mut db).to(vec![value(244), c]);
c_in.set_inputs(&mut db)
.to(vec![Input::Successor(Box::new(b))]);
a.assert_bounds(&db);
}
#[test]
fn three_fork_converge() {
let mut db = DbImpl::new();
let a_in = Inputs::new(&db, vec![]);
let b_in = Inputs::new(&db, vec![]);
let c_in = Inputs::new(&db, vec![]);
let a = Input::MinIterate(a_in);
let b = Input::MinPanic(b_in);
let c = Input::MinPanic(c_in);
a_in.set_inputs(&mut db).to(vec![b]);
b_in.set_inputs(&mut db).to(vec![a.clone(), c]);
c_in.set_inputs(&mut db).to(vec![value(25), a.clone()]);
a.assert_value(&db, 25);
}
#[test]
fn layered_converge() {
let mut db = DbImpl::new();
let a_in = Inputs::new(&db, vec![]);
let b_in = Inputs::new(&db, vec![]);
let c_in = Inputs::new(&db, vec![]);
let a = Input::MinIterate(a_in);
let b = Input::MinIterate(b_in);
let c = Input::MinPanic(c_in);
a_in.set_inputs(&mut db).to(vec![b.clone()]);
b_in.set_inputs(&mut db).to(vec![a.clone(), c]);
c_in.set_inputs(&mut db).to(vec![value(25), b]);
a.assert_value(&db, 25);
}
#[test]
fn layered_fallback_count() {
let mut db = DbImpl::new();
let a_in = Inputs::new(&db, vec![]);
let b_in = Inputs::new(&db, vec![]);
let c_in = Inputs::new(&db, vec![]);
let a = Input::MaxIterate(a_in);
let b = Input::MaxIterate(b_in);
let c = Input::MaxPanic(c_in);
a_in.set_inputs(&mut db).to(vec![b.clone()]);
b_in.set_inputs(&mut db).to(vec![a.clone(), c]);
c_in.set_inputs(&mut db)
.to(vec![value(25), Input::Successor(Box::new(b))]);
a.assert_count(&db);
}
#[test]
fn layered_fallback_value() {
let mut db = DbImpl::new();
let a_in = Inputs::new(&db, vec![]);
let b_in = Inputs::new(&db, vec![]);
let c_in = Inputs::new(&db, vec![]);
let a = Input::MaxIterate(a_in);
let b = Input::MaxIterate(b_in);
let c = Input::MaxPanic(c_in);
a_in.set_inputs(&mut db).to(vec![b.clone()]);
b_in.set_inputs(&mut db).to(vec![a.clone(), c]);
c_in.set_inputs(&mut db)
.to(vec![value(243), Input::Successor(Box::new(b))]);
a.assert_bounds(&db);
}
#[test]
fn nested_converge() {
let mut db = DbImpl::new();
let a_in = Inputs::new(&db, vec![]);
let b_in = Inputs::new(&db, vec![]);
let c_in = Inputs::new(&db, vec![]);
let a = Input::MinIterate(a_in);
let b = Input::MinIterate(b_in);
let c = Input::MinPanic(c_in);
a_in.set_inputs(&mut db).to(vec![b.clone()]);
b_in.set_inputs(&mut db).to(vec![c]);
c_in.set_inputs(&mut db).to(vec![value(25), a.clone(), b]);
a.assert_value(&db, 25);
}
#[test]
fn nested_inner_first_converge() {
let mut db = DbImpl::new();
let a_in = Inputs::new(&db, vec![]);
let b_in = Inputs::new(&db, vec![]);
let c_in = Inputs::new(&db, vec![]);
let a = Input::MinIterate(a_in);
let b = Input::MinIterate(b_in);
let c = Input::MinPanic(c_in);
a_in.set_inputs(&mut db).to(vec![b.clone()]);
b_in.set_inputs(&mut db).to(vec![c]);
c_in.set_inputs(&mut db).to(vec![value(25), b, a.clone()]);
a.assert_value(&db, 25);
}
#[test]
fn nested_fallback_count() {
let mut db = DbImpl::new();
let a_in = Inputs::new(&db, vec![]);
let b_in = Inputs::new(&db, vec![]);
let c_in = Inputs::new(&db, vec![]);
let a = Input::MaxIterate(a_in);
let b = Input::MaxIterate(b_in);
let c = Input::MaxPanic(c_in);
a_in.set_inputs(&mut db).to(vec![b.clone()]);
b_in.set_inputs(&mut db).to(vec![c]);
c_in.set_inputs(&mut db)
.to(vec![value(25), a.clone(), Input::Successor(Box::new(b))]);
a.assert_count(&db);
}
#[test]
fn nested_inner_first_fallback_count() {
let mut db = DbImpl::new();
let a_in = Inputs::new(&db, vec![]);
let b_in = Inputs::new(&db, vec![]);
let c_in = Inputs::new(&db, vec![]);
let a = Input::MaxIterate(a_in);
let b = Input::MaxIterate(b_in);
let c = Input::MaxPanic(c_in);
a_in.set_inputs(&mut db).to(vec![b.clone()]);
b_in.set_inputs(&mut db).to(vec![c]);
c_in.set_inputs(&mut db)
.to(vec![value(25), b, Input::Successor(Box::new(a.clone()))]);
a.assert_count(&db);
}
#[test]
fn nested_fallback_value() {
let mut db = DbImpl::new();
let a_in = Inputs::new(&db, vec![]);
let b_in = Inputs::new(&db, vec![]);
let c_in = Inputs::new(&db, vec![]);
let a = Input::MaxIterate(a_in);
let b = Input::MaxIterate(b_in);
let c = Input::MaxPanic(c_in);
a_in.set_inputs(&mut db).to(vec![b.clone()]);
b_in.set_inputs(&mut db).to(vec![c.clone()]);
c_in.set_inputs(&mut db).to(vec![
value(243),
a.clone(),
Input::Successor(Box::new(b.clone())),
]);
a.assert_bounds(&db);
b.assert_bounds(&db);
c.assert_bounds(&db);
}
#[test]
fn nested_inner_first_fallback_value() {
let mut db = DbImpl::new();
let a_in = Inputs::new(&db, vec![]);
let b_in = Inputs::new(&db, vec![]);
let c_in = Inputs::new(&db, vec![]);
let a = Input::MaxIterate(a_in);
let b = Input::MaxIterate(b_in);
let c = Input::MaxPanic(c_in);
a_in.set_inputs(&mut db).to(vec![b.clone()]);
b_in.set_inputs(&mut db).to(vec![c]);
c_in.set_inputs(&mut db)
.to(vec![value(243), b, Input::Successor(Box::new(a.clone()))]);
a.assert_bounds(&db);
}
#[test]
fn nested_double_converge() {
let mut db = DbImpl::new();
let a_in = Inputs::new(&db, vec![]);
let b_in = Inputs::new(&db, vec![]);
let c_in = Inputs::new(&db, vec![]);
let a = Input::MinIterate(a_in);
let b = Input::MinIterate(b_in);
let c = Input::MinPanic(c_in);
a_in.set_inputs(&mut db).to(vec![b.clone()]);
b_in.set_inputs(&mut db).to(vec![c, a.clone()]);
c_in.set_inputs(&mut db).to(vec![value(25), a.clone(), b]);
a.assert_value(&db, 25);
}
#[test]
fn cycle_becomes_non_cycle() {
let mut db = DbImpl::new();
let a_in = Inputs::new(&db, vec![]);
let b_in = Inputs::new(&db, vec![]);
let a = Input::MinIterate(a_in);
let b = Input::MinPanic(b_in);
a_in.set_inputs(&mut db).to(vec![b]);
b_in.set_inputs(&mut db).to(vec![a.clone()]);
a.assert_value(&db, 255);
b_in.set_inputs(&mut db).to(vec![value(30)]);
a.assert_value(&db, 30);
}
#[test]
fn non_cycle_becomes_cycle() {
let mut db = DbImpl::new();
let a_in = Inputs::new(&db, vec![]);
let b_in = Inputs::new(&db, vec![]);
let a = Input::MinIterate(a_in);
let b = Input::MinPanic(b_in);
a_in.set_inputs(&mut db).to(vec![b]);
b_in.set_inputs(&mut db).to(vec![value(30)]);
a.assert_value(&db, 30);
b_in.set_inputs(&mut db).to(vec![a.clone()]);
a.assert_value(&db, 255);
}
#[test]
fn nested_double_multiple_revisions() {
let mut db = DbImpl::new();
let a_in = Inputs::new(&db, vec![]);
let b_in = Inputs::new(&db, vec![]);
let c_in = Inputs::new(&db, vec![]);
let a = Input::MaxIterate(a_in);
let b = Input::MaxIterate(b_in);
let c = Input::MaxPanic(c_in);
a_in.set_inputs(&mut db).to(vec![b.clone()]);
b_in.set_inputs(&mut db).to(vec![c, a.clone()]);
c_in.set_inputs(&mut db).to(vec![
value(25),
a.clone(),
Input::Successor(Box::new(b.clone())),
]);
a.assert_count(&db);
tracing::info!("New revision, expect max value");
c_in.set_inputs(&mut db).to(vec![
value(243),
a.clone(),
Input::Successor(Box::new(b.clone())),
]);
a.assert_bounds(&db);
tracing::info!("New revision, expect converge");
c_in.set_inputs(&mut db)
.to(vec![value(240), a.clone(), b.clone()]);
a.assert_value(&db, 240);
tracing::info!("New revision, expect converge again");
a_in.set_inputs(&mut db).to(vec![b]);
a.assert_value(&db, 240);
}
#[test]
fn cycle_durability() {
let mut db = DbImpl::new();
let a_in = Inputs::new(&db, vec![]);
let b_in = Inputs::new(&db, vec![]);
let c_in = Inputs::new(&db, vec![]);
let a = Input::MinIterate(a_in);
let b = Input::MinIterate(b_in);
let c = Input::MinIterate(c_in);
a_in.set_inputs(&mut db)
.with_durability(Durability::LOW)
.to(vec![b.clone()]);
b_in.set_inputs(&mut db)
.with_durability(Durability::HIGH)
.to(vec![c]);
c_in.set_inputs(&mut db)
.with_durability(Durability::HIGH)
.to(vec![a.clone()]);
a.assert_value(&db, 255);
a_in.set_inputs(&mut db)
.with_durability(Durability::LOW)
.to(vec![value(45), b]);
a.assert_value(&db, 45);
}
#[test]
fn cycle_unchanged() {
let mut db = ExecuteValidateLoggerDatabase::default();
let a_in = Inputs::new(&db, vec![]);
let b_in = Inputs::new(&db, vec![]);
let c_in = Inputs::new(&db, vec![]);
let a = Input::MinPanic(a_in);
let b = Input::MinIterate(b_in);
let c = Input::MinPanic(c_in);
a_in.set_inputs(&mut db).to(vec![value(59), b.clone()]);
b_in.set_inputs(&mut db).to(vec![value(60), c]);
c_in.set_inputs(&mut db).to(vec![b.clone()]);
a.assert_value(&db, 59);
b.assert_value(&db, 60);
db.assert_logs_len(6);
a_in.set_inputs(&mut db).to(vec![value(45), b.clone()]);
b.assert_value(&db, 60);
db.assert_logs(expect![[r#"
[
"salsa_event(DidValidateMemoizedValue { database_key: min_iterate(Id(1)) })",
]"#]]);
a.assert_value(&db, 45);
}
#[test_log::test]
fn cycle_unchanged_nested() {
let mut db = ExecuteValidateLoggerDatabase::default();
let a_in = Inputs::new(&db, vec![]);
let b_in = Inputs::new(&db, vec![]);
let c_in = Inputs::new(&db, vec![]);
let d_in = Inputs::new(&db, vec![]);
let e_in = Inputs::new(&db, vec![]);
let a = Input::MinPanic(a_in);
let b = Input::MinIterate(b_in);
let c = Input::MinPanic(c_in);
let d = Input::MinIterate(d_in);
let e = Input::MinPanic(e_in);
a_in.set_inputs(&mut db).to(vec![value(59), b.clone()]);
b_in.set_inputs(&mut db).to(vec![value(60), c.clone()]);
c_in.set_inputs(&mut db).to(vec![d.clone()]);
d_in.set_inputs(&mut db)
.to(vec![value(61), b.clone(), e.clone()]);
e_in.set_inputs(&mut db).to(vec![d.clone()]);
a.assert_value(&db, 59);
b.assert_value(&db, 60);
db.assert_logs_len(14);
a_in.set_inputs(&mut db).to(vec![value(45), b.clone()]);
b.assert_value(&db, 60);
db.assert_logs(expect![[r#"
[
"salsa_event(DidValidateMemoizedValue { database_key: min_iterate(Id(1)) })",
]"#]]);
a.assert_value(&db, 45);
}
#[test_log::test]
fn cycle_unchanged_nested_intertwined() {
for i in 0..1 {
let mut db = ExecuteValidateLoggerDatabase::default();
let a_in = Inputs::new(&db, vec![]);
let b_in = Inputs::new(&db, vec![]);
let c_in = Inputs::new(&db, vec![]);
let d_in = Inputs::new(&db, vec![]);
let e_in = Inputs::new(&db, vec![]);
let a = Input::MinPanic(a_in);
let b = Input::MinIterate(b_in);
let c = Input::MinPanic(c_in);
let d = Input::MinIterate(d_in);
let e = Input::MinIterate(e_in);
a_in.set_inputs(&mut db).to(vec![value(59), b.clone()]);
b_in.set_inputs(&mut db).to(vec![value(60), c.clone()]);
c_in.set_inputs(&mut db).to(vec![d.clone(), e.clone()]);
d_in.set_inputs(&mut db)
.to(vec![value(61), b.clone(), e.clone()]);
e_in.set_inputs(&mut db).to(vec![d.clone()]);
a.assert_value(&db, 59);
b.assert_value(&db, 60);
if i == 1 {
c.assert_value(&db, 60);
d.assert_value(&db, 60);
e.assert_value(&db, 60);
}
db.assert_logs_len(14 + i);
a_in.set_inputs(&mut db).to(vec![value(45), b.clone()]);
b.assert_value(&db, 60);
db.assert_logs(expect![[r#"
[
"salsa_event(DidValidateMemoizedValue { database_key: min_iterate(Id(1)) })",
]"#]]);
a.assert_value(&db, 45);
}
}
#[test]
fn cycle_sibling_interference() {
let mut db = ExecuteValidateLoggerDatabase::default();
let a_in = Inputs::new(&db, vec![]); let b_in = Inputs::new(&db, vec![]); let c_in = Inputs::new(&db, vec![]); let d_in = Inputs::new(&db, vec![]); let a = Input::MinIterate(a_in);
let b = Input::MinIterate(b_in);
let c = Input::MinPanic(c_in);
let d = Input::MinPanic(d_in);
a_in.set_inputs(&mut db)
.to(vec![b.clone(), c.clone(), d.clone()]); b_in.set_inputs(&mut db).to(vec![a.clone()]); c_in.set_inputs(&mut db).to(vec![value(100)]); d_in.set_inputs(&mut db).to(vec![value(200)]);
a.assert_value(&db, 100);
b.assert_value(&db, 100);
c.assert_value(&db, 100);
d.assert_value(&db, 200);
db.clear_logs();
d_in.set_inputs(&mut db).to(vec![value(201)]);
a.assert_value(&db, 100);
db.assert_logs(expect![[r#"
[
"salsa_event(DidValidateMemoizedValue { database_key: min_panic(Id(2)) })",
"salsa_event(WillExecute { database_key: min_panic(Id(3)) })",
"salsa_event(WillExecute { database_key: min_iterate(Id(0)) })",
"salsa_event(WillExecute { database_key: min_iterate(Id(1)) })",
"salsa_event(WillIterateCycle { database_key: min_iterate(Id(0)), iteration_count: IterationCount(1) })",
"salsa_event(WillExecute { database_key: min_iterate(Id(1)) })",
"salsa_event(DidFinalizeCycle { database_key: min_iterate(Id(0)), iteration_count: IterationCount(1) })",
]"#]]);
}
#[test]
fn repeat_provisional_query() {
let mut db = ExecuteValidateLoggerDatabase::default();
let a_in = Inputs::new(&db, vec![]);
let b_in = Inputs::new(&db, vec![]);
let c_in = Inputs::new(&db, vec![]);
let a = Input::MinIterate(a_in);
let b = Input::MinPanic(b_in);
let c = Input::MinPanic(c_in);
a_in.set_inputs(&mut db).to(vec![value(59), b.clone()]);
b_in.set_inputs(&mut db)
.to(vec![value(60), c.clone(), c.clone(), c]);
c_in.set_inputs(&mut db).to(vec![a.clone()]);
a.assert_value(&db, 59);
db.assert_logs(expect![[r#"
[
"salsa_event(WillExecute { database_key: min_iterate(Id(0)) })",
"salsa_event(WillExecute { database_key: min_panic(Id(1)) })",
"salsa_event(WillExecute { database_key: min_panic(Id(2)) })",
"salsa_event(WillIterateCycle { database_key: min_iterate(Id(0)), iteration_count: IterationCount(1) })",
"salsa_event(WillExecute { database_key: min_panic(Id(1)) })",
"salsa_event(WillExecute { database_key: min_panic(Id(2)) })",
"salsa_event(DidFinalizeCycle { database_key: min_iterate(Id(0)), iteration_count: IterationCount(1) })",
]"#]]);
}
#[test]
fn repeat_provisional_query_incremental() {
let mut db = ExecuteValidateLoggerDatabase::default();
let a_in = Inputs::new(&db, vec![]);
let b_in = Inputs::new(&db, vec![]);
let c_in = Inputs::new(&db, vec![]);
let a = Input::MinIterate(a_in);
let b = Input::MinPanic(b_in);
let c = Input::MinPanic(c_in);
a_in.set_inputs(&mut db).to(vec![value(59), b.clone()]);
b_in.set_inputs(&mut db)
.to(vec![value(60), c.clone(), c.clone(), c]);
c_in.set_inputs(&mut db).to(vec![a.clone()]);
a.assert_value(&db, 59);
db.clear_logs();
c_in.set_inputs(&mut db).to(vec![a.clone()]);
a.assert_value(&db, 59);
db.assert_logs(expect![[r#"
[
"salsa_event(WillExecute { database_key: min_iterate(Id(0)) })",
"salsa_event(WillExecute { database_key: min_panic(Id(1)) })",
"salsa_event(WillExecute { database_key: min_panic(Id(2)) })",
"salsa_event(WillIterateCycle { database_key: min_iterate(Id(0)), iteration_count: IterationCount(1) })",
"salsa_event(WillExecute { database_key: min_panic(Id(1)) })",
"salsa_event(WillExecute { database_key: min_panic(Id(2)) })",
"salsa_event(DidFinalizeCycle { database_key: min_iterate(Id(0)), iteration_count: IterationCount(1) })",
]"#]]);
}
#[test]
fn repeat_query_participating_in_cycle() {
#[salsa::input]
struct Input {
value: u32,
}
#[salsa::interned]
struct Interned {
value: u32,
}
#[salsa::tracked(cycle_initial=initial)]
fn head(db: &dyn Db, input: Input) -> u32 {
let a = query_a(db, input);
a.min(2)
}
fn initial(_db: &dyn Db, _id: salsa::Id, _input: Input) -> u32 {
0
}
#[salsa::tracked]
fn query_a(db: &dyn Db, input: Input) -> u32 {
let _ = query_b(db, input);
query_hot(db, input)
}
#[salsa::tracked]
fn query_b(db: &dyn Db, input: Input) -> u32 {
let _ = query_c(db, input);
query_hot(db, input)
}
#[salsa::tracked]
fn query_c(db: &dyn Db, input: Input) -> u32 {
let _ = query_d(db, input);
query_hot(db, input)
}
#[salsa::tracked]
fn query_d(db: &dyn Db, input: Input) -> u32 {
query_hot(db, input)
}
#[salsa::tracked]
fn query_hot(db: &dyn Db, input: Input) -> u32 {
let value = head(db, input);
let _ = Interned::new(db, 2);
let _ = input.value(db);
value + 1
}
let mut db = ExecuteValidateLoggerDatabase::default();
let input = Input::new(&db, 1);
assert_eq!(head(&db, input), 2);
db.clear_logs();
input.set_value(&mut db).to(10);
assert_eq!(head(&db, input), 2);
db.assert_logs(expect![[r#"
[
"salsa_event(DidValidateInternedValue { key: Interned(Id(400)), revision: R2 })",
"salsa_event(WillExecute { database_key: head(Id(0)) })",
"salsa_event(WillExecute { database_key: query_a(Id(0)) })",
"salsa_event(WillExecute { database_key: query_b(Id(0)) })",
"salsa_event(WillExecute { database_key: query_c(Id(0)) })",
"salsa_event(WillExecute { database_key: query_d(Id(0)) })",
"salsa_event(WillExecute { database_key: query_hot(Id(0)) })",
"salsa_event(WillIterateCycle { database_key: head(Id(0)), iteration_count: IterationCount(1) })",
"salsa_event(WillExecute { database_key: query_a(Id(0)) })",
"salsa_event(WillExecute { database_key: query_b(Id(0)) })",
"salsa_event(WillExecute { database_key: query_c(Id(0)) })",
"salsa_event(WillExecute { database_key: query_d(Id(0)) })",
"salsa_event(WillExecute { database_key: query_hot(Id(0)) })",
"salsa_event(WillIterateCycle { database_key: head(Id(0)), iteration_count: IterationCount(2) })",
"salsa_event(WillExecute { database_key: query_a(Id(0)) })",
"salsa_event(WillExecute { database_key: query_b(Id(0)) })",
"salsa_event(WillExecute { database_key: query_c(Id(0)) })",
"salsa_event(WillExecute { database_key: query_d(Id(0)) })",
"salsa_event(WillExecute { database_key: query_hot(Id(0)) })",
"salsa_event(DidFinalizeCycle { database_key: head(Id(0)), iteration_count: IterationCount(2) })",
]"#]]);
}
#[test]
fn repeat_query_participating_in_cycle2() {
#[salsa::input]
struct Input {
value: u32,
}
#[salsa::interned]
struct Interned {
value: u32,
}
#[salsa::tracked(cycle_initial=initial)]
fn head(db: &dyn Db, input: Input) -> u32 {
let a = query_a(db, input);
a.min(2)
}
fn initial(_db: &dyn Db, _id: salsa::Id, _input: Input) -> u32 {
0
}
#[salsa::tracked(cycle_initial=initial)]
fn query_a(db: &dyn Db, input: Input) -> u32 {
let _ = query_hot(db, input);
query_b(db, input)
}
#[salsa::tracked]
fn query_b(db: &dyn Db, input: Input) -> u32 {
let _ = query_hot(db, input);
query_c(db, input)
}
#[salsa::tracked]
fn query_c(db: &dyn Db, input: Input) -> u32 {
let _ = query_hot(db, input);
query_d(db, input)
}
#[salsa::tracked]
fn query_d(db: &dyn Db, input: Input) -> u32 {
let _ = query_hot(db, input);
let value = head(db, input);
let _ = input.value(db);
value + 1
}
#[salsa::tracked]
fn query_hot(db: &dyn Db, input: Input) -> u32 {
let _ = Interned::new(db, 2);
let _ = head(db, input);
1
}
let mut db = ExecuteValidateLoggerDatabase::default();
let input = Input::new(&db, 1);
assert_eq!(head(&db, input), 2);
db.clear_logs();
input.set_value(&mut db).to(10);
assert_eq!(head(&db, input), 2);
db.assert_logs(expect![[r#"
[
"salsa_event(DidValidateInternedValue { key: Interned(Id(400)), revision: R2 })",
"salsa_event(WillExecute { database_key: head(Id(0)) })",
"salsa_event(WillExecute { database_key: query_a(Id(0)) })",
"salsa_event(WillExecute { database_key: query_hot(Id(0)) })",
"salsa_event(WillExecute { database_key: query_b(Id(0)) })",
"salsa_event(WillExecute { database_key: query_c(Id(0)) })",
"salsa_event(WillExecute { database_key: query_d(Id(0)) })",
"salsa_event(WillIterateCycle { database_key: head(Id(0)), iteration_count: IterationCount(1) })",
"salsa_event(WillExecute { database_key: query_a(Id(0)) })",
"salsa_event(WillExecute { database_key: query_hot(Id(0)) })",
"salsa_event(WillExecute { database_key: query_b(Id(0)) })",
"salsa_event(WillExecute { database_key: query_c(Id(0)) })",
"salsa_event(WillExecute { database_key: query_d(Id(0)) })",
"salsa_event(WillIterateCycle { database_key: head(Id(0)), iteration_count: IterationCount(2) })",
"salsa_event(WillExecute { database_key: query_a(Id(0)) })",
"salsa_event(WillExecute { database_key: query_hot(Id(0)) })",
"salsa_event(WillExecute { database_key: query_b(Id(0)) })",
"salsa_event(WillExecute { database_key: query_c(Id(0)) })",
"salsa_event(WillExecute { database_key: query_d(Id(0)) })",
"salsa_event(DidFinalizeCycle { database_key: head(Id(0)), iteration_count: IterationCount(2) })",
]"#]]);
}