461 lines
14 KiB
Rust
461 lines
14 KiB
Rust
use react::*;
|
|
|
|
#[test]
|
|
fn input_cells_have_a_value() {
|
|
let mut reactor = Reactor::new();
|
|
let input = reactor.create_input(10);
|
|
assert_eq!(reactor.value(CellId::Input(input)), Some(10));
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn an_input_cells_value_can_be_set() {
|
|
let mut reactor = Reactor::new();
|
|
let input = reactor.create_input(4);
|
|
assert!(reactor.set_value(input, 20));
|
|
assert_eq!(reactor.value(CellId::Input(input)), Some(20));
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn error_setting_a_nonexistent_input_cell() {
|
|
let mut dummy_reactor = Reactor::new();
|
|
let input = dummy_reactor.create_input(1);
|
|
assert!(!Reactor::new().set_value(input, 0));
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn compute_cells_calculate_initial_value() {
|
|
let mut reactor = Reactor::new();
|
|
let input = reactor.create_input(1);
|
|
let output = reactor
|
|
.create_compute(&[CellId::Input(input)], |v| v[0] + 1)
|
|
.unwrap();
|
|
assert_eq!(reactor.value(CellId::Compute(output)), Some(2));
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn compute_cells_take_inputs_in_the_right_order() {
|
|
let mut reactor = Reactor::new();
|
|
let one = reactor.create_input(1);
|
|
let two = reactor.create_input(2);
|
|
let output = reactor
|
|
.create_compute(&[CellId::Input(one), CellId::Input(two)], |v| {
|
|
v[0] + v[1] * 10
|
|
})
|
|
.unwrap();
|
|
assert_eq!(reactor.value(CellId::Compute(output)), Some(21));
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn error_creating_compute_cell_if_input_doesnt_exist() {
|
|
let mut dummy_reactor = Reactor::new();
|
|
let input = dummy_reactor.create_input(1);
|
|
assert_eq!(
|
|
Reactor::new().create_compute(&[CellId::Input(input)], |_| 0),
|
|
Err(CellId::Input(input))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn do_not_break_cell_if_creating_compute_cell_with_valid_and_invalid_input() {
|
|
let mut dummy_reactor = Reactor::new();
|
|
let _ = dummy_reactor.create_input(1);
|
|
let dummy_cell = dummy_reactor.create_input(2);
|
|
let mut reactor = Reactor::new();
|
|
let input = reactor.create_input(1);
|
|
assert_eq!(
|
|
reactor.create_compute(&[CellId::Input(input), CellId::Input(dummy_cell)], |_| 0),
|
|
Err(CellId::Input(dummy_cell))
|
|
);
|
|
assert!(reactor.set_value(input, 5));
|
|
assert_eq!(reactor.value(CellId::Input(input)), Some(5));
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn compute_cells_update_value_when_dependencies_are_changed() {
|
|
let mut reactor = Reactor::new();
|
|
let input = reactor.create_input(1);
|
|
let output = reactor
|
|
.create_compute(&[CellId::Input(input)], |v| v[0] + 1)
|
|
.unwrap();
|
|
assert_eq!(reactor.value(CellId::Compute(output)), Some(2));
|
|
assert!(reactor.set_value(input, 3));
|
|
assert_eq!(reactor.value(CellId::Compute(output)), Some(4));
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn compute_cells_can_depend_on_other_compute_cells() {
|
|
let mut reactor = Reactor::new();
|
|
let input = reactor.create_input(1);
|
|
let times_two = reactor
|
|
.create_compute(&[CellId::Input(input)], |v| v[0] * 2)
|
|
.unwrap();
|
|
let times_thirty = reactor
|
|
.create_compute(&[CellId::Input(input)], |v| v[0] * 30)
|
|
.unwrap();
|
|
let output = reactor
|
|
.create_compute(
|
|
&[CellId::Compute(times_two), CellId::Compute(times_thirty)],
|
|
|v| v[0] + v[1],
|
|
)
|
|
.unwrap();
|
|
assert_eq!(reactor.value(CellId::Compute(output)), Some(32));
|
|
assert!(reactor.set_value(input, 3));
|
|
assert_eq!(reactor.value(CellId::Compute(output)), Some(96));
|
|
}
|
|
|
|
/// A CallbackRecorder helps tests whether callbacks get called correctly.
|
|
/// You'll see it used in tests that deal with callbacks.
|
|
/// The names should be descriptive enough so that the tests make sense,
|
|
/// so it's not necessary to fully understand the implementation,
|
|
/// though you are welcome to.
|
|
struct CallbackRecorder {
|
|
// Note that this `Cell` is https://doc.rust-lang.org/std/cell/
|
|
// a mechanism to allow internal mutability,
|
|
// distinct from the cells (input cells, compute cells) in the reactor
|
|
value: std::cell::Cell<Option<i32>>,
|
|
}
|
|
|
|
impl CallbackRecorder {
|
|
fn new() -> Self {
|
|
CallbackRecorder {
|
|
value: std::cell::Cell::new(None),
|
|
}
|
|
}
|
|
|
|
fn expect_to_have_been_called_with(&self, v: i32) {
|
|
assert_ne!(
|
|
self.value.get(),
|
|
None,
|
|
"Callback was not called, but should have been"
|
|
);
|
|
assert_eq!(
|
|
self.value.replace(None),
|
|
Some(v),
|
|
"Callback was called with incorrect value"
|
|
);
|
|
}
|
|
|
|
fn expect_not_to_have_been_called(&self) {
|
|
assert_eq!(
|
|
self.value.get(),
|
|
None,
|
|
"Callback was called, but should not have been"
|
|
);
|
|
}
|
|
|
|
fn callback_called(&self, v: i32) {
|
|
assert_eq!(
|
|
self.value.replace(Some(v)),
|
|
None,
|
|
"Callback was called too many times; can't be called with {}",
|
|
v
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn compute_cells_fire_callbacks() {
|
|
let cb = CallbackRecorder::new();
|
|
let mut reactor = Reactor::new();
|
|
let input = reactor.create_input(1);
|
|
let output = reactor
|
|
.create_compute(&[CellId::Input(input)], |v| v[0] + 1)
|
|
.unwrap();
|
|
assert!(reactor
|
|
.add_callback(output, |v| cb.callback_called(v))
|
|
.is_some());
|
|
assert!(reactor.set_value(input, 3));
|
|
cb.expect_to_have_been_called_with(4);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn error_adding_callback_to_nonexistent_cell() {
|
|
let mut dummy_reactor = Reactor::new();
|
|
let input = dummy_reactor.create_input(1);
|
|
let output = dummy_reactor
|
|
.create_compute(&[CellId::Input(input)], |_| 0)
|
|
.unwrap();
|
|
assert_eq!(
|
|
Reactor::new().add_callback(output, |_: u32| println!("hi")),
|
|
None
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn error_removing_callback_from_nonexisting_cell() {
|
|
let mut dummy_reactor = Reactor::new();
|
|
let dummy_input = dummy_reactor.create_input(1);
|
|
let _ = dummy_reactor
|
|
.create_compute(&[CellId::Input(dummy_input)], |_| 0)
|
|
.unwrap();
|
|
let dummy_output = dummy_reactor
|
|
.create_compute(&[CellId::Input(dummy_input)], |_| 0)
|
|
.unwrap();
|
|
|
|
let mut reactor = Reactor::new();
|
|
let input = reactor.create_input(1);
|
|
let output = reactor
|
|
.create_compute(&[CellId::Input(input)], |_| 0)
|
|
.unwrap();
|
|
let callback = reactor.add_callback(output, |_| ()).unwrap();
|
|
assert_eq!(
|
|
reactor.remove_callback(dummy_output, callback),
|
|
Err(RemoveCallbackError::NonexistentCell)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn callbacks_only_fire_on_change() {
|
|
let cb = CallbackRecorder::new();
|
|
let mut reactor = Reactor::new();
|
|
let input = reactor.create_input(1);
|
|
let output = reactor
|
|
.create_compute(
|
|
&[CellId::Input(input)],
|
|
|v| if v[0] < 3 { 111 } else { 222 },
|
|
)
|
|
.unwrap();
|
|
assert!(reactor
|
|
.add_callback(output, |v| cb.callback_called(v))
|
|
.is_some());
|
|
|
|
assert!(reactor.set_value(input, 2));
|
|
cb.expect_not_to_have_been_called();
|
|
assert!(reactor.set_value(input, 4));
|
|
cb.expect_to_have_been_called_with(222);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn callbacks_can_be_called_multiple_times() {
|
|
let cb = CallbackRecorder::new();
|
|
let mut reactor = Reactor::new();
|
|
let input = reactor.create_input(1);
|
|
let output = reactor
|
|
.create_compute(&[CellId::Input(input)], |v| v[0] + 1)
|
|
.unwrap();
|
|
assert!(reactor
|
|
.add_callback(output, |v| cb.callback_called(v))
|
|
.is_some());
|
|
|
|
assert!(reactor.set_value(input, 2));
|
|
cb.expect_to_have_been_called_with(3);
|
|
assert!(reactor.set_value(input, 3));
|
|
cb.expect_to_have_been_called_with(4);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn callbacks_can_be_called_from_multiple_cells() {
|
|
let cb1 = CallbackRecorder::new();
|
|
let cb2 = CallbackRecorder::new();
|
|
let mut reactor = Reactor::new();
|
|
let input = reactor.create_input(1);
|
|
let plus_one = reactor
|
|
.create_compute(&[CellId::Input(input)], |v| v[0] + 1)
|
|
.unwrap();
|
|
let minus_one = reactor
|
|
.create_compute(&[CellId::Input(input)], |v| v[0] - 1)
|
|
.unwrap();
|
|
assert!(reactor
|
|
.add_callback(plus_one, |v| cb1.callback_called(v))
|
|
.is_some());
|
|
assert!(reactor
|
|
.add_callback(minus_one, |v| cb2.callback_called(v))
|
|
.is_some());
|
|
|
|
assert!(reactor.set_value(input, 10));
|
|
cb1.expect_to_have_been_called_with(11);
|
|
cb2.expect_to_have_been_called_with(9);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn callbacks_can_be_added_and_removed() {
|
|
let cb1 = CallbackRecorder::new();
|
|
let cb2 = CallbackRecorder::new();
|
|
let cb3 = CallbackRecorder::new();
|
|
|
|
let mut reactor = Reactor::new();
|
|
let input = reactor.create_input(11);
|
|
let output = reactor
|
|
.create_compute(&[CellId::Input(input)], |v| v[0] + 1)
|
|
.unwrap();
|
|
|
|
let callback = reactor
|
|
.add_callback(output, |v| cb1.callback_called(v))
|
|
.unwrap();
|
|
assert!(reactor
|
|
.add_callback(output, |v| cb2.callback_called(v))
|
|
.is_some());
|
|
|
|
assert!(reactor.set_value(input, 31));
|
|
cb1.expect_to_have_been_called_with(32);
|
|
cb2.expect_to_have_been_called_with(32);
|
|
|
|
assert!(reactor.remove_callback(output, callback).is_ok());
|
|
assert!(reactor
|
|
.add_callback(output, |v| cb3.callback_called(v))
|
|
.is_some());
|
|
|
|
assert!(reactor.set_value(input, 41));
|
|
cb1.expect_not_to_have_been_called();
|
|
cb2.expect_to_have_been_called_with(42);
|
|
cb3.expect_to_have_been_called_with(42);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn removing_a_callback_multiple_times_doesnt_interfere_with_other_callbacks() {
|
|
let cb1 = CallbackRecorder::new();
|
|
let cb2 = CallbackRecorder::new();
|
|
|
|
let mut reactor = Reactor::new();
|
|
let input = reactor.create_input(1);
|
|
let output = reactor
|
|
.create_compute(&[CellId::Input(input)], |v| v[0] + 1)
|
|
.unwrap();
|
|
let callback = reactor
|
|
.add_callback(output, |v| cb1.callback_called(v))
|
|
.unwrap();
|
|
assert!(reactor
|
|
.add_callback(output, |v| cb2.callback_called(v))
|
|
.is_some());
|
|
// We want the first remove to be Ok, but the others should be errors.
|
|
assert!(reactor.remove_callback(output, callback).is_ok());
|
|
for _ in 1..5 {
|
|
assert_eq!(
|
|
reactor.remove_callback(output, callback),
|
|
Err(RemoveCallbackError::NonexistentCallback)
|
|
);
|
|
}
|
|
|
|
assert!(reactor.set_value(input, 2));
|
|
cb1.expect_not_to_have_been_called();
|
|
cb2.expect_to_have_been_called_with(3);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn callbacks_should_only_be_called_once_even_if_multiple_dependencies_change() {
|
|
let cb = CallbackRecorder::new();
|
|
let mut reactor = Reactor::new();
|
|
let input = reactor.create_input(1);
|
|
let plus_one = reactor
|
|
.create_compute(&[CellId::Input(input)], |v| v[0] + 1)
|
|
.unwrap();
|
|
let minus_one1 = reactor
|
|
.create_compute(&[CellId::Input(input)], |v| v[0] - 1)
|
|
.unwrap();
|
|
let minus_one2 = reactor
|
|
.create_compute(&[CellId::Compute(minus_one1)], |v| v[0] - 1)
|
|
.unwrap();
|
|
let output = reactor
|
|
.create_compute(
|
|
&[CellId::Compute(plus_one), CellId::Compute(minus_one2)],
|
|
|v| v[0] * v[1],
|
|
)
|
|
.unwrap();
|
|
assert!(reactor
|
|
.add_callback(output, |v| cb.callback_called(v))
|
|
.is_some());
|
|
assert!(reactor.set_value(input, 4));
|
|
cb.expect_to_have_been_called_with(10);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn callbacks_should_not_be_called_if_dependencies_change_but_output_value_doesnt_change() {
|
|
let cb = CallbackRecorder::new();
|
|
let mut reactor = Reactor::new();
|
|
let input = reactor.create_input(1);
|
|
let plus_one = reactor
|
|
.create_compute(&[CellId::Input(input)], |v| v[0] + 1)
|
|
.unwrap();
|
|
let minus_one = reactor
|
|
.create_compute(&[CellId::Input(input)], |v| v[0] - 1)
|
|
.unwrap();
|
|
let always_two = reactor
|
|
.create_compute(
|
|
&[CellId::Compute(plus_one), CellId::Compute(minus_one)],
|
|
|v| v[0] - v[1],
|
|
)
|
|
.unwrap();
|
|
assert!(reactor
|
|
.add_callback(always_two, |v| cb.callback_called(v))
|
|
.is_some());
|
|
for i in 2..5 {
|
|
assert!(reactor.set_value(input, i));
|
|
cb.expect_not_to_have_been_called();
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn test_adder_with_boolean_values() {
|
|
// This is a digital logic circuit called an adder:
|
|
// https://en.wikipedia.org/wiki/Adder_(electronics)
|
|
let mut reactor = Reactor::new();
|
|
let a = reactor.create_input(false);
|
|
let b = reactor.create_input(false);
|
|
let carry_in = reactor.create_input(false);
|
|
|
|
let a_xor_b = reactor
|
|
.create_compute(&[CellId::Input(a), CellId::Input(b)], |v| v[0] ^ v[1])
|
|
.unwrap();
|
|
let sum = reactor
|
|
.create_compute(&[CellId::Compute(a_xor_b), CellId::Input(carry_in)], |v| {
|
|
v[0] ^ v[1]
|
|
})
|
|
.unwrap();
|
|
|
|
let a_xor_b_and_cin = reactor
|
|
.create_compute(&[CellId::Compute(a_xor_b), CellId::Input(carry_in)], |v| {
|
|
v[0] && v[1]
|
|
})
|
|
.unwrap();
|
|
let a_and_b = reactor
|
|
.create_compute(&[CellId::Input(a), CellId::Input(b)], |v| v[0] && v[1])
|
|
.unwrap();
|
|
let carry_out = reactor
|
|
.create_compute(
|
|
&[CellId::Compute(a_xor_b_and_cin), CellId::Compute(a_and_b)],
|
|
|v| v[0] || v[1],
|
|
)
|
|
.unwrap();
|
|
|
|
let tests = &[
|
|
(false, false, false, false, false),
|
|
(false, false, true, false, true),
|
|
(false, true, false, false, true),
|
|
(false, true, true, true, false),
|
|
(true, false, false, false, true),
|
|
(true, false, true, true, false),
|
|
(true, true, false, true, false),
|
|
(true, true, true, true, true),
|
|
];
|
|
|
|
for &(aval, bval, cinval, expected_cout, expected_sum) in tests {
|
|
assert!(reactor.set_value(a, aval));
|
|
assert!(reactor.set_value(b, bval));
|
|
assert!(reactor.set_value(carry_in, cinval));
|
|
|
|
assert_eq!(reactor.value(CellId::Compute(sum)), Some(expected_sum));
|
|
assert_eq!(
|
|
reactor.value(CellId::Compute(carry_out)),
|
|
Some(expected_cout)
|
|
);
|
|
}
|
|
}
|