use crate::{
ManifestPath,
OptimizationPasses,
};
use anyhow::{
Context,
Result,
};
use std::{
fs,
ops::Deref,
path::{
Path,
PathBuf,
},
};
use toml::value;
pub fn with_tmp_dir<F>(f: F)
where
F: FnOnce(&Path) -> Result<()>,
{
let tmp_dir = tempfile::Builder::new()
.prefix("cargo-contract.test.")
.tempdir()
.expect("temporary directory creation failed");
f(&tmp_dir.path().canonicalize().unwrap()).expect("Error executing test with tmp dir")
}
pub fn with_new_contract_project<F>(f: F)
where
F: FnOnce(ManifestPath) -> Result<()>,
{
with_tmp_dir(|tmp_dir| {
let project_name = "new_project";
crate::cmd::new::execute(project_name, Some(tmp_dir))
.expect("new project creation failed");
let working_dir = tmp_dir.join(project_name);
let manifest_path = ManifestPath::new(working_dir.join("Cargo.toml"))?;
f(manifest_path)
})
}
pub struct MockGuard(PathBuf);
impl Drop for MockGuard {
fn drop(&mut self) {
std::fs::remove_file(&self.0).ok();
}
}
impl Deref for MockGuard {
type Target = Path;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(unix)]
pub fn create_executable(path: &Path, content: &str) -> MockGuard {
use std::{
env,
io::Write,
os::unix::fs::PermissionsExt,
};
let mut guard = MockGuard(path.to_path_buf());
let mut file = std::fs::File::create(path).unwrap();
let path = path.canonicalize().unwrap();
guard.0 = path.clone();
file.write_all(content.as_bytes())
.expect("writing of executable failed");
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o777))
.expect("setting permissions failed");
let env_paths = {
let work_dir = path.parent().unwrap().to_path_buf();
let pathes = env::var_os("PATH").unwrap_or_default();
let mut pathes: Vec<_> = env::split_paths(&pathes).collect();
if !pathes.contains(&work_dir) {
pathes.insert(0, work_dir);
}
pathes
};
env::set_var("PATH", env::join_paths(&env_paths).unwrap());
guard
}
#[cfg(any(feature = "integration-tests", feature = "test-ci-only"))]
pub fn init_tracing_subscriber() {
let _ = tracing_subscriber::fmt()
.with_max_level(tracing::Level::TRACE)
.with_test_writer()
.try_init();
}
pub struct BuildTestContext {
template_dir: PathBuf,
working_dir: PathBuf,
}
impl BuildTestContext {
pub fn new(tmp_dir: &Path, working_project_name: &str) -> Result<Self> {
crate::cmd::new::execute(working_project_name, Some(tmp_dir))
.expect("new project creation failed");
let working_dir = tmp_dir.join(working_project_name);
let template_dir = tmp_dir.join(format!("{}_template", working_project_name));
fs::rename(&working_dir, &template_dir)?;
copy_dir_all(&template_dir, &working_dir)?;
Ok(Self {
template_dir,
working_dir,
})
}
pub fn run_test(
&self,
name: &str,
test: impl FnOnce(&ManifestPath) -> Result<()>,
) -> Result<()> {
println!("Running {}", name);
let manifest_path = ManifestPath::new(self.working_dir.join("Cargo.toml"))?;
match test(&manifest_path) {
Ok(()) => (),
Err(err) => {
println!("{} FAILED: {:?}", name, err);
}
}
self.remove_all_except_target_dir()?;
copy_dir_all(&self.template_dir, &self.working_dir)?;
Ok(())
}
fn remove_all_except_target_dir(&self) -> Result<()> {
for entry in fs::read_dir(&self.working_dir)? {
let entry = entry?;
let ty = entry.file_type()?;
if ty.is_dir() {
if entry.file_name() != "target" {
fs::remove_dir_all(entry.path())?
}
} else {
fs::remove_file(entry.path())?
}
}
Ok(())
}
}
fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<()> {
fs::create_dir_all(&dst)?;
for entry in fs::read_dir(src)? {
let entry = entry?;
let ty = entry.file_type()?;
if ty.is_dir() {
copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
} else {
fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
}
}
Ok(())
}
pub struct TestContractManifest {
toml: value::Table,
manifest_path: ManifestPath,
}
impl TestContractManifest {
pub fn new(manifest_path: ManifestPath) -> Result<Self> {
Ok(Self {
toml: toml::from_slice(&fs::read(&manifest_path)?)?,
manifest_path,
})
}
fn package_mut(&mut self) -> Result<&mut value::Table> {
self.toml
.get_mut("package")
.context("package section not found")?
.as_table_mut()
.context("package section should be a table")
}
pub fn add_user_metadata_value(
&mut self,
key: &'static str,
value: value::Value,
) -> Result<()> {
self.package_mut()?
.entry("metadata")
.or_insert(value::Value::Table(Default::default()))
.as_table_mut()
.context("metadata section should be a table")?
.entry("contract")
.or_insert(value::Value::Table(Default::default()))
.as_table_mut()
.context("metadata.contract section should be a table")?
.entry("user")
.or_insert(value::Value::Table(Default::default()))
.as_table_mut()
.context("metadata.contract.user section should be a table")?
.insert(key.into(), value);
Ok(())
}
pub fn add_package_value(
&mut self,
key: &'static str,
value: value::Value,
) -> Result<()> {
self.package_mut()?.insert(key.into(), value);
Ok(())
}
pub fn set_profile_optimization_passes(
&mut self,
passes: OptimizationPasses,
) -> Result<Option<value::Value>> {
Ok(self
.toml
.entry("package")
.or_insert(value::Value::Table(Default::default()))
.as_table_mut()
.context("package section should be a table")?
.entry("metadata")
.or_insert(value::Value::Table(Default::default()))
.as_table_mut()
.context("metadata section should be a table")?
.entry("contract")
.or_insert(value::Value::Table(Default::default()))
.as_table_mut()
.context("metadata.contract section should be a table")?
.insert(
"optimization-passes".to_string(),
value::Value::String(passes.to_string()),
))
}
pub fn set_dependency_version(
&mut self,
dependency: &str,
version: &str,
) -> Result<Option<toml::Value>> {
Ok(self
.toml
.get_mut("dependencies")
.ok_or_else(|| anyhow::anyhow!("[dependencies] section not found"))?
.get_mut(dependency)
.ok_or_else(|| anyhow::anyhow!("{} dependency not found", dependency))?
.as_table_mut()
.ok_or_else(|| {
anyhow::anyhow!("{} dependency should be a table", dependency)
})?
.insert("version".into(), value::Value::String(version.into())))
}
pub fn set_lib_name(&mut self, name: &str) -> Result<Option<toml::Value>> {
Ok(self
.toml
.get_mut("lib")
.ok_or_else(|| anyhow::anyhow!("[lib] section not found"))?
.as_table_mut()
.ok_or_else(|| anyhow::anyhow!("[lib] should be a table"))?
.insert("name".into(), value::Value::String(name.into())))
}
pub fn set_package_name(&mut self, name: &str) -> Result<Option<toml::Value>> {
Ok(self
.toml
.get_mut("package")
.ok_or_else(|| anyhow::anyhow!("[package] section not found"))?
.as_table_mut()
.ok_or_else(|| anyhow::anyhow!("[package] should be a table"))?
.insert("name".into(), value::Value::String(name.into())))
}
pub fn set_lib_path(&mut self, path: &str) -> Result<Option<toml::Value>> {
Ok(self
.toml
.get_mut("lib")
.ok_or_else(|| anyhow::anyhow!("[lib] section not found"))?
.as_table_mut()
.ok_or_else(|| anyhow::anyhow!("[lib] should be a table"))?
.insert("path".into(), value::Value::String(path.into())))
}
pub fn write(&self) -> Result<()> {
let toml = toml::to_string(&self.toml)?;
fs::write(&self.manifest_path, toml).map_err(Into::into)
}
}