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

Commit a211010

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. 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).
1 parent 58f445a commit a211010

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 = ["temp_dir"]
20+
temp_dir = ["tempdir", "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+
tempdir = { version="0.3", 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 tempdir;
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+
/// `tempdir::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 tempdir;
10+
use failure;
11+
12+
// Quick and dirty for doc tests; not meant for long term use.
13+
pub use tempdir::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 tempdir::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 tempdir::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 tempdir::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)