Skip to content
This repository has been archived by the owner on Dec 29, 2021. It is now read-only.

Commit

Permalink
feat(tmpdir): Augment tempdir::TempDir
Browse files Browse the repository at this point in the history
This is an experiment in what kind of tempdir operations a holistic CLI
testing framework might provide, following on the previous experiments
with extension traits. The exact structure in this crate or across
crates is TBD.

For the path to stablization, we need to keep in mind that `tempdir` is
deprecated, pending some issues being resolved in `tempfile`.

This crate extends `TempDir` with the following
- In TempDir or a child path, run a command.
- On child path, touch a file.
- On child path, write a binary blob or str to file.
- Copy to a TempDir or a child path some files.

Some other potential operations include
- `write_yml(serde)`
- `write_json(serde)`
- `write_toml(serde)`

In contrast, `cli_test_dir` can:
- Run a single pre-defined program within the tempdir
- Write binary files to tempdir
- Offer a absolute path to a child file within the crate source (so its
  safe to pass to the program running in the tempdir).
  • Loading branch information
epage committed Mar 27, 2018
1 parent 58f445a commit a211010
Show file tree
Hide file tree
Showing 3 changed files with 280 additions and 0 deletions.
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,19 @@ build = "build.rs"
[[bin]]
name = "assert_fixture"

[features]
default = ["temp_dir"]
temp_dir = ["tempdir", "globwalk"]

[dependencies]
colored = "1.5"
difference = "2.0"
failure = "0.1"
failure_derive = "0.1"
serde_json = "1.0"
environment = "0.1"
tempdir = { version="0.3", optional=true }
globwalk = { version="0.1", optional=true }

[build-dependencies]
skeptic = "0.13"
Expand Down
5 changes: 5 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,9 @@ extern crate environment;
extern crate failure;
#[macro_use]
extern crate failure_derive;
extern crate globwalk;
extern crate serde_json;
extern crate tempdir;

mod errors;
pub use errors::AssertionError;
Expand All @@ -139,6 +141,9 @@ mod output;

/// `std::process::Command` extensions.
pub mod cmd;
/// `tempdir::TempDir` extensions.
#[cfg(feature = "tempdir")]
pub mod temp;

pub use assert::Assert;
pub use assert::OutputAssertionBuilder;
Expand Down
269 changes: 269 additions & 0 deletions src/temp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
use std::ffi;
use std::fs;
use std::io;
use std::io::Write;
use std::path;
use std::process;

use globwalk;
use tempdir;
use failure;

// Quick and dirty for doc tests; not meant for long term use.
pub use tempdir::TempDir;

/// Extend `TempDir` to perform operations on relative paths within the temp directory via
/// `ChildPath`.
pub trait TempDirChildExt {
/// Create a path within the temp directory.
///
/// # Examples
///
/// ```rust,ignore
/// extern crate assert_cli;
/// use assert_cli::temp::*;
///
/// let temp = TempDir::new("TempDirChildExt_demo").unwrap();
/// println!("{:?}", temp.path());
/// println!("{:?}", temp.child("foo/bar.txt").path());
/// temp.close().unwrap();
/// ```
fn child<P>(&self, path: P) -> ChildPath
where
P: AsRef<path::Path>;
}

impl TempDirChildExt for tempdir::TempDir {
fn child<P>(&self, path: P) -> ChildPath
where
P: AsRef<path::Path>,
{
ChildPath::new(self.path().join(path.as_ref()))
}
}

/// A path within a TempDir
pub struct ChildPath {
path: path::PathBuf,
}

impl ChildPath {
/// Wrap a path for use with special built extension traits.
///
/// See trait implementations or `TempDirChildExt` for more details.
pub fn new<P>(path: P) -> Self
where
P: Into<path::PathBuf>,
{
Self { path: path.into() }
}

/// Access the path.
pub fn path(&self) -> &path::Path {
&self.path
}
}

/// Extend `TempDir` to run commands in it.
pub trait TempDirCommandExt {
/// Constructs a new Command for launching the program at path program, with the following
/// default configuration:
///
/// - The current working directory is the temp dir
/// - No arguments to the program
/// - Inherit the current process's environment
/// - Inherit the current process's working directory
/// - Inherit stdin/stdout/stderr for spawn or status, but create pipes for output
/// - Builder methods are provided to change these defaults and otherwise configure the process.
///
/// If program is not an absolute path, the PATH will be searched in an OS-defined way.
///
/// The search path to be used may be controlled by setting the PATH environment variable on
/// the Command, but this has some implementation limitations on Windows (see
/// https://github.com/rust-lang/rust/issues/37519).
///
/// # Examples
///
/// ```rust,ignore
/// extern crate assert_cli;
/// use assert_cli::temp::*;
///
/// let temp = TempDir::new("TempDirChildExt_demo").unwrap();
/// temp.command("pwd").output().unwrap();
/// temp.close().unwrap();
/// ```
fn command<S>(&self, program: S) -> process::Command
where
S: AsRef<ffi::OsStr>;
}

impl TempDirCommandExt for tempdir::TempDir {
fn command<S>(&self, program: S) -> process::Command
where
S: AsRef<ffi::OsStr>,
{
let mut cmd = process::Command::new(program);
cmd.current_dir(self.path());
cmd
}
}

impl TempDirCommandExt for ChildPath {
fn command<S>(&self, program: S) -> process::Command
where
S: AsRef<ffi::OsStr>,
{
let mut cmd = process::Command::new(program);
cmd.current_dir(self.path());
cmd
}
}

/// Extend `ChildPath` to create empty files.
pub trait ChildPathTouchExt {
/// Create an empty file at `ChildPath`.
///
/// # Examples
///
/// ```rust,ignore
/// extern crate assert_cli;
/// use assert_cli::temp::*;
///
/// let temp = TempDir::new("TempDirChildExt_demo").unwrap();
/// temp.child("foo.txt").touch().unwrap();
/// temp.close().unwrap();
/// ```
fn touch(&self) -> io::Result<()>;
}

impl ChildPathTouchExt for ChildPath {
fn touch(&self) -> io::Result<()> {
touch(self.path())
}
}

/// Extend `ChildPath` to write binary files.
pub trait ChildPathWriteBinExt {
/// Write a binary file at `ChildPath`.
///
/// # Examples
///
/// ```rust,ignore
/// extern crate assert_cli;
/// use assert_cli::temp::*;
///
/// let temp = TempDir::new("TempDirChildExt_demo").unwrap();
/// temp.child("foo.txt").write_binary(b"To be or not to be...").unwrap();
/// temp.close().unwrap();
/// ```
fn write_binary(&self, data: &[u8]) -> io::Result<()>;
}

impl ChildPathWriteBinExt for ChildPath {
fn write_binary(&self, data: &[u8]) -> io::Result<()> {
write_binary(self.path(), data)
}
}

/// Extend `ChildPath` to write text files.
pub trait ChildPathWriteStrExt {
/// Write a text file at `ChildPath`.
///
/// # Examples
///
/// ```rust,ignore
/// extern crate assert_cli;
/// use assert_cli::temp::*;
///
/// let temp = TempDir::new("TempDirChildExt_demo").unwrap();
/// temp.child("foo.txt").write_str("To be or not to be...").unwrap();
/// temp.close().unwrap();
/// ```
fn write_str(&self, data: &str) -> io::Result<()>;
}

impl ChildPathWriteStrExt for ChildPath {
fn write_str(&self, data: &str) -> io::Result<()> {
write_str(self.path(), data)
}
}

/// Extend `TempDir` to copy files into it.
pub trait TempDirCopyExt {
/// Copy files and directories into the current path from the `source` according to the glob
/// `patterns`.
///
/// # Examples
///
/// ```rust,ignore
/// extern crate assert_cli;
/// use assert_cli::temp::*;
///
/// let temp = TempDir::new("TempDirChildExt_demo").unwrap();
/// temp.copy_from(".", &["*.rs"]).unwrap();
/// temp.close().unwrap();
/// ```
fn copy_from<P, S>(&self, source: P, patterns: &[S]) -> Result<(), failure::Error>
where
P: AsRef<path::Path>,
S: AsRef<str>;
}

impl TempDirCopyExt for tempdir::TempDir {
fn copy_from<P, S>(&self, source: P, patterns: &[S]) -> Result<(), failure::Error>
where
P: AsRef<path::Path>,
S: AsRef<str>,
{
copy_from(self.path(), source.as_ref(), patterns)
}
}

impl TempDirCopyExt for ChildPath {
fn copy_from<P, S>(&self, source: P, patterns: &[S]) -> Result<(), failure::Error>
where
P: AsRef<path::Path>,
S: AsRef<str>,
{
copy_from(self.path(), source.as_ref(), patterns)
}
}

fn touch(path: &path::Path) -> io::Result<()> {
fs::File::create(path)?;
Ok(())
}

fn write_binary(path: &path::Path, data: &[u8]) -> io::Result<()> {
let mut file = fs::File::create(path)?;
file.write_all(data)?;
Ok(())
}

fn write_str(path: &path::Path, data: &str) -> io::Result<()> {
write_binary(path, data.as_bytes())
}

fn copy_from<S>(
target: &path::Path,
source: &path::Path,
patterns: &[S],
) -> Result<(), failure::Error>
where
S: AsRef<str>,
{
for entry in globwalk::GlobWalker::from_patterns(patterns, source)?.follow_links(true) {
let entry = entry?;
let rel = entry
.path()
.strip_prefix(source)
.expect("entries to be under `source`");
let target_path = target.join(rel);
if entry.file_type().is_dir() {
fs::create_dir_all(target_path)?;
} else if entry.file_type().is_file() {
fs::copy(entry.path(), target)?;
}
}
Ok(())
}

0 comments on commit a211010

Please sign in to comment.