Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli): add test permissions to Deno.test #10188

Merged
merged 36 commits into from
Apr 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
7fbc519
feat(cli): add test permissions to Deno.TestDefinition
caspervonb Apr 14, 2021
83a4469
Prefer const
caspervonb Apr 14, 2021
edf2c1a
Tag comment
caspervonb Apr 14, 2021
71cee3b
Better permission denied tests
caspervonb Apr 14, 2021
c6a4906
Fixup hrtime argument
caspervonb Apr 14, 2021
c3115f8
Fixup test
caspervonb Apr 14, 2021
917f5cb
Better test coverage
caspervonb Apr 14, 2021
5e873e1
Require explicit initialization of test ops
caspervonb Apr 14, 2021
587eb56
s/request/pledge/
caspervonb Apr 14, 2021
f9191aa
s/restore_test_permissions/permissions/
caspervonb Apr 14, 2021
65e2549
Format
caspervonb Apr 15, 2021
8fc8eed
s/requestTestPermissions/pledgeTestPermissions
caspervonb Apr 15, 2021
2a469aa
Lint
caspervonb Apr 15, 2021
ed9d07a
Re-use worker permissions as test permissions
caspervonb Apr 15, 2021
8a8cbc9
Format
caspervonb Apr 15, 2021
d04f19e
Lint
caspervonb Apr 15, 2021
d719813
revert test_util.ts changes
caspervonb Apr 15, 2021
001773f
Add token
caspervonb Apr 15, 2021
8cf960c
Lint
caspervonb Apr 16, 2021
ac8e8ee
Mark permissions as unstable
caspervonb Apr 16, 2021
d8429ef
Move type definition to unstable
caspervonb Apr 16, 2021
c87220f
Doc comment
caspervonb Apr 16, 2021
8cff7e1
Merge branch 'main' into feat-add-test-permissions
bartlomieju Apr 21, 2021
d95c6b0
Merge branch 'main' into feat-add-test-permissions
caspervonb Apr 22, 2021
f896194
Error if restore is called without any permissions to take
caspervonb Apr 25, 2021
4228ab6
Clarify outdated comment
caspervonb Apr 25, 2021
ac6af79
Merge branch 'main' into feat-add-test-permissions
caspervonb Apr 25, 2021
27568e2
Use Uuid inplace of Value where applicable
caspervonb Apr 25, 2021
5f07ce1
Update docs
caspervonb Apr 25, 2021
da70412
Add missing defaults doc comments
caspervonb Apr 25, 2021
34c46ea
Fix rogue u keystroke
caspervonb Apr 25, 2021
6251f43
Return ()
caspervonb Apr 25, 2021
0b9e8b6
Add Bartek's comment note
caspervonb Apr 25, 2021
5a4b659
More net doc comments
caspervonb Apr 25, 2021
83fbff0
Fixup examples
caspervonb Apr 25, 2021
afa31da
Merge branch 'main' into feat-add-test-permissions
caspervonb Apr 25, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
134 changes: 134 additions & 0 deletions cli/dts/lib.deno.unstable.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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" | {
piscisaureus marked this conversation as resolved.
Show resolved Hide resolved
/** 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>;
piscisaureus marked this conversation as resolved.
Show resolved Hide resolved

/** 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(
Expand Down
25 changes: 18 additions & 7 deletions cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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();
Expand Down Expand Up @@ -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'))")?;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions cli/ops/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@

pub mod errors;
pub mod runtime_compiler;
pub mod test_runner;

pub use deno_runtime::ops::{reg_async, reg_sync};
66 changes: 66 additions & 0 deletions cli/ops/test_runner.rs
Original file line number Diff line number Diff line change
@@ -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);
caspervonb marked this conversation as resolved.
Show resolved Hide resolved

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"))
}
caspervonb marked this conversation as resolved.
Show resolved Hide resolved
}
12 changes: 12 additions & 0 deletions cli/tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
18 changes: 18 additions & 0 deletions cli/tests/test/allow_all.out
Original file line number Diff line number Diff line change
@@ -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]
35 changes: 35 additions & 0 deletions cli/tests/test/allow_all.ts
Original file line number Diff line number Diff line change
@@ -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");
},
});
}
Loading