summaryrefslogtreecommitdiff
path: root/cli
diff options
context:
space:
mode:
Diffstat (limited to 'cli')
-rw-r--r--cli/Cargo.toml16
-rw-r--r--cli/build.rs574
-rw-r--r--cli/js.rs57
-rw-r--r--cli/js/40_testing.js1412
-rw-r--r--cli/lsp/testing/execution.rs4
-rw-r--r--cli/main.rs35
-rw-r--r--cli/standalone.rs2
-rw-r--r--cli/tests/testdata/test/steps/failing_steps.out8
-rw-r--r--cli/tools/bench.rs4
-rw-r--r--cli/tools/test.rs4
-rw-r--r--cli/worker.rs267
11 files changed, 2035 insertions, 348 deletions
diff --git a/cli/Cargo.toml b/cli/Cargo.toml
index 83ff3d77e..732d586d2 100644
--- a/cli/Cargo.toml
+++ b/cli/Cargo.toml
@@ -26,25 +26,16 @@ harness = false
path = "./bench/lsp_bench_standalone.rs"
[build-dependencies]
-deno_broadcast_channel = { version = "0.72.0", path = "../ext/broadcast_channel" }
-deno_cache = { version = "0.10.0", path = "../ext/cache" }
-deno_console = { version = "0.78.0", path = "../ext/console" }
+deno_runtime = { version = "0.86.0", path = "../runtime" }
deno_core = { version = "0.160.0", path = "../core" }
-deno_crypto = { version = "0.92.0", path = "../ext/crypto" }
-deno_fetch = { version = "0.101.0", path = "../ext/fetch" }
-deno_net = { version = "0.70.0", path = "../ext/net" }
-deno_node = { version = "0.15.0", path = "../ext/node" }
-deno_url = { version = "0.78.0", path = "../ext/url" }
-deno_web = { version = "0.109.0", path = "../ext/web" }
-deno_webgpu = { version = "0.79.0", path = "../ext/webgpu" }
-deno_websocket = { version = "0.83.0", path = "../ext/websocket" }
-deno_webstorage = { version = "0.73.0", path = "../ext/webstorage" }
regex = "=1.6.0"
serde = { version = "=1.0.144", features = ["derive"] }
serde_json = "1.0.64"
zstd = '=0.11.2'
glibc_version = "0.1.2"
+lzzzz = '1.0'
+
[target.'cfg(windows)'.build-dependencies]
winapi = "=0.3.9"
winres = "=0.1.12"
@@ -86,6 +77,7 @@ jsonc-parser = { version = "=0.21.0", features = ["serde"] }
libc = "=0.2.126"
log = { version = "=0.4.17", features = ["serde"] }
lsp-types = "=0.93.2" # used by tower-lsp and "proposed" feature is unstable in patch releases
+lzzzz = '1.0'
mitata = "=0.0.7"
monch = "=0.4.0"
notify = "=5.0.0"
diff --git a/cli/build.rs b/cli/build.rs
index 73d0208f6..c7d902941 100644
--- a/cli/build.rs
+++ b/cli/build.rs
@@ -1,306 +1,319 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
-use deno_core::error::custom_error;
-use deno_core::error::AnyError;
-use deno_core::op;
-use deno_core::serde::Deserialize;
-use deno_core::serde_json::json;
-use deno_core::serde_json::Value;
use deno_core::Extension;
-use deno_core::JsRuntime;
-use deno_core::OpState;
-use deno_core::RuntimeOptions;
-use regex::Regex;
-use std::collections::HashMap;
use std::env;
use std::path::Path;
use std::path::PathBuf;
-// TODO(bartlomieju): this module contains a lot of duplicated
-// logic with `runtime/build.rs`, factor out to `deno_core`.
-fn create_snapshot(
- mut js_runtime: JsRuntime,
- snapshot_path: &Path,
- files: Vec<PathBuf>,
-) {
- // TODO(nayeemrmn): https://github.com/rust-lang/cargo/issues/3946 to get the
- // workspace root.
- let display_root = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap();
- for file in files {
- println!("cargo:rerun-if-changed={}", file.display());
- let display_path = file.strip_prefix(display_root).unwrap();
- let display_path_str = display_path.display().to_string();
- js_runtime
- .execute_script(
- &("deno:".to_string() + &display_path_str.replace('\\', "/")),
- &std::fs::read_to_string(&file).unwrap(),
- )
- .unwrap();
+use deno_core::snapshot_util::*;
+use deno_runtime::deno_cache::SqliteBackedCache;
+use deno_runtime::permissions::Permissions;
+use deno_runtime::*;
+
+mod ts {
+ use super::*;
+ use crate::deno_webgpu_get_declaration;
+ use deno_core::error::custom_error;
+ use deno_core::error::AnyError;
+ use deno_core::op;
+ use deno_core::OpState;
+ use regex::Regex;
+ use serde::Deserialize;
+ use serde_json::json;
+ use serde_json::Value;
+ use std::collections::HashMap;
+ use std::path::Path;
+ use std::path::PathBuf;
+
+ #[derive(Debug, Deserialize)]
+ struct LoadArgs {
+ /// The fully qualified specifier that should be loaded.
+ specifier: String,
}
- let snapshot = js_runtime.snapshot();
- let snapshot_slice: &[u8] = &snapshot;
- println!("Snapshot size: {}", snapshot_slice.len());
-
- let compressed_snapshot_with_size = {
- let mut vec = vec![];
-
- vec.extend_from_slice(
- &u32::try_from(snapshot.len())
- .expect("snapshot larger than 4gb")
- .to_le_bytes(),
+ pub fn create_compiler_snapshot(
+ snapshot_path: PathBuf,
+ files: Vec<PathBuf>,
+ cwd: &Path,
+ ) {
+ // libs that are being provided by op crates.
+ let mut op_crate_libs = HashMap::new();
+ op_crate_libs.insert("deno.cache", deno_cache::get_declaration());
+ op_crate_libs.insert("deno.console", deno_console::get_declaration());
+ op_crate_libs.insert("deno.url", deno_url::get_declaration());
+ op_crate_libs.insert("deno.web", deno_web::get_declaration());
+ op_crate_libs.insert("deno.fetch", deno_fetch::get_declaration());
+ op_crate_libs.insert("deno.webgpu", deno_webgpu_get_declaration());
+ op_crate_libs.insert("deno.websocket", deno_websocket::get_declaration());
+ op_crate_libs.insert("deno.webstorage", deno_webstorage::get_declaration());
+ op_crate_libs.insert("deno.crypto", deno_crypto::get_declaration());
+ op_crate_libs.insert(
+ "deno.broadcast_channel",
+ deno_broadcast_channel::get_declaration(),
);
+ op_crate_libs.insert("deno.net", deno_net::get_declaration());
- vec.extend_from_slice(
- &zstd::bulk::compress(snapshot_slice, 22)
- .expect("snapshot compression failed"),
- );
-
- vec
- };
-
- println!(
- "Snapshot compressed size: {}",
- compressed_snapshot_with_size.len()
- );
-
- std::fs::write(snapshot_path, compressed_snapshot_with_size).unwrap();
- println!("Snapshot written to: {} ", snapshot_path.display());
-}
-
-#[derive(Debug, Deserialize)]
-struct LoadArgs {
- /// The fully qualified specifier that should be loaded.
- specifier: String,
-}
-
-fn create_compiler_snapshot(
- snapshot_path: &Path,
- files: Vec<PathBuf>,
- cwd: &Path,
-) {
- // libs that are being provided by op crates.
- let mut op_crate_libs = HashMap::new();
- op_crate_libs.insert("deno.cache", deno_cache::get_declaration());
- op_crate_libs.insert("deno.console", deno_console::get_declaration());
- op_crate_libs.insert("deno.url", deno_url::get_declaration());
- op_crate_libs.insert("deno.web", deno_web::get_declaration());
- op_crate_libs.insert("deno.fetch", deno_fetch::get_declaration());
- op_crate_libs.insert("deno.webgpu", deno_webgpu_get_declaration());
- op_crate_libs.insert("deno.websocket", deno_websocket::get_declaration());
- op_crate_libs.insert("deno.webstorage", deno_webstorage::get_declaration());
- op_crate_libs.insert("deno.crypto", deno_crypto::get_declaration());
- op_crate_libs.insert(
- "deno.broadcast_channel",
- deno_broadcast_channel::get_declaration(),
- );
- op_crate_libs.insert("deno.net", deno_net::get_declaration());
-
- // ensure we invalidate the build properly.
- for (_, path) in op_crate_libs.iter() {
- println!("cargo:rerun-if-changed={}", path.display());
- }
-
- // libs that should be loaded into the isolate before snapshotting.
- let libs = vec![
- // Deno custom type libraries
- "deno.window",
- "deno.worker",
- "deno.shared_globals",
- "deno.ns",
- "deno.unstable",
- // Deno built-in type libraries
- "es5",
- "es2015.collection",
- "es2015.core",
- "es2015",
- "es2015.generator",
- "es2015.iterable",
- "es2015.promise",
- "es2015.proxy",
- "es2015.reflect",
- "es2015.symbol",
- "es2015.symbol.wellknown",
- "es2016.array.include",
- "es2016",
- "es2017",
- "es2017.intl",
- "es2017.object",
- "es2017.sharedmemory",
- "es2017.string",
- "es2017.typedarrays",
- "es2018.asyncgenerator",
- "es2018.asynciterable",
- "es2018",
- "es2018.intl",
- "es2018.promise",
- "es2018.regexp",
- "es2019.array",
- "es2019",
- "es2019.object",
- "es2019.string",
- "es2019.symbol",
- "es2020.bigint",
- "es2020",
- "es2020.date",
- "es2020.intl",
- "es2020.number",
- "es2020.promise",
- "es2020.sharedmemory",
- "es2020.string",
- "es2020.symbol.wellknown",
- "es2021",
- "es2021.intl",
- "es2021.promise",
- "es2021.string",
- "es2021.weakref",
- "es2022",
- "es2022.array",
- "es2022.error",
- "es2022.intl",
- "es2022.object",
- "es2022.string",
- "esnext",
- "esnext.array",
- "esnext.intl",
- ];
+ // ensure we invalidate the build properly.
+ for (_, path) in op_crate_libs.iter() {
+ println!("cargo:rerun-if-changed={}", path.display());
+ }
- let path_dts = cwd.join("dts");
- // ensure we invalidate the build properly.
- for name in libs.iter() {
- println!(
- "cargo:rerun-if-changed={}",
- path_dts.join(format!("lib.{}.d.ts", name)).display()
- );
- }
+ // libs that should be loaded into the isolate before snapshotting.
+ let libs = vec![
+ // Deno custom type libraries
+ "deno.window",
+ "deno.worker",
+ "deno.shared_globals",
+ "deno.ns",
+ "deno.unstable",
+ // Deno built-in type libraries
+ "es5",
+ "es2015.collection",
+ "es2015.core",
+ "es2015",
+ "es2015.generator",
+ "es2015.iterable",
+ "es2015.promise",
+ "es2015.proxy",
+ "es2015.reflect",
+ "es2015.symbol",
+ "es2015.symbol.wellknown",
+ "es2016.array.include",
+ "es2016",
+ "es2017",
+ "es2017.intl",
+ "es2017.object",
+ "es2017.sharedmemory",
+ "es2017.string",
+ "es2017.typedarrays",
+ "es2018.asyncgenerator",
+ "es2018.asynciterable",
+ "es2018",
+ "es2018.intl",
+ "es2018.promise",
+ "es2018.regexp",
+ "es2019.array",
+ "es2019",
+ "es2019.object",
+ "es2019.string",
+ "es2019.symbol",
+ "es2020.bigint",
+ "es2020",
+ "es2020.date",
+ "es2020.intl",
+ "es2020.number",
+ "es2020.promise",
+ "es2020.sharedmemory",
+ "es2020.string",
+ "es2020.symbol.wellknown",
+ "es2021",
+ "es2021.intl",
+ "es2021.promise",
+ "es2021.string",
+ "es2021.weakref",
+ "es2022",
+ "es2022.array",
+ "es2022.error",
+ "es2022.intl",
+ "es2022.object",
+ "es2022.string",
+ "esnext",
+ "esnext.array",
+ "esnext.intl",
+ ];
+
+ let path_dts = cwd.join("dts");
+ // ensure we invalidate the build properly.
+ for name in libs.iter() {
+ println!(
+ "cargo:rerun-if-changed={}",
+ path_dts.join(format!("lib.{}.d.ts", name)).display()
+ );
+ }
- // create a copy of the vector that includes any op crate libs to be passed
- // to the JavaScript compiler to build into the snapshot
- let mut build_libs = libs.clone();
- for (op_lib, _) in op_crate_libs.iter() {
- build_libs.push(op_lib.to_owned());
- }
+ // create a copy of the vector that includes any op crate libs to be passed
+ // to the JavaScript compiler to build into the snapshot
+ let mut build_libs = libs.clone();
+ for (op_lib, _) in op_crate_libs.iter() {
+ build_libs.push(op_lib.to_owned());
+ }
- #[op]
- fn op_build_info(state: &mut OpState) -> Value {
- let build_specifier = "asset:///bootstrap.ts";
- let build_libs = state.borrow::<Vec<&str>>();
- json!({
- "buildSpecifier": build_specifier,
- "libs": build_libs,
- })
- }
+ #[op]
+ fn op_build_info(state: &mut OpState) -> Value {
+ let build_specifier = "asset:///bootstrap.ts";
+ let build_libs = state.borrow::<Vec<&str>>();
+ json!({
+ "buildSpecifier": build_specifier,
+ "libs": build_libs,
+ })
+ }
- #[op]
- fn op_cwd() -> String {
- "cache:///".into()
- }
+ #[op]
+ fn op_cwd() -> String {
+ "cache:///".into()
+ }
- #[op]
- fn op_exists() -> bool {
- false
- }
+ #[op]
+ fn op_exists() -> bool {
+ false
+ }
- #[op]
- fn op_is_node_file() -> bool {
- false
- }
+ #[op]
+ fn op_is_node_file() -> bool {
+ false
+ }
- #[op]
- fn op_script_version(
- _state: &mut OpState,
- _args: Value,
- ) -> Result<Option<String>, AnyError> {
- Ok(Some("1".to_string()))
- }
+ #[op]
+ fn op_script_version(
+ _state: &mut OpState,
+ _args: Value,
+ ) -> Result<Option<String>, AnyError> {
+ Ok(Some("1".to_string()))
+ }
- #[op]
- // using the same op that is used in `tsc.rs` for loading modules and reading
- // files, but a slightly different implementation at build time.
- fn op_load(state: &mut OpState, args: LoadArgs) -> Result<Value, AnyError> {
- let op_crate_libs = state.borrow::<HashMap<&str, PathBuf>>();
- let path_dts = state.borrow::<PathBuf>();
- let re_asset =
- Regex::new(r"asset:/{3}lib\.(\S+)\.d\.ts").expect("bad regex");
- let build_specifier = "asset:///bootstrap.ts";
-
- // we need a basic file to send to tsc to warm it up.
- if args.specifier == build_specifier {
- Ok(json!({
- "data": r#"console.log("hello deno!");"#,
- "version": "1",
- // this corresponds to `ts.ScriptKind.TypeScript`
- "scriptKind": 3
- }))
- // specifiers come across as `asset:///lib.{lib_name}.d.ts` and we need to
- // parse out just the name so we can lookup the asset.
- } else if let Some(caps) = re_asset.captures(&args.specifier) {
- if let Some(lib) = caps.get(1).map(|m| m.as_str()) {
- // if it comes from an op crate, we were supplied with the path to the
- // file.
- let path = if let Some(op_crate_lib) = op_crate_libs.get(lib) {
- PathBuf::from(op_crate_lib).canonicalize().unwrap()
- // otherwise we are will generate the path ourself
- } else {
- path_dts.join(format!("lib.{}.d.ts", lib))
- };
- let data = std::fs::read_to_string(path)?;
+ #[op]
+ // using the same op that is used in `tsc.rs` for loading modules and reading
+ // files, but a slightly different implementation at build time.
+ fn op_load(state: &mut OpState, args: LoadArgs) -> Result<Value, AnyError> {
+ let op_crate_libs = state.borrow::<HashMap<&str, PathBuf>>();
+ let path_dts = state.borrow::<PathBuf>();
+ let re_asset =
+ Regex::new(r"asset:/{3}lib\.(\S+)\.d\.ts").expect("bad regex");
+ let build_specifier = "asset:///bootstrap.ts";
+
+ // we need a basic file to send to tsc to warm it up.
+ if args.specifier == build_specifier {
Ok(json!({
- "data": data,
+ "data": r#"console.log("hello deno!");"#,
"version": "1",
// this corresponds to `ts.ScriptKind.TypeScript`
"scriptKind": 3
}))
+ // specifiers come across as `asset:///lib.{lib_name}.d.ts` and we need to
+ // parse out just the name so we can lookup the asset.
+ } else if let Some(caps) = re_asset.captures(&args.specifier) {
+ if let Some(lib) = caps.get(1).map(|m| m.as_str()) {
+ // if it comes from an op crate, we were supplied with the path to the
+ // file.
+ let path = if let Some(op_crate_lib) = op_crate_libs.get(lib) {
+ PathBuf::from(op_crate_lib).canonicalize().unwrap()
+ // otherwise we are will generate the path ourself
+ } else {
+ path_dts.join(format!("lib.{}.d.ts", lib))
+ };
+ let data = std::fs::read_to_string(path)?;
+ Ok(json!({
+ "data": data,
+ "version": "1",
+ // this corresponds to `ts.ScriptKind.TypeScript`
+ "scriptKind": 3
+ }))
+ } else {
+ Err(custom_error(
+ "InvalidSpecifier",
+ format!("An invalid specifier was requested: {}", args.specifier),
+ ))
+ }
} else {
Err(custom_error(
"InvalidSpecifier",
format!("An invalid specifier was requested: {}", args.specifier),
))
}
- } else {
- Err(custom_error(
- "InvalidSpecifier",
- format!("An invalid specifier was requested: {}", args.specifier),
- ))
}
+
+ create_snapshot(CreateSnapshotOptions {
+ cargo_manifest_dir: env!("CARGO_MANIFEST_DIR"),
+ snapshot_path,
+ startup_snapshot: None,
+ extensions: vec![Extension::builder()
+ .ops(vec![
+ op_build_info::decl(),
+ op_cwd::decl(),
+ op_exists::decl(),
+ op_is_node_file::decl(),
+ op_load::decl(),
+ op_script_version::decl(),
+ ])
+ .state(move |state| {
+ state.put(op_crate_libs.clone());
+ state.put(build_libs.clone());
+ state.put(path_dts.clone());
+
+ Ok(())
+ })
+ .build()],
+ additional_files: files,
+ compression_cb: Some(Box::new(|vec, snapshot_slice| {
+ vec.extend_from_slice(
+ &zstd::bulk::compress(snapshot_slice, 22)
+ .expect("snapshot compression failed"),
+ );
+ })),
+ });
}
- let js_runtime = JsRuntime::new(RuntimeOptions {
- will_snapshot: true,
- extensions: vec![Extension::builder()
- .ops(vec![
- op_build_info::decl(),
- op_cwd::decl(),
- op_exists::decl(),
- op_is_node_file::decl(),
- op_load::decl(),
- op_script_version::decl(),
- ])
- .state(move |state| {
- state.put(op_crate_libs.clone());
- state.put(build_libs.clone());
- state.put(path_dts.clone());
-
- Ok(())
- })
- .build()],
- ..Default::default()
- });
- create_snapshot(js_runtime, snapshot_path, files);
+ pub(crate) fn version() -> String {
+ std::fs::read_to_string("tsc/00_typescript.js")
+ .unwrap()
+ .lines()
+ .find(|l| l.contains("ts.version = "))
+ .expect(
+ "Failed to find the pattern `ts.version = ` in typescript source code",
+ )
+ .chars()
+ .skip_while(|c| !char::is_numeric(*c))
+ .take_while(|c| *c != '"')
+ .collect::<String>()
+ }
}
-fn ts_version() -> String {
- std::fs::read_to_string("tsc/00_typescript.js")
- .unwrap()
- .lines()
- .find(|l| l.contains("ts.version = "))
- .expect(
- "Failed to find the pattern `ts.version = ` in typescript source code",
- )
- .chars()
- .skip_while(|c| !char::is_numeric(*c))
- .take_while(|c| *c != '"')
- .collect::<String>()
+fn create_cli_snapshot(snapshot_path: PathBuf, files: Vec<PathBuf>) {
+ let extensions: Vec<Extension> = vec![
+ deno_webidl::init(),
+ deno_console::init(),
+ deno_url::init(),
+ deno_tls::init(),
+ deno_web::init::<Permissions>(
+ deno_web::BlobStore::default(),
+ Default::default(),
+ ),
+ deno_fetch::init::<Permissions>(Default::default()),
+ deno_cache::init::<SqliteBackedCache>(None),
+ deno_websocket::init::<Permissions>("".to_owned(), None, None),
+ deno_webstorage::init(None),
+ deno_crypto::init(None),
+ deno_webgpu::init(false),
+ deno_broadcast_channel::init(
+ deno_broadcast_channel::InMemoryBroadcastChannel::default(),
+ false, // No --unstable.
+ ),
+ deno_node::init::<Permissions>(None), // No --unstable.
+ deno_ffi::init::<Permissions>(false),
+ deno_net::init::<Permissions>(
+ None, false, // No --unstable.
+ None,
+ ),
+ deno_napi::init::<Permissions>(false),
+ deno_http::init(),
+ deno_flash::init::<Permissions>(false), // No --unstable
+ ];
+
+ create_snapshot(CreateSnapshotOptions {
+ cargo_manifest_dir: env!("CARGO_MANIFEST_DIR"),
+ snapshot_path,
+ startup_snapshot: Some(deno_runtime::js::deno_isolate_init()),
+ extensions,
+ additional_files: files,
+ compression_cb: Some(Box::new(|vec, snapshot_slice| {
+ lzzzz::lz4_hc::compress_to_vec(
+ snapshot_slice,
+ vec,
+ lzzzz::lz4_hc::CLEVEL_MAX,
+ )
+ .expect("snapshot compression failed");
+ })),
+ })
}
fn git_commit_hash() -> String {
@@ -386,7 +399,7 @@ fn main() {
println!("cargo:rustc-env=GIT_COMMIT_HASH={}", git_commit_hash());
println!("cargo:rerun-if-env-changed=GIT_COMMIT_HASH");
- println!("cargo:rustc-env=TS_VERSION={}", ts_version());
+ println!("cargo:rustc-env=TS_VERSION={}", ts::version());
println!("cargo:rerun-if-env-changed=TS_VERSION");
println!(
@@ -440,11 +453,14 @@ fn main() {
let c = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
let o = PathBuf::from(env::var_os("OUT_DIR").unwrap());
- // Main snapshot
let compiler_snapshot_path = o.join("COMPILER_SNAPSHOT.bin");
+ let js_files = get_js_files(env!("CARGO_MANIFEST_DIR"), "tsc");
+ ts::create_compiler_snapshot(compiler_snapshot_path, js_files, &c);
- let js_files = get_js_files("tsc");
- create_compiler_snapshot(&compiler_snapshot_path, js_files, &c);
+ let cli_snapshot_path = o.join("CLI_SNAPSHOT.bin");
+ let mut js_files = get_js_files(env!("CARGO_MANIFEST_DIR"), "js");
+ js_files.push(deno_runtime::js::get_99_main());
+ create_cli_snapshot(cli_snapshot_path, js_files);
#[cfg(target_os = "windows")]
{
@@ -462,17 +478,3 @@ fn deno_webgpu_get_declaration() -> PathBuf {
let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
manifest_dir.join("dts").join("lib.deno_webgpu.d.ts")
}
-
-fn get_js_files(d: &str) -> Vec<PathBuf> {
- let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
- let mut js_files = std::fs::read_dir(d)
- .unwrap()
- .map(|dir_entry| {
- let file = dir_entry.unwrap();
- manifest_dir.join(file.path())
- })
- .filter(|path| path.extension().unwrap_or_default() == "js")
- .collect::<Vec<PathBuf>>();
- js_files.sort();
- js_files
-}
diff --git a/cli/js.rs b/cli/js.rs
new file mode 100644
index 000000000..4bf3da627
--- /dev/null
+++ b/cli/js.rs
@@ -0,0 +1,57 @@
+use deno_core::Snapshot;
+use log::debug;
+use once_cell::sync::Lazy;
+
+pub static CLI_SNAPSHOT: Lazy<Box<[u8]>> = Lazy::new(
+ #[allow(clippy::uninit_vec)]
+ #[cold]
+ #[inline(never)]
+ || {
+ static COMPRESSED_CLI_SNAPSHOT: &[u8] =
+ include_bytes!(concat!(env!("OUT_DIR"), "/CLI_SNAPSHOT.bin"));
+
+ let size =
+ u32::from_le_bytes(COMPRESSED_CLI_SNAPSHOT[0..4].try_into().unwrap())
+ as usize;
+ let mut vec = Vec::with_capacity(size);
+
+ // SAFETY: vec is allocated with exact snapshot size (+ alignment)
+ // SAFETY: non zeroed bytes are overwritten with decompressed snapshot
+ unsafe {
+ vec.set_len(size);
+ }
+
+ lzzzz::lz4::decompress(&COMPRESSED_CLI_SNAPSHOT[4..], &mut vec).unwrap();
+
+ vec.into_boxed_slice()
+ },
+);
+
+pub fn deno_isolate_init() -> Snapshot {
+ debug!("Deno isolate init with snapshots.");
+ Snapshot::Static(&CLI_SNAPSHOT)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn runtime_snapshot() {
+ let mut js_runtime = deno_core::JsRuntime::new(deno_core::RuntimeOptions {
+ startup_snapshot: Some(deno_isolate_init()),
+ ..Default::default()
+ });
+ js_runtime
+ .execute_script(
+ "<anon>",
+ r#"
+ if (!(bootstrap.mainRuntime && bootstrap.workerRuntime)) {
+ throw Error("bad");
+ }
+ console.log("we have console.log!!!");
+ "#,
+ )
+ .unwrap();
+ }
+}
diff --git a/cli/js/40_testing.js b/cli/js/40_testing.js
new file mode 100644
index 000000000..864d50104
--- /dev/null
+++ b/cli/js/40_testing.js
@@ -0,0 +1,1412 @@
+// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
+"use strict";
+
+((window) => {
+ const core = window.Deno.core;
+ const ops = core.ops;
+ const { setExitHandler } = window.__bootstrap.os;
+ const { Console } = window.__bootstrap.console;
+ const { serializePermissions } = window.__bootstrap.permissions;
+ const { assert } = window.__bootstrap.infra;
+ const {
+ ArrayFrom,
+ ArrayPrototypeFilter,
+ ArrayPrototypeJoin,
+ ArrayPrototypeMap,
+ ArrayPrototypePush,
+ ArrayPrototypeShift,
+ ArrayPrototypeSort,
+ BigInt,
+ DateNow,
+ Error,
+ FunctionPrototype,
+ Map,
+ MapPrototypeGet,
+ MapPrototypeHas,
+ MapPrototypeSet,
+ MathCeil,
+ ObjectKeys,
+ ObjectPrototypeIsPrototypeOf,
+ Promise,
+ SafeArrayIterator,
+ Set,
+ SymbolToStringTag,
+ TypeError,
+ } = window.__bootstrap.primordials;
+
+ const opSanitizerDelayResolveQueue = [];
+
+ // Even if every resource is closed by the end of a test, there can be a delay
+ // until the pending ops have all finished. This function returns a promise
+ // that resolves when it's (probably) fine to run the op sanitizer.
+ //
+ // This is implemented by adding a macrotask callback that runs after the
+ // timer macrotasks, so we can guarantee that a currently running interval
+ // will have an associated op. An additional `setTimeout` of 0 is needed
+ // before that, though, in order to give time for worker message ops to finish
+ // (since timeouts of 0 don't queue tasks in the timer queue immediately).
+ function opSanitizerDelay() {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ ArrayPrototypePush(opSanitizerDelayResolveQueue, resolve);
+ }, 0);
+ });
+ }
+
+ function handleOpSanitizerDelayMacrotask() {
+ ArrayPrototypeShift(opSanitizerDelayResolveQueue)?.();
+ return opSanitizerDelayResolveQueue.length === 0;
+ }
+
+ // An async operation to $0 was started in this test, but never completed. This is often caused by not $1.
+ // An async operation to $0 was started in this test, but never completed. Async operations should not complete in a test if they were not started in that test.
+ // deno-fmt-ignore
+ const OP_DETAILS = {
+ "op_blob_read_part": ["read from a Blob or File", "awaiting the result of a Blob or File read"],
+ "op_broadcast_recv": ["receive a message from a BroadcastChannel", "closing the BroadcastChannel"],
+ "op_broadcast_send": ["send a message to a BroadcastChannel", "closing the BroadcastChannel"],
+ "op_chmod_async": ["change the permissions of a file", "awaiting the result of a `Deno.chmod` call"],
+ "op_chown_async": ["change the owner of a file", "awaiting the result of a `Deno.chown` call"],
+ "op_copy_file_async": ["copy a file", "awaiting the result of a `Deno.copyFile` call"],
+ "op_crypto_decrypt": ["decrypt data", "awaiting the result of a `crypto.subtle.decrypt` call"],
+ "op_crypto_derive_bits": ["derive bits from a key", "awaiting the result of a `crypto.subtle.deriveBits` call"],
+ "op_crypto_encrypt": ["encrypt data", "awaiting the result of a `crypto.subtle.encrypt` call"],
+ "op_crypto_generate_key": ["generate a key", "awaiting the result of a `crypto.subtle.generateKey` call"],
+ "op_crypto_sign_key": ["sign data", "awaiting the result of a `crypto.subtle.sign` call"],
+ "op_crypto_subtle_digest": ["digest data", "awaiting the result of a `crypto.subtle.digest` call"],
+ "op_crypto_verify_key": ["verify data", "awaiting the result of a `crypto.subtle.verify` call"],
+ "op_net_recv_udp": ["receive a datagram message via UDP", "awaiting the result of `Deno.DatagramConn#receive` call, or not breaking out of a for await loop looping over a `Deno.DatagramConn`"],
+ "op_net_recv_unixpacket": ["receive a datagram message via Unixpacket", "awaiting the result of `Deno.DatagramConn#receive` call, or not breaking out of a for await loop looping over a `Deno.DatagramConn`"],
+ "op_net_send_udp": ["send a datagram message via UDP", "awaiting the result of `Deno.DatagramConn#send` call"],
+ "op_net_send_unixpacket": ["send a datagram message via Unixpacket", "awaiting the result of `Deno.DatagramConn#send` call"],
+ "op_dns_resolve": ["resolve a DNS name", "awaiting the result of a `Deno.resolveDns` call"],
+ "op_fdatasync_async": ["flush pending data operations for a file to disk", "awaiting the result of a `Deno.fdatasync` call"],
+ "op_fetch_send": ["send a HTTP request", "awaiting the result of a `fetch` call"],
+ "op_ffi_call_nonblocking": ["do a non blocking ffi call", "awaiting the returned promise"] ,
+ "op_ffi_call_ptr_nonblocking": ["do a non blocking ffi call", "awaiting the returned promise"],
+ "op_flock_async": ["lock a file", "awaiting the result of a `Deno.flock` call"],
+ "op_fs_events_poll": ["get the next file system event", "breaking out of a for await loop looping over `Deno.FsEvents`"],
+ "op_fstat_async": ["get file metadata", "awaiting the result of a `Deno.File#fstat` call"],
+ "op_fsync_async": ["flush pending data operations for a file to disk", "awaiting the result of a `Deno.fsync` call"],
+ "op_ftruncate_async": ["truncate a file", "awaiting the result of a `Deno.ftruncate` call"],
+ "op_funlock_async": ["unlock a file", "awaiting the result of a `Deno.funlock` call"],
+ "op_futime_async": ["change file timestamps", "awaiting the result of a `Deno.futime` call"],
+ "op_http_accept": ["accept a HTTP request", "closing a `Deno.HttpConn`"],
+ "op_http_shutdown": ["shutdown a HTTP connection", "awaiting `Deno.HttpEvent#respondWith`"],
+ "op_http_upgrade_websocket": ["upgrade a HTTP connection to a WebSocket", "awaiting `Deno.HttpEvent#respondWith`"],
+ "op_http_write_headers": ["write HTTP response headers", "awaiting `Deno.HttpEvent#respondWith`"],
+ "op_http_write": ["write HTTP response body", "awaiting `Deno.HttpEvent#respondWith`"],
+ "op_link_async": ["create a hard link", "awaiting the result of a `Deno.link` call"],
+ "op_make_temp_dir_async": ["create a temporary directory", "awaiting the result of a `Deno.makeTempDir` call"],
+ "op_make_temp_file_async": ["create a temporary file", "awaiting the result of a `Deno.makeTempFile` call"],
+ "op_message_port_recv_message": ["receive a message from a MessagePort", "awaiting the result of not closing a `MessagePort`"],
+ "op_mkdir_async": ["create a directory", "awaiting the result of a `Deno.mkdir` call"],
+ "op_net_accept_tcp": ["accept a TCP stream", "closing a `Deno.Listener`"],
+ "op_net_accept_unix": ["accept a Unix stream", "closing a `Deno.Listener`"],
+ "op_net_connect_tcp": ["connect to a TCP server", "awaiting a `Deno.connect` call"],
+ "op_net_connect_unix": ["connect to a Unix server", "awaiting a `Deno.connect` call"],
+ "op_open_async": ["open a file", "awaiting the result of a `Deno.open` call"],
+ "op_read_dir_async": ["read a directory", "collecting all items in the async iterable returned from a `Deno.readDir` call"],
+ "op_read_link_async": ["read a symlink", "awaiting the result of a `Deno.readLink` call"],
+ "op_realpath_async": ["resolve a path", "awaiting the result of a `Deno.realpath` call"],
+ "op_remove_async": ["remove a file or directory", "awaiting the result of a `Deno.remove` call"],
+ "op_rename_async": ["rename a file or directory", "awaiting the result of a `Deno.rename` call"],
+ "op_run_status": ["get the status of a subprocess", "awaiting the result of a `Deno.Process#status` call"],
+ "op_seek_async": ["seek in a file", "awaiting the result of a `Deno.File#seek` call"],
+ "op_signal_poll": ["get the next signal", "un-registering a OS signal handler"],
+ "op_sleep": ["sleep for a duration", "cancelling a `setTimeout` or `setInterval` call"],
+ "op_stat_async": ["get file metadata", "awaiting the result of a `Deno.stat` call"],
+ "op_symlink_async": ["create a symlink", "awaiting the result of a `Deno.symlink` call"],
+ "op_net_accept_tls": ["accept a TLS stream", "closing a `Deno.TlsListener`"],
+ "op_net_connect_tls": ["connect to a TLS server", "awaiting a `Deno.connectTls` call"],
+ "op_tls_handshake": ["perform a TLS handshake", "awaiting a `Deno.TlsConn#handshake` call"],
+ "op_tls_start": ["start a TLS connection", "awaiting a `Deno.startTls` call"],
+ "op_truncate_async": ["truncate a file", "awaiting the result of a `Deno.truncate` call"],
+ "op_utime_async": ["change file timestamps", "awaiting the result of a `Deno.utime` call"],
+ "op_webgpu_buffer_get_map_async": ["map a WebGPU buffer", "awaiting the result of a `GPUBuffer#mapAsync` call"],
+ "op_webgpu_request_adapter": ["request a WebGPU adapter", "awaiting the result of a `navigator.gpu.requestAdapter` call"],
+ "op_webgpu_request_device": ["request a WebGPU device", "awaiting the result of a `GPUAdapter#requestDevice` call"],
+ "op_worker_recv_message": ["receive a message from a web worker", "terminating a `Worker`"],
+ "op_ws_close": ["close a WebSocket", "awaiting until the `close` event is emitted on a `WebSocket`, or the `WebSocketStream#closed` promise resolves"],
+ "op_ws_create": ["create a WebSocket", "awaiting until the `open` event is emitted on a `WebSocket`, or the result of a `WebSocketStream#connection` promise"],
+ "op_ws_next_event": ["receive the next message on a WebSocket", "closing a `WebSocket` or `WebSocketStream`"],
+ "op_ws_send": ["send a message on a WebSocket", "closing a `WebSocket` or `WebSocketStream`"],
+ };
+
+ // Wrap test function in additional assertion that makes sure
+ // the test case does not leak async "ops" - ie. number of async
+ // completed ops after the test is the same as number of dispatched
+ // ops. Note that "unref" ops are ignored since in nature that are
+ // optional.
+ function assertOps(fn) {
+ /** @param desc {TestDescription | TestStepDescription} */
+ return async function asyncOpSanitizer(desc) {
+ const pre = core.metrics();
+ const preTraces = new Map(core.opCallTraces);
+ try {
+ await fn(desc);
+ } finally {
+ // Defer until next event loop turn - that way timeouts and intervals
+ // cleared can actually be removed from resource table, otherwise
+ // false positives may occur (https://github.com/denoland/deno/issues/4591)
+ await opSanitizerDelay();
+ await opSanitizerDelay();
+ }
+
+ if (shouldSkipSanitizers(desc)) return;
+
+ const post = core.metrics();
+ const postTraces = new Map(core.opCallTraces);
+
+ // We're checking diff because one might spawn HTTP server in the background
+ // that will be a pending async op before test starts.
+ const dispatchedDiff = post.opsDispatchedAsync - pre.opsDispatchedAsync;
+ const completedDiff = post.opsCompletedAsync - pre.opsCompletedAsync;
+
+ if (dispatchedDiff === completedDiff) return;
+
+ const details = [];
+ for (const key in post.ops) {
+ const preOp = pre.ops[key] ??
+ { opsDispatchedAsync: 0, opsCompletedAsync: 0 };
+ const postOp = post.ops[key];
+ const dispatchedDiff = postOp.opsDispatchedAsync -
+ preOp.opsDispatchedAsync;
+ const completedDiff = postOp.opsCompletedAsync -
+ preOp.opsCompletedAsync;
+
+ if (dispatchedDiff > completedDiff) {
+ const [name, hint] = OP_DETAILS[key] || [key, null];
+ const count = dispatchedDiff - completedDiff;
+ let message = `${count} async operation${
+ count === 1 ? "" : "s"
+ } to ${name} ${
+ count === 1 ? "was" : "were"
+ } started in this test, but never completed.`;
+ if (hint) {
+ message += ` This is often caused by not ${hint}.`;
+ }
+ const traces = [];
+ for (const [id, { opName, stack }] of postTraces) {
+ if (opName !== key) continue;
+ if (MapPrototypeHas(preTraces, id)) continue;
+ ArrayPrototypePush(traces, stack);
+ }
+ if (traces.length === 1) {
+ message += " The operation was started here:\n";
+ message += traces[0];
+ } else if (traces.length > 1) {
+ message += " The operations were started here:\n";
+ message += ArrayPrototypeJoin(traces, "\n\n");
+ }
+ ArrayPrototypePush(details, message);
+ } else if (dispatchedDiff < completedDiff) {
+ const [name, hint] = OP_DETAILS[key] || [key, null];
+ const count = completedDiff - dispatchedDiff;
+ ArrayPrototypePush(
+ details,
+ `${count} async operation${count === 1 ? "" : "s"} to ${name} ${
+ count === 1 ? "was" : "were"
+ } started before this test, but ${
+ count === 1 ? "was" : "were"
+ } completed during the test. Async operations should not complete in a test if they were not started in that test.
+ ${hint ? `This is often caused by not ${hint}.` : ""}`,
+ );
+ }
+ }
+
+ let msg = `Test case is leaking async ops.
+
+ - ${ArrayPrototypeJoin(details, "\n - ")}`;
+
+ if (!core.isOpCallTracingEnabled()) {
+ msg +=
+ `\n\nTo get more details where ops were leaked, run again with --trace-ops flag.`;
+ } else {
+ msg += "\n";
+ }
+
+ throw assert(false, msg);
+ };
+ }
+
+ function prettyResourceNames(name) {
+ switch (name) {
+ case "fsFile":
+ return ["A file", "opened", "closed"];
+ case "fetchRequest":
+ return ["A fetch request", "started", "finished"];
+ case "fetchRequestBody":
+ return ["A fetch request body", "created", "closed"];
+ case "fetchResponseBody":
+ return ["A fetch response body", "created", "consumed"];
+ case "httpClient":
+ return ["An HTTP client", "created", "closed"];
+ case "dynamicLibrary":
+ return ["A dynamic library", "loaded", "unloaded"];
+ case "httpConn":
+ return ["An inbound HTTP connection", "accepted", "closed"];
+ case "httpStream":
+ return ["An inbound HTTP request", "accepted", "closed"];
+ case "tcpStream":
+ return ["A TCP connection", "opened/accepted", "closed"];
+ case "unixStream":
+ return ["A Unix connection", "opened/accepted", "closed"];
+ case "tlsStream":
+ return ["A TLS connection", "opened/accepted", "closed"];
+ case "tlsListener":
+ return ["A TLS listener", "opened", "closed"];
+ case "unixListener":
+ return ["A Unix listener", "opened", "closed"];
+ case "unixDatagram":
+ return ["A Unix datagram", "opened", "closed"];
+ case "tcpListener":
+ return ["A TCP listener", "opened", "closed"];
+ case "udpSocket":
+ return ["A UDP socket", "opened", "closed"];
+ case "timer":
+ return ["A timer", "started", "fired/cleared"];
+ case "textDecoder":
+ return ["A text decoder", "created", "finished"];
+ case "messagePort":
+ return ["A message port", "created", "closed"];
+ case "webSocketStream":
+ return ["A WebSocket", "opened", "closed"];
+ case "fsEvents":
+ return ["A file system watcher", "created", "closed"];
+ case "childStdin":
+ return ["A child process stdin", "opened", "closed"];
+ case "childStdout":
+ return ["A child process stdout", "opened", "closed"];
+ case "childStderr":
+ return ["A child process stderr", "opened", "closed"];
+ case "child":
+ return ["A child process", "started", "closed"];
+ case "signal":
+ return ["A signal listener", "created", "fired/cleared"];
+ case "stdin":
+ return ["The stdin pipe", "opened", "closed"];
+ case "stdout":
+ return ["The stdout pipe", "opened", "closed"];
+ case "stderr":
+ return ["The stderr pipe", "opened", "closed"];
+ case "compression":
+ return ["A CompressionStream", "created", "closed"];
+ default:
+ return [`A "${name}" resource`, "created", "cleaned up"];
+ }
+ }
+
+ function resourceCloseHint(name) {
+ switch (name) {
+ case "fsFile":
+ return "Close the file handle by calling `file.close()`.";
+ case "fetchRequest":
+ return "Await the promise returned from `fetch()` or abort the fetch with an abort signal.";
+ case "fetchRequestBody":
+ return "Terminate the request body `ReadableStream` by closing or erroring it.";
+ case "fetchResponseBody":
+ return "Consume or close the response body `ReadableStream`, e.g `await resp.text()` or `await resp.body.cancel()`.";
+ case "httpClient":
+ return "Close the HTTP client by calling `httpClient.close()`.";
+ case "dynamicLibrary":
+ return "Unload the dynamic library by calling `dynamicLibrary.close()`.";
+ case "httpConn":
+ return "Close the inbound HTTP connection by calling `httpConn.close()`.";
+ case "httpStream":
+ return "Close the inbound HTTP request by responding with `e.respondWith().` or closing the HTTP connection.";
+ case "tcpStream":
+ return "Close the TCP connection by calling `tcpConn.close()`.";
+ case "unixStream":
+ return "Close the Unix socket connection by calling `unixConn.close()`.";
+ case "tlsStream":
+ return "Close the TLS connection by calling `tlsConn.close()`.";
+ case "tlsListener":
+ return "Close the TLS listener by calling `tlsListener.close()`.";
+ case "unixListener":
+ return "Close the Unix socket listener by calling `unixListener.close()`.";
+ case "unixDatagram":
+ return "Close the Unix datagram socket by calling `unixDatagram.close()`.";
+ case "tcpListener":
+ return "Close the TCP listener by calling `tcpListener.close()`.";
+ case "udpSocket":
+ return "Close the UDP socket by calling `udpSocket.close()`.";
+ case "timer":
+ return "Clear the timer by calling `clearInterval` or `clearTimeout`.";
+ case "textDecoder":
+ return "Close the text decoder by calling `textDecoder.decode('')` or `await textDecoderStream.readable.cancel()`.";
+ case "messagePort":
+ return "Close the message port by calling `messagePort.close()`.";
+ case "webSocketStream":
+ return "Close the WebSocket by calling `webSocket.close()`.";
+ case "fsEvents":
+ return "Close the file system watcher by calling `watcher.close()`.";
+ case "childStdin":
+ return "Close the child process stdin by calling `proc.stdin.close()`.";
+ case "childStdout":
+ return "Close the child process stdout by calling `proc.stdout.close()`.";
+ case "childStderr":
+ return "Close the child process stderr by calling `proc.stderr.close()`.";
+ case "child":
+ return "Close the child process by calling `proc.kill()` or `proc.close()`.";
+ case "signal":
+ return "Clear the signal listener by calling `Deno.removeSignalListener`.";
+ case "stdin":
+ return "Close the stdin pipe by calling `Deno.stdin.close()`.";
+ case "stdout":
+ return "Close the stdout pipe by calling `Deno.stdout.close()`.";
+ case "stderr":
+ return "Close the stderr pipe by calling `Deno.stderr.close()`.";
+ case "compression":
+ return "Close the compression stream by calling `await stream.writable.close()`.";
+ default:
+ return "Close the resource before the end of the test.";
+ }
+ }
+
+ // Wrap test function in additional assertion that makes sure
+ // the test case does not "leak" resources - ie. resource table after
+ // the test has exactly the same contents as before the test.
+ function assertResources(fn) {
+ /** @param desc {TestDescription | TestStepDescription} */
+ return async function resourceSanitizer(desc) {
+ const pre = core.resources();
+ await fn(desc);
+
+ if (shouldSkipSanitizers(desc)) {
+ return;
+ }
+
+ const post = core.resources();
+
+ const allResources = new Set([
+ ...new SafeArrayIterator(ObjectKeys(pre)),
+ ...new SafeArrayIterator(ObjectKeys(post)),
+ ]);
+
+ const details = [];
+ for (const resource of allResources) {
+ const preResource = pre[resource];
+ const postResource = post[resource];
+ if (preResource === postResource) continue;
+
+ if (preResource === undefined) {
+ const [name, action1, action2] = prettyResourceNames(postResource);
+ const hint = resourceCloseHint(postResource);
+ const detail =
+ `${name} (rid ${resource}) was ${action1} during the test, but not ${action2} during the test. ${hint}`;
+ ArrayPrototypePush(details, detail);
+ } else {
+ const [name, action1, action2] = prettyResourceNames(preResource);
+ const detail =
+ `${name} (rid ${resource}) was ${action1} before the test started, but was ${action2} during the test. Do not close resources in a test that were not created during that test.`;
+ ArrayPrototypePush(details, detail);
+ }
+ }
+
+ const message = `Test case is leaking ${details.length} resource${
+ details.length === 1 ? "" : "s"
+ }:
+
+ - ${details.join("\n - ")}
+`;
+ assert(details.length === 0, message);
+ };
+ }
+
+ // Wrap test function in additional assertion that makes sure
+ // that the test case does not accidentally exit prematurely.
+ function assertExit(fn, isTest) {
+ return async function exitSanitizer(...params) {
+ setExitHandler((exitCode) => {
+ assert(
+ false,
+ `${
+ isTest ? "Test case" : "Bench"
+ } attempted to exit with exit code: ${exitCode}`,
+ );
+ });
+
+ try {
+ await fn(...new SafeArrayIterator(params));
+ } catch (err) {
+ throw err;
+ } finally {
+ setExitHandler(null);
+ }
+ };
+ }
+
+ function assertTestStepScopes(fn) {
+ /** @param desc {TestDescription | TestStepDescription} */
+ return async function testStepSanitizer(desc) {
+ preValidation();
+ // only report waiting after pre-validation
+ if (canStreamReporting(desc) && "parent" in desc) {
+ stepReportWait(desc);
+ }
+ await fn(MapPrototypeGet(testStates, desc.id).context);
+ testStepPostValidation(desc);
+
+ function preValidation() {
+ const runningStepDescs = getRunningStepDescs();
+ const runningStepDescsWithSanitizers = ArrayPrototypeFilter(
+ runningStepDescs,
+ (d) => usesSanitizer(d),
+ );
+
+ if (runningStepDescsWithSanitizers.length > 0) {
+ throw new Error(
+ "Cannot start test step while another test step with sanitizers is running.\n" +
+ runningStepDescsWithSanitizers
+ .map((d) => ` * ${getFullName(d)}`)
+ .join("\n"),
+ );
+ }
+
+ if (usesSanitizer(desc) && runningStepDescs.length > 0) {
+ throw new Error(
+ "Cannot start test step with sanitizers while another test step is running.\n" +
+ runningStepDescs.map((d) => ` * ${getFullName(d)}`).join("\n"),
+ );
+ }
+
+ function getRunningStepDescs() {
+ const results = [];
+ let childDesc = desc;
+ while (childDesc.parent != null) {
+ const state = MapPrototypeGet(testStates, childDesc.parent.id);
+ for (const siblingDesc of state.children) {
+ if (siblingDesc.id == childDesc.id) {
+ continue;
+ }
+ const siblingState = MapPrototypeGet(testStates, siblingDesc.id);
+ if (!siblingState.finalized) {
+ ArrayPrototypePush(results, siblingDesc);
+ }
+ }
+ childDesc = childDesc.parent;
+ }
+ return results;
+ }
+ }
+ };
+ }
+
+ function testStepPostValidation(desc) {
+ // check for any running steps
+ for (const childDesc of MapPrototypeGet(testStates, desc.id).children) {
+ if (MapPrototypeGet(testStates, childDesc.id).status == "pending") {
+ throw new Error(
+ "There were still test steps running after the current scope finished execution. Ensure all steps are awaited (ex. `await t.step(...)`).",
+ );
+ }
+ }
+
+ // check if an ancestor already completed
+ let currentDesc = desc.parent;
+ while (currentDesc != null) {
+ if (MapPrototypeGet(testStates, currentDesc.id).finalized) {
+ throw new Error(
+ "Parent scope completed before test step finished execution. Ensure all steps are awaited (ex. `await t.step(...)`).",
+ );
+ }
+ currentDesc = currentDesc.parent;
+ }
+ }
+
+ function pledgePermissions(permissions) {
+ return ops.op_pledge_test_permissions(
+ serializePermissions(permissions),
+ );
+ }
+
+ function restorePermissions(token) {
+ ops.op_restore_test_permissions(token);
+ }
+
+ function withPermissions(fn, permissions) {
+ return async function applyPermissions(...params) {
+ const token = pledgePermissions(permissions);
+
+ try {
+ await fn(...new SafeArrayIterator(params));
+ } finally {
+ restorePermissions(token);
+ }
+ };
+ }
+
+ /**
+ * @typedef {{
+ * id: number,
+ * name: string,
+ * fn: TestFunction
+ * origin: string,
+ * location: TestLocation,
+ * filteredOut: boolean,
+ * ignore: boolean,
+ * only: boolean.
+ * sanitizeOps: boolean,
+ * sanitizeResources: boolean,
+ * sanitizeExit: boolean,
+ * permissions: PermissionOptions,
+ * }} TestDescription
+ *
+ * @typedef {{
+ * id: number,
+ * name: string,
+ * fn: TestFunction
+ * origin: string,
+ * location: TestLocation,
+ * ignore: boolean,
+ * level: number,
+ * parent: TestDescription | TestStepDescription,
+ * rootId: number,
+ * rootName: String,
+ * sanitizeOps: boolean,
+ * sanitizeResources: boolean,
+ * sanitizeExit: boolean,
+ * }} TestStepDescription
+ *
+ * @typedef {{
+ * context: TestContext,
+ * children: TestStepDescription[],
+ * finalized: boolean,
+ * }} TestState
+ *
+ * @typedef {{
+ * context: TestContext,
+ * children: TestStepDescription[],
+ * finalized: boolean,
+ * status: "pending" | "ok" | ""failed" | ignored",
+ * error: unknown,
+ * elapsed: number | null,
+ * reportedWait: boolean,
+ * reportedResult: boolean,
+ * }} TestStepState
+ *
+ * @typedef {{
+ * id: number,
+ * name: string,
+ * fn: BenchFunction
+ * origin: string,
+ * filteredOut: boolean,
+ * ignore: boolean,
+ * only: boolean.
+ * sanitizeExit: boolean,
+ * permissions: PermissionOptions,
+ * }} BenchDescription
+ */
+
+ /** @type {TestDescription[]} */
+ const testDescs = [];
+ /** @type {Map<number, TestState | TestStepState>} */
+ const testStates = new Map();
+ /** @type {BenchDescription[]} */
+ const benchDescs = [];
+ let isTestSubcommand = false;
+ let isBenchSubcommand = false;
+
+ // Main test function provided by Deno.
+ function test(
+ nameOrFnOrOptions,
+ optionsOrFn,
+ maybeFn,
+ ) {
+ if (!isTestSubcommand) {
+ return;
+ }
+
+ let testDesc;
+ const defaults = {
+ ignore: false,
+ only: false,
+ sanitizeOps: true,
+ sanitizeResources: true,
+ sanitizeExit: true,
+ permissions: null,
+ };
+
+ if (typeof nameOrFnOrOptions === "string") {
+ if (!nameOrFnOrOptions) {
+ throw new TypeError("The test name can't be empty");
+ }
+ if (typeof optionsOrFn === "function") {
+ testDesc = { fn: optionsOrFn, name: nameOrFnOrOptions, ...defaults };
+ } else {
+ if (!maybeFn || typeof maybeFn !== "function") {
+ throw new TypeError("Missing test function");
+ }
+ if (optionsOrFn.fn != undefined) {
+ throw new TypeError(
+ "Unexpected 'fn' field in options, test function is already provided as the third argument.",
+ );
+ }
+ if (optionsOrFn.name != undefined) {
+ throw new TypeError(
+ "Unexpected 'name' field in options, test name is already provided as the first argument.",
+ );
+ }
+ testDesc = {
+ ...defaults,
+ ...optionsOrFn,
+ fn: maybeFn,
+ name: nameOrFnOrOptions,
+ };
+ }
+ } else if (typeof nameOrFnOrOptions === "function") {
+ if (!nameOrFnOrOptions.name) {
+ throw new TypeError("The test function must have a name");
+ }
+ if (optionsOrFn != undefined) {
+ throw new TypeError("Unexpected second argument to Deno.test()");
+ }
+ if (maybeFn != undefined) {
+ throw new TypeError("Unexpected third argument to Deno.test()");
+ }
+ testDesc = {
+ ...defaults,
+ fn: nameOrFnOrOptions,
+ name: nameOrFnOrOptions.name,
+ };
+ } else {
+ let fn;
+ let name;
+ if (typeof optionsOrFn === "function") {
+ fn = optionsOrFn;
+ if (nameOrFnOrOptions.fn != undefined) {
+ throw new TypeError(
+ "Unexpected 'fn' field in options, test function is already provided as the second argument.",
+ );
+ }
+ name = nameOrFnOrOptions.name ?? fn.name;
+ } else {
+ if (
+ !nameOrFnOrOptions.fn || typeof nameOrFnOrOptions.fn !== "function"
+ ) {
+ throw new TypeError(
+ "Expected 'fn' field in the first argument to be a test function.",
+ );
+ }
+ fn = nameOrFnOrOptions.fn;
+ name = nameOrFnOrOptions.name ?? fn.name;
+ }
+ if (!name) {
+ throw new TypeError("The test name can't be empty");
+ }
+ testDesc = { ...defaults, ...nameOrFnOrOptions, fn, name };
+ }
+
+ // Delete this prop in case the user passed it. It's used to detect steps.
+ delete testDesc.parent;
+ testDesc.fn = wrapTestFnWithSanitizers(testDesc.fn, testDesc);
+ if (testDesc.permissions) {
+ testDesc.fn = withPermissions(
+ testDesc.fn,
+ testDesc.permissions,
+ );
+ }
+ testDesc.origin = getTestOrigin();
+ const jsError = Deno.core.destructureError(new Error());
+ testDesc.location = {
+ fileName: jsError.frames[1].fileName,
+ lineNumber: jsError.frames[1].lineNumber,
+ columnNumber: jsError.frames[1].columnNumber,
+ };
+
+ const { id, filteredOut } = ops.op_register_test(testDesc);
+ testDesc.id = id;
+ testDesc.filteredOut = filteredOut;
+
+ ArrayPrototypePush(testDescs, testDesc);
+ MapPrototypeSet(testStates, testDesc.id, {
+ context: createTestContext(testDesc),
+ children: [],
+ finalized: false,
+ });
+ }
+
+ // Main bench function provided by Deno.
+ function bench(
+ nameOrFnOrOptions,
+ optionsOrFn,
+ maybeFn,
+ ) {
+ if (!isBenchSubcommand) {
+ return;
+ }
+
+ let benchDesc;
+ const defaults = {
+ ignore: false,
+ baseline: false,
+ only: false,
+ sanitizeExit: true,
+ permissions: null,
+ };
+
+ if (typeof nameOrFnOrOptions === "string") {
+ if (!nameOrFnOrOptions) {
+ throw new TypeError("The bench name can't be empty");
+ }
+ if (typeof optionsOrFn === "function") {
+ benchDesc = { fn: optionsOrFn, name: nameOrFnOrOptions, ...defaults };
+ } else {
+ if (!maybeFn || typeof maybeFn !== "function") {
+ throw new TypeError("Missing bench function");
+ }
+ if (optionsOrFn.fn != undefined) {
+ throw new TypeError(
+ "Unexpected 'fn' field in options, bench function is already provided as the third argument.",
+ );
+ }
+ if (optionsOrFn.name != undefined) {
+ throw new TypeError(
+ "Unexpected 'name' field in options, bench name is already provided as the first argument.",
+ );
+ }
+ benchDesc = {
+ ...defaults,
+ ...optionsOrFn,
+ fn: maybeFn,
+ name: nameOrFnOrOptions,
+ };
+ }
+ } else if (typeof nameOrFnOrOptions === "function") {
+ if (!nameOrFnOrOptions.name) {
+ throw new TypeError("The bench function must have a name");
+ }
+ if (optionsOrFn != undefined) {
+ throw new TypeError("Unexpected second argument to Deno.bench()");
+ }
+ if (maybeFn != undefined) {
+ throw new TypeError("Unexpected third argument to Deno.bench()");
+ }
+ benchDesc = {
+ ...defaults,
+ fn: nameOrFnOrOptions,
+ name: nameOrFnOrOptions.name,
+ };
+ } else {
+ let fn;
+ let name;
+ if (typeof optionsOrFn === "function") {
+ fn = optionsOrFn;
+ if (nameOrFnOrOptions.fn != undefined) {
+ throw new TypeError(
+ "Unexpected 'fn' field in options, bench function is already provided as the second argument.",
+ );
+ }
+ name = nameOrFnOrOptions.name ?? fn.name;
+ } else {
+ if (
+ !nameOrFnOrOptions.fn || typeof nameOrFnOrOptions.fn !== "function"
+ ) {
+ throw new TypeError(
+ "Expected 'fn' field in the first argument to be a bench function.",
+ );
+ }
+ fn = nameOrFnOrOptions.fn;
+ name = nameOrFnOrOptions.name ?? fn.name;
+ }
+ if (!name) {
+ throw new TypeError("The bench name can't be empty");
+ }
+ benchDesc = { ...defaults, ...nameOrFnOrOptions, fn, name };
+ }
+
+ benchDesc.origin = getBenchOrigin();
+ const AsyncFunction = (async () => {}).constructor;
+ benchDesc.async = AsyncFunction === benchDesc.fn.constructor;
+
+ const { id, filteredOut } = ops.op_register_bench(benchDesc);
+ benchDesc.id = id;
+ benchDesc.filteredOut = filteredOut;
+
+ ArrayPrototypePush(benchDescs, benchDesc);
+ }
+
+ async function runTest(desc) {
+ if (desc.ignore) {
+ return "ignored";
+ }
+
+ try {
+ await desc.fn(desc);
+ const failCount = failedChildStepsCount(desc);
+ return failCount === 0 ? "ok" : {
+ "failed": core.destructureError(
+ new Error(
+ `${failCount} test step${failCount === 1 ? "" : "s"} failed.`,
+ ),
+ ),
+ };
+ } catch (error) {
+ return {
+ "failed": core.destructureError(error),
+ };
+ } finally {
+ const state = MapPrototypeGet(testStates, desc.id);
+ state.finalized = true;
+ // ensure the children report their result
+ for (const childDesc of state.children) {
+ stepReportResult(childDesc);
+ }
+ }
+ }
+
+ function compareMeasurements(a, b) {
+ if (a > b) return 1;
+ if (a < b) return -1;
+
+ return 0;
+ }
+
+ function benchStats(n, highPrecision, avg, min, max, all) {
+ return {
+ n,
+ min,
+ max,
+ p75: all[MathCeil(n * (75 / 100)) - 1],
+ p99: all[MathCeil(n * (99 / 100)) - 1],
+ p995: all[MathCeil(n * (99.5 / 100)) - 1],
+ p999: all[MathCeil(n * (99.9 / 100)) - 1],
+ avg: !highPrecision ? (avg / n) : MathCeil(avg / n),
+ };
+ }
+
+ async function benchMeasure(timeBudget, desc) {
+ const fn = desc.fn;
+ let n = 0;
+ let avg = 0;
+ let wavg = 0;
+ const all = [];
+ let min = Infinity;
+ let max = -Infinity;
+ const lowPrecisionThresholdInNs = 1e4;
+
+ // warmup step
+ let c = 0;
+ let iterations = 20;
+ let budget = 10 * 1e6;
+
+ if (!desc.async) {
+ while (budget > 0 || iterations-- > 0) {
+ const t1 = benchNow();
+
+ fn();
+ const iterationTime = benchNow() - t1;
+
+ c++;
+ wavg += iterationTime;
+ budget -= iterationTime;
+ }
+ } else {
+ while (budget > 0 || iterations-- > 0) {
+ const t1 = benchNow();
+
+ await fn();
+ const iterationTime = benchNow() - t1;
+
+ c++;
+ wavg += iterationTime;
+ budget -= iterationTime;
+ }
+ }
+
+ wavg /= c;
+
+ // measure step
+ if (wavg > lowPrecisionThresholdInNs) {
+ let iterations = 10;
+ let budget = timeBudget * 1e6;
+
+ if (!desc.async) {
+ while (budget > 0 || iterations-- > 0) {
+ const t1 = benchNow();
+
+ fn();
+ const iterationTime = benchNow() - t1;
+
+ n++;
+ avg += iterationTime;
+ budget -= iterationTime;
+ ArrayPrototypePush(all, iterationTime);
+ if (iterationTime < min) min = iterationTime;
+ if (iterationTime > max) max = iterationTime;
+ }
+ } else {
+ while (budget > 0 || iterations-- > 0) {
+ const t1 = benchNow();
+
+ await fn();
+ const iterationTime = benchNow() - t1;
+
+ n++;
+ avg += iterationTime;
+ budget -= iterationTime;
+ ArrayPrototypePush(all, iterationTime);
+ if (iterationTime < min) min = iterationTime;
+ if (iterationTime > max) max = iterationTime;
+ }
+ }
+ } else {
+ let iterations = 10;
+ let budget = timeBudget * 1e6;
+
+ if (!desc.async) {
+ while (budget > 0 || iterations-- > 0) {
+ const t1 = benchNow();
+ for (let c = 0; c < lowPrecisionThresholdInNs; c++) fn();
+ const iterationTime = (benchNow() - t1) / lowPrecisionThresholdInNs;
+
+ n++;
+ avg += iterationTime;
+ ArrayPrototypePush(all, iterationTime);
+ if (iterationTime < min) min = iterationTime;
+ if (iterationTime > max) max = iterationTime;
+ budget -= iterationTime * lowPrecisionThresholdInNs;
+ }
+ } else {
+ while (budget > 0 || iterations-- > 0) {
+ const t1 = benchNow();
+ for (let c = 0; c < lowPrecisionThresholdInNs; c++) await fn();
+ const iterationTime = (benchNow() - t1) / lowPrecisionThresholdInNs;
+
+ n++;
+ avg += iterationTime;
+ ArrayPrototypePush(all, iterationTime);
+ if (iterationTime < min) min = iterationTime;
+ if (iterationTime > max) max = iterationTime;
+ budget -= iterationTime * lowPrecisionThresholdInNs;
+ }
+ }
+ }
+
+ all.sort(compareMeasurements);
+ return benchStats(n, wavg > lowPrecisionThresholdInNs, avg, min, max, all);
+ }
+
+ async function runBench(desc) {
+ let token = null;
+
+ try {
+ if (desc.permissions) {
+ token = pledgePermissions(desc.permissions);
+ }
+
+ if (desc.sanitizeExit) {
+ setExitHandler((exitCode) => {
+ assert(
+ false,
+ `Bench attempted to exit with exit code: ${exitCode}`,
+ );
+ });
+ }
+
+ const benchTimeInMs = 500;
+ const stats = await benchMeasure(benchTimeInMs, desc);
+
+ return { ok: stats };
+ } catch (error) {
+ return { failed: core.destructureError(error) };
+ } finally {
+ if (bench.sanitizeExit) setExitHandler(null);
+ if (token !== null) restorePermissions(token);
+ }
+ }
+
+ let origin = null;
+
+ function getTestOrigin() {
+ if (origin == null) {
+ origin = ops.op_get_test_origin();
+ }
+ return origin;
+ }
+
+ function getBenchOrigin() {
+ if (origin == null) {
+ origin = ops.op_get_bench_origin();
+ }
+ return origin;
+ }
+
+ function benchNow() {
+ return ops.op_bench_now();
+ }
+
+ function enableTest() {
+ isTestSubcommand = true;
+ }
+
+ function enableBench() {
+ isBenchSubcommand = true;
+ }
+
+ async function runTests({
+ shuffle = null,
+ } = {}) {
+ core.setMacrotaskCallback(handleOpSanitizerDelayMacrotask);
+
+ const origin = getTestOrigin();
+ const only = ArrayPrototypeFilter(testDescs, (test) => test.only);
+ const filtered = ArrayPrototypeFilter(
+ only.length > 0 ? only : testDescs,
+ (desc) => !desc.filteredOut,
+ );
+
+ ops.op_dispatch_test_event({
+ plan: {
+ origin,
+ total: filtered.length,
+ filteredOut: testDescs.length - filtered.length,
+ usedOnly: only.length > 0,
+ },
+ });
+
+ if (shuffle !== null) {
+ // http://en.wikipedia.org/wiki/Linear_congruential_generator
+ // Use BigInt for everything because the random seed is u64.
+ const nextInt = (function (state) {
+ const m = 0x80000000n;
+ const a = 1103515245n;
+ const c = 12345n;
+
+ return function (max) {
+ return state = ((a * state + c) % m) % BigInt(max);
+ };
+ }(BigInt(shuffle)));
+
+ for (let i = filtered.length - 1; i > 0; i--) {
+ const j = nextInt(i);
+ [filtered[i], filtered[j]] = [filtered[j], filtered[i]];
+ }
+ }
+
+ for (const desc of filtered) {
+ ops.op_dispatch_test_event({ wait: desc.id });
+ const earlier = DateNow();
+ const result = await runTest(desc);
+ const elapsed = DateNow() - earlier;
+ ops.op_dispatch_test_event({
+ result: [desc.id, result, elapsed],
+ });
+ }
+ }
+
+ async function runBenchmarks() {
+ core.setMacrotaskCallback(handleOpSanitizerDelayMacrotask);
+
+ const origin = getBenchOrigin();
+ const originalConsole = globalThis.console;
+
+ globalThis.console = new Console((s) => {
+ ops.op_dispatch_bench_event({ output: s });
+ });
+
+ const only = ArrayPrototypeFilter(benchDescs, (bench) => bench.only);
+ const filtered = ArrayPrototypeFilter(
+ only.length > 0 ? only : benchDescs,
+ (desc) => !desc.filteredOut && !desc.ignore,
+ );
+
+ let groups = new Set();
+ // make sure ungrouped benchmarks are placed above grouped
+ groups.add(undefined);
+
+ for (const desc of filtered) {
+ desc.group ||= undefined;
+ groups.add(desc.group);
+ }
+
+ groups = ArrayFrom(groups);
+ ArrayPrototypeSort(
+ filtered,
+ (a, b) => groups.indexOf(a.group) - groups.indexOf(b.group),
+ );
+
+ ops.op_dispatch_bench_event({
+ plan: {
+ origin,
+ total: filtered.length,
+ usedOnly: only.length > 0,
+ names: ArrayPrototypeMap(filtered, (desc) => desc.name),
+ },
+ });
+
+ for (const desc of filtered) {
+ desc.baseline = !!desc.baseline;
+ ops.op_dispatch_bench_event({ wait: desc.id });
+ ops.op_dispatch_bench_event({
+ result: [desc.id, await runBench(desc)],
+ });
+ }
+
+ globalThis.console = originalConsole;
+ }
+
+ function getFullName(desc) {
+ if ("parent" in desc) {
+ return `${desc.parent.name} > ${desc.name}`;
+ }
+ return desc.name;
+ }
+
+ function usesSanitizer(desc) {
+ return desc.sanitizeResources || desc.sanitizeOps || desc.sanitizeExit;
+ }
+
+ function canStreamReporting(desc) {
+ let currentDesc = desc;
+ while (currentDesc != null) {
+ if (!usesSanitizer(currentDesc)) {
+ return false;
+ }
+ currentDesc = currentDesc.parent;
+ }
+ for (const childDesc of MapPrototypeGet(testStates, desc.id).children) {
+ const state = MapPrototypeGet(testStates, childDesc.id);
+ if (!usesSanitizer(childDesc) && !state.finalized) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ function stepReportWait(desc) {
+ const state = MapPrototypeGet(testStates, desc.id);
+ if (state.reportedWait) {
+ return;
+ }
+ ops.op_dispatch_test_event({ stepWait: desc.id });
+ state.reportedWait = true;
+ }
+
+ function stepReportResult(desc) {
+ const state = MapPrototypeGet(testStates, desc.id);
+ if (state.reportedResult) {
+ return;
+ }
+ stepReportWait(desc);
+ for (const childDesc of state.children) {
+ stepReportResult(childDesc);
+ }
+ let result;
+ if (state.status == "pending" || state.status == "failed") {
+ result = {
+ [state.status]: state.error && core.destructureError(state.error),
+ };
+ } else {
+ result = state.status;
+ }
+ ops.op_dispatch_test_event({
+ stepResult: [desc.id, result, state.elapsed],
+ });
+ state.reportedResult = true;
+ }
+
+ function failedChildStepsCount(desc) {
+ return ArrayPrototypeFilter(
+ MapPrototypeGet(testStates, desc.id).children,
+ (d) => MapPrototypeGet(testStates, d.id).status === "failed",
+ ).length;
+ }
+
+ /** If a test validation error already occurred then don't bother checking
+ * the sanitizers as that will create extra noise.
+ */
+ function shouldSkipSanitizers(desc) {
+ try {
+ testStepPostValidation(desc);
+ return false;
+ } catch {
+ return true;
+ }
+ }
+
+ /** @param desc {TestDescription | TestStepDescription} */
+ function createTestContext(desc) {
+ let parent;
+ let level;
+ let rootId;
+ let rootName;
+ if ("parent" in desc) {
+ parent = MapPrototypeGet(testStates, desc.parent.id).context;
+ level = desc.level;
+ rootId = desc.rootId;
+ rootName = desc.rootName;
+ } else {
+ parent = undefined;
+ level = 0;
+ rootId = desc.id;
+ rootName = desc.name;
+ }
+ return {
+ [SymbolToStringTag]: "TestContext",
+ /**
+ * The current test name.
+ */
+ name: desc.name,
+ /**
+ * Parent test context.
+ */
+ parent,
+ /**
+ * File Uri of the test code.
+ */
+ origin: desc.origin,
+ /**
+ * @param nameOrTestDefinition {string | TestStepDefinition}
+ * @param fn {(t: TestContext) => void | Promise<void>}
+ */
+ async step(nameOrTestDefinition, fn) {
+ if (MapPrototypeGet(testStates, desc.id).finalized) {
+ throw new Error(
+ "Cannot run test step after parent scope has finished execution. " +
+ "Ensure any `.step(...)` calls are executed before their parent scope completes execution.",
+ );
+ }
+
+ let stepDesc;
+ if (typeof nameOrTestDefinition === "string") {
+ if (!(ObjectPrototypeIsPrototypeOf(FunctionPrototype, fn))) {
+ throw new TypeError("Expected function for second argument.");
+ }
+ stepDesc = {
+ name: nameOrTestDefinition,
+ fn,
+ };
+ } else if (typeof nameOrTestDefinition === "object") {
+ stepDesc = nameOrTestDefinition;
+ } else {
+ throw new TypeError(
+ "Expected a test definition or name and function.",
+ );
+ }
+ stepDesc.ignore ??= false;
+ stepDesc.sanitizeOps ??= desc.sanitizeOps;
+ stepDesc.sanitizeResources ??= desc.sanitizeResources;
+ stepDesc.sanitizeExit ??= desc.sanitizeExit;
+ stepDesc.origin = getTestOrigin();
+ const jsError = Deno.core.destructureError(new Error());
+ stepDesc.location = {
+ fileName: jsError.frames[1].fileName,
+ lineNumber: jsError.frames[1].lineNumber,
+ columnNumber: jsError.frames[1].columnNumber,
+ };
+ stepDesc.level = level + 1;
+ stepDesc.parent = desc;
+ stepDesc.rootId = rootId;
+ stepDesc.rootName = rootName;
+ const { id } = ops.op_register_test_step(stepDesc);
+ stepDesc.id = id;
+ const state = {
+ context: createTestContext(stepDesc),
+ children: [],
+ finalized: false,
+ status: "pending",
+ error: null,
+ elapsed: null,
+ reportedWait: false,
+ reportedResult: false,
+ };
+ MapPrototypeSet(testStates, stepDesc.id, state);
+ ArrayPrototypePush(
+ MapPrototypeGet(testStates, stepDesc.parent.id).children,
+ stepDesc,
+ );
+
+ try {
+ if (stepDesc.ignore) {
+ state.status = "ignored";
+ state.finalized = true;
+ if (canStreamReporting(stepDesc)) {
+ stepReportResult(stepDesc);
+ }
+ return false;
+ }
+
+ const testFn = wrapTestFnWithSanitizers(stepDesc.fn, stepDesc);
+ const start = DateNow();
+
+ try {
+ await testFn(stepDesc);
+
+ if (failedChildStepsCount(stepDesc) > 0) {
+ state.status = "failed";
+ } else {
+ state.status = "ok";
+ }
+ } catch (error) {
+ state.error = error;
+ state.status = "failed";
+ }
+
+ state.elapsed = DateNow() - start;
+
+ if (MapPrototypeGet(testStates, stepDesc.parent.id).finalized) {
+ // always point this test out as one that was still running
+ // if the parent step finalized
+ state.status = "pending";
+ }
+
+ state.finalized = true;
+
+ if (state.reportedWait && canStreamReporting(stepDesc)) {
+ stepReportResult(stepDesc);
+ }
+
+ return state.status === "ok";
+ } finally {
+ if (canStreamReporting(stepDesc.parent)) {
+ const parentState = MapPrototypeGet(testStates, stepDesc.parent.id);
+ // flush any buffered steps
+ for (const childDesc of parentState.children) {
+ stepReportResult(childDesc);
+ }
+ }
+ }
+ },
+ };
+ }
+
+ /**
+ * @template T {Function}
+ * @param testFn {T}
+ * @param opts {{
+ * sanitizeOps: boolean,
+ * sanitizeResources: boolean,
+ * sanitizeExit: boolean,
+ * }}
+ * @returns {T}
+ */
+ function wrapTestFnWithSanitizers(testFn, opts) {
+ testFn = assertTestStepScopes(testFn);
+
+ if (opts.sanitizeOps) {
+ testFn = assertOps(testFn);
+ }
+ if (opts.sanitizeResources) {
+ testFn = assertResources(testFn);
+ }
+ if (opts.sanitizeExit) {
+ testFn = assertExit(testFn, true);
+ }
+ return testFn;
+ }
+
+ window.__bootstrap.internals = {
+ ...window.__bootstrap.internals ?? {},
+ testing: {
+ runTests,
+ runBenchmarks,
+ enableTest,
+ enableBench,
+ },
+ };
+
+ window.__bootstrap.denoNs.bench = bench;
+ window.__bootstrap.denoNs.test = test;
+})(this);
diff --git a/cli/lsp/testing/execution.rs b/cli/lsp/testing/execution.rs
index 950f2a96e..d839cda56 100644
--- a/cli/lsp/testing/execution.rs
+++ b/cli/lsp/testing/execution.rs
@@ -7,7 +7,6 @@ use super::lsp_custom;
use crate::args::flags_from_vec;
use crate::args::DenoSubcommand;
use crate::checksum;
-use crate::create_main_worker;
use crate::lsp::client::Client;
use crate::lsp::client::TestingNotification;
use crate::lsp::config;
@@ -16,6 +15,7 @@ use crate::ops;
use crate::proc_state;
use crate::tools::test;
use crate::tools::test::TestEventSender;
+use crate::worker::create_main_worker_for_test_or_bench;
use deno_core::anyhow::anyhow;
use deno_core::error::AnyError;
@@ -154,7 +154,7 @@ async fn test_specifier(
filter: test::TestFilter,
) -> Result<(), AnyError> {
if !token.is_cancelled() {
- let mut worker = create_main_worker(
+ let mut worker = create_main_worker_for_test_or_bench(
&ps,
specifier.clone(),
permissions,
diff --git a/cli/main.rs b/cli/main.rs
index ed3d459e1..b91540c37 100644
--- a/cli/main.rs
+++ b/cli/main.rs
@@ -18,6 +18,7 @@ mod fs_util;
mod graph_util;
mod http_cache;
mod http_util;
+mod js;
mod lockfile;
mod logger;
mod lsp;
@@ -289,14 +290,8 @@ async fn eval_command(
resolve_url_or_path(&format!("./$deno$eval.{}", eval_flags.ext))?;
let permissions = Permissions::from_options(&flags.permissions_options())?;
let ps = ProcState::build(flags).await?;
- let mut worker = create_main_worker(
- &ps,
- main_module.clone(),
- permissions,
- vec![],
- Default::default(),
- )
- .await?;
+ let mut worker =
+ create_main_worker(&ps, main_module.clone(), permissions).await?;
// Create a dummy source file.
let source_code = if eval_flags.print {
format!("console.log({})", eval_flags.code)
@@ -602,8 +597,6 @@ async fn repl_command(
&ps,
main_module.clone(),
Permissions::from_options(&ps.options.permissions_options())?,
- vec![],
- Default::default(),
)
.await?;
worker.setup_repl().await?;
@@ -623,8 +616,6 @@ async fn run_from_stdin(flags: Flags) -> Result<i32, AnyError> {
&ps.clone(),
main_module.clone(),
Permissions::from_options(&ps.options.permissions_options())?,
- vec![],
- Default::default(),
)
.await?;
@@ -664,14 +655,8 @@ async fn run_with_watch(flags: Flags, script: String) -> Result<i32, AnyError> {
let ps =
ProcState::build_for_file_watcher((*flags).clone(), sender.clone())
.await?;
- let worker = create_main_worker(
- &ps,
- main_module.clone(),
- permissions,
- vec![],
- Default::default(),
- )
- .await?;
+ let worker =
+ create_main_worker(&ps, main_module.clone(), permissions).await?;
worker.run_for_watcher().await?;
Ok(())
@@ -722,14 +707,8 @@ async fn run_command(
};
let permissions =
Permissions::from_options(&ps.options.permissions_options())?;
- let mut worker = create_main_worker(
- &ps,
- main_module.clone(),
- permissions,
- vec![],
- Default::default(),
- )
- .await?;
+ let mut worker =
+ create_main_worker(&ps, main_module.clone(), permissions).await?;
let exit_code = worker.run().await?;
Ok(exit_code)
diff --git a/cli/standalone.rs b/cli/standalone.rs
index 5e66bcb18..2742f9bbd 100644
--- a/cli/standalone.rs
+++ b/cli/standalone.rs
@@ -287,7 +287,7 @@ pub async fn run(
inspect: ps.options.is_inspecting(),
},
extensions: ops::cli_exts(ps.clone()),
- startup_snapshot: None,
+ startup_snapshot: Some(crate::js::deno_isolate_init()),
unsafely_ignore_certificate_errors: metadata
.unsafely_ignore_certificate_errors,
root_cert_store: Some(root_cert_store),
diff --git a/cli/tests/testdata/test/steps/failing_steps.out b/cli/tests/testdata/test/steps/failing_steps.out
index 1e5f2f64d..4df104bd7 100644
--- a/cli/tests/testdata/test/steps/failing_steps.out
+++ b/cli/tests/testdata/test/steps/failing_steps.out
@@ -37,13 +37,13 @@ failing step in failing test ... FAILED ([WILDCARD])
nested failure => ./test/steps/failing_steps.ts:[WILDCARD]
error: Error: 1 test step failed.
- at runTest (deno:runtime/js/40_testing.js:[WILDCARD])
- at async runTests (deno:runtime/js/40_testing.js:[WILDCARD])
+ at runTest (deno:cli/js/40_testing.js:[WILDCARD])
+ at async runTests (deno:cli/js/40_testing.js:[WILDCARD])
multiple test step failures => ./test/steps/failing_steps.ts:[WILDCARD]
error: Error: 2 test steps failed.
- at runTest (deno:runtime/js/40_testing.js:[WILDCARD])
- at async runTests (deno:runtime/js/40_testing.js:[WILDCARD])
+ at runTest (deno:cli/js/40_testing.js:[WILDCARD])
+ at async runTests (deno:cli/js/40_testing.js:[WILDCARD])
failing step in failing test => ./test/steps/failing_steps.ts:[WILDCARD]
error: Error: Fail test.
diff --git a/cli/tools/bench.rs b/cli/tools/bench.rs
index c055d8a9c..a81c0a406 100644
--- a/cli/tools/bench.rs
+++ b/cli/tools/bench.rs
@@ -4,7 +4,6 @@ use crate::args::BenchFlags;
use crate::args::Flags;
use crate::args::TypeCheckMode;
use crate::colors;
-use crate::create_main_worker;
use crate::file_watcher;
use crate::file_watcher::ResolutionResult;
use crate::fs_util::collect_specifiers;
@@ -15,6 +14,7 @@ use crate::ops;
use crate::proc_state::ProcState;
use crate::tools::test::format_test_error;
use crate::tools::test::TestFilter;
+use crate::worker::create_main_worker_for_test_or_bench;
use deno_core::error::generic_error;
use deno_core::error::AnyError;
@@ -352,7 +352,7 @@ async fn bench_specifier(
options: BenchSpecifierOptions,
) -> Result<(), AnyError> {
let filter = TestFilter::from_flag(&options.filter);
- let mut worker = create_main_worker(
+ let mut worker = create_main_worker_for_test_or_bench(
&ps,
specifier.clone(),
permissions,
diff --git a/cli/tools/test.rs b/cli/tools/test.rs
index 09257efff..1bb891a1e 100644
--- a/cli/tools/test.rs
+++ b/cli/tools/test.rs
@@ -5,7 +5,6 @@ use crate::args::TestFlags;
use crate::args::TypeCheckMode;
use crate::checksum;
use crate::colors;
-use crate::create_main_worker;
use crate::display;
use crate::file_fetcher::File;
use crate::file_watcher;
@@ -18,6 +17,7 @@ use crate::graph_util::contains_specifier;
use crate::graph_util::graph_valid;
use crate::ops;
use crate::proc_state::ProcState;
+use crate::worker::create_main_worker_for_test_or_bench;
use deno_ast::swc::common::comments::CommentKind;
use deno_ast::MediaType;
@@ -715,7 +715,7 @@ async fn test_specifier(
sender: &TestEventSender,
options: TestSpecifierOptions,
) -> Result<(), AnyError> {
- let mut worker = create_main_worker(
+ let mut worker = create_main_worker_for_test_or_bench(
&ps,
specifier.clone(),
permissions,
diff --git a/cli/worker.rs b/cli/worker.rs
index 7fe1f3c0b..d06864634 100644
--- a/cli/worker.rs
+++ b/cli/worker.rs
@@ -7,6 +7,9 @@ use deno_core::error::AnyError;
use deno_core::futures::task::LocalFutureObj;
use deno_core::futures::FutureExt;
use deno_core::located_script_name;
+use deno_core::serde_json::json;
+use deno_core::serde_v8;
+use deno_core::v8;
use deno_core::Extension;
use deno_core::ModuleId;
use deno_runtime::colors;
@@ -38,6 +41,11 @@ pub struct CliMainWorker {
is_main_cjs: bool,
worker: MainWorker,
ps: ProcState,
+
+ js_run_tests_callback: Option<v8::Global<v8::Function>>,
+ js_run_benchmarks_callback: Option<v8::Global<v8::Function>>,
+ js_enable_test_callback: Option<v8::Global<v8::Function>>,
+ js_enable_bench_callback: Option<v8::Global<v8::Function>>,
}
impl CliMainWorker {
@@ -168,7 +176,7 @@ impl CliMainWorker {
&mut self,
mode: TestMode,
) -> Result<(), AnyError> {
- self.worker.enable_test();
+ self.enable_test();
// Enable op call tracing in core to enable better debugging of op sanitizer
// failures.
@@ -194,10 +202,7 @@ impl CliMainWorker {
}
self.worker.dispatch_load_event(&located_script_name!())?;
- self
- .worker
- .run_tests(&self.ps.options.shuffle_tests())
- .await?;
+ self.run_tests(&self.ps.options.shuffle_tests()).await?;
loop {
if !self
.worker
@@ -223,7 +228,7 @@ impl CliMainWorker {
&mut self,
mode: TestMode,
) -> Result<(), AnyError> {
- self.worker.enable_test();
+ self.enable_test();
self
.worker
@@ -239,7 +244,7 @@ impl CliMainWorker {
}
self.worker.dispatch_load_event(&located_script_name!())?;
- self.worker.run_tests(&None).await?;
+ self.run_tests(&None).await?;
loop {
if !self
.worker
@@ -254,13 +259,13 @@ impl CliMainWorker {
}
pub async fn run_bench_specifier(&mut self) -> Result<(), AnyError> {
- self.worker.enable_bench();
+ self.enable_bench();
// We execute the module module as a side module so that import.meta.main is not set.
self.execute_side_module_possibly_with_npm().await?;
self.worker.dispatch_load_event(&located_script_name!())?;
- self.worker.run_benchmarks().await?;
+ self.run_benchmarks().await?;
loop {
if !self
.worker
@@ -340,14 +345,104 @@ impl CliMainWorker {
Ok(None)
}
}
+
+ /// Run tests declared with `Deno.test()`. Test events will be dispatched
+ /// by calling ops which are currently only implemented in the CLI crate.
+ pub async fn run_tests(
+ &mut self,
+ shuffle: &Option<u64>,
+ ) -> Result<(), AnyError> {
+ let promise = {
+ let scope = &mut self.worker.js_runtime.handle_scope();
+ let cb = self.js_run_tests_callback.as_ref().unwrap().open(scope);
+ let this = v8::undefined(scope).into();
+ let options =
+ serde_v8::to_v8(scope, json!({ "shuffle": shuffle })).unwrap();
+ let promise = cb.call(scope, this, &[options]).unwrap();
+ v8::Global::new(scope, promise)
+ };
+ self.worker.js_runtime.resolve_value(promise).await?;
+ Ok(())
+ }
+
+ /// Run benches declared with `Deno.bench()`. Bench events will be dispatched
+ /// by calling ops which are currently only implemented in the CLI crate.
+ pub async fn run_benchmarks(&mut self) -> Result<(), AnyError> {
+ let promise = {
+ let scope = &mut self.worker.js_runtime.handle_scope();
+ let cb = self
+ .js_run_benchmarks_callback
+ .as_ref()
+ .unwrap()
+ .open(scope);
+ let this = v8::undefined(scope).into();
+ let promise = cb.call(scope, this, &[]).unwrap();
+ v8::Global::new(scope, promise)
+ };
+ self.worker.js_runtime.resolve_value(promise).await?;
+ Ok(())
+ }
+
+ /// Enable `Deno.test()`. If this isn't called before executing user code,
+ /// `Deno.test()` calls will noop.
+ pub fn enable_test(&mut self) {
+ let scope = &mut self.worker.js_runtime.handle_scope();
+ let cb = self.js_enable_test_callback.as_ref().unwrap().open(scope);
+ let this = v8::undefined(scope).into();
+ cb.call(scope, this, &[]).unwrap();
+ }
+
+ /// Enable `Deno.bench()`. If this isn't called before executing user code,
+ /// `Deno.bench()` calls will noop.
+ pub fn enable_bench(&mut self) {
+ let scope = &mut self.worker.js_runtime.handle_scope();
+ let cb = self.js_enable_bench_callback.as_ref().unwrap().open(scope);
+ let this = v8::undefined(scope).into();
+ cb.call(scope, this, &[]).unwrap();
+ }
}
pub async fn create_main_worker(
ps: &ProcState,
main_module: ModuleSpecifier,
permissions: Permissions,
+) -> Result<CliMainWorker, AnyError> {
+ create_main_worker_internal(
+ ps,
+ main_module,
+ permissions,
+ vec![],
+ Default::default(),
+ false,
+ )
+ .await
+}
+
+pub async fn create_main_worker_for_test_or_bench(
+ ps: &ProcState,
+ main_module: ModuleSpecifier,
+ permissions: Permissions,
+ custom_extensions: Vec<Extension>,
+ stdio: deno_runtime::ops::io::Stdio,
+) -> Result<CliMainWorker, AnyError> {
+ create_main_worker_internal(
+ ps,
+ main_module,
+ permissions,
+ custom_extensions,
+ stdio,
+ true,
+ )
+ .await
+}
+
+async fn create_main_worker_internal(
+ ps: &ProcState,
+ main_module: ModuleSpecifier,
+ permissions: Permissions,
mut custom_extensions: Vec<Extension>,
stdio: deno_runtime::ops::io::Stdio,
+ bench_or_test: bool,
) -> Result<CliMainWorker, AnyError> {
let (main_module, is_main_cjs) = if let Ok(package_ref) =
NpmPackageReference::from_specifier(&main_module)
@@ -426,7 +521,7 @@ pub async fn create_main_worker(
inspect: ps.options.is_inspecting(),
},
extensions,
- startup_snapshot: None,
+ startup_snapshot: Some(crate::js::deno_isolate_init()),
unsafely_ignore_certificate_errors: ps
.options
.unsafely_ignore_certificate_errors()
@@ -452,16 +547,59 @@ pub async fn create_main_worker(
stdio,
};
- let worker = MainWorker::bootstrap_from_options(
+ let mut worker = MainWorker::bootstrap_from_options(
main_module.clone(),
permissions,
options,
);
+
+ let (
+ js_run_tests_callback,
+ js_run_benchmarks_callback,
+ js_enable_test_callback,
+ js_enable_bench_callback,
+ ) = if bench_or_test {
+ let scope = &mut worker.js_runtime.handle_scope();
+ let js_run_tests_callback = deno_core::JsRuntime::eval::<v8::Function>(
+ scope,
+ "Deno[Deno.internal].testing.runTests",
+ )
+ .unwrap();
+ let js_run_benchmarks_callback =
+ deno_core::JsRuntime::eval::<v8::Function>(
+ scope,
+ "Deno[Deno.internal].testing.runBenchmarks",
+ )
+ .unwrap();
+ let js_enable_tests_callback = deno_core::JsRuntime::eval::<v8::Function>(
+ scope,
+ "Deno[Deno.internal].testing.enableTest",
+ )
+ .unwrap();
+ let js_enable_bench_callback = deno_core::JsRuntime::eval::<v8::Function>(
+ scope,
+ "Deno[Deno.internal].testing.enableBench",
+ )
+ .unwrap();
+ (
+ Some(v8::Global::new(scope, js_run_tests_callback)),
+ Some(v8::Global::new(scope, js_run_benchmarks_callback)),
+ Some(v8::Global::new(scope, js_enable_tests_callback)),
+ Some(v8::Global::new(scope, js_enable_bench_callback)),
+ )
+ } else {
+ (None, None, None, None)
+ };
+
Ok(CliMainWorker {
main_module,
is_main_cjs,
worker,
ps: ps.clone(),
+ js_run_tests_callback,
+ js_run_benchmarks_callback,
+ js_enable_test_callback,
+ js_enable_bench_callback,
})
}
@@ -544,6 +682,7 @@ fn create_web_worker_callback(
inspect: ps.options.is_inspecting(),
},
extensions,
+ startup_snapshot: Some(crate::js::deno_isolate_init()),
unsafely_ignore_certificate_errors: ps
.options
.unsafely_ignore_certificate_errors()
@@ -577,3 +716,109 @@ fn create_web_worker_callback(
)
})
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use deno_core::{resolve_url_or_path, FsModuleLoader};
+ use deno_runtime::deno_broadcast_channel::InMemoryBroadcastChannel;
+ use deno_runtime::deno_web::BlobStore;
+
+ fn create_test_worker() -> MainWorker {
+ let main_module = resolve_url_or_path("./hello.js").unwrap();
+ let permissions = Permissions::default();
+
+ let options = WorkerOptions {
+ bootstrap: BootstrapOptions {
+ args: vec![],
+ cpu_count: 1,
+ debug_flag: false,
+ enable_testing_features: false,
+ locale: deno_core::v8::icu::get_language_tag(),
+ location: None,
+ no_color: true,
+ is_tty: false,
+ runtime_version: "x".to_string(),
+ ts_version: "x".to_string(),
+ unstable: false,
+ user_agent: "x".to_string(),
+ inspect: false,
+ },
+ extensions: vec![],
+ startup_snapshot: Some(crate::js::deno_isolate_init()),
+ unsafely_ignore_certificate_errors: None,
+ root_cert_store: None,
+ seed: None,
+ format_js_error_fn: None,
+ source_map_getter: None,
+ web_worker_preload_module_cb: Arc::new(|_| unreachable!()),
+ web_worker_pre_execute_module_cb: Arc::new(|_| unreachable!()),
+ create_web_worker_cb: Arc::new(|_| unreachable!()),
+ maybe_inspector_server: None,
+ should_break_on_first_statement: false,
+ module_loader: Rc::new(FsModuleLoader),
+ npm_resolver: None,
+ get_error_class_fn: None,
+ cache_storage_dir: None,
+ origin_storage_dir: None,
+ blob_store: BlobStore::default(),
+ broadcast_channel: InMemoryBroadcastChannel::default(),
+ shared_array_buffer_store: None,
+ compiled_wasm_module_store: None,
+ stdio: Default::default(),
+ };
+
+ MainWorker::bootstrap_from_options(main_module, permissions, options)
+ }
+
+ #[tokio::test]
+ async fn execute_mod_esm_imports_a() {
+ let p = test_util::testdata_path().join("runtime/esm_imports_a.js");
+ let module_specifier = resolve_url_or_path(&p.to_string_lossy()).unwrap();
+ let mut worker = create_test_worker();
+ let result = worker.execute_main_module(&module_specifier).await;
+ if let Err(err) = result {
+ eprintln!("execute_mod err {:?}", err);
+ }
+ if let Err(e) = worker.run_event_loop(false).await {
+ panic!("Future got unexpected error: {:?}", e);
+ }
+ }
+
+ #[tokio::test]
+ async fn execute_mod_circular() {
+ let p = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
+ .parent()
+ .unwrap()
+ .join("tests/circular1.js");
+ let module_specifier = resolve_url_or_path(&p.to_string_lossy()).unwrap();
+ let mut worker = create_test_worker();
+ let result = worker.execute_main_module(&module_specifier).await;
+ if let Err(err) = result {
+ eprintln!("execute_mod err {:?}", err);
+ }
+ if let Err(e) = worker.run_event_loop(false).await {
+ panic!("Future got unexpected error: {:?}", e);
+ }
+ }
+
+ #[tokio::test]
+ async fn execute_mod_resolve_error() {
+ // "foo" is not a valid module specifier so this should return an error.
+ let mut worker = create_test_worker();
+ let module_specifier = resolve_url_or_path("does-not-exist").unwrap();
+ let result = worker.execute_main_module(&module_specifier).await;
+ assert!(result.is_err());
+ }
+
+ #[tokio::test]
+ async fn execute_mod_002_hello() {
+ // This assumes cwd is project root (an assumption made throughout the
+ // tests).
+ let mut worker = create_test_worker();
+ let p = test_util::testdata_path().join("run/001_hello.js");
+ let module_specifier = resolve_url_or_path(&p.to_string_lossy()).unwrap();
+ let result = worker.execute_main_module(&module_specifier).await;
+ assert!(result.is_ok());
+ }
+}