Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

File inclusion with an explicit destination path #11

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 28 additions & 4 deletions assay-proc-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use syn::{
};

struct AssayAttribute {
include: Option<Vec<String>>,
include: Option<Vec<(String, Option<String>)>>,
should_panic: bool,
env: Option<Vec<(String, String)>>,
setup: Option<Expr>,
Expand Down Expand Up @@ -48,7 +48,27 @@ impl Parse for AssayAttribute {
Expr::Lit(ExprLit {
lit: Lit::Str(lit_str),
..
}) => Some(lit_str.value()),
}) => {
let value = lit_str.value();
Some((value, None))
},
Expr::Tuple(tuple) => {
let mut elements = Vec::new();
for e in tuple.elems.into_iter() {
if let Expr::Lit(ExprLit { lit: Lit::Str(lit_str), ..}) = e {
elements.push(lit_str.value());
} else {
// TODO: Should we return a parsing error to the user here? How?
LukeMathWalker marked this conversation as resolved.
Show resolved Hide resolved
return None;
}
}
if elements.len() == 2 {
Some((elements[0].clone(), Some(elements[1].clone())))
} else {
// TODO: Should we return a parsing error to the user here? How?
None
}
}
_ => None,
})
.collect(),
Expand Down Expand Up @@ -112,10 +132,14 @@ pub fn assay(attr: TokenStream, item: TokenStream) -> TokenStream {
let mut out = quote! {
let fs = assay::PrivateFS::new()?;
};
for file in include {
for (source_path, destination_path) in include {
let destination_fragment = match destination_path {
None => quote!(std::option::Option::None),
Some(p) => quote!(std::option::Option::Some(#p))
};
out = quote! {
#out
fs.include(#file)?;
fs.include(#source_path, #destination_fragment)?;
};
}
out
Expand Down
49 changes: 28 additions & 21 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ pub use rusty_fork::{fork, rusty_fork_id, rusty_fork_test_name, ChildWrapper};
use std::{
env,
error::Error,
fs::{copy, create_dir_all},
path::{Component, Path, PathBuf},
fs::{copy},
path::{Path, PathBuf},
};
use std::fs::create_dir_all;
use tempfile::{Builder, TempDir};

#[doc(hidden)]
Expand All @@ -47,39 +48,45 @@ impl PrivateFS {
})
}

pub fn include(&self, path: impl AsRef<Path>) -> Result<(), Box<dyn Error>> {
pub fn include(&self, source_path: impl AsRef<Path>, destination_path: Option<&str>) -> Result<(), Box<dyn Error>> {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An alternative here would be to add a generic parameter S: AsRef<Path> and tie both inputs to use S.
We cannot have destination_path: Option<impl AsRef<Path>> because it causes an inference error when the path is omitted.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@LukeMathWalker would it be possible to do this so they can be separate types or is the inference breaking here?

pub fn include(&self, source_path: S, destination_path: Option<D>)
where S: AsRef<Path>,
      D: AsRef<Path>

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That still failed, but I managed to work around the inference issue by forcing D to be PathBuf in the code generated by the proc macro for the None case.

// Get our pathbuf to the file to include
let mut inner_path = path.as_ref().to_owned();
let mut inner_path = source_path.as_ref().to_owned();

// If the path given is not absolute then it's relative to the dir we
// ran the test from
let is_relative = inner_path.is_relative();
if is_relative {
inner_path = self.ran_from.join(&path);
inner_path = self.ran_from.join(&source_path);
}

// Get our working directory
let dir = self.directory.path().to_owned();

// Make the relative path of the file in relation to our temp file
// system based on if it was absolute or not
let relative = if !is_relative {
inner_path
.components()
.filter(|c| *c != Component::RootDir)
.collect::<PathBuf>()
} else {
path.as_ref().into()
let destination_path = match destination_path {
None => {
// If the destination path is unspecified, we mount the file in the root directory
// of the test's private filesystem
// TODO: return meaningful error message
let filename = inner_path.file_name().unwrap().to_owned();
dir.join(filename)
},
Some(p) => {
let p = PathBuf::from(p);
if !p.is_relative() {
// TODO: do we want to panic here?
panic!("The destination path for included files must be a relative path. {:?} isn't.", p);
LukeMathWalker marked this conversation as resolved.
Show resolved Hide resolved
}
// If the relative path to the file includes parent directories create
// them
if let Some(parent) = p.parent() {
create_dir_all(dir.join(parent))?;
}
dir.join(p)
}
};

// If the relative path to the file includes parent directories create
// them
if let Some(parent) = relative.parent() {
create_dir_all(dir.join(parent))?;
}

// Copy the file over from the file system into the temp file system
copy(inner_path, dir.join(relative))?;
copy(inner_path, destination_path)?;

Ok(())
}
Expand Down
9 changes: 5 additions & 4 deletions tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ fn private_2() {
assert_eq!("This is a test\nprivate 2\n", &fs::read_to_string("test")?);
}

#[assay(include = ["Cargo.toml", "src/lib.rs"])]
#[assay(include = ["Cargo.toml", "src/lib.rs", ("HOW_TO_USE.md", "docs/GUIDE.md")])]
fn include() {
assert!(fs::metadata("src/lib.rs")?.is_file());
assert!(fs::metadata("lib.rs")?.is_file());
assert!(fs::metadata("Cargo.toml")?.is_file());
assert!(fs::metadata("docs/GUIDE.md")?.is_file());
}

#[assay(should_panic)]
Expand Down Expand Up @@ -95,7 +96,7 @@ fn setup_teardown_test_2() {

#[assay(
setup = setup_func_2(),
include = ["Cargo.toml", "src/lib.rs"],
include = ["Cargo.toml", ("src/lib.rs", "src/lib.rs")],
env = [
("GOODBOY", "Bukka"),
("BADDOGS", "false")
Expand Down Expand Up @@ -133,7 +134,7 @@ async fn one_test_to_call_it_all_2() {
assert_eq!(env::var("BADDOGS")?, "false");
assert_eq!(fs::read_to_string("setup")?, "Value: 5");
assert!(PathBuf::from("Cargo.toml").exists());
assert!(PathBuf::from("src/lib.rs").exists());
assert!(PathBuf::from("lib.rs").exists());

// Removing this actually causes the test to fail
panic!();
Expand Down