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

Add with_env helper #46

Merged
merged 1 commit into from
Oct 12, 2017
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ matrix:
env: CLIPPY_VERS="0.0.165"
before_script: |
[[ "$(cargo +nightly-2017-10-09 clippy --version)" != "$CLIPPY_VERS" ]] && \
cargo +nightly-2017-10-09 install clippy --vers "$CLIPPY_VERS" --force
cargo +nightly-2017-10-09 install clippy --vers "$CLIPPY_VERS" --force || true
script: |
cargo +nightly-2017-10-09 clippy -- -D warnings
- rust: nightly-2017-10-09
env: RUSTFMT_VERS="0.2.8"
before_script: |
[[ "$(cargo +nightly-2017-10-09 fmt -- --version)" != "$RUSTFMT_VERS"* ]] && \
cargo +nightly-2017-10-09 install rustfmt-nightly --vers "$RUSTFMT_VERS" --force
cargo +nightly-2017-10-09 install rustfmt-nightly --vers "$RUSTFMT_VERS" --force || true
script: |
cargo +nightly-2017-10-09 fmt --all -- --write-mode=diff
before_script:
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ colored = "1.5"
difference = "1.0"
error-chain = "0.11"
serde_json = "1.0"
environment = "0.1"

[build-dependencies]
skeptic = "0.13"
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ like this:
if there are special characters, which the macro doesn't accept, e.g.
`assert_cmd!(cat "foo.txt")`.

Assert Cli use [Environment][Environment] underneath to deal with environment variables.

More detailed information is available in the [documentation]. :-)

## License
Expand All @@ -95,3 +97,4 @@ license, shall be dual licensed as above, without any additional terms or
conditions.

[Documentation]: https://docs.rs/assert_cli
[Environment]: https://github.com/Freyskeyd/environment
212 changes: 210 additions & 2 deletions src/assert.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use environment::Environment;
use errors::*;
use output::{OutputAssertion, OutputKind};
use std::default;
Expand All @@ -10,6 +11,7 @@ use std::vec::Vec;
#[derive(Debug)]
pub struct Assert {
cmd: Vec<String>,
env: Environment,
current_dir: Option<PathBuf>,
expect_success: Option<bool>,
expect_exit_code: Option<i32>,
Expand All @@ -27,6 +29,7 @@ impl default::Default for Assert {
.into_iter()
.map(String::from)
.collect(),
env: Environment::inherit(),
current_dir: None,
expect_success: Some(true),
expect_exit_code: None,
Expand Down Expand Up @@ -87,6 +90,7 @@ impl Assert {
/// .with_args(&["42"])
/// .stdout().contains("42")
/// .unwrap();
///
/// ```
pub fn with_args(mut self, args: &[&str]) -> Self {
self.cmd.extend(args.into_iter().cloned().map(String::from));
Expand Down Expand Up @@ -128,6 +132,41 @@ impl Assert {
self
}

/// Sets environments variables for the command.
///
/// # Examples
///
/// ```rust
/// extern crate assert_cli;
///
/// assert_cli::Assert::command(&["printenv"])
/// .with_env(&[("TEST_ENV", "OK")])
/// .stdout().contains("TEST_ENV=OK")
/// .execute()
/// .unwrap();
///
/// let env = assert_cli::Environment::empty()
/// .insert("FOO", "BAR");
///
/// assert_cli::Assert::command(&["printenv"])
/// .with_env(&env)
/// .stdout().is("FOO=BAR")
/// .execute()
/// .unwrap();
///
/// ::std::env::set_var("BAZ", "BAR");
///
/// assert_cli::Assert::command(&["printenv"])
/// .stdout().contains("BAZ=BAR")
/// .execute()
/// .unwrap();
/// ```
pub fn with_env<E: Into<Environment>>(mut self, env: E) -> Self {
self.env = env.into();

self
}

/// Small helper to make chains more readable.
///
/// # Examples
Expand Down Expand Up @@ -253,13 +292,17 @@ impl Assert {
/// ```
pub fn execute(self) -> Result<()> {
let cmd = &self.cmd[0];

let args: Vec<_> = self.cmd.iter().skip(1).collect();
let mut command = Command::new(cmd);
let command = command
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
let command = command.args(&args);
.stderr(Stdio::piped())
.env_clear()
Copy link
Collaborator

Choose a reason for hiding this comment

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

There a reason we unconditionally clear and then re-add the vars we cleared via compile?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In fact we need the env_clear() because .envs doesn't clear it or replace the whole variables.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think I'm missing something. I had assumed we'd only call env_clear() when inherit == false. Its unclear to me why we need env_clear when inherit == true.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

By clearing the environment before adding our own environment, we don't have to check if inherit is true nor false in execute command. We just want to clear and add the appropriate environment. That's the environment role to know when to add the existing env or not.

If inherit is set to true, the compile method will return a merge of actual environment (env::vars()) and our customized variables. I think it's nicer to have a compile method returning what our environment will look like. But feel free to propose something else I'm ok with it :)

Copy link
Collaborator

Choose a reason for hiding this comment

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

tl;dr move along, these aren't the droids you're looking for.

I think it's nicer to have a compile method returning what our environment will look like.

That is nice. It helps with testing, etc.

I just assumed that Assert would directly access the members of Environment and there wouldn't be a need for a public .compile().

Recreating an inherited environment is more work but I'm assuming it doesn't matter in this case, despite it being against "you pay for what you use" (adds cost to people not using these new features).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@killercup how do you see this test?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Set an env var to something that is not UTF-8. How would I get a byte slice like that? head -c 42 /dev/urandom | rcat --quote (with this rcat) gives me:

b"\xf4\xa9\xd5\x8d\xc0\xe8\x00\xff\x1dqz\x86k\x83\xcb~\xc1\x18!\xfa\x01J\x1c\\:\x18)%\xd4\xa6^7\xb4\x93\x80.\x9a\x9c\xe1\xcf\xe2,"

which is not valid UTF-8.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, but vars() use UTF-8 variables, do we really want to support not valid UTF-8? I don't think..

Copy link
Collaborator

Choose a reason for hiding this comment

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

That's exactly what I'm saying: I'm fine with only allowing our users to add UTF-8 vars but we should not destroy valid, inherited env vars. So, calling vars at all is probably not a good call, and vars_os is the better option. Btw, you can find the impl of how std::process::Command deals with env vars on Unix here (it uses a HashMap<OsString, (usize, CString)> to store env vars).

How much work would it be to allow OsStr instead of String in our APIs?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you still add a test with the non-UTF-8 env var value?

.envs(self.env.clone().compile())
.args(&args);

let command = match self.current_dir {
Some(ref dir) => command.current_dir(dir),
None => command,
Expand Down Expand Up @@ -426,3 +469,168 @@ impl OutputAssertionBuilder {
self.not().is(output)
}
}

#[cfg(test)]
mod test {
use super::*;
use std::ffi::OsString;

fn command() -> Assert {
Assert::command(&["printenv"])
}

#[test]
fn take_ownership() {
let x = Environment::inherit();

command().with_env(x.clone()).with_env(&x).with_env(x);
}

#[test]
fn in_place_mod() {
let y = Environment::empty();

let y = y.insert("key", "value");

assert_eq!(
y.compile(),
vec![(OsString::from("key"), OsString::from("value"))]
);
}

#[test]
fn in_place_mod2() {
let x = Environment::inherit();

command()
.with_env(&x.insert("key", "value").insert("key", "vv"))
.stdout()
.contains("key=vv")
.execute()
.unwrap();
// Granted, `insert` moved `x`, so we can no longer reference it, even
// though only a reference was passed to `with_env`
}

#[test]
fn in_place_mod3() {
// In-place modification while allowing later accesses to the `Environment`
let y = Environment::empty();

assert_eq!(
y.clone().insert("key", "value").compile(),
vec![(OsString::from("key"), OsString::from("value"))]
);

command()
.with_env(y)
.stdout()
.not()
.contains("key=value")
.execute()
.unwrap();
}

#[test]
fn empty_env() {
// In-place modification while allowing later accesses to the `Environment`
let y = Environment::empty();

assert!(command().with_env(y).stdout().is("").execute().is_ok());
}
#[test]
fn take_vec() {
let v = vec![("bar".to_string(), "baz".to_string())];

command()
.with_env(&vec![("bar", "baz")])
.stdout()
.contains("bar=baz")
.execute()
.unwrap();

command()
.with_env(&v)
.stdout()
.contains("bar=baz")
.execute()
.unwrap();

command()
.with_env(&vec![("bar", "baz")])
.stdout()
.isnt("")
.execute()
.unwrap();
}

#[test]
fn take_slice_of_strs() {
command()
.with_env(&[("bar", "BAZ")])
.stdout()
.contains("bar=BAZ")
.execute()
.unwrap();

command()
.with_env(&[("bar", "BAZ")][..])
.stdout()
.contains("bar=BAZ")
.execute()
.unwrap();

command()
.with_env([("bar", "BAZ")].as_ref())
.stdout()
.contains("bar=BAZ")
.execute()
.unwrap();
}

#[test]
fn take_slice_of_strings() {
// same deal as above

command()
.with_env(&[("bar".to_string(), "BAZ".to_string())])
.stdout()
.contains("bar=BAZ")
.execute()
.unwrap();

command()
.with_env(&[("bar".to_string(), "BAZ".to_string())][..])
.stdout()
.contains("bar=BAZ")
.execute()
.unwrap();
}

#[test]
fn take_slice() {
command()
.with_env(&[("hey", "ho")])
.stdout()
.contains("hey=ho")
.execute()
.unwrap();

command()
.with_env(&[("hey", "ho".to_string())])
.stdout()
.contains("hey=ho")
.execute()
.unwrap();
}

#[test]
fn take_string_i32() {
command()
.with_env(&[("bar", 3 as i32)])
.stdout()
.contains("bar=3")
.execute()
.unwrap();
}
}
5 changes: 5 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
#![deny(missing_docs)]

extern crate difference;
extern crate environment;
#[macro_use]
extern crate error_chain;
extern crate serde_json;
Expand All @@ -135,3 +136,7 @@ mod diff;
mod assert;
pub use assert::Assert;
pub use assert::OutputAssertionBuilder;
/// Environment is a re-export of the Environment crate
///
/// It allow you to define/override environment variables for one or more assertions.
pub use environment::Environment;