use std::fs;
use std::path::{Path, PathBuf};
use std::sync::LazyLock;
use assert_fs::TempDir;
use assert_fs::prelude::*;
use itertools::Itertools;
use lsp_types::Url;
use crate::support::cairo_project_toml::WELL_KNOWN_CAIRO_PROJECT_TOMLS;
const TOOL_VERSIONS: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/.tool-versions"));
pub struct Fixture {
t: LazyLock<TempDir>,
files: Vec<PathBuf>,
insta_settings: Option<insta::internals::SettingsBindDropGuard>,
}
impl Fixture {
pub fn new() -> Self {
Self {
t: LazyLock::new(|| TempDir::new().unwrap()),
files: Vec::new(),
insta_settings: None,
}
}
}
impl Fixture {
pub fn add_file(&mut self, path: impl AsRef<Path>, contents: impl AsRef<str>) -> &mut Self {
self.files.push(path.as_ref().to_owned());
self.edit_file(path, contents);
self
}
pub fn add_file_if_not_exists(
&mut self,
path: impl AsRef<Path>,
contents: impl AsRef<str>,
) -> &mut Self {
let path = path.as_ref().to_owned();
if self.files.contains(&path) {
return self;
}
self.add_file(path, contents);
self
}
pub fn edit_file(&mut self, path: impl AsRef<Path>, contents: impl AsRef<str>) -> &mut Self {
self.t.child(path).write_str(contents.as_ref().trim()).unwrap();
self
}
pub fn add_tool_versions(&mut self) {
self.add_file(".tool-versions", TOOL_VERSIONS);
}
}
impl Fixture {
pub fn root_path(&self) -> PathBuf {
self.t.path().canonicalize().unwrap()
}
pub fn root_url(&self) -> Url {
Url::from_directory_path(self.t.path().canonicalize().unwrap()).unwrap()
}
pub fn file_absolute_path(&self, path: impl AsRef<Path>) -> PathBuf {
let path = path.as_ref();
if path.is_absolute() {
path.to_path_buf()
} else {
self.t.child(path).canonicalize().unwrap().to_owned()
}
}
pub fn file_relative_path(&self, path: impl AsRef<Path>) -> PathBuf {
let path = self.file_absolute_path(path);
PathBuf::from("./").join(path.strip_prefix(self.root_path()).unwrap())
}
pub fn file_url(&self, path: impl AsRef<Path>) -> Url {
Url::from_file_path(self.file_absolute_path(path)).unwrap()
}
pub fn read_file(&self, path: impl AsRef<Path>) -> String {
fs::read_to_string(self.file_absolute_path(path)).unwrap()
}
pub fn maybe_read_file(&self, path: impl AsRef<Path>) -> Option<String> {
fs::read_to_string(self.file_absolute_path(path)).ok()
}
pub fn url_path(&self, url: &Url) -> Result<PathBuf, String> {
let path = url.to_file_path().map_err(|()| format!("not a file url: {url}"))?;
let path = path
.strip_prefix(self.root_path())
.map_err(|_| format!("url leads to a file outside test fixture: {url}"))?;
Ok(path.to_path_buf())
}
pub fn files(&self) -> &[PathBuf] {
&self.files
}
}
impl Fixture {
#[doc(hidden)]
pub fn update_insta_settings(&mut self) {
let mut settings = insta::Settings::clone_current();
settings.set_description(self.build_insta_description());
drop(self.insta_settings.take());
self.insta_settings = Some(settings.bind_to_scope());
}
fn build_insta_description(&self) -> String {
self.files
.iter()
.sorted()
.map(|path| {
let test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests");
(
path,
self.read_file(path).trim().to_owned().replace(test_dir.to_str().unwrap(), ""),
)
})
.filter(|(path, contents)| {
let str_file_name = path.file_name().unwrap().to_str().unwrap();
match str_file_name {
".tool-versions" => false,
"cairo_project.toml" => !WELL_KNOWN_CAIRO_PROJECT_TOMLS
.iter()
.any(|it| it.trim() == contents.trim()),
_ => true,
}
})
.map(|(path, contents)| format!("// → {path}\n{contents}", path = path.display()))
.join("\n\n")
}
}
macro_rules! fixture {
{ $($file:expr => $content:expr),* $(,)? } => {{
let mut fixture = $crate::support::fixture::Fixture::new();
$(fixture.add_file($file, $content);)*
fixture
}};
}
pub(crate) use fixture;