LCOV - code coverage report
Current view: top level - dashql-dataflow/src/ortho_router - tests.rs Coverage Total Hit
Test: DashQL 6e74948 Lines: 97.5 % 243 237
Test Date: 2026-11-06 17:46:50

            Line data    Source code
       1              : //! Routes: straight when clear, around one obstacle, through a
       2              : //! corridor, the fallback when boxed in, port sides, and determinism.
       3              : 
       4              : use super::{Connector, Options, Point, Port, Rect, Route, Router, Side};
       5              : 
       6           86 : fn rect(x: f32, y: f32, w: f32, h: f32) -> Rect {
       7           86 :     Rect::from_min_size([x, y], w, h)
       8           86 : }
       9              : 
      10           59 : fn right(x: f32, y: f32) -> Port {
      11           59 :     Port::new([x, y], Side::Right)
      12           59 : }
      13              : 
      14           59 : fn left(x: f32, y: f32) -> Port {
      15           59 :     Port::new([x, y], Side::Left)
      16           59 : }
      17              : 
      18           17 : fn router(obstacles: &[Rect]) -> Router {
      19           17 :     Router::new(Options::default()).with_obstacles(obstacles.iter().copied())
      20           17 : }
      21              : 
      22              : /// Every segment is horizontal or vertical.
      23           53 : fn assert_orthogonal(route: &Route) {
      24          141 :     for w in route.points.windows(2) {
      25          141 :         assert!(
      26          141 :             w[0].x == w[1].x || w[0].y == w[1].y,
      27              :             "a slanted segment: {:?}",
      28              :             route.points
      29              :         );
      30              :     }
      31           53 : }
      32              : 
      33              : /// No segment enters an obstacle's inside, except within `slack` of
      34              : /// the route's own ports.
      35           52 : fn assert_clear(route: &Route, obstacles: &[Rect]) {
      36       120483 :     let inside = |o: &Rect, p: Point| {
      37       120483 :         p.x > o.min.x + 0.01 && p.x < o.max.x - 0.01 && p.y > o.min.y + 0.01 && p.y < o.max.y - 0.01
      38       120483 :     };
      39          138 :     for w in route.points.windows(2) {
      40         4554 :         for k in 0..=32 {
      41         4554 :             let t = k as f32 / 32.0;
      42         4554 :             let p = Point::new(
      43         4554 :                 w[0].x + (w[1].x - w[0].x) * t,
      44         4554 :                 w[0].y + (w[1].y - w[0].y) * t,
      45              :             );
      46       120483 :             for o in obstacles {
      47       120483 :                 assert!(!inside(o, p), "{p:?} is inside {o:?}: {:?}", route.points);
      48              :             }
      49              :         }
      50              :     }
      51           52 : }
      52              : 
      53              : #[test]
      54            1 : fn a_clear_line_is_one_straight_segment() {
      55            1 :     let nodes = [rect(0.0, 0.0, 100.0, 60.0), rect(300.0, 0.0, 100.0, 60.0)];
      56            1 :     let wire = Connector::new(right(100.0, 30.0), left(300.0, 30.0));
      57            1 :     let route = router(&nodes).route(&wire);
      58            1 :     assert!(route.routed);
      59            1 :     assert_eq!(route.points, vec![wire.from.at, wire.to.at]);
      60            1 :     assert_eq!(route.bends(), 0);
      61            1 :     assert_eq!(route.length(), 200.0);
      62            1 : }
      63              : 
      64              : #[test]
      65            1 : fn offset_ports_take_two_bends() {
      66            1 :     let nodes = [rect(0.0, 0.0, 100.0, 60.0), rect(300.0, 100.0, 100.0, 60.0)];
      67            1 :     let wire = Connector::new(right(100.0, 30.0), left(300.0, 130.0));
      68            1 :     let route = router(&nodes).route(&wire);
      69            1 :     assert!(route.routed);
      70            1 :     assert_eq!(route.bends(), 2);
      71            1 :     assert_orthogonal(&route);
      72            1 :     assert_clear(&route, &nodes);
      73              :     // Out to the right, in from the left.
      74            1 :     assert!(route.points[1].x > wire.from.at.x);
      75            1 :     assert!(route.points[route.points.len() - 2].x < wire.to.at.x);
      76            1 : }
      77              : 
      78              : #[test]
      79            1 : fn a_node_in_the_way_is_gone_around() {
      80            1 :     let nodes = [
      81            1 :         rect(0.0, 0.0, 100.0, 60.0),
      82            1 :         rect(400.0, 0.0, 100.0, 60.0),
      83            1 :         rect(200.0, -40.0, 100.0, 140.0),
      84            1 :     ];
      85            1 :     let wire = Connector::new(right(100.0, 30.0), left(400.0, 30.0));
      86            1 :     let route = router(&nodes).route(&wire);
      87            1 :     assert!(route.routed);
      88            1 :     assert_orthogonal(&route);
      89            1 :     assert_clear(&route, &nodes);
      90            1 :     assert_eq!(route.bends(), 4);
      91              :     // At the margin's distance, above or below the wall.
      92            1 :     let margin = Options::default().margin;
      93            1 :     assert!(
      94            1 :         route
      95            1 :             .points
      96            1 :             .iter()
      97            3 :             .any(|p| p.y == -40.0 - margin || p.y == 100.0 + margin),
      98              :         "{:?}",
      99              :         route.points
     100              :     );
     101            1 : }
     102              : 
     103              : #[test]
     104            1 : fn a_corridor_between_two_nodes_is_taken() {
     105              :     // A wall with a gap: the route goes through the gap rather than
     106              :     // around the whole wall.
     107            1 :     let nodes = [
     108            1 :         rect(0.0, 0.0, 100.0, 60.0),
     109            1 :         rect(600.0, 400.0, 100.0, 60.0),
     110            1 :         rect(250.0, -1000.0, 100.0, 1200.0),
     111            1 :         rect(250.0, 260.0, 100.0, 1000.0),
     112            1 :     ];
     113            1 :     let wire = Connector::new(right(100.0, 30.0), left(600.0, 430.0));
     114            1 :     let route = router(&nodes).route(&wire);
     115            1 :     assert!(route.routed);
     116            1 :     assert_orthogonal(&route);
     117            1 :     assert_clear(&route, &nodes);
     118            1 :     let through = route
     119            1 :         .points
     120            1 :         .windows(2)
     121            3 :         .any(|w| w[0].y == w[1].y && w[0].y > 200.0 && w[0].y < 260.0);
     122            1 :     assert!(through, "not through the gap: {:?}", route.points);
     123            1 :     assert!(route.length() < 1200.0);
     124            1 : }
     125              : 
     126              : #[test]
     127            1 : fn a_backward_wire_goes_around_its_own_nodes() {
     128              :     // The input is left of the output: out right, around, in from the
     129              :     // left, never through either node.
     130            1 :     let nodes = [rect(300.0, 0.0, 100.0, 60.0), rect(0.0, 0.0, 100.0, 60.0)];
     131            1 :     let wire = Connector::new(right(400.0, 30.0), left(0.0, 30.0));
     132            1 :     let route = router(&nodes).route(&wire);
     133            1 :     assert!(route.routed);
     134            1 :     assert_orthogonal(&route);
     135            1 :     assert_clear(&route, &nodes);
     136            1 :     assert!(route.points[1].x > 400.0);
     137            1 :     assert!(route.points[route.points.len() - 2].x < 0.0);
     138            1 : }
     139              : 
     140              : #[test]
     141            1 : fn top_and_bottom_ports_leave_vertically() {
     142            1 :     let nodes = [rect(0.0, 0.0, 100.0, 60.0), rect(0.0, 200.0, 100.0, 60.0)];
     143            1 :     let wire = Connector::new(
     144            1 :         Port::new([50.0, 60.0], Side::Bottom),
     145            1 :         Port::new([50.0, 200.0], Side::Top),
     146              :     );
     147            1 :     let route = router(&nodes).route(&wire);
     148            1 :     assert!(route.routed);
     149            1 :     assert_eq!(route.points, vec![wire.from.at, wire.to.at]);
     150            1 : }
     151              : 
     152              : #[test]
     153            1 : fn a_boxed_in_port_falls_back_to_one_elbow() {
     154              :     // The second node is walled in on every side.
     155            1 :     let target = rect(400.0, 0.0, 100.0, 60.0);
     156            1 :     let nodes = [
     157            1 :         rect(0.0, 0.0, 100.0, 60.0),
     158            1 :         target,
     159            1 :         rect(340.0, -60.0, 220.0, 40.0),
     160            1 :         rect(340.0, 80.0, 220.0, 40.0),
     161            1 :         rect(340.0, -60.0, 40.0, 180.0),
     162            1 :         rect(520.0, -60.0, 40.0, 180.0),
     163            1 :     ];
     164            1 :     let wire = Connector::new(right(100.0, 30.0), left(400.0, 50.0));
     165            1 :     let route = router(&nodes).route(&wire);
     166            1 :     assert!(!route.routed);
     167            1 :     assert_orthogonal(&route);
     168            1 :     assert_eq!(route.points.first(), Some(&wire.from.at));
     169            1 :     assert_eq!(route.points.last(), Some(&wire.to.at));
     170              :     // Out, one elbow halfway, in: the stubs merge into its arms.
     171            1 :     assert_eq!(route.bends(), 2);
     172            1 :     assert_eq!(route.points[1].x, 250.0);
     173            1 : }
     174              : 
     175              : #[test]
     176            1 : fn a_coordinate_that_is_not_finite_falls_back_to_a_segment() {
     177            1 :     let wire = Connector::new(right(f32::NAN, 0.0), left(10.0, 0.0));
     178            1 :     let route = router(&[]).route(&wire);
     179            1 :     assert!(!route.routed);
     180            1 :     assert_eq!(route.points.len(), 2);
     181            1 : }
     182              : 
     183              : #[test]
     184            1 : fn routes_are_deterministic() {
     185            1 :     let (nodes, wires) = rows_and_columns();
     186            1 :     let mut a = router(&nodes);
     187            1 :     let first = a.route_all(&wires);
     188            1 :     let again = a.route_all(&wires);
     189            1 :     let fresh = router(&nodes).route_all(&wires);
     190            1 :     assert_eq!(first, again);
     191            1 :     assert_eq!(first, fresh);
     192           24 :     for r in &first {
     193           24 :         assert!(r.routed);
     194           24 :         assert_orthogonal(r);
     195           24 :         assert_clear(r, &nodes);
     196              :     }
     197            1 : }
     198              : 
     199              : #[test]
     200            1 : fn wires_sharing_a_channel_are_spread_apart() {
     201            1 :     let (nodes, wires) = rows_and_columns();
     202            1 :     let routes = router(&nodes).route_all(&wires);
     203           24 :     for (i, ri) in routes.iter().enumerate() {
     204          276 :         for rj in &routes[i + 1..] {
     205          276 :             for a in inner_segments(ri) {
     206          198 :                 for b in inner_segments(rj) {
     207          153 :                     assert!(!overlap(a, b), "{a:?} on top of {b:?}");
     208              :                 }
     209              :             }
     210              :         }
     211              :     }
     212           24 :     for r in &routes {
     213           24 :         assert_orthogonal(r);
     214           24 :         assert_clear(r, &nodes);
     215           24 :     }
     216            1 : }
     217              : 
     218              : /// A route's segments that may move: not those at its ports.
     219          474 : fn inner_segments(r: &Route) -> Vec<(Point, Point)> {
     220          474 :     let n = r.points.len();
     221          474 :     (1..n.saturating_sub(2))
     222          474 :         .map(|k| (r.points[k], r.points[k + 1]))
     223          474 :         .collect()
     224          474 : }
     225              : 
     226              : /// Whether two segments lie on one line and share a stretch of it.
     227          153 : fn overlap(a: (Point, Point), b: (Point, Point)) -> bool {
     228          918 :     let horizontal = |s: (Point, Point)| s.0.y == s.1.y;
     229          153 :     if horizontal(a) != horizontal(b) {
     230            0 :         return false;
     231          153 :     }
     232              :     // Along the segments, and across.
     233          612 :     let uv = |p: Point| {
     234          612 :         if horizontal(a) {
     235            0 :             (p.x, p.y)
     236              :         } else {
     237          612 :             (p.y, p.x)
     238              :         }
     239          612 :     };
     240          153 :     let ((a0, va), (a1, _)) = (uv(a.0), uv(a.1));
     241          153 :     let ((b0, vb), (b1, _)) = (uv(b.0), uv(b.1));
     242          153 :     if (va - vb).abs() > 1e-3 {
     243          153 :         return false;
     244            0 :     }
     245            0 :     let lo = a0.min(a1).max(b0.min(b1));
     246            0 :     let hi = a0.max(a1).min(b0.max(b1));
     247            0 :     hi - lo > 1e-3
     248          153 : }
     249              : 
     250              : /// Six rows of five nodes, each node wired to one in the next column.
     251            2 : fn rows_and_columns() -> (Vec<Rect>, Vec<Connector>) {
     252            2 :     let nodes: Vec<Rect> = (0..6)
     253           60 :         .flat_map(|r| (0..5).map(move |c| rect(c as f32 * 240.0, r as f32 * 130.0, 180.0, 70.0)))
     254            2 :         .collect();
     255            2 :     let wires: Vec<Connector> = (0..6)
     256           12 :         .flat_map(|r| {
     257           48 :             (0..4).map(move |c| {
     258           48 :                 let y = r as f32 * 130.0;
     259           48 :                 let to_row = (r + c) % 6;
     260           48 :                 Connector::new(
     261           48 :                     right(c as f32 * 240.0 + 180.0, y + 35.0),
     262           48 :                     left((c + 1) as f32 * 240.0, to_row as f32 * 130.0 + 35.0),
     263              :                 )
     264           48 :             })
     265           12 :         })
     266            2 :         .collect();
     267            2 :     (nodes, wires)
     268            2 : }
     269              : 
     270              : #[test]
     271            1 : fn a_batch_routes_like_single_calls() {
     272            1 :     let nodes = [
     273            1 :         rect(0.0, 0.0, 100.0, 60.0),
     274            1 :         rect(0.0, 120.0, 100.0, 60.0),
     275            1 :         rect(300.0, 60.0, 100.0, 60.0),
     276            1 :     ];
     277            1 :     let wires = [
     278            1 :         Connector::new(right(100.0, 30.0), left(300.0, 80.0)),
     279            1 :         Connector::new(right(100.0, 150.0), left(300.0, 100.0)),
     280            1 :     ];
     281            1 :     let mut r = router(&nodes);
     282            1 :     let batch = r.route_all(&wires);
     283            2 :     for (w, b) in wires.iter().zip(&batch) {
     284            2 :         let one = r.route(w);
     285            2 :         assert!(one.routed && b.routed);
     286            2 :         assert_eq!(one.bends(), b.bends());
     287            2 :         assert_eq!(one.length(), b.length());
     288              :     }
     289            1 : }
     290              : 
     291              : #[test]
     292            1 : fn the_fingerprint_follows_the_layout() {
     293            1 :     let nodes = [rect(0.0, 0.0, 100.0, 60.0)];
     294            1 :     let wires = [Connector::new(right(100.0, 30.0), left(300.0, 30.0))];
     295            1 :     let r = router(&nodes);
     296            1 :     let key = r.fingerprint(&wires);
     297            1 :     assert_eq!(key, router(&nodes).fingerprint(&wires));
     298            1 :     assert_ne!(
     299              :         key,
     300            1 :         router(&[rect(0.0, 0.0, 100.0, 61.0)]).fingerprint(&wires)
     301              :     );
     302            1 :     let moved = [Connector::new(right(100.0, 31.0), left(300.0, 30.0))];
     303            1 :     assert_ne!(key, r.fingerprint(&moved));
     304            1 :     let mut wider = router(&nodes);
     305            1 :     wider.set_options(Options {
     306            1 :         margin: 20.0,
     307            1 :         ..Options::default()
     308            1 :     });
     309            1 :     assert_ne!(key, wider.fingerprint(&wires));
     310            1 : }
     311              : 
     312              : #[test]
     313            1 : fn invalid_obstacles_are_ignored() {
     314            1 :     let bad = Rect {
     315            1 :         min: Point::new(10.0, 10.0),
     316            1 :         max: Point::new(f32::INFINITY, 0.0),
     317            1 :     };
     318            1 :     let r = router(&[bad]);
     319            1 :     assert!(r.obstacles().is_empty());
     320            1 : }
        

Generated by: LCOV version 2.0-1