summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Sherret <dsherret@users.noreply.github.com>2023-02-27 16:52:49 -0400
committerGitHub <noreply@github.com>2023-02-27 16:52:49 -0400
commit7c090b1b14e6b5000dbbed434525387c414ca62c (patch)
tree1645fbd8a8b1aee55f79cfe74d2bb7ecadcf5215
parent6bbb4c3af60d568a34e1472a0721ddd8a3dab469 (diff)
chore: test builders for integration tests (#17965)
Start of adding test builders to simplify integration tests. I only updated a few test files. We can complete upgrading over time.
-rw-r--r--cli/tests/integration/bench_tests.rs39
-rw-r--r--cli/tests/integration/cert_tests.rs173
-rw-r--r--cli/tests/integration/lint_tests.rs26
-rw-r--r--cli/tests/integration/mod.rs61
-rw-r--r--cli/tests/integration/test_tests.rs12
-rw-r--r--test_util/src/assertions.rs60
-rw-r--r--test_util/src/builders.rs369
-rw-r--r--test_util/src/lib.rs152
8 files changed, 629 insertions, 263 deletions
diff --git a/cli/tests/integration/bench_tests.rs b/cli/tests/integration/bench_tests.rs
index 240c4b2d4..4e94463e5 100644
--- a/cli/tests/integration/bench_tests.rs
+++ b/cli/tests/integration/bench_tests.rs
@@ -2,7 +2,11 @@
use deno_core::url::Url;
use test_util as util;
+use util::assert_contains;
+use util::assert_exit_code;
+use util::assert_output_file;
use util::env_vars_for_npm_tests;
+use util::TestContext;
itest!(overloads {
args: "bench bench/overloads.ts",
@@ -187,19 +191,16 @@ itest!(json_output {
#[test]
fn recursive_permissions_pledge() {
- let output = util::deno_cmd()
- .current_dir(util::testdata_path())
- .arg("bench")
- .arg("bench/recursive_permissions_pledge.js")
- .stderr(std::process::Stdio::piped())
- .spawn()
- .unwrap()
- .wait_with_output()
- .unwrap();
- assert!(!output.status.success());
- assert!(String::from_utf8(output.stderr).unwrap().contains(
+ let context = TestContext::default();
+ let output = context
+ .new_command()
+ .args("bench bench/recursive_permissions_pledge.js")
+ .run();
+ assert_exit_code!(output, 1);
+ assert_contains!(
+ output.text(),
"pledge test permissions called before restoring previous pledge"
- ));
+ );
}
#[test]
@@ -208,14 +209,12 @@ fn file_protocol() {
Url::from_file_path(util::testdata_path().join("bench/file_protocol.ts"))
.unwrap()
.to_string();
-
- (util::CheckOutputIntegrationTest {
- args_vec: vec!["bench", &file_url],
- exit_code: 0,
- output: "bench/file_protocol.out",
- ..Default::default()
- })
- .run();
+ let context = TestContext::default();
+ let output = context
+ .new_command()
+ .args(format!("bench bench/file_protocol.ts {file_url}"))
+ .run();
+ assert_output_file!(output, "bench/file_protocol.out");
}
itest!(package_json_basic {
diff --git a/cli/tests/integration/cert_tests.rs b/cli/tests/integration/cert_tests.rs
index 320e1b2a9..8fa439d78 100644
--- a/cli/tests/integration/cert_tests.rs
+++ b/cli/tests/integration/cert_tests.rs
@@ -3,6 +3,7 @@
use deno_runtime::deno_net::ops_tls::TlsStream;
use deno_runtime::deno_tls::rustls;
use deno_runtime::deno_tls::rustls_pemfile;
+use lsp_types::Url;
use std::io::BufReader;
use std::io::Cursor;
use std::io::Read;
@@ -11,6 +12,9 @@ use std::sync::Arc;
use test_util as util;
use test_util::TempDir;
use tokio::task::LocalSet;
+use util::assert_exit_code;
+use util::assert_output_text;
+use util::TestContext;
itest_flaky!(cafile_url_imports {
args: "run --quiet --reload --cert tls/RootCA.pem cert/cafile_url_imports.ts",
@@ -19,132 +23,113 @@ itest_flaky!(cafile_url_imports {
});
itest_flaky!(cafile_ts_fetch {
- args:
- "run --quiet --reload --allow-net --cert tls/RootCA.pem cert/cafile_ts_fetch.ts",
- output: "cert/cafile_ts_fetch.ts.out",
- http_server: true,
- });
+ args:
+ "run --quiet --reload --allow-net --cert tls/RootCA.pem cert/cafile_ts_fetch.ts",
+ output: "cert/cafile_ts_fetch.ts.out",
+ http_server: true,
+});
itest_flaky!(cafile_eval {
- args: "eval --cert tls/RootCA.pem fetch('https://localhost:5545/cert/cafile_ts_fetch.ts.out').then(r=>r.text()).then(t=>console.log(t.trimEnd()))",
- output: "cert/cafile_ts_fetch.ts.out",
- http_server: true,
- });
+ args: "eval --cert tls/RootCA.pem fetch('https://localhost:5545/cert/cafile_ts_fetch.ts.out').then(r=>r.text()).then(t=>console.log(t.trimEnd()))",
+ output: "cert/cafile_ts_fetch.ts.out",
+ http_server: true,
+});
itest_flaky!(cafile_info {
- args:
- "info --quiet --cert tls/RootCA.pem https://localhost:5545/cert/cafile_info.ts",
- output: "cert/cafile_info.ts.out",
- http_server: true,
- });
+ args:
+ "info --quiet --cert tls/RootCA.pem https://localhost:5545/cert/cafile_info.ts",
+ output: "cert/cafile_info.ts.out",
+ http_server: true,
+});
itest_flaky!(cafile_url_imports_unsafe_ssl {
- args: "run --quiet --reload --unsafely-ignore-certificate-errors=localhost cert/cafile_url_imports.ts",
- output: "cert/cafile_url_imports_unsafe_ssl.ts.out",
- http_server: true,
- });
+ args: "run --quiet --reload --unsafely-ignore-certificate-errors=localhost cert/cafile_url_imports.ts",
+ output: "cert/cafile_url_imports_unsafe_ssl.ts.out",
+ http_server: true,
+});
itest_flaky!(cafile_ts_fetch_unsafe_ssl {
- args:
- "run --quiet --reload --allow-net --unsafely-ignore-certificate-errors cert/cafile_ts_fetch.ts",
- output: "cert/cafile_ts_fetch_unsafe_ssl.ts.out",
- http_server: true,
- });
+ args:
+ "run --quiet --reload --allow-net --unsafely-ignore-certificate-errors cert/cafile_ts_fetch.ts",
+ output: "cert/cafile_ts_fetch_unsafe_ssl.ts.out",
+ http_server: true,
+});
// TODO(bartlomieju): reenable, this test was flaky on macOS CI during 1.30.3 release
// itest!(deno_land_unsafe_ssl {
-// args:
-// "run --quiet --reload --allow-net --unsafely-ignore-certificate-errors=deno.land cert/deno_land_unsafe_ssl.ts",
-// output: "cert/deno_land_unsafe_ssl.ts.out",
-// });
+// args:
+// "run --quiet --reload --allow-net --unsafely-ignore-certificate-errors=deno.land cert/deno_land_unsafe_ssl.ts",
+// output: "cert/deno_land_unsafe_ssl.ts.out",
+// });
itest!(ip_address_unsafe_ssl {
- args:
- "run --quiet --reload --allow-net --unsafely-ignore-certificate-errors=1.1.1.1 cert/ip_address_unsafe_ssl.ts",
- output: "cert/ip_address_unsafe_ssl.ts.out",
- });
+ args:
+ "run --quiet --reload --allow-net --unsafely-ignore-certificate-errors=1.1.1.1 cert/ip_address_unsafe_ssl.ts",
+ output: "cert/ip_address_unsafe_ssl.ts.out",
+});
itest!(localhost_unsafe_ssl {
- args:
- "run --quiet --reload --allow-net --unsafely-ignore-certificate-errors=deno.land cert/cafile_url_imports.ts",
- output: "cert/localhost_unsafe_ssl.ts.out",
- http_server: true,
- exit_code: 1,
- });
+ args: "run --quiet --reload --allow-net --unsafely-ignore-certificate-errors=deno.land cert/cafile_url_imports.ts",
+ output: "cert/localhost_unsafe_ssl.ts.out",
+ http_server: true,
+ exit_code: 1,
+});
#[flaky_test::flaky_test]
fn cafile_env_fetch() {
- use deno_core::url::Url;
- let _g = util::http_server();
- let deno_dir = TempDir::new();
let module_url =
Url::parse("https://localhost:5545/cert/cafile_url_imports.ts").unwrap();
- let cafile = util::testdata_path().join("tls/RootCA.pem");
- let output = Command::new(util::deno_exe_path())
- .env("DENO_DIR", deno_dir.path())
- .env("DENO_CERT", cafile)
- .current_dir(util::testdata_path())
- .arg("cache")
- .arg(module_url.to_string())
- .output()
- .expect("Failed to spawn script");
- assert!(output.status.success());
+ let context = TestContext::with_http_server();
+ let cafile = context.testdata_path().join("tls/RootCA.pem");
+ let output = context
+ .new_command()
+ .args(format!("cache {module_url}"))
+ .env("DENO_CERT", cafile.to_string_lossy())
+ .run();
+
+ assert_exit_code!(output, 0);
+ output.skip_output_check();
}
#[flaky_test::flaky_test]
fn cafile_fetch() {
- use deno_core::url::Url;
- let _g = util::http_server();
- let deno_dir = TempDir::new();
let module_url =
Url::parse("http://localhost:4545/cert/cafile_url_imports.ts").unwrap();
- let cafile = util::testdata_path().join("tls/RootCA.pem");
- let output = Command::new(util::deno_exe_path())
- .env("DENO_DIR", deno_dir.path())
- .current_dir(util::testdata_path())
- .arg("cache")
- .arg("--cert")
- .arg(cafile)
- .arg(module_url.to_string())
- .output()
- .expect("Failed to spawn script");
- assert!(output.status.success());
- let out = std::str::from_utf8(&output.stdout).unwrap();
- assert_eq!(out, "");
+ let context = TestContext::with_http_server();
+ let cafile = context.testdata_path().join("tls/RootCA.pem");
+ let output = context
+ .new_command()
+ .args(format!(
+ "cache --quiet --cert {} {}",
+ cafile.to_string_lossy(),
+ module_url,
+ ))
+ .run();
+
+ assert_exit_code!(output, 0);
+ assert_output_text!(output, "");
}
#[test]
fn cafile_compile() {
- let _g = util::http_server();
- let dir = TempDir::new();
- let exe = if cfg!(windows) {
- dir.path().join("cert.exe")
+ let context = TestContext::with_http_server();
+ let temp_dir = context.deno_dir().path();
+ let output_exe = if cfg!(windows) {
+ temp_dir.join("cert.exe")
} else {
- dir.path().join("cert")
+ temp_dir.join("cert")
};
- let output = util::deno_cmd()
- .current_dir(util::testdata_path())
- .arg("compile")
- .arg("--cert")
- .arg("./tls/RootCA.pem")
- .arg("--allow-net")
- .arg("--output")
- .arg(&exe)
- .arg("./cert/cafile_ts_fetch.ts")
- .stdout(std::process::Stdio::piped())
- .spawn()
- .unwrap()
- .wait_with_output()
- .unwrap();
- assert!(output.status.success());
- let output = Command::new(exe)
- .stdout(std::process::Stdio::piped())
- .spawn()
- .unwrap()
- .wait_with_output()
- .unwrap();
- assert!(output.status.success());
- assert_eq!(output.stdout, b"[WILDCARD]\nHello\n")
+ let output = context.new_command()
+ .args(format!("compile --quiet --cert ./tls/RootCA.pem --allow-net --output {} ./cert/cafile_ts_fetch.ts", output_exe.to_string_lossy()))
+ .run();
+ output.skip_output_check();
+
+ let exe_output = context
+ .new_command()
+ .command_name(output_exe.to_string_lossy())
+ .run();
+
+ assert_output_text!(exe_output, "[WILDCARD]\nHello\n");
}
#[flaky_test::flaky_test]
diff --git a/cli/tests/integration/lint_tests.rs b/cli/tests/integration/lint_tests.rs
index 990db16b6..8bf35ed8f 100644
--- a/cli/tests/integration/lint_tests.rs
+++ b/cli/tests/integration/lint_tests.rs
@@ -1,26 +1,10 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
-use test_util as util;
-
-#[test]
-fn ignore_unexplicit_files() {
- let output = util::deno_cmd()
- .current_dir(util::root_path())
- .env("NO_COLOR", "1")
- .arg("lint")
- .arg("--unstable")
- .arg("--ignore=./")
- .stderr(std::process::Stdio::piped())
- .spawn()
- .unwrap()
- .wait_with_output()
- .unwrap();
- assert!(!output.status.success());
- assert_eq!(
- String::from_utf8_lossy(&output.stderr),
- "error: No target files found.\n"
- );
-}
+itest!(ignore_unexplicit_files {
+ args: "lint --unstable --ignore=./",
+ output_str: Some("error: No target files found.\n"),
+ exit_code: 1,
+});
itest!(all {
args: "lint lint/without_config/file1.js lint/without_config/file2.ts lint/without_config/ignored_file.ts",
diff --git a/cli/tests/integration/mod.rs b/cli/tests/integration/mod.rs
index d102b486d..6d1daf7d7 100644
--- a/cli/tests/integration/mod.rs
+++ b/cli/tests/integration/mod.rs
@@ -5,12 +5,20 @@ macro_rules! itest(
($name:ident {$( $key:ident: $value:expr,)*}) => {
#[test]
fn $name() {
- (test_util::CheckOutputIntegrationTest {
+ let test = test_util::CheckOutputIntegrationTest {
$(
$key: $value,
)*
.. Default::default()
- }).run()
+ };
+ let output = test.output();
+ test_util::assert_exit_code!(output, test.exit_code);
+ if !test.output.is_empty() {
+ assert!(test.output_str.is_none());
+ test_util::assert_output_file!(output, test.output);
+ } else {
+ test_util::assert_output_text!(output, test.output_str.unwrap_or(""));
+ }
}
}
);
@@ -20,7 +28,42 @@ macro_rules! itest_flaky(
($name:ident {$( $key:ident: $value:expr,)*}) => {
#[flaky_test::flaky_test]
fn $name() {
- (test_util::CheckOutputIntegrationTest {
+ let test = test_util::CheckOutputIntegrationTest {
+ $(
+ $key: $value,
+ )*
+ .. Default::default()
+ };
+ let output = test.output();
+ test_util::assert_exit_code!(output, test.exit_code);
+ if !test.output.is_empty() {
+ assert!(test.output_str.is_none());
+ test_util::assert_output_file!(output, test.output);
+ } else {
+ test_util::assert_output_text!(output, test.output_str.unwrap_or(""));
+ }
+ }
+}
+);
+
+#[macro_export]
+macro_rules! context(
+({$( $key:ident: $value:expr,)*}) => {
+ test_util::TestContext::create(test_util::TestContextOptions {
+ $(
+ $key: $value,
+ )*
+ .. Default::default()
+ })
+}
+);
+
+#[macro_export]
+macro_rules! itest_steps(
+($name:ident {$( $key:ident: $value:expr,)*}) => {
+ #[test]
+ fn $name() {
+ (test_util::CheckOutputIntegrationTestSteps {
$(
$key: $value,
)*
@@ -30,6 +73,18 @@ macro_rules! itest_flaky(
}
);
+#[macro_export]
+macro_rules! command_step(
+({$( $key:ident: $value:expr,)*}) => {
+ test_util::CheckOutputIntegrationTestCommandStep {
+ $(
+ $key: $value,
+ )*
+ .. Default::default()
+ }
+}
+);
+
// These files have `_tests.rs` suffix to make it easier to tell which file is
// the test (ex. `lint_tests.rs`) and which is the implementation (ex. `lint.rs`)
// when both are open, especially for two tabs in VS Code
diff --git a/cli/tests/integration/test_tests.rs b/cli/tests/integration/test_tests.rs
index d52109d2b..0ff09e69d 100644
--- a/cli/tests/integration/test_tests.rs
+++ b/cli/tests/integration/test_tests.rs
@@ -2,7 +2,9 @@
use deno_core::url::Url;
use test_util as util;
+use util::assert_output_file;
use util::env_vars_for_npm_tests;
+use util::TestContext;
#[test]
fn no_color() {
@@ -414,13 +416,9 @@ fn file_protocol() {
.unwrap()
.to_string();
- (util::CheckOutputIntegrationTest {
- args_vec: vec!["test", &file_url],
- exit_code: 0,
- output: "test/file_protocol.out",
- ..Default::default()
- })
- .run();
+ let context = TestContext::default();
+ let output = context.new_command().args(format!("test {file_url}")).run();
+ assert_output_file!(output, "test/file_protocol.out");
}
itest!(uncaught_errors {
diff --git a/test_util/src/assertions.rs b/test_util/src/assertions.rs
index a004530b6..994926566 100644
--- a/test_util/src/assertions.rs
+++ b/test_util/src/assertions.rs
@@ -39,3 +39,63 @@ macro_rules! assert_not_contains {
}
}
}
+
+#[macro_export]
+macro_rules! assert_output_text {
+ ($output:expr, $expected:expr) => {
+ let expected_text = $expected;
+ let actual = $output.text();
+
+ if !expected_text.contains("[WILDCARD]") {
+ assert_eq!(actual, expected_text)
+ } else if !test_util::wildcard_match(&expected_text, actual) {
+ println!("OUTPUT START\n{}\nOUTPUT END", actual);
+ println!("EXPECTED START\n{expected_text}\nEXPECTED END");
+ panic!("pattern match failed");
+ }
+ };
+}
+
+#[macro_export]
+macro_rules! assert_output_file {
+ ($output:expr, $file_path:expr) => {
+ let output = &$output;
+ let output_path = output.testdata_dir().join($file_path);
+ println!("output path {}", output_path.display());
+ let expected_text =
+ std::fs::read_to_string(&output_path).unwrap_or_else(|err| {
+ panic!("failed loading {}\n\n{err:#}", output_path.display())
+ });
+ test_util::assert_output_text!(output, expected_text);
+ };
+}
+
+#[macro_export]
+macro_rules! assert_exit_code {
+ ($output:expr, $exit_code:expr) => {
+ let output = &$output;
+ let actual = output.text();
+ let expected_exit_code = $exit_code;
+ let actual_exit_code = output.exit_code();
+
+ if let Some(exit_code) = &actual_exit_code {
+ if *exit_code != expected_exit_code {
+ println!("OUTPUT\n{actual}\nOUTPUT");
+ panic!(
+ "bad exit code, expected: {:?}, actual: {:?}",
+ expected_exit_code, exit_code
+ );
+ }
+ } else {
+ println!("OUTPUT\n{actual}\nOUTPUT");
+ if let Some(signal) = output.signal() {
+ panic!(
+ "process terminated by signal, expected exit code: {:?}, actual signal: {:?}",
+ actual_exit_code, signal,
+ );
+ } else {
+ panic!("process terminated without status code on non unix platform, expected exit code: {:?}", actual_exit_code);
+ }
+ }
+ };
+}
diff --git a/test_util/src/builders.rs b/test_util/src/builders.rs
new file mode 100644
index 000000000..bbd045a10
--- /dev/null
+++ b/test_util/src/builders.rs
@@ -0,0 +1,369 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::io::Read;
+use std::io::Write;
+use std::path::PathBuf;
+use std::process::Command;
+use std::process::Stdio;
+use std::rc::Rc;
+
+use os_pipe::pipe;
+
+use crate::copy_dir_recursive;
+use crate::deno_exe_path;
+use crate::http_server;
+use crate::new_deno_dir;
+use crate::strip_ansi_codes;
+use crate::testdata_path;
+use crate::HttpServerGuard;
+use crate::TempDir;
+
+#[derive(Default)]
+pub struct TestContextBuilder {
+ use_http_server: bool,
+ use_temp_cwd: bool,
+ /// Copies the files at the specified directory in the "testdata" directory
+ /// to the temp folder and runs the test from there. This is useful when
+ /// the test creates files in the testdata directory (ex. a node_modules folder)
+ copy_temp_dir: Option<String>,
+ cwd: Option<String>,
+ envs: HashMap<String, String>,
+}
+
+impl TestContextBuilder {
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ pub fn use_http_server(&mut self) -> &mut Self {
+ self.use_http_server = true;
+ self
+ }
+
+ pub fn use_temp_cwd(&mut self) -> &mut Self {
+ self.use_temp_cwd = true;
+ self
+ }
+
+ /// Copies the files at the specified directory in the "testdata" directory
+ /// to the temp folder and runs the test from there. This is useful when
+ /// the test creates files in the testdata directory (ex. a node_modules folder)
+ pub fn use_copy_temp_dir(&mut self, dir: impl AsRef<str>) {
+ self.copy_temp_dir = Some(dir.as_ref().to_string());
+ }
+
+ pub fn set_cwd(&mut self, cwd: impl AsRef<str>) -> &mut Self {
+ self.cwd = Some(cwd.as_ref().to_string());
+ self
+ }
+
+ pub fn env(
+ &mut self,
+ key: impl AsRef<str>,
+ value: impl AsRef<str>,
+ ) -> &mut Self {
+ self
+ .envs
+ .insert(key.as_ref().to_string(), value.as_ref().to_string());
+ self
+ }
+
+ pub fn build(&self) -> TestContext {
+ let deno_dir = new_deno_dir(); // keep this alive for the test
+ let testdata_dir = if let Some(temp_copy_dir) = &self.copy_temp_dir {
+ let test_data_path = testdata_path().join(temp_copy_dir);
+ let temp_copy_dir = deno_dir.path().join(temp_copy_dir);
+ std::fs::create_dir_all(&temp_copy_dir).unwrap();
+ copy_dir_recursive(&test_data_path, &temp_copy_dir).unwrap();
+ deno_dir.path().to_owned()
+ } else {
+ testdata_path()
+ };
+
+ let deno_exe = deno_exe_path();
+ println!("deno_exe path {}", deno_exe.display());
+
+ let http_server_guard = if self.use_http_server {
+ Some(Rc::new(http_server()))
+ } else {
+ None
+ };
+
+ TestContext {
+ cwd: self.cwd.clone(),
+ envs: self.envs.clone(),
+ use_temp_cwd: self.use_temp_cwd,
+ _http_server_guard: http_server_guard,
+ deno_dir,
+ testdata_dir,
+ }
+ }
+}
+
+#[derive(Clone)]
+pub struct TestContext {
+ envs: HashMap<String, String>,
+ use_temp_cwd: bool,
+ cwd: Option<String>,
+ _http_server_guard: Option<Rc<HttpServerGuard>>,
+ deno_dir: TempDir,
+ testdata_dir: PathBuf,
+}
+
+impl Default for TestContext {
+ fn default() -> Self {
+ TestContextBuilder::default().build()
+ }
+}
+
+impl TestContext {
+ pub fn with_http_server() -> Self {
+ TestContextBuilder::default().use_http_server().build()
+ }
+
+ pub fn testdata_path(&self) -> &PathBuf {
+ &self.testdata_dir
+ }
+
+ pub fn deno_dir(&self) -> &TempDir {
+ &self.deno_dir
+ }
+
+ pub fn new_command(&self) -> TestCommandBuilder {
+ TestCommandBuilder {
+ command_name: Default::default(),
+ args: Default::default(),
+ args_vec: Default::default(),
+ stdin: Default::default(),
+ envs: Default::default(),
+ env_clear: Default::default(),
+ cwd: Default::default(),
+ context: self.clone(),
+ }
+ }
+}
+
+pub struct TestCommandBuilder {
+ command_name: Option<String>,
+ args: String,
+ args_vec: Vec<String>,
+ stdin: Option<String>,
+ envs: HashMap<String, String>,
+ env_clear: bool,
+ cwd: Option<String>,
+ context: TestContext,
+}
+
+impl TestCommandBuilder {
+ pub fn command_name(&mut self, name: impl AsRef<str>) -> &mut Self {
+ self.command_name = Some(name.as_ref().to_string());
+ self
+ }
+
+ pub fn args(&mut self, text: impl AsRef<str>) -> &mut Self {
+ self.args = text.as_ref().to_string();
+ self
+ }
+
+ pub fn args_vec(&mut self, args: Vec<String>) -> &mut Self {
+ self.args_vec = args;
+ self
+ }
+
+ pub fn stdin(&mut self, text: impl AsRef<str>) -> &mut Self {
+ self.stdin = Some(text.as_ref().to_string());
+ self
+ }
+
+ pub fn env(
+ &mut self,
+ key: impl AsRef<str>,
+ value: impl AsRef<str>,
+ ) -> &mut Self {
+ self
+ .envs
+ .insert(key.as_ref().to_string(), value.as_ref().to_string());
+ self
+ }
+
+ pub fn env_clear(&mut self) -> &mut Self {
+ self.env_clear = true;
+ self
+ }
+
+ pub fn cwd(&mut self, cwd: impl AsRef<str>) -> &mut Self {
+ self.cwd = Some(cwd.as_ref().to_string());
+ self
+ }
+
+ pub fn run(&self) -> TestCommandOutput {
+ let cwd = self.cwd.as_ref().or(self.context.cwd.as_ref());
+ let cwd = if self.context.use_temp_cwd {
+ assert!(cwd.is_none());
+ self.context.deno_dir.path().to_owned()
+ } else if let Some(cwd_) = cwd {
+ self.context.testdata_dir.join(cwd_)
+ } else {
+ self.context.testdata_dir.clone()
+ };
+ let args = if self.args_vec.is_empty() {
+ std::borrow::Cow::Owned(
+ self
+ .args
+ .split_whitespace()
+ .map(|s| s.to_string())
+ .collect::<Vec<_>>(),
+ )
+ } else {
+ assert!(
+ self.args.is_empty(),
+ "Do not provide args when providing args_vec."
+ );
+ std::borrow::Cow::Borrowed(&self.args_vec)
+ }
+ .iter()
+ .map(|arg| {
+ arg.replace("$TESTDATA", &self.context.testdata_dir.to_string_lossy())
+ })
+ .collect::<Vec<_>>();
+ let (mut reader, writer) = pipe().unwrap();
+ let command_name = self
+ .command_name
+ .as_ref()
+ .cloned()
+ .unwrap_or("deno".to_string());
+ let mut command = if command_name == "deno" {
+ Command::new(deno_exe_path())
+ } else {
+ Command::new(&command_name)
+ };
+ command.env("DENO_DIR", self.context.deno_dir.path());
+
+ println!("command {} {}", command_name, args.join(" "));
+ println!("command cwd {:?}", &cwd);
+ command.args(args.iter());
+ if self.env_clear {
+ command.env_clear();
+ }
+ command.envs({
+ let mut envs = self.context.envs.clone();
+ for (key, value) in &self.envs {
+ envs.insert(key.to_string(), value.to_string());
+ }
+ envs
+ });
+ command.current_dir(cwd);
+ command.stdin(Stdio::piped());
+ let writer_clone = writer.try_clone().unwrap();
+ command.stderr(writer_clone);
+ command.stdout(writer);
+
+ let mut process = command.spawn().unwrap();
+
+ if let Some(input) = &self.stdin {
+ let mut p_stdin = process.stdin.take().unwrap();
+ write!(p_stdin, "{input}").unwrap();
+ }
+
+ // This parent process is still holding its copies of the write ends,
+ // and we have to close them before we read, otherwise the read end
+ // will never report EOF. The Command object owns the writers now,
+ // and dropping it closes them.
+ drop(command);
+
+ let mut actual = String::new();
+ reader.read_to_string(&mut actual).unwrap();
+
+ let status = process.wait().expect("failed to finish process");
+ let exit_code = status.code();
+ #[cfg(unix)]
+ let signal = {
+ use std::os::unix::process::ExitStatusExt;
+ status.signal()
+ };
+ #[cfg(not(unix))]
+ let signal = None;
+
+ actual = strip_ansi_codes(&actual).to_string();
+
+ // deno test's output capturing flushes with a zero-width space in order to
+ // synchronize the output pipes. Occassionally this zero width space
+ // might end up in the output so strip it from the output comparison here.
+ if args.first().map(|s| s.as_str()) == Some("test") {
+ actual = actual.replace('\u{200B}', "");
+ }
+
+ TestCommandOutput {
+ exit_code,
+ signal,
+ text: actual,
+ testdata_dir: self.context.testdata_dir.clone(),
+ asserted_exit_code: RefCell::new(false),
+ asserted_text: RefCell::new(false),
+ _test_context: self.context.clone(),
+ }
+ }
+}
+
+pub struct TestCommandOutput {
+ text: String,
+ exit_code: Option<i32>,
+ signal: Option<i32>,
+ testdata_dir: PathBuf,
+ asserted_text: RefCell<bool>,
+ asserted_exit_code: RefCell<bool>,
+ // keep alive for the duration of the output reference
+ _test_context: TestContext,
+}
+
+impl Drop for TestCommandOutput {
+ fn drop(&mut self) {
+ if std::thread::panicking() {
+ return;
+ }
+ // force the caller to assert these
+ if !*self.asserted_exit_code.borrow() && self.exit_code != Some(0) {
+ panic!(
+ "The non-zero exit code of the command was not asserted: {:?}.",
+ self.exit_code
+ )
+ }
+ if !*self.asserted_text.borrow() && !self.text.is_empty() {
+ println!("OUTPUT\n{}\nOUTPUT", self.text);
+ panic!(concat!(
+ "The non-empty text of the command was not asserted. ",
+ "Call `output.skip_output_check()` to skip if necessary.",
+ ));
+ }
+ }
+}
+
+impl TestCommandOutput {
+ pub fn testdata_dir(&self) -> &PathBuf {
+ &self.testdata_dir
+ }
+
+ pub fn skip_output_check(&self) {
+ *self.asserted_text.borrow_mut() = true;
+ }
+
+ pub fn skip_exit_code_check(&self) {
+ *self.asserted_exit_code.borrow_mut() = true;
+ }
+
+ pub fn exit_code(&self) -> Option<i32> {
+ self.skip_exit_code_check();
+ self.exit_code
+ }
+
+ pub fn signal(&self) -> Option<i32> {
+ self.signal
+ }
+
+ pub fn text(&self) -> &str {
+ self.skip_output_check();
+ &self.text
+ }
+}
diff --git a/test_util/src/lib.rs b/test_util/src/lib.rs
index 2e053f85f..2a22afb4b 100644
--- a/test_util/src/lib.rs
+++ b/test_util/src/lib.rs
@@ -15,7 +15,6 @@ use hyper::Response;
use hyper::StatusCode;
use lazy_static::lazy_static;
use npm::CUSTOM_NPM_PACKAGE_CACHE;
-use os_pipe::pipe;
use pretty_assertions::assert_eq;
use regex::Regex;
use rustls::Certificate;
@@ -54,11 +53,16 @@ use tokio_tungstenite::accept_async;
use url::Url;
pub mod assertions;
+mod builders;
pub mod lsp;
mod npm;
pub mod pty;
mod temp_dir;
+pub use builders::TestCommandBuilder;
+pub use builders::TestCommandOutput;
+pub use builders::TestContext;
+pub use builders::TestContextBuilder;
pub use temp_dir::TempDir;
const PORT: u16 = 4545;
@@ -1948,131 +1952,43 @@ pub struct CheckOutputIntegrationTest<'a> {
}
impl<'a> CheckOutputIntegrationTest<'a> {
- pub fn run(&self) {
- let deno_dir = new_deno_dir(); // keep this alive for the test
- let args = if self.args_vec.is_empty() {
- std::borrow::Cow::Owned(self.args.split_whitespace().collect::<Vec<_>>())
- } else {
- assert!(
- self.args.is_empty(),
- "Do not provide args when providing args_vec."
- );
- std::borrow::Cow::Borrowed(&self.args_vec)
- };
- let testdata_dir = if let Some(temp_copy_dir) = &self.copy_temp_dir {
- let test_data_path = testdata_path().join(temp_copy_dir);
- let temp_copy_dir = deno_dir.path().join(temp_copy_dir);
- std::fs::create_dir_all(&temp_copy_dir).unwrap();
- copy_dir_recursive(&test_data_path, &temp_copy_dir).unwrap();
- deno_dir.path().to_owned()
- } else {
- testdata_path()
- };
- let args = args
- .iter()
- .map(|arg| arg.replace("$TESTDATA", &testdata_dir.to_string_lossy()))
- .collect::<Vec<_>>();
- let deno_exe = deno_exe_path();
- println!("deno_exe path {}", deno_exe.display());
-
- let _http_server_guard = if self.http_server {
- Some(http_server())
- } else {
- None
- };
-
- let (mut reader, writer) = pipe().unwrap();
- let mut command = deno_cmd_with_deno_dir(&deno_dir);
- let cwd = if self.temp_cwd {
- deno_dir.path().to_owned()
- } else if let Some(cwd_) = &self.cwd {
- testdata_dir.join(cwd_)
- } else {
- testdata_dir.clone()
- };
- println!("deno_exe args {}", args.join(" "));
- println!("deno_exe cwd {:?}", &cwd);
- command.args(args.iter());
- if self.env_clear {
- command.env_clear();
+ pub fn output(&self) -> TestCommandOutput {
+ let mut context_builder = TestContextBuilder::default();
+ if self.temp_cwd {
+ context_builder.use_temp_cwd();
}
- command.envs(self.envs.clone());
- command.current_dir(cwd);
- command.stdin(Stdio::piped());
- let writer_clone = writer.try_clone().unwrap();
- command.stderr(writer_clone);
- command.stdout(writer);
-
- let mut process = command.spawn().expect("failed to execute process");
-
- if let Some(input) = self.input {
- let mut p_stdin = process.stdin.take().unwrap();
- write!(p_stdin, "{input}").unwrap();
+ if let Some(dir) = &self.copy_temp_dir {
+ context_builder.use_copy_temp_dir(dir);
+ }
+ if self.http_server {
+ context_builder.use_http_server();
}
- // Very important when using pipes: This parent process is still
- // holding its copies of the write ends, and we have to close them
- // before we read, otherwise the read end will never report EOF. The
- // Command object owns the writers now, and dropping it closes them.
- drop(command);
-
- let mut actual = String::new();
- reader.read_to_string(&mut actual).unwrap();
+ let context = context_builder.build();
- let status = process.wait().expect("failed to finish process");
+ let mut command_builder = context.new_command();
- if let Some(exit_code) = status.code() {
- if self.exit_code != exit_code {
- println!("OUTPUT\n{actual}\nOUTPUT");
- panic!(
- "bad exit code, expected: {:?}, actual: {:?}",
- self.exit_code, exit_code
- );
- }
- } else {
- #[cfg(unix)]
- {
- use std::os::unix::process::ExitStatusExt;
- let signal = status.signal().unwrap();
- println!("OUTPUT\n{actual}\nOUTPUT");
- panic!(
- "process terminated by signal, expected exit code: {:?}, actual signal: {:?}",
- self.exit_code, signal,
- );
- }
- #[cfg(not(unix))]
- {
- println!("OUTPUT\n{actual}\nOUTPUT");
- panic!("process terminated without status code on non unix platform, expected exit code: {:?}", self.exit_code);
- }
+ if !self.args.is_empty() {
+ command_builder.args(self.args);
}
-
- actual = strip_ansi_codes(&actual).to_string();
-
- // deno test's output capturing flushes with a zero-width space in order to
- // synchronize the output pipes. Occassionally this zero width space
- // might end up in the output so strip it from the output comparison here.
- if args.first().map(|s| s.as_str()) == Some("test") {
- actual = actual.replace('\u{200B}', "");
+ if !self.args_vec.is_empty() {
+ command_builder
+ .args_vec(self.args_vec.iter().map(|a| a.to_string()).collect());
}
-
- let expected = if let Some(s) = self.output_str {
- s.to_owned()
- } else if self.output.is_empty() {
- String::new()
- } else {
- let output_path = testdata_dir.join(self.output);
- println!("output path {}", output_path.display());
- std::fs::read_to_string(output_path).expect("cannot read output")
- };
-
- if !expected.contains("[WILDCARD]") {
- assert_eq!(actual, expected)
- } else if !wildcard_match(&expected, &actual) {
- println!("OUTPUT\n{actual}\nOUTPUT");
- println!("EXPECTED\n{expected}\nEXPECTED");
- panic!("pattern match failed");
+ if let Some(input) = &self.input {
+ command_builder.stdin(input);
}
+ for (key, value) in &self.envs {
+ command_builder.env(key, value);
+ }
+ if self.env_clear {
+ command_builder.env_clear();
+ }
+ if let Some(cwd) = &self.cwd {
+ command_builder.cwd(cwd);
+ }
+
+ command_builder.run()
}
}