diff --git a/rust/fastsim-core/Cargo.toml b/rust/fastsim-core/Cargo.toml index 31ecb917..261739e9 100644 --- a/rust/fastsim-core/Cargo.toml +++ b/rust/fastsim-core/Cargo.toml @@ -39,6 +39,9 @@ include_dir = "0.7.3" itertools = "0.12.0" ndarray-stats = "0.5.1" tempfile = "3.8.1" +url = "2.5.0" +ureq = "2.9.1" +isahc = "1.7.2" [package.metadata] include = [ diff --git a/rust/fastsim-core/src/cycle.rs b/rust/fastsim-core/src/cycle.rs index eb9d1979..95c4e2ed 100644 --- a/rust/fastsim-core/src/cycle.rs +++ b/rust/fastsim-core/src/cycle.rs @@ -631,6 +631,7 @@ pub struct RustCycle { impl SerdeAPI for RustCycle { const ACCEPTED_BYTE_FORMATS: &'static [&'static str] = &["yaml", "json", "bin", "csv"]; const ACCEPTED_STR_FORMATS: &'static [&'static str] = &["yaml", "json", "csv"]; + const CACHE_FOLDER: &'static str = &"cycles"; fn init(&mut self) -> anyhow::Result<()> { ensure!(!self.is_empty(), "Deserialized cycle is empty"); diff --git a/rust/fastsim-core/src/imports.rs b/rust/fastsim-core/src/imports.rs index f938a8cb..0b7bbf6b 100644 --- a/rust/fastsim-core/src/imports.rs +++ b/rust/fastsim-core/src/imports.rs @@ -13,3 +13,4 @@ pub(crate) use std::path::PathBuf; pub(crate) use crate::traits::*; pub(crate) use crate::utils::*; +pub(crate) use crate::vehicle_utils::*; diff --git a/rust/fastsim-core/src/traits.rs b/rust/fastsim-core/src/traits.rs index e39c1678..5924a8de 100644 --- a/rust/fastsim-core/src/traits.rs +++ b/rust/fastsim-core/src/traits.rs @@ -1,9 +1,11 @@ use crate::imports::*; use std::collections::HashMap; +use ureq; pub trait SerdeAPI: Serialize + for<'a> Deserialize<'a> { const ACCEPTED_BYTE_FORMATS: &'static [&'static str] = &["yaml", "json", "bin"]; const ACCEPTED_STR_FORMATS: &'static [&'static str] = &["yaml", "json"]; + const CACHE_FOLDER: &'static str = &""; /// Specialized code to execute upon initialization fn init(&mut self) -> anyhow::Result<()> { @@ -185,6 +187,83 @@ pub trait SerdeAPI: Serialize + for<'a> Deserialize<'a> { bincode_de.init()?; Ok(bincode_de) } + + /// Instantiates an object from a url. Accepts yaml and json file types + /// # Arguments + /// - url: URL (either as a string or url type) to object + /// Note: The URL needs to be a URL pointing directly to a file, for example + /// a raw github URL. + fn from_url>(url: S) -> anyhow::Result { + let url = url::Url::parse(url.as_ref())?; + let format = url + .path_segments() + .and_then(|segments| segments.last()) + .and_then(|filename| Path::new(filename).extension()) + .and_then(OsStr::to_str) + .with_context(|| "Could not parse file format from URL: {url:?}")?; + let response = ureq::get(url.as_ref()).call()?.into_reader(); + Self::from_reader(response, format) + } + + /// Takes an instantiated Rust object and saves it in the FASTSim data directory in + /// a rust_objects folder. + /// WARNING: If there is a file already in the data subdirectory with the + /// same name, it will be replaced by the new file. + /// # Arguments + /// - self (rust object) + /// - file_path: path to file within subdirectory. If only the file name is + /// listed, file will sit directly within the subdirectory of + /// the FASTSim data directory. If a path is given, the file will live + /// within the path specified, within the subdirectory CACHE_FOLDER of the + /// FASTSim data directory. + fn to_cache>(&self, file_path: P) -> anyhow::Result<()> { + let file_name = file_path + .as_ref() + .file_name() + .with_context(|| "Could not determine file name")? + .to_str() + .context("Could not determine file name.")?; + let mut subpath = PathBuf::new(); + let file_path_internal = file_path + .as_ref() + .to_str() + .context("Could not determine file name.")?; + if file_name == file_path_internal { + subpath = PathBuf::from(Self::CACHE_FOLDER); + } else { + subpath = Path::new(Self::CACHE_FOLDER).join( + file_path_internal + .strip_suffix(file_name) + .context("Could not determine path to subdirectory.")?, + ); + } + let data_subdirectory = create_project_subdir(subpath) + .with_context(|| "Could not find or build Fastsim data subdirectory.")?; + let file_path = data_subdirectory.join(file_name); + self.to_file(file_path) + } + + /// Instantiates a Rust object from the subdirectory within the FASTSim data + /// directory corresponding to the Rust Object ("vehices" for a RustVehice, + /// "cycles" for a RustCycle, and the root folder of the data directory for + /// all other objects). + /// # Arguments + /// - file_path: subpath to object, including file name, within subdirectory. + /// If the file sits directly in the subdirectory, this will just be the + /// file name. + /// Note: This function will work for all objects cached using the + /// to_cache() method. If a file has been saved manually to a different + /// subdirectory than the correct one for the object type (for instance a + /// RustVehicle saved within a subdirectory other than "vehicles" using the + /// utils::url_to_cache() function), then from_cache() will not be able to + /// find and instantiate the object. Instead, use the from_file method, and + /// use the utils::path_to_cache() to find the FASTSim data directory + /// location if needed. + fn from_cache>(file_path: P) -> anyhow::Result { + let full_file_path = Path::new(Self::CACHE_FOLDER).join(file_path); + let path_including_directory = path_to_cache()?.join(full_file_path); + Self::from_file(path_including_directory) + } } pub trait ApproxEq { diff --git a/rust/fastsim-core/src/utils.rs b/rust/fastsim-core/src/utils.rs index 80681efa..0b35d63b 100644 --- a/rust/fastsim-core/src/utils.rs +++ b/rust/fastsim-core/src/utils.rs @@ -7,6 +7,7 @@ use ndarray::*; use ndarray_stats::QuantileExt; use regex::Regex; use std::collections::HashSet; +use url::Url; use crate::imports::*; #[cfg(feature = "pyo3")] @@ -525,6 +526,59 @@ pub fn create_project_subdir>(subpath: P) -> anyhow::Result anyhow::Result { + let proj_dirs = ProjectDirs::from("gov", "NREL", "fastsim").ok_or_else(|| { + anyhow!("Could not build path to project directory: \"gov.NREL.fastsim\"") + })?; + Ok(PathBuf::from(proj_dirs.config_dir())) +} + +/// Deletes FASTSim data directory, clearing its contents. If subpath is +/// provided, will only delete the subdirectory pointed to by the subpath, +/// rather than deleting the whole data directory. If the subpath is an empty +/// string, deletes the entire FASTSim directory. +/// USE WITH CAUTION, as this function deletes ALL objects stored in the FASTSim +/// data directory or provided subdirectory. +/// # Arguments +/// - subpath: Subpath to a subdirectory within the FASTSim data directory. If +/// an empty string, the function will delete the whole FASTSim data +/// directory, clearing all its contents. +/// Note: it is not possible to delete single files using this function, only +/// directories. If a single file needs deleting, the path_to_cache() function +/// can be used to find the FASTSim data directory location. The file can then +/// be found and manually deleted. +pub fn clear_cache>(subpath: P) -> anyhow::Result<()> { + let path = path_to_cache()?.join(subpath); + Ok(std::fs::remove_dir_all(path)?) +} + +/// takes an object from a url and saves it in the FASTSim data directory in a +/// rust_objects folder +/// WARNING: if there is a file already in the data subdirectory with the same +/// name, it will be replaced by the new file +/// to save to a folder other than rust_objects, define constant CACHE_FOLDER to +/// be the desired folder name +/// # Arguments +/// - url: url (either as a string or url type) to object +/// - subpath: path to subdirectory within FASTSim data directory. Suggested +/// paths are "vehicles" for a RustVehicle, "cycles" for a RustCycle, and +/// "rust_objects" for other Rust objects. +/// Note: In order for the file to be save in the proper format, the URL needs +/// to be a URL pointing directly to a file, for example a raw github URL. +pub fn url_to_cache, P: AsRef>(url: S, subpath: P) -> anyhow::Result<()> { + let url = Url::parse(url.as_ref())?; + let file_name = url + .path_segments() + .and_then(|segments| segments.last()) + .with_context(|| "Could not parse filename from URL: {url:?}")?; + let data_subdirectory = create_project_subdir(subpath) + .with_context(|| "Could not find or build Fastsim data subdirectory.")?; + let file_path = data_subdirectory.join(file_name); + download_file_from_url(url.as_ref(), &file_path)?; + Ok(()) +} + #[cfg(feature = "pyo3")] pub mod array_wrappers { use crate::proc_macros::add_pyo3_api; @@ -732,4 +786,28 @@ mod tests { let y_lookup = interpolate_vectors(&x, &xs.to_vec(), &ys.to_vec(), false); assert_eq!(expected_y_lookup, y_lookup); } + + #[test] + fn test_path_to_cache() { + let path = path_to_cache().unwrap(); + println!("{:?}", path); + } + + #[test] + fn test_clear_cache() { + let temp_sub_dir = tempfile::TempDir::new_in(create_project_subdir("").unwrap()).unwrap(); + let sub_dir_path = temp_sub_dir.path().to_str().unwrap(); + let still_exists_before = std::fs::metadata(sub_dir_path).is_ok(); + assert_eq!(still_exists_before, true); + url_to_cache("https://raw.githubusercontent.com/NREL/fastsim-vehicles/main/public/1110_2022_Tesla_Model_Y_RWD_opt45017.yaml", "").unwrap(); + clear_cache(sub_dir_path).unwrap(); + let still_exists = std::fs::metadata(sub_dir_path).is_ok(); + assert_eq!(still_exists, false); + let path_to_vehicle = path_to_cache() + .unwrap() + .join("1110_2022_Tesla_Model_Y_RWD_opt45017.yaml"); + let vehicle_still_exists = std::fs::metadata(&path_to_vehicle).is_ok(); + assert_eq!(vehicle_still_exists, true); + std::fs::remove_file(path_to_vehicle).unwrap(); + } } diff --git a/rust/fastsim-core/src/vehicle.rs b/rust/fastsim-core/src/vehicle.rs index 61968488..fd65e436 100644 --- a/rust/fastsim-core/src/vehicle.rs +++ b/rust/fastsim-core/src/vehicle.rs @@ -545,6 +545,8 @@ pub struct RustVehicle { /// RustVehicle rust methods impl RustVehicle { + const VEHICLE_DIRECTORY_URL: &'static str = + &"https://raw.githubusercontent.com/NREL/fastsim-vehicles/main/"; /// Sets the following parameters: /// - `ess_mass_kg` /// - `mc_mass_kg` @@ -1023,6 +1025,43 @@ impl RustVehicle { v.set_derived().unwrap(); v } + + /// Downloads specified vehicle from FASTSim vehicle repo or url and + /// instantiates it into a RustVehicle. Notes in vehicle.doc the origin of + /// the vehicle. Returns vehicle. + /// # Arguments + /// - vehicle_file_name: file name for vehicle to be downloaded, including + /// path from url directory or FASTSim repository (if applicable) + /// - url: url for vehicle repository where vehicle will be downloaded from, + /// if None, assumed to be downloaded from vehicle FASTSim repo + /// Note: The URL needs to be a URL pointing directly to a file, for example + /// a raw github URL, split up so that the "url" argument is the path to the + /// directory, and the "vehicle_file_name" is the path within the directory + /// to the file. + /// Note: If downloading from the FASTSim Vehicle Repo, the + /// vehicle_file_name should include the path to the file from the root of + /// the Repo, as listed in the output of the + /// vehicle_utils::fetch_github_list() function. + /// Note: the url should not include the file name, only the path to the + /// file or a root directory of the file. + pub fn from_github_or_url>( + vehicle_file_name: S, + url: Option, + ) -> anyhow::Result { + let url_internal = match url { + Some(s) => { + s.as_ref().trim_end_matches('/').to_owned() + + "/" + + &vehicle_file_name.as_ref().trim_start_matches('/') + } + None => Self::VEHICLE_DIRECTORY_URL.to_string() + vehicle_file_name.as_ref(), + }; + let mut vehicle = + Self::from_url(&url_internal).with_context(|| "Could not parse vehicle from url")?; + let vehicle_origin = "Vehicle from ".to_owned() + url_internal.as_str(); + vehicle.doc = Some(vehicle_origin); + Ok(vehicle) + } } impl Default for RustVehicle { @@ -1051,9 +1090,33 @@ impl Default for RustVehicle { } impl SerdeAPI for RustVehicle { + const CACHE_FOLDER: &'static str = &"vehicles"; + fn init(&mut self) -> anyhow::Result<()> { self.set_derived() } + + /// instantiates a vehicle from a url, and notes in vehicle.doc the origin + /// of the vehicle. + /// accepts yaml and json file types + /// # Arguments + /// - url: URL (either as a string or url type) to object + /// Note: The URL needs to be a URL pointing directly to a file, for example + /// a raw github URL. + fn from_url>(url: S) -> anyhow::Result { + let url = url::Url::parse(url.as_ref())?; + let format = url + .path_segments() + .and_then(|segments| segments.last()) + .and_then(|filename| Path::new(filename).extension()) + .and_then(OsStr::to_str) + .with_context(|| "Could not parse file format from URL: {url:?}")?; + let response = ureq::get(url.as_ref()).call()?.into_reader(); + let mut vehicle = Self::from_reader(response, format)?; + let vehicle_origin = "Vehicle from ".to_owned() + url.as_ref(); + vehicle.doc = Some(vehicle_origin); + Ok(vehicle) + } } #[cfg(test)] diff --git a/rust/fastsim-core/src/vehicle_utils.rs b/rust/fastsim-core/src/vehicle_utils.rs index a34b99a8..3c0bf498 100644 --- a/rust/fastsim-core/src/vehicle_utils.rs +++ b/rust/fastsim-core/src/vehicle_utils.rs @@ -15,6 +15,8 @@ use std::iter::FromIterator; use std::num::ParseIntError; use std::option::Option; use std::path::PathBuf; +use std::{result::Result, thread, time::Duration}; +use ureq::{Error as OtherError, Error::Status, Response}; use zip::ZipArchive; use crate::air::*; @@ -1771,6 +1773,99 @@ pub fn import_and_save_all_vehicles( Ok(()) } +#[derive(Deserialize)] +pub struct ObjectLinks { + #[serde(rename = "self")] + pub self_url: Option, + pub git: Option, + pub html: Option, +} + +#[derive(Deserialize)] +pub struct GitObjectInfo { + pub name: String, + pub path: String, + pub sha: Option, + pub size: Option, + pub url: String, + pub html_url: Option, + pub git_url: Option, + pub download_url: Option, + #[serde(rename = "type")] + pub url_type: String, + #[serde(rename = "_links")] + pub links: Option, +} + +const VEHICLE_REPO_LIST_URL: &'static str = + &"https://api.github.com/repos/NREL/fastsim-vehicles/contents/public"; + +/// Function that takes a url and calls the url. If a 503 or 429 error is +/// thrown, it tries again after a pause, up to four times. It returns either a +/// result or an error. +/// # Arguments +/// - url: url to be called +/// Source: https://docs.rs/ureq/latest/ureq/enum.Error.html +fn get_response>(url: S) -> Result { + for _ in 1..4 { + match ureq::get(url.as_ref()).call() { + Err(Status(503, r)) | Err(Status(429, r)) | Err(Status(403, r)) => { + let retry: Option = r.header("retry-after").and_then(|h| h.parse().ok()); + let retry = retry.unwrap_or(5); + eprintln!("{} for {}, retry in {}", r.status(), r.get_url(), retry); + thread::sleep(Duration::from_secs(retry)); + } + result => return result, + }; + } + // Ran out of retries; try one last time and return whatever result we get. + ureq::get(url.as_ref()).call() +} + +/// Returns a list of vehicle file names in the Fastsim Vehicle Repo, or, +/// optionally, a different GitHub repo, in which case the url provided needs to +/// be the url for the file tree within GitHub for the root folder the Rust +/// objects, for example +/// "https://api.github.com/repos/NREL/fastsim-vehicles/contents/public" +/// Note: for each file, the output will list the vehicle file name, including +/// the path from the root of the repository +/// # Arguments +/// - repo_url: url to the GitHub repository, Option, if None, defaults to the +/// FASTSim Vehicle Repo +pub fn fetch_github_list(repo_url: Option) -> anyhow::Result> { + let repo_url = repo_url.unwrap_or(VEHICLE_REPO_LIST_URL.to_string()); + let response = get_response(repo_url)?.into_reader(); + let github_list: Vec = + serde_json::from_reader(response).with_context(|| "Cannot parse github vehicle list.")?; + let mut vehicle_name_list: Vec = Vec::new(); + for object in github_list.iter() { + if object.url_type == "dir" { + let url = &object.url; + let vehicle_name_sublist = fetch_github_list(Some(url.to_owned()))?; + for name in vehicle_name_sublist.iter() { + vehicle_name_list.push(name.to_owned()); + } + } else if object.url_type == "file" { + let url = url::Url::parse(&object.url)?; + let path = &object.path; + let format = url + .path_segments() + .and_then(|segments| segments.last()) + .and_then(|filename| Path::new(filename).extension()) + .and_then(OsStr::to_str) + .with_context(|| "Could not parse file format from URL: {url:?}")?; + match format.trim_start_matches('.').to_lowercase().as_str() { + "yaml" | "yml" => vehicle_name_list.push(path.to_owned()), + "json" => vehicle_name_list.push(path.to_owned()), + _ => continue, + } + } else { + continue; + } + } + Ok(vehicle_name_list) +} + #[cfg(test)] mod vehicle_utils_tests { use super::*; @@ -2029,4 +2124,18 @@ mod vehicle_utils_tests { .unwrap() .is_empty()); } + + // NOTE: this test does not seem to reliably pass. Sometimes the function + // will give a 403 error and sometimes it will succeed -- I don't think + // there's any way to ensure the function succeeds 100% of the time. + #[test] + fn test_fetch_github_list() { + let list = fetch_github_list(Some( + "https://api.github.com/repos/NREL/fastsim-vehicles/contents".to_owned(), + )) + .unwrap(); + let other_list = fetch_github_list(None).unwrap(); + println!("{:?}", list); + println!("{:?}", other_list); + } } diff --git a/rust/fastsim-core/tests/assets/1110_2022_Tesla_Model_Y_RWD_opt45017.yaml b/rust/fastsim-core/tests/assets/1110_2022_Tesla_Model_Y_RWD_opt45017.yaml new file mode 100644 index 00000000..0f6fdc3a --- /dev/null +++ b/rust/fastsim-core/tests/assets/1110_2022_Tesla_Model_Y_RWD_opt45017.yaml @@ -0,0 +1,226 @@ +--- +scenario_name: 2022 Tesla Model Y RWD +veh_year: 2022 +veh_pt_type: BEV +drag_coef: 0.21716617209403616 +frontal_area_m2: 3.121538772737215 +glider_kg: 306.4316324129186 +veh_cg_m: -0.53 +drive_axle_weight_frac: 0.59 +wheel_base_m: 2.6 +cargo_kg: 136.0 +veh_override_kg: ~ +comp_mass_multiplier: 1.4 +fs_max_kw: 0.0 +fs_secs_to_peak_pwr: 1.0 +fs_kwh: 0.0 +fs_kwh_per_kg: 9.89 +fc_max_kw: 0.0 +fc_pwr_out_perc: + v: 1 + dim: + - 12 + data: + - 0.0 + - 0.005 + - 0.015 + - 0.04 + - 0.06 + - 0.1 + - 0.14 + - 0.2 + - 0.4 + - 0.6 + - 0.8 + - 1.0 +fc_eff_map: + v: 1 + dim: + - 12 + data: + - 0.1 + - 0.12 + - 0.28 + - 0.35 + - 0.38 + - 0.39 + - 0.4 + - 0.4 + - 0.38 + - 0.37 + - 0.36 + - 0.35 +fc_eff_type: SI +fc_sec_to_peak_pwr: 6.0 +fc_base_kg: 61.0 +fc_kw_per_kg: 2.13 +min_fc_time_on: 30.0 +idle_fc_kw: 0.0 +mc_max_kw: 208.79628939165707 +mc_pwr_out_perc: + v: 1 + dim: + - 11 + data: + - 0.0 + - 0.02 + - 0.04 + - 0.06 + - 0.08 + - 0.1 + - 0.2 + - 0.4 + - 0.6 + - 0.8 + - 1.0 +mc_eff_map: + v: 1 + dim: + - 11 + data: + - 0.41 + - 0.45 + - 0.48 + - 0.54 + - 0.58 + - 0.62 + - 0.83 + - 0.93 + - 0.94 + - 0.93 + - 0.92 +mc_sec_to_peak_pwr: 4.0 +mc_pe_kg_per_kw: 0.833 +mc_pe_base_kg: 21.6 +ess_max_kw: 219.23610386123994 +ess_max_kwh: 81.0 +ess_kg_per_kwh: 8.0 +ess_base_kg: 75.0 +ess_round_trip_eff: 0.97 +ess_life_coef_a: 110.0 +ess_life_coef_b: -0.6811 +min_soc: 0.05 +max_soc: 0.98 +ess_dischg_to_fc_max_eff_perc: 0.0 +ess_chg_to_fc_max_eff_perc: 0.0 +wheel_inertia_kg_m2: 0.815 +num_wheels: 4.0 +wheel_rr_coef: 0.007752538840328322 +wheel_radius_m: 0.336 +wheel_coef_of_fric: 0.7 +max_accel_buffer_mph: 60.0 +max_accel_buffer_perc_of_useable_soc: 0.2 +perc_high_acc_buf: 0.0 +mph_fc_on: 1.0 +kw_demand_fc_on: 100.0 +max_regen: 0.98 +stop_start: false +force_aux_on_fc: false +alt_eff: 1.0 +chg_eff: 0.86 +aux_kw: 0.25 +trans_kg: 114.0 +trans_eff: 0.98 +ess_to_fuel_ok_error: 0.005 +fc_eff_array: + - 0.1 + - 0.10400000000000001 + - 0.108 + - 0.112 + - 0.11599999999999999 + - 0.12 + - 0.124 + - 0.128 + - 0.132 + - 0.136 + - 0.14 + - 0.14400000000000002 + - 0.14800000000000002 + - 0.15200000000000002 + - 0.15600000000000003 + - 0.16 + - 0.16240000000000002 + - 0.1648 + - 0.16720000000000002 + - 0.1696 + - 0.17200000000000001 + - 0.1744 + - 0.1768 + - 0.1792 + - 0.1816 + - 0.184 + - 0.1864 + - 0.1888 + - 0.1912 + - 0.1936 + - 0.196 + - 0.20800000000000002 + - 0.22 + - 0.23500000000000001 + - 0.25 + - 0.265 + - 0.28 + - 0.28625 + - 0.29250000000000004 + - 0.30500000000000005 + - 0.3175 + - 0.33 + - 0.335 + - 0.33999999999999997 + - 0.345 + - 0.35 + - 0.3516666666666666 + - 0.35333333333333333 + - 0.355 + - 0.35666666666666663 + - 0.35833333333333334 + - 0.36 + - 0.3595 + - 0.359 + - 0.3585 + - 0.358 + - 0.3575 + - 0.357 + - 0.3565 + - 0.356 + - 0.3555 + - 0.355 + - 0.3545 + - 0.354 + - 0.3535 + - 0.353 + - 0.3525 + - 0.352 + - 0.3515 + - 0.351 + - 0.3505 + - 0.35 + - 0.3495 + - 0.349 + - 0.3485 + - 0.348 + - 0.3475 + - 0.347 + - 0.3465 + - 0.346 + - 0.3455 + - 0.345 + - 0.34450000000000003 + - 0.34400000000000003 + - 0.3435 + - 0.343 + - 0.3425 + - 0.342 + - 0.3415 + - 0.341 + - 0.3405 + - 0.34 + - 0.335 + - 0.33 + - 0.325 + - 0.32 + - 0.315 + - 0.31 + - 0.305 + - 0.3 +veh_kg: 1887.969865101469 \ No newline at end of file diff --git a/rust/fastsim-core/tests/integration-tests.rs b/rust/fastsim-core/tests/integration-tests.rs new file mode 100644 index 00000000..9834230f --- /dev/null +++ b/rust/fastsim-core/tests/integration-tests.rs @@ -0,0 +1,81 @@ +use std::path::Path; + +use fastsim_core::traits::*; +use fastsim_core::*; + +const REFERENCE_VEHICLE: &str = include_str!("assets/1110_2022_Tesla_Model_Y_RWD_opt45017.yaml"); + +#[test] +fn test_from_cache() { + let test_path = "1110_2022_Tesla_Model_Y_RWD_opt45017_from_cache.yaml"; + let comparison_vehicle = crate::vehicle::RustVehicle::from_yaml(REFERENCE_VEHICLE).unwrap(); + crate::vehicle::RustVehicle::to_cache(&comparison_vehicle, test_path).unwrap(); + let vehicle = crate::vehicle::RustVehicle::from_cache(test_path).unwrap(); + assert_eq!(comparison_vehicle, vehicle); + let full_file_path = Path::new("vehicles").join(test_path); + let path_including_directory = utils::path_to_cache().unwrap().join(full_file_path); + std::fs::remove_file(path_including_directory).unwrap(); +} + +#[test] +fn test_to_cache() { + let comparison_vehicle = crate::vehicle::RustVehicle::from_yaml(REFERENCE_VEHICLE).unwrap(); + crate::vehicle::RustVehicle::to_cache( + &comparison_vehicle, + "1110_2022_Tesla_Model_Y_RWD_opt45017.yaml", + ) + .unwrap(); + let data_subdirectory = utils::create_project_subdir("vehicles").unwrap(); + let file_path = data_subdirectory.join("1110_2022_Tesla_Model_Y_RWD_opt45017.yaml"); + println!("{}", file_path.to_string_lossy()); + println!("{}", crate::vehicle::RustVehicle::CACHE_FOLDER); + let vehicle_b = crate::vehicle::RustVehicle::from_file(&file_path).unwrap(); + assert_eq!(comparison_vehicle, vehicle_b); + std::fs::remove_file(file_path).unwrap(); +} + +#[test] +fn test_url_to_cache() { + utils::url_to_cache("https://raw.githubusercontent.com/NREL/fastsim-vehicles/main/public/1110_2022_Tesla_Model_Y_RWD_opt45017.yaml", "vehicles").unwrap(); + let data_subdirectory = utils::create_project_subdir("vehicles").unwrap(); + let file_path = data_subdirectory.join("1110_2022_Tesla_Model_Y_RWD_opt45017.yaml"); + println!("{}", file_path.to_string_lossy()); + let vehicle = crate::vehicle::RustVehicle::from_file(&file_path).unwrap(); + let comparison_vehicle = crate::vehicle::RustVehicle::from_yaml(REFERENCE_VEHICLE).unwrap(); + assert_eq!(vehicle, comparison_vehicle); + std::fs::remove_file(file_path).unwrap(); +} + +#[test] +fn test_from_github_or_url() { + let mut comparison_vehicle = vehicle::RustVehicle::from_yaml(REFERENCE_VEHICLE).unwrap(); + comparison_vehicle.doc = Some("Vehicle from https://raw.githubusercontent.com/NREL/fastsim-vehicles/main/public/1110_2022_Tesla_Model_Y_RWD_opt45017.yaml".to_owned()); + // test no url provided + let vehicle = vehicle::RustVehicle::from_github_or_url( + "public/1110_2022_Tesla_Model_Y_RWD_opt45017.yaml", + None, + ) + .unwrap(); + assert_eq!(vehicle, comparison_vehicle); + // test url provided + let vehicle_1 = vehicle::RustVehicle::from_github_or_url( + "1110_2022_Tesla_Model_Y_RWD_opt45017.yaml", + Some("https://raw.githubusercontent.com/NREL/fastsim-vehicles/main/public/"), + ) + .unwrap(); + assert_eq!(vehicle_1, comparison_vehicle); + let vehicle_2 = vehicle::RustVehicle::from_github_or_url( + "public/1110_2022_Tesla_Model_Y_RWD_opt45017.yaml", + Some("https://raw.githubusercontent.com/NREL/fastsim-vehicles/main/"), + ) + .unwrap(); + assert_eq!(vehicle_2, comparison_vehicle); +} + +#[test] +fn test_from_url() { + let vehicle = vehicle::RustVehicle::from_url("https://raw.githubusercontent.com/NREL/fastsim-vehicles/main/public/1110_2022_Tesla_Model_Y_RWD_opt45017.yaml").unwrap(); + let mut comparison_vehicle = vehicle::RustVehicle::from_yaml(REFERENCE_VEHICLE).unwrap(); + comparison_vehicle.doc = Some("Vehicle from https://raw.githubusercontent.com/NREL/fastsim-vehicles/main/public/1110_2022_Tesla_Model_Y_RWD_opt45017.yaml".to_owned()); + assert_eq!(vehicle, comparison_vehicle); +}