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 {
"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 == "arm" => Some((OS::Mac, "")),
"macos" if env::consts::ARCH == "aarch64" => Some((OS::Mac, "")),
"windows" if env::consts::ARCH == "x86_64" => Some((OS::Windows, "")),
_ => 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);
}
let flatc = match Path::new(executable_path).exists() {
true => flatc_rust::Flatc::from_path(executable_path),
false => flatc_rust::Flatc::from_env_path(),
let (flatc, location) = match Path::new(executable_path).exists() {
true => (flatc_rust::Flatc::from_path(executable_path), "local"),
false => (flatc_rust::Flatc::from_env_path(), "downloaded"),
};
assert!(flatc.check().is_ok());
let version = &flatc.version().unwrap();
build_println!(
"using {location} flatc v{} to compile schema files",
version.version()
);
flatc
.run(flatc_rust::Args {
extra: &["--gen-all"],

View File

@ -46,7 +46,7 @@ class OSRMBaseLoader{
let retry = (err) => {
if (err) {
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++;
setTimeout(() => { tryConnect(this.scope.OSRM_IP, this.scope.OSRM_PORT, retry); }, timeoutMs);
} else {

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
pub fn approx_equal(a: f32, b: f32, dp: u8) -> bool {
let p = 10f32.powi(-(dp as i32));
(a - b).abs() < p
}
}

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;
@ -13,4 +17,4 @@ pub fn get_file_as_byte_vec(path: &PathBuf) -> Vec<u8> {
}
buffer
}
}

View File

@ -2,7 +2,9 @@ use std::{env, fs, path::PathBuf};
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 {
// 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());
}
md5
}
}

View File

@ -2,23 +2,23 @@
// functions to make nearest, route, etc calls
// 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 {
agent: Agent,
}
// pub struct HttpRequest {
// agent: Agent,
// }
impl HttpRequest {
// pub fn fetch_to_file(url: &str, output: &Path) -> Result<()> {}
// impl HttpRequest {
// // pub fn fetch_to_file(url: &str, output: &Path) -> Result<()> {}
pub fn new() -> Self {
let agent = AgentBuilder::new()
.timeout_read(Duration::from_secs(5))
.timeout_write(Duration::from_secs(5))
.build();
// pub fn new() -> Self {
// let agent = AgentBuilder::new()
// .timeout_read(Duration::from_secs(5))
// .timeout_write(Duration::from_secs(5))
// .build();
Self { agent }
}
}
// Self { agent }
// }
// }

View File

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

View File

@ -1,6 +1,6 @@
#![allow(clippy::derivable_impls, clippy::all)]
extern crate flatbuffers;
pub mod cli_arguments;
pub mod dot_writer;
pub mod f64_utils;
@ -8,27 +8,28 @@ pub mod file_util;
pub mod hash_util;
pub mod http_request;
pub mod lexicographic_file_walker;
pub mod local_task;
pub mod location;
pub mod nearest_response;
pub mod osm;
pub mod osm_db;
pub mod osrm_world;
pub mod route_response;
pub mod scenario_id;
pub mod task_starter;
// flatbuffer
#[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"]
pub mod position_flatbuffers;
#[allow(dead_code, unused_imports)]
#[path = "../../target/flatbuffers/waypoint_generated.rs"]
pub mod waypoint_flatbuffers;
#[path = "../../target/flatbuffers/route_generated.rs"]
pub mod route_flatbuffers;
#[allow(dead_code, unused_imports)]
#[path = "../../target/flatbuffers/table_generated.rs"]
pub mod table_flatbuffers;
#[allow(dead_code, unused_imports)]
#[path = "../../target/flatbuffers/route_generated.rs"]
pub mod route_flatbuffers;
#[allow(dead_code, unused_imports)]
#[path = "../../target/flatbuffers/fbresult_generated.rs"]
pub mod fbresult_flatbuffers;
#[path = "../../target/flatbuffers/waypoint_generated.rs"]
pub mod waypoint_flatbuffers;

View File

@ -1,11 +1,11 @@
use crate::common::fbresult_flatbuffers::osrm::engine::api::fbresult::FBResult;
use super::location::Location;
use crate::common::fbresult_flatbuffers::osrm::engine::api::fbresult::FBResult;
use serde::Deserialize;
#[derive(Deserialize, Debug)]
pub struct Waypoint {
pub hint: String,
pub nodes: Vec<u64>,
pub nodes: Option<Vec<u64>>,
pub distance: f32,
pub name: String,
location: Location,
@ -66,7 +66,7 @@ impl NearestResponse {
longitude: location.longitude(),
};
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();
Waypoint {

View File

@ -93,15 +93,19 @@ mod tests {
let mut node1 = OSMNode {
id: 123,
lat: 50.1234,
lon: 8.9876,
location: Location {
longitude: 8.9876,
latitude: 50.1234,
},
..Default::default()
};
let mut node2 = OSMNode {
id: 321,
lat: 50.1234,
lon: 8.9876,
location: Location {
longitude: 8.9876,
latitude: 50.1234,
},
..Default::default()
};
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 log::debug;
use std::{
collections::HashMap,
fs::{create_dir_all, File},
io::Write,
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 feature_path: Option<PathBuf>,
pub scenario_id: String,
@ -25,6 +35,36 @@ pub struct OSRMWorld {
pub extraction_parameters: Vec<String>,
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 {
@ -59,7 +99,11 @@ impl OSRMWorld {
.ancestors()
.find(|p| p.ends_with("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");
routed_path
}
@ -119,4 +163,112 @@ impl OSRMWorld {
}
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;
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, nearest_response::NearestResponse,
osm::OSMWay, osrm_world::OSRMWorld, task_starter::TaskStarter,
hash_util::md5_of_osrm_executables, location::Location, osm::OSMWay, osrm_world::OSRMWorld,
};
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 geo_types::point;
use geo_types::Point;
use log::debug;
use std::{collections::HashMap, fs::File, io::Write, time::Duration};
use ureq::Agent;
use std::collections::HashMap;
const DEFAULT_ORIGIN: [f64; 2] = [1., 1.]; // TODO: move to world?
const DEFAULT_GRID_SIZE: f64 = 100.; // TODO: move to world?
fn offset_origin_by(dx: f64, dy: f64) -> Location {
let ruler = CheapRuler::new(DEFAULT_ORIGIN[1], cheap_ruler::DistanceUnit::Meters);
fn offset_origin_by(dx: f32, dy: f32, origin: Location, grid_size: f32) -> Location {
let ruler = CheapRuler::new(origin.latitude, cheap_ruler::DistanceUnit::Meters);
let loc = ruler.offset(
&point!(DEFAULT_ORIGIN),
dx * DEFAULT_GRID_SIZE,
dy * DEFAULT_GRID_SIZE,
&Point::new(origin.longitude, origin.latitude),
dx * grid_size,
dy * grid_size,
); //TODO: needs to be world's gridSize, not the local one
Location {
latitude: loc.y() as f32,
longitude: loc.x() as f32,
latitude: loc.y(),
longitude: loc.x(),
}
}
@ -95,12 +91,16 @@ fn set_node_map(world: &mut OSRMWorld, step: &Step) {
.filter(|(_column_index, charater)| *charater != ' ')
.for_each(|(column_index, name)| {
// This ports the logic from previous implementations.
let location =
offset_origin_by(column_index as f64 * 0.5, -(row_index as f64 - 1.));
let location = offset_origin_by(
column_index as f32 * 0.5,
-(row_index as f32 - 1.),
world.origin,
world.grid_size,
);
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]"),
_ => 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);
}
#[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")]
fn set_ways(world: &mut OSRMWorld, step: &Step) {
// debug!("using profile: {profile}");
@ -159,43 +164,13 @@ fn set_ways(world: &mut OSRMWorld, step: &Step) {
} else {
debug!("no table found {step:#?}");
}
// debug!("{}", world.osm_db.to_xml())
}
// #[when("I request nearest I should get")]
#[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");
}
fn parse_table_from_steps(table: &Option<&Table>) -> Vec<HashMap<String, String>> {
// 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
let header = table.rows.first().expect("node locations table empty");
// TODO: move to common functionality
let test_cases: Vec<_> = table
.rows
.iter()
@ -212,23 +187,22 @@ fn request_nearest(world: &mut OSRMWorld, step: &Step, state: String) {
row_map
})
.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
let mut task = TaskStarter::new(world.routed_path().to_str().expect("task can be started"));
task.arg(data_path.to_str().expect("data path unwrappable"));
task.spawn_wait_till_ready("running and waiting for requests");
assert!(task.is_ready());
world.write_osm_file();
world.extract_osm_file();
// TODO: move to generic http request handling struct
let agent: Agent = ureq::AgentBuilder::new()
.timeout_read(Duration::from_secs(5))
.timeout_write(Duration::from_secs(5))
.build();
// parse query data
let test_cases = parse_table_from_steps(&step.table.as_ref());
// parse and run test cases
for test_case in test_cases {
// run test cases
for test_case in &test_cases {
let query_location = world.get_location(
test_case
.get("in")
@ -237,7 +211,10 @@ fn request_nearest(world: &mut OSRMWorld, step: &Step, state: String) {
.next()
.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
.get("out")
.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"),
);
// 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") {
// check that result node is (approximately) equivalent
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() {
let args = Args::parse();
debug!("arguments: {:?}", args);
@ -307,6 +356,8 @@ fn main() {
future::ready(()).boxed()
})
.with_writer(DotWriter::default().normalized())
.run("features/nearest/"),
.filter_run("features/car/names.feature", |_, _, sc| {
!sc.tags.iter().any(|t| t == "todo")
}),
);
}