mirror of
https://github.com/nushell/nushell.git
synced 2025-06-30 06:30:08 +02:00
Add initial nu-test-support port (#913)
* Add initial nu-test-support port * finish changing binary name * Oops, these aren't Windows-safe tests
This commit is contained in:
@ -15,6 +15,7 @@ nu-pretty-hex = { path = "../nu-pretty-hex" }
|
||||
nu-protocol = { path = "../nu-protocol" }
|
||||
nu-table = { path = "../nu-table" }
|
||||
nu-term-grid = { path = "../nu-term-grid" }
|
||||
nu-test-support = { path = "../nu-test-support" }
|
||||
nu-parser = { path = "../nu-parser" }
|
||||
nu-system = { path = "../nu-system" }
|
||||
# nu-ansi-term = { path = "../nu-ansi-term" }
|
||||
@ -95,3 +96,9 @@ dataframe = ["polars", "num"]
|
||||
|
||||
[build-dependencies]
|
||||
shadow-rs = "0.8.1"
|
||||
|
||||
[dev-dependencies]
|
||||
hamcrest2 = "0.3.0"
|
||||
dirs-next = "2.0.0"
|
||||
quickcheck = "1.0.3"
|
||||
quickcheck_macros = "1.0.0"
|
@ -1028,13 +1028,25 @@ fn compute(size: i64, unit: Unit, span: Span) -> Value {
|
||||
val: size * 1000 * 1000 * 1000 * 60 * 60,
|
||||
span,
|
||||
},
|
||||
Unit::Day => Value::Duration {
|
||||
val: size * 1000 * 1000 * 1000 * 60 * 60 * 24,
|
||||
span,
|
||||
Unit::Day => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24) {
|
||||
Some(val) => Value::Duration { val, span },
|
||||
None => Value::Error {
|
||||
error: ShellError::SpannedLabeledError(
|
||||
"duration too large".into(),
|
||||
"duration too large".into(),
|
||||
span,
|
||||
),
|
||||
},
|
||||
},
|
||||
Unit::Week => Value::Duration {
|
||||
val: size * 1000 * 1000 * 1000 * 60 * 60 * 24 * 7,
|
||||
span,
|
||||
Unit::Week => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24 * 7) {
|
||||
Some(val) => Value::Duration { val, span },
|
||||
None => Value::Error {
|
||||
error: ShellError::SpannedLabeledError(
|
||||
"duration too large".into(),
|
||||
"duration too large".into(),
|
||||
span,
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -88,7 +88,34 @@ pub fn math_result_type(
|
||||
)
|
||||
}
|
||||
},
|
||||
Operator::Multiply | Operator::Pow => match (&lhs.ty, &rhs.ty) {
|
||||
Operator::Multiply => match (&lhs.ty, &rhs.ty) {
|
||||
(Type::Int, Type::Int) => (Type::Int, None),
|
||||
(Type::Float, Type::Int) => (Type::Float, None),
|
||||
(Type::Int, Type::Float) => (Type::Float, None),
|
||||
(Type::Float, Type::Float) => (Type::Float, None),
|
||||
|
||||
(Type::Filesize, Type::Int) => (Type::Filesize, None),
|
||||
(Type::Int, Type::Filesize) => (Type::Filesize, None),
|
||||
(Type::Duration, Type::Int) => (Type::Filesize, None),
|
||||
(Type::Int, Type::Duration) => (Type::Filesize, None),
|
||||
|
||||
(Type::Unknown, _) => (Type::Unknown, None),
|
||||
(_, Type::Unknown) => (Type::Unknown, None),
|
||||
_ => {
|
||||
*op = Expression::garbage(op.span);
|
||||
(
|
||||
Type::Unknown,
|
||||
Some(ParseError::UnsupportedOperation(
|
||||
op.span,
|
||||
lhs.span,
|
||||
lhs.ty.clone(),
|
||||
rhs.span,
|
||||
rhs.ty.clone(),
|
||||
)),
|
||||
)
|
||||
}
|
||||
},
|
||||
Operator::Pow => match (&lhs.ty, &rhs.ty) {
|
||||
(Type::Int, Type::Int) => (Type::Int, None),
|
||||
(Type::Float, Type::Int) => (Type::Float, None),
|
||||
(Type::Int, Type::Float) => (Type::Float, None),
|
||||
@ -118,6 +145,9 @@ pub fn math_result_type(
|
||||
(Type::Filesize, Type::Filesize) => (Type::Float, None),
|
||||
(Type::Duration, Type::Duration) => (Type::Float, None),
|
||||
|
||||
(Type::Filesize, Type::Int) => (Type::Filesize, None),
|
||||
(Type::Duration, Type::Int) => (Type::Duration, None),
|
||||
|
||||
(Type::Unknown, _) => (Type::Unknown, None),
|
||||
(_, Type::Unknown) => (Type::Unknown, None),
|
||||
_ => {
|
||||
|
@ -7,9 +7,7 @@ use super::{EngineState, Stack};
|
||||
pub trait Command: Send + Sync + CommandClone {
|
||||
fn name(&self) -> &str;
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::new(self.name()).desc(self.usage()).filter()
|
||||
}
|
||||
fn signature(&self) -> Signature;
|
||||
|
||||
fn usage(&self) -> &str;
|
||||
|
||||
|
@ -1122,6 +1122,30 @@ impl Value {
|
||||
val: lhs * rhs,
|
||||
span,
|
||||
}),
|
||||
(Value::Int { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => {
|
||||
Ok(Value::Filesize {
|
||||
val: *lhs * *rhs,
|
||||
span,
|
||||
})
|
||||
}
|
||||
(Value::Filesize { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
||||
Ok(Value::Filesize {
|
||||
val: *lhs * *rhs,
|
||||
span,
|
||||
})
|
||||
}
|
||||
(Value::Int { val: lhs, .. }, Value::Duration { val: rhs, .. }) => {
|
||||
Ok(Value::Duration {
|
||||
val: *lhs * *rhs,
|
||||
span,
|
||||
})
|
||||
}
|
||||
(Value::Duration { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
||||
Ok(Value::Duration {
|
||||
val: *lhs * *rhs,
|
||||
span,
|
||||
})
|
||||
}
|
||||
(Value::CustomValue { val: lhs, span }, rhs) => {
|
||||
lhs.operation(*span, Operator::Multiply, op, rhs)
|
||||
}
|
||||
@ -1220,6 +1244,26 @@ impl Value {
|
||||
Err(ShellError::DivisionByZero(op))
|
||||
}
|
||||
}
|
||||
(Value::Filesize { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
||||
if *rhs != 0 {
|
||||
Ok(Value::Filesize {
|
||||
val: lhs / rhs,
|
||||
span,
|
||||
})
|
||||
} else {
|
||||
Err(ShellError::DivisionByZero(op))
|
||||
}
|
||||
}
|
||||
(Value::Duration { val: lhs, .. }, Value::Int { val: rhs, .. }) => {
|
||||
if *rhs != 0 {
|
||||
Ok(Value::Duration {
|
||||
val: lhs / rhs,
|
||||
span,
|
||||
})
|
||||
} else {
|
||||
Err(ShellError::DivisionByZero(op))
|
||||
}
|
||||
}
|
||||
(Value::CustomValue { val: lhs, span }, rhs) => {
|
||||
lhs.operation(*span, Operator::Divide, op, rhs)
|
||||
}
|
||||
|
23
crates/nu-test-support/Cargo.toml
Normal file
23
crates/nu-test-support/Cargo.toml
Normal file
@ -0,0 +1,23 @@
|
||||
[package]
|
||||
authors = ["The Nu Project Contributors"]
|
||||
description = "Support for writing Nushell tests"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu-test-support"
|
||||
version = "0.43.0"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
nu-path = { path="../nu-path" }
|
||||
nu-protocol = { path="../nu-protocol" }
|
||||
|
||||
bigdecimal = { package = "bigdecimal", version = "0.3.0", features = ["serde"] }
|
||||
chrono = "0.4.19"
|
||||
getset = "0.1.1"
|
||||
glob = "0.3.0"
|
||||
indexmap = { version="1.6.1", features=["serde-1"] }
|
||||
num-bigint = { version="0.4.3", features=["serde"] }
|
||||
tempfile = "3.2.0"
|
||||
hamcrest2 = "0.3.0"
|
53
crates/nu-test-support/src/commands.rs
Normal file
53
crates/nu-test-support/src/commands.rs
Normal file
@ -0,0 +1,53 @@
|
||||
// use nu_protocol::{
|
||||
// ast::{Expr, Expression},
|
||||
// Span, Spanned, Type,
|
||||
// };
|
||||
|
||||
// pub struct ExternalBuilder {
|
||||
// name: String,
|
||||
// args: Vec<String>,
|
||||
// }
|
||||
|
||||
// impl ExternalBuilder {
|
||||
// pub fn for_name(name: &str) -> ExternalBuilder {
|
||||
// ExternalBuilder {
|
||||
// name: name.to_string(),
|
||||
// args: vec![],
|
||||
// }
|
||||
// }
|
||||
|
||||
// pub fn arg(&mut self, value: &str) -> &mut Self {
|
||||
// self.args.push(value.to_string());
|
||||
// self
|
||||
// }
|
||||
|
||||
// pub fn build(&mut self) -> ExternalCommand {
|
||||
// let mut path = crate::fs::binaries();
|
||||
// path.push(&self.name);
|
||||
|
||||
// let name = Spanned {
|
||||
// item: path.to_string_lossy().to_string(),
|
||||
// span: Span::new(0, 0),
|
||||
// };
|
||||
|
||||
// let args = self
|
||||
// .args
|
||||
// .iter()
|
||||
// .map(|arg| Expression {
|
||||
// expr: Expr::String(arg.to_string()),
|
||||
// span: Span::new(0, 0),
|
||||
// ty: Type::Unknown,
|
||||
// custom_completion: None,
|
||||
// })
|
||||
// .collect::<Vec<_>>();
|
||||
|
||||
// ExternalCommand {
|
||||
// name: name.to_string(),
|
||||
// name_tag: Tag::unknown(),
|
||||
// args: ExternalArgs {
|
||||
// list: args,
|
||||
// span: name.span,
|
||||
// },
|
||||
// }
|
||||
// }
|
||||
// }
|
277
crates/nu-test-support/src/fs.rs
Normal file
277
crates/nu-test-support/src/fs.rs
Normal file
@ -0,0 +1,277 @@
|
||||
use std::io::Read;
|
||||
use std::ops::Div;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub struct AbsoluteFile {
|
||||
inner: PathBuf,
|
||||
}
|
||||
|
||||
impl AbsoluteFile {
|
||||
pub fn new(path: impl AsRef<Path>) -> AbsoluteFile {
|
||||
let path = path.as_ref();
|
||||
|
||||
if !path.is_absolute() {
|
||||
panic!(
|
||||
"AbsoluteFile::new must take an absolute path :: {}",
|
||||
path.display()
|
||||
)
|
||||
} else if path.is_dir() {
|
||||
// At the moment, this is not an invariant, but rather a way to catch bugs
|
||||
// in tests.
|
||||
panic!(
|
||||
"AbsoluteFile::new must not take a directory :: {}",
|
||||
path.display()
|
||||
)
|
||||
} else {
|
||||
AbsoluteFile {
|
||||
inner: path.to_path_buf(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dir(&self) -> AbsolutePath {
|
||||
AbsolutePath::new(if let Some(parent) = self.inner.parent() {
|
||||
parent
|
||||
} else {
|
||||
unreachable!("Internal error: could not get parent in dir")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AbsoluteFile> for PathBuf {
|
||||
fn from(file: AbsoluteFile) -> Self {
|
||||
file.inner
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AbsolutePath {
|
||||
pub inner: PathBuf,
|
||||
}
|
||||
|
||||
impl AbsolutePath {
|
||||
pub fn new(path: impl AsRef<Path>) -> AbsolutePath {
|
||||
let path = path.as_ref();
|
||||
|
||||
if path.is_absolute() {
|
||||
AbsolutePath {
|
||||
inner: path.to_path_buf(),
|
||||
}
|
||||
} else {
|
||||
panic!("AbsolutePath::new must take an absolute path")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<&str> for &AbsolutePath {
|
||||
type Output = AbsolutePath;
|
||||
|
||||
fn div(self, rhs: &str) -> Self::Output {
|
||||
let parts = rhs.split('/');
|
||||
let mut result = self.inner.clone();
|
||||
|
||||
for part in parts {
|
||||
result = result.join(part);
|
||||
}
|
||||
|
||||
AbsolutePath::new(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Path> for AbsolutePath {
|
||||
fn as_ref(&self) -> &Path {
|
||||
self.inner.as_path()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RelativePath {
|
||||
inner: PathBuf,
|
||||
}
|
||||
|
||||
impl RelativePath {
|
||||
pub fn new(path: impl Into<PathBuf>) -> RelativePath {
|
||||
let path = path.into();
|
||||
|
||||
if path.is_relative() {
|
||||
RelativePath { inner: path }
|
||||
} else {
|
||||
panic!("RelativePath::new must take a relative path")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<str>> Div<T> for &RelativePath {
|
||||
type Output = RelativePath;
|
||||
|
||||
fn div(self, rhs: T) -> Self::Output {
|
||||
let parts = rhs.as_ref().split('/');
|
||||
let mut result = self.inner.clone();
|
||||
|
||||
for part in parts {
|
||||
result = result.join(part);
|
||||
}
|
||||
|
||||
RelativePath::new(result)
|
||||
}
|
||||
}
|
||||
pub trait DisplayPath {
|
||||
fn display_path(&self) -> String;
|
||||
}
|
||||
|
||||
impl DisplayPath for AbsolutePath {
|
||||
fn display_path(&self) -> String {
|
||||
self.inner.display().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl DisplayPath for PathBuf {
|
||||
fn display_path(&self) -> String {
|
||||
self.display().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl DisplayPath for str {
|
||||
fn display_path(&self) -> String {
|
||||
self.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl DisplayPath for &str {
|
||||
fn display_path(&self) -> String {
|
||||
(*self).to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl DisplayPath for String {
|
||||
fn display_path(&self) -> String {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl DisplayPath for &String {
|
||||
fn display_path(&self) -> String {
|
||||
(*self).to_string()
|
||||
}
|
||||
}
|
||||
pub enum Stub<'a> {
|
||||
FileWithContent(&'a str, &'a str),
|
||||
FileWithContentToBeTrimmed(&'a str, &'a str),
|
||||
EmptyFile(&'a str),
|
||||
}
|
||||
|
||||
pub fn file_contents(full_path: impl AsRef<Path>) -> String {
|
||||
let mut file = std::fs::File::open(full_path.as_ref()).expect("can not open file");
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents)
|
||||
.expect("can not read file");
|
||||
contents
|
||||
}
|
||||
|
||||
pub fn file_contents_binary(full_path: impl AsRef<Path>) -> Vec<u8> {
|
||||
let mut file = std::fs::File::open(full_path.as_ref()).expect("can not open file");
|
||||
let mut contents = Vec::new();
|
||||
file.read_to_end(&mut contents).expect("can not read file");
|
||||
contents
|
||||
}
|
||||
|
||||
pub fn line_ending() -> String {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
String::from("\r\n")
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
String::from("\n")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete_file_at(full_path: impl AsRef<Path>) {
|
||||
let full_path = full_path.as_ref();
|
||||
|
||||
if full_path.exists() {
|
||||
std::fs::remove_file(full_path).expect("can not delete file");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_file_at(full_path: impl AsRef<Path>) -> Result<(), std::io::Error> {
|
||||
let full_path = full_path.as_ref();
|
||||
|
||||
if full_path.parent().is_some() {
|
||||
panic!("path exists");
|
||||
}
|
||||
|
||||
std::fs::write(full_path, b"fake data")
|
||||
}
|
||||
|
||||
pub fn copy_file_to(source: &str, destination: &str) {
|
||||
std::fs::copy(source, destination).expect("can not copy file");
|
||||
}
|
||||
|
||||
pub fn files_exist_at(files: Vec<impl AsRef<Path>>, path: impl AsRef<Path>) -> bool {
|
||||
files.iter().all(|f| {
|
||||
let mut loc = PathBuf::from(path.as_ref());
|
||||
loc.push(f);
|
||||
loc.exists()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn delete_directory_at(full_path: &str) {
|
||||
std::fs::remove_dir_all(PathBuf::from(full_path)).expect("can not remove directory");
|
||||
}
|
||||
|
||||
pub fn executable_path() -> PathBuf {
|
||||
let mut path = binaries();
|
||||
path.push("nu");
|
||||
path
|
||||
}
|
||||
|
||||
pub fn root() -> PathBuf {
|
||||
let manifest_dir = if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") {
|
||||
PathBuf::from(manifest_dir)
|
||||
} else {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
};
|
||||
|
||||
let test_path = manifest_dir.join("Cargo.lock");
|
||||
if test_path.exists() {
|
||||
manifest_dir
|
||||
} else {
|
||||
manifest_dir
|
||||
.parent()
|
||||
.expect("Couldn't find the debug binaries directory")
|
||||
.parent()
|
||||
.expect("Couldn't find the debug binaries directory")
|
||||
.to_path_buf()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn binaries() -> PathBuf {
|
||||
let mut build_type = "debug";
|
||||
if !cfg!(debug_assertions) {
|
||||
build_type = "release"
|
||||
}
|
||||
|
||||
std::env::var("CARGO_TARGET_DIR")
|
||||
.ok()
|
||||
.map(|target_dir| PathBuf::from(target_dir).join(&build_type))
|
||||
.unwrap_or_else(|| root().join(format!("target/{}", &build_type)))
|
||||
}
|
||||
|
||||
pub fn fixtures() -> PathBuf {
|
||||
root().join("tests/fixtures")
|
||||
}
|
||||
|
||||
pub fn assets() -> PathBuf {
|
||||
root().join("tests/assets")
|
||||
}
|
||||
|
||||
pub fn in_directory(str: impl AsRef<Path>) -> String {
|
||||
let path = str.as_ref();
|
||||
let path = if path.is_relative() {
|
||||
root().join(path)
|
||||
} else {
|
||||
path.to_path_buf()
|
||||
};
|
||||
|
||||
path.display().to_string()
|
||||
}
|
70
crates/nu-test-support/src/lib.rs
Normal file
70
crates/nu-test-support/src/lib.rs
Normal file
@ -0,0 +1,70 @@
|
||||
pub mod commands;
|
||||
pub mod fs;
|
||||
pub mod macros;
|
||||
pub mod playground;
|
||||
|
||||
pub struct Outcome {
|
||||
pub out: String,
|
||||
pub err: String,
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub const NATIVE_PATH_ENV_VAR: &str = "Path";
|
||||
#[cfg(not(windows))]
|
||||
pub const NATIVE_PATH_ENV_VAR: &str = "PATH";
|
||||
|
||||
#[cfg(windows)]
|
||||
pub const NATIVE_PATH_ENV_SEPARATOR: char = ';';
|
||||
#[cfg(not(windows))]
|
||||
pub const NATIVE_PATH_ENV_SEPARATOR: char = ':';
|
||||
|
||||
impl Outcome {
|
||||
pub fn new(out: String, err: String) -> Outcome {
|
||||
Outcome { out, err }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pipeline(commands: &str) -> String {
|
||||
commands
|
||||
.lines()
|
||||
.skip(1)
|
||||
.map(|line| line.trim())
|
||||
.collect::<Vec<&str>>()
|
||||
.join(" ")
|
||||
.trim_end()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
pub fn shell_os_paths() -> Vec<std::path::PathBuf> {
|
||||
let mut original_paths = vec![];
|
||||
|
||||
if let Some(paths) = std::env::var_os(NATIVE_PATH_ENV_VAR) {
|
||||
original_paths = std::env::split_paths(&paths).collect::<Vec<_>>();
|
||||
}
|
||||
|
||||
original_paths
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::pipeline;
|
||||
|
||||
#[test]
|
||||
fn constructs_a_pipeline() {
|
||||
let actual = pipeline(
|
||||
r#"
|
||||
open los_tres_amigos.txt
|
||||
| from-csv
|
||||
| get rusty_luck
|
||||
| str to-int
|
||||
| math sum
|
||||
| echo "$it"
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
actual,
|
||||
r#"open los_tres_amigos.txt | from-csv | get rusty_luck | str to-int | math sum | echo "$it""#
|
||||
);
|
||||
}
|
||||
}
|
170
crates/nu-test-support/src/macros.rs
Normal file
170
crates/nu-test-support/src/macros.rs
Normal file
@ -0,0 +1,170 @@
|
||||
#[macro_export]
|
||||
macro_rules! nu {
|
||||
(cwd: $cwd:expr, $path:expr, $($part:expr),*) => {{
|
||||
use $crate::fs::DisplayPath;
|
||||
|
||||
let path = format!($path, $(
|
||||
$part.display_path()
|
||||
),*);
|
||||
|
||||
nu!($cwd, &path)
|
||||
}};
|
||||
|
||||
(cwd: $cwd:expr, $path:expr) => {{
|
||||
nu!($cwd, $path)
|
||||
}};
|
||||
|
||||
($cwd:expr, $path:expr) => {{
|
||||
pub use itertools::Itertools;
|
||||
pub use std::error::Error;
|
||||
pub use std::io::prelude::*;
|
||||
pub use std::process::{Command, Stdio};
|
||||
pub use $crate::NATIVE_PATH_ENV_VAR;
|
||||
|
||||
// let commands = &*format!(
|
||||
// "
|
||||
// cd \"{}\"
|
||||
// {}
|
||||
// exit",
|
||||
// $crate::fs::in_directory($cwd),
|
||||
// $crate::fs::DisplayPath::display_path(&$path)
|
||||
// );
|
||||
|
||||
let test_bins = $crate::fs::binaries();
|
||||
|
||||
let cwd = std::env::current_dir().expect("Could not get current working directory.");
|
||||
let test_bins = nu_path::canonicalize_with(&test_bins, cwd).unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"Couldn't canonicalize dummy binaries path {}: {:?}",
|
||||
test_bins.display(),
|
||||
e
|
||||
)
|
||||
});
|
||||
|
||||
let mut paths = $crate::shell_os_paths();
|
||||
paths.insert(0, test_bins);
|
||||
|
||||
let path = $path.lines().collect::<Vec<_>>().join("; ");
|
||||
|
||||
let paths_joined = match std::env::join_paths(paths) {
|
||||
Ok(all) => all,
|
||||
Err(_) => panic!("Couldn't join paths for PATH var."),
|
||||
};
|
||||
|
||||
let mut process = match Command::new($crate::fs::executable_path())
|
||||
.env(NATIVE_PATH_ENV_VAR, paths_joined)
|
||||
// .arg("--skip-plugins")
|
||||
// .arg("--no-history")
|
||||
// .arg("--config-file")
|
||||
// .arg($crate::fs::DisplayPath::display_path(&$crate::fs::fixtures().join("playground/config/default.toml")))
|
||||
.arg(format!("-c 'cd {}; {}'", $crate::fs::in_directory($cwd), $crate::fs::DisplayPath::display_path(&path)))
|
||||
.stdout(Stdio::piped())
|
||||
// .stdin(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
{
|
||||
Ok(child) => child,
|
||||
Err(why) => panic!("Can't run test {:?} {}", $crate::fs::executable_path(), why.to_string()),
|
||||
};
|
||||
|
||||
// let stdin = process.stdin.as_mut().expect("couldn't open stdin");
|
||||
// stdin
|
||||
// .write_all(b"exit\n")
|
||||
// .expect("couldn't write to stdin");
|
||||
|
||||
let output = process
|
||||
.wait_with_output()
|
||||
.expect("couldn't read from stdout/stderr");
|
||||
|
||||
let out = $crate::macros::read_std(&output.stdout);
|
||||
let err = String::from_utf8_lossy(&output.stderr);
|
||||
|
||||
println!("=== stderr\n{}", err);
|
||||
|
||||
$crate::Outcome::new(out,err.into_owned())
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! nu_with_plugins {
|
||||
(cwd: $cwd:expr, $path:expr, $($part:expr),*) => {{
|
||||
use $crate::fs::DisplayPath;
|
||||
|
||||
let path = format!($path, $(
|
||||
$part.display_path()
|
||||
),*);
|
||||
|
||||
nu_with_plugins!($cwd, &path)
|
||||
}};
|
||||
|
||||
(cwd: $cwd:expr, $path:expr) => {{
|
||||
nu_with_plugins!($cwd, $path)
|
||||
}};
|
||||
|
||||
($cwd:expr, $path:expr) => {{
|
||||
pub use std::error::Error;
|
||||
pub use std::io::prelude::*;
|
||||
pub use std::process::{Command, Stdio};
|
||||
pub use crate::NATIVE_PATH_ENV_VAR;
|
||||
|
||||
let commands = &*format!(
|
||||
"
|
||||
cd \"{}\"
|
||||
{}
|
||||
exit",
|
||||
$crate::fs::in_directory($cwd),
|
||||
$crate::fs::DisplayPath::display_path(&$path)
|
||||
);
|
||||
|
||||
let test_bins = $crate::fs::binaries();
|
||||
let test_bins = nu_path::canonicalize(&test_bins).unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"Couldn't canonicalize dummy binaries path {}: {:?}",
|
||||
test_bins.display(),
|
||||
e
|
||||
)
|
||||
});
|
||||
|
||||
let mut paths = $crate::shell_os_paths();
|
||||
paths.insert(0, test_bins);
|
||||
|
||||
let paths_joined = match std::env::join_paths(paths) {
|
||||
Ok(all) => all,
|
||||
Err(_) => panic!("Couldn't join paths for PATH var."),
|
||||
};
|
||||
|
||||
let mut process = match Command::new($crate::fs::executable_path())
|
||||
.env(NATIVE_PATH_ENV_VAR, paths_joined)
|
||||
.stdout(Stdio::piped())
|
||||
.stdin(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
{
|
||||
Ok(child) => child,
|
||||
Err(why) => panic!("Can't run test {}", why.to_string()),
|
||||
};
|
||||
|
||||
let stdin = process.stdin.as_mut().expect("couldn't open stdin");
|
||||
stdin
|
||||
.write_all(commands.as_bytes())
|
||||
.expect("couldn't write to stdin");
|
||||
|
||||
let output = process
|
||||
.wait_with_output()
|
||||
.expect("couldn't read from stdout/stderr");
|
||||
|
||||
let out = $crate::macros::read_std(&output.stdout);
|
||||
let err = String::from_utf8_lossy(&output.stderr);
|
||||
|
||||
println!("=== stderr\n{}", err);
|
||||
|
||||
$crate::Outcome::new(out,err.into_owned())
|
||||
}};
|
||||
}
|
||||
|
||||
pub fn read_std(std: &[u8]) -> String {
|
||||
let out = String::from_utf8_lossy(std);
|
||||
let out = out.lines().collect::<Vec<_>>().join("\n");
|
||||
let out = out.replace("\r\n", "");
|
||||
out.replace("\n", "")
|
||||
}
|
12
crates/nu-test-support/src/playground.rs
Normal file
12
crates/nu-test-support/src/playground.rs
Normal file
@ -0,0 +1,12 @@
|
||||
mod director;
|
||||
pub mod matchers;
|
||||
pub mod nu_process;
|
||||
mod play;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub use director::Director;
|
||||
pub use matchers::says;
|
||||
pub use nu_process::{Executable, NuProcess, NuResult, Outcome};
|
||||
pub use play::{Dirs, EnvironmentVariable, Playground};
|
163
crates/nu-test-support/src/playground/director.rs
Normal file
163
crates/nu-test-support/src/playground/director.rs
Normal file
@ -0,0 +1,163 @@
|
||||
use super::nu_process::*;
|
||||
use super::EnvironmentVariable;
|
||||
use std::ffi::OsString;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Director {
|
||||
pub cwd: Option<OsString>,
|
||||
pub environment_vars: Vec<EnvironmentVariable>,
|
||||
pub config: Option<OsString>,
|
||||
pub pipeline: Option<Vec<String>>,
|
||||
pub executable: Option<NuProcess>,
|
||||
}
|
||||
|
||||
impl Director {
|
||||
pub fn cococo(&self, arg: &str) -> Self {
|
||||
let mut process = NuProcess {
|
||||
environment_vars: self.environment_vars.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
process.args(&["--testbin", "cococo", arg]);
|
||||
Director {
|
||||
config: self.config.clone(),
|
||||
executable: Some(process),
|
||||
environment_vars: self.environment_vars.clone(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn and_then(&mut self, commands: &str) -> &mut Self {
|
||||
let commands = commands.to_string();
|
||||
|
||||
if let Some(ref mut pipeline) = self.pipeline {
|
||||
pipeline.push(commands);
|
||||
} else {
|
||||
self.pipeline = Some(vec![commands]);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn pipeline(&self, commands: &str) -> Self {
|
||||
let mut director = Director {
|
||||
pipeline: if commands.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(vec![commands.to_string()])
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut process = NuProcess {
|
||||
environment_vars: self.environment_vars.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if let Some(working_directory) = &self.cwd {
|
||||
process.cwd(working_directory);
|
||||
}
|
||||
|
||||
process.arg("--skip-plugins");
|
||||
process.arg("--no-history");
|
||||
if let Some(config_file) = self.config.as_ref() {
|
||||
process.args(&[
|
||||
"--config-file",
|
||||
config_file.to_str().expect("failed to convert."),
|
||||
]);
|
||||
}
|
||||
process.arg("--perf");
|
||||
|
||||
director.executable = Some(process);
|
||||
director
|
||||
}
|
||||
|
||||
pub fn executable(&self) -> Option<&NuProcess> {
|
||||
if let Some(binary) = &self.executable {
|
||||
Some(binary)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Executable for Director {
|
||||
fn execute(&mut self) -> NuResult {
|
||||
use std::io::Write;
|
||||
use std::process::Stdio;
|
||||
|
||||
match self.executable() {
|
||||
Some(binary) => {
|
||||
let mut process = match binary
|
||||
.construct()
|
||||
.stdout(Stdio::piped())
|
||||
.stdin(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
{
|
||||
Ok(child) => child,
|
||||
Err(why) => panic!("Can't run test {}", why),
|
||||
};
|
||||
|
||||
if let Some(pipelines) = &self.pipeline {
|
||||
let child = process.stdin.as_mut().expect("Failed to open stdin");
|
||||
|
||||
for pipeline in pipelines {
|
||||
child
|
||||
.write_all(format!("{}\n", pipeline).as_bytes())
|
||||
.expect("Could not write to");
|
||||
}
|
||||
|
||||
child.write_all(b"exit\n").expect("Could not write to");
|
||||
}
|
||||
|
||||
process
|
||||
.wait_with_output()
|
||||
.map_err(|_| {
|
||||
let reason = format!(
|
||||
"could not execute process {} ({})",
|
||||
binary, "No execution took place"
|
||||
);
|
||||
|
||||
NuError {
|
||||
desc: reason,
|
||||
exit: None,
|
||||
output: None,
|
||||
}
|
||||
})
|
||||
.and_then(|process| {
|
||||
let out =
|
||||
Outcome::new(&read_std(&process.stdout), &read_std(&process.stderr));
|
||||
|
||||
match process.status.success() {
|
||||
true => Ok(out),
|
||||
false => Err(NuError {
|
||||
desc: String::new(),
|
||||
exit: Some(process.status),
|
||||
output: Some(out),
|
||||
}),
|
||||
}
|
||||
})
|
||||
}
|
||||
None => Err(NuError {
|
||||
desc: String::from("err"),
|
||||
exit: None,
|
||||
output: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Director {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "director")
|
||||
}
|
||||
}
|
||||
|
||||
fn read_std(std: &[u8]) -> Vec<u8> {
|
||||
let out = String::from_utf8_lossy(std);
|
||||
let out = out.lines().collect::<Vec<_>>().join("\n");
|
||||
let out = out.replace("\r\n", "");
|
||||
out.replace("\n", "").into_bytes()
|
||||
}
|
105
crates/nu-test-support/src/playground/matchers.rs
Normal file
105
crates/nu-test-support/src/playground/matchers.rs
Normal file
@ -0,0 +1,105 @@
|
||||
use hamcrest2::core::{MatchResult, Matcher};
|
||||
use std::fmt;
|
||||
use std::str;
|
||||
|
||||
use super::nu_process::Outcome;
|
||||
use super::{Director, Executable};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Play {
|
||||
stdout_expectation: Option<String>,
|
||||
}
|
||||
|
||||
impl fmt::Display for Play {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "play")
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Play {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "play")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn says() -> Play {
|
||||
Play {
|
||||
stdout_expectation: None,
|
||||
}
|
||||
}
|
||||
|
||||
trait CheckerMatchers {
|
||||
fn output(&self, actual: &Outcome) -> MatchResult;
|
||||
fn std(&self, actual: &[u8], expected: Option<&String>, description: &str) -> MatchResult;
|
||||
fn stdout(&self, actual: &Outcome) -> MatchResult;
|
||||
}
|
||||
|
||||
impl CheckerMatchers for Play {
|
||||
fn output(&self, actual: &Outcome) -> MatchResult {
|
||||
self.stdout(actual)
|
||||
}
|
||||
|
||||
fn stdout(&self, actual: &Outcome) -> MatchResult {
|
||||
self.std(&actual.out, self.stdout_expectation.as_ref(), "stdout")
|
||||
}
|
||||
|
||||
fn std(&self, actual: &[u8], expected: Option<&String>, description: &str) -> MatchResult {
|
||||
let out = match expected {
|
||||
Some(out) => out,
|
||||
None => return Ok(()),
|
||||
};
|
||||
let actual = match str::from_utf8(actual) {
|
||||
Err(..) => return Err(format!("{} was not utf8 encoded", description)),
|
||||
Ok(actual) => actual,
|
||||
};
|
||||
|
||||
if actual != *out {
|
||||
return Err(format!(
|
||||
"not equal:\n actual: {}\n expected: {}\n\n",
|
||||
actual, out
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher<Outcome> for Play {
|
||||
fn matches(&self, output: Outcome) -> MatchResult {
|
||||
self.output(&output)
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher<Director> for Play {
|
||||
fn matches(&self, mut director: Director) -> MatchResult {
|
||||
self.matches(&mut director)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Matcher<&'a mut Director> for Play {
|
||||
fn matches(&self, director: &'a mut Director) -> MatchResult {
|
||||
if director.executable().is_none() {
|
||||
return Err(format!("no such process {}", director));
|
||||
}
|
||||
|
||||
let res = director.execute();
|
||||
|
||||
match res {
|
||||
Ok(out) => self.output(&out),
|
||||
Err(err) => {
|
||||
if let Some(out) = &err.output {
|
||||
return self.output(out);
|
||||
}
|
||||
|
||||
Err(format!("could not exec process {}: {:?}", director, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Play {
|
||||
pub fn stdout(mut self, expected: &str) -> Self {
|
||||
self.stdout_expectation = Some(expected.to_string());
|
||||
self
|
||||
}
|
||||
}
|
104
crates/nu-test-support/src/playground/nu_process.rs
Normal file
104
crates/nu-test-support/src/playground/nu_process.rs
Normal file
@ -0,0 +1,104 @@
|
||||
use super::EnvironmentVariable;
|
||||
use crate::fs::{binaries as test_bins_path, executable_path};
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::fmt;
|
||||
use std::path::Path;
|
||||
use std::process::{Command, ExitStatus};
|
||||
|
||||
pub trait Executable {
|
||||
fn execute(&mut self) -> NuResult;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Outcome {
|
||||
pub out: Vec<u8>,
|
||||
pub err: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Outcome {
|
||||
pub fn new(out: &[u8], err: &[u8]) -> Outcome {
|
||||
Outcome {
|
||||
out: out.to_vec(),
|
||||
err: err.to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type NuResult = Result<Outcome, NuError>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NuError {
|
||||
pub desc: String,
|
||||
pub exit: Option<ExitStatus>,
|
||||
pub output: Option<Outcome>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct NuProcess {
|
||||
pub arguments: Vec<OsString>,
|
||||
pub environment_vars: Vec<EnvironmentVariable>,
|
||||
pub cwd: Option<OsString>,
|
||||
}
|
||||
|
||||
impl fmt::Display for NuProcess {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "`nu")?;
|
||||
|
||||
for arg in &self.arguments {
|
||||
write!(f, " {}", arg.to_string_lossy())?;
|
||||
}
|
||||
|
||||
write!(f, "`")
|
||||
}
|
||||
}
|
||||
|
||||
impl NuProcess {
|
||||
pub fn arg<T: AsRef<OsStr>>(&mut self, arg: T) -> &mut Self {
|
||||
self.arguments.push(arg.as_ref().to_os_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn args<T: AsRef<OsStr>>(&mut self, arguments: &[T]) -> &mut NuProcess {
|
||||
self.arguments
|
||||
.extend(arguments.iter().map(|t| t.as_ref().to_os_string()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn cwd<T: AsRef<OsStr>>(&mut self, path: T) -> &mut NuProcess {
|
||||
self.cwd = Some(path.as_ref().to_os_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_cwd(&self) -> Option<&Path> {
|
||||
self.cwd.as_ref().map(Path::new)
|
||||
}
|
||||
|
||||
pub fn construct(&self) -> Command {
|
||||
let mut command = Command::new(&executable_path());
|
||||
|
||||
if let Some(cwd) = self.get_cwd() {
|
||||
command.current_dir(cwd);
|
||||
}
|
||||
|
||||
command.env_clear();
|
||||
|
||||
let paths = vec![test_bins_path()];
|
||||
|
||||
let paths_joined = match std::env::join_paths(&paths) {
|
||||
Ok(all) => all,
|
||||
Err(_) => panic!("Couldn't join paths for PATH var."),
|
||||
};
|
||||
|
||||
command.env(crate::NATIVE_PATH_ENV_VAR, paths_joined);
|
||||
|
||||
for env_var in &self.environment_vars {
|
||||
command.env(&env_var.name, &env_var.value);
|
||||
}
|
||||
|
||||
for arg in &self.arguments {
|
||||
command.arg(arg);
|
||||
}
|
||||
|
||||
command
|
||||
}
|
||||
}
|
248
crates/nu-test-support/src/playground/play.rs
Normal file
248
crates/nu-test-support/src/playground/play.rs
Normal file
@ -0,0 +1,248 @@
|
||||
use super::Director;
|
||||
use crate::fs;
|
||||
use crate::fs::Stub;
|
||||
use getset::Getters;
|
||||
use glob::glob;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str;
|
||||
use tempfile::{tempdir, TempDir};
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct EnvironmentVariable {
|
||||
pub name: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl EnvironmentVariable {
|
||||
fn new(name: &str, value: &str) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
value: value.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Playground<'a> {
|
||||
root: TempDir,
|
||||
tests: String,
|
||||
cwd: PathBuf,
|
||||
config: PathBuf,
|
||||
environment_vars: Vec<EnvironmentVariable>,
|
||||
dirs: &'a Dirs,
|
||||
}
|
||||
|
||||
#[derive(Default, Getters, Clone)]
|
||||
#[get = "pub"]
|
||||
pub struct Dirs {
|
||||
pub root: PathBuf,
|
||||
pub test: PathBuf,
|
||||
pub fixtures: PathBuf,
|
||||
}
|
||||
|
||||
impl Dirs {
|
||||
pub fn formats(&self) -> PathBuf {
|
||||
self.fixtures.join("formats")
|
||||
}
|
||||
|
||||
pub fn config_fixtures(&self) -> PathBuf {
|
||||
self.fixtures.join("playground/config")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Playground<'a> {
|
||||
pub fn root(&self) -> &Path {
|
||||
self.root.path()
|
||||
}
|
||||
|
||||
pub fn cwd(&self) -> &Path {
|
||||
&self.cwd
|
||||
}
|
||||
|
||||
pub fn back_to_playground(&mut self) -> &mut Self {
|
||||
self.cwd = PathBuf::from(self.root()).join(self.tests.clone());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn play(&mut self) -> &mut Self {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn setup(topic: &str, block: impl FnOnce(Dirs, &mut Playground)) {
|
||||
let root = tempdir().expect("Couldn't create a tempdir");
|
||||
let nuplay_dir = root.path().join(topic);
|
||||
|
||||
if PathBuf::from(&nuplay_dir).exists() {
|
||||
std::fs::remove_dir_all(PathBuf::from(&nuplay_dir)).expect("can not remove directory");
|
||||
}
|
||||
|
||||
std::fs::create_dir(PathBuf::from(&nuplay_dir)).expect("can not create directory");
|
||||
|
||||
let fixtures = fs::fixtures();
|
||||
let cwd = std::env::current_dir().expect("Could not get current working directory.");
|
||||
let fixtures = nu_path::canonicalize_with(fixtures.clone(), cwd).unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"Couldn't canonicalize fixtures path {}: {:?}",
|
||||
fixtures.display(),
|
||||
e
|
||||
)
|
||||
});
|
||||
|
||||
let mut playground = Playground {
|
||||
root,
|
||||
tests: topic.to_string(),
|
||||
cwd: nuplay_dir,
|
||||
config: fixtures.join("playground/config/default.toml"),
|
||||
environment_vars: Vec::default(),
|
||||
dirs: &Dirs::default(),
|
||||
};
|
||||
|
||||
let playground_root = playground.root.path();
|
||||
|
||||
let cwd = std::env::current_dir().expect("Could not get current working directory.");
|
||||
let test =
|
||||
nu_path::canonicalize_with(playground_root.join(topic), cwd).unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"Couldn't canonicalize test path {}: {:?}",
|
||||
playground_root.join(topic).display(),
|
||||
e
|
||||
)
|
||||
});
|
||||
|
||||
let cwd = std::env::current_dir().expect("Could not get current working directory.");
|
||||
let root = nu_path::canonicalize_with(playground_root, cwd).unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"Couldn't canonicalize tests root path {}: {:?}",
|
||||
playground_root.display(),
|
||||
e
|
||||
)
|
||||
});
|
||||
|
||||
let dirs = Dirs {
|
||||
root,
|
||||
test,
|
||||
fixtures,
|
||||
};
|
||||
|
||||
playground.dirs = &dirs;
|
||||
|
||||
block(dirs.clone(), &mut playground);
|
||||
}
|
||||
|
||||
pub fn with_config(&mut self, source_file: impl AsRef<Path>) -> &mut Self {
|
||||
self.config = source_file.as_ref().to_path_buf();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_env(&mut self, name: &str, value: &str) -> &mut Self {
|
||||
self.environment_vars
|
||||
.push(EnvironmentVariable::new(name, value));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_config(&self) -> &str {
|
||||
self.config.to_str().expect("could not convert path.")
|
||||
}
|
||||
|
||||
pub fn build(&mut self) -> Director {
|
||||
Director {
|
||||
cwd: Some(self.dirs.test().into()),
|
||||
config: Some(self.config.clone().into()),
|
||||
environment_vars: self.environment_vars.clone(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cococo(&mut self, arg: &str) -> Director {
|
||||
self.build().cococo(arg)
|
||||
}
|
||||
|
||||
pub fn pipeline(&mut self, commands: &str) -> Director {
|
||||
self.build().pipeline(commands)
|
||||
}
|
||||
|
||||
pub fn mkdir(&mut self, directory: &str) -> &mut Self {
|
||||
self.cwd.push(directory);
|
||||
std::fs::create_dir_all(&self.cwd).expect("can not create directory");
|
||||
self.back_to_playground();
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn symlink(&mut self, from: impl AsRef<Path>, to: impl AsRef<Path>) -> &mut Self {
|
||||
let from = self.cwd.join(from);
|
||||
let to = self.cwd.join(to);
|
||||
|
||||
let create_symlink = {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
std::os::unix::fs::symlink
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if from.is_file() {
|
||||
std::os::windows::fs::symlink_file
|
||||
} else if from.is_dir() {
|
||||
std::os::windows::fs::symlink_dir
|
||||
} else {
|
||||
panic!("symlink from must be a file or dir")
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
create_symlink(from, to).expect("can not create symlink");
|
||||
self.back_to_playground();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_files(&mut self, files: Vec<Stub>) -> &mut Self {
|
||||
let endl = fs::line_ending();
|
||||
|
||||
files
|
||||
.iter()
|
||||
.map(|f| {
|
||||
let mut path = PathBuf::from(&self.cwd);
|
||||
|
||||
let (file_name, contents) = match *f {
|
||||
Stub::EmptyFile(name) => (name, "fake data".to_string()),
|
||||
Stub::FileWithContent(name, content) => (name, content.to_string()),
|
||||
Stub::FileWithContentToBeTrimmed(name, content) => (
|
||||
name,
|
||||
content
|
||||
.lines()
|
||||
.skip(1)
|
||||
.map(|line| line.trim())
|
||||
.collect::<Vec<&str>>()
|
||||
.join(&endl),
|
||||
),
|
||||
};
|
||||
|
||||
path.push(file_name);
|
||||
|
||||
std::fs::write(path, contents.as_bytes()).expect("can not create file");
|
||||
})
|
||||
.for_each(drop);
|
||||
self.back_to_playground();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn within(&mut self, directory: &str) -> &mut Self {
|
||||
self.cwd.push(directory);
|
||||
std::fs::create_dir(&self.cwd).expect("can not create directory");
|
||||
self
|
||||
}
|
||||
|
||||
pub fn glob_vec(pattern: &str) -> Vec<PathBuf> {
|
||||
let glob = glob(pattern);
|
||||
|
||||
glob.expect("invalid pattern")
|
||||
.map(|path| {
|
||||
if let Ok(path) = path {
|
||||
path
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
41
crates/nu-test-support/src/playground/tests.rs
Normal file
41
crates/nu-test-support/src/playground/tests.rs
Normal file
@ -0,0 +1,41 @@
|
||||
use crate::playground::Playground;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use super::matchers::says;
|
||||
use hamcrest2::assert_that;
|
||||
use hamcrest2::prelude::*;
|
||||
|
||||
fn path(p: &Path) -> PathBuf {
|
||||
let cwd = std::env::current_dir().expect("Could not get current working directory.");
|
||||
nu_path::canonicalize_with(p, cwd)
|
||||
.unwrap_or_else(|e| panic!("Couldn't canonicalize path {}: {:?}", p.display(), e))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn asserts_standard_out_expectation_from_nu_executable() {
|
||||
Playground::setup("topic", |_, nu| {
|
||||
assert_that!(nu.cococo("andres"), says().stdout("andres"));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn current_working_directory_in_sandbox_directory_created() {
|
||||
Playground::setup("topic", |dirs, nu| {
|
||||
let original_cwd = dirs.test();
|
||||
nu.within("some_directory_within");
|
||||
|
||||
assert_eq!(path(nu.cwd()), original_cwd.join("some_directory_within"));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn current_working_directory_back_to_root_from_anywhere() {
|
||||
Playground::setup("topic", |dirs, nu| {
|
||||
let original_cwd = dirs.test();
|
||||
|
||||
nu.within("some_directory_within");
|
||||
nu.back_to_playground();
|
||||
|
||||
assert_eq!(path(nu.cwd()), *original_cwd);
|
||||
})
|
||||
}
|
Reference in New Issue
Block a user