//! This is a tool that runs a simulation, constantly interrupting to apply random map edits to the
//! live sim without resetting to midnight. The purpose is to trigger crashes and find bugs.
// TODO Eventually rewrite this to go through the public API. Faster to iterate in Rust for now.
#[macro_use]
extern crate log;
use rand::seq::SliceRandom;
use rand_xorshift::XorShiftRng;
use structopt::StructOpt;
use abstutil::{prettyprint_usize, Timer};
use geom::Duration;
use map_model::{LaneID, LaneType, Map, MapEdits};
use sim::Sim;
#[derive(StructOpt)]
#[structopt(
name = "traffic_seitan",
about = "Automated fuzz testing for live map edits"
)]
struct Args {
#[structopt(flatten)]
flags: sim::SimFlags,
}
fn main() {
abstutil::logger::setup();
let mut sim_flags = Args::from_args().flags;
sim_flags.initialize();
let mut timer = Timer::throwaway();
let (mut map, mut sim, mut rng) = sim_flags.load_synchronously(&mut timer);
// Set the edits name up-front, so that the savestates get named reasonably too.
{
let mut edits = map.get_edits().clone();
edits.edits_name = "traffic_seitan".to_string();
map.must_apply_edits(edits, &mut timer);
map.recalculate_pathfinding_after_edits(&mut timer);
sim.handle_live_edits(&map, &mut timer);
}
if let Err(err) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
run(&mut map, &mut sim, &mut rng, &mut timer);
})) {
let mut edits = map.get_edits().clone();
edits.edits_name = "traffic_seitan_crash".to_string();
map.must_apply_edits(edits, &mut timer);
map.save_edits();
println!("Crashed at {}", sim.time());
std::panic::resume_unwind(err)
}
}
fn run(map: &mut Map, sim: &mut Sim, rng: &mut XorShiftRng, timer: &mut Timer) {
let edit_frequency = Duration::minutes(5);
while !sim.is_done() {
println!();
sim.timed_step(map, edit_frequency, &mut None, timer);
sim.save();
map.save_edits();
let mut edits = map.get_edits().clone();
nuke_random_parking(map, rng, &mut edits);
alter_turn_destinations(sim, map, rng, &mut edits);
map.must_apply_edits(edits, timer);
map.recalculate_pathfinding_after_edits(timer);
sim.handle_live_edited_traffic_signals(map);
sim.handle_live_edits(map, timer);
}
let mut finished = 0;
let mut cancelled = 0;
for (_, _, _, maybe_dt) in &sim.get_analytics().finished_trips {
if maybe_dt.is_some() {
finished += 1;
} else {
cancelled += 1;
}
}
println!(
"\nDone! {} finished trips, {} cancelled",
prettyprint_usize(finished),
prettyprint_usize(cancelled)
);
}
fn alter_turn_destinations(sim: &Sim, map: &Map, rng: &mut XorShiftRng, edits: &mut MapEdits) {
let num_edits = 3;
// Find active turns
let mut active_destinations = Vec::new();
for i in map.all_intersections() {
for (_, t) in sim.get_accepted_agents(i.id) {
if !map.get_l(t.dst).is_walkable() {
active_destinations.push(t.dst);
}
}
}
active_destinations.sort();
active_destinations.dedup();
active_destinations.shuffle(rng);
for l in active_destinations.into_iter().take(num_edits) {
info!("Closing someone's target {}", l);
edits.commands.push(map.edit_road_cmd(l.road, |new| {
new.lanes_ltr[l.offset].lt = LaneType::Construction;
// If we're getting rid of the last driving lane, also remove any parking lanes. This
// mimics the check that the UI does.
if new
.lanes_ltr
.iter()
.all(|spec| spec.lt != LaneType::Driving)
{
for spec in &mut new.lanes_ltr {
if spec.lt == LaneType::Parking {
spec.lt = LaneType::Construction;
}
}
}
}));
}
}
fn nuke_random_parking(map: &Map, rng: &mut XorShiftRng, edits: &mut MapEdits) {
let num_edits = 5;
let mut parking_lanes: Vec<LaneID> = map
.all_lanes()
.filter(|l| l.is_parking())
.map(|l| l.id)
.collect();
parking_lanes.shuffle(rng);
for l in parking_lanes.into_iter().take(num_edits) {
info!("Closing parking {}", l);
edits.commands.push(map.edit_road_cmd(l.road, |new| {
new.lanes_ltr[l.offset].lt = LaneType::Construction;
}));
}
}