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,
|
||||
};
|
||||
|
||||
/// 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 {
|
||||
let mut state: serde_json::Value = serde_json::from_str(&world.state_json()).unwrap();
|
||||
state.as_object_mut().unwrap().remove("actor_stats");
|
||||
|
|
@ -2863,15 +2890,22 @@ mod tests {
|
|||
world.calendar.ticks_per_day = 1;
|
||||
world.calendar.days_per_week = 1;
|
||||
world.food = 1000;
|
||||
let eligible = world
|
||||
.people
|
||||
.iter()
|
||||
.filter(|person| person.stage.can_partner())
|
||||
.count();
|
||||
world.find_partners();
|
||||
assert_eq!(world.partnerships.len(), 4);
|
||||
let partnerships = eligible / 2;
|
||||
assert!(partnerships > 0);
|
||||
assert_eq!(world.partnerships.len(), partnerships);
|
||||
assert_eq!(
|
||||
world
|
||||
.people
|
||||
.iter()
|
||||
.filter(|person| person.partner_id.is_some())
|
||||
.count(),
|
||||
8
|
||||
partnerships * 2
|
||||
);
|
||||
world.people[0].x = 0;
|
||||
world.people[0].y = 0;
|
||||
|
|
@ -2879,18 +2913,20 @@ mod tests {
|
|||
world.sync_entities();
|
||||
assert_eq!(
|
||||
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();
|
||||
assert_eq!(world.partnerships.len(), 4);
|
||||
assert_eq!(world.partnerships.len(), partnerships);
|
||||
|
||||
let initial_population = world.people.len();
|
||||
let births = 3;
|
||||
world.config.population_capacity = initial_population + births;
|
||||
while world.people.len() < world.population_capacity() {
|
||||
world.tick += 1;
|
||||
world.have_children(1);
|
||||
}
|
||||
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
|
||||
.people
|
||||
.iter()
|
||||
|
|
@ -2904,21 +2940,39 @@ mod tests {
|
|||
fn each_fertile_partnership_can_have_a_weekly_birth() {
|
||||
let mut world = World::new(42);
|
||||
world.calendar.ticks_per_day = 1;
|
||||
world.food = 1000;
|
||||
world.houses.extend([(0, 0), (1, 0)]);
|
||||
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.have_children(7);
|
||||
|
||||
assert_eq!(world.people.len(), 13);
|
||||
assert_eq!(
|
||||
world.people.len(),
|
||||
initial_population + fertile_partnerships
|
||||
);
|
||||
assert_eq!(
|
||||
world
|
||||
.people
|
||||
.iter()
|
||||
.filter(|person| person.stage == LifeStage::Toddler)
|
||||
.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!(
|
||||
world.people.len() >= 10,
|
||||
world.people.len() >= minimum_population,
|
||||
"village failed to replace its initial population: daily={daily:?}, people={:?}, events={:?}",
|
||||
world
|
||||
.people
|
||||
|
|
@ -2991,13 +3051,14 @@ mod tests {
|
|||
|
||||
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!(
|
||||
restored
|
||||
.people
|
||||
.iter()
|
||||
.skip(3)
|
||||
.skip(config.initial_young_adults)
|
||||
.all(|person| person.stage == LifeStage::Adult)
|
||||
);
|
||||
}
|
||||
|
|
@ -3009,7 +3070,8 @@ mod tests {
|
|||
world.sync_entities();
|
||||
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!(
|
||||
serde_json::from_str::<serde_json::Value>(&restored.state_json()).unwrap()["people"][0]
|
||||
|
|
@ -3065,7 +3127,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[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);
|
||||
original.apply("speed 4");
|
||||
original.paused = true;
|
||||
|
|
@ -3076,7 +3138,7 @@ mod tests {
|
|||
assert_eq!(reset.seed(), 42);
|
||||
assert_eq!(reset.speed, 4);
|
||||
assert!(reset.paused);
|
||||
assert_eq!(reset.people.len(), 9);
|
||||
assert_eq!(reset.people.len(), default_config().initial_population);
|
||||
assert_eq!(
|
||||
reset.tick,
|
||||
default_config().calendar.ticks_per_day / default_config().starting_tick_fraction
|
||||
|
|
@ -3168,13 +3230,18 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn rune_config_controls_initial_world_and_survives_snapshot_restore() {
|
||||
let source = DEFAULT_SCRIPT
|
||||
.replace("WIDTH = 96", "WIDTH = 48")
|
||||
.replace("HEIGHT = 64", "HEIGHT = 32")
|
||||
.replace("INITIAL_POPULATION = 9", "INITIAL_POPULATION = 6")
|
||||
.replace("INITIAL_YOUNG_ADULTS = 3", "INITIAL_YOUNG_ADULTS = 2")
|
||||
.replace("INITIAL_FOOD = 60", "INITIAL_FOOD = 125")
|
||||
.replace("TICKS_PER_DAY = 1800", "TICKS_PER_DAY = 240");
|
||||
let source = [
|
||||
("WIDTH", "48"),
|
||||
("HEIGHT", "32"),
|
||||
("INITIAL_POPULATION", "6"),
|
||||
("INITIAL_YOUNG_ADULTS", "2"),
|
||||
("INITIAL_FOOD", "125"),
|
||||
("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 world = World::new_with_script(42, script.clone());
|
||||
|
||||
|
|
@ -3184,7 +3251,10 @@ mod tests {
|
|||
assert_eq!(world.calendar.ticks_per_day, 240);
|
||||
assert!(world.terrain_json().contains("\"w\":48,\"h\":32"));
|
||||
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 =
|
||||
World::from_snapshot_with_script(&world.snapshot_json().unwrap(), 42, script).unwrap();
|
||||
|
|
@ -3195,45 +3265,52 @@ mod tests {
|
|||
#[test]
|
||||
fn restored_snapshots_use_the_provided_script() {
|
||||
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();
|
||||
assert_eq!(world.config.max_simulation_speed, 3);
|
||||
assert_eq!(world.script(), edited);
|
||||
|
||||
let restored = World::from_snapshot(&world.snapshot_json().unwrap(), 42).unwrap();
|
||||
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]
|
||||
fn behaviours_read_script_constants() {
|
||||
let mut world = World::new(42);
|
||||
let all_stay = DEFAULT_SCRIPT.replace(
|
||||
"RABBIT_BEHAVIOR_WANDER_CHANCE = 0.5",
|
||||
"RABBIT_BEHAVIOR_WANDER_CHANCE = 0.0",
|
||||
);
|
||||
let all_stay = with_constant(DEFAULT_SCRIPT, "RABBIT_BEHAVIOR_WANDER_CHANCE", "0.0");
|
||||
world.set_script(&all_stay).unwrap();
|
||||
let c = super::Ctx {
|
||||
roll: 0.25,
|
||||
..Default::default()
|
||||
};
|
||||
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");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_rune_configuration_is_rejected() {
|
||||
let source = DEFAULT_SCRIPT
|
||||
.replace("INITIAL_POPULATION = 9", "INITIAL_POPULATION = 3")
|
||||
.replace("INITIAL_YOUNG_ADULTS = 3", "INITIAL_YOUNG_ADULTS = 4");
|
||||
let source = with_constant(
|
||||
&with_constant(DEFAULT_SCRIPT, "INITIAL_POPULATION", "3"),
|
||||
"INITIAL_YOUNG_ADULTS",
|
||||
"4",
|
||||
);
|
||||
assert!(
|
||||
SimScript::load(&source)
|
||||
.err()
|
||||
.unwrap()
|
||||
.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"));
|
||||
let no_behaviour = DEFAULT_SCRIPT.replace("pub fn wolf(c)", "pub fn wolves(c)");
|
||||
assert!(SimScript::load(&no_behaviour).is_err());
|
||||
|
|
|
|||
Loading…
Reference in New Issue