mirror of
https://github.com/denoland/deno.git
synced 2024-11-28 16:20:57 -05:00
chore: restore pty tests and make them run on the Linux CI (#18424)
1. Rewrites the tests to be more back and forth rather than getting the output all at once (which I believe was causing the hangs on linux and maybe mac) 2. Runs the pty tests on the linux ci. 3. Fixes a bunch of tests that were just wrong. 4. Adds timeouts on the pty tests.
This commit is contained in:
parent
c65149c0a0
commit
6fb6b0c1f3
14 changed files with 1195 additions and 1075 deletions
63
Cargo.lock
generated
63
Cargo.lock
generated
|
@ -12,15 +12,6 @@ dependencies = [
|
||||||
"regex",
|
"regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "addr2line"
|
|
||||||
version = "0.19.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97"
|
|
||||||
dependencies = [
|
|
||||||
"gimli",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "adler"
|
name = "adler"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
|
@ -208,21 +199,6 @@ version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "backtrace"
|
|
||||||
version = "0.3.67"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca"
|
|
||||||
dependencies = [
|
|
||||||
"addr2line",
|
|
||||||
"cc",
|
|
||||||
"cfg-if",
|
|
||||||
"libc",
|
|
||||||
"miniz_oxide 0.6.2",
|
|
||||||
"object",
|
|
||||||
"rustc-demangle",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base16ct"
|
name = "base16ct"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
@ -484,9 +460,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "console_static_text"
|
name = "console_static_text"
|
||||||
version = "0.7.1"
|
version = "0.8.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "953d2c3cf53213a4eccdbe8f2e0b49b5d0f77e87a2a9060117bbf9346f92b64e"
|
checksum = "f4be93df536dfbcbd39ff7c129635da089901116b88bfc29ec1acb9b56f8ff35"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
"vte",
|
"vte",
|
||||||
|
@ -1777,7 +1753,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6"
|
checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"miniz_oxide 0.5.4",
|
"miniz_oxide",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1998,12 +1974,6 @@ dependencies = [
|
||||||
"polyval",
|
"polyval",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "gimli"
|
|
||||||
version = "0.27.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glibc_version"
|
name = "glibc_version"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
|
@ -2724,15 +2694,6 @@ dependencies = [
|
||||||
"adler",
|
"adler",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "miniz_oxide"
|
|
||||||
version = "0.6.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
|
|
||||||
dependencies = [
|
|
||||||
"adler",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "0.8.6"
|
version = "0.8.6"
|
||||||
|
@ -2912,15 +2873,6 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "object"
|
|
||||||
version = "0.30.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.17.1"
|
version = "1.17.1"
|
||||||
|
@ -3588,12 +3540,6 @@ dependencies = [
|
||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustc-demangle"
|
|
||||||
version = "0.1.21"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-hash"
|
name = "rustc-hash"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
@ -4669,13 +4615,14 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-stream",
|
"async-stream",
|
||||||
"atty",
|
"atty",
|
||||||
"backtrace",
|
|
||||||
"base64 0.13.1",
|
"base64 0.13.1",
|
||||||
|
"console_static_text",
|
||||||
"flate2",
|
"flate2",
|
||||||
"futures",
|
"futures",
|
||||||
"hyper",
|
"hyper",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"lsp-types",
|
"lsp-types",
|
||||||
|
"nix",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"os_pipe",
|
"os_pipe",
|
||||||
"parking_lot 0.12.1",
|
"parking_lot 0.12.1",
|
||||||
|
|
|
@ -86,7 +86,7 @@ bencher = "0.1"
|
||||||
bytes = "1.4.0"
|
bytes = "1.4.0"
|
||||||
cache_control = "=0.2.0"
|
cache_control = "=0.2.0"
|
||||||
cbc = { version = "=0.1.2", features = ["alloc"] }
|
cbc = { version = "=0.1.2", features = ["alloc"] }
|
||||||
console_static_text = "=0.7.1"
|
console_static_text = "=0.8.1"
|
||||||
data-url = "=0.2.0"
|
data-url = "=0.2.0"
|
||||||
dlopen = "0.1.8"
|
dlopen = "0.1.8"
|
||||||
encoding_rs = "=0.8.31"
|
encoding_rs = "=0.8.31"
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -6,6 +6,7 @@ use std::io::Read;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::process::Stdio;
|
use std::process::Stdio;
|
||||||
|
use std::time::Duration;
|
||||||
use test_util as util;
|
use test_util as util;
|
||||||
use test_util::TempDir;
|
use test_util::TempDir;
|
||||||
use tokio::task::LocalSet;
|
use tokio::task::LocalSet;
|
||||||
|
@ -570,88 +571,183 @@ itest!(_089_run_allow_list {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn _090_run_permissions_request() {
|
fn _090_run_permissions_request() {
|
||||||
let args = "run --quiet run/090_run_permissions_request.ts";
|
util::with_pty(
|
||||||
use util::PtyData::*;
|
&["run", "--quiet", "run/090_run_permissions_request.ts"],
|
||||||
util::test_pty2(args, vec![
|
|mut console| {
|
||||||
Output("⚠️ ️Deno requests run access to \"ls\". Run again with --allow-run to bypass this prompt.\r\n Allow? [y/n (y = yes allow, n = no deny)]"),
|
console.expect(concat!(
|
||||||
Input("y\n"),
|
"┌ ⚠️ Deno requests run access to \"ls\".\r\n",
|
||||||
Output("⚠️ ️Deno requests run access to \"cat\". Run again with --allow-run to bypass this prompt.\r\n Allow? [y/n (y = yes allow, n = no deny)]"),
|
"├ Requested by `Deno.permissions.query()` API.\r\n",
|
||||||
Input("n\n"),
|
"├ Run again with --allow-run to bypass this prompt.\r\n",
|
||||||
Output("granted\r\n"),
|
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all run permissions)",
|
||||||
Output("prompt\r\n"),
|
));
|
||||||
Output("denied\r\n"),
|
console.write_line_raw("y");
|
||||||
]);
|
console.expect("Granted run access to \"ls\".");
|
||||||
|
console.expect(concat!(
|
||||||
|
"┌ ⚠️ Deno requests run access to \"cat\".\r\n",
|
||||||
|
"├ Requested by `Deno.permissions.query()` API.\r\n",
|
||||||
|
"├ Run again with --allow-run to bypass this prompt.\r\n",
|
||||||
|
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all run permissions)",
|
||||||
|
));
|
||||||
|
console.write_line_raw("n");
|
||||||
|
console.expect("Denied run access to \"cat\".");
|
||||||
|
console.expect("granted");
|
||||||
|
console.expect("denied");
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn _090_run_permissions_request_sync() {
|
fn _090_run_permissions_request_sync() {
|
||||||
let args = "run --quiet run/090_run_permissions_request_sync.ts";
|
util::with_pty(
|
||||||
use util::PtyData::*;
|
&["run", "--quiet", "run/090_run_permissions_request_sync.ts"],
|
||||||
util::test_pty2(args, vec![
|
|mut console| {
|
||||||
Output("⚠️ ️Deno requests run access to \"ls\". Run again with --allow-run to bypass this prompt.\r\n Allow? [y/n (y = yes allow, n = no deny)]"),
|
console.expect(concat!(
|
||||||
Input("y\n"),
|
"┌ ⚠️ Deno requests run access to \"ls\".\r\n",
|
||||||
Output("⚠️ ️Deno requests run access to \"cat\". Run again with --allow-run to bypass this prompt.\r\n Allow? [y/n (y = yes allow, n = no deny)]"),
|
"├ Requested by `Deno.permissions.query()` API.\r\n",
|
||||||
Input("n\n"),
|
"├ Run again with --allow-run to bypass this prompt.\r\n",
|
||||||
Output("granted\r\n"),
|
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all run permissions)",
|
||||||
Output("prompt\r\n"),
|
));
|
||||||
Output("denied\r\n"),
|
console.write_line_raw("y");
|
||||||
]);
|
console.expect("Granted run access to \"ls\".");
|
||||||
|
console.expect(concat!(
|
||||||
|
"┌ ⚠️ Deno requests run access to \"cat\".\r\n",
|
||||||
|
"├ Requested by `Deno.permissions.query()` API.\r\n",
|
||||||
|
"├ Run again with --allow-run to bypass this prompt.\r\n",
|
||||||
|
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all run permissions)",
|
||||||
|
));
|
||||||
|
console.write_line_raw("n");
|
||||||
|
console.expect("Denied run access to \"cat\".");
|
||||||
|
console.expect("granted");
|
||||||
|
console.expect("denied");
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn permissions_prompt_allow_all() {
|
fn permissions_prompt_allow_all() {
|
||||||
let args = "run --quiet run/permissions_prompt_allow_all.ts";
|
util::with_pty(
|
||||||
use util::PtyData::*;
|
&["run", "--quiet", "run/permissions_prompt_allow_all.ts"],
|
||||||
util::test_pty2(args, vec![
|
|mut console| {
|
||||||
// "run" permissions
|
// "run" permissions
|
||||||
Output("┌ ⚠️ Deno requests run access to \"FOO\".\r\n├ Requested by `Deno.permissions.query()` API\r\n ├ Run again with --allow-run to bypass this prompt.\r\n└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all run permissions) >"),
|
console.expect(concat!(
|
||||||
Input("a\n"),
|
"┌ ⚠️ Deno requests run access to \"FOO\".\r\n",
|
||||||
Output("✅ Granted all run access.\r\n"),
|
"├ Requested by `Deno.permissions.query()` API.\r\n",
|
||||||
|
"├ Run again with --allow-run to bypass this prompt.\r\n",
|
||||||
|
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all run permissions)",
|
||||||
|
));
|
||||||
|
console.write_line_raw("A");
|
||||||
|
console.expect("✅ Granted all run access.");
|
||||||
// "read" permissions
|
// "read" permissions
|
||||||
Output("┌ ⚠️ Deno requests read access to \"FOO\".\r\n├ Requested by `Deno.permissions.query()` API\r\n ├ Run again with --allow-read to bypass this prompt.\r\n└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions) >"),
|
console.expect(concat!(
|
||||||
Input("a\n"),
|
"┌ ⚠️ Deno requests read access to \"FOO\".\r\n",
|
||||||
Output("✅ Granted all read access.\r\n"),
|
"├ Requested by `Deno.permissions.query()` API.\r\n",
|
||||||
|
"├ Run again with --allow-read to bypass this prompt.\r\n",
|
||||||
|
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions)",
|
||||||
|
));
|
||||||
|
console.write_line_raw("A");
|
||||||
|
console.expect("✅ Granted all read access.");
|
||||||
// "write" permissions
|
// "write" permissions
|
||||||
Output("┌ ⚠️ Deno requests write access to \"FOO\".\r\n├ Requested by `Deno.permissions.query()` API\r\n ├ Run again with --allow-write to bypass this prompt.\r\n└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all write permissions) >"),
|
console.expect(concat!(
|
||||||
Input("a\n"),
|
"┌ ⚠️ Deno requests write access to \"FOO\".\r\n",
|
||||||
Output("✅ Granted all write access.\r\n"),
|
"├ Requested by `Deno.permissions.query()` API.\r\n",
|
||||||
|
"├ Run again with --allow-write to bypass this prompt.\r\n",
|
||||||
|
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all write permissions)",
|
||||||
|
));
|
||||||
|
console.write_line_raw("A");
|
||||||
|
console.expect("✅ Granted all write access.");
|
||||||
// "net" permissions
|
// "net" permissions
|
||||||
Output("┌ ⚠️ Deno requests net access to \"FOO\".\r\n├ Requested by `Deno.permissions.query()` API\r\n ├ Run again with --allow-net to bypass this prompt.\r\n└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all net permissions) >"),
|
console.expect(concat!(
|
||||||
Input("a\n"),
|
"┌ ⚠️ Deno requests network access to \"foo\".\r\n",
|
||||||
Output("✅ Granted all net access.\r\n"),
|
"├ Requested by `Deno.permissions.query()` API.\r\n",
|
||||||
|
"├ Run again with --allow-net to bypass this prompt.\r\n",
|
||||||
|
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all net permissions)",
|
||||||
|
));
|
||||||
|
console.write_line_raw("A\n");
|
||||||
|
console.expect("✅ Granted all net access.");
|
||||||
// "env" permissions
|
// "env" permissions
|
||||||
Output("┌ ⚠️ Deno requests env access to \"FOO\".\r\n├ Requested by `Deno.permissions.query()` API\r\n ├ Run again with --allow-env to bypass this prompt.\r\n└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all env permissions) >"),
|
console.expect(concat!(
|
||||||
Input("a\n"),
|
"┌ ⚠️ Deno requests env access to \"FOO\".\r\n",
|
||||||
Output("✅ Granted all env access.\r\n"),
|
"├ Requested by `Deno.permissions.query()` API.\r\n",
|
||||||
|
"├ Run again with --allow-env to bypass this prompt.\r\n",
|
||||||
|
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all env permissions)",
|
||||||
|
));
|
||||||
|
console.write_line_raw("A\n");
|
||||||
|
console.expect("✅ Granted all env access.");
|
||||||
// "sys" permissions
|
// "sys" permissions
|
||||||
Output("┌ ⚠️ Deno requests sys access to \"loadavg\".\r\n├ Requested by `Deno.permissions.query()` API\r\n ├ Run again with --allow-sys to bypass this prompt.\r\n└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all sys permissions) >"),
|
console.expect(concat!(
|
||||||
Input("a\n"),
|
"┌ ⚠️ Deno requests sys access to \"loadavg\".\r\n",
|
||||||
Output("✅ Granted all sys access.\r\n"),
|
"├ Requested by `Deno.permissions.query()` API.\r\n",
|
||||||
|
"├ Run again with --allow-sys to bypass this prompt.\r\n",
|
||||||
|
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all sys permissions)",
|
||||||
|
));
|
||||||
|
console.write_line_raw("A\n");
|
||||||
|
console.expect("✅ Granted all sys access.");
|
||||||
// "ffi" permissions
|
// "ffi" permissions
|
||||||
Output("┌ ⚠️ Deno requests ffi access to \"FOO\".\r\n├ Requested by `Deno.permissions.query()` API\r\n ├ Run again with --allow-ffi to bypass this prompt.\r\n└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all ffi permissions) >"),
|
console.expect(concat!(
|
||||||
Input("a\n"),
|
"┌ ⚠️ Deno requests ffi access to \"FOO\".\r\n",
|
||||||
Output("✅ Granted all ffi access.\r\n")
|
"├ Requested by `Deno.permissions.query()` API.\r\n",
|
||||||
]);
|
"├ Run again with --allow-ffi to bypass this prompt.\r\n",
|
||||||
|
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all ffi permissions)",
|
||||||
|
));
|
||||||
|
console.write_line_raw("A\n");
|
||||||
|
console.expect("✅ Granted all ffi access.")
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn permissions_prompt_allow_all_2() {
|
fn permissions_prompt_allow_all_2() {
|
||||||
let args = "run --quiet run/permissions_prompt_allow_all_2.ts";
|
util::with_pty(
|
||||||
use util::PtyData::*;
|
&["run", "--quiet", "run/permissions_prompt_allow_all_2.ts"],
|
||||||
util::test_pty2(args, vec![
|
|mut console| {
|
||||||
// "env" permissions
|
// "env" permissions
|
||||||
Output("┌ ⚠️ Deno requests env access to \"FOO\".\r\n├ Run again with --allow-env to bypass this prompt.\r\n└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all env permissions) >"),
|
console.expect(concat!(
|
||||||
Input("d\n"),
|
"┌ ⚠️ Deno requests env access to \"FOO\".\r\n",
|
||||||
Output("✅ Granted all env access.\r\n"),
|
"├ Run again with --allow-env to bypass this prompt.\r\n",
|
||||||
|
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all env permissions)",
|
||||||
|
));
|
||||||
|
console.write_line_raw("A");
|
||||||
|
console.expect("✅ Granted all env access.");
|
||||||
|
|
||||||
// "sys" permissions
|
// "sys" permissions
|
||||||
Output("┌ ⚠️ Deno requests sys access to \"FOO\".\r\n├ Run again with --allow-sys to bypass this prompt.\r\n└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all sys permissions) >"),
|
console.expect(concat!(
|
||||||
Input("d\n"),
|
"┌ ⚠️ Deno requests sys access to \"loadavg\".\r\n",
|
||||||
Output("✅ Granted all sys access.\r\n"),
|
"├ Requested by `Deno.loadavg()` API.\r\n",
|
||||||
|
"├ Run again with --allow-sys to bypass this prompt.\r\n",
|
||||||
|
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all sys permissions)",
|
||||||
|
));
|
||||||
|
console.write_line_raw("A");
|
||||||
|
console.expect("✅ Granted all sys access.");
|
||||||
|
|
||||||
// "read" permissions
|
// "read" permissions
|
||||||
Output("┌ ⚠️ Deno requests read access to \"FOO\".\r\n├ Run again with --allow-read to bypass this prompt.\r\n└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions) >"),
|
console.expect(concat!(
|
||||||
Input("d\n"),
|
"┌ ⚠️ Deno requests read access to <CWD>.\r\n",
|
||||||
Output("✅ Granted all read access.\r\n"),
|
"├ Requested by `Deno.cwd()` API.\r\n",
|
||||||
]);
|
"├ Run again with --allow-read to bypass this prompt.\r\n",
|
||||||
|
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions)",
|
||||||
|
));
|
||||||
|
console.write_line_raw("A");
|
||||||
|
console.expect("✅ Granted all read access.");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn permissions_prompt_allow_all_lowercase_a() {
|
||||||
|
util::with_pty(
|
||||||
|
&["run", "--quiet", "run/permissions_prompt_allow_all.ts"],
|
||||||
|
|mut console| {
|
||||||
|
// "run" permissions
|
||||||
|
console.expect(concat!(
|
||||||
|
"┌ ⚠️ Deno requests run access to \"FOO\".\r\n",
|
||||||
|
"├ Requested by `Deno.permissions.query()` API.\r\n",
|
||||||
|
"├ Run again with --allow-run to bypass this prompt.\r\n",
|
||||||
|
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all run permissions)",
|
||||||
|
));
|
||||||
|
console.write_line_raw("a");
|
||||||
|
console.expect("Unrecognized option.");
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
itest!(_091_use_define_for_class_fields {
|
itest!(_091_use_define_for_class_fields {
|
||||||
|
@ -2407,58 +2503,102 @@ mod permissions {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn _061_permissions_request() {
|
fn _061_permissions_request() {
|
||||||
let args = "run --quiet run/061_permissions_request.ts";
|
util::with_pty(
|
||||||
use util::PtyData::*;
|
&["run", "--quiet", "run/061_permissions_request.ts"],
|
||||||
util::test_pty2(args, vec![
|
|mut console| {
|
||||||
Output("⚠️ ️Deno requests read access to \"foo\". Run again with --allow-read to bypass this prompt.\r\n Allow? [y/n (y = yes allow, n = no deny)] "),
|
console.expect(concat!(
|
||||||
Input("y\n"),
|
"┌ ⚠️ Deno requests read access to \"foo\".\r\n",
|
||||||
Output("⚠️ ️Deno requests read access to \"bar\". Run again with --allow-read to bypass this prompt.\r\n Allow? [y/n (y = yes allow, n = no deny)]"),
|
"├ Requested by `Deno.permissions.query()` API.\r\n",
|
||||||
Input("n\n"),
|
"├ Run again with --allow-read to bypass this prompt.\r\n",
|
||||||
Output("granted\r\n"),
|
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions)",
|
||||||
Output("prompt\r\n"),
|
));
|
||||||
Output("denied\r\n"),
|
console.write_line_raw("y");
|
||||||
]);
|
console.expect(concat!(
|
||||||
|
"┌ ⚠️ Deno requests read access to \"bar\".\r\n",
|
||||||
|
"├ Requested by `Deno.permissions.query()` API.\r\n",
|
||||||
|
"├ Run again with --allow-read to bypass this prompt.\r\n",
|
||||||
|
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions)",
|
||||||
|
));
|
||||||
|
console.write_line_raw("n");
|
||||||
|
console.expect("granted");
|
||||||
|
console.expect("prompt");
|
||||||
|
console.expect("denied");
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn _061_permissions_request_sync() {
|
fn _061_permissions_request_sync() {
|
||||||
let args = "run --quiet run/061_permissions_request_sync.ts";
|
util::with_pty(
|
||||||
use util::PtyData::*;
|
&["run", "--quiet", "run/061_permissions_request_sync.ts"],
|
||||||
util::test_pty2(args, vec![
|
|mut console| {
|
||||||
Output("⚠️ ️Deno requests read access to \"foo\". Run again with --allow-read to bypass this prompt.\r\n Allow? [y/n (y = yes allow, n = no deny)] "),
|
console.expect(concat!(
|
||||||
Input("y\n"),
|
"┌ ⚠️ Deno requests read access to \"foo\".\r\n",
|
||||||
Output("⚠️ ️Deno requests read access to \"bar\". Run again with --allow-read to bypass this prompt.\r\n Allow? [y/n (y = yes allow, n = no deny)]"),
|
"├ Requested by `Deno.permissions.query()` API.\r\n",
|
||||||
Input("n\n"),
|
"├ Run again with --allow-read to bypass this prompt.\r\n",
|
||||||
Output("granted\r\n"),
|
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions)",
|
||||||
Output("prompt\r\n"),
|
));
|
||||||
Output("denied\r\n"),
|
console.write_line_raw("y");
|
||||||
]);
|
console.expect(concat!(
|
||||||
|
"┌ ⚠️ Deno requests read access to \"bar\".\r\n",
|
||||||
|
"├ Requested by `Deno.permissions.query()` API.\r\n",
|
||||||
|
"├ Run again with --allow-read to bypass this prompt.\r\n",
|
||||||
|
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions)",
|
||||||
|
));
|
||||||
|
console.write_line_raw("n");
|
||||||
|
console.expect("granted");
|
||||||
|
console.expect("prompt");
|
||||||
|
console.expect("denied");
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn _062_permissions_request_global() {
|
fn _062_permissions_request_global() {
|
||||||
let args = "run --quiet run/062_permissions_request_global.ts";
|
util::with_pty(
|
||||||
use util::PtyData::*;
|
&["run", "--quiet", "run/062_permissions_request_global.ts"],
|
||||||
util::test_pty2(args, vec![
|
|mut console| {
|
||||||
Output("⚠️ ️Deno requests read access. Run again with --allow-read to bypass this prompt.\r\n Allow? [y/n (y = yes allow, n = no deny)] "),
|
console.expect(concat!(
|
||||||
Input("y\n"),
|
"┌ ⚠️ Deno requests read access.\r\n",
|
||||||
Output("PermissionStatus { state: \"granted\", onchange: null }\r\n"),
|
"├ Requested by `Deno.permissions.query()` API.\r\n",
|
||||||
Output("PermissionStatus { state: \"granted\", onchange: null }\r\n"),
|
"├ Run again with --allow-read to bypass this prompt.\r\n",
|
||||||
Output("PermissionStatus { state: \"granted\", onchange: null }\r\n"),
|
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions)",
|
||||||
]);
|
));
|
||||||
|
console.write_line_raw("y\n");
|
||||||
|
console
|
||||||
|
.expect("PermissionStatus { state: \"granted\", onchange: null }");
|
||||||
|
console
|
||||||
|
.expect("PermissionStatus { state: \"granted\", onchange: null }");
|
||||||
|
console
|
||||||
|
.expect("PermissionStatus { state: \"granted\", onchange: null }");
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn _062_permissions_request_global_sync() {
|
fn _062_permissions_request_global_sync() {
|
||||||
let args = "run --quiet run/062_permissions_request_global_sync.ts";
|
util::with_pty(
|
||||||
use util::PtyData::*;
|
&[
|
||||||
util::test_pty2(args, vec![
|
"run",
|
||||||
Output("⚠️ ️Deno requests read access. Run again with --allow-read to bypass this prompt.\r\n Allow? [y/n (y = yes allow, n = no deny)] "),
|
"--quiet",
|
||||||
Input("y\n"),
|
"run/062_permissions_request_global_sync.ts",
|
||||||
Output("PermissionStatus { state: \"granted\", onchange: null }\r\n"),
|
],
|
||||||
Output("PermissionStatus { state: \"granted\", onchange: null }\r\n"),
|
|mut console| {
|
||||||
Output("PermissionStatus { state: \"granted\", onchange: null }\r\n"),
|
console.expect(concat!(
|
||||||
]);
|
"┌ ⚠️ Deno requests read access.\r\n",
|
||||||
|
"├ Requested by `Deno.permissions.query()` API.\r\n",
|
||||||
|
"├ Run again with --allow-read to bypass this prompt.\r\n",
|
||||||
|
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions)",
|
||||||
|
));
|
||||||
|
console.write_line_raw("y");
|
||||||
|
console
|
||||||
|
.expect("PermissionStatus { state: \"granted\", onchange: null }");
|
||||||
|
console
|
||||||
|
.expect("PermissionStatus { state: \"granted\", onchange: null }");
|
||||||
|
console
|
||||||
|
.expect("PermissionStatus { state: \"granted\", onchange: null }");
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
itest!(_063_permissions_revoke {
|
itest!(_063_permissions_revoke {
|
||||||
|
@ -2483,44 +2623,42 @@ mod permissions {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn _066_prompt() {
|
fn _066_prompt() {
|
||||||
let args = "run --quiet --unstable run/066_prompt.ts";
|
util::with_pty(
|
||||||
use util::PtyData::*;
|
&["run", "--quiet", "--unstable", "run/066_prompt.ts"],
|
||||||
util::test_pty2(
|
|mut console| {
|
||||||
args,
|
console.expect("What is your name? [Jane Doe] ");
|
||||||
vec![
|
console.write_line_raw("John Doe");
|
||||||
Output("What is your name? [Jane Doe] "),
|
console.expect("Your name is John Doe.");
|
||||||
Input("John Doe\n"),
|
console.expect("What is your name? [Jane Doe] ");
|
||||||
Output("Your name is John Doe.\r\n"),
|
console.write_line_raw("");
|
||||||
Output("What is your name? [Jane Doe] "),
|
console.expect("Your name is Jane Doe.");
|
||||||
Input("\n"),
|
console.expect("Prompt ");
|
||||||
Output("Your name is Jane Doe.\r\n"),
|
console.write_line_raw("foo");
|
||||||
Output("Prompt "),
|
console.expect("Your input is foo.");
|
||||||
Input("foo\n"),
|
console.expect("Question 0 [y/N] ");
|
||||||
Output("Your input is foo.\r\n"),
|
console.write_line_raw("Y");
|
||||||
Output("Question 0 [y/N] "),
|
console.expect("Your answer is true");
|
||||||
Input("Y\n"),
|
console.expect("Question 1 [y/N] ");
|
||||||
Output("Your answer is true\r\n"),
|
console.write_line_raw("N");
|
||||||
Output("Question 1 [y/N] "),
|
console.expect("Your answer is false");
|
||||||
Input("N\n"),
|
console.expect("Question 2 [y/N] ");
|
||||||
Output("Your answer is false\r\n"),
|
console.write_line_raw("yes");
|
||||||
Output("Question 2 [y/N] "),
|
console.expect("Your answer is false");
|
||||||
Input("yes\n"),
|
console.expect("Confirm [y/N] ");
|
||||||
Output("Your answer is false\r\n"),
|
console.write_line("");
|
||||||
Output("Confirm [y/N] "),
|
console.expect("Your answer is false");
|
||||||
Input("\n"),
|
console.expect("What is Windows EOL? ");
|
||||||
Output("Your answer is false\r\n"),
|
console.write_line("windows");
|
||||||
Output("What is Windows EOL? "),
|
console.expect("Your answer is \"windows\"");
|
||||||
Input("windows\n"),
|
console.expect("Hi [Enter] ");
|
||||||
Output("Your answer is \"windows\"\r\n"),
|
console.write_line("");
|
||||||
Output("Hi [Enter] "),
|
console.expect("Alert [Enter] ");
|
||||||
Input("\n"),
|
console.write_line("");
|
||||||
Output("Alert [Enter] "),
|
console.expect("The end of test");
|
||||||
Input("\n"),
|
console.expect("What is EOF? ");
|
||||||
Output("The end of test\r\n"),
|
console.write_line("");
|
||||||
Output("What is EOF? "),
|
console.expect("Your answer is null");
|
||||||
Input("\n"),
|
},
|
||||||
Output("Your answer is null\r\n"),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2577,19 +2715,28 @@ itest!(byte_order_mark {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn issue9750() {
|
fn issue9750() {
|
||||||
use util::PtyData::*;
|
util::with_pty(&["run", "--prompt", "run/issue9750.js"], |mut console| {
|
||||||
util::test_pty2(
|
console.expect("Enter 'yy':");
|
||||||
"run --prompt run/issue9750.js",
|
console.write_line_raw("yy");
|
||||||
vec![
|
console.expect(concat!(
|
||||||
Output("Enter 'yy':\r\n"),
|
"┌ ⚠️ Deno requests env access.\r\n",
|
||||||
Input("yy\n"),
|
"├ Requested by `Deno.permissions.query()` API.\r\n",
|
||||||
Output("⚠️ ️Deno requests env access. Run again with --allow-env to bypass this prompt.\r\n Allow? [y/n (y = yes allow, n = no deny)]"),
|
"├ Run again with --allow-env to bypass this prompt.\r\n",
|
||||||
Input("n\n"),
|
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all env permissions)",
|
||||||
Output("⚠️ ️Deno requests env access to \"SECRET\". Run again with --allow-env to bypass this prompt.\r\n Allow? [y/n (y = yes allow, n = no deny)]"),
|
));
|
||||||
Input("n\n"),
|
console.write_line_raw("n");
|
||||||
Output("error: Uncaught (in promise) PermissionDenied: Requires env access to \"SECRET\", run again with the --allow-env flag\r\n"),
|
console.expect("Denied env access.");
|
||||||
],
|
console.expect(concat!(
|
||||||
);
|
"┌ ⚠️ Deno requests env access to \"SECRET\".\r\n",
|
||||||
|
"├ Run again with --allow-env to bypass this prompt.\r\n",
|
||||||
|
"└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all env permissions)",
|
||||||
|
));
|
||||||
|
console.write_line_raw("n");
|
||||||
|
console.expect_all(&[
|
||||||
|
"Denied env access to \"SECRET\".",
|
||||||
|
"PermissionDenied: Requires env access to \"SECRET\", run again with the --allow-env flag",
|
||||||
|
]);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Regression test for https://github.com/denoland/deno/issues/11451.
|
// Regression test for https://github.com/denoland/deno/issues/11451.
|
||||||
|
@ -4100,87 +4247,94 @@ itest!(permission_args_quiet {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Regression test for https://github.com/denoland/deno/issues/16772
|
// Regression test for https://github.com/denoland/deno/issues/16772
|
||||||
#[ignore]
|
|
||||||
#[test]
|
#[test]
|
||||||
|
// todo(dsherret): getting a dns error on windows for some reason
|
||||||
|
#[cfg(unix)]
|
||||||
fn file_fetcher_preserves_permissions() {
|
fn file_fetcher_preserves_permissions() {
|
||||||
let _guard = util::http_server();
|
let _guard = util::http_server();
|
||||||
util::with_pty(&["repl"], |mut console| {
|
util::with_pty(&["repl", "--quiet"], |mut console| {
|
||||||
console.write_text(
|
console.write_line(
|
||||||
"const a = import('http://127.0.0.1:4545/run/019_media_types.ts');",
|
"const a = await import('http://localhost:4545/run/019_media_types.ts');",
|
||||||
);
|
);
|
||||||
console.write_text("y");
|
console.expect("Allow?");
|
||||||
console.write_line("");
|
console.write_line_raw("y");
|
||||||
console.write_line("close();");
|
console.expect_all(&["success", "true"]);
|
||||||
let output = console.read_all_output();
|
|
||||||
assert_contains!(output, "success");
|
|
||||||
assert_contains!(output, "true");
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[ignore]
|
|
||||||
#[test]
|
#[test]
|
||||||
fn stdio_streams_are_locked_in_permission_prompt() {
|
fn stdio_streams_are_locked_in_permission_prompt() {
|
||||||
let _guard = util::http_server();
|
let context = TestContextBuilder::new()
|
||||||
util::with_pty(&[
|
.use_http_server()
|
||||||
"repl",
|
.use_copy_temp_dir("run/stdio_streams_are_locked_in_permission_prompt")
|
||||||
"--allow-read=run/stdio_streams_are_locked_in_permission_prompt/worker.js,run/stdio_streams_are_locked_in_permission_prompt/text.txt"
|
.build();
|
||||||
], |mut console| {
|
context
|
||||||
console.write_line(
|
.new_command()
|
||||||
r#"new Worker(`${Deno.cwd()}/run/stdio_streams_are_locked_in_permissions_prompt/worker.js`, { type: "module" });
|
.args("repl --allow-read")
|
||||||
await Deno.writeTextFile("./run/stdio_streams_are_locked_in_permissions_prompt/text.txt", "some code");"#,
|
.with_pty(|mut console| {
|
||||||
);
|
console.write_line(r#"const url = "file://" + Deno.cwd().replace("\\", "/") + "/run/stdio_streams_are_locked_in_permission_prompt/worker.js";"#);
|
||||||
console.write_line("y");
|
console.expect("undefined");
|
||||||
console.write_line("close();");
|
// ensure this file exists
|
||||||
let output = console.read_all_output();
|
console.write_line(r#"const _file = Deno.readTextFileSync("./run/stdio_streams_are_locked_in_permission_prompt/worker.js");"#);
|
||||||
|
console.expect("undefined");
|
||||||
|
console.write_line(r#"new Worker(url, { type: "module" }); await Deno.writeTextFile("./text.txt", "some code");"#);
|
||||||
|
console.expect("Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all write permissions)");
|
||||||
|
std::thread::sleep(Duration::from_millis(50)); // give the other thread some time to output
|
||||||
|
console.write_line_raw("invalid");
|
||||||
|
console.expect("Unrecognized option.");
|
||||||
|
console.write_line_raw("y");
|
||||||
|
console.expect("Granted write access to");
|
||||||
|
|
||||||
let expected_output = r#"\x1b[1;1H\x1b[0JAre you sure you want to continue?"#;
|
// this output should now be shown below and not above
|
||||||
assert_eq!(output, expected_output);
|
let expected_output = r#"Are you sure you want to continue?"#;
|
||||||
|
console.expect(expected_output);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore]
|
|
||||||
fn permission_prompt_strips_ansi_codes_and_control_chars() {
|
fn permission_prompt_strips_ansi_codes_and_control_chars() {
|
||||||
let _guard = util::http_server();
|
let _guard = util::http_server();
|
||||||
util::with_pty(&["repl"], |mut console| {
|
util::with_pty(&["repl"], |mut console| {
|
||||||
console.write_line(
|
console.write_line(
|
||||||
r#"Deno.permissions.request({ name: "env", variable: "\rDo you like ice cream? y/n" });"#
|
r#"Deno.permissions.request({ name: "env", variable: "\rDo you like ice cream? y/n" });"#
|
||||||
);
|
);
|
||||||
console.write_line("close();");
|
console.expect(
|
||||||
let output = console.read_all_output();
|
"┌ ⚠️ Deno requests env access to \"Do you like ice cream? y/n\".",
|
||||||
|
)
|
||||||
assert!(output.contains(
|
|
||||||
"┌ ⚠️ Deno requests env access to \"Do you like ice cream? y/n\"."
|
|
||||||
));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
util::with_pty(&["repl"], |mut console| {
|
util::with_pty(&["repl"], |mut console| {
|
||||||
console.write_line(
|
console.write_line_raw(r#"const boldANSI = "\u001b[1m";"#);
|
||||||
r#"
|
console.expect("undefined");
|
||||||
const boldANSI = "\u001b[1m" // bold
|
console.write_line_raw(r#"const unboldANSI = "\u001b[22m";"#);
|
||||||
const unboldANSI = "\u001b[22m" // unbold
|
console.expect("undefined");
|
||||||
|
console.write_line_raw(r#"const prompt = `┌ ⚠️ ${boldANSI}Deno requests run access to "echo"${unboldANSI}\n ├ Requested by \`Deno.Command().output()`"#);
|
||||||
|
console.expect("undefined");
|
||||||
|
console.write_line_raw(r#"const moveANSIUp = "\u001b[1A";"#);
|
||||||
|
console.expect("undefined");
|
||||||
|
console.write_line_raw(r#"const clearANSI = "\u001b[2K";"#);
|
||||||
|
console.expect("undefined");
|
||||||
|
console.write_line_raw(r#"const moveANSIStart = "\u001b[1000D";"#);
|
||||||
|
console.expect("undefined");
|
||||||
|
|
||||||
const prompt = `┌ ⚠️ ${boldANSI}Deno requests run access to "echo"${unboldANSI}
|
console.write_line_raw(
|
||||||
├ Requested by \`Deno.Command().output()`
|
r#"Deno[Deno.internal].core.ops.op_spawn_child({
|
||||||
|
|
||||||
const moveANSIUp = "\u001b[1A" // moves to the start of the line
|
|
||||||
const clearANSI = "\u001b[2K" // clears the line
|
|
||||||
const moveANSIStart = "\u001b[1000D" // moves to the start of the line
|
|
||||||
|
|
||||||
Deno[Object.getOwnPropertySymbols(Deno)[0]].core.ops.op_spawn_child({
|
|
||||||
cmd: "cat",
|
cmd: "cat",
|
||||||
args: ["/etc/passwd"],
|
args: ["file.txt"],
|
||||||
clearEnv: false,
|
clearEnv: false,
|
||||||
|
cwd: undefined,
|
||||||
env: [],
|
env: [],
|
||||||
|
uid: undefined,
|
||||||
|
gid: undefined,
|
||||||
stdin: "null",
|
stdin: "null",
|
||||||
stdout: "inherit",
|
stdout: "inherit",
|
||||||
stderr: "piped"
|
stderr: "piped",
|
||||||
|
signal: undefined,
|
||||||
|
windowsRawArguments: false,
|
||||||
}, moveANSIUp + clearANSI + moveANSIStart + prompt)"#,
|
}, moveANSIUp + clearANSI + moveANSIStart + prompt)"#,
|
||||||
);
|
);
|
||||||
console.write_line("close();");
|
|
||||||
let output = console.read_all_output();
|
|
||||||
|
|
||||||
assert!(output.contains(r#"┌ ⚠️ Deno requests run access to "cat""#));
|
console.expect(r#"┌ ⚠️ Deno requests run access to "cat""#);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,9 +53,12 @@ itest!(task_non_existent {
|
||||||
#[test]
|
#[test]
|
||||||
fn task_emoji() {
|
fn task_emoji() {
|
||||||
// this bug only appears when using a pty/tty
|
// this bug only appears when using a pty/tty
|
||||||
let args = "task --config task/deno_json/deno.json echo_emoji";
|
test_util::with_pty(
|
||||||
use test_util::PtyData::*;
|
&["task", "--config", "task/deno_json/deno.json", "echo_emoji"],
|
||||||
test_util::test_pty2(args, vec![Output("Task echo_emoji echo 🔥\r\n🔥")]);
|
|mut console| {
|
||||||
|
console.expect("Task echo_emoji echo 🔥\r\n🔥");
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
itest!(task_boolean_logic {
|
itest!(task_boolean_logic {
|
||||||
|
|
|
@ -446,6 +446,8 @@ itest!(parallel_output {
|
||||||
});
|
});
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
// todo(#18480): re-enable
|
||||||
|
#[ignore]
|
||||||
fn sigint_with_hanging_test() {
|
fn sigint_with_hanging_test() {
|
||||||
util::with_pty(
|
util::with_pty(
|
||||||
&[
|
&[
|
||||||
|
@ -457,9 +459,10 @@ fn sigint_with_hanging_test() {
|
||||||
|mut console| {
|
|mut console| {
|
||||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||||
console.write_line("\x03");
|
console.write_line("\x03");
|
||||||
|
let text = console.read_until("hanging_test.ts:10:15");
|
||||||
wildcard_match(
|
wildcard_match(
|
||||||
include_str!("../testdata/test/sigint_with_hanging_test.out"),
|
include_str!("../testdata/test/sigint_with_hanging_test.out"),
|
||||||
&console.read_all_output(),
|
&text,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
\x1B[2J\x1B[1;1H
|
|
|
@ -249,17 +249,26 @@ fn validate(input: &str) -> ValidationResult {
|
||||||
let mut in_template = false;
|
let mut in_template = false;
|
||||||
let mut div_token_count_on_current_line = 0;
|
let mut div_token_count_on_current_line = 0;
|
||||||
let mut last_line_index = 0;
|
let mut last_line_index = 0;
|
||||||
|
let mut queued_validation_error = None;
|
||||||
|
let tokens = deno_ast::lex(input, deno_ast::MediaType::TypeScript)
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|item| match item.inner {
|
||||||
|
deno_ast::TokenOrComment::Token(token) => Some((token, item.range)),
|
||||||
|
deno_ast::TokenOrComment::Comment { .. } => None,
|
||||||
|
});
|
||||||
|
|
||||||
for item in deno_ast::lex(input, deno_ast::MediaType::TypeScript) {
|
for (token, range) in tokens {
|
||||||
let current_line_index = line_info.line_index(item.range.start);
|
let current_line_index = line_info.line_index(range.start);
|
||||||
if current_line_index != last_line_index {
|
if current_line_index != last_line_index {
|
||||||
div_token_count_on_current_line = 0;
|
div_token_count_on_current_line = 0;
|
||||||
last_line_index = current_line_index;
|
last_line_index = current_line_index;
|
||||||
|
|
||||||
|
if let Some(error) = queued_validation_error {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if let deno_ast::TokenOrComment::Token(token) = item.inner {
|
|
||||||
match token {
|
match token {
|
||||||
Token::BinOp(BinOpToken::Div)
|
Token::BinOp(BinOpToken::Div) | Token::AssignOp(AssignOp::DivAssign) => {
|
||||||
| Token::AssignOp(AssignOp::DivAssign) => {
|
|
||||||
// it's too complicated to write code to detect regular expression literals
|
// it's too complicated to write code to detect regular expression literals
|
||||||
// which are no longer tokenized, so if a `/` or `/=` happens twice on the same
|
// which are no longer tokenized, so if a `/` or `/=` happens twice on the same
|
||||||
// line, then we bail
|
// line, then we bail
|
||||||
|
@ -269,10 +278,9 @@ fn validate(input: &str) -> ValidationResult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Token::BackQuote => in_template = !in_template,
|
Token::BackQuote => in_template = !in_template,
|
||||||
Token::LParen
|
Token::LParen | Token::LBracket | Token::LBrace | Token::DollarLBrace => {
|
||||||
| Token::LBracket
|
stack.push(token)
|
||||||
| Token::LBrace
|
}
|
||||||
| Token::DollarLBrace => stack.push(token),
|
|
||||||
Token::RParen | Token::RBracket | Token::RBrace => {
|
Token::RParen | Token::RBracket | Token::RBrace => {
|
||||||
match (stack.pop(), token) {
|
match (stack.pop(), token) {
|
||||||
(Some(Token::LParen), Token::RParen)
|
(Some(Token::LParen), Token::RParen)
|
||||||
|
@ -280,9 +288,10 @@ fn validate(input: &str) -> ValidationResult {
|
||||||
| (Some(Token::LBrace), Token::RBrace)
|
| (Some(Token::LBrace), Token::RBrace)
|
||||||
| (Some(Token::DollarLBrace), Token::RBrace) => {}
|
| (Some(Token::DollarLBrace), Token::RBrace) => {}
|
||||||
(Some(left), _) => {
|
(Some(left), _) => {
|
||||||
return ValidationResult::Invalid(Some(format!(
|
// queue up a validation error to surface once we've finished examininig the current line
|
||||||
"Mismatched pairs: {left:?} is not properly closed"
|
queued_validation_error = Some(ValidationResult::Invalid(Some(
|
||||||
)))
|
format!("Mismatched pairs: {left:?} is not properly closed"),
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
(None, _) => {
|
(None, _) => {
|
||||||
// While technically invalid when unpaired, it should be V8's task to output error instead.
|
// While technically invalid when unpaired, it should be V8's task to output error instead.
|
||||||
|
@ -305,13 +314,14 @@ fn validate(input: &str) -> ValidationResult {
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if !stack.is_empty() || in_template {
|
|
||||||
return ValidationResult::Incomplete;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if let Some(error) = queued_validation_error {
|
||||||
|
error
|
||||||
|
} else if !stack.is_empty() || in_template {
|
||||||
|
ValidationResult::Incomplete
|
||||||
|
} else {
|
||||||
ValidationResult::Valid(None)
|
ValidationResult::Valid(None)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Highlighter for EditorHelper {
|
impl Highlighter for EditorHelper {
|
||||||
|
|
|
@ -37,12 +37,15 @@ function prompt(message = "Prompt", defaultValue) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
core.print(`${message} `, false);
|
|
||||||
|
|
||||||
if (defaultValue) {
|
if (defaultValue) {
|
||||||
core.print(`[${defaultValue}] `, false);
|
message += ` [${defaultValue}]`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message += " ";
|
||||||
|
|
||||||
|
// output in one shot to make the tests more reliable
|
||||||
|
core.print(message, false);
|
||||||
|
|
||||||
return readLineFromStdinSync() || defaultValue;
|
return readLineFromStdinSync() || defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,11 @@ use crate::colors;
|
||||||
use deno_core::error::AnyError;
|
use deno_core::error::AnyError;
|
||||||
use deno_core::parking_lot::Mutex;
|
use deno_core::parking_lot::Mutex;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
/// Helper function to strip ansi codes and ASCII control characters.
|
/// Helper function to strip ansi codes and ASCII control characters.
|
||||||
fn strip_ansi_codes_and_ascii_control(s: &str) -> std::borrow::Cow<str> {
|
fn strip_ansi_codes_and_ascii_control(s: &str) -> std::borrow::Cow<str> {
|
||||||
console_static_text::strip_ansi_codes(s)
|
console_static_text::ansi::strip_ansi_codes(s)
|
||||||
.chars()
|
.chars()
|
||||||
.filter(|c| !c.is_ascii_control())
|
.filter(|c| !c.is_ascii_control())
|
||||||
.collect()
|
.collect()
|
||||||
|
@ -221,17 +222,25 @@ impl PermissionPrompter for TtyPrompter {
|
||||||
} else {
|
} else {
|
||||||
"[y/n] (y = yes, allow; n = no, deny)".to_string()
|
"[y/n] (y = yes, allow; n = no, deny)".to_string()
|
||||||
};
|
};
|
||||||
eprint!("┌ {PERMISSION_EMOJI} ");
|
|
||||||
eprint!("{}", colors::bold("Deno requests "));
|
// output everything in one shot to make the tests more reliable
|
||||||
eprint!("{}", colors::bold(message.clone()));
|
{
|
||||||
eprintln!("{}", colors::bold("."));
|
let mut output = String::new();
|
||||||
|
write!(&mut output, "┌ {PERMISSION_EMOJI} ").unwrap();
|
||||||
|
write!(&mut output, "{}", colors::bold("Deno requests ")).unwrap();
|
||||||
|
write!(&mut output, "{}", colors::bold(message.clone())).unwrap();
|
||||||
|
writeln!(&mut output, "{}", colors::bold(".")).unwrap();
|
||||||
if let Some(api_name) = api_name.clone() {
|
if let Some(api_name) = api_name.clone() {
|
||||||
eprintln!("├ Requested by `{api_name}` API");
|
writeln!(&mut output, "├ Requested by `{api_name}` API.").unwrap();
|
||||||
}
|
}
|
||||||
let msg = format!("Run again with --allow-{name} to bypass this prompt.");
|
let msg = format!("Run again with --allow-{name} to bypass this prompt.");
|
||||||
eprintln!("├ {}", colors::italic(&msg));
|
writeln!(&mut output, "├ {}", colors::italic(&msg)).unwrap();
|
||||||
eprint!("└ {}", colors::bold("Allow?"));
|
write!(&mut output, "└ {}", colors::bold("Allow?")).unwrap();
|
||||||
eprint!(" {opts} > ");
|
write!(&mut output, " {opts} > ").unwrap();
|
||||||
|
|
||||||
|
eprint!("{}", output);
|
||||||
|
}
|
||||||
|
|
||||||
let value = loop {
|
let value = loop {
|
||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
let stdin = std::io::stdin();
|
let stdin = std::io::stdin();
|
||||||
|
|
|
@ -17,13 +17,14 @@ path = "src/test_server.rs"
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
async-stream = "0.3.3"
|
async-stream = "0.3.3"
|
||||||
atty.workspace = true
|
atty.workspace = true
|
||||||
backtrace = "0.3.67"
|
|
||||||
base64.workspace = true
|
base64.workspace = true
|
||||||
|
console_static_text.workspace = true
|
||||||
flate2.workspace = true
|
flate2.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
hyper = { workspace = true, features = ["server", "http1", "http2", "runtime"] }
|
hyper = { workspace = true, features = ["server", "http1", "http2", "runtime"] }
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
lsp-types.workspace = true
|
lsp-types.workspace = true
|
||||||
|
nix.workspace = true
|
||||||
once_cell.workspace = true
|
once_cell.workspace = true
|
||||||
os_pipe.workspace = true
|
os_pipe.workspace = true
|
||||||
parking_lot.workspace = true
|
parking_lot.workspace = true
|
||||||
|
|
|
@ -10,7 +10,6 @@ use std::process::Command;
|
||||||
use std::process::Stdio;
|
use std::process::Stdio;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use backtrace::Backtrace;
|
|
||||||
use os_pipe::pipe;
|
use os_pipe::pipe;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
@ -20,6 +19,7 @@ use crate::env_vars_for_npm_tests_no_sync_download;
|
||||||
use crate::http_server;
|
use crate::http_server;
|
||||||
use crate::lsp::LspClientBuilder;
|
use crate::lsp::LspClientBuilder;
|
||||||
use crate::new_deno_dir;
|
use crate::new_deno_dir;
|
||||||
|
use crate::pty::Pty;
|
||||||
use crate::strip_ansi_codes;
|
use crate::strip_ansi_codes;
|
||||||
use crate::testdata_path;
|
use crate::testdata_path;
|
||||||
use crate::wildcard_match;
|
use crate::wildcard_match;
|
||||||
|
@ -268,34 +268,29 @@ impl TestCommandBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&self) -> TestCommandOutput {
|
fn build_cwd(&self) -> PathBuf {
|
||||||
fn read_pipe_to_string(mut pipe: os_pipe::PipeReader) -> String {
|
|
||||||
let mut output = String::new();
|
|
||||||
pipe.read_to_string(&mut output).unwrap();
|
|
||||||
output
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sanitize_output(text: String, args: &[String]) -> String {
|
|
||||||
let mut text = strip_ansi_codes(&text).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") {
|
|
||||||
text = text.replace('\u{200B}', "");
|
|
||||||
}
|
|
||||||
text
|
|
||||||
}
|
|
||||||
|
|
||||||
let cwd = self.cwd.as_ref().or(self.context.cwd.as_ref());
|
let cwd = self.cwd.as_ref().or(self.context.cwd.as_ref());
|
||||||
let cwd = if self.context.use_temp_cwd {
|
if self.context.use_temp_cwd {
|
||||||
assert!(cwd.is_none());
|
assert!(cwd.is_none());
|
||||||
self.context.temp_dir.path().to_owned()
|
self.context.temp_dir.path().to_owned()
|
||||||
} else if let Some(cwd_) = cwd {
|
} else if let Some(cwd_) = cwd {
|
||||||
self.context.testdata_dir.join(cwd_)
|
self.context.testdata_dir.join(cwd_)
|
||||||
} else {
|
} else {
|
||||||
self.context.testdata_dir.clone()
|
self.context.testdata_dir.clone()
|
||||||
};
|
}
|
||||||
let args = if self.args_vec.is_empty() {
|
}
|
||||||
|
|
||||||
|
fn build_command_path(&self) -> PathBuf {
|
||||||
|
let command_name = &self.command_name;
|
||||||
|
if command_name == "deno" {
|
||||||
|
deno_exe_path()
|
||||||
|
} else {
|
||||||
|
PathBuf::from(command_name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_args(&self) -> Vec<String> {
|
||||||
|
if self.args_vec.is_empty() {
|
||||||
std::borrow::Cow::Owned(
|
std::borrow::Cow::Owned(
|
||||||
self
|
self
|
||||||
.args
|
.args
|
||||||
|
@ -314,21 +309,58 @@ impl TestCommandBuilder {
|
||||||
.map(|arg| {
|
.map(|arg| {
|
||||||
arg.replace("$TESTDATA", &self.context.testdata_dir.to_string_lossy())
|
arg.replace("$TESTDATA", &self.context.testdata_dir.to_string_lossy())
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>()
|
||||||
let command_name = &self.command_name;
|
}
|
||||||
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(" "));
|
pub fn with_pty(&self, mut action: impl FnMut(Pty)) {
|
||||||
|
if !Pty::is_supported() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let args = self.build_args();
|
||||||
|
let args = args.iter().map(|s| s.as_str()).collect::<Vec<_>>();
|
||||||
|
let mut envs = self.envs.clone();
|
||||||
|
if !envs.contains_key("NO_COLOR") {
|
||||||
|
// set this by default for pty tests
|
||||||
|
envs.insert("NO_COLOR".to_string(), "1".to_string());
|
||||||
|
}
|
||||||
|
action(Pty::new(
|
||||||
|
&self.build_command_path(),
|
||||||
|
&args,
|
||||||
|
&self.build_cwd(),
|
||||||
|
Some(envs),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&self) -> TestCommandOutput {
|
||||||
|
fn read_pipe_to_string(mut pipe: os_pipe::PipeReader) -> String {
|
||||||
|
let mut output = String::new();
|
||||||
|
pipe.read_to_string(&mut output).unwrap();
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sanitize_output(text: String, args: &[String]) -> String {
|
||||||
|
let mut text = strip_ansi_codes(&text).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") {
|
||||||
|
text = text.replace('\u{200B}', "");
|
||||||
|
}
|
||||||
|
text
|
||||||
|
}
|
||||||
|
|
||||||
|
let cwd = self.build_cwd();
|
||||||
|
let args = self.build_args();
|
||||||
|
let mut command = Command::new(self.build_command_path());
|
||||||
|
|
||||||
|
println!("command {} {}", self.command_name, args.join(" "));
|
||||||
println!("command cwd {:?}", &cwd);
|
println!("command cwd {:?}", &cwd);
|
||||||
command.args(args.iter());
|
command.args(args.iter());
|
||||||
if self.env_clear {
|
if self.env_clear {
|
||||||
command.env_clear();
|
command.env_clear();
|
||||||
}
|
}
|
||||||
|
command.env("DENO_DIR", self.context.deno_dir.path());
|
||||||
command.envs({
|
command.envs({
|
||||||
let mut envs = self.context.envs.clone();
|
let mut envs = self.context.envs.clone();
|
||||||
for (key, value) in &self.envs {
|
for (key, value) in &self.envs {
|
||||||
|
@ -423,13 +455,10 @@ impl Drop for TestCommandOutput {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
fn panic_unasserted_output(text: &str) {
|
fn panic_unasserted_output(text: &str) {
|
||||||
println!("OUTPUT\n{text}\nOUTPUT");
|
println!("OUTPUT\n{text}\nOUTPUT");
|
||||||
panic!(
|
panic!(concat!(
|
||||||
concat!(
|
"The non-empty text of the command was not asserted. ",
|
||||||
"The non-empty text of the command was not asserted at {}. ",
|
|
||||||
"Call `output.skip_output_check()` to skip if necessary.",
|
"Call `output.skip_output_check()` to skip if necessary.",
|
||||||
),
|
),);
|
||||||
failed_position()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if std::thread::panicking() {
|
if std::thread::panicking() {
|
||||||
|
@ -438,9 +467,8 @@ impl Drop for TestCommandOutput {
|
||||||
// force the caller to assert these
|
// force the caller to assert these
|
||||||
if !*self.asserted_exit_code.borrow() && self.exit_code != Some(0) {
|
if !*self.asserted_exit_code.borrow() && self.exit_code != Some(0) {
|
||||||
panic!(
|
panic!(
|
||||||
"The non-zero exit code of the command was not asserted: {:?} at {}.",
|
"The non-zero exit code of the command was not asserted: {:?}",
|
||||||
self.exit_code,
|
self.exit_code,
|
||||||
failed_position(),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -511,6 +539,7 @@ impl TestCommandOutput {
|
||||||
.expect("call .split_output() on the builder")
|
.expect("call .split_output() on the builder")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
pub fn assert_exit_code(&self, expected_exit_code: i32) -> &Self {
|
pub fn assert_exit_code(&self, expected_exit_code: i32) -> &Self {
|
||||||
let actual_exit_code = self.exit_code();
|
let actual_exit_code = self.exit_code();
|
||||||
|
|
||||||
|
@ -518,26 +547,22 @@ impl TestCommandOutput {
|
||||||
if *exit_code != expected_exit_code {
|
if *exit_code != expected_exit_code {
|
||||||
self.print_output();
|
self.print_output();
|
||||||
panic!(
|
panic!(
|
||||||
"bad exit code, expected: {:?}, actual: {:?} at {}",
|
"bad exit code, expected: {:?}, actual: {:?}",
|
||||||
expected_exit_code,
|
expected_exit_code, exit_code,
|
||||||
exit_code,
|
|
||||||
failed_position(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.print_output();
|
self.print_output();
|
||||||
if let Some(signal) = self.signal() {
|
if let Some(signal) = self.signal() {
|
||||||
panic!(
|
panic!(
|
||||||
"process terminated by signal, expected exit code: {:?}, actual signal: {:?} at {}",
|
"process terminated by signal, expected exit code: {:?}, actual signal: {:?}",
|
||||||
actual_exit_code,
|
actual_exit_code,
|
||||||
signal,
|
signal,
|
||||||
failed_position(),
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
panic!(
|
panic!(
|
||||||
"process terminated without status code on non unix platform, expected exit code: {:?} at {}",
|
"process terminated without status code on non unix platform, expected exit code: {:?}",
|
||||||
actual_exit_code,
|
actual_exit_code,
|
||||||
failed_position(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -554,14 +579,17 @@ impl TestCommandOutput {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
pub fn assert_matches_text(&self, expected_text: impl AsRef<str>) -> &Self {
|
pub fn assert_matches_text(&self, expected_text: impl AsRef<str>) -> &Self {
|
||||||
self.inner_assert_matches_text(self.combined_output(), expected_text)
|
self.inner_assert_matches_text(self.combined_output(), expected_text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
pub fn assert_matches_file(&self, file_path: impl AsRef<Path>) -> &Self {
|
pub fn assert_matches_file(&self, file_path: impl AsRef<Path>) -> &Self {
|
||||||
self.inner_assert_matches_file(self.combined_output(), file_path)
|
self.inner_assert_matches_file(self.combined_output(), file_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
pub fn assert_stdout_matches_text(
|
pub fn assert_stdout_matches_text(
|
||||||
&self,
|
&self,
|
||||||
expected_text: impl AsRef<str>,
|
expected_text: impl AsRef<str>,
|
||||||
|
@ -569,6 +597,7 @@ impl TestCommandOutput {
|
||||||
self.inner_assert_matches_text(self.stdout(), expected_text)
|
self.inner_assert_matches_text(self.stdout(), expected_text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
pub fn assert_stdout_matches_file(
|
pub fn assert_stdout_matches_file(
|
||||||
&self,
|
&self,
|
||||||
file_path: impl AsRef<Path>,
|
file_path: impl AsRef<Path>,
|
||||||
|
@ -576,6 +605,7 @@ impl TestCommandOutput {
|
||||||
self.inner_assert_matches_file(self.stdout(), file_path)
|
self.inner_assert_matches_file(self.stdout(), file_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
pub fn assert_stderr_matches_text(
|
pub fn assert_stderr_matches_text(
|
||||||
&self,
|
&self,
|
||||||
expected_text: impl AsRef<str>,
|
expected_text: impl AsRef<str>,
|
||||||
|
@ -583,6 +613,7 @@ impl TestCommandOutput {
|
||||||
self.inner_assert_matches_text(self.stderr(), expected_text)
|
self.inner_assert_matches_text(self.stderr(), expected_text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
pub fn assert_stderrr_matches_file(
|
pub fn assert_stderrr_matches_file(
|
||||||
&self,
|
&self,
|
||||||
file_path: impl AsRef<Path>,
|
file_path: impl AsRef<Path>,
|
||||||
|
@ -590,6 +621,7 @@ impl TestCommandOutput {
|
||||||
self.inner_assert_matches_file(self.stderr(), file_path)
|
self.inner_assert_matches_file(self.stderr(), file_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
fn inner_assert_matches_text(
|
fn inner_assert_matches_text(
|
||||||
&self,
|
&self,
|
||||||
actual: &str,
|
actual: &str,
|
||||||
|
@ -597,15 +629,16 @@ impl TestCommandOutput {
|
||||||
) -> &Self {
|
) -> &Self {
|
||||||
let expected = expected.as_ref();
|
let expected = expected.as_ref();
|
||||||
if !expected.contains("[WILDCARD]") {
|
if !expected.contains("[WILDCARD]") {
|
||||||
assert_eq!(actual, expected, "at {}", failed_position());
|
assert_eq!(actual, expected);
|
||||||
} else if !wildcard_match(expected, actual) {
|
} else if !wildcard_match(expected, actual) {
|
||||||
println!("OUTPUT START\n{actual}\nOUTPUT END");
|
println!("OUTPUT START\n{actual}\nOUTPUT END");
|
||||||
println!("EXPECTED START\n{expected}\nEXPECTED END");
|
println!("EXPECTED START\n{expected}\nEXPECTED END");
|
||||||
panic!("pattern match failed at {}", failed_position());
|
panic!("pattern match failed");
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
fn inner_assert_matches_file(
|
fn inner_assert_matches_file(
|
||||||
&self,
|
&self,
|
||||||
actual: &str,
|
actual: &str,
|
||||||
|
@ -620,21 +653,3 @@ impl TestCommandOutput {
|
||||||
self.inner_assert_matches_text(actual, expected_text)
|
self.inner_assert_matches_text(actual, expected_text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn failed_position() -> String {
|
|
||||||
let backtrace = Backtrace::new();
|
|
||||||
|
|
||||||
for frame in backtrace.frames() {
|
|
||||||
for symbol in frame.symbols() {
|
|
||||||
if let Some(filename) = symbol.filename() {
|
|
||||||
if !filename.to_string_lossy().ends_with("builders.rs") {
|
|
||||||
let line_num = symbol.lineno().unwrap_or(0);
|
|
||||||
let line_col = symbol.colno().unwrap_or(0);
|
|
||||||
return format!("{}:{}:{}", filename.display(), line_num, line_col);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"<unknown>".to_string()
|
|
||||||
}
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ use hyper::StatusCode;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use npm::CUSTOM_NPM_PACKAGE_CACHE;
|
use npm::CUSTOM_NPM_PACKAGE_CACHE;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
use pty::Pty;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use rustls::Certificate;
|
use rustls::Certificate;
|
||||||
use rustls::PrivateKey;
|
use rustls::PrivateKey;
|
||||||
|
@ -24,7 +25,6 @@ use std::collections::HashMap;
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::Read;
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::mem::replace;
|
use std::mem::replace;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
@ -92,13 +92,8 @@ pub const PERMISSION_VARIANTS: [&str; 5] =
|
||||||
pub const PERMISSION_DENIED_PATTERN: &str = "PermissionDenied";
|
pub const PERMISSION_DENIED_PATTERN: &str = "PermissionDenied";
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
// STRIP_ANSI_RE and strip_ansi_codes are lifted from the "console" crate.
|
static ref GUARD: Mutex<HttpServerCount> =
|
||||||
// Copyright 2017 Armin Ronacher <armin.ronacher@active-4.com>. MIT License.
|
Mutex::new(HttpServerCount::default());
|
||||||
static ref STRIP_ANSI_RE: Regex = Regex::new(
|
|
||||||
r"[\x1b\x9b][\[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-PRZcf-nqry=><]"
|
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
static ref GUARD: Mutex<HttpServerCount> = Mutex::new(HttpServerCount::default());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn env_vars_for_npm_tests_no_sync_download() -> Vec<(String, String)> {
|
pub fn env_vars_for_npm_tests_no_sync_download() -> Vec<(String, String)> {
|
||||||
|
@ -1758,7 +1753,7 @@ pub fn http_server() -> HttpServerGuard {
|
||||||
|
|
||||||
/// Helper function to strip ansi codes.
|
/// Helper function to strip ansi codes.
|
||||||
pub fn strip_ansi_codes(s: &str) -> std::borrow::Cow<str> {
|
pub fn strip_ansi_codes(s: &str) -> std::borrow::Cow<str> {
|
||||||
STRIP_ANSI_RE.replace_all(s, "")
|
console_static_text::ansi::strip_ansi_codes(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(
|
pub fn run(
|
||||||
|
@ -2171,82 +2166,8 @@ pub fn pattern_match(pattern: &str, s: &str, wildcard: &str) -> bool {
|
||||||
t.1.is_empty()
|
t.1.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum PtyData {
|
pub fn with_pty(deno_args: &[&str], mut action: impl FnMut(Pty)) {
|
||||||
Input(&'static str),
|
if !Pty::is_supported() {
|
||||||
Output(&'static str),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn test_pty2(args: &str, data: Vec<PtyData>) {
|
|
||||||
use std::io::BufRead;
|
|
||||||
|
|
||||||
with_pty(&args.split_whitespace().collect::<Vec<_>>(), |console| {
|
|
||||||
let mut buf_reader = std::io::BufReader::new(console);
|
|
||||||
for d in data.iter() {
|
|
||||||
match d {
|
|
||||||
PtyData::Input(s) => {
|
|
||||||
println!("INPUT {}", s.escape_debug());
|
|
||||||
buf_reader.get_mut().write_text(s);
|
|
||||||
|
|
||||||
// Because of tty echo, we should be able to read the same string back.
|
|
||||||
assert!(s.ends_with('\n'));
|
|
||||||
let mut echo = String::new();
|
|
||||||
buf_reader.read_line(&mut echo).unwrap();
|
|
||||||
println!("ECHO: {}", echo.escape_debug());
|
|
||||||
|
|
||||||
// Windows may also echo the previous line, so only check the end
|
|
||||||
assert_ends_with!(normalize_text(&echo), normalize_text(s));
|
|
||||||
}
|
|
||||||
PtyData::Output(s) => {
|
|
||||||
let mut line = String::new();
|
|
||||||
if s.ends_with('\n') {
|
|
||||||
buf_reader.read_line(&mut line).unwrap();
|
|
||||||
} else {
|
|
||||||
// assumes the buffer won't have overlapping virtual terminal sequences
|
|
||||||
while normalize_text(&line).len() < normalize_text(s).len() {
|
|
||||||
let mut buf = [0; 64 * 1024];
|
|
||||||
let bytes_read = buf_reader.read(&mut buf).unwrap();
|
|
||||||
assert!(bytes_read > 0);
|
|
||||||
let buf_str = std::str::from_utf8(&buf)
|
|
||||||
.unwrap()
|
|
||||||
.trim_end_matches(char::from(0));
|
|
||||||
line += buf_str;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println!("OUTPUT {}", line.escape_debug());
|
|
||||||
assert_eq!(normalize_text(&line), normalize_text(s));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// This normalization function is not comprehensive
|
|
||||||
// and may need to updated as new scenarios emerge.
|
|
||||||
fn normalize_text(text: &str) -> String {
|
|
||||||
lazy_static! {
|
|
||||||
static ref MOVE_CURSOR_RIGHT_ONE_RE: Regex =
|
|
||||||
Regex::new(r"\x1b\[1C").unwrap();
|
|
||||||
static ref FOUND_SEQUENCES_RE: Regex =
|
|
||||||
Regex::new(r"(\x1b\]0;[^\x07]*\x07)*(\x08)*(\x1b\[\d+X)*").unwrap();
|
|
||||||
static ref CARRIAGE_RETURN_RE: Regex =
|
|
||||||
Regex::new(r"[^\n]*\r([^\n])").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// any "move cursor right" sequences should just be a space
|
|
||||||
let text = MOVE_CURSOR_RIGHT_ONE_RE.replace_all(text, " ");
|
|
||||||
// replace additional virtual terminal sequences that strip ansi codes doesn't catch
|
|
||||||
let text = FOUND_SEQUENCES_RE.replace_all(&text, "");
|
|
||||||
// strip any ansi codes, which also strips more terminal sequences
|
|
||||||
let text = strip_ansi_codes(&text);
|
|
||||||
// get rid of any text that is overwritten with only a carriage return
|
|
||||||
let text = CARRIAGE_RETURN_RE.replace_all(&text, "$1");
|
|
||||||
// finally, trim surrounding whitespace
|
|
||||||
text.trim().to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_pty(deno_args: &[&str], mut action: impl FnMut(Box<dyn pty::Pty>)) {
|
|
||||||
if !atty::is(atty::Stream::Stdin) || !atty::is(atty::Stream::Stderr) {
|
|
||||||
eprintln!("Ignoring non-tty environment.");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2257,14 +2178,12 @@ pub fn with_pty(deno_args: &[&str], mut action: impl FnMut(Box<dyn pty::Pty>)) {
|
||||||
"DENO_DIR".to_string(),
|
"DENO_DIR".to_string(),
|
||||||
deno_dir.path().to_string_lossy().to_string(),
|
deno_dir.path().to_string_lossy().to_string(),
|
||||||
);
|
);
|
||||||
let pty = pty::create_pty(
|
action(Pty::new(
|
||||||
&deno_exe_path().to_string_lossy().to_string(),
|
&deno_exe_path(),
|
||||||
deno_args,
|
deno_args,
|
||||||
testdata_path(),
|
&testdata_path(),
|
||||||
Some(env_vars),
|
Some(env_vars),
|
||||||
);
|
))
|
||||||
|
|
||||||
action(pty);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct WrkOutput {
|
pub struct WrkOutput {
|
||||||
|
|
|
@ -1,36 +1,253 @@
|
||||||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::collections::HashSet;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
use std::io::Write;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
pub trait Pty: Read {
|
use crate::strip_ansi_codes;
|
||||||
fn write_text(&mut self, text: &str);
|
|
||||||
|
|
||||||
fn write_line(&mut self, text: &str) {
|
/// Points to know about when writing pty tests:
|
||||||
self.write_text(&format!("{text}\n"));
|
///
|
||||||
|
/// - Consecutive writes cause issues where you might write while a prompt
|
||||||
|
/// is not showing. So when you write, always `.expect(...)` on the output.
|
||||||
|
/// - Similar to the last point, using `.expect(...)` can help make the test
|
||||||
|
/// more deterministic. If the test is flaky, try adding more `.expect(...)`s
|
||||||
|
pub struct Pty {
|
||||||
|
pty: Box<dyn SystemPty>,
|
||||||
|
read_bytes: Vec<u8>,
|
||||||
|
last_index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pty {
|
||||||
|
pub fn new(
|
||||||
|
program: &Path,
|
||||||
|
args: &[&str],
|
||||||
|
cwd: &Path,
|
||||||
|
env_vars: Option<HashMap<String, String>>,
|
||||||
|
) -> Self {
|
||||||
|
let pty = create_pty(program, args, cwd, env_vars);
|
||||||
|
let mut pty = Self {
|
||||||
|
pty,
|
||||||
|
read_bytes: Vec::new(),
|
||||||
|
last_index: 0,
|
||||||
|
};
|
||||||
|
if args[0] == "repl" && !args.contains(&"--quiet") {
|
||||||
|
// wait for the repl to start up before writing to it
|
||||||
|
pty.expect("exit using ctrl+d, ctrl+c, or close()");
|
||||||
|
}
|
||||||
|
pty
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads the output to the EOF.
|
pub fn is_supported() -> bool {
|
||||||
fn read_all_output(&mut self) -> String {
|
let is_mac_or_windows = cfg!(target_os = "macos") || cfg!(windows);
|
||||||
let mut text = String::new();
|
if is_mac_or_windows && std::env::var("CI").is_ok() {
|
||||||
self.read_to_string(&mut text).unwrap();
|
// the pty tests give a ENOTTY error for Mac and don't really start up
|
||||||
|
// on the windows CI for some reason so ignore them for now
|
||||||
|
eprintln!("Ignoring windows CI.");
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
pub fn write_raw(&mut self, line: impl AsRef<str>) {
|
||||||
|
let line = if cfg!(windows) {
|
||||||
|
line.as_ref().replace('\n', "\r\n")
|
||||||
|
} else {
|
||||||
|
line.as_ref().to_string()
|
||||||
|
};
|
||||||
|
if let Err(err) = self.pty.write(line.as_bytes()) {
|
||||||
|
panic!("{:#}", err)
|
||||||
|
}
|
||||||
|
self.pty.flush().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
pub fn write_line(&mut self, line: impl AsRef<str>) {
|
||||||
|
self.write_line_raw(&line);
|
||||||
|
|
||||||
|
// expect what was written to show up in the output
|
||||||
|
// due to "pty echo"
|
||||||
|
for line in line.as_ref().lines() {
|
||||||
|
self.expect(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes a line without checking if it's in the output.
|
||||||
|
#[track_caller]
|
||||||
|
pub fn write_line_raw(&mut self, line: impl AsRef<str>) {
|
||||||
|
self.write_raw(format!("{}\n", line.as_ref()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
pub fn read_until(&mut self, end_text: impl AsRef<str>) -> String {
|
||||||
|
self.read_until_with_advancing(|text| {
|
||||||
text
|
text
|
||||||
|
.find(end_text.as_ref())
|
||||||
|
.map(|index| index + end_text.as_ref().len())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
pub fn expect(&mut self, text: impl AsRef<str>) {
|
||||||
|
self.read_until(text.as_ref());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
pub fn expect_any(&mut self, texts: &[&str]) {
|
||||||
|
self.read_until_with_advancing(|text| {
|
||||||
|
for find_text in texts {
|
||||||
|
if let Some(index) = text.find(find_text) {
|
||||||
|
return Some(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes and expects to find all the text until a timeout is hit.
|
||||||
|
#[track_caller]
|
||||||
|
pub fn expect_all(&mut self, texts: &[&str]) {
|
||||||
|
let mut pending_texts: HashSet<&&str> = HashSet::from_iter(texts);
|
||||||
|
let mut max_index: Option<usize> = None;
|
||||||
|
self.read_until_with_advancing(|text| {
|
||||||
|
for pending_text in pending_texts.clone() {
|
||||||
|
if let Some(index) = text.find(pending_text) {
|
||||||
|
let index = index + pending_text.len();
|
||||||
|
match &max_index {
|
||||||
|
Some(current) => {
|
||||||
|
if *current < index {
|
||||||
|
max_index = Some(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
max_index = Some(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pending_texts.remove(pending_text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pending_texts.is_empty() {
|
||||||
|
max_index
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expects the raw text to be found, which may include ANSI codes.
|
||||||
|
/// Note: this expects the raw bytes in any output that has already
|
||||||
|
/// occurred or may occur within the next few seconds.
|
||||||
|
#[track_caller]
|
||||||
|
pub fn expect_raw_in_current_output(&mut self, text: impl AsRef<str>) {
|
||||||
|
self.read_until_condition(|pty| {
|
||||||
|
let data = String::from_utf8_lossy(&pty.read_bytes);
|
||||||
|
data.contains(text.as_ref())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn read_until_with_advancing(
|
||||||
|
&mut self,
|
||||||
|
mut condition: impl FnMut(&str) -> Option<usize>,
|
||||||
|
) -> String {
|
||||||
|
let mut final_text = String::new();
|
||||||
|
self.read_until_condition(|pty| {
|
||||||
|
let text = pty.next_text();
|
||||||
|
if let Some(end_index) = condition(&text) {
|
||||||
|
pty.last_index += end_index;
|
||||||
|
final_text = text[..end_index].to_string();
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
final_text
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn read_until_condition(
|
||||||
|
&mut self,
|
||||||
|
mut condition: impl FnMut(&mut Self) -> bool,
|
||||||
|
) {
|
||||||
|
let timeout_time =
|
||||||
|
Instant::now().checked_add(Duration::from_secs(5)).unwrap();
|
||||||
|
while Instant::now() < timeout_time {
|
||||||
|
self.fill_more_bytes();
|
||||||
|
if condition(self) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let text = self.next_text();
|
||||||
|
eprintln!(
|
||||||
|
"------ Start Full Text ------\n{:?}\n------- End Full Text -------",
|
||||||
|
String::from_utf8_lossy(&self.read_bytes)
|
||||||
|
);
|
||||||
|
eprintln!("Next text: {:?}", text);
|
||||||
|
panic!("Timed out.")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_text(&self) -> String {
|
||||||
|
let text = String::from_utf8_lossy(&self.read_bytes).to_string();
|
||||||
|
let text = strip_ansi_codes(&text);
|
||||||
|
text[self.last_index..].to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill_more_bytes(&mut self) {
|
||||||
|
let mut buf = [0; 256];
|
||||||
|
if let Ok(count) = self.pty.read(&mut buf) {
|
||||||
|
self.read_bytes.extend(&buf[..count]);
|
||||||
|
} else {
|
||||||
|
std::thread::sleep(Duration::from_millis(10));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trait SystemPty: Read + Write {}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub fn create_pty(
|
fn setup_pty(master: &pty2::fork::Master) {
|
||||||
program: impl AsRef<Path>,
|
use nix::fcntl::fcntl;
|
||||||
|
use nix::fcntl::FcntlArg;
|
||||||
|
use nix::fcntl::OFlag;
|
||||||
|
use nix::sys::termios;
|
||||||
|
use nix::sys::termios::tcgetattr;
|
||||||
|
use nix::sys::termios::tcsetattr;
|
||||||
|
use nix::sys::termios::SetArg;
|
||||||
|
use std::os::fd::AsRawFd;
|
||||||
|
|
||||||
|
let fd = master.as_raw_fd();
|
||||||
|
let mut term = tcgetattr(fd).unwrap();
|
||||||
|
// disable cooked mode
|
||||||
|
term.local_flags.remove(termios::LocalFlags::ICANON);
|
||||||
|
tcsetattr(fd, SetArg::TCSANOW, &term).unwrap();
|
||||||
|
|
||||||
|
// turn on non-blocking mode so we get timeouts
|
||||||
|
let flags = fcntl(fd, FcntlArg::F_GETFL).unwrap();
|
||||||
|
let new_flags = OFlag::from_bits_truncate(flags) | OFlag::O_NONBLOCK;
|
||||||
|
fcntl(fd, FcntlArg::F_SETFL(new_flags)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn create_pty(
|
||||||
|
program: &Path,
|
||||||
args: &[&str],
|
args: &[&str],
|
||||||
cwd: impl AsRef<Path>,
|
cwd: &Path,
|
||||||
env_vars: Option<HashMap<String, String>>,
|
env_vars: Option<HashMap<String, String>>,
|
||||||
) -> Box<dyn Pty> {
|
) -> Box<dyn SystemPty> {
|
||||||
let fork = pty2::fork::Fork::from_ptmx().unwrap();
|
let fork = pty2::fork::Fork::from_ptmx().unwrap();
|
||||||
if fork.is_parent().is_ok() {
|
if fork.is_parent().is_ok() {
|
||||||
|
let master = fork.is_parent().unwrap();
|
||||||
|
setup_pty(&master);
|
||||||
Box::new(unix::UnixPty { fork })
|
Box::new(unix::UnixPty { fork })
|
||||||
} else {
|
} else {
|
||||||
std::process::Command::new(program.as_ref())
|
std::process::Command::new(program)
|
||||||
.current_dir(cwd)
|
.current_dir(cwd)
|
||||||
.args(args)
|
.args(args)
|
||||||
.envs(env_vars.unwrap_or_default())
|
.envs(env_vars.unwrap_or_default())
|
||||||
|
@ -47,7 +264,7 @@ mod unix {
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
use super::Pty;
|
use super::SystemPty;
|
||||||
|
|
||||||
pub struct UnixPty {
|
pub struct UnixPty {
|
||||||
pub fork: pty2::fork::Fork,
|
pub fork: pty2::fork::Fork,
|
||||||
|
@ -55,16 +272,18 @@ mod unix {
|
||||||
|
|
||||||
impl Drop for UnixPty {
|
impl Drop for UnixPty {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.fork.wait().unwrap();
|
use nix::sys::signal::kill;
|
||||||
|
use nix::sys::signal::Signal;
|
||||||
|
use nix::unistd::Pid;
|
||||||
|
|
||||||
|
if let pty2::fork::Fork::Parent(child_pid, _) = self.fork {
|
||||||
|
let pid = Pid::from_raw(child_pid);
|
||||||
|
kill(pid, Signal::SIGTERM).unwrap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pty for UnixPty {
|
impl SystemPty for UnixPty {}
|
||||||
fn write_text(&mut self, text: &str) {
|
|
||||||
let mut master = self.fork.is_parent().unwrap();
|
|
||||||
master.write_all(text.as_bytes()).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Read for UnixPty {
|
impl Read for UnixPty {
|
||||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||||
|
@ -72,29 +291,36 @@ mod unix {
|
||||||
master.read(buf)
|
master.read(buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Write for UnixPty {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||||
|
let mut master = self.fork.is_parent().unwrap();
|
||||||
|
master.write(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> std::io::Result<()> {
|
||||||
|
let mut master = self.fork.is_parent().unwrap();
|
||||||
|
master.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub fn create_pty(
|
fn create_pty(
|
||||||
program: impl AsRef<Path>,
|
program: &Path,
|
||||||
args: &[&str],
|
args: &[&str],
|
||||||
cwd: impl AsRef<Path>,
|
cwd: &Path,
|
||||||
env_vars: Option<HashMap<String, String>>,
|
env_vars: Option<HashMap<String, String>>,
|
||||||
) -> Box<dyn Pty> {
|
) -> Box<dyn SystemPty> {
|
||||||
let pty = windows::WinPseudoConsole::new(
|
let pty = windows::WinPseudoConsole::new(program, args, cwd, env_vars);
|
||||||
program,
|
|
||||||
args,
|
|
||||||
&cwd.as_ref().to_string_lossy(),
|
|
||||||
env_vars,
|
|
||||||
);
|
|
||||||
Box::new(pty)
|
Box::new(pty)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
mod windows {
|
mod windows {
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::io::ErrorKind;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::io::Write;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
@ -105,11 +331,13 @@ mod windows {
|
||||||
use winapi::shared::winerror::S_OK;
|
use winapi::shared::winerror::S_OK;
|
||||||
use winapi::um::consoleapi::ClosePseudoConsole;
|
use winapi::um::consoleapi::ClosePseudoConsole;
|
||||||
use winapi::um::consoleapi::CreatePseudoConsole;
|
use winapi::um::consoleapi::CreatePseudoConsole;
|
||||||
|
use winapi::um::fileapi::FlushFileBuffers;
|
||||||
use winapi::um::fileapi::ReadFile;
|
use winapi::um::fileapi::ReadFile;
|
||||||
use winapi::um::fileapi::WriteFile;
|
use winapi::um::fileapi::WriteFile;
|
||||||
use winapi::um::handleapi::DuplicateHandle;
|
use winapi::um::handleapi::DuplicateHandle;
|
||||||
use winapi::um::handleapi::INVALID_HANDLE_VALUE;
|
use winapi::um::handleapi::INVALID_HANDLE_VALUE;
|
||||||
use winapi::um::namedpipeapi::CreatePipe;
|
use winapi::um::namedpipeapi::CreatePipe;
|
||||||
|
use winapi::um::namedpipeapi::PeekNamedPipe;
|
||||||
use winapi::um::processthreadsapi::CreateProcessW;
|
use winapi::um::processthreadsapi::CreateProcessW;
|
||||||
use winapi::um::processthreadsapi::DeleteProcThreadAttributeList;
|
use winapi::um::processthreadsapi::DeleteProcThreadAttributeList;
|
||||||
use winapi::um::processthreadsapi::GetCurrentProcess;
|
use winapi::um::processthreadsapi::GetCurrentProcess;
|
||||||
|
@ -127,7 +355,7 @@ mod windows {
|
||||||
use winapi::um::winnt::DUPLICATE_SAME_ACCESS;
|
use winapi::um::winnt::DUPLICATE_SAME_ACCESS;
|
||||||
use winapi::um::winnt::HANDLE;
|
use winapi::um::winnt::HANDLE;
|
||||||
|
|
||||||
use super::Pty;
|
use super::SystemPty;
|
||||||
|
|
||||||
macro_rules! assert_win_success {
|
macro_rules! assert_win_success {
|
||||||
($expression:expr) => {
|
($expression:expr) => {
|
||||||
|
@ -138,6 +366,15 @@ mod windows {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! handle_err {
|
||||||
|
($expression:expr) => {
|
||||||
|
let success = $expression;
|
||||||
|
if success != TRUE {
|
||||||
|
return Err(std::io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub struct WinPseudoConsole {
|
pub struct WinPseudoConsole {
|
||||||
stdin_write_handle: WinHandle,
|
stdin_write_handle: WinHandle,
|
||||||
stdout_read_handle: WinHandle,
|
stdout_read_handle: WinHandle,
|
||||||
|
@ -149,9 +386,9 @@ mod windows {
|
||||||
|
|
||||||
impl WinPseudoConsole {
|
impl WinPseudoConsole {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
program: impl AsRef<Path>,
|
program: &Path,
|
||||||
args: &[&str],
|
args: &[&str],
|
||||||
cwd: &str,
|
cwd: &Path,
|
||||||
maybe_env_vars: Option<HashMap<String, String>>,
|
maybe_env_vars: Option<HashMap<String, String>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// https://docs.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session
|
// https://docs.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session
|
||||||
|
@ -184,15 +421,19 @@ mod windows {
|
||||||
let mut proc_info: PROCESS_INFORMATION = std::mem::zeroed();
|
let mut proc_info: PROCESS_INFORMATION = std::mem::zeroed();
|
||||||
let command = format!(
|
let command = format!(
|
||||||
"\"{}\" {}",
|
"\"{}\" {}",
|
||||||
program.as_ref().to_string_lossy(),
|
program.to_string_lossy(),
|
||||||
args.join(" ")
|
args
|
||||||
|
.iter()
|
||||||
|
.map(|a| format!("\"{}\"", a))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(" ")
|
||||||
)
|
)
|
||||||
.trim()
|
.trim()
|
||||||
.to_string();
|
.to_string();
|
||||||
let mut application_str =
|
let mut application_str = to_windows_str(&program.to_string_lossy());
|
||||||
to_windows_str(&program.as_ref().to_string_lossy());
|
|
||||||
let mut command_str = to_windows_str(&command);
|
let mut command_str = to_windows_str(&command);
|
||||||
let mut cwd = to_windows_str(cwd);
|
let cwd = cwd.to_string_lossy().replace('/', "\\");
|
||||||
|
let mut cwd = to_windows_str(&cwd);
|
||||||
|
|
||||||
assert_win_success!(CreateProcessW(
|
assert_win_success!(CreateProcessW(
|
||||||
application_str.as_mut_ptr(),
|
application_str.as_mut_ptr(),
|
||||||
|
@ -242,11 +483,26 @@ mod windows {
|
||||||
|
|
||||||
impl Read for WinPseudoConsole {
|
impl Read for WinPseudoConsole {
|
||||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||||
loop {
|
// don't do a blocking read in order to support timing out
|
||||||
|
let mut bytes_available = 0;
|
||||||
|
// SAFETY: winapi call
|
||||||
|
handle_err!(unsafe {
|
||||||
|
PeekNamedPipe(
|
||||||
|
self.stdout_read_handle.as_raw_handle(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
0,
|
||||||
|
ptr::null_mut(),
|
||||||
|
&mut bytes_available,
|
||||||
|
ptr::null_mut(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
if bytes_available == 0 {
|
||||||
|
return Err(std::io::Error::new(ErrorKind::WouldBlock, "Would block."));
|
||||||
|
}
|
||||||
|
|
||||||
let mut bytes_read = 0;
|
let mut bytes_read = 0;
|
||||||
// SAFETY:
|
// SAFETY: winapi call
|
||||||
// winapi call
|
handle_err!(unsafe {
|
||||||
let success = unsafe {
|
|
||||||
ReadFile(
|
ReadFile(
|
||||||
self.stdout_read_handle.as_raw_handle(),
|
self.stdout_read_handle.as_raw_handle(),
|
||||||
buf.as_mut_ptr() as _,
|
buf.as_mut_ptr() as _,
|
||||||
|
@ -254,33 +510,20 @@ mod windows {
|
||||||
&mut bytes_read,
|
&mut bytes_read,
|
||||||
ptr::null_mut(),
|
ptr::null_mut(),
|
||||||
)
|
)
|
||||||
};
|
});
|
||||||
|
|
||||||
// ignore zero-byte writes
|
Ok(bytes_read as usize)
|
||||||
let is_zero_byte_write = bytes_read == 0 && success == TRUE;
|
|
||||||
if !is_zero_byte_write {
|
|
||||||
return Ok(bytes_read as usize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pty for WinPseudoConsole {
|
impl SystemPty for WinPseudoConsole {}
|
||||||
fn write_text(&mut self, text: &str) {
|
|
||||||
// windows pseudo console requires a \r\n to do a newline
|
|
||||||
let newline_re = regex::Regex::new("\r?\n").unwrap();
|
|
||||||
self
|
|
||||||
.write_all(newline_re.replace_all(text, "\r\n").as_bytes())
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::io::Write for WinPseudoConsole {
|
impl std::io::Write for WinPseudoConsole {
|
||||||
fn write(&mut self, buffer: &[u8]) -> std::io::Result<usize> {
|
fn write(&mut self, buffer: &[u8]) -> std::io::Result<usize> {
|
||||||
let mut bytes_written = 0;
|
let mut bytes_written = 0;
|
||||||
// SAFETY:
|
// SAFETY:
|
||||||
// winapi call
|
// winapi call
|
||||||
assert_win_success!(unsafe {
|
handle_err!(unsafe {
|
||||||
WriteFile(
|
WriteFile(
|
||||||
self.stdin_write_handle.as_raw_handle(),
|
self.stdin_write_handle.as_raw_handle(),
|
||||||
buffer.as_ptr() as *const _,
|
buffer.as_ptr() as *const _,
|
||||||
|
@ -293,6 +536,10 @@ mod windows {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flush(&mut self) -> std::io::Result<()> {
|
fn flush(&mut self) -> std::io::Result<()> {
|
||||||
|
// SAFETY: winapi call
|
||||||
|
handle_err!(unsafe {
|
||||||
|
FlushFileBuffers(self.stdin_write_handle.as_raw_handle())
|
||||||
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -307,12 +554,10 @@ mod windows {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn duplicate(&self) -> WinHandle {
|
pub fn duplicate(&self) -> WinHandle {
|
||||||
// SAFETY:
|
// SAFETY: winapi call
|
||||||
// winapi call
|
|
||||||
let process_handle = unsafe { GetCurrentProcess() };
|
let process_handle = unsafe { GetCurrentProcess() };
|
||||||
let mut duplicate_handle = ptr::null_mut();
|
let mut duplicate_handle = ptr::null_mut();
|
||||||
// SAFETY:
|
// SAFETY: winapi call
|
||||||
// winapi call
|
|
||||||
assert_win_success!(unsafe {
|
assert_win_success!(unsafe {
|
||||||
DuplicateHandle(
|
DuplicateHandle(
|
||||||
process_handle,
|
process_handle,
|
||||||
|
@ -410,8 +655,7 @@ mod windows {
|
||||||
|
|
||||||
impl Drop for ProcThreadAttributeList {
|
impl Drop for ProcThreadAttributeList {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
// SAFETY:
|
// SAFETY: winapi call
|
||||||
// winapi call
|
|
||||||
unsafe { DeleteProcThreadAttributeList(self.as_mut_ptr()) };
|
unsafe { DeleteProcThreadAttributeList(self.as_mut_ptr()) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -420,8 +664,7 @@ mod windows {
|
||||||
let mut read_handle = std::ptr::null_mut();
|
let mut read_handle = std::ptr::null_mut();
|
||||||
let mut write_handle = std::ptr::null_mut();
|
let mut write_handle = std::ptr::null_mut();
|
||||||
|
|
||||||
// SAFETY:
|
// SAFETY: Creating an anonymous pipe with winapi.
|
||||||
// Creating an anonymous pipe with winapi.
|
|
||||||
assert_win_success!(unsafe {
|
assert_win_success!(unsafe {
|
||||||
CreatePipe(&mut read_handle, &mut write_handle, ptr::null_mut(), 0)
|
CreatePipe(&mut read_handle, &mut write_handle, ptr::null_mut(), 0)
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue