diff --git a/Cargo.lock b/Cargo.lock
index 2881d08cf..fe64bd21a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -60,9 +60,9 @@ dependencies = [
[[package]]
name = "anstyle-query"
-version = "1.0.3"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5"
+checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391"
dependencies = [
"windows-sys 0.52.0",
]
@@ -176,9 +176,9 @@ dependencies = [
[[package]]
name = "cc"
-version = "1.0.98"
+version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f"
+checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695"
dependencies = [
"jobserver",
"libc",
@@ -249,9 +249,9 @@ dependencies = [
[[package]]
name = "clap"
-version = "4.5.4"
+version = "4.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
+checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f"
dependencies = [
"clap_builder",
"clap_derive",
@@ -259,9 +259,9 @@ dependencies = [
[[package]]
name = "clap_builder"
-version = "4.5.2"
+version = "4.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
+checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f"
dependencies = [
"anstream",
"anstyle",
@@ -272,21 +272,21 @@ dependencies = [
[[package]]
name = "clap_derive"
-version = "4.5.4"
+version = "4.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64"
+checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6"
dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
- "syn 2.0.66",
+ "syn",
]
[[package]]
name = "clap_lex"
-version = "0.7.0"
+version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
+checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70"
[[package]]
name = "colorchoice"
@@ -378,9 +378,9 @@ dependencies = [
[[package]]
name = "cucumber"
-version = "0.21.0"
+version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2940c675f8b0dd864bfedb4283d5fa07b8799eed59a4f7b09fb1257b18c88a1e"
+checksum = "6cd12917efc3a8b069a4975ef3cb2f2d835d42d04b3814d90838488f9dd9bf69"
dependencies = [
"anyhow",
"clap",
@@ -410,9 +410,9 @@ dependencies = [
[[package]]
name = "cucumber-codegen"
-version = "0.21.0"
+version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a5c9c7e0af8103f81ab300a21be3df1d57a003a151cf0bf41fdd343f85d14552"
+checksum = "9e19cd9e8e7cfd79fbf844eb6a7334117973c01f6bad35571262b00891e60f1c"
dependencies = [
"cucumber-expressions",
"inflections",
@@ -420,7 +420,7 @@ dependencies = [
"proc-macro2",
"quote",
"regex",
- "syn 2.0.66",
+ "syn",
"synthez",
]
@@ -440,13 +440,13 @@ dependencies = [
[[package]]
name = "derive_more"
-version = "0.99.17"
+version = "0.99.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
+checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce"
dependencies = [
"proc-macro2",
"quote",
- "syn 1.0.109",
+ "syn",
]
[[package]]
@@ -597,7 +597,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.66",
+ "syn",
]
[[package]]
@@ -673,7 +673,7 @@ dependencies = [
"quote",
"serde",
"serde_json",
- "syn 2.0.66",
+ "syn",
"textwrap",
"thiserror",
"typed-builder",
@@ -689,7 +689,7 @@ dependencies = [
"bstr",
"log",
"regex-automata",
- "regex-syntax 0.8.3",
+ "regex-syntax 0.8.4",
]
[[package]]
@@ -807,9 +807,9 @@ checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
[[package]]
name = "itertools"
-version = "0.12.1"
+version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
+checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
@@ -849,7 +849,7 @@ dependencies = [
"proc-macro2",
"quote",
"regex",
- "syn 2.0.66",
+ "syn",
]
[[package]]
@@ -890,9 +890,9 @@ checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "memchr"
-version = "2.7.2"
+version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "minimal-lexical"
@@ -902,9 +902,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
-version = "0.7.3"
+version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae"
+checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
dependencies = [
"adler",
]
@@ -1058,7 +1058,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.66",
+ "syn",
]
[[package]]
@@ -1081,9 +1081,9 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
[[package]]
name = "proc-macro2"
-version = "1.0.84"
+version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6"
+checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23"
dependencies = [
"unicode-ident",
]
@@ -1105,25 +1105,25 @@ checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
[[package]]
name = "regex"
-version = "1.10.4"
+version = "1.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
+checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
- "regex-syntax 0.8.3",
+ "regex-syntax 0.8.4",
]
[[package]]
name = "regex-automata"
-version = "0.4.6"
+version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
+checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
dependencies = [
"aho-corasick",
"memchr",
- "regex-syntax 0.8.3",
+ "regex-syntax 0.8.4",
]
[[package]]
@@ -1134,9 +1134,9 @@ checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
[[package]]
name = "regex-syntax"
-version = "0.8.3"
+version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
+checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
[[package]]
name = "ring"
@@ -1230,7 +1230,7 @@ dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
- "syn 2.0.66",
+ "syn",
]
[[package]]
@@ -1256,7 +1256,7 @@ checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.66",
+ "syn",
]
[[package]]
@@ -1333,7 +1333,7 @@ checksum = "0eb01866308440fc64d6c44d9e86c5cc17adfe33c4d6eed55da9145044d0ffc1"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.66",
+ "syn",
]
[[package]]
@@ -1356,20 +1356,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "subtle"
-version = "2.5.0"
+version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
-
-[[package]]
-name = "syn"
-version = "1.0.109"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
+checksum = "0d0208408ba0c3df17ed26eb06992cb1a1268d41b2c0e12e65203fbe3972cee5"
[[package]]
name = "syn"
@@ -1388,7 +1377,7 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3d2c2202510a1e186e63e596d9318c91a8cbe85cd1a56a7be0c333e5f59ec8d"
dependencies = [
- "syn 2.0.66",
+ "syn",
"synthez-codegen",
"synthez-core",
]
@@ -1399,7 +1388,7 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f724aa6d44b7162f3158a57bccd871a77b39a4aef737e01bcdff41f4772c7746"
dependencies = [
- "syn 2.0.66",
+ "syn",
"synthez-core",
]
@@ -1412,7 +1401,7 @@ dependencies = [
"proc-macro2",
"quote",
"sealed",
- "syn 2.0.66",
+ "syn",
]
[[package]]
@@ -1453,7 +1442,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.66",
+ "syn",
]
[[package]]
@@ -1534,7 +1523,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.66",
+ "syn",
]
[[package]]
@@ -1589,7 +1578,7 @@ checksum = "29a3151c41d0b13e3d011f98adc24434560ef06673a155a6c7f66b9879eecce2"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.66",
+ "syn",
]
[[package]]
@@ -1627,9 +1616,9 @@ dependencies = [
[[package]]
name = "unicode-width"
-version = "0.1.12"
+version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6"
+checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
[[package]]
name = "untrusted"
@@ -1657,9 +1646,9 @@ dependencies = [
[[package]]
name = "url"
-version = "2.5.0"
+version = "2.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
+checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c"
dependencies = [
"form_urlencoded",
"idna",
@@ -1668,9 +1657,9 @@ dependencies = [
[[package]]
name = "utf8parse"
-version = "0.2.1"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "valuable"
@@ -1702,9 +1691,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "webpki-roots"
-version = "0.26.1"
+version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009"
+checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd"
dependencies = [
"rustls-pki-types",
]
@@ -1951,9 +1940,9 @@ dependencies = [
[[package]]
name = "zstd-sys"
-version = "2.0.10+zstd.1.5.6"
+version = "2.0.11+zstd.1.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa"
+checksum = "75652c55c0b6f3e6f12eb786fe1bc960396bf05a1eb3bf1f3691c3610ac2e6d4"
dependencies = [
"cc",
"pkg-config",
diff --git a/Cargo.toml b/Cargo.toml
index eb9356c51..6eb484c7d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,9 +6,9 @@ edition = "2021"
[dependencies]
cheap-ruler = "0.4.0"
chksum-md5 = "0.0.0"
-clap = "4.5.4"
+clap = "4.5.7"
colored = "2.1.0"
-cucumber = { version = "0.21.0", features = ["tracing"] }
+cucumber = { version = "0.21.1", features = ["tracing"] }
flatbuffers = "24.3.25"
futures = "0.3.30"
geo-types = "0.7.13"
@@ -26,6 +26,9 @@ harness = false
[profile.bench]
debug = true
+[profile.release]
+debug = true
+
[build-dependencies]
flatc-rust = "0.2.0"
serde = { version = "1.0.203", features = ["serde_derive"] }
diff --git a/features/car/bridge.feature b/features/car/bridge.feature
index 7c43f24cc..efb18c0d4 100644
--- a/features/car/bridge.feature
+++ b/features/car/bridge.feature
@@ -60,5 +60,5 @@ Feature: Car - Handle driving
When I route I should get
| from | to | route | modes | speed | turns |
| a | g | abc,cde,efg,efg | driving,driving,driving,driving | 7 km/h | depart,new name right,new name left,arrive |
- | c | e | cde,cde | driving,driving | 2 km/h | depart,arrive |
- | e | c | cde,cde | driving,driving | 2 km/h | depart,arrive |
+ | c | e | cde,cde | driving,driving | 2.4 km/h | depart,arrive |
+ | e | c | cde,cde | driving,driving | 2.4 km/h | depart,arrive |
diff --git a/tests/common/f64_utils.rs b/tests/common/f64_utils.rs
index aafc74c84..2bc9ddbc6 100644
--- a/tests/common/f64_utils.rs
+++ b/tests/common/f64_utils.rs
@@ -2,3 +2,16 @@ pub fn approx_equal(a: f32, b: f32, dp: u8) -> bool {
let p = 10f32.powi(-(dp as i32));
(a - b).abs() < p
}
+
+pub fn aprox_equal_within_percentage_range(actual: f64, expectation: f64, percentage: u8) -> bool {
+ assert!(percentage <= 100);
+ let factor = 0.01 * percentage as f64;
+ actual >= expectation - (factor * expectation) && actual <= expectation + (factor * expectation)
+}
+
+pub fn approx_equal_within_offset_range(actual: f64, expectation: f64, offset: f64) -> bool {
+ assert!(offset >= 0., "offset must be positive");
+ actual >= expectation - offset && actual <= expectation + offset
+}
+
+// TODO: test coverage
diff --git a/tests/common/osm_db.rs b/tests/common/osm_db.rs
index c33bcae0c..aa7af53b9 100644
--- a/tests/common/osm_db.rs
+++ b/tests/common/osm_db.rs
@@ -125,7 +125,7 @@ mod tests {
let actual = osm_db.to_xml();
let expected = "\n\n\t\n\t\t\n\t\n\t\n\t\t\n\t\n\t\n\t\t\n\t\t\n\t\n\n";
- println!("{actual}");
+ // println!("{actual}");
assert_eq!(actual, expected);
}
}
diff --git a/tests/common/osrm_world.rs b/tests/common/osrm_world.rs
index d623f771d..0017c3ec6 100644
--- a/tests/common/osrm_world.rs
+++ b/tests/common/osrm_world.rs
@@ -18,6 +18,7 @@ const DEFAULT_ORIGIN: Location = Location {
latitude: 1.0f32,
};
const DEFAULT_GRID_SIZE: f32 = 100.;
+const WAY_SPACING: f32 = 100.;
#[derive(Debug, World)]
pub struct OSRMWorld {
@@ -35,9 +36,12 @@ pub struct OSRMWorld {
pub extraction_parameters: Vec,
pub request_with_flatbuffers: bool,
+ pub bearings: Option,
pub grid_size: f32,
pub origin: Location,
+ pub way_spacing: f32,
+
task: LocalTask,
agent: ureq::Agent,
}
@@ -56,8 +60,10 @@ impl Default for OSRMWorld {
osm_db: Default::default(),
extraction_parameters: Default::default(),
request_with_flatbuffers: Default::default(),
+ bearings: None,
grid_size: DEFAULT_GRID_SIZE,
origin: DEFAULT_ORIGIN,
+ way_spacing: WAY_SPACING,
task: LocalTask::default(),
agent: ureq::AgentBuilder::new()
.timeout_read(Duration::from_secs(5))
@@ -212,7 +218,7 @@ impl OSRMWorld {
pub fn nearest(
&mut self,
query_location: &Location,
- request_with_flatbuffers: bool,
+ // request_with_flatbuffers: bool,
) -> NearestResponse {
self.start_routed();
@@ -220,7 +226,7 @@ impl OSRMWorld {
"http://localhost:5000/nearest/v1/{}/{:?},{:?}",
self.profile, query_location.longitude, query_location.latitude
);
- if request_with_flatbuffers {
+ if self.request_with_flatbuffers {
url += ".flatbuffers";
}
let call = self.agent.get(&url).call();
@@ -230,42 +236,43 @@ impl OSRMWorld {
Err(e) => panic!("http error: {e}"),
};
- let response = match request_with_flatbuffers {
+ let response = match self.request_with_flatbuffers {
true => NearestResponse::from_flatbuffer(body),
false => NearestResponse::from_json_reader(body),
};
response
}
- pub fn route(
- &mut self,
- from_location: &Location,
- to_location: &Location,
- request_with_flatbuffers: bool,
- ) -> RouteResponse {
+ pub fn route(&mut self, waypoints: &[Location]) -> RouteResponse {
self.start_routed();
+ let waypoint_string = waypoints
+ .iter()
+ .map(|location| format!("{:?},{:?}", location.longitude, location.latitude))
+ .collect::>()
+ .join(";");
+
let mut url = format!(
- "http://localhost:5000/route/v1/{}/{:?},{:?};{:?},{:?}?steps=true&alternatives=false",
+ "http://localhost:5000/route/v1/{}/{waypoint_string}?steps=true&alternatives=false",
self.profile,
- from_location.longitude,
- from_location.latitude,
- to_location.longitude,
- to_location.latitude,
);
- if request_with_flatbuffers {
+ if self.request_with_flatbuffers {
url += ".flatbuffers";
}
+ if let Some(bearings) = &self.bearings {
+ url += "&bearings=";
+ url += bearings;
+ }
// println!("url: {url}");
let call = self.agent.get(&url).call();
let body = match call {
Ok(response) => response.into_reader(),
- Err(e) => panic!("http error: {e}"),
+ Err(_e) => return RouteResponse::default(),
};
let text = std::io::read_to_string(body).unwrap();
- let response = match request_with_flatbuffers {
+ let response = match self.request_with_flatbuffers {
true => unimplemented!("RouteResponse::from_flatbuffer(body)"),
false => RouteResponse::from_string(&text),
};
diff --git a/tests/common/route_response.rs b/tests/common/route_response.rs
index ebb8f2a65..920d5be0d 100644
--- a/tests/common/route_response.rs
+++ b/tests/common/route_response.rs
@@ -1,12 +1,26 @@
use serde::Deserialize;
-use super::nearest_response::Waypoint;
+use super::{location::Location, nearest_response::Waypoint};
+
+#[derive(Deserialize, Default, Debug)]
+pub struct Maneuver {
+ pub bearing_after: f64,
+ pub bearing_before: f64,
+ pub location: Location,
+ pub modifier: Option, // TODO: should be an enum
+ pub r#type: String, // TODO: should be an enum
+}
#[derive(Deserialize, Default, Debug)]
pub struct Step {
+ pub geometry: String,
+ pub mode: String,
+ pub maneuver: Maneuver,
pub name: String,
pub pronunciation: Option,
pub r#ref: Option,
+ pub duration: f64,
+ pub distance: f64,
}
// #[derive(Deserialize, Debug)]
@@ -34,7 +48,7 @@ pub struct Route {
pub distance: f64,
}
-#[derive(Deserialize, Debug)]
+#[derive(Debug, Default, Deserialize)]
pub struct RouteResponse {
pub code: String,
pub routes: Vec,
@@ -43,13 +57,13 @@ pub struct RouteResponse {
}
impl RouteResponse {
- // pub fn from_json_reader(reader: impl std::io::Read) -> Self {
- // let response = match serde_json::from_reader::<_, Self>(reader) {
- // Ok(response) => response,
- // Err(e) => panic!("parsing error {e}"),
- // };
- // response
- // }
+ pub fn from_json_reader(reader: impl std::io::Read) -> Self {
+ let response = match serde_json::from_reader::<_, Self>(reader) {
+ Ok(response) => response,
+ Err(e) => panic!("parsing error {e}"),
+ };
+ response
+ }
pub fn from_string(input: &str) -> Self {
// println!("{input}");
diff --git a/tests/cucumber.rs b/tests/cucumber.rs
index 8983d7c41..359ae5327 100644
--- a/tests/cucumber.rs
+++ b/tests/cucumber.rs
@@ -3,18 +3,28 @@ mod common;
use cheap_ruler::CheapRuler;
use clap::Parser;
use common::{
- cli_arguments::Args, dot_writer::DotWriter, f64_utils::approx_equal,
- hash_util::md5_of_osrm_executables, location::Location, osm::OSMWay, osrm_world::OSRMWorld,
+ cli_arguments::Args,
+ dot_writer::DotWriter,
+ f64_utils::{
+ approx_equal, approx_equal_within_offset_range, aprox_equal_within_percentage_range,
+ },
+ hash_util::md5_of_osrm_executables,
+ location::Location,
+ osm::OSMWay,
+ osrm_world::OSRMWorld,
};
use core::panic;
use cucumber::{
gherkin::{Step, Table},
- given, when, World, WriterExt,
+ given, then, when, World, WriterExt,
};
use futures::{future, FutureExt};
use geo_types::Point;
use log::debug;
-use std::collections::HashMap;
+use std::{
+ collections::{HashMap, HashSet},
+ iter::zip,
+};
fn offset_origin_by(dx: f32, dy: f32, origin: Location, grid_size: f32) -> Location {
let ruler = CheapRuler::new(origin.latitude, cheap_ruler::DistanceUnit::Meters);
@@ -42,10 +52,10 @@ fn set_profile(world: &mut OSRMWorld, profile: String) {
fn set_node_locations(world: &mut OSRMWorld, step: &Step) {
let table = step.table().expect("cannot get table");
let header = table.rows.first().expect("node locations table empty");
- assert_eq!(header.len(), 3, "header needs to define three columns");
- assert_eq!(
- header[0], "node",
- "first column needs to be 'node' indicating the one-letter name"
+ assert!(header.len() >= 3, "header needs to define three columns");
+ assert!(
+ header.contains(&"node".to_string()),
+ "a column needs to be 'node' indicating the one-letter name"
);
// the following lookup allows to define lat lon columns in any order
let header_lookup: HashMap<&str, usize> = header
@@ -61,7 +71,10 @@ fn set_node_locations(world: &mut OSRMWorld, step: &Step) {
});
table.rows.iter().skip(1).for_each(|row| {
- assert_eq!(3, row.len());
+ assert!(
+ row.len() >= 3,
+ "nod locations must at least specify three tables: node, lat, and lon"
+ );
assert_eq!(row[0].len(), 1, "node name not in [0..9][a..z]");
let name = &row[0].chars().next().expect("node name cannot be empty"); // the error is unreachable
let lon = &row[header_lookup["lon"]];
@@ -70,9 +83,18 @@ fn set_node_locations(world: &mut OSRMWorld, step: &Step) {
latitude: lat.parse::().expect("lat {lat} needs to be a f64"),
longitude: lon.parse::().expect("lon {lon} needs to be a f64"),
};
+ let id = match header_lookup.get("id") {
+ Some(index) => {
+ let id = row[*index]
+ .parse::()
+ .expect("id of a node must be u64 number");
+ Some(id)
+ }
+ _ => None,
+ };
match name {
'0'...'9' => world.add_location(*name, location),
- 'a'...'z' => world.add_osm_node(*name, location, None),
+ 'a'...'z' => world.add_osm_node(*name, location, id),
_ => unreachable!("node name not in [0..9][a..z]"),
}
});
@@ -100,7 +122,8 @@ fn set_node_map(world: &mut OSRMWorld, step: &Step) {
match name {
'0'...'9' => world.add_location(name, location),
'a'...'z' => world.add_osm_node(name, location, None),
- _ => unreachable!("node name not in [0..9][a..z]: {docstring}"),
+ _ => {} // TODO: unreachable!("node name not in [0..9][a..z]: {docstring}"),
+ // tests contain random characters.
}
});
});
@@ -166,7 +189,7 @@ fn set_ways(world: &mut OSRMWorld, step: &Step) {
}
}
-fn parse_table_from_steps(table: &Option<&Table>) -> Vec> {
+fn parse_table_from_steps(table: &Option<&Table>) -> (Vec, Vec>) {
// parse query data
let table = table.expect("no query table specified");
// the following lookup allows to define lat lon columns in any order
@@ -187,19 +210,18 @@ fn parse_table_from_steps(table: &Option<&Table>) -> Vec
row_map
})
.collect();
- test_cases
- // TODO: also return the header
+ (header.clone(), test_cases)
}
#[when(regex = r"^I request nearest( with flatbuffers|) I should get$")]
fn request_nearest(world: &mut OSRMWorld, step: &Step, state: String) {
- let request_with_flatbuffers = state == " with flatbuffers";
+ world.request_with_flatbuffers = state == " with flatbuffers";
world.write_osm_file();
world.extract_osm_file();
// parse query data
- let test_cases = parse_table_from_steps(&step.table.as_ref());
+ let (_, test_cases) = parse_table_from_steps(&step.table.as_ref());
// run test cases
for test_case in &test_cases {
@@ -212,7 +234,7 @@ fn request_nearest(world: &mut OSRMWorld, step: &Step, state: String) {
.expect("node name is one char long"),
);
- let response = world.nearest(&query_location, request_with_flatbuffers);
+ let response = world.nearest(&query_location);
let expected_location = &world.get_location(
test_case
@@ -246,117 +268,456 @@ fn request_nearest(world: &mut OSRMWorld, step: &Step, state: String) {
}
}
-#[when(regex = r"^I route( with flatbuffers|) I should get$")]
-fn request_route(world: &mut OSRMWorld, step: &Step, state: String) {
- let request_with_flatbuffers = state == " with flatbuffers";
+#[then(expr = "routability should be")]
+fn routability(world: &mut OSRMWorld, step: &Step) {
world.write_osm_file();
world.extract_osm_file();
// TODO: preprocess
- let test_cases = parse_table_from_steps(&step.table.as_ref());
- for test_case in &test_cases {
- let from_location = world.get_location(
- test_case
- .get("from")
- .expect("node name is one char long")
- .chars()
- .next()
- .expect("node name is one char long"),
+ let (header, test_cases) = parse_table_from_steps(&step.table.as_ref());
+ // TODO: rename forw/backw to forw/backw_speed
+ let supported_headers = HashSet::<_>::from([
+ "forw",
+ "backw",
+ "bothw",
+ "forw_rate",
+ "backw_rate",
+ "bothw_rate",
+ ]);
+ if 0 == header
+ .iter()
+ .filter(|title| supported_headers.contains(title.as_str()))
+ .count()
+ {
+ panic!(
+ r#"*** routability table must contain either "forw", "backw", "bothw", "forw_rate" or "backw_mode" column"#
);
- let to_location = world.get_location(
- test_case
- .get("to")
- .expect("node name is one char long")
- .chars()
- .next()
- .expect("node name is one char long"),
- );
-
- let response = world.route(&from_location, &to_location, request_with_flatbuffers);
-
- if test_case.contains_key("route") {
- // NOTE: the following code ports logic from JavaScript that checks only properties of the first route
- let route = response
- .routes
- .first()
- .expect("no route returned")
- .legs
- .first()
- .expect("legs required")
- .steps
- .iter()
- .map(|step| step.name.clone())
- .collect::>()
- .join(",");
-
- assert_eq!(*test_case.get("route").expect("msg"), route);
- }
-
- if test_case.contains_key("pronunciations") {
- let pronunciations = response
- .routes
- .first()
- .expect("no route returned")
- .legs
- .first()
- .expect("legs required")
- .steps
- .iter()
- .map(|step| match &step.pronunciation {
- Some(p) => p.clone(),
- None => "".to_string(),
- })
- .collect::>()
- .join(",");
- assert_eq!(
- *test_case.get("pronunciations").expect("msg"),
- pronunciations
- );
- }
-
- if test_case.contains_key("ref") {
- let refs = response
- .routes
- .first()
- .expect("no route returned")
- .legs
- .first()
- .expect("legs required")
- .steps
- .iter()
- .map(|step| match &step.r#ref {
- Some(p) => p.clone(),
- None => "".to_string(),
- })
- .collect::>()
- .join(",");
- assert_eq!(*test_case.get("ref").expect("msg"), refs);
- }
- // TODO: more checks need to be implemented
-
- // TODO: check for unchecked test columns
}
- // unimplemented!("route");
+ test_cases
+ .iter()
+ .enumerate()
+ .for_each(|(index, test_case)| {
+ let source = offset_origin_by(
+ 1. + world.way_spacing * index as f32,
+ 0.,
+ world.origin,
+ world.grid_size,
+ );
+ let target = offset_origin_by(
+ 3. + world.way_spacing * index as f32,
+ 0.,
+ world.origin,
+ world.grid_size,
+ );
+ test_case
+ .iter()
+ .filter(|(title, _)| supported_headers.contains(title.as_str()))
+ .for_each(|(title, expectation)| {
+ let forward = title.starts_with("forw");
+ // println!("{direction}: >{expectation}<");
+ let response = match forward {
+ true => world.route(&vec![source, target]),
+ false => world.route(&vec![target, source]),
+ };
+ if expectation.is_empty() {
+ // if !response.routes.is_empty() {
+ // println!("> {title} {expectation}");
+ // println!("{response:?}");
+ // }
+
+ assert!(
+ response.routes.is_empty()
+ || response.routes.first().unwrap().distance == 0.,
+ "no route expected when result column {title} is unset"
+ );
+ } else if expectation.contains("km/h") {
+ assert!(
+ !response.routes.is_empty(),
+ "route expected when result column is set"
+ );
+
+ let (expected_speed, offset) =
+ extract_number_and_offset("km/h", expectation);
+ let route = response.routes.first().unwrap();
+ let actual_speed = route.distance / route.duration * 3.6;
+ assert!(
+ aprox_equal_within_percentage_range(
+ actual_speed,
+ expected_speed,
+ offset
+ ),
+ "{actual_speed} and {expected_speed} differ by more than {offset}"
+ );
+ } else if title.ends_with("_rate") {
+ assert!(!response.routes.is_empty());
+ let expected_rate = expectation
+ .parse::()
+ .expect("rate needs to be a number");
+ let route = response.routes.first().unwrap();
+ let actual_rate = route.distance / route.weight;
+ assert!(
+ aprox_equal_within_percentage_range(actual_rate, expected_rate, 1),
+ "{actual_rate} and {expected_rate} differ by more than 1%"
+ );
+ } else {
+ unimplemented!("{title} = {expectation}");
+ }
+ });
+ });
+ // unimplemented!("{test_cases:#?}");
+}
+
+fn extract_number_and_offset(unit: &str, expectation: &str) -> (f64, u8) {
+ let tokens: Vec<_> = expectation
+ .split(unit)
+ .map(|token| token.trim())
+ .filter(|token| !token.is_empty())
+ .collect();
+ // println!("{tokens:?}");
+ let number = tokens[0]
+ .parse::()
+ .expect("{expectation} needs to define a speed");
+ let offset = match tokens.len() {
+ 1 => 5u8, // TODO: the JS fuzzy matcher has a default margin of 5% for absolute comparsions. This is imprecise
+ 2 => tokens[1]
+ .replace("+-", "")
+ .trim()
+ .parse()
+ .expect(&format!("{} needs to specify a number", tokens[1])),
+ _ => unreachable!("expectations can't be parsed"),
+ };
+ (number, offset)
+}
+
+fn extract_number_vector_and_offset(unit: &str, expectation: &str) -> (Vec, u8) {
+ let expectation = expectation.replace(",", "");
+ let tokens: Vec<_> = expectation
+ .split(unit)
+ .map(|token| token.trim())
+ .filter(|token| !token.is_empty())
+ .collect();
+ let numbers = tokens
+ .iter()
+ .filter(|token| !token.contains("+-"))
+ .map(|token| {
+ token
+ .parse::()
+ .expect("input needs to specify a number followed by unit")
+ })
+ .collect();
+
+ // panic!("{tokens:?}");
+ let offset = match tokens.len() {
+ 1 => 5u8, // TODO: the JS fuzzy matcher has a default margin of 5% for absolute comparsions. This is imprecise
+ _ => tokens
+ .last()
+ .expect("offset needs to be specified")
+ .replace("+-", "")
+ .trim()
+ .parse()
+ .expect(&format!("{} needs to specify a number", tokens[1])),
+ // _ => unreachable!("expectations can't be parsed"),
+ };
+ (numbers, offset)
+}
+
+enum WaypointsOrLocation {
+ Waypoints,
+ Locations,
+ // Undefined,
+}
+
+pub fn get_location_specification(test_case: &HashMap) -> WaypointsOrLocation {
+ assert!(
+ test_case.contains_key("from")
+ && test_case.contains_key("to")
+ && !test_case.contains_key("waypoints")
+ || !test_case.contains_key("from")
+ && !test_case.contains_key("to")
+ && test_case.contains_key("waypoints"),
+ "waypoints need to be specified by either from/to columns or a waypoint column, but not both"
+ );
+
+ if test_case.contains_key("from")
+ && test_case.contains_key("to")
+ && !test_case.contains_key("waypoints")
+ {
+ return WaypointsOrLocation::Locations;
+ }
+
+ if !test_case.contains_key("from")
+ && !test_case.contains_key("to")
+ && test_case.contains_key("waypoints")
+ {
+ return WaypointsOrLocation::Waypoints;
+ }
+ unreachable!("waypoints need to be specified by either from/to columns or a waypoint column, but not both");
+ // WaypointsOrLocation::Undefined
+}
+
+#[when(regex = r"^I route( with flatbuffers|) I should get$")]
+fn request_route(world: &mut OSRMWorld, step: &Step, state: String) {
+ world.request_with_flatbuffers = state == " with flatbuffers";
+ world.write_osm_file();
+ world.extract_osm_file();
+ // TODO: preprocess
+
+ let (_, test_cases) = parse_table_from_steps(&step.table.as_ref());
+ for test_case in &test_cases {
+ let waypoints = match get_location_specification(&test_case) {
+ WaypointsOrLocation::Waypoints => {
+ let locations: Vec = test_case
+ .get("waypoints")
+ .expect("locations specified as waypoints")
+ .split(",")
+ .into_iter()
+ .map(|name| {
+ assert!(name.len() == 1, "node names need to be of length one");
+ world.get_location(name.chars().next().unwrap())
+ })
+ .collect();
+ locations
+ }
+ WaypointsOrLocation::Locations => {
+ let from_location = world.get_location(
+ test_case
+ .get("from")
+ .expect("test case doesn't have a 'from' column")
+ .chars()
+ .next()
+ .expect("from node name is one char long"),
+ );
+ let to_location = world.get_location(
+ test_case
+ .get("to")
+ .expect("test case doesn't have a 'to' column")
+ .chars()
+ .next()
+ .expect("to node name is one char long"),
+ );
+ vec![from_location, to_location]
+ }
+ };
+
+ if let Some(bearing) = test_case.get("bearings").cloned() {
+ world.bearings = Some(bearing.replace(" ", ";"));
+ }
+
+ let response = world.route(&waypoints);
+
+ test_case
+ .iter()
+ .map(|(column_title, expectation)| (column_title.as_str(), expectation.as_str()))
+ .for_each(|(case, expectation)| match case {
+ "from" | "to" | "bearings"=> {}, // ignore input columns
+ "route" => {
+ let route = if expectation.is_empty() {
+ assert!(response.routes.is_empty());
+ String::new()
+ } else {
+ response
+ .routes
+ .first()
+ .expect("no route returned")
+ .legs
+ .iter()
+ .map(|leg| {
+ leg.steps
+ .iter()
+ .map(|step| step.name.clone())
+ .collect::>()
+ .join(",")
+ }).collect::>()
+ .join(",")
+
+ };
+
+ assert_eq!(expectation, route);
+ },
+ "pronunciations" => {
+ let pronunciations = response
+ .routes
+ .first()
+ .expect("no route returned")
+ .legs
+ .first()
+ .expect("legs required")
+ .steps
+ .iter()
+ .map(|step| match &step.pronunciation {
+ Some(p) => p.clone(),
+ None => "".to_string(),
+ })
+ .collect::>()
+ .join(",");
+ assert_eq!(expectation, pronunciations);
+ },
+ "ref" => {
+ let refs = response
+ .routes
+ .first()
+ .expect("no route returned")
+ .legs
+ .first()
+ .expect("legs required")
+ .steps
+ .iter()
+ .map(|step| match &step.r#ref {
+ Some(p) => p.clone(),
+ None => "".to_string(),
+ })
+ .collect::>()
+ .join(",");
+ assert_eq!(expectation, refs);
+ },
+ "speed" => {
+ let route = response.routes.first().expect("no route returned");
+ let actual_speed = route.distance / route.duration * 3.6;
+ let (expected_speed, offset) = extract_number_and_offset("km/h", expectation);
+ // println!("{actual_speed} == {expected_speed} +- {offset}");
+ assert!(
+ aprox_equal_within_percentage_range(actual_speed, expected_speed, offset),
+ "actual time {actual_speed} not equal to expected value {expected_speed}"
+ );
+ },
+ "modes" => {
+ let route = response.routes.first().expect("no route returned");
+ let actual_modes = route
+ .legs
+ .iter()
+ .map(|leg| {
+ leg.steps
+ .iter()
+ .map(|step| step.mode.clone())
+ .collect::>()
+ .join(",")
+ })
+ .collect::>()
+ .join(",");
+ assert_eq!(actual_modes, expectation);
+ },
+ "turns" => {
+ let route = response.routes.first().expect("no route returned");
+ let actual_turns = route
+ .legs
+ .iter()
+ .map(|leg| {
+ leg.steps
+ .iter()
+ .map(|step| {
+ let prefix = step.maneuver.r#type.clone();
+ let suffix = match &step.maneuver.modifier {
+ Some(modifier) => " ".to_string() + &modifier,
+ None => "".into(),
+ };
+ prefix + &suffix
+ })
+ .collect::>()
+ .join(",")
+ })
+ .collect::>()
+ .join(",");
+ assert_eq!(actual_turns, expectation);
+ },
+ "time" => {
+ let actual_time = response.routes.first().expect("no route returned").duration;
+ let (expected_time, offset) = extract_number_and_offset("s", expectation);
+ // println!("{actual_time} == {expected_time} +- {offset}");
+ assert!(
+ approx_equal_within_offset_range(actual_time, expected_time, offset as f64),
+ "actual time {actual_time} not equal to expected value {expected_time}"
+ );
+ },
+ "times" => {
+ // TODO: go over steps
+
+ let actual_times : Vec= response.routes.first().expect("no route returned").legs.iter().map(|leg| {
+ leg.steps.iter().map(|step| step.duration).collect::>()
+ }).flatten().collect();
+ let (expected_times, offset) = extract_number_vector_and_offset("s", expectation);
+ println!("{actual_times:?} == {expected_times:?} +- {offset}");
+ assert_eq!(actual_times.len(), expected_times.len(), "times mismatch: {actual_times:?} != {expected_times:?} +- {offset}");
+
+ zip(actual_times, expected_times).for_each(|(actual_time, expected_time)| {
+ assert!(approx_equal_within_offset_range(actual_time, expected_time, offset as f64),
+ "actual time {actual_time} not equal to expected value {expected_time}");
+ });
+ },
+ "distances" => {
+
+ println!("{:?}",response.routes.first().expect("no route returned"));
+ // TODO: go over steps
+ let actual_distances : Vec = response.routes.first().expect("no route returned").legs.iter().map(|leg| leg.distance).collect();
+ let (expected_distances, offset) = extract_number_vector_and_offset("m", expectation);
+ println!("{expected_distances:?} == {actual_distances:?}");
+ println!("!");
+ assert_eq!(expected_distances.len(), actual_distances.len(), "distances mismatch {expected_distances:?} != {actual_distances:?} +- {offset}");
+
+ zip(actual_distances, expected_distances).for_each(|(actual_distance, expected_distance)| {
+ assert!(approx_equal_within_offset_range(actual_distance, expected_distance, offset as f64),
+ "actual distance {actual_distance} not equal to expected value {expected_distance}");
+ });
+ // // println!("{actual_time} == {expected_time} +- {offset}");
+ // assert!(
+ // approx_equal_within_offset_range(actual_time, expected_time, offset as f64),
+ // "actual time {actual_time} not equal to expected value {expected_time}"
+ // );
+ },
+ "weight" => {
+ let actual_weight = response.routes.first().expect("no route returned").weight;
+ let (expected_weight, offset) = extract_number_and_offset("s", expectation);
+ // println!("{actual_weight} == {expected_weight} +- {offset}");
+ assert!(
+ approx_equal_within_offset_range(
+ actual_weight,
+ expected_weight,
+ offset as f64
+ ),
+ "actual time {actual_weight} not equal to expected value {expected_weight}"
+ );
+ },
+ "distance" => {
+ let actual_distance = response.routes.first().expect("no route returned").distance;
+ let (expected_distance, offset) = extract_number_and_offset("m", expectation);
+ assert!(
+ approx_equal_within_offset_range(
+ actual_distance,
+ expected_distance,
+ offset as f64
+ ),
+ "actual time {actual_distance} not equal to expected value {expected_distance}"
+ );
+ },
+ "waypoints" => {},
+ // TODO: more checks need to be implemented
+ _ => {
+ let msg = format!("case {case} = {expectation} not implemented");
+ unimplemented!("{msg}");
+ }
+ });
+ }
}
fn main() {
let args = Args::parse();
debug!("arguments: {:?}", args);
+ let digest = md5_of_osrm_executables().digest().to_hex_lowercase();
+
futures::executor::block_on(
OSRMWorld::cucumber()
.max_concurrent_scenarios(1)
.before(move |feature, _rule, scenario, world| {
world.scenario_id = common::scenario_id::scenario_id(scenario);
world.set_scenario_specific_paths_and_digests(feature.path.clone());
- world.osrm_digest = md5_of_osrm_executables().digest().to_hex_lowercase();
+ world.osrm_digest = digest.clone();
// TODO: clean up cache if needed? Or do in scenarios?
future::ready(()).boxed()
})
- .with_writer(DotWriter::default().normalized())
- .filter_run("features/car/names.feature", |_, _, sc| {
+ // .with_writer(DotWriter::default().normalized())
+ .filter_run("features/testbot/time.feature", |_, _, sc| {
!sc.tags.iter().any(|t| t == "todo")
}),
);