use crate::components::{RiderPhase, ServiceMode};
use crate::events::Event;
use crate::stop::StopId;
use super::helpers::{default_config, scan};
#[test]
fn default_mode_is_normal() {
let config = default_config();
let mut sim = crate::sim::Simulation::new(&config, scan()).unwrap();
let elev = sim.world().elevator_ids()[0];
assert_eq!(sim.service_mode(elev), ServiceMode::Normal);
sim.spawn_rider_by_stop_id(StopId(0), StopId(2), 75.0)
.unwrap();
for _ in 0..1000 {
sim.step();
}
assert!(sim.metrics().total_delivered() > 0);
}
#[test]
fn independent_skips_dispatch() {
let config = default_config();
let mut sim = crate::sim::Simulation::new(&config, scan()).unwrap();
let elev = sim.world().elevator_ids()[0];
sim.set_service_mode(elev, ServiceMode::Independent)
.unwrap();
sim.spawn_rider_by_stop_id(StopId(0), StopId(2), 75.0)
.unwrap();
for _ in 0..500 {
sim.step();
}
assert!(
sim.world()
.iter_riders()
.any(|(_, r)| r.phase() == RiderPhase::Waiting),
"rider should still be waiting with Independent elevator"
);
assert_eq!(sim.metrics().total_delivered(), 0);
}
#[test]
fn inspection_reduced_speed() {
use crate::components::ElevatorPhase;
fn measure_travel(mode: ServiceMode) -> u64 {
let config = default_config();
let mut sim = crate::sim::Simulation::new(&config, scan()).unwrap();
let elev = sim.world().elevator_ids()[0];
sim.spawn_rider_by_stop_id(StopId(0), StopId(2), 75.0)
.unwrap();
let mut mode_set = false;
let mut depart_tick: Option<u64> = None;
for _ in 0..10000 {
sim.step();
if !mode_set {
let car = sim.world().elevator(elev).unwrap();
if matches!(car.phase(), ElevatorPhase::MovingToStop(_)) {
sim.set_service_mode(elev, mode).unwrap();
mode_set = true;
depart_tick = Some(sim.current_tick());
}
}
if let (true, Some(depart)) = (mode_set, depart_tick) {
let car = sim.world().elevator(elev).unwrap();
if !matches!(car.phase(), ElevatorPhase::MovingToStop(_))
&& sim.current_tick() > depart
{
return sim.current_tick() - depart;
}
}
}
panic!("elevator never arrived in {mode} mode");
}
let normal_ticks = measure_travel(ServiceMode::Normal);
let inspect_ticks = measure_travel(ServiceMode::Inspection);
assert!(
inspect_ticks > normal_ticks * 3,
"inspection should be at least 3x slower: normal={normal_ticks}, inspection={inspect_ticks}"
);
}
#[test]
fn inspection_doors_hold_open() {
let config = default_config();
let mut sim = crate::sim::Simulation::new(&config, scan()).unwrap();
let elev = sim.world().elevator_ids()[0];
sim.set_service_mode(elev, ServiceMode::Inspection).unwrap();
sim.spawn_rider_by_stop_id(StopId(0), StopId(2), 75.0)
.unwrap();
let mut doors_opened = false;
for _ in 0..200 {
sim.step();
let car = sim.world().elevator(elev).unwrap();
if car.door().is_open() {
doors_opened = true;
break;
}
}
assert!(doors_opened, "doors should open at some point");
for _ in 0..100 {
sim.step();
}
let car = sim.world().elevator(elev).unwrap();
assert!(
car.door().is_open(),
"doors should remain open in Inspection mode, but state is: {}",
car.door()
);
}
#[test]
fn service_mode_changed_event() {
let config = default_config();
let mut sim = crate::sim::Simulation::new(&config, scan()).unwrap();
let elev = sim.world().elevator_ids()[0];
sim.drain_events();
sim.set_service_mode(elev, ServiceMode::Independent)
.unwrap();
let events = sim.drain_events();
let mode_events: Vec<_> = events
.iter()
.filter(|e| matches!(e, Event::ServiceModeChanged { .. }))
.collect();
assert_eq!(mode_events.len(), 1);
match &mode_events[0] {
Event::ServiceModeChanged {
elevator, from, to, ..
} => {
assert_eq!(*elevator, elev);
assert_eq!(*from, ServiceMode::Normal);
assert_eq!(*to, ServiceMode::Independent);
}
_ => panic!("expected ServiceModeChanged"),
}
}
#[test]
fn noop_mode_change_no_event() {
let config = default_config();
let mut sim = crate::sim::Simulation::new(&config, scan()).unwrap();
let elev = sim.world().elevator_ids()[0];
sim.drain_events();
sim.set_service_mode(elev, ServiceMode::Normal).unwrap();
let events = sim.drain_events();
assert!(
!events
.iter()
.any(|e| matches!(e, Event::ServiceModeChanged { .. })),
"no event should be emitted for no-op mode change"
);
}
#[test]
fn independent_excluded_from_reposition() {
use crate::builder::SimulationBuilder;
use crate::dispatch::BuiltinReposition;
use crate::dispatch::reposition::ReturnToLobby;
use crate::stop::StopConfig;
let mut sim = SimulationBuilder::demo()
.stops(vec![
StopConfig {
id: StopId(0),
name: "Ground".into(),
position: 0.0,
},
StopConfig {
id: StopId(1),
name: "Floor 2".into(),
position: 10.0,
},
StopConfig {
id: StopId(2),
name: "Floor 3".into(),
position: 20.0,
},
])
.reposition(ReturnToLobby::new(), BuiltinReposition::ReturnToLobby)
.build()
.unwrap();
let elev = sim.world().elevator_ids()[0];
sim.spawn_rider_by_stop_id(StopId(0), StopId(2), 75.0)
.unwrap();
for _ in 0..1000 {
sim.step();
}
assert!(sim.metrics().total_delivered() > 0);
sim.set_service_mode(elev, ServiceMode::Independent)
.unwrap();
for _ in 0..500 {
sim.step();
}
let pos_before = sim.world().position(elev).unwrap().value;
for _ in 0..500 {
sim.step();
}
let pos_after = sim.world().position(elev).unwrap().value;
let phase = sim.world().elevator(elev).unwrap().phase();
assert!(
(pos_before - pos_after).abs() < 1e-9,
"independent elevator should not be repositioned: before={pos_before}, after={pos_after}, phase={phase}"
);
}