#![cfg_attr(feature = "deny-warnings", deny(warnings))] #![warn(
unused,
clippy::dbg_macro,
clippy::decimal_literal_representation,
clippy::undocumented_unsafe_blocks,
clippy::empty_structs_with_brackets,
clippy::format_push_string,
clippy::get_unwrap,
clippy::if_then_some_else_none,
clippy::impl_trait_in_params,
clippy::integer_division,
clippy::large_include_file,
clippy::let_underscore_must_use,
clippy::semicolon_outside_block,
clippy::str_to_string,
clippy::todo,
clippy::unimplemented,
clippy::unneeded_field_pattern,
clippy::use_debug,
clippy::branches_sharing_code,
clippy::cast_possible_wrap,
clippy::doc_markdown,
clippy::empty_enum,
clippy::if_not_else,
clippy::inefficient_to_string,
clippy::items_after_statements,
clippy::large_digit_groups,
clippy::large_types_passed_by_value,
clippy::match_same_arms,
clippy::missing_const_for_fn,
clippy::missing_panics_doc,
clippy::needless_bitwise_bool,
clippy::needless_collect,
clippy::needless_pass_by_value,
clippy::no_effect_underscore_binding,
clippy::nonstandard_macro_braces,
clippy::or_fun_call,
clippy::range_plus_one,
clippy::range_minus_one,
clippy::similar_names,
clippy::suboptimal_flops,
clippy::too_many_lines,
clippy::unused_self
)]
use std::{
env,
ffi::OsStr,
fs::{write, File},
io::Read,
os,
path::Path,
process::{Command, Output},
str,
};
use anyhow::Result;
#[cfg(feature = "better_panic")]
pub use better_panic;
#[cfg(feature = "pretty_assertions")]
use pretty_assertions::assert_eq;
#[cfg(feature = "regex")]
use regex::Regex;
use tempfile::{tempdir, TempDir};
#[cfg(feature = "better_panic")]
pub mod panic {
use better_panic::{Settings, Verbosity};
#[inline]
pub fn minimal() {
Settings::new().verbosity(Verbosity::Minimal).install();
}
#[inline]
pub fn medium() {
Settings::new().verbosity(Verbosity::Medium).install();
}
#[inline]
pub fn full() {
Settings::new().verbosity(Verbosity::Full).install();
}
}
#[derive(Debug)]
pub struct Project {
tempdir: TempDir,
}
#[inline(always)]
pub fn project() -> Result<Project> {
Project::new()
}
pub fn init() {
let md = cargo_metadata::MetadataCommand::new()
.exec()
.expect("Couldn't get Cargo Metadata");
let root = md.root_package().unwrap();
env::set_var("SANDBOX_TARGET_DIR", &md.target_directory);
env::set_var("SANDBOX_PKG_NAME", &root.name);
}
impl Project {
pub fn new() -> Result<Self> {
Ok(Self {
tempdir: tempdir()?,
})
}
pub fn path(&self) -> &Path {
self.tempdir.path()
}
#[inline]
pub fn new_file<P: AsRef<Path>>(&mut self, path: P, contents: &str) -> Result<()> {
Ok(write(self.path().join(path), contents)?)
}
pub fn check_file<P: AsRef<Path>>(&self, path: P, contents: &str) -> Result<()> {
let mut f = File::open(self.path().join(path))?;
let mut buf = Vec::new();
f.read_to_end(&mut buf)?;
let mut buf2 = String::new();
buf2.push_str(match str::from_utf8(&buf) {
Ok(val) => val,
Err(_) => panic!("buf isn't UTF-8 (bug)"),
});
assert_eq!(buf2, contents);
Ok(())
}
pub fn command<I, S>(&self, args: I) -> Result<Output>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
#[cfg(feature = "dev")]
return Ok(Command::new(
Path::new(&std::env::var("SANDBOX_TARGET_DIR")?)
.join("debug")
.join(std::env::var("SANDBOX_PKG_NAME")?),
)
.current_dir(self.path())
.args(args)
.output()?);
#[cfg(feature = "release")]
return Ok(Command::new(
Path::new(&std::env::var("CARGO_MANIFEST_DIR")?)
.join("target")
.join("release")
.join(env!("CARGO_PKG_NAME")),
)
.current_dir(&self.path())
.args(args)
.output()?);
}
pub fn is_bin<P: AsRef<Path>>(&self, path: P) -> bool {
let mut buf: [u8; 8] = [0; 8];
let mut f = File::open(self.path().join(&path)).expect("Couldn't open that path");
match f.read_exact(&mut buf) {
Ok(()) => {}
Err(_) => {
buf.fill(0x01) }
};
match buf {
[0x4D, 0x5A, ..] | [0x5A, 0x4D, ..] | [0x7F, 0x45, 0x4C, 0x46, ..] | [0x64, 0x65, 0x78, 0x0A, 0x30, 0x33, 0x35, 0x00] | [0x4A, 0x6F, 0x79, 0x21, ..] | [0x00, 0x00, 0x03, 0xF3, ..] => {
true
}
_ => {
false
}
}
}
pub fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(&self, src: P, dst: Q) {
let src = self.path().join(src.as_ref());
let dst = self.path().join(dst.as_ref());
#[cfg(unix)]
{
if let Err(e) = os::unix::fs::symlink(&src, &dst) {
panic!("failed to symlink {:?} to {:?}: {:?}", src, dst, e);
}
}
#[cfg(windows)]
{
if src.is_dir() {
if let Err(e) = os::windows::fs::symlink_dir(&src, &dst) {
panic!("failed to symlink {:?} to {:?}: {:?}", src, dst, e);
}
} else {
if let Err(e) = os::windows::fs::symlink_file(&src, &dst) {
panic!("failed to symlink {:?} to {:?}: {:?}", src, dst, e);
}
}
}
}
pub fn clean_env(&self, prefix: &str) {
let cwd = env::current_dir().expect("Couldn't get current working directory");
env::set_current_dir(self.path()).expect("Couldn't change path");
for (k, _) in env::vars() {
if k.starts_with(prefix) {
env::remove_var(k);
};
}
env::set_current_dir(cwd).expect("Couldn't return to the origin directory");
}
}
pub trait WithStdout {
fn with_stdout<S: AsRef<str>>(&self, stdout: S);
fn with_stderr<S: AsRef<str>>(&self, stderr: S);
#[cfg(feature = "regex")]
fn with_stdout_regex<S: AsRef<str>>(&self, stdout: S);
#[cfg(feature = "regex")]
fn with_stderr_regex<S: AsRef<str>>(&self, stderr: S);
fn stdout_warns(&self) -> bool;
fn stderr_warns(&self) -> bool;
fn empty_stderr(&self) -> bool;
fn empty_stdout(&self) -> bool;
fn with_stdout_file<P: AsRef<Path>>(&self, filename: P);
fn with_stderr_file<P: AsRef<Path>>(&self, filename: P);
}
impl WithStdout for Output {
fn with_stdout<S: AsRef<str>>(&self, stdout: S) {
let mut buf = String::new();
buf.push_str(match str::from_utf8(&self.stdout) {
Ok(val) => val,
Err(_) => panic!("stdout isn't UTF-8 (bug)"),
});
assert_eq!(buf, stdout.as_ref());
}
fn with_stderr<S: AsRef<str>>(&self, stderr: S) {
let mut buf = String::new();
buf.push_str(match str::from_utf8(&self.stderr) {
Ok(val) => val,
Err(_) => panic!("stderr isn't UTF-8 (bug)"),
});
assert_eq!(buf, stderr.as_ref());
}
#[cfg(feature = "regex")]
fn with_stderr_regex<S: AsRef<str>>(&self, regex: S) {
let re = match Regex::new(regex.as_ref()) {
Ok(re) => re,
Err(e) => panic!("Regex {} isn't valid: {e}", regex.as_ref()),
};
let mut buf = String::new();
buf.push_str(match str::from_utf8(&self.stderr) {
Ok(val) => val,
Err(_) => panic!("stderr isn't UTF-8 (bug)"),
});
if !re.is_match(&buf) {
assert_eq!(buf, regex.as_ref()); };
}
#[cfg(feature = "regex")]
fn with_stdout_regex<S: AsRef<str>>(&self, regex: S) {
let re = match Regex::new(regex.as_ref()) {
Ok(re) => re,
Err(e) => panic!("Regex {} isn't valid: {e}", regex.as_ref()),
};
let mut buf = String::new();
buf.push_str(match str::from_utf8(&self.stdout) {
Ok(val) => val,
Err(_) => panic!("stdout isn't UTF-8 (bug)"),
});
if !re.is_match(&buf) {
assert_eq!(buf, regex.as_ref()); };
}
fn stdout_warns(&self) -> bool {
let mut buf = String::new();
buf.push_str(match str::from_utf8(&self.stdout) {
Ok(val) => val,
Err(_) => panic!("stdout isn't UTF-8 (bug)"),
});
buf.contains("warnings:")
}
fn stderr_warns(&self) -> bool {
let mut buf = String::new();
buf.push_str(match str::from_utf8(&self.stderr) {
Ok(val) => val,
Err(_) => panic!("stderr isn't UTF-8 (bug)"),
});
buf.contains("warnings:")
}
#[inline]
fn empty_stderr(&self) -> bool {
self.stdout.is_empty()
}
#[inline]
fn empty_stdout(&self) -> bool {
self.stdout.is_empty()
}
fn with_stdout_file<P: AsRef<Path>>(&self, filename: P) {
let expected = match std::fs::read_to_string(&filename) {
Ok(s) => s,
Err(e) => panic!("Couldn't read file {}: {e}", filename.as_ref().display()),
};
let mut buf = String::new();
buf.push_str(match str::from_utf8(&self.stdout) {
Ok(val) => val,
Err(_) => panic!("stdout isn't UTF-8 (bug)"),
});
assert_eq!(expected, buf);
}
fn with_stderr_file<P: AsRef<Path>>(&self, filename: P) {
let expected = match std::fs::read_to_string(&filename) {
Ok(s) => s,
Err(e) => panic!("Couldn't read file {}: {e}", filename.as_ref().display()),
};
let mut buf = String::new();
buf.push_str(match str::from_utf8(&self.stderr) {
Ok(val) => val,
Err(_) => panic!("stderr isn't UTF-8 (bug)"),
});
assert_eq!(expected, buf);
}
}
#[cfg(feature = "fuzz")]
pub fn fuzz(length: usize) -> String {
let charset = if let Ok(charset) = env::var("CARGO_CFG_FUZZ_CHARSET") {
charset
} else {
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".into()
};
let chars = charset.chars().collect::<Vec<char>>();
let mut buf = String::new();
for _ in 0..=length {
buf.push(chars[fastrand::usize(..charset.len())]);
}
buf
}
#[cfg(feature = "fuzz_seed")]
pub fn fuzz_seed(length: usize, seed: u64) -> String {
fastrand::seed(seed);
let charset = if let Ok(charset) = env::var("CARGO_CFG_FUZZ_CHARSET") {
charset
} else {
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".into()
};
let mut chars = charset.chars();
let mut buf = String::new();
for _ in 0..=length {
buf.push(
chars
.nth(fastrand::u8(..charset.len() as u8).into())
.unwrap(),
);
}
charset
}
pub const MANIFEST_DIR: &str = env!("CARGO_MANIFEST_DIR");