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

Commit 9767301

Browse files
committed
feat(tmpdir): Augment tempdir::TempDir
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. 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).
1 parent f7d6716 commit 9767301

File tree

3 files changed

+280
-0
lines changed

3 files changed

+280
-0
lines changed

Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,19 @@ build = "build.rs"
1515
[[bin]]
1616
name = "assert_fixture"
1717

18+
[features]
19+
default = ["tempdir"]
20+
tempdir = ["tempfile", "globwalk"]
21+
1822
[dependencies]
1923
colored = "1.5"
2024
difference = "2.0"
2125
failure = "0.1"
2226
failure_derive = "0.1"
2327
serde_json = "1.0"
2428
environment = "0.1"
29+
tempfile = { version="3.0", optional=true }
30+
globwalk = { version="0.1", optional=true }
2531

2632
[build-dependencies]
2733
skeptic = "0.13"

src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,9 @@ extern crate environment;
124124
extern crate failure;
125125
#[macro_use]
126126
extern crate failure_derive;
127+
extern crate globwalk;
127128
extern crate serde_json;
129+
extern crate tempfile;
128130

129131
mod errors;
130132
pub use errors::AssertionError;
@@ -139,6 +141,9 @@ mod output;
139141

140142
/// `std::process::Command` extensions.
141143
pub mod cmd;
144+
/// `tempfile::TempDir` extensions.
145+
#[cfg(feature = "tempdir")]
146+
pub mod temp;
142147

143148
pub use assert::Assert;
144149
pub use assert::OutputAssertionBuilder;

src/temp.rs

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
use std::ffi;
2+
use std::fs;
3+
use std::io;
4+
use std::io::Write;
5+
use std::path;
6+
use std::process;
7+
8+
use globwalk;
9+
use tempfile;
10+
use failure;
11+
12+
// Quick and dirty for doc tests; not meant for long term use.
13+
pub use tempfile::TempDir;
14+
15+
/// Extend `TempDir` to perform operations on relative paths within the temp directory via
16+
/// `ChildPath`.
17+
pub trait TempDirChildExt {
18+
/// Create a path within the temp directory.
19+
///
20+
/// # Examples
21+
///
22+
/// ```rust,ignore
23+
/// extern crate assert_cli;
24+
/// use assert_cli::temp::*;
25+
///
26+
/// let temp = TempDir::new("TempDirChildExt_demo").unwrap();
27+
/// println!("{:?}", temp.path());
28+
/// println!("{:?}", temp.child("foo/bar.txt").path());
29+
/// temp.close().unwrap();
30+
/// ```
31+
fn child<P>(&self, path: P) -> ChildPath
32+
where
33+
P: AsRef<path::Path>;
34+
}
35+
36+
impl TempDirChildExt for tempfile::TempDir {
37+
fn child<P>(&self, path: P) -> ChildPath
38+
where
39+
P: AsRef<path::Path>,
40+
{
41+
ChildPath::new(self.path().join(path.as_ref()))
42+
}
43+
}
44+
45+
/// A path within a TempDir
46+
pub struct ChildPath {
47+
path: path::PathBuf,
48+
}
49+
50+
impl ChildPath {
51+
/// Wrap a path for use with special built extension traits.
52+
///
53+
/// See trait implementations or `TempDirChildExt` for more details.
54+
pub fn new<P>(path: P) -> Self
55+
where
56+
P: Into<path::PathBuf>,
57+
{
58+
Self { path: path.into() }
59+
}
60+
61+
/// Access the path.
62+
pub fn path(&self) -> &path::Path {
63+
&self.path
64+
}
65+
}
66+
67+
/// Extend `TempDir` to run commands in it.
68+
pub trait TempDirCommandExt {
69+
/// Constructs a new Command for launching the program at path program, with the following
70+
/// default configuration:
71+
///
72+
/// - The current working directory is the temp dir
73+
/// - No arguments to the program
74+
/// - Inherit the current process's environment
75+
/// - Inherit the current process's working directory
76+
/// - Inherit stdin/stdout/stderr for spawn or status, but create pipes for output
77+
/// - Builder methods are provided to change these defaults and otherwise configure the process.
78+
///
79+
/// If program is not an absolute path, the PATH will be searched in an OS-defined way.
80+
///
81+
/// The search path to be used may be controlled by setting the PATH environment variable on
82+
/// the Command, but this has some implementation limitations on Windows (see
83+
/// https://github.com/rust-lang/rust/issues/37519).
84+
///
85+
/// # Examples
86+
///
87+
/// ```rust,ignore
88+
/// extern crate assert_cli;
89+
/// use assert_cli::temp::*;
90+
///
91+
/// let temp = TempDir::new("TempDirChildExt_demo").unwrap();
92+
/// temp.command("pwd").output().unwrap();
93+
/// temp.close().unwrap();
94+
/// ```
95+
fn command<S>(&self, program: S) -> process::Command
96+
where
97+
S: AsRef<ffi::OsStr>;
98+
}
99+
100+
impl TempDirCommandExt for tempfile::TempDir {
101+
fn command<S>(&self, program: S) -> process::Command
102+
where
103+
S: AsRef<ffi::OsStr>,
104+
{
105+
let mut cmd = process::Command::new(program);
106+
cmd.current_dir(self.path());
107+
cmd
108+
}
109+
}
110+
111+
impl TempDirCommandExt for ChildPath {
112+
fn command<S>(&self, program: S) -> process::Command
113+
where
114+
S: AsRef<ffi::OsStr>,
115+
{
116+
let mut cmd = process::Command::new(program);
117+
cmd.current_dir(self.path());
118+
cmd
119+
}
120+
}
121+
122+
/// Extend `ChildPath` to create empty files.
123+
pub trait ChildPathTouchExt {
124+
/// Create an empty file at `ChildPath`.
125+
///
126+
/// # Examples
127+
///
128+
/// ```rust,ignore
129+
/// extern crate assert_cli;
130+
/// use assert_cli::temp::*;
131+
///
132+
/// let temp = TempDir::new("TempDirChildExt_demo").unwrap();
133+
/// temp.child("foo.txt").touch().unwrap();
134+
/// temp.close().unwrap();
135+
/// ```
136+
fn touch(&self) -> io::Result<()>;
137+
}
138+
139+
impl ChildPathTouchExt for ChildPath {
140+
fn touch(&self) -> io::Result<()> {
141+
touch(self.path())
142+
}
143+
}
144+
145+
/// Extend `ChildPath` to write binary files.
146+
pub trait ChildPathWriteBinExt {
147+
/// Write a binary file at `ChildPath`.
148+
///
149+
/// # Examples
150+
///
151+
/// ```rust,ignore
152+
/// extern crate assert_cli;
153+
/// use assert_cli::temp::*;
154+
///
155+
/// let temp = TempDir::new("TempDirChildExt_demo").unwrap();
156+
/// temp.child("foo.txt").write_binary(b"To be or not to be...").unwrap();
157+
/// temp.close().unwrap();
158+
/// ```
159+
fn write_binary(&self, data: &[u8]) -> io::Result<()>;
160+
}
161+
162+
impl ChildPathWriteBinExt for ChildPath {
163+
fn write_binary(&self, data: &[u8]) -> io::Result<()> {
164+
write_binary(self.path(), data)
165+
}
166+
}
167+
168+
/// Extend `ChildPath` to write text files.
169+
pub trait ChildPathWriteStrExt {
170+
/// Write a text file at `ChildPath`.
171+
///
172+
/// # Examples
173+
///
174+
/// ```rust,ignore
175+
/// extern crate assert_cli;
176+
/// use assert_cli::temp::*;
177+
///
178+
/// let temp = TempDir::new("TempDirChildExt_demo").unwrap();
179+
/// temp.child("foo.txt").write_str("To be or not to be...").unwrap();
180+
/// temp.close().unwrap();
181+
/// ```
182+
fn write_str(&self, data: &str) -> io::Result<()>;
183+
}
184+
185+
impl ChildPathWriteStrExt for ChildPath {
186+
fn write_str(&self, data: &str) -> io::Result<()> {
187+
write_str(self.path(), data)
188+
}
189+
}
190+
191+
/// Extend `TempDir` to copy files into it.
192+
pub trait TempDirCopyExt {
193+
/// Copy files and directories into the current path from the `source` according to the glob
194+
/// `patterns`.
195+
///
196+
/// # Examples
197+
///
198+
/// ```rust,ignore
199+
/// extern crate assert_cli;
200+
/// use assert_cli::temp::*;
201+
///
202+
/// let temp = TempDir::new("TempDirChildExt_demo").unwrap();
203+
/// temp.copy_from(".", &["*.rs"]).unwrap();
204+
/// temp.close().unwrap();
205+
/// ```
206+
fn copy_from<P, S>(&self, source: P, patterns: &[S]) -> Result<(), failure::Error>
207+
where
208+
P: AsRef<path::Path>,
209+
S: AsRef<str>;
210+
}
211+
212+
impl TempDirCopyExt for tempfile::TempDir {
213+
fn copy_from<P, S>(&self, source: P, patterns: &[S]) -> Result<(), failure::Error>
214+
where
215+
P: AsRef<path::Path>,
216+
S: AsRef<str>,
217+
{
218+
copy_from(self.path(), source.as_ref(), patterns)
219+
}
220+
}
221+
222+
impl TempDirCopyExt for ChildPath {
223+
fn copy_from<P, S>(&self, source: P, patterns: &[S]) -> Result<(), failure::Error>
224+
where
225+
P: AsRef<path::Path>,
226+
S: AsRef<str>,
227+
{
228+
copy_from(self.path(), source.as_ref(), patterns)
229+
}
230+
}
231+
232+
fn touch(path: &path::Path) -> io::Result<()> {
233+
fs::File::create(path)?;
234+
Ok(())
235+
}
236+
237+
fn write_binary(path: &path::Path, data: &[u8]) -> io::Result<()> {
238+
let mut file = fs::File::create(path)?;
239+
file.write_all(data)?;
240+
Ok(())
241+
}
242+
243+
fn write_str(path: &path::Path, data: &str) -> io::Result<()> {
244+
write_binary(path, data.as_bytes())
245+
}
246+
247+
fn copy_from<S>(
248+
target: &path::Path,
249+
source: &path::Path,
250+
patterns: &[S],
251+
) -> Result<(), failure::Error>
252+
where
253+
S: AsRef<str>,
254+
{
255+
for entry in globwalk::GlobWalker::from_patterns(patterns, source)?.follow_links(true) {
256+
let entry = entry?;
257+
let rel = entry
258+
.path()
259+
.strip_prefix(source)
260+
.expect("entries to be under `source`");
261+
let target_path = target.join(rel);
262+
if entry.file_type().is_dir() {
263+
fs::create_dir_all(target_path)?;
264+
} else if entry.file_type().is_file() {
265+
fs::copy(entry.path(), target)?;
266+
}
267+
}
268+
Ok(())
269+
}

0 commit comments

Comments
 (0)