traffic_seitan/
main.rs

1//! This is a tool that runs a simulation, constantly interrupting to apply random map edits to the
2//! live sim without resetting to midnight. The purpose is to trigger crashes and find bugs.
3
4// TODO Eventually rewrite this to go through the public API. Faster to iterate in Rust for now.
5
6#[macro_use]
7extern crate log;
8
9use rand::seq::SliceRandom;
10use rand_xorshift::XorShiftRng;
11use structopt::StructOpt;
12
13use abstutil::{prettyprint_usize, Timer};
14use geom::Duration;
15use map_model::{LaneID, LaneType, Map, MapEdits};
16use sim::Sim;
17
18#[derive(StructOpt)]
19#[structopt(
20    name = "traffic_seitan",
21    about = "Automated fuzz testing for live map edits"
22)]
23struct Args {
24    #[structopt(flatten)]
25    flags: sim::SimFlags,
26}
27
28fn main() {
29    abstutil::logger::setup();
30    let mut sim_flags = Args::from_args().flags;
31    sim_flags.initialize();
32
33    let mut timer = Timer::throwaway();
34    let (mut map, mut sim, mut rng) = sim_flags.load_synchronously(&mut timer);
35
36    // Set the edits name up-front, so that the savestates get named reasonably too.
37    {
38        let mut edits = map.get_edits().clone();
39        edits.edits_name = "traffic_seitan".to_string();
40        map.must_apply_edits(edits, &mut timer);
41        map.recalculate_pathfinding_after_edits(&mut timer);
42        sim.handle_live_edits(&map, &mut timer);
43    }
44
45    if let Err(err) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
46        run(&mut map, &mut sim, &mut rng, &mut timer);
47    })) {
48        let mut edits = map.get_edits().clone();
49        edits.edits_name = "traffic_seitan_crash".to_string();
50        map.must_apply_edits(edits, &mut timer);
51        map.save_edits();
52
53        println!("Crashed at {}", sim.time());
54
55        std::panic::resume_unwind(err)
56    }
57}
58
59fn run(map: &mut Map, sim: &mut Sim, rng: &mut XorShiftRng, timer: &mut Timer) {
60    let edit_frequency = Duration::minutes(5);
61
62    while !sim.is_done() {
63        println!();
64        sim.timed_step(map, edit_frequency, &mut None, timer);
65        sim.save();
66        map.save_edits();
67
68        let mut edits = map.get_edits().clone();
69        nuke_random_parking(map, rng, &mut edits);
70        alter_turn_destinations(sim, map, rng, &mut edits);
71
72        map.must_apply_edits(edits, timer);
73        map.recalculate_pathfinding_after_edits(timer);
74        sim.handle_live_edited_traffic_signals(map);
75        sim.handle_live_edits(map, timer);
76    }
77
78    let mut finished = 0;
79    let mut cancelled = 0;
80    for (_, _, _, maybe_dt) in &sim.get_analytics().finished_trips {
81        if maybe_dt.is_some() {
82            finished += 1;
83        } else {
84            cancelled += 1;
85        }
86    }
87    println!(
88        "\nDone! {} finished trips, {} cancelled",
89        prettyprint_usize(finished),
90        prettyprint_usize(cancelled)
91    );
92}
93
94fn alter_turn_destinations(sim: &Sim, map: &Map, rng: &mut XorShiftRng, edits: &mut MapEdits) {
95    let num_edits = 3;
96
97    // Find active turns
98    let mut active_destinations = Vec::new();
99    for i in map.all_intersections() {
100        for (_, t) in sim.get_accepted_agents(i.id) {
101            if !map.get_l(t.dst).is_walkable() {
102                active_destinations.push(t.dst);
103            }
104        }
105    }
106    active_destinations.sort();
107    active_destinations.dedup();
108    active_destinations.shuffle(rng);
109
110    for l in active_destinations.into_iter().take(num_edits) {
111        info!("Closing someone's target {}", l);
112        edits.commands.push(map.edit_road_cmd(l.road, |new| {
113            new.lanes_ltr[l.offset].lt = LaneType::Construction;
114
115            // If we're getting rid of the last driving lane, also remove any parking lanes. This
116            // mimics the check that the UI does.
117            if new
118                .lanes_ltr
119                .iter()
120                .all(|spec| spec.lt != LaneType::Driving)
121            {
122                for spec in &mut new.lanes_ltr {
123                    if spec.lt == LaneType::Parking {
124                        spec.lt = LaneType::Construction;
125                    }
126                }
127            }
128        }));
129    }
130}
131
132fn nuke_random_parking(map: &Map, rng: &mut XorShiftRng, edits: &mut MapEdits) {
133    let num_edits = 5;
134
135    let mut parking_lanes: Vec<LaneID> = map
136        .all_lanes()
137        .filter(|l| l.is_parking())
138        .map(|l| l.id)
139        .collect();
140    parking_lanes.shuffle(rng);
141    for l in parking_lanes.into_iter().take(num_edits) {
142        info!("Closing parking {}", l);
143        edits.commands.push(map.edit_road_cmd(l.road, |new| {
144            new.lanes_ltr[l.offset].lt = LaneType::Construction;
145        }));
146    }
147}