mirror of
https://github.com/nushell/nushell.git
synced 2025-06-30 14:40:06 +02:00
Merge pull request #1081 from andrasio/test-extract
Start test organization facelift.
This commit is contained in:
16
crates/test-support/Cargo.toml
Normal file
16
crates/test-support/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "test-support"
|
||||
version = "0.1.0"
|
||||
authors = ["Yehuda Katz <wycats@gmail.com>", "Jonathan Turner <jonathan.d.turner@gmail.com>", "Andrés N. Robalino <andres@androbtech.com>"]
|
||||
edition = "2018"
|
||||
description = "A source string characterizer for Nushell"
|
||||
license = "MIT"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
app_dirs = "1.2.1"
|
||||
dunce = "1.0.0"
|
||||
getset = "0.0.9"
|
||||
glob = "0.3.0"
|
||||
tempfile = "3.1.0"
|
228
crates/test-support/src/fs.rs
Normal file
228
crates/test-support/src/fs.rs
Normal file
@ -0,0 +1,228 @@
|
||||
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(self.inner.parent().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AbsoluteFile> for PathBuf {
|
||||
fn from(file: AbsoluteFile) -> Self {
|
||||
file.inner
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AbsolutePath {
|
||||
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 let Some(parent) = full_path.parent() {
|
||||
panic!(format!("{:?} exists", parent.display()));
|
||||
}
|
||||
|
||||
std::fs::write(full_path, "fake data".as_bytes())
|
||||
}
|
||||
|
||||
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 buf = PathBuf::new();
|
||||
buf.push("target");
|
||||
buf.push("debug");
|
||||
buf.push("nu");
|
||||
buf
|
||||
}
|
||||
|
||||
pub fn in_directory(str: impl AsRef<Path>) -> String {
|
||||
str.as_ref().display().to_string()
|
||||
}
|
38
crates/test-support/src/lib.rs
Normal file
38
crates/test-support/src/lib.rs
Normal file
@ -0,0 +1,38 @@
|
||||
pub mod fs;
|
||||
pub mod macros;
|
||||
pub mod playground;
|
||||
|
||||
pub fn pipeline(commands: &str) -> String {
|
||||
commands
|
||||
.lines()
|
||||
.skip(1)
|
||||
.map(|line| line.trim())
|
||||
.collect::<Vec<&str>>()
|
||||
.join(" ")
|
||||
.trim_end()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[cfg(tests)]
|
||||
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
|
||||
| sum
|
||||
| echo "$it"
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
actual,
|
||||
r#"open los_tres_amigos.txt | from-csv | get rusty_luck | str --to-int | sum | echo "$it""#
|
||||
);
|
||||
}
|
||||
}
|
159
crates/test-support/src/macros.rs
Normal file
159
crates/test-support/src/macros.rs
Normal file
@ -0,0 +1,159 @@
|
||||
#[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 std::error::Error;
|
||||
pub use std::io::prelude::*;
|
||||
pub use std::process::{Command, Stdio};
|
||||
|
||||
let commands = &*format!(
|
||||
"
|
||||
cd {}
|
||||
{}
|
||||
exit",
|
||||
$crate::fs::in_directory($cwd),
|
||||
$crate::fs::DisplayPath::display_path(&$path)
|
||||
);
|
||||
|
||||
let mut process = match Command::new($crate::fs::executable_path())
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
{
|
||||
Ok(child) => child,
|
||||
Err(why) => panic!("Can't run test {}", why.description()),
|
||||
};
|
||||
|
||||
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");
|
||||
|
||||
let out = String::from_utf8_lossy(&output.stdout);
|
||||
let out = out.replace("\r\n", "");
|
||||
let out = out.replace("\n", "");
|
||||
out
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! nu_error {
|
||||
(cwd: $cwd:expr, $path:expr, $($part:expr),*) => {{
|
||||
use $crate::fs::DisplayPath;
|
||||
|
||||
let path = format!($path, $(
|
||||
$part.display_path()
|
||||
),*);
|
||||
|
||||
nu_error!($cwd, &path)
|
||||
}};
|
||||
|
||||
(cwd: $cwd:expr, $path:expr) => {{
|
||||
nu_error!($cwd, $path)
|
||||
}};
|
||||
|
||||
($cwd:expr, $path:expr) => {{
|
||||
pub use std::error::Error;
|
||||
pub use std::io::prelude::*;
|
||||
pub use std::process::{Command, Stdio};
|
||||
|
||||
let commands = &*format!(
|
||||
"
|
||||
cd {}
|
||||
{}
|
||||
exit",
|
||||
$crate::fs::in_directory($cwd),
|
||||
$crate::fs::DisplayPath::display_path(&$path)
|
||||
);
|
||||
|
||||
let mut process = Command::new($crate::fs::executable_path())
|
||||
.stdin(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("couldn't run test");
|
||||
|
||||
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 stderr");
|
||||
|
||||
let out = String::from_utf8_lossy(&output.stderr);
|
||||
out.into_owned()
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! nu_combined {
|
||||
(cwd: $cwd:expr, $path:expr, $($part:expr),*) => {{
|
||||
use $crate::fs::DisplayPath;
|
||||
|
||||
let path = format!($path, $(
|
||||
$part.display_path()
|
||||
),*);
|
||||
|
||||
nu_combined!($cwd, &path)
|
||||
}};
|
||||
|
||||
(cwd: $cwd:expr, $path:expr) => {{
|
||||
nu_combined!($cwd, $path)
|
||||
}};
|
||||
|
||||
($cwd:expr, $path:expr) => {{
|
||||
pub use std::error::Error;
|
||||
pub use std::io::prelude::*;
|
||||
pub use std::process::{Command, Stdio};
|
||||
|
||||
let commands = &*format!(
|
||||
"
|
||||
cd {}
|
||||
{}
|
||||
exit",
|
||||
$crate::fs::in_directory($cwd),
|
||||
$crate::fs::DisplayPath::display_path(&$path)
|
||||
);
|
||||
|
||||
let mut process = Command::new($crate::fs::executable_path())
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("couldn't run test");
|
||||
|
||||
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 err = String::from_utf8_lossy(&output.stderr).into_owned();
|
||||
let out = String::from_utf8_lossy(&output.stdout).into_owned();
|
||||
let out = out.replace("\r\n", "");
|
||||
let out = out.replace("\n", "");
|
||||
(out, err)
|
||||
}};
|
||||
}
|
152
crates/test-support/src/playground.rs
Normal file
152
crates/test-support/src/playground.rs
Normal file
@ -0,0 +1,152 @@
|
||||
use crate::fs::line_ending;
|
||||
use crate::fs::Stub;
|
||||
|
||||
use getset::Getters;
|
||||
use glob::glob;
|
||||
use std::path::{Path, PathBuf};
|
||||
use tempfile::{tempdir, TempDir};
|
||||
|
||||
pub struct Playground {
|
||||
root: TempDir,
|
||||
tests: String,
|
||||
cwd: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Getters)]
|
||||
#[get = "pub"]
|
||||
pub struct Dirs {
|
||||
pub root: PathBuf,
|
||||
pub test: PathBuf,
|
||||
pub fixtures: PathBuf,
|
||||
}
|
||||
|
||||
impl Dirs {
|
||||
pub fn formats(&self) -> PathBuf {
|
||||
PathBuf::from(self.fixtures.join("formats"))
|
||||
}
|
||||
}
|
||||
|
||||
impl Playground {
|
||||
pub fn root(&self) -> &Path {
|
||||
self.root.path()
|
||||
}
|
||||
|
||||
pub fn back_to_playground(&mut self) -> &mut Self {
|
||||
self.cwd = PathBuf::from(self.root()).join(self.tests.clone());
|
||||
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 mut playground = Playground {
|
||||
root: root,
|
||||
tests: topic.to_string(),
|
||||
cwd: nuplay_dir,
|
||||
};
|
||||
|
||||
let project_root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
let playground_root = playground.root.path();
|
||||
|
||||
let fixtures = project_root;
|
||||
let fixtures = fixtures
|
||||
.parent()
|
||||
.expect("Couldn't find the fixtures directory")
|
||||
.parent()
|
||||
.expect("Couldn't find the fixtures directory")
|
||||
.join("tests/fixtures");
|
||||
|
||||
let fixtures = dunce::canonicalize(fixtures.clone()).expect(&format!(
|
||||
"Couldn't canonicalize fixtures path {}",
|
||||
fixtures.display()
|
||||
));
|
||||
|
||||
let test =
|
||||
dunce::canonicalize(PathBuf::from(playground_root.join(topic))).expect(&format!(
|
||||
"Couldn't canonicalize test path {}",
|
||||
playground_root.join(topic).display()
|
||||
));
|
||||
|
||||
let root = dunce::canonicalize(playground_root).expect(&format!(
|
||||
"Couldn't canonicalize tests root path {}",
|
||||
playground_root.display()
|
||||
));
|
||||
|
||||
let dirs = Dirs {
|
||||
root,
|
||||
test,
|
||||
fixtures,
|
||||
};
|
||||
|
||||
block(dirs, &mut playground);
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
pub fn with_files(&mut self, files: Vec<Stub>) -> &mut Self {
|
||||
let endl = 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(PathBuf::from(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);
|
||||
|
||||
match glob {
|
||||
Ok(paths) => paths
|
||||
.map(|path| {
|
||||
if let Ok(path) = path {
|
||||
path
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
Err(_) => panic!("Invalid pattern."),
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user