Skip to content

Commit

Permalink
feat: Initial implementation of assert module (#668)
Browse files Browse the repository at this point in the history
* feat: Initial implementation of assert module

---------

Co-authored-by: Richard Davison <[email protected]>
  • Loading branch information
nabetti1720 and richarddavison authored Nov 13, 2024
1 parent 1b20ad1 commit 77cf8ef
Show file tree
Hide file tree
Showing 15 changed files with 263 additions and 20 deletions.
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(())
}

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

0 comments on commit 77cf8ef

Please sign in to comment.