Implement support for car/names.feature

This commit is contained in:
Dennis 2024-06-13 17:02:28 +02:00
parent d985459696
commit 37f5780472
No known key found for this signature in database
GPG Key ID: 6937EAEA33A3FA5D
16 changed files with 442 additions and 152 deletions

View File

@ -66,7 +66,7 @@ fn main() {
if let Some((platform, compiler)) = match env::consts::OS { if let Some((platform, compiler)) = match env::consts::OS {
"linux" if env::consts::ARCH == "x86_64" => Some((OS::Linux, ".clang++-15")), "linux" if env::consts::ARCH == "x86_64" => Some((OS::Linux, ".clang++-15")),
"macos" if env::consts::ARCH == "x86_64" => Some((OS::MacIntel, "")), "macos" if env::consts::ARCH == "x86_64" => Some((OS::MacIntel, "")),
"macos" if env::consts::ARCH == "arm" => Some((OS::Mac, "")), "macos" if env::consts::ARCH == "aarch64" => Some((OS::Mac, "")),
"windows" if env::consts::ARCH == "x86_64" => Some((OS::Windows, "")), "windows" if env::consts::ARCH == "x86_64" => Some((OS::Windows, "")),
_ => None, _ => None,
} { } {
@ -98,11 +98,16 @@ fn main() {
build_println!("unsupported platform: {} {}. 'flatc' binary supporting version {} of the library needs to be in system path", env::consts::OS, env::consts::ARCH, version); build_println!("unsupported platform: {} {}. 'flatc' binary supporting version {} of the library needs to be in system path", env::consts::OS, env::consts::ARCH, version);
} }
let flatc = match Path::new(executable_path).exists() { let (flatc, location) = match Path::new(executable_path).exists() {
true => flatc_rust::Flatc::from_path(executable_path), true => (flatc_rust::Flatc::from_path(executable_path), "local"),
false => flatc_rust::Flatc::from_env_path(), false => (flatc_rust::Flatc::from_env_path(), "downloaded"),
}; };
assert!(flatc.check().is_ok()); assert!(flatc.check().is_ok());
let version = &flatc.version().unwrap();
build_println!(
"using {location} flatc v{} to compile schema files",
version.version()
);
flatc flatc
.run(flatc_rust::Args { .run(flatc_rust::Args {
extra: &["--gen-all"], extra: &["--gen-all"],

View File

@ -46,7 +46,7 @@ class OSRMBaseLoader{
let retry = (err) => { let retry = (err) => {
if (err) { if (err) {
if (retryCount < this.scope.OSRM_CONNECTION_RETRIES) { if (retryCount < this.scope.OSRM_CONNECTION_RETRIES) {
const timeoutMs = 10 * Math.pow(this.scope.OSRM_CONNECTION_EXP_BACKOFF_COEF, retryCount); const timeoutMs = 10 * Math.pow(1.1, retryCount);
retryCount++; retryCount++;
setTimeout(() => { tryConnect(this.scope.OSRM_IP, this.scope.OSRM_PORT, retry); }, timeoutMs); setTimeout(() => { tryConnect(this.scope.OSRM_IP, this.scope.OSRM_PORT, retry); }, timeoutMs);
} else { } else {

View File

@ -29,6 +29,8 @@ module.exports = function () {
var params = this.paramsToString(parameters); var params = this.paramsToString(parameters);
this.query = baseUri + (params.length ? '/' + params : ''); this.query = baseUri + (params.length ? '/' + params : '');
console.log(this.query);
request(this.query, (err, res, body) => { request(this.query, (err, res, body) => {
if (err && err.code === 'ECONNREFUSED') { if (err && err.code === 'ECONNREFUSED') {
return cb(new Error('*** osrm-routed is not running.')); return cb(new Error('*** osrm-routed is not running.'));

View File

@ -37,7 +37,6 @@ impl Display for RoutingAlgorithm {
} }
} }
// TODO: move to external file // TODO: move to external file
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(version, about, long_about = None)] #[command(version, about, long_about = None)]

View File

@ -1,6 +1,9 @@
use colored::Colorize; use colored::Colorize;
use cucumber::{cli, event, parser, Event}; use cucumber::{cli, event, parser, Event};
use std::{io::{self, Write}, time::Instant}; use std::{
io::{self, Write},
time::Instant,
};
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct DotWriter { pub struct DotWriter {
@ -75,8 +78,8 @@ impl<W: 'static> cucumber::Writer<W> for DotWriter {
let p = format!("{} passed", self.step_passed).green(); let p = format!("{} passed", self.step_passed).green();
println!("{} steps ({f}, {s}, {p})", self.step_started); println!("{} steps ({f}, {s}, {p})", self.step_started);
let elapsed = Instant::now() - self.start_time.unwrap(); let elapsed = Instant::now() - self.start_time.unwrap();
let minutes = elapsed.as_secs()/60; let minutes = elapsed.as_secs() / 60;
let seconds = (elapsed.as_millis() % 60_000) as f64 / 1000.; let seconds = (elapsed.as_millis() % 60_000) as f64 / 1000.;
println!("{}m{}s", minutes, seconds); println!("{}m{}s", minutes, seconds);
} }
@ -86,10 +89,10 @@ impl<W: 'static> cucumber::Writer<W> for DotWriter {
scenarios: _, scenarios: _,
steps: _, steps: _,
parser_errors: _, parser_errors: _,
} => {}, } => {}
event::Cucumber::Started => { event::Cucumber::Started => {
self.start_time = Some(Instant::now()); self.start_time = Some(Instant::now());
}, }
}, },
Err(e) => println!("Error: {e}"), Err(e) => println!("Error: {e}"),
} }

View File

@ -1,4 +1,8 @@
use std::{fs::{self, File}, io::Read, path::PathBuf}; use std::{
fs::{self, File},
io::Read,
path::PathBuf,
};
use log::debug; use log::debug;

View File

@ -2,7 +2,9 @@ use std::{env, fs, path::PathBuf};
use log::debug; use log::debug;
use crate::common::{file_util::get_file_as_byte_vec, lexicographic_file_walker::LexicographicFileWalker}; use crate::common::{
file_util::get_file_as_byte_vec, lexicographic_file_walker::LexicographicFileWalker,
};
pub fn md5_of_osrm_executables() -> chksum_md5::MD5 { pub fn md5_of_osrm_executables() -> chksum_md5::MD5 {
// create OSRM digest before any tests are executed since cucumber-rs doesn't have @beforeAll // create OSRM digest before any tests are executed since cucumber-rs doesn't have @beforeAll
@ -75,4 +77,4 @@ pub fn md5_of_osrm_executables() -> chksum_md5::MD5 {
// debug!("md5: {}", md5.digest().to_hex_lowercase()); // debug!("md5: {}", md5.digest().to_hex_lowercase());
} }
md5 md5
} }

View File

@ -2,23 +2,23 @@
// functions to make nearest, route, etc calls // functions to make nearest, route, etc calls
// fn nearest(arg1, ... argn) -> NearestResponse // fn nearest(arg1, ... argn) -> NearestResponse
use std::{path::Path, time::Duration}; // use std::{path::Path, time::Duration};
use ureq::{Agent, AgentBuilder}; // use ureq::{Agent, AgentBuilder};
pub struct HttpRequest { // pub struct HttpRequest {
agent: Agent, // agent: Agent,
} // }
impl HttpRequest { // impl HttpRequest {
// pub fn fetch_to_file(url: &str, output: &Path) -> Result<()> {} // // pub fn fetch_to_file(url: &str, output: &Path) -> Result<()> {}
pub fn new() -> Self { // pub fn new() -> Self {
let agent = AgentBuilder::new() // let agent = AgentBuilder::new()
.timeout_read(Duration::from_secs(5)) // .timeout_read(Duration::from_secs(5))
.timeout_write(Duration::from_secs(5)) // .timeout_write(Duration::from_secs(5))
.build(); // .build();
Self { agent } // Self { agent }
} // }
} // }

View File

@ -3,24 +3,25 @@ use std::{
process::{Child, Command, Stdio}, process::{Child, Command, Stdio},
}; };
#[derive(Default)] #[derive(Debug, Default)]
pub struct TaskStarter { pub struct LocalTask {
ready: bool, ready: bool,
command: String, command: String,
arguments: Vec<String>, arguments: Vec<String>,
child: Option<Child>, child: Option<Child>,
} }
impl TaskStarter { impl LocalTask {
pub fn new(command: &str) -> Self { pub fn new(command: String) -> Self {
Self { Self {
ready: false, ready: false,
command: command.into(), command: command,
arguments: Vec::new(), arguments: Vec::new(),
child: None, child: None,
} }
} }
pub fn is_ready(&self) -> bool { pub fn is_ready(&self) -> bool {
// TODO: also check that process is running
self.ready self.ready
} }
pub fn arg(&mut self, argument: &str) -> &mut Self { pub fn arg(&mut self, argument: &str) -> &mut Self {
@ -39,6 +40,10 @@ impl TaskStarter {
Err(e) => panic!("cannot spawn task: {e}"), Err(e) => panic!("cannot spawn task: {e}"),
} }
if self.child.is_none() {
return;
}
if let Some(output) = &mut self.child.as_mut().unwrap().stdout { if let Some(output) = &mut self.child.as_mut().unwrap().stdout {
// implement with a timeout // implement with a timeout
let mut reader = BufReader::new(output); let mut reader = BufReader::new(output);
@ -54,10 +59,10 @@ impl TaskStarter {
} }
} }
impl Drop for TaskStarter { // impl Drop for LocalTask {
fn drop(&mut self) { // fn drop(&mut self) {
if let Err(e) = self.child.as_mut().expect("can't access child").kill() { // if let Err(e) = self.child.as_mut().expect("can't access child").kill() {
panic!("shutdown failed: {e}"); // panic!("shutdown failed: {e}");
} // }
} // }
} // }

View File

@ -8,27 +8,28 @@ pub mod file_util;
pub mod hash_util; pub mod hash_util;
pub mod http_request; pub mod http_request;
pub mod lexicographic_file_walker; pub mod lexicographic_file_walker;
pub mod local_task;
pub mod location; pub mod location;
pub mod nearest_response; pub mod nearest_response;
pub mod osm; pub mod osm;
pub mod osm_db; pub mod osm_db;
pub mod osrm_world; pub mod osrm_world;
pub mod route_response;
pub mod scenario_id; pub mod scenario_id;
pub mod task_starter;
// flatbuffer // flatbuffer
#[allow(dead_code, unused_imports)] #[allow(dead_code, unused_imports)]
#[path = "../../target/flatbuffers/fbresult_generated.rs"]
pub mod fbresult_flatbuffers;
#[allow(dead_code, unused_imports)]
#[path = "../../target/flatbuffers/position_generated.rs"] #[path = "../../target/flatbuffers/position_generated.rs"]
pub mod position_flatbuffers; pub mod position_flatbuffers;
#[allow(dead_code, unused_imports)] #[allow(dead_code, unused_imports)]
#[path = "../../target/flatbuffers/waypoint_generated.rs"] #[path = "../../target/flatbuffers/route_generated.rs"]
pub mod waypoint_flatbuffers; pub mod route_flatbuffers;
#[allow(dead_code, unused_imports)] #[allow(dead_code, unused_imports)]
#[path = "../../target/flatbuffers/table_generated.rs"] #[path = "../../target/flatbuffers/table_generated.rs"]
pub mod table_flatbuffers; pub mod table_flatbuffers;
#[allow(dead_code, unused_imports)] #[allow(dead_code, unused_imports)]
#[path = "../../target/flatbuffers/route_generated.rs"] #[path = "../../target/flatbuffers/waypoint_generated.rs"]
pub mod route_flatbuffers; pub mod waypoint_flatbuffers;
#[allow(dead_code, unused_imports)]
#[path = "../../target/flatbuffers/fbresult_generated.rs"]
pub mod fbresult_flatbuffers;

View File

@ -1,11 +1,11 @@
use crate::common::fbresult_flatbuffers::osrm::engine::api::fbresult::FBResult;
use super::location::Location; use super::location::Location;
use crate::common::fbresult_flatbuffers::osrm::engine::api::fbresult::FBResult;
use serde::Deserialize; use serde::Deserialize;
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
pub struct Waypoint { pub struct Waypoint {
pub hint: String, pub hint: String,
pub nodes: Vec<u64>, pub nodes: Option<Vec<u64>>,
pub distance: f32, pub distance: f32,
pub name: String, pub name: String,
location: Location, location: Location,
@ -66,7 +66,7 @@ impl NearestResponse {
longitude: location.longitude(), longitude: location.longitude(),
}; };
let nodes = wp.nodes().expect("waypoint mus have nodes"); let nodes = wp.nodes().expect("waypoint mus have nodes");
let nodes = vec![nodes.first(), nodes.second()]; let nodes = Some(vec![nodes.first(), nodes.second()]);
let distance = wp.distance(); let distance = wp.distance();
Waypoint { Waypoint {

View File

@ -93,15 +93,19 @@ mod tests {
let mut node1 = OSMNode { let mut node1 = OSMNode {
id: 123, id: 123,
lat: 50.1234, location: Location {
lon: 8.9876, longitude: 8.9876,
latitude: 50.1234,
},
..Default::default() ..Default::default()
}; };
let mut node2 = OSMNode { let mut node2 = OSMNode {
id: 321, id: 321,
lat: 50.1234, location: Location {
lon: 8.9876, longitude: 8.9876,
latitude: 50.1234,
},
..Default::default() ..Default::default()
}; };
node1.add_tag("name", "a"); node1.add_tag("name", "a");

View File

@ -1,15 +1,25 @@
use crate::Location; use super::{
nearest_response::NearestResponse, osm::OSMNode, osm_db::OSMDb, route_response::RouteResponse,
};
use crate::{common::local_task::LocalTask, Location};
use core::panic;
use cucumber::World; use cucumber::World;
use log::debug; use log::debug;
use std::{ use std::{
collections::HashMap, collections::HashMap,
fs::{create_dir_all, File}, fs::{create_dir_all, File},
io::Write,
path::PathBuf, path::PathBuf,
time::Duration,
}; };
use super::{osm::OSMNode, osm_db::OSMDb}; const DEFAULT_ORIGIN: Location = Location {
longitude: 1.0f32,
latitude: 1.0f32,
};
const DEFAULT_GRID_SIZE: f32 = 100.;
#[derive(Debug, Default, World)] #[derive(Debug, World)]
pub struct OSRMWorld { pub struct OSRMWorld {
pub feature_path: Option<PathBuf>, pub feature_path: Option<PathBuf>,
pub scenario_id: String, pub scenario_id: String,
@ -25,6 +35,36 @@ pub struct OSRMWorld {
pub extraction_parameters: Vec<String>, pub extraction_parameters: Vec<String>,
pub request_with_flatbuffers: bool, pub request_with_flatbuffers: bool,
pub grid_size: f32,
pub origin: Location,
task: LocalTask,
agent: ureq::Agent,
}
impl Default for OSRMWorld {
fn default() -> Self {
Self {
feature_path: Default::default(),
scenario_id: Default::default(),
feature_digest: Default::default(),
osrm_digest: Default::default(),
osm_id: Default::default(),
profile: Default::default(),
known_osm_nodes: Default::default(),
known_locations: Default::default(),
osm_db: Default::default(),
extraction_parameters: Default::default(),
request_with_flatbuffers: Default::default(),
grid_size: DEFAULT_GRID_SIZE,
origin: DEFAULT_ORIGIN,
task: LocalTask::default(),
agent: ureq::AgentBuilder::new()
.timeout_read(Duration::from_secs(5))
.timeout_write(Duration::from_secs(5))
.build(),
}
}
} }
impl OSRMWorld { impl OSRMWorld {
@ -59,7 +99,11 @@ impl OSRMWorld {
.ancestors() .ancestors()
.find(|p| p.ends_with("features")) .find(|p| p.ends_with("features"))
.expect(".feature files reside in a directory tree with the root name 'features'"); .expect(".feature files reside in a directory tree with the root name 'features'");
let routed_path = path.parent().expect("cannot get parent path").join("build").join("osrm-routed"); let routed_path = path
.parent()
.expect("cannot get parent path")
.join("build")
.join("osrm-routed");
assert!(routed_path.exists(), "osrm-routed binary not found"); assert!(routed_path.exists(), "osrm-routed binary not found");
routed_path routed_path
} }
@ -119,4 +163,112 @@ impl OSRMWorld {
} }
self.known_locations.insert(name, location); self.known_locations.insert(name, location);
} }
pub fn write_osm_file(&self) {
let osm_file = self
.feature_cache_path()
.join(self.scenario_id.clone() + ".osm");
if !osm_file.exists() {
debug!("writing to osm file: {osm_file:?}");
let mut file = File::create(osm_file).expect("could not create OSM file");
file.write_all(self.osm_db.to_xml().as_bytes())
.expect("could not write OSM file");
} else {
debug!("not writing to OSM file {osm_file:?}");
}
}
pub fn extract_osm_file(&self) {
let cache_path = self.artefact_cache_path();
if cache_path.exists() {
debug!("{cache_path:?} exists");
} else {
unimplemented!("{cache_path:?} does not exist");
}
}
pub fn artefact_cache_path(&self) -> PathBuf {
self.feature_cache_path().join(&self.osrm_digest)
}
fn start_routed(&mut self) {
if self.task.is_ready() {
// task running already
return;
}
let data_path = self
.artefact_cache_path()
.join(self.scenario_id.to_owned() + ".osrm");
// TODO: this should not require a temporary and behave like the API of std::process
self.task = LocalTask::new(self.routed_path().to_string_lossy().into());
self.task
.arg(data_path.to_str().expect("data path unwrappable"));
self.task
.spawn_wait_till_ready("running and waiting for requests");
assert!(self.task.is_ready());
}
pub fn nearest(
&mut self,
query_location: &Location,
request_with_flatbuffers: bool,
) -> NearestResponse {
self.start_routed();
let mut url = format!(
"http://localhost:5000/nearest/v1/{}/{:?},{:?}",
self.profile, query_location.longitude, query_location.latitude
);
if request_with_flatbuffers {
url += ".flatbuffers";
}
let call = self.agent.get(&url).call();
let body = match call {
Ok(response) => response.into_reader(),
Err(e) => panic!("http error: {e}"),
};
let response = match 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 {
self.start_routed();
let mut url = format!(
"http://localhost:5000/route/v1/{}/{:?},{:?};{:?},{:?}?steps=true&alternatives=false",
self.profile,
from_location.longitude,
from_location.latitude,
to_location.longitude,
to_location.latitude,
);
if request_with_flatbuffers {
url += ".flatbuffers";
}
// 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}"),
};
let text = std::io::read_to_string(body).unwrap();
let response = match request_with_flatbuffers {
true => unimplemented!("RouteResponse::from_flatbuffer(body)"),
false => RouteResponse::from_string(&text),
};
response
}
} }

View File

@ -0,0 +1,62 @@
use serde::Deserialize;
use super::nearest_response::Waypoint;
#[derive(Deserialize, Default, Debug)]
pub struct Step {
pub name: String,
pub pronunciation: Option<String>,
pub r#ref: Option<String>,
}
// #[derive(Deserialize, Debug)]
// pub struct Annotation {
// pub nodes: Option<Vec<u64>>,
// }
#[derive(Debug, Default, Deserialize)]
pub struct Leg {
pub summary: String,
pub weight: f64,
pub duration: f64,
pub steps: Vec<Step>,
pub distance: f64,
// pub annotation: Option<Vec<Annotation>>,
}
#[derive(Deserialize, Debug)]
pub struct Route {
pub geometry: String,
pub weight: f64,
pub duration: f64,
pub legs: Vec<Leg>,
pub weight_name: String,
pub distance: f64,
}
#[derive(Deserialize, Debug)]
pub struct RouteResponse {
pub code: String,
pub routes: Vec<Route>,
pub waypoints: Vec<Waypoint>,
pub data_version: Option<String>,
}
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_string(input: &str) -> Self {
// println!("{input}");
let response = match serde_json::from_str(input) {
Ok(response) => response,
Err(e) => panic!("parsing error {e} => {input}"),
};
response
}
}

View File

@ -1,35 +1,31 @@
// extern crate clap;
mod common; mod common;
use cheap_ruler::CheapRuler; use cheap_ruler::CheapRuler;
use clap::Parser; use clap::Parser;
use common::{ use common::{
cli_arguments::Args, dot_writer::DotWriter, f64_utils::approx_equal, cli_arguments::Args, dot_writer::DotWriter, f64_utils::approx_equal,
hash_util::md5_of_osrm_executables, location::Location, nearest_response::NearestResponse, hash_util::md5_of_osrm_executables, location::Location, osm::OSMWay, osrm_world::OSRMWorld,
osm::OSMWay, osrm_world::OSRMWorld, task_starter::TaskStarter,
}; };
use core::panic; use core::panic;
use cucumber::{gherkin::Step, given, when, World, WriterExt}; use cucumber::{
gherkin::{Step, Table},
given, when, World, WriterExt,
};
use futures::{future, FutureExt}; use futures::{future, FutureExt};
use geo_types::point; use geo_types::Point;
use log::debug; use log::debug;
use std::{collections::HashMap, fs::File, io::Write, time::Duration}; use std::collections::HashMap;
use ureq::Agent;
const DEFAULT_ORIGIN: [f64; 2] = [1., 1.]; // TODO: move to world? fn offset_origin_by(dx: f32, dy: f32, origin: Location, grid_size: f32) -> Location {
const DEFAULT_GRID_SIZE: f64 = 100.; // TODO: move to world? let ruler = CheapRuler::new(origin.latitude, cheap_ruler::DistanceUnit::Meters);
fn offset_origin_by(dx: f64, dy: f64) -> Location {
let ruler = CheapRuler::new(DEFAULT_ORIGIN[1], cheap_ruler::DistanceUnit::Meters);
let loc = ruler.offset( let loc = ruler.offset(
&point!(DEFAULT_ORIGIN), &Point::new(origin.longitude, origin.latitude),
dx * DEFAULT_GRID_SIZE, dx * grid_size,
dy * DEFAULT_GRID_SIZE, dy * grid_size,
); //TODO: needs to be world's gridSize, not the local one ); //TODO: needs to be world's gridSize, not the local one
Location { Location {
latitude: loc.y() as f32, latitude: loc.y(),
longitude: loc.x() as f32, longitude: loc.x(),
} }
} }
@ -95,12 +91,16 @@ fn set_node_map(world: &mut OSRMWorld, step: &Step) {
.filter(|(_column_index, charater)| *charater != ' ') .filter(|(_column_index, charater)| *charater != ' ')
.for_each(|(column_index, name)| { .for_each(|(column_index, name)| {
// This ports the logic from previous implementations. // This ports the logic from previous implementations.
let location = let location = offset_origin_by(
offset_origin_by(column_index as f64 * 0.5, -(row_index as f64 - 1.)); column_index as f32 * 0.5,
-(row_index as f32 - 1.),
world.origin,
world.grid_size,
);
match name { match name {
'0'...'9' => world.add_location(name, location), '0'...'9' => world.add_location(name, location),
'a'...'z' => world.add_osm_node(name, location, None), 'a'...'z' => world.add_osm_node(name, location, None),
_ => unreachable!("node name not in [0..9][a..z]"), _ => unreachable!("node name not in [0..9][a..z]: {docstring}"),
} }
}); });
}); });
@ -114,6 +114,11 @@ fn extra_parameters(world: &mut OSRMWorld, parameters: String) {
world.extraction_parameters.push(parameters); world.extraction_parameters.push(parameters);
} }
#[given(expr = "a grid size of {float} meters")]
fn set_grid_size(world: &mut OSRMWorld, meters: f32) {
world.grid_size = meters;
}
#[given(regex = "the ways")] #[given(regex = "the ways")]
fn set_ways(world: &mut OSRMWorld, step: &Step) { fn set_ways(world: &mut OSRMWorld, step: &Step) {
// debug!("using profile: {profile}"); // debug!("using profile: {profile}");
@ -159,43 +164,13 @@ fn set_ways(world: &mut OSRMWorld, step: &Step) {
} else { } else {
debug!("no table found {step:#?}"); debug!("no table found {step:#?}");
} }
// debug!("{}", world.osm_db.to_xml())
} }
// #[when("I request nearest I should get")] fn parse_table_from_steps(table: &Option<&Table>) -> Vec<HashMap<String, String>> {
#[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";
// if .osm file does not exist
// write osm file
// TODO: the OSRMWorld instance should have a function to write the .osm file
let osm_file = world
.feature_cache_path()
.join(world.scenario_id.clone() + ".osm");
if !osm_file.exists() {
debug!("writing to osm file: {osm_file:?}");
let mut file = File::create(osm_file).expect("could not create OSM file");
file.write_all(world.osm_db.to_xml().as_bytes())
.expect("could not write OSM file");
} else {
debug!("not writing to OSM file {osm_file:?}");
}
// if extracted file does not exist
let cache_path = world.feature_cache_path().join(&world.osrm_digest);
if cache_path.exists() {
debug!("{cache_path:?} exists");
} else {
debug!("{cache_path:?} does not exist");
}
// parse query data // parse query data
let table = &step.table.as_ref().expect("no query table specified"); let table = table.expect("no query table specified");
// the following lookup allows to define lat lon columns in any order // the following lookup allows to define lat lon columns in any order
let header = table.rows.first().expect("node locations table empty"); let header = table.rows.first().expect("node locations table empty");
// TODO: move to common functionality
let test_cases: Vec<_> = table let test_cases: Vec<_> = table
.rows .rows
.iter() .iter()
@ -212,23 +187,22 @@ fn request_nearest(world: &mut OSRMWorld, step: &Step, state: String) {
row_map row_map
}) })
.collect(); .collect();
test_cases
// TODO: also return the header
}
let data_path = cache_path.join(world.scenario_id.to_owned() + ".osrm"); #[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";
// TODO: this should not require a temporary and behave like the API of std::process world.write_osm_file();
let mut task = TaskStarter::new(world.routed_path().to_str().expect("task can be started")); world.extract_osm_file();
task.arg(data_path.to_str().expect("data path unwrappable"));
task.spawn_wait_till_ready("running and waiting for requests");
assert!(task.is_ready());
// TODO: move to generic http request handling struct // parse query data
let agent: Agent = ureq::AgentBuilder::new() let test_cases = parse_table_from_steps(&step.table.as_ref());
.timeout_read(Duration::from_secs(5))
.timeout_write(Duration::from_secs(5))
.build();
// parse and run test cases // run test cases
for test_case in test_cases { for test_case in &test_cases {
let query_location = world.get_location( let query_location = world.get_location(
test_case test_case
.get("in") .get("in")
@ -237,7 +211,10 @@ fn request_nearest(world: &mut OSRMWorld, step: &Step, state: String) {
.next() .next()
.expect("node name is one char long"), .expect("node name is one char long"),
); );
let expected_location = world.get_location(
let response = world.nearest(&query_location, request_with_flatbuffers);
let expected_location = &world.get_location(
test_case test_case
.get("out") .get("out")
.expect("node name is one char long") .expect("node name is one char long")
@ -246,27 +223,6 @@ fn request_nearest(world: &mut OSRMWorld, step: &Step, state: String) {
.expect("node name is one char long"), .expect("node name is one char long"),
); );
// debug!("{query_location:?} => {expected_location:?}");
// run queries
let mut url = format!(
"http://localhost:5000/nearest/v1/{}/{:?},{:?}",
world.profile, query_location.longitude, query_location.latitude
);
if request_with_flatbuffers {
url += ".flatbuffers";
}
let call = agent.get(&url).call();
let body = match call {
Ok(response) => response.into_reader(),
Err(e) => panic!("http error: {e}"),
};
let response = match request_with_flatbuffers {
true => NearestResponse::from_flatbuffer(body),
false => NearestResponse::from_json_reader(body),
};
if test_case.contains_key("out") { if test_case.contains_key("out") {
// check that result node is (approximately) equivalent // check that result node is (approximately) equivalent
let result_location = response.waypoints[0].location(); let result_location = response.waypoints[0].location();
@ -290,6 +246,99 @@ 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";
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 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::<Vec<String>>()
.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::<Vec<String>>()
.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::<Vec<String>>()
.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");
}
fn main() { fn main() {
let args = Args::parse(); let args = Args::parse();
debug!("arguments: {:?}", args); debug!("arguments: {:?}", args);
@ -307,6 +356,8 @@ fn main() {
future::ready(()).boxed() future::ready(()).boxed()
}) })
.with_writer(DotWriter::default().normalized()) .with_writer(DotWriter::default().normalized())
.run("features/nearest/"), .filter_run("features/car/names.feature", |_, _, sc| {
!sc.tags.iter().any(|t| t == "todo")
}),
); );
} }