diff --git a/tests/common/f64_utils.rs b/tests/common/f64_utils.rs new file mode 100644 index 000000000..e69de29bb diff --git a/tests/common/file_util.rs b/tests/common/file_util.rs new file mode 100644 index 000000000..d2092fec7 --- /dev/null +++ b/tests/common/file_util.rs @@ -0,0 +1,16 @@ +use std::{fs::{self, File}, io::Read, path::PathBuf}; + +use log::debug; + +pub fn get_file_as_byte_vec(path: &PathBuf) -> Vec { + debug!("opening {path:?}"); + let mut f = File::open(path).expect("no file found"); + let metadata = fs::metadata(path).expect("unable to read metadata"); + let mut buffer = vec![0; metadata.len() as usize]; + match f.read(&mut buffer) { + Ok(l) => assert_eq!(metadata.len() as usize, l, "data was not completely read"), + Err(e) => panic!("Error: {e}"), + } + + buffer +} \ No newline at end of file diff --git a/tests/common/hash_util.rs b/tests/common/hash_util.rs new file mode 100644 index 000000000..152086dae --- /dev/null +++ b/tests/common/hash_util.rs @@ -0,0 +1,78 @@ +use std::{env, fs, path::PathBuf}; + +use log::debug; + +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 + let exe_path = env::current_exe().expect("failed to get current exe path"); + let path = exe_path + .ancestors() + .find(|p| p.ends_with("target")) + .expect("compiled cucumber test executable resides in a directory tree with the root name 'target'") + .parent().unwrap(); + // TODO: Remove after migration to Rust build dir + let build_path = path.join("build"); + // TODO: Remove after migration to Rust build dir + let mut dependencies = Vec::new(); + + // FIXME: the following iterator gymnastics port the exact order and behavior of the JavaScript implementation + let names = [ + "osrm-extract", + "osrm-contract", + "osrm-customize", + "osrm-partition", + "osrm_extract", + "osrm_contract", + "osrm_customize", + "osrm_partition", + ]; + + let files: Vec = fs::read_dir(build_path) + .unwrap() + .filter_map(|e| e.ok()) + .map(|dir_entry| dir_entry.path()) + .collect(); + + let iter = names.iter().map(|name| { + files + .iter() + .find(|path_buf| { + path_buf + .file_stem() + .unwrap() + .to_str() + .unwrap() + .contains(name) + }) + .cloned() + .expect("file exists and is usable") + }); + + dependencies.extend(iter); + + let profiles_path = path.join("profiles"); + debug!("{profiles_path:?}"); + + dependencies.extend( + LexicographicFileWalker::new(&profiles_path) + .filter(|pb| !pb.to_str().unwrap().contains("examples")) + .filter(|pathbuf| match pathbuf.extension() { + Some(ext) => ext.to_str().unwrap() == "lua", + None => false, + }), + ); + let mut md5 = chksum_md5::new(); + debug!("md5: {}", md5.digest().to_hex_lowercase()); + + for path_buf in dependencies { + let data = get_file_as_byte_vec(&path_buf); + if data.is_empty() { + continue; + } + md5.update(data); + // debug!("md5: {}", md5.digest().to_hex_lowercase()); + } + md5 + } diff --git a/tests/osm.rs b/tests/osm.rs new file mode 100644 index 000000000..fdc46e25a --- /dev/null +++ b/tests/osm.rs @@ -0,0 +1,264 @@ +// TODO: consider trait for OSM entities +// TODO: better error handling in XML creation +// FIXME: today nodes are stored twice +// TODO: move node lookup by name to here +use std::collections::HashMap; + +use xml_builder::{XMLBuilder, XMLElement, XMLVersion}; + +static OSM_USER: &str = "osrm"; +static OSM_TIMESTAMP :&str = "2000-01-01T00:00:00Z"; +static OSM_UID: &str = "1"; + +#[derive(Clone, Debug, Default)] +pub struct OSMNode { + pub id: u64, + pub lat: f64, + pub lon: f64, + pub tags: HashMap, +} + +impl OSMNode { + pub fn add_tag(&mut self, key: &str, value: &str) { + self.tags.insert(key.into(), value.into()); + } + + // pub fn set_id_(&mut self, id: u64) { + // self.id = id; + // } + + // pub fn set_tags(&mut self, tags: HashMap) { + // self.tags = tags + // } + + pub fn to_xml(&self) -> XMLElement { + let mut node = XMLElement::new("node"); + node.add_attribute("id", &self.id.to_string()); + node.add_attribute("version", "1"); + node.add_attribute("uid", OSM_UID); + node.add_attribute("user", OSM_USER); + node.add_attribute("timestamp", OSM_TIMESTAMP); + node.add_attribute("lon", &format!("{:?}", self.lon)); + node.add_attribute("lat", &format!("{:?}", self.lat)); + + if !self.tags.is_empty() { + for (key, value) in &self.tags { + let mut tags = XMLElement::new("tag"); + tags.add_attribute("k", key); + tags.add_attribute("v", value); + node.add_child(tags).unwrap(); + } + } + + node + } +} + +#[derive(Clone, Debug, Default)] +pub struct OSMWay { + pub id: u64, + pub osm_user: String, + pub osm_time_stamp: String, + pub osm_uid: u64, + pub tags: HashMap, + pub nodes: Vec, + pub add_locations: bool, +} + +impl OSMWay { + pub fn add_node(&mut self, node: OSMNode) { + self.nodes.push(node); + } + + // pub fn set_tags(&mut self, tags: HashMap) { + // self.tags = tags; + // } + + pub fn to_xml(&self) -> XMLElement { + let mut way = XMLElement::new("way"); + way.add_attribute("id", &self.id.to_string()); + way.add_attribute("version", "1"); + way.add_attribute("uid", &self.osm_uid.to_string()); + way.add_attribute("user", &self.osm_user); + way.add_attribute("timestamp", &self.osm_time_stamp); + + assert!(self.nodes.len() >= 2); + + for node in &self.nodes { + let mut nd = XMLElement::new("nd"); + nd.add_attribute("ref", &node.id.to_string()); + if self.add_locations { + nd.add_attribute("lon", &format!("{:?}", node.lon)); + nd.add_attribute("lat", &format!("{:?}", node.lat)); + } + way.add_child(nd).unwrap(); + } + + if !self.tags.is_empty() { + for (key, value) in &self.tags { + let mut tags = XMLElement::new("tag"); + tags.add_attribute("k", key); + tags.add_attribute("v", value); + way.add_child(tags).unwrap(); + } + } + + way + } +} + +#[derive(Clone, Debug)] +pub struct Member { + pub id: u64, + pub member_type: String, + pub member_role: String, +} + +#[derive(Clone, Debug)] +pub struct OSMRelation { + pub id: u64, + pub osm_user: String, + pub osm_time_stamp: String, + pub osm_uid: String, + pub members: Vec, + pub tags: HashMap, +} + +// impl OSMRelation { +// pub fn add_member(&mut self, member_type: String, id: u64, member_role: String) { +// self.members.push(Member { +// id, +// member_type, +// member_role, +// }); +// } + +// pub fn add_tag(&mut self, key: &str, value: &str) { +// self.tags.insert(key.into(), value.into()); +// } +// } + +#[derive(Debug, Default)] +pub struct OSMDb { + nodes: Vec<(char, OSMNode)>, + ways: Vec, + relations: Vec, +} + +impl OSMDb { + pub fn add_node(&mut self, node: OSMNode) { + let name = node.tags.get("name").unwrap(); + assert!(name.len() == 1, "name needs to be of length 1, but was \"{name}\""); + self.nodes.push((name.chars().next().unwrap(), node)); + } + + pub fn find_node(&self, search_name: char) -> Option<&(char, OSMNode)> { + // TODO: this is a linear search. + self.nodes.iter().find(|(name, _node)| { + search_name == *name + }) + } + + pub fn add_way(&mut self, way: OSMWay) { + self.ways.push(way); + } + + // pub fn add_relation(&mut self, relation: OSMRelation) { + // self.relations.push(relation); + // } + + // pub fn clear(&mut self) { + // self.nodes.clear(); + // self.ways.clear(); + // self.relations.clear(); + // } + + pub fn to_xml(&self) -> String { + let mut xml = XMLBuilder::new() + .version(XMLVersion::XML1_0) + .encoding("UTF-8".into()) + .build(); + + let mut osm = XMLElement::new("osm"); + osm.add_attribute("generator", "osrm-test"); + osm.add_attribute("version", "0.6"); + + for (_, node) in &self.nodes { + osm.add_child(node.to_xml()).unwrap(); + } + + for way in &self.ways { + osm.add_child(way.to_xml()).unwrap(); + } + + xml.set_root_element(osm); + + let mut writer: Vec = Vec::new(); + xml.generate(&mut writer).unwrap(); + String::from_utf8(writer).unwrap() + } + + pub fn node_len(&self) -> usize { + self.nodes.len() + } + pub fn way_len(&self) -> usize { + self.ways.len() + } + pub fn relation_len(&self) -> usize { + self.relations.len() + } +} + +#[cfg(test)] +mod test { + use super::{OSMNode, OSMWay}; + + #[test] + fn empty_db_by_default() { + let osm_db = crate::OSMDb::default(); + assert_eq!(0, osm_db.node_len()); + assert_eq!(0, osm_db.way_len()); + assert_eq!(0, osm_db.relation_len()); + } + + #[test] + fn osm_db_with_single_node() { + let mut osm_db = crate::OSMDb::default(); + + let mut node1 = OSMNode { + id: 123, + lat: 50.1234, + lon: 8.9876, + ..Default::default() + }; + + let mut node2 = OSMNode { + id: 321, + lat: 50.1234, + lon: 8.9876, + ..Default::default() + }; + node1.add_tag("name", "a"); + node2.add_tag("name", "b"); + osm_db.add_node(node1.clone()); + osm_db.add_node(node2.clone()); + + let mut way = OSMWay { + id: 890, + osm_user: "osrm".into(), + osm_time_stamp: "2000-01-01T00:00:00Z".into(), + osm_uid: 1, + ..Default::default() + }; + way.nodes.push(node1); + way.nodes.push(node2); + + osm_db.add_way(way); + + 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}"); + assert_eq!(actual, expected); + } +}