While I already added quite a few helper functions for the definition of simulation tests, it still was a lot of work and pretty annoying to add one. As an example:
#[test]
fn source_to_sink_splitter_hole() {
let duration = 5000;
type S = StructureAction;
let actions_struct = vec![
S::WantAdd(
PosAnchor(Pos::default()),
(&Source::new(Material::Coal.into())).into(),
),
S::WantAdd(PosAnchor(Pos { y: 0, x: 1 }), (&Arm::e()).into()),
S::WantAdd(PosAnchor(Pos { y: 0, x: 2 }), (&Splitter::e()).into()),
S::WantAdd(PosAnchor(Pos { y: 0, x: 3 }), (&Arm::e()).into()),
S::WantAdd(PosAnchor(Pos { y: 1, x: 3 }), (&Arm::e()).into()),
S::WantAdd(PosAnchor(Pos { y: 0, x: 4 }), (&Sink::default()).into()),
S::WantAdd(PosAnchor(Pos { y: 1, x: 4 }), (&Hole::default()).into()),
];
let module = Ok(ModuleCheckData::new(
[5, 2].into(),
vec![ModuleSource {
pos: [0, 0],
item: Material::Coal.into(),
count: 26,
}],
vec![ModuleSink {
pos: [4, 0],
item: Material::Coal.into(),
count: 13,
}],
vec![
(3, s_arm()),
(1, s_source()),
(1, s_sink()),
(1, s_splitter()),
(1, s_hole()),
],
duration,
));
check(actions_struct, duration, module);
}
This test moves Item
s from a Source
via an Arm
onto a Splitter
from which two Arm
s will grab the Item
s and put them either into a Hole
(a special Structure
for tests that simply deletes Item
s passed to it) or a Sink
.
The simulation is then run for 5000
ticks, and is expected to create a Module
of size 5x2
, take 26
Coal
from the Source
and place 13
of them into the Sink
. The other half is deleted via the Hole
.
As you can see writing such a test is quite involved.
Test parser
To reduce the overhead for writing a test I wrote a custom parser for test files. It parses text into one of the following commands:
pub enum Cmd {
Add(AddAction),
SetDuration(u32),
SetExpectedDuration(u32),
SetExpectedSize(Size<u32>),
SetError(ModularizeError),
AddExpectedSource(PosAnchor<i64>, usize, Item),
AddExpectedSink(PosAnchor<i64>, usize, Item),
}
pub struct AddAction {
pub pos: PosAnchor<i64>,
pub rot: Rot,
pub structure: StructureTemplate,
}
Once the commands are parsed, a SimulationTest
can be generated and run from them:
pub struct SimulationTest {
error: Option<ModularizeError>,
actions: Vec<AddAction>,
duration: u32,
e_duration: u32,
e_size: Size<u32>,
e_sources: Vec<(PosAnchor<i64>, usize, Item)>,
e_sinks: Vec<(PosAnchor<i64>, usize, Item)>,
}
impl SimulationTest {
pub fn run(&self) {
...
}
pub fn update(&mut self, cmd: Cmd) {
match cmd {
Cmd::Add(x) => self.actions.push(x),
...
}
}
...
}
With all those helpers the test from above can now be written as:
duration 5000
e_duration 5000
e_size 5/2
+ 0/0 0 source coal
+ 1/0 0 arm
+ 2/0 0 splitter
+ 3/0 0 arm
+ 3/1 0 arm
+ 4/0 0 sink
+ 4/1 0 hole
e_source 0/0 26 coal
e_sink 4/0 13 coal