pub mod criterion; use std::fmt::{Display, Formatter}; use std::path::PathBuf; use std::process::Command; use url::Url; /// Relative size of a test case. Benchmarks can use it to configure the time for how long a benchmark should run to get stable results. #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] pub enum TestCaseSpeed { /// A test case that is fast to run Fast, /// A normal test case Normal, /// A slow test case Slow, } #[derive(Debug, Clone)] pub struct TestCase { file: TestFile, speed: TestCaseSpeed, } impl TestCase { pub fn fast(file: TestFile) -> Self { Self { file, speed: TestCaseSpeed::Fast, } } pub fn normal(file: TestFile) -> Self { Self { file, speed: TestCaseSpeed::Normal, } } pub fn slow(file: TestFile) -> Self { Self { file, speed: TestCaseSpeed::Slow, } } } impl TestCase { pub fn code(&self) -> &str { &self.file.code } pub fn name(&self) -> &str { &self.file.name } pub fn speed(&self) -> TestCaseSpeed { self.speed } pub fn path(&self) -> PathBuf { TARGET_DIR.join(self.name()) } } #[derive(Debug, Clone)] pub struct TestFile { name: String, code: String, } static TARGET_DIR: once_cell::sync::Lazy = once_cell::sync::Lazy::new(|| { cargo_target_directory().unwrap_or_else(|| PathBuf::from("target")) }); fn cargo_target_directory() -> Option { #[derive(serde::Deserialize)] struct Metadata { target_directory: PathBuf, } std::env::var_os("CARGO_TARGET_DIR") .map(PathBuf::from) .or_else(|| { let output = Command::new(std::env::var_os("CARGO")?) .args(["metadata", "--format-version", "1"]) .output() .ok()?; let metadata: Metadata = serde_json::from_slice(&output.stdout).ok()?; Some(metadata.target_directory) }) } impl TestFile { pub fn new(name: String, code: String) -> Self { Self { name, code } } #[allow(clippy::print_stderr)] pub fn try_download(name: &str, url: &str) -> Result { let url = Url::parse(url)?; let cached_filename = TARGET_DIR.join(name); if let Ok(content) = std::fs::read_to_string(&cached_filename) { Ok(TestFile::new(name.to_string(), content)) } else { // File not yet cached, download and cache it in the target directory let response = ureq::get(url.as_str()).call()?; let content = response.into_string()?; // SAFETY: There's always the `target` directory let parent = cached_filename.parent().unwrap(); if let Err(error) = std::fs::create_dir_all(parent) { eprintln!("Failed to crate the directory for the test case {name}: {error}"); } else if let Err(error) = std::fs::write(cached_filename, &content) { eprintln!("Failed to cache test case file downloaded from {url}: {error}"); } Ok(TestFile::new(name.to_string(), content)) } } } #[derive(Debug)] pub enum TestFileDownloadError { UrlParse(url::ParseError), Request(Box), Download(std::io::Error), } impl From for TestFileDownloadError { fn from(value: url::ParseError) -> Self { Self::UrlParse(value) } } impl From for TestFileDownloadError { fn from(value: ureq::Error) -> Self { Self::Request(Box::new(value)) } } impl From for TestFileDownloadError { fn from(value: std::io::Error) -> Self { Self::Download(value) } } impl Display for TestFileDownloadError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { TestFileDownloadError::UrlParse(inner) => { write!(f, "Failed to parse url: {inner}") } TestFileDownloadError::Request(inner) => { write!(f, "Failed to download file: {inner}") } TestFileDownloadError::Download(inner) => { write!(f, "Failed to download file: {inner}") } } } } impl std::error::Error for TestFileDownloadError {}