test: derive test expectations from config.rn values
The defaults in config.rn changed (90 initial people, fertile teens and older adults), but the tests still hard-coded the previous 9-person village. Tests now override Rune constants by name and compute expected counts from the loaded configuration. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
549cd2c283
commit
3035b9f9f1
145
src/world.rs
145
src/world.rs
|
|
@ -2815,6 +2815,33 @@ mod tests {
|
||||||
SimScript, W, World, cheb, default_config,
|
SimScript, W, World, cheb, default_config,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Replaces the value of `pub const {name}` in `source`, whatever its current value is.
|
||||||
|
fn with_constant(source: &str, name: &str, value: &str) -> String {
|
||||||
|
let prefix = format!("pub const {name} = ");
|
||||||
|
assert!(
|
||||||
|
source.lines().any(|line| line.starts_with(&prefix)),
|
||||||
|
"config.rn has no constant {name}"
|
||||||
|
);
|
||||||
|
source
|
||||||
|
.lines()
|
||||||
|
.map(|line| {
|
||||||
|
if line.starts_with(&prefix) {
|
||||||
|
format!("{prefix}{value};")
|
||||||
|
} else {
|
||||||
|
line.to_string()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_fertile(world: &World, stage: LifeStage) -> bool {
|
||||||
|
world
|
||||||
|
.calendar
|
||||||
|
.duration(stage.key())
|
||||||
|
.is_some_and(|duration| duration.fertile)
|
||||||
|
}
|
||||||
|
|
||||||
fn simulation_state(world: &World) -> serde_json::Value {
|
fn simulation_state(world: &World) -> serde_json::Value {
|
||||||
let mut state: serde_json::Value = serde_json::from_str(&world.state_json()).unwrap();
|
let mut state: serde_json::Value = serde_json::from_str(&world.state_json()).unwrap();
|
||||||
state.as_object_mut().unwrap().remove("actor_stats");
|
state.as_object_mut().unwrap().remove("actor_stats");
|
||||||
|
|
@ -2863,15 +2890,22 @@ mod tests {
|
||||||
world.calendar.ticks_per_day = 1;
|
world.calendar.ticks_per_day = 1;
|
||||||
world.calendar.days_per_week = 1;
|
world.calendar.days_per_week = 1;
|
||||||
world.food = 1000;
|
world.food = 1000;
|
||||||
|
let eligible = world
|
||||||
|
.people
|
||||||
|
.iter()
|
||||||
|
.filter(|person| person.stage.can_partner())
|
||||||
|
.count();
|
||||||
world.find_partners();
|
world.find_partners();
|
||||||
assert_eq!(world.partnerships.len(), 4);
|
let partnerships = eligible / 2;
|
||||||
|
assert!(partnerships > 0);
|
||||||
|
assert_eq!(world.partnerships.len(), partnerships);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
world
|
world
|
||||||
.people
|
.people
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|person| person.partner_id.is_some())
|
.filter(|person| person.partner_id.is_some())
|
||||||
.count(),
|
.count(),
|
||||||
8
|
partnerships * 2
|
||||||
);
|
);
|
||||||
world.people[0].x = 0;
|
world.people[0].x = 0;
|
||||||
world.people[0].y = 0;
|
world.people[0].y = 0;
|
||||||
|
|
@ -2879,18 +2913,20 @@ mod tests {
|
||||||
world.sync_entities();
|
world.sync_entities();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
world.entities.len(),
|
world.entities.len(),
|
||||||
world.people.len() + world.rabbits.len() + world.wolves.len() + 4
|
world.people.len() + world.rabbits.len() + world.wolves.len() + partnerships
|
||||||
);
|
);
|
||||||
world.load_entities();
|
world.load_entities();
|
||||||
assert_eq!(world.partnerships.len(), 4);
|
assert_eq!(world.partnerships.len(), partnerships);
|
||||||
|
|
||||||
let initial_population = world.people.len();
|
let initial_population = world.people.len();
|
||||||
|
let births = 3;
|
||||||
|
world.config.population_capacity = initial_population + births;
|
||||||
while world.people.len() < world.population_capacity() {
|
while world.people.len() < world.population_capacity() {
|
||||||
world.tick += 1;
|
world.tick += 1;
|
||||||
world.have_children(1);
|
world.have_children(1);
|
||||||
}
|
}
|
||||||
assert_eq!(world.people.len(), world.population_capacity());
|
assert_eq!(world.people.len(), world.population_capacity());
|
||||||
assert_eq!(world.people.len() - initial_population, 3);
|
assert_eq!(world.people.len() - initial_population, births);
|
||||||
let toddler = world
|
let toddler = world
|
||||||
.people
|
.people
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -2904,21 +2940,39 @@ mod tests {
|
||||||
fn each_fertile_partnership_can_have_a_weekly_birth() {
|
fn each_fertile_partnership_can_have_a_weekly_birth() {
|
||||||
let mut world = World::new(42);
|
let mut world = World::new(42);
|
||||||
world.calendar.ticks_per_day = 1;
|
world.calendar.ticks_per_day = 1;
|
||||||
world.food = 1000;
|
|
||||||
world.houses.extend([(0, 0), (1, 0)]);
|
|
||||||
world.find_partners();
|
world.find_partners();
|
||||||
|
let fertile_partnerships = world
|
||||||
|
.partnerships
|
||||||
|
.iter()
|
||||||
|
.filter(|partnership| {
|
||||||
|
[partnership.first_person_id, partnership.second_person_id]
|
||||||
|
.iter()
|
||||||
|
.all(|id| {
|
||||||
|
let person = world.people.iter().find(|person| person.id == *id);
|
||||||
|
person.is_some_and(|person| is_fertile(&world, person.stage))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.count();
|
||||||
|
assert!(fertile_partnerships > 0);
|
||||||
|
let initial_population = world.people.len();
|
||||||
|
world.config.population_capacity = initial_population + world.partnerships.len();
|
||||||
|
world.food = world.config.birth_food_threshold
|
||||||
|
+ world.config.birth_food_cost * u32::try_from(fertile_partnerships).unwrap();
|
||||||
world.tick += 7;
|
world.tick += 7;
|
||||||
|
|
||||||
world.have_children(7);
|
world.have_children(7);
|
||||||
|
|
||||||
assert_eq!(world.people.len(), 13);
|
assert_eq!(
|
||||||
|
world.people.len(),
|
||||||
|
initial_population + fertile_partnerships
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
world
|
world
|
||||||
.people
|
.people
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|person| person.stage == LifeStage::Toddler)
|
.filter(|person| person.stage == LifeStage::Toddler)
|
||||||
.count(),
|
.count(),
|
||||||
4
|
fertile_partnerships
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2953,8 +3007,14 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let initial_population = world.config.initial_population;
|
||||||
|
let minimum_population = if initial_population < world.population_capacity() {
|
||||||
|
initial_population + 1
|
||||||
|
} else {
|
||||||
|
initial_population
|
||||||
|
};
|
||||||
assert!(
|
assert!(
|
||||||
world.people.len() >= 10,
|
world.people.len() >= minimum_population,
|
||||||
"village failed to replace its initial population: daily={daily:?}, people={:?}, events={:?}",
|
"village failed to replace its initial population: daily={daily:?}, people={:?}, events={:?}",
|
||||||
world
|
world
|
||||||
.people
|
.people
|
||||||
|
|
@ -2991,13 +3051,14 @@ mod tests {
|
||||||
|
|
||||||
let restored = World::from_snapshot(&snapshot.to_string(), 42).unwrap();
|
let restored = World::from_snapshot(&snapshot.to_string(), 42).unwrap();
|
||||||
|
|
||||||
assert_eq!(restored.people.len(), 9);
|
let config = default_config();
|
||||||
|
assert_eq!(restored.people.len(), config.initial_population);
|
||||||
assert_eq!(restored.people[0].stage, LifeStage::YoungAdult);
|
assert_eq!(restored.people[0].stage, LifeStage::YoungAdult);
|
||||||
assert!(
|
assert!(
|
||||||
restored
|
restored
|
||||||
.people
|
.people
|
||||||
.iter()
|
.iter()
|
||||||
.skip(3)
|
.skip(config.initial_young_adults)
|
||||||
.all(|person| person.stage == LifeStage::Adult)
|
.all(|person| person.stage == LifeStage::Adult)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -3009,7 +3070,8 @@ mod tests {
|
||||||
world.sync_entities();
|
world.sync_entities();
|
||||||
let restored = World::from_snapshot(&world.snapshot_json().unwrap(), 42).unwrap();
|
let restored = World::from_snapshot(&world.snapshot_json().unwrap(), 42).unwrap();
|
||||||
|
|
||||||
assert_eq!(restored.partnerships.len(), 4);
|
assert!(!world.partnerships.is_empty());
|
||||||
|
assert_eq!(restored.partnerships.len(), world.partnerships.len());
|
||||||
assert_eq!(simulation_state(&restored), simulation_state(&world));
|
assert_eq!(simulation_state(&restored), simulation_state(&world));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
serde_json::from_str::<serde_json::Value>(&restored.state_json()).unwrap()["people"][0]
|
serde_json::from_str::<serde_json::Value>(&restored.state_json()).unwrap()["people"][0]
|
||||||
|
|
@ -3065,7 +3127,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn reset_keeps_seed_and_settings_with_nine_people() {
|
fn reset_keeps_seed_and_settings_with_initial_population() {
|
||||||
let mut original = World::new(42);
|
let mut original = World::new(42);
|
||||||
original.apply("speed 4");
|
original.apply("speed 4");
|
||||||
original.paused = true;
|
original.paused = true;
|
||||||
|
|
@ -3076,7 +3138,7 @@ mod tests {
|
||||||
assert_eq!(reset.seed(), 42);
|
assert_eq!(reset.seed(), 42);
|
||||||
assert_eq!(reset.speed, 4);
|
assert_eq!(reset.speed, 4);
|
||||||
assert!(reset.paused);
|
assert!(reset.paused);
|
||||||
assert_eq!(reset.people.len(), 9);
|
assert_eq!(reset.people.len(), default_config().initial_population);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
reset.tick,
|
reset.tick,
|
||||||
default_config().calendar.ticks_per_day / default_config().starting_tick_fraction
|
default_config().calendar.ticks_per_day / default_config().starting_tick_fraction
|
||||||
|
|
@ -3168,13 +3230,18 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rune_config_controls_initial_world_and_survives_snapshot_restore() {
|
fn rune_config_controls_initial_world_and_survives_snapshot_restore() {
|
||||||
let source = DEFAULT_SCRIPT
|
let source = [
|
||||||
.replace("WIDTH = 96", "WIDTH = 48")
|
("WIDTH", "48"),
|
||||||
.replace("HEIGHT = 64", "HEIGHT = 32")
|
("HEIGHT", "32"),
|
||||||
.replace("INITIAL_POPULATION = 9", "INITIAL_POPULATION = 6")
|
("INITIAL_POPULATION", "6"),
|
||||||
.replace("INITIAL_YOUNG_ADULTS = 3", "INITIAL_YOUNG_ADULTS = 2")
|
("INITIAL_YOUNG_ADULTS", "2"),
|
||||||
.replace("INITIAL_FOOD = 60", "INITIAL_FOOD = 125")
|
("INITIAL_FOOD", "125"),
|
||||||
.replace("TICKS_PER_DAY = 1800", "TICKS_PER_DAY = 240");
|
("TICKS_PER_DAY", "240"),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.fold(DEFAULT_SCRIPT.to_string(), |source, (name, value)| {
|
||||||
|
with_constant(&source, name, value)
|
||||||
|
});
|
||||||
let script = SimScript::load(&source).unwrap();
|
let script = SimScript::load(&source).unwrap();
|
||||||
let world = World::new_with_script(42, script.clone());
|
let world = World::new_with_script(42, script.clone());
|
||||||
|
|
||||||
|
|
@ -3184,7 +3251,10 @@ mod tests {
|
||||||
assert_eq!(world.calendar.ticks_per_day, 240);
|
assert_eq!(world.calendar.ticks_per_day, 240);
|
||||||
assert!(world.terrain_json().contains("\"w\":48,\"h\":32"));
|
assert!(world.terrain_json().contains("\"w\":48,\"h\":32"));
|
||||||
let state: serde_json::Value = serde_json::from_str(&world.state_json()).unwrap();
|
let state: serde_json::Value = serde_json::from_str(&world.state_json()).unwrap();
|
||||||
assert_eq!(state["limits"]["speed"], 8);
|
assert_eq!(
|
||||||
|
state["limits"]["speed"],
|
||||||
|
default_config().max_simulation_speed
|
||||||
|
);
|
||||||
|
|
||||||
let restored =
|
let restored =
|
||||||
World::from_snapshot_with_script(&world.snapshot_json().unwrap(), 42, script).unwrap();
|
World::from_snapshot_with_script(&world.snapshot_json().unwrap(), 42, script).unwrap();
|
||||||
|
|
@ -3195,45 +3265,52 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn restored_snapshots_use_the_provided_script() {
|
fn restored_snapshots_use_the_provided_script() {
|
||||||
let mut world = World::new(42);
|
let mut world = World::new(42);
|
||||||
let edited = DEFAULT_SCRIPT.replace("MAX_SIMULATION_SPEED = 8", "MAX_SIMULATION_SPEED = 3");
|
let edited = with_constant(DEFAULT_SCRIPT, "MAX_SIMULATION_SPEED", "3");
|
||||||
world.set_script(&edited).unwrap();
|
world.set_script(&edited).unwrap();
|
||||||
assert_eq!(world.config.max_simulation_speed, 3);
|
assert_eq!(world.config.max_simulation_speed, 3);
|
||||||
assert_eq!(world.script(), edited);
|
assert_eq!(world.script(), edited);
|
||||||
|
|
||||||
let restored = World::from_snapshot(&world.snapshot_json().unwrap(), 42).unwrap();
|
let restored = World::from_snapshot(&world.snapshot_json().unwrap(), 42).unwrap();
|
||||||
assert_eq!(restored.script(), DEFAULT_SCRIPT);
|
assert_eq!(restored.script(), DEFAULT_SCRIPT);
|
||||||
assert_eq!(restored.config.max_simulation_speed, 8);
|
assert_eq!(
|
||||||
|
restored.config.max_simulation_speed,
|
||||||
|
default_config().max_simulation_speed
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn behaviours_read_script_constants() {
|
fn behaviours_read_script_constants() {
|
||||||
let mut world = World::new(42);
|
let mut world = World::new(42);
|
||||||
let all_stay = DEFAULT_SCRIPT.replace(
|
let all_stay = with_constant(DEFAULT_SCRIPT, "RABBIT_BEHAVIOR_WANDER_CHANCE", "0.0");
|
||||||
"RABBIT_BEHAVIOR_WANDER_CHANCE = 0.5",
|
|
||||||
"RABBIT_BEHAVIOR_WANDER_CHANCE = 0.0",
|
|
||||||
);
|
|
||||||
world.set_script(&all_stay).unwrap();
|
world.set_script(&all_stay).unwrap();
|
||||||
let c = super::Ctx {
|
let c = super::Ctx {
|
||||||
roll: 0.25,
|
roll: 0.25,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert_eq!(world.decide("rabbit", c.clone()), "stay");
|
assert_eq!(world.decide("rabbit", c.clone()), "stay");
|
||||||
world.set_script(DEFAULT_SCRIPT).unwrap();
|
let all_wander = with_constant(DEFAULT_SCRIPT, "RABBIT_BEHAVIOR_WANDER_CHANCE", "1.0");
|
||||||
|
world.set_script(&all_wander).unwrap();
|
||||||
assert_eq!(world.decide("rabbit", c), "wander");
|
assert_eq!(world.decide("rabbit", c), "wander");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_rune_configuration_is_rejected() {
|
fn invalid_rune_configuration_is_rejected() {
|
||||||
let source = DEFAULT_SCRIPT
|
let source = with_constant(
|
||||||
.replace("INITIAL_POPULATION = 9", "INITIAL_POPULATION = 3")
|
&with_constant(DEFAULT_SCRIPT, "INITIAL_POPULATION", "3"),
|
||||||
.replace("INITIAL_YOUNG_ADULTS = 3", "INITIAL_YOUNG_ADULTS = 4");
|
"INITIAL_YOUNG_ADULTS",
|
||||||
|
"4",
|
||||||
|
);
|
||||||
assert!(
|
assert!(
|
||||||
SimScript::load(&source)
|
SimScript::load(&source)
|
||||||
.err()
|
.err()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.contains("initial_young_adults cannot exceed initial_population")
|
.contains("initial_young_adults cannot exceed initial_population")
|
||||||
);
|
);
|
||||||
let missing = DEFAULT_SCRIPT.replace("pub const WIDTH = 96;", "");
|
let missing = DEFAULT_SCRIPT
|
||||||
|
.lines()
|
||||||
|
.filter(|line| !line.starts_with("pub const WIDTH = "))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
assert!(SimScript::load(&missing).err().unwrap().contains("WIDTH"));
|
assert!(SimScript::load(&missing).err().unwrap().contains("WIDTH"));
|
||||||
let no_behaviour = DEFAULT_SCRIPT.replace("pub fn wolf(c)", "pub fn wolves(c)");
|
let no_behaviour = DEFAULT_SCRIPT.replace("pub fn wolf(c)", "pub fn wolves(c)");
|
||||||
assert!(SimScript::load(&no_behaviour).is_err());
|
assert!(SimScript::load(&no_behaviour).is_err());
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue