summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--cli/Cargo.toml2
-rw-r--r--cli/dts/lib.deno.unstable.d.ts134
-rw-r--r--cli/main.rs25
-rw-r--r--cli/ops/mod.rs1
-rw-r--r--cli/ops/test_runner.rs66
-rw-r--r--cli/tests/integration_tests.rs12
-rw-r--r--cli/tests/test/allow_all.out18
-rw-r--r--cli/tests/test/allow_all.ts35
-rw-r--r--cli/tests/test/allow_none.out51
-rw-r--r--cli/tests/test/allow_none.ts23
-rw-r--r--runtime/js/11_workers.js1
-rw-r--r--runtime/js/40_testing.js29
-rw-r--r--runtime/ops/worker_host.rs4
14 files changed, 392 insertions, 10 deletions
diff --git a/Cargo.lock b/Cargo.lock
index a27cce718..f505f318c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3897,6 +3897,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
dependencies = [
"getrandom 0.2.2",
+ "serde",
]
[[package]]
diff --git a/cli/Cargo.toml b/cli/Cargo.toml
index 909ec1537..90fa80593 100644
--- a/cli/Cargo.toml
+++ b/cli/Cargo.toml
@@ -78,7 +78,7 @@ termcolor = "1.1.2"
text-size = "1.1.0"
tokio = { version = "1.4.0", features = ["full"] }
tokio-rustls = "0.22.0"
-uuid = { version = "0.8.2", features = ["v4"] }
+uuid = { version = "0.8.2", features = ["v4", "serde"] }
walkdir = "2.3.2"
[target.'cfg(windows)'.dependencies]
diff --git a/cli/dts/lib.deno.unstable.d.ts b/cli/dts/lib.deno.unstable.d.ts
index d955a825f..76d90b10d 100644
--- a/cli/dts/lib.deno.unstable.d.ts
+++ b/cli/dts/lib.deno.unstable.d.ts
@@ -1181,6 +1181,140 @@ declare namespace Deno {
* then the underlying HttpConn resource is closed automatically.
*/
export function serveHttp(conn: Conn): HttpConn;
+
+ /** **UNSTABLE**: New option, yet to be vetted. */
+ export interface TestDefinition {
+ /** Specifies the permissions that should be used to run the test.
+ * Set this to "inherit" to keep the calling thread's permissions.
+ * Set this to "none" to revoke all permissions.
+ *
+ * Defaults to "inherit".
+ */
+ permissions?: "inherit" | "none" | {
+ /** Specifies if the `net` permission should be requested or revoked.
+ * If set to `"inherit"`, the current `env` permission will be inherited.
+ * If set to `true`, the global `net` permission will be requested.
+ * If set to `false`, the global `net` permission will be revoked.
+ *
+ * Defaults to "inherit".
+ */
+ env?: "inherit" | boolean;
+
+ /** Specifies if the `hrtime` permission should be requested or revoked.
+ * If set to `"inherit"`, the current `hrtime` permission will be inherited.
+ * If set to `true`, the global `hrtime` permission will be requested.
+ * If set to `false`, the global `hrtime` permission will be revoked.
+ *
+ * Defaults to "inherit".
+ */
+ hrtime?: "inherit" | boolean;
+
+ /** Specifies if the `net` permission should be requested or revoked.
+ * if set to `"inherit"`, the current `net` permission will be inherited.
+ * if set to `true`, the global `net` permission will be requested.
+ * if set to `false`, the global `net` permission will be revoked.
+ * if set to `string[]`, the `net` permission will be requested with the
+ * specified host strings with the format `"<host>[:<port>]`.
+ *
+ * Defaults to "inherit".
+ *
+ * Examples:
+ *
+ * ```
+ * Deno.test({
+ * name: "inherit",
+ * permissions: {
+ * net: "inherit",
+ * },
+ * async fn() {
+ * const status = await Deno.permissions.query({ name: "net" })
+ * assertEquals(status.state, "granted");
+ * },
+ * };
+ * ```
+ *
+ * ```
+ * Deno.test({
+ * name: "true",
+ * permissions: {
+ * net: true,
+ * },
+ * async fn() {
+ * const status = await Deno.permissions.query({ name: "net" });
+ * assertEquals(status.state, "granted");
+ * },
+ * };
+ * ```
+ *
+ * ```
+ * Deno.test({
+ * name: "false",
+ * permissions: {
+ * net: false,
+ * },
+ * async fn() {
+ * const status = await Deno.permissions.query({ name: "net" });
+ * assertEquals(status.state, "denied");
+ * },
+ * };
+ * ```
+ *
+ * ```
+ * Deno.test({
+ * name: "localhost:8080",
+ * permissions: {
+ * net: ["localhost:8080"],
+ * },
+ * async fn() {
+ * const status = await Deno.permissions.query({ name: "net", host: "localhost:8080" });
+ * assertEquals(status.state, "granted");
+ * },
+ * };
+ * ```
+ */
+ net?: "inherit" | boolean | string[];
+
+ /** Specifies if the `plugin` permission should be requested or revoked.
+ * If set to `"inherit"`, the current `plugin` permission will be inherited.
+ * If set to `true`, the global `plugin` permission will be requested.
+ * If set to `false`, the global `plugin` permission will be revoked.
+ *
+ * Defaults to "inherit".
+ */
+ plugin?: "inherit" | boolean;
+
+ /** Specifies if the `read` permission should be requested or revoked.
+ * If set to `"inherit"`, the current `read` permission will be inherited.
+ * If set to `true`, the global `read` permission will be requested.
+ * If set to `false`, the global `read` permission will be revoked.
+ * If set to `Array<string | URL>`, the `read` permission will be requested with the
+ * specified file paths.
+ *
+ * Defaults to "inherit".
+ */
+ read?: "inherit" | boolean | Array<string | URL>;
+
+ /** Specifies if the `run` permission should be requested or revoked.
+ * If set to `"inherit"`, the current `run` permission will be inherited.
+ * If set to `true`, the global `run` permission will be requested.
+ * If set to `false`, the global `run` permission will be revoked.
+ *
+ * Defaults to "inherit".
+ */
+ run?: "inherit" | boolean;
+
+ /** Specifies if the `write` permission should be requested or revoked.
+ * If set to `"inherit"`, the current `write` permission will be inherited.
+ * If set to `true`, the global `write` permission will be requested.
+ * If set to `false`, the global `write` permission will be revoked.
+ * If set to `Array<string | URL>`, the `write` permission will be requested with the
+ * specified file paths.
+ *
+ * Defaults to "inherit".
+ */
+ write?: "inherit" | boolean | Array<string | URL>;
+ };
+ }
}
declare function fetch(
diff --git a/cli/main.rs b/cli/main.rs
index c42604146..38e67f46a 100644
--- a/cli/main.rs
+++ b/cli/main.rs
@@ -160,6 +160,7 @@ pub fn create_main_worker(
program_state: &Arc<ProgramState>,
main_module: ModuleSpecifier,
permissions: Permissions,
+ enable_testing: bool,
) -> MainWorker {
let module_loader = CliModuleLoader::new(program_state.clone());
@@ -219,6 +220,11 @@ pub fn create_main_worker(
// above
ops::errors::init(js_runtime);
ops::runtime_compiler::init(js_runtime);
+
+ if enable_testing {
+ ops::test_runner::init(js_runtime);
+ }
+
js_runtime.sync_ops_cache();
}
worker.bootstrap(&options);
@@ -427,7 +433,7 @@ async fn install_command(
let program_state = ProgramState::build(preload_flags).await?;
let main_module = resolve_url_or_path(&module_url)?;
let mut worker =
- create_main_worker(&program_state, main_module.clone(), permissions);
+ create_main_worker(&program_state, main_module.clone(), permissions, false);
// First, fetch and compile the module; this step ensures that the module exists.
worker.preload_module(&main_module).await?;
tools::installer::install(flags, &module_url, args, name, root, force)
@@ -494,7 +500,7 @@ async fn eval_command(
let permissions = Permissions::from_options(&flags.clone().into());
let program_state = ProgramState::build(flags).await?;
let mut worker =
- create_main_worker(&program_state, main_module.clone(), permissions);
+ create_main_worker(&program_state, main_module.clone(), permissions, false);
// Create a dummy source file.
let source_code = if print {
format!("console.log({})", code)
@@ -728,7 +734,7 @@ async fn run_repl(flags: Flags) -> Result<(), AnyError> {
let permissions = Permissions::from_options(&flags.clone().into());
let program_state = ProgramState::build(flags).await?;
let mut worker =
- create_main_worker(&program_state, main_module.clone(), permissions);
+ create_main_worker(&program_state, main_module.clone(), permissions, false);
worker.run_event_loop().await?;
tools::repl::run(&program_state, worker).await
@@ -742,6 +748,7 @@ async fn run_from_stdin(flags: Flags) -> Result<(), AnyError> {
&program_state.clone(),
main_module.clone(),
permissions,
+ false,
);
let mut source = Vec::new();
@@ -819,8 +826,12 @@ async fn run_with_watch(flags: Flags, script: String) -> Result<(), AnyError> {
async move {
let main_module = main_module.clone();
let program_state = ProgramState::build(flags).await?;
- let mut worker =
- create_main_worker(&program_state, main_module.clone(), permissions);
+ let mut worker = create_main_worker(
+ &program_state,
+ main_module.clone(),
+ permissions,
+ false,
+ );
debug!("main_module {}", main_module);
worker.execute_module(&main_module).await?;
worker.execute("window.dispatchEvent(new Event('load'))")?;
@@ -853,7 +864,7 @@ async fn run_command(flags: Flags, script: String) -> Result<(), AnyError> {
let program_state = ProgramState::build(flags.clone()).await?;
let permissions = Permissions::from_options(&flags.clone().into());
let mut worker =
- create_main_worker(&program_state, main_module.clone(), permissions);
+ create_main_worker(&program_state, main_module.clone(), permissions, false);
let mut maybe_coverage_collector =
if let Some(ref coverage_dir) = program_state.coverage_dir {
@@ -970,7 +981,7 @@ async fn test_command(
}
let mut worker =
- create_main_worker(&program_state, main_module.clone(), permissions);
+ create_main_worker(&program_state, main_module.clone(), permissions, true);
if let Some(ref coverage_dir) = flags.coverage_dir {
env::set_var("DENO_UNSTABLE_COVERAGE_DIR", coverage_dir);
diff --git a/cli/ops/mod.rs b/cli/ops/mod.rs
index cce0625c6..386ad16fa 100644
--- a/cli/ops/mod.rs
+++ b/cli/ops/mod.rs
@@ -2,5 +2,6 @@
pub mod errors;
pub mod runtime_compiler;
+pub mod test_runner;
pub use deno_runtime::ops::{reg_async, reg_sync};
diff --git a/cli/ops/test_runner.rs b/cli/ops/test_runner.rs
new file mode 100644
index 000000000..380ec7fb0
--- /dev/null
+++ b/cli/ops/test_runner.rs
@@ -0,0 +1,66 @@
+// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+
+use deno_core::error::generic_error;
+use deno_core::error::AnyError;
+use deno_core::serde_json;
+use deno_core::serde_json::Value;
+use deno_core::OpState;
+use deno_core::ZeroCopyBuf;
+use deno_runtime::ops::worker_host::create_worker_permissions;
+use deno_runtime::ops::worker_host::PermissionsArg;
+use deno_runtime::permissions::Permissions;
+use uuid::Uuid;
+
+pub fn init(rt: &mut deno_core::JsRuntime) {
+ super::reg_sync(rt, "op_pledge_test_permissions", op_pledge_test_permissions);
+ super::reg_sync(
+ rt,
+ "op_restore_test_permissions",
+ op_restore_test_permissions,
+ );
+}
+
+#[derive(Clone)]
+struct PermissionsHolder(Uuid, Permissions);
+
+pub fn op_pledge_test_permissions(
+ state: &mut OpState,
+ args: Value,
+ _zero_copy: Option<ZeroCopyBuf>,
+) -> Result<Uuid, AnyError> {
+ deno_runtime::ops::check_unstable(state, "Deno.test.permissions");
+
+ let token = Uuid::new_v4();
+ let parent_permissions = state.borrow::<Permissions>().clone();
+ let worker_permissions = {
+ let permissions: PermissionsArg = serde_json::from_value(args)?;
+ create_worker_permissions(parent_permissions.clone(), permissions)?
+ };
+
+ state.put::<PermissionsHolder>(PermissionsHolder(token, parent_permissions));
+
+ // NOTE: This call overrides current permission set for the worker
+ state.put::<Permissions>(worker_permissions);
+
+ Ok(token)
+}
+
+pub fn op_restore_test_permissions(
+ state: &mut OpState,
+ token: Uuid,
+ _zero_copy: Option<ZeroCopyBuf>,
+) -> Result<(), AnyError> {
+ deno_runtime::ops::check_unstable(state, "Deno.test.permissions");
+
+ if let Some(permissions_holder) = state.try_take::<PermissionsHolder>() {
+ if token != permissions_holder.0 {
+ panic!("restore test permissions token does not match the stored token");
+ }
+
+ let permissions = permissions_holder.1;
+ state.put::<Permissions>(permissions);
+ Ok(())
+ } else {
+ Err(generic_error("no permissions to restore"))
+ }
+}
diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs
index 28d2dc7c6..770e87244 100644
--- a/cli/tests/integration_tests.rs
+++ b/cli/tests/integration_tests.rs
@@ -2395,6 +2395,18 @@ mod integration {
output: "test/deno_test.out",
});
+ itest!(allow_all {
+ args: "test --unstable --allow-all test/allow_all.ts",
+ exit_code: 0,
+ output: "test/allow_all.out",
+ });
+
+ itest!(allow_none {
+ args: "test --unstable test/allow_none.ts",
+ exit_code: 1,
+ output: "test/allow_none.out",
+ });
+
itest!(fail_fast {
args: "test --fail-fast test/test_runner_test.ts",
exit_code: 1,
diff --git a/cli/tests/test/allow_all.out b/cli/tests/test/allow_all.out
new file mode 100644
index 000000000..3edb88d0f
--- /dev/null
+++ b/cli/tests/test/allow_all.out
@@ -0,0 +1,18 @@
+[WILDCARD]
+running 14 tests
+test read false ... ok [WILDCARD]
+test read true ... ok [WILDCARD]
+test write false ... ok [WILDCARD]
+test write true ... ok [WILDCARD]
+test net false ... ok [WILDCARD]
+test net true ... ok [WILDCARD]
+test env false ... ok [WILDCARD]
+test env true ... ok [WILDCARD]
+test run false ... ok [WILDCARD]
+test run true ... ok [WILDCARD]
+test plugin false ... ok [WILDCARD]
+test plugin true ... ok [WILDCARD]
+test hrtime false ... ok [WILDCARD]
+test hrtime true ... ok [WILDCARD]
+
+test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out [WILDCARD]
diff --git a/cli/tests/test/allow_all.ts b/cli/tests/test/allow_all.ts
new file mode 100644
index 000000000..e4e12144e
--- /dev/null
+++ b/cli/tests/test/allow_all.ts
@@ -0,0 +1,35 @@
+import { assertEquals } from "../../../test_util/std/testing/asserts.ts";
+
+const permissions: Deno.PermissionName[] = [
+ "read",
+ "write",
+ "net",
+ "env",
+ "run",
+ "plugin",
+ "hrtime",
+];
+
+for (const name of permissions) {
+ Deno.test({
+ name: `${name} false`,
+ permissions: {
+ [name]: false,
+ },
+ async fn() {
+ const status = await Deno.permissions.query({ name });
+ assertEquals(status.state, "denied");
+ },
+ });
+
+ Deno.test({
+ name: `${name} true`,
+ permissions: {
+ [name]: true,
+ },
+ async fn() {
+ const status = await Deno.permissions.query({ name });
+ assertEquals(status.state, "granted");
+ },
+ });
+}
diff --git a/cli/tests/test/allow_none.out b/cli/tests/test/allow_none.out
new file mode 100644
index 000000000..6565a0800
--- /dev/null
+++ b/cli/tests/test/allow_none.out
@@ -0,0 +1,51 @@
+[WILDCARD]
+running 7 tests
+test read ... FAILED [WILDCARD]
+test write ... FAILED [WILDCARD]
+test net ... FAILED [WILDCARD]
+test env ... FAILED [WILDCARD]
+test run ... FAILED [WILDCARD]
+test plugin ... FAILED [WILDCARD]
+test hrtime ... FAILED [WILDCARD]
+
+failures:
+
+read
+PermissionDenied: Can't escalate parent thread permissions
+[WILDCARD]
+
+write
+PermissionDenied: Can't escalate parent thread permissions
+[WILDCARD]
+
+net
+PermissionDenied: Can't escalate parent thread permissions
+[WILDCARD]
+
+env
+PermissionDenied: Can't escalate parent thread permissions
+[WILDCARD]
+
+run
+PermissionDenied: Can't escalate parent thread permissions
+[WILDCARD]
+
+plugin
+PermissionDenied: Can't escalate parent thread permissions
+[WILDCARD]
+
+hrtime
+PermissionDenied: Can't escalate parent thread permissions
+[WILDCARD]
+
+failures:
+
+ read
+ write
+ net
+ env
+ run
+ plugin
+ hrtime
+
+test result: FAILED. 0 passed; 7 failed; 0 ignored; 0 measured; 0 filtered out [WILDCARD]
diff --git a/cli/tests/test/allow_none.ts b/cli/tests/test/allow_none.ts
new file mode 100644
index 000000000..c0a930eb1
--- /dev/null
+++ b/cli/tests/test/allow_none.ts
@@ -0,0 +1,23 @@
+import { unreachable } from "../../../test_util/std/testing/asserts.ts";
+
+const permissions: Deno.PermissionName[] = [
+ "read",
+ "write",
+ "net",
+ "env",
+ "run",
+ "plugin",
+ "hrtime",
+];
+
+for (const name of permissions) {
+ Deno.test({
+ name,
+ permissions: {
+ [name]: true,
+ },
+ fn() {
+ unreachable();
+ },
+ });
+}
diff --git a/runtime/js/11_workers.js b/runtime/js/11_workers.js
index 2c602ab60..508dd46d4 100644
--- a/runtime/js/11_workers.js
+++ b/runtime/js/11_workers.js
@@ -333,6 +333,7 @@
defineEventHandler(Worker.prototype, "messageerror");
window.__bootstrap.worker = {
+ parsePermissions,
Worker,
};
})(this);
diff --git a/runtime/js/40_testing.js b/runtime/js/40_testing.js
index 7666fa050..4a97f6437 100644
--- a/runtime/js/40_testing.js
+++ b/runtime/js/40_testing.js
@@ -4,6 +4,7 @@
((window) => {
const core = window.Deno.core;
const colors = window.__bootstrap.colors;
+ const { parsePermissions } = window.__bootstrap.worker;
const { setExitHandler, exit } = window.__bootstrap.os;
const { Console, inspectArgs } = window.__bootstrap.console;
const { stdout } = window.__bootstrap.files;
@@ -121,6 +122,7 @@ finishing test case.`;
sanitizeOps: true,
sanitizeResources: true,
sanitizeExit: true,
+ permissions: null,
};
if (typeof t === "string") {
@@ -226,6 +228,17 @@ finishing test case.`;
}
}
+ function pledgeTestPermissions(permissions) {
+ return core.opSync(
+ "op_pledge_test_permissions",
+ parsePermissions(permissions),
+ );
+ }
+
+ function restoreTestPermissions(token) {
+ core.opSync("op_restore_test_permissions", token);
+ }
+
// TODO(bartlomieju): already implements AsyncGenerator<RunTestsMessage>, but add as "implements to class"
// TODO(bartlomieju): implements PromiseLike<RunTestsEndResult>
class TestRunner {
@@ -257,6 +270,7 @@ finishing test case.`;
const results = [];
const suiteStart = +new Date();
+
for (const test of this.testsToRun) {
const endMessage = {
name: test.name,
@@ -268,15 +282,30 @@ finishing test case.`;
this.stats.ignored++;
} else {
const start = +new Date();
+
+ let token;
try {
+ if (test.permissions) {
+ token = pledgeTestPermissions(test.permissions);
+ }
+
await test.fn();
+
endMessage.status = "passed";
this.stats.passed++;
} catch (err) {
endMessage.status = "failed";
endMessage.error = err;
this.stats.failed++;
+ } finally {
+ // Permissions must always be restored for a clean environment,
+ // otherwise the process can end up dropping permissions
+ // until there are none left.
+ if (token) {
+ restoreTestPermissions(token);
+ }
}
+
endMessage.duration = +new Date() - start;
}
results.push(endMessage);
diff --git a/runtime/ops/worker_host.rs b/runtime/ops/worker_host.rs
index 905ae1334..ff861c01d 100644
--- a/runtime/ops/worker_host.rs
+++ b/runtime/ops/worker_host.rs
@@ -228,7 +228,7 @@ fn merge_run_permission(
Ok(main)
}
-fn create_worker_permissions(
+pub fn create_worker_permissions(
main_perms: Permissions,
worker_perms: PermissionsArg,
) -> Result<Permissions, AnyError> {
@@ -244,7 +244,7 @@ fn create_worker_permissions(
}
#[derive(Debug, Deserialize)]
-struct PermissionsArg {
+pub struct PermissionsArg {
#[serde(default, deserialize_with = "as_unary_env_permission")]
env: Option<UnaryPermission<EnvDescriptor>>,
#[serde(default, deserialize_with = "as_permission_state")]