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: Initial implementation of assert module #668

Merged
merged 12 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
> [!NOTE]
> The long term goal for LLRT is to become [Winter CG compliant](https://github.com/wintercg/admin/blob/main/proposals.md). Not every API from Node.js will be supported.
## assert

[ok](https://nodejs.org/api/assert.html#assertokvalue-message)

## buffer

[alloc](https://nodejs.org/api/buffer.html#static-method-bufferallocsize-fill-encoding)
Expand Down
11 changes: 11 additions & 0 deletions Cargo.lock

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

39 changes: 23 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,32 +98,39 @@ The test runner also has support for filters. Using filters is as simple as addi
> [!NOTE]
> LLRT only support a fraction of the Node.js APIs. It is **NOT** a drop in replacement for Node.js, nor will it ever be. Below is a high level overview of partially supported APIs and modules. For more details consult the [API](API.md) documentation

| | Node.js | LLRT ⚠️ |
| Modules | Node.js | LLRT ⚠️ |
| ------------- | ------- | ------- |
| assert | ✔︎ | ✔︎️ |
| buffer | ✔︎ | ✔︎️ |
| streams | ✔︎ | ✔︎\* |
| child_process | ✔︎ | ✔︎⏱ |
| net:sockets | ✔︎ | ✔︎⏱ |
| net:server | ✔︎ | ✔︎ |
| tls | ✔︎ | ✘⏱ |
| fetch | ✔︎ | ✔︎ |
| http | ✔︎ | ✘⏱\*\* |
| https | ✔︎ | ✘⏱\*\* |
| console | ✔︎ | ✔︎ |
| crypto | ✔︎ | ✔︎ |
| events | ✔︎ | ✔︎ |
| fs/promises | ✔︎ | ✔︎ |
| fs | ✔︎ | ✘⏱ |
| http | ✔︎ | ✘⏱\*\* |
| https | ✔︎ | ✘⏱\*\* |
| net:sockets | ✔︎ | ✔︎⏱ |
| net:server | ✔︎ | ✔︎ |
| os | ✔︎ | ✔︎ |
| path | ✔︎ | ✔︎ |
| timers | ✔︎ | ✔︎ |
| crypto | ✔︎ | ✔︎ |
| perf_hooks | ✔︎ | ✔︎ |
| process | ✔︎ | ✔︎ |
| encoding | ✔︎ | ✔︎ |
| console | ✔︎ | ✔︎ |
| events | ✔︎ | ✔︎ |
| streams | ✔︎ | ✔︎\* |
| timers | ✔︎ | ✔︎ |
| url | ✔︎ | ✔︎ |
| tls | ✔︎ | ✘⏱ |
| zlib | ✔︎ | ✔︎ |
| ESM | ✔︎ | ✔︎ |
| CJS | ✔︎ | ✔︎ |
| async/await | ✔︎ | ✔︎ |
| Other modules | ✔︎ | ✘ |

| Features | Node.js | LLRT ⚠️ |
| ----------- | ------- | ------- |
| async/await | ✔︎ | ✔︎ |
| encoding | ✔︎ | ✔︎ |
| fetch | ✔︎ | ✔︎ |
| ESM | ✔︎ | ✔︎ |
| CJS | ✔︎ | ✔︎ |

_⚠️ = partially supported in LLRT_<br />
_⏱ = planned partial support_<br />
_\* = Not native_<br />
Expand Down
1 change: 1 addition & 0 deletions build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const ES_BUILD_OPTIONS = {
platform: "browser",
format: "esm",
external: [
"assert",
"console",
"node:console",
"crypto",
Expand Down
2 changes: 2 additions & 0 deletions llrt_core/src/module_builder.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::collections::HashSet;

use crate::modules::{
assert::AssertModule,
buffer::BufferModule,
child_process::ChildProcessModule,
console::ConsoleModule,
Expand Down Expand Up @@ -67,6 +68,7 @@ pub struct ModuleBuilder {
impl Default for ModuleBuilder {
fn default() -> Self {
Self::new()
.with_module(AssertModule)
.with_module(CryptoModule)
.with_global(crate::modules::crypto::init)
.with_global(crate::modules::util::init)
Expand Down
4 changes: 2 additions & 2 deletions llrt_core/src/modules/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
pub use llrt_modules::{
abort, buffer, child_process, crypto, events, exceptions, fs, http, navigator, net, os, path,
perf_hooks, process, url, zlib,
abort, assert, buffer, child_process, crypto, events, exceptions, fs, http, navigator, net, os,
path, perf_hooks, process, url, zlib,
};

pub mod console;
Expand Down
19 changes: 17 additions & 2 deletions llrt_core/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,6 @@ fn init(ctx: &Ctx<'_>, module_names: HashSet<&'static str>) -> Result<()> {
let mut require_in_progress_map = require_in_progress.lock().unwrap();
if let Some(obj) = require_in_progress_map.get(&import_name) {
let value = obj.clone().into_value();
require_cache.set(import_name.as_ref(), value.clone())?;
return Ok(value);
}

Expand Down Expand Up @@ -458,7 +457,23 @@ fn init(ctx: &Ctx<'_>, module_names: HashSet<&'static str>) -> Result<()> {
}
}

for prop in imported_object.props::<String, Value>() {
let props = imported_object.props::<String, Value>();

let default_export: Option<Value> = imported_object.get(PredefinedAtom::Default)?;
if let Some(default_export) = default_export {
//if default export is object attach all named exports to
if let Some(default_object) = default_export.as_object() {
for prop in props {
let (key, value) = prop?;
default_object.set(key, value)?;
}
let default_object = default_object.clone().into_value();
require_cache.set(import_name.as_ref(), default_object.clone())?;
return Ok(default_object);
}
}

for prop in props {
let (key, value) = prop?;
obj.set(key, value)?;
}
Expand Down
3 changes: 3 additions & 0 deletions llrt_modules/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ readme = "README.md"
default = ["all"]
all = [
"abort",
"assert",
"buffer",
"child-process",
"crypto",
Expand All @@ -31,6 +32,7 @@ all = [

# Modules
abort = ["llrt_abort"]
assert = ["llrt_assert"]
buffer = ["llrt_buffer"]
child-process = ["llrt_child_process"]
crypto = ["llrt_crypto"]
Expand All @@ -50,6 +52,7 @@ zlib = ["llrt_zlib"]

[dependencies]
llrt_abort = { version = "0.3.0-beta", path = "../modules/llrt_abort", optional = true }
llrt_assert = { version = "0.3.0-beta", path = "../modules/llrt_assert", optional = true }
llrt_buffer = { version = "0.3.0-beta", path = "../modules/llrt_buffer", optional = true }
llrt_child_process = { version = "0.3.0-beta", path = "../modules/llrt_child_process", optional = true }
llrt_crypto = { version = "0.3.0-beta", path = "../modules/llrt_crypto", optional = true }
Expand Down
1 change: 1 addition & 0 deletions llrt_modules/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ async fn main() -> anyhow::Result<()> {
| | Node.js | LLRT Modules | Feature | Crate |
| ------------- | ------- | ------------ | --------------- | -------------------- |
| abort | ✔︎ | ✔︎️ | `abort` | `llrt_abort` |
| assert | ✔︎ | ⚠️ | `assert` | `llrt_assert` |
| buffer | ✔︎ | ✔︎️ | `buffer` | `llrt_buffer` |
| child process | ✔︎ | ⚠️ | `child-process` | `llrt_child_process` |
| crypto | ✔︎ | ⚠️ | `crypto` | `llrt_cryto` |
Expand Down
2 changes: 2 additions & 0 deletions llrt_modules/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ pub use self::modules::*;
mod modules {
#[cfg(feature = "abort")]
pub use llrt_abort as abort;
#[cfg(feature = "assert")]
pub use llrt_assert as assert;
#[cfg(feature = "buffer")]
pub use llrt_buffer as buffer;
#[cfg(feature = "child-process")]
Expand Down
19 changes: 19 additions & 0 deletions modules/llrt_assert/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "llrt_assert"
description = "LLRT Module assert"
version = "0.3.0-beta"
edition = "2021"
license = "Apache-2.0"
repository = "https://github.com/awslabs/llrt"

[lib]
name = "llrt_assert"
path = "src/lib.rs"

[dependencies]
llrt_utils = { version = "0.3.0-beta", path = "../../libs/llrt_utils", default-features = false }
rquickjs = { git = "https://github.com/DelSkayn/rquickjs.git", rev = "3af3f46b13eb89a2694e5e4e2e73924a20fa9dd1", default-features = false }

[dev-dependencies]
llrt_test = { path = "../../libs/llrt_test" }
tokio = { version = "1", features = ["full"] }
83 changes: 83 additions & 0 deletions modules/llrt_assert/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use llrt_utils::module::ModuleInfo;
use rquickjs::{
module::{Declarations, Exports, ModuleDef},
prelude::Opt,
Ctx, Exception, Function, Result, Type, Value,
};

fn ok(ctx: Ctx, value: Value, message: Opt<Value>) -> Result<()> {
match value.type_of() {
Type::Bool => {
if value.as_bool().unwrap() {
return Ok(());
}
},
Type::Float | Type::Int => {
if value.as_number().unwrap() != 0.0 {
return Ok(());
}
},
Type::String => {
if !value.as_string().unwrap().to_string().unwrap().is_empty() {
return Ok(());
}
},
Type::Array
| Type::BigInt
| Type::Constructor
| Type::Exception
| Type::Function
| Type::Symbol
| Type::Object => {
return Ok(());
},
_ => {},
}

if let Some(obj) = message.0 {
match obj.type_of() {
Type::String => {
let msg = obj.as_string().unwrap().to_string().unwrap();
return Err(Exception::throw_message(&ctx, &msg));
},
Type::Exception => return Err(obj.as_exception().cloned().unwrap().throw()),
_ => {},
};
}

Err(Exception::throw_message(
&ctx,
"AssertionError: The expression was evaluated to a falsy value",
))
}

pub struct AssertModule;

impl ModuleDef for AssertModule {
fn declare(declare: &Declarations) -> Result<()> {
declare.declare("ok")?;

declare.declare("default")?;
Ok(())
richarddavison marked this conversation as resolved.
Show resolved Hide resolved
}

fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> {
let ok_function = Function::new(ctx.clone(), ok)?.with_name("ok")?;
ok_function.set("ok", ok_function.clone())?;

exports.export("ok", ok_function.clone())?;
exports.export("default", ok_function)?;
Ok(())
}
}

impl From<AssertModule> for ModuleInfo<AssertModule> {
fn from(val: AssertModule) -> Self {
ModuleInfo {
name: "assert",
module: val,
}
}
}
55 changes: 55 additions & 0 deletions tests/unit/assert.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import assert from "assert";

describe("assert.ok", () => {
it("Should be returned 'undefined' (So it's not an error)", () => {
expect(assert.ok(true)).toBeUndefined(); //bool
expect(assert.ok(1)).toBeUndefined(); // numeric
expect(assert.ok("non-empty string")).toBeUndefined(); // string
expect(assert.ok([])).toBeUndefined(); // array
expect(assert.ok({})).toBeUndefined(); // object
expect(assert.ok(() => {})).toBeUndefined(); // function
expect(assert.ok(123n)).toBeUndefined(); // bigint
expect(assert.ok(Symbol())).toBeUndefined(); // symbol
expect(assert.ok(new Error())).toBeUndefined(); // error
class AssertTestClass {}
expect(assert.ok(AssertTestClass)).toBeUndefined(); // constructor
});

it("Should be returned exception", () => {
const errMsg =
"AssertionError: The expression was evaluated to a falsy value";
expect(() => assert.ok(false)).toThrow(errMsg);
expect(() => assert.ok(0)).toThrow(errMsg);
expect(() => assert.ok("")).toThrow(errMsg);
expect(() => assert.ok(null)).toThrow(errMsg);
});

it("should be returned as original error message", () => {
const errMsg = "Error: Value must be true";
expect(() => assert.ok(false, errMsg)).toThrow(errMsg);
});

it("should be returned as original error", () => {
const errMsg = "Error: This is error";
expect(() => assert.ok(false, Error(errMsg))).toThrow(errMsg);
});

it("Should be handled correctly even within functions", () => {
const errMsg = "Error: Value should be truthy";
function checkValue(value) {
assert.ok(value, errMsg);
}
expect(checkValue(true)).toBeUndefined();
expect(() => checkValue(false)).toThrow(errMsg);
});
});

describe("assert", () => {
it("Should be returned 'undefined' (So it's not an error)", () => {
expect(assert(true)).toBeUndefined();
expect(assert(1)).toBeUndefined();
expect(assert("non-empty string")).toBeUndefined();
expect(assert([])).toBeUndefined();
expect(assert({})).toBeUndefined();
});
});
Loading