use rust_alloc::string::ToString;
use std::fs;
use std::io;
use rust_alloc::borrow::ToOwned;
use std::path::Path;
use crate::alloc::borrow::Cow;
use crate::alloc::{String, Vec};
use crate::runtime::Protocol;
use crate::ItemBuf;
use anyhow::{Context as _, Error, Result};
use base64::display::Base64Display;
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use relative_path::{RelativePath, RelativePathBuf};
use sha2::{Digest, Sha256};
#[derive(Debug, Default, Clone, Copy)]
pub(crate) struct TestParams {
pub(crate) no_run: bool,
pub(crate) should_panic: bool,
pub(crate) ignore: bool,
}
#[derive(Default, Debug, Clone, Copy)]
pub(crate) enum TestKind {
#[default]
Free,
Protocol(&'static Protocol),
}
pub(crate) struct Test {
pub(crate) item: ItemBuf,
pub(crate) kind: TestKind,
pub(crate) content: String,
pub(crate) params: TestParams,
}
pub(crate) struct Artifacts {
pub(crate) enabled: bool,
assets: Vec<Asset>,
tests: Vec<Test>,
}
impl Artifacts {
pub(crate) fn new() -> Self {
Self {
enabled: true,
assets: Vec::new(),
tests: Vec::new(),
}
}
pub(crate) fn without_assets() -> Self {
Self {
enabled: false,
assets: Vec::new(),
tests: Vec::new(),
}
}
pub(crate) fn set_tests(&mut self, tests: Vec<Test>) {
self.tests = tests;
}
pub(crate) fn assets(&self) -> impl Iterator<Item = &Asset> {
self.assets.iter()
}
pub(crate) fn tests(&self) -> impl Iterator<Item = &Test> {
self.tests.iter()
}
pub(crate) fn asset<P, F>(
&mut self,
hash: bool,
path: &P,
content: F,
) -> Result<RelativePathBuf>
where
P: ?Sized + AsRef<RelativePath>,
F: FnOnce() -> Result<Cow<'static, [u8]>>,
{
if !self.enabled {
return Ok(path.as_ref().to_owned());
}
let content = content().context("Building asset content")?;
let path = if hash {
let mut hasher = Sha256::new();
hasher.update(content.as_ref());
let result = hasher.finalize();
let hash = Base64Display::new(&result[..], &URL_SAFE_NO_PAD);
let path = path.as_ref();
let stem = path.file_stem().context("Missing file stem")?;
let ext = path.extension().context("Missing file extension")?;
path.with_file_name(format!("{stem}-{hash}.{ext}"))
} else {
path.as_ref().to_owned()
};
self.assets.try_push(Asset {
path: path.clone(),
content,
})?;
Ok(path)
}
}
pub(crate) struct Asset {
path: RelativePathBuf,
content: Cow<'static, [u8]>,
}
impl Asset {
pub(crate) fn build(&self, root: &Path) -> Result<()> {
let p = self.path.to_path(root);
tracing::info!("Writing: {}", p.display());
ensure_parent_dir(&p)?;
fs::write(&p, &self.content).with_context(|| p.display().to_string())?;
Ok(())
}
}
fn ensure_parent_dir(path: &Path) -> Result<()> {
if let Some(p) = path.parent() {
if p.is_dir() {
return Ok(());
}
tracing::info!("create dir: {}", p.display());
match fs::create_dir_all(p) {
Ok(()) => {}
Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {}
Err(e) => return Err(Error::from(e)).context(p.display().to_string()),
}
}
Ok(())
}