mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-28 14:43:28 +00:00
Import some of the zig-js-runtime env tests
- Fix passing nothing into variadic (i.e. slice) parameter - Optimize @sizeOf(T) == 0 types by avoiding uncessary allocations (something zig-js-runtime is doing)
This commit is contained in:
@@ -21,7 +21,6 @@ const ArenaAllocator = std.heap.ArenaAllocator;
|
||||
|
||||
const log = std.log.scoped(.js);
|
||||
|
||||
|
||||
// Global, should only be initialized once.
|
||||
pub const Platform = struct {
|
||||
inner: v8.Platform,
|
||||
@@ -453,7 +452,11 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
|
||||
}
|
||||
}.callback);
|
||||
|
||||
template.getInstanceTemplate().setInternalFieldCount(1);
|
||||
if (comptime isEmpty(Receiver(Struct)) == false) {
|
||||
// If the struct is empty, we won't store a Zig reference inside
|
||||
// the JS object, so we don't need to set the internal field count
|
||||
template.getInstanceTemplate().setInternalFieldCount(1);
|
||||
}
|
||||
|
||||
const class_name = v8.String.initUtf8(self.isolate, comptime classNameForStruct(Struct));
|
||||
template.setClassName(class_name);
|
||||
@@ -973,19 +976,28 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
|
||||
else => @compileError("mapZigInstanceToJs requires a v8.Object (constructors) or v8.FunctionTemplate, got: " ++ @typeName(@TypeOf(js_obj_or_template))),
|
||||
};
|
||||
|
||||
// The TAO contains the pointer ot our Zig instance as
|
||||
// well as any meta data we'll need to use it later.
|
||||
// See the TaggedAnyOpaque struct for more details.
|
||||
const tao = try scope_arena.create(TaggedAnyOpaque);
|
||||
tao.* = .{
|
||||
.ptr = value,
|
||||
.index = @field(TYPE_LOOKUP, @typeName(ptr.child)),
|
||||
.sub_type = if (@hasDecl(ptr.child, "sub_type")) ptr.child.sub_type else null,
|
||||
.offset = if (@typeInfo(ptr.child) != .@"opaque" and @hasField(ptr.child, "proto")) @offsetOf(ptr.child, "proto") else -1,
|
||||
};
|
||||
|
||||
const isolate = self.isolate;
|
||||
js_obj.setInternalField(0, v8.External.init(isolate, tao));
|
||||
|
||||
if (isEmpty(ptr.child) == false) {
|
||||
// The TAO contains the pointer ot our Zig instance as
|
||||
// well as any meta data we'll need to use it later.
|
||||
// See the TaggedAnyOpaque struct for more details.
|
||||
const tao = try scope_arena.create(TaggedAnyOpaque);
|
||||
tao.* = .{
|
||||
.ptr = value,
|
||||
.index = @field(TYPE_LOOKUP, @typeName(ptr.child)),
|
||||
.sub_type = if (@hasDecl(ptr.child, "sub_type")) ptr.child.sub_type else null,
|
||||
.offset = if (@typeInfo(ptr.child) != .@"opaque" and @hasField(ptr.child, "proto")) @offsetOf(ptr.child, "proto") else -1,
|
||||
};
|
||||
|
||||
js_obj.setInternalField(0, v8.External.init(isolate, tao));
|
||||
} else {
|
||||
// If the struct is empty, we don't need to do all
|
||||
// the TOA stuff and setting the internal data.
|
||||
// When we try to map this from JS->Zig, in
|
||||
// typeTaggedAnyOpaque, we'll also know there that
|
||||
// the type is empty and can create an empty instance.
|
||||
}
|
||||
const js_persistent = PersistentObject.init(isolate, js_obj);
|
||||
gop.value_ptr.* = js_persistent;
|
||||
return js_persistent;
|
||||
@@ -1267,7 +1279,7 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
|
||||
|
||||
// Reverses the mapZigInstanceToJs, making sure that our TaggedAnyOpaque
|
||||
// contains a ptr to the correct type.
|
||||
fn typeTaggedAnyOpaque(comptime named_function: anytype, comptime R: type, op: ?*anyopaque) !R {
|
||||
fn typeTaggedAnyOpaque(comptime named_function: anytype, comptime R: type, js_obj: v8.Object) !R {
|
||||
const ti = @typeInfo(R);
|
||||
if (ti != .pointer) {
|
||||
@compileError(std.fmt.comptimePrint(
|
||||
@@ -1276,7 +1288,15 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
|
||||
));
|
||||
}
|
||||
|
||||
const type_name = @typeName(ti.pointer.child);
|
||||
const T = ti.pointer.child;
|
||||
if (comptime isEmpty(T)) {
|
||||
// Empty structs aren't stored as TOAs and there's no data
|
||||
// stored in the JSObject's IntenrnalField. Why bother when
|
||||
// we can just return an empty struct here?
|
||||
return @constCast(@as(*const T, &.{}));
|
||||
}
|
||||
|
||||
const type_name = @typeName(T);
|
||||
if (@hasField(TypeLookup, type_name) == false) {
|
||||
@compileError(std.fmt.comptimePrint(
|
||||
"{s} has an unknown Zig type: {s}",
|
||||
@@ -1284,8 +1304,9 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
|
||||
));
|
||||
}
|
||||
|
||||
const op = js_obj.getInternalField(0).castTo(v8.External).get();
|
||||
const toa: *TaggedAnyOpaque = @alignCast(@ptrCast(op));
|
||||
const expected_type_index = @field(TYPE_LOOKUP, @typeName(ti.pointer.child));
|
||||
const expected_type_index = @field(TYPE_LOOKUP, type_name);
|
||||
|
||||
var type_index = toa.index;
|
||||
if (type_index == expected_type_index) {
|
||||
@@ -1319,6 +1340,10 @@ pub fn Env(comptime S: type, comptime types: anytype) type {
|
||||
};
|
||||
}
|
||||
|
||||
fn isEmpty(comptime T: type) bool {
|
||||
return @typeInfo(T) != .@"opaque" and @sizeOf(T) == 0;
|
||||
}
|
||||
|
||||
// Responsible for calling Zig functions from JS invokations. This could
|
||||
// probably just contained in Executor, but having this specific logic, which
|
||||
// is somewhat repetitive between constructors, functions, getters, etc contained
|
||||
@@ -1380,8 +1405,7 @@ fn Caller(comptime E: type) type {
|
||||
comptime assertSelfReceiver(named_function);
|
||||
|
||||
var args = try self.getArgs(named_function, 1, info);
|
||||
const external = info.getThis().getInternalField(0).castTo(v8.External);
|
||||
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(S), external.get());
|
||||
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(S), info.getThis());
|
||||
|
||||
// inject 'self' as the first parameter
|
||||
@field(args, "0") = zig_instance;
|
||||
@@ -1402,8 +1426,7 @@ fn Caller(comptime E: type) type {
|
||||
switch (arg_fields.len) {
|
||||
0 => {}, // getters _can_ be parameterless
|
||||
1, 2 => {
|
||||
const external = info.getThis().getInternalField(0).castTo(v8.External);
|
||||
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(S), external.get());
|
||||
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(S), info.getThis());
|
||||
comptime assertSelfReceiver(named_function);
|
||||
@field(args, "0") = zig_instance;
|
||||
if (comptime arg_fields.len == 2) {
|
||||
@@ -1421,8 +1444,7 @@ fn Caller(comptime E: type) type {
|
||||
const S = named_function.S;
|
||||
comptime assertSelfReceiver(named_function);
|
||||
|
||||
const external = info.getThis().getInternalField(0).castTo(v8.External);
|
||||
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(S), external.get());
|
||||
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(S), info.getThis());
|
||||
|
||||
const Setter = @TypeOf(named_function.func);
|
||||
var args: ParamterTypes(Setter) = undefined;
|
||||
@@ -1464,8 +1486,7 @@ fn Caller(comptime E: type) type {
|
||||
switch (arg_fields.len) {
|
||||
0, 1, 2 => @compileError(named_function.full_name ++ " must take at least a u32 and *bool parameter"),
|
||||
3, 4 => {
|
||||
const external = info.getThis().getInternalField(0).castTo(v8.External);
|
||||
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(S), external.get());
|
||||
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(S), info.getThis());
|
||||
comptime assertSelfReceiver(named_function);
|
||||
@field(args, "0") = zig_instance;
|
||||
@field(args, "1") = idx;
|
||||
@@ -1492,8 +1513,7 @@ fn Caller(comptime E: type) type {
|
||||
const S = named_function.S;
|
||||
comptime assertSelfReceiver(named_function);
|
||||
|
||||
const external = info.getThis().getInternalField(0).castTo(v8.External);
|
||||
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(S), external.get());
|
||||
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(S), info.getThis());
|
||||
|
||||
const IndexedSet = @TypeOf(named_function.func);
|
||||
var args: ParamterTypes(IndexedSet) = undefined;
|
||||
@@ -1537,8 +1557,7 @@ fn Caller(comptime E: type) type {
|
||||
switch (arg_fields.len) {
|
||||
0, 1, 2 => @compileError(named_function.full_name ++ " must take at least a u32 and *bool parameter"),
|
||||
3, 4 => {
|
||||
const external = info.getThis().getInternalField(0).castTo(v8.External);
|
||||
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(S), external.get());
|
||||
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(S), info.getThis());
|
||||
comptime assertSelfReceiver(named_function);
|
||||
@field(args, "0") = zig_instance;
|
||||
@field(args, "1") = try self.nameToString(name);
|
||||
@@ -1565,8 +1584,7 @@ fn Caller(comptime E: type) type {
|
||||
const S = named_function.S;
|
||||
comptime assertSelfReceiver(named_function);
|
||||
|
||||
const external = info.getThis().getInternalField(0).castTo(v8.External);
|
||||
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(S), external.get());
|
||||
const zig_instance = try E.typeTaggedAnyOpaque(named_function, *Receiver(S), info.getThis());
|
||||
|
||||
const IndexedSet = @TypeOf(named_function.func);
|
||||
var args: ParamterTypes(IndexedSet) = undefined;
|
||||
@@ -1749,14 +1767,17 @@ fn Caller(comptime E: type) type {
|
||||
const corresponding_js_index = last_parameter_index - adjusted_offset;
|
||||
const corresponding_js_value = info.getArg(@as(u32, @intCast(corresponding_js_index)));
|
||||
if (corresponding_js_value.isArray() == false and slice_type != u8) {
|
||||
const arr = try self.call_allocator.alloc(last_parameter_type_info.pointer.child, js_parameter_count - expected_js_parameters + 1);
|
||||
for (arr, corresponding_js_index..) |*a, i| {
|
||||
const js_value = info.getArg(@as(u32, @intCast(i)));
|
||||
a.* = try self.jsValueToZig(named_function, slice_type, js_value);
|
||||
}
|
||||
|
||||
is_variadic = true;
|
||||
@field(args, tupleFieldName(last_parameter_index)) = arr;
|
||||
if (js_parameter_count == 0) {
|
||||
@field(args, tupleFieldName(last_parameter_index)) = &.{};
|
||||
} else {
|
||||
const arr = try self.call_allocator.alloc(last_parameter_type_info.pointer.child, js_parameter_count - expected_js_parameters + 1);
|
||||
for (arr, corresponding_js_index..) |*a, i| {
|
||||
const js_value = info.getArg(@as(u32, @intCast(i)));
|
||||
a.* = try self.jsValueToZig(named_function, slice_type, js_value);
|
||||
}
|
||||
@field(args, tupleFieldName(last_parameter_index)) = arr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1773,7 +1794,7 @@ fn Caller(comptime E: type) type {
|
||||
@compileError("State must be the 2nd parameter: " ++ named_function.full_name);
|
||||
} else if (i >= js_parameter_count) {
|
||||
if (@typeInfo(param.type.?) != .optional) {
|
||||
return error.TypeError;
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
@field(args, tupleFieldName(field_index)) = null;
|
||||
} else {
|
||||
@@ -1812,7 +1833,7 @@ fn Caller(comptime E: type) type {
|
||||
if (obj.internalFieldCount() == 0) {
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
return E.typeTaggedAnyOpaque(named_function, *Receiver(ptr.child), obj.getInternalField(0).castTo(v8.External).get());
|
||||
return E.typeTaggedAnyOpaque(named_function, *Receiver(ptr.child), obj);
|
||||
}
|
||||
},
|
||||
.slice => {
|
||||
@@ -2271,3 +2292,8 @@ fn getTaggedAnyOpaque(value: v8.Value) ?*TaggedAnyOpaque {
|
||||
const external_data = obj.getInternalField(0).castTo(v8.External).get().?;
|
||||
return @alignCast(@ptrCast(external_data));
|
||||
}
|
||||
|
||||
test {
|
||||
std.testing.refAllDecls(@import("test_primitive_types.zig"));
|
||||
std.testing.refAllDecls(@import("test_complex_types.zig"));
|
||||
}
|
||||
|
||||
256
src/runtime/test_complex_types.zig
Normal file
256
src/runtime/test_complex_types.zig
Normal file
@@ -0,0 +1,256 @@
|
||||
// Copyright 2023-2024 Lightpanda (Selecy SAS)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const MyList = struct {
|
||||
items: []u8,
|
||||
|
||||
pub fn constructor(state: State, elem1: u8, elem2: u8, elem3: u8) MyList {
|
||||
var items = state.arena.alloc(u8, 3) catch unreachable;
|
||||
items[0] = elem1;
|
||||
items[1] = elem2;
|
||||
items[2] = elem3;
|
||||
return .{ .items = items };
|
||||
}
|
||||
|
||||
pub fn _first(self: *const MyList) u8 {
|
||||
return self.items[0];
|
||||
}
|
||||
|
||||
pub fn _symbol_iterator(self: *const MyList) IterableU8 {
|
||||
return IterableU8.init(self.items);
|
||||
}
|
||||
};
|
||||
|
||||
const MyVariadic = struct {
|
||||
member: u8,
|
||||
|
||||
pub fn constructor() MyVariadic {
|
||||
return .{ .member = 0 };
|
||||
}
|
||||
|
||||
pub fn _len(_: *const MyVariadic, variadic: []bool) u64 {
|
||||
return @as(u64, variadic.len);
|
||||
}
|
||||
|
||||
pub fn _first(_: *const MyVariadic, _: []const u8, variadic: []bool) bool {
|
||||
return variadic[0];
|
||||
}
|
||||
|
||||
pub fn _last(_: *const MyVariadic, variadic: []bool) bool {
|
||||
return variadic[variadic.len - 1];
|
||||
}
|
||||
|
||||
pub fn _empty(_: *const MyVariadic, _: []bool) bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn _myListLen(_: *const MyVariadic, variadic: []*const MyList) u8 {
|
||||
return @as(u8, @intCast(variadic.len));
|
||||
}
|
||||
|
||||
pub fn _myListFirst(_: *const MyVariadic, variadic: []*const MyList) ?u8 {
|
||||
if (variadic.len == 0) return null;
|
||||
return variadic[0]._first();
|
||||
}
|
||||
};
|
||||
|
||||
const MyErrorUnion = struct {
|
||||
pub fn constructor(is_err: bool) !MyErrorUnion {
|
||||
if (is_err) return error.MyError;
|
||||
return .{};
|
||||
}
|
||||
|
||||
pub fn get_withoutError(_: *const MyErrorUnion) !u8 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
pub fn get_withError(_: *const MyErrorUnion) !u8 {
|
||||
return error.MyError;
|
||||
}
|
||||
|
||||
pub fn set_withoutError(_: *const MyErrorUnion, _: bool) !void {}
|
||||
|
||||
pub fn set_withError(_: *const MyErrorUnion, _: bool) !void {
|
||||
return error.MyError;
|
||||
}
|
||||
|
||||
pub fn _funcWithoutError(_: *const MyErrorUnion) !void {}
|
||||
|
||||
pub fn _funcWithError(_: *const MyErrorUnion) !void {
|
||||
return error.MyError;
|
||||
}
|
||||
};
|
||||
|
||||
pub const MyException = struct {
|
||||
err: ErrorSet,
|
||||
|
||||
const errorNames = [_][]const u8{
|
||||
"MyCustomError",
|
||||
};
|
||||
const errorMsgs = [_][]const u8{
|
||||
"Some custom message.",
|
||||
};
|
||||
fn errorStrings(comptime i: usize) []const u8 {
|
||||
return errorNames[0] ++ ": " ++ errorMsgs[i];
|
||||
}
|
||||
|
||||
// interface definition
|
||||
|
||||
pub const ErrorSet = error{
|
||||
MyCustomError,
|
||||
};
|
||||
|
||||
pub fn init(_: Allocator, err: anyerror, _: []const u8) !MyException {
|
||||
return .{ .err = @as(ErrorSet, @errorCast(err)) };
|
||||
}
|
||||
|
||||
pub fn get_name(self: *const MyException) []const u8 {
|
||||
return switch (self.err) {
|
||||
ErrorSet.MyCustomError => errorNames[0],
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_message(self: *const MyException) []const u8 {
|
||||
return switch (self.err) {
|
||||
ErrorSet.MyCustomError => errorMsgs[0],
|
||||
};
|
||||
}
|
||||
|
||||
pub fn _toString(self: *const MyException) []const u8 {
|
||||
return switch (self.err) {
|
||||
ErrorSet.MyCustomError => errorStrings(0),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const MyTypeWithException = struct {
|
||||
pub const Exception = MyException;
|
||||
|
||||
pub fn constructor() MyTypeWithException {
|
||||
return .{};
|
||||
}
|
||||
|
||||
pub fn _withoutError(_: *const MyTypeWithException) MyException.ErrorSet!void {}
|
||||
|
||||
pub fn _withError(_: *const MyTypeWithException) MyException.ErrorSet!void {
|
||||
return MyException.ErrorSet.MyCustomError;
|
||||
}
|
||||
|
||||
pub fn _superSetError(_: *const MyTypeWithException) !void {
|
||||
return MyException.ErrorSet.MyCustomError;
|
||||
}
|
||||
|
||||
pub fn _outOfMemory(_: *const MyTypeWithException) !void {
|
||||
return error.OutOfMemory;
|
||||
}
|
||||
};
|
||||
|
||||
const IterableU8 = Iterable(u8);
|
||||
|
||||
pub fn Iterable(comptime T: type) type {
|
||||
return struct {
|
||||
const Self = @This();
|
||||
|
||||
items: []T,
|
||||
index: usize = 0,
|
||||
|
||||
pub fn init(items: []T) Self {
|
||||
return .{ .items = items };
|
||||
}
|
||||
|
||||
pub const Return = struct {
|
||||
value: ?T,
|
||||
done: bool,
|
||||
};
|
||||
|
||||
pub fn _next(self: *Self) Return {
|
||||
if (self.items.len > self.index) {
|
||||
const val = self.items[self.index];
|
||||
self.index += 1;
|
||||
return .{ .value = val, .done = false };
|
||||
} else {
|
||||
return .{ .value = null, .done = true };
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const State = struct {
|
||||
arena: Allocator,
|
||||
};
|
||||
|
||||
const testing = @import("testing.zig");
|
||||
test "JS: complex types" {
|
||||
var arena = std.heap.ArenaAllocator.init(testing.allocator);
|
||||
defer arena.deinit();
|
||||
|
||||
var runner = try testing.Runner(State, void, .{
|
||||
MyList,
|
||||
IterableU8,
|
||||
MyVariadic,
|
||||
MyErrorUnion,
|
||||
MyException,
|
||||
MyTypeWithException,
|
||||
}).init(.{ .arena = arena.allocator() }, {});
|
||||
|
||||
defer runner.deinit();
|
||||
|
||||
try runner.testCases(&.{
|
||||
.{ "let myList = new MyList(1, 2, 3);", "undefined" },
|
||||
.{ "myList.first();", "1" },
|
||||
.{ "let iter = myList[Symbol.iterator]();", "undefined" },
|
||||
.{ "iter.next().value;", "1" },
|
||||
.{ "iter.next().value;", "2" },
|
||||
.{ "iter.next().value;", "3" },
|
||||
.{ "iter.next().done;", "true" },
|
||||
.{ "let arr = Array.from(myList);", "undefined" },
|
||||
.{ "arr.length;", "3" },
|
||||
.{ "arr[0];", "1" },
|
||||
}, .{});
|
||||
|
||||
try runner.testCases(&.{
|
||||
.{ "let myVariadic = new MyVariadic();", "undefined" },
|
||||
.{ "myVariadic.len(true, false, true)", "3" },
|
||||
.{ "myVariadic.first('a_str', true, false, true, false)", "true" },
|
||||
.{ "myVariadic.last(true, false)", "false" },
|
||||
.{ "myVariadic.empty()", "true" },
|
||||
.{ "myVariadic.myListLen(myList)", "1" },
|
||||
.{ "myVariadic.myListFirst(myList)", "1" },
|
||||
}, .{});
|
||||
|
||||
try runner.testCases(&.{
|
||||
.{ "var myErrorCstr = ''; try {new MyErrorUnion(true)} catch (error) {myErrorCstr = error}; myErrorCstr", "Error: MyError" },
|
||||
.{ "let myErrorUnion = new MyErrorUnion(false);", "undefined" },
|
||||
.{ "myErrorUnion.withoutError", "0" },
|
||||
.{ "var myErrorGetter = ''; try {myErrorUnion.withError} catch (error) {myErrorGetter = error}; myErrorGetter", "Error: MyError" },
|
||||
.{ "myErrorUnion.withoutError = true", "true" },
|
||||
.{ "var myErrorSetter = ''; try {myErrorUnion.withError = true} catch (error) {myErrorSetter = error}; myErrorSetter", "Error: MyError" },
|
||||
.{ "myErrorUnion.funcWithoutError()", "undefined" },
|
||||
.{ "var myErrorFunc = ''; try {myErrorUnion.funcWithError()} catch (error) {myErrorFunc = error}; myErrorFunc", "Error: MyError" },
|
||||
}, .{});
|
||||
|
||||
try runner.testCases(&.{
|
||||
.{ "MyException.prototype.__proto__ === Error.prototype", "true" },
|
||||
.{ "let myTypeWithException = new MyTypeWithException();", "undefined" },
|
||||
.{ "myTypeWithException.withoutError()", "undefined" },
|
||||
.{ "var myCustomError = ''; try {myTypeWithException.withError()} catch (error) {myCustomError = error}", "MyCustomError: Some custom message." },
|
||||
.{ "myCustomError instanceof MyException", "true" },
|
||||
.{ "myCustomError instanceof Error", "true" },
|
||||
.{ "var mySuperError = ''; try {myTypeWithException.superSetError()} catch (error) {mySuperError = error}", "MyCustomError: Some custom message." },
|
||||
.{ "var oomError = ''; try {myTypeWithException.outOfMemory()} catch (error) {oomError = error}; oomError", "Error: out of memory" },
|
||||
}, .{});
|
||||
}
|
||||
158
src/runtime/test_primitive_types.zig
Normal file
158
src/runtime/test_primitive_types.zig
Normal file
@@ -0,0 +1,158 @@
|
||||
const std = @import("std");
|
||||
|
||||
// TODO: use functions instead of "fake" struct once we handle function API generation
|
||||
const Primitives = struct {
|
||||
pub fn constructor() Primitives {
|
||||
return .{};
|
||||
}
|
||||
|
||||
// List of bytes (string)
|
||||
pub fn _checkString(_: *const Primitives, v: []u8) []u8 {
|
||||
return v;
|
||||
}
|
||||
|
||||
// Integers signed
|
||||
|
||||
pub fn _checkI32(_: *const Primitives, v: i32) i32 {
|
||||
return v;
|
||||
}
|
||||
|
||||
pub fn _checkI64(_: *const Primitives, v: i64) i64 {
|
||||
return v;
|
||||
}
|
||||
|
||||
// Integers unsigned
|
||||
|
||||
pub fn _checkU32(_: *const Primitives, v: u32) u32 {
|
||||
return v;
|
||||
}
|
||||
|
||||
pub fn _checkU64(_: *const Primitives, v: u64) u64 {
|
||||
return v;
|
||||
}
|
||||
|
||||
// Floats
|
||||
|
||||
pub fn _checkF32(_: *const Primitives, v: f32) f32 {
|
||||
return v;
|
||||
}
|
||||
|
||||
pub fn _checkF64(_: *const Primitives, v: f64) f64 {
|
||||
return v;
|
||||
}
|
||||
|
||||
// Bool
|
||||
pub fn _checkBool(_: *const Primitives, v: bool) bool {
|
||||
return v;
|
||||
}
|
||||
|
||||
// Undefined
|
||||
// TODO: there is a bug with this function
|
||||
// void paramater does not work => avoid for now
|
||||
// pub fn _checkUndefined(_: *const Primitives, v: void) void {
|
||||
// return v;
|
||||
// }
|
||||
|
||||
// Null
|
||||
pub fn _checkNullEmpty(_: *const Primitives, v: ?u32) bool {
|
||||
return (v == null);
|
||||
}
|
||||
pub fn _checkNullNotEmpty(_: *const Primitives, v: ?u32) bool {
|
||||
return (v != null);
|
||||
}
|
||||
|
||||
// Optionals
|
||||
pub fn _checkOptional(_: *const Primitives, _: ?u8, v: u8, _: ?u8, _: ?u8) u8 {
|
||||
return v;
|
||||
}
|
||||
pub fn _checkNonOptional(_: *const Primitives, v: u8) u8 {
|
||||
std.debug.print("x: {d}\n", .{v});
|
||||
return v;
|
||||
}
|
||||
pub fn _checkOptionalReturn(_: *const Primitives) ?bool {
|
||||
return true;
|
||||
}
|
||||
pub fn _checkOptionalReturnNull(_: *const Primitives) ?bool {
|
||||
return null;
|
||||
}
|
||||
pub fn _checkOptionalReturnString(_: *const Primitives) ?[]const u8 {
|
||||
return "ok";
|
||||
}
|
||||
};
|
||||
|
||||
const testing = @import("testing.zig");
|
||||
test "JS: primitive types" {
|
||||
var runner = try testing.Runner(void, void, .{Primitives}).init({}, {});
|
||||
defer runner.deinit();
|
||||
|
||||
// constructor
|
||||
try runner.testCases(&.{
|
||||
.{ "let p = new Primitives();", "undefined" },
|
||||
}, .{});
|
||||
|
||||
// JS <> Native translation of primitive types
|
||||
try runner.testCases(&.{
|
||||
.{ "p.checkString('ok ascii') === 'ok ascii';", "true" },
|
||||
.{ "p.checkString('ok emoji 🚀') === 'ok emoji 🚀';", "true" },
|
||||
.{ "p.checkString('ok chinese 鿍') === 'ok chinese 鿍';", "true" },
|
||||
|
||||
// String (JS liberal cases)
|
||||
.{ "p.checkString(1) === '1';", "true" },
|
||||
.{ "p.checkString(null) === 'null';", "true" },
|
||||
.{ "p.checkString(undefined) === 'undefined';", "true" },
|
||||
|
||||
// Integers
|
||||
|
||||
// signed
|
||||
.{ "const min_i32 = -2147483648", "undefined" },
|
||||
.{ "p.checkI32(min_i32) === min_i32;", "true" },
|
||||
.{ "p.checkI32(min_i32-1) === min_i32-1;", "false" },
|
||||
.{ "try { p.checkI32(9007199254740995n) } catch(e) { e instanceof TypeError; }", "true" },
|
||||
|
||||
// unsigned
|
||||
.{ "const max_u32 = 4294967295", "undefined" },
|
||||
.{ "p.checkU32(max_u32) === max_u32;", "true" },
|
||||
.{ "p.checkU32(max_u32+1) === max_u32+1;", "false" },
|
||||
|
||||
// int64 (with BigInt)
|
||||
.{ "const big_int = 9007199254740995n", "undefined" },
|
||||
.{ "p.checkI64(big_int) === big_int", "true" },
|
||||
.{ "p.checkU64(big_int) === big_int;", "true" },
|
||||
.{ "p.checkI64(0) === 0;", "true" },
|
||||
.{ "p.checkI64(-1) === -1;", "true" },
|
||||
.{ "p.checkU64(0) === 0;", "true" },
|
||||
|
||||
// Floats
|
||||
// use round 2 decimals for float to ensure equality
|
||||
.{ "const r = function(x) {return Math.round(x * 100) / 100};", "undefined" },
|
||||
.{ "const double = 10.02;", "undefined" },
|
||||
.{ "r(p.checkF32(double)) === double;", "true" },
|
||||
.{ "r(p.checkF64(double)) === double;", "true" },
|
||||
|
||||
// Bool
|
||||
.{ "p.checkBool(true);", "true" },
|
||||
.{ "p.checkBool(false);", "false" },
|
||||
.{ "p.checkBool(0);", "false" },
|
||||
.{ "p.checkBool(1);", "true" },
|
||||
|
||||
// Bool (JS liberal cases)
|
||||
.{ "p.checkBool(null);", "false" },
|
||||
.{ "p.checkBool(undefined);", "false" },
|
||||
|
||||
// Undefined
|
||||
// see TODO on Primitives.checkUndefined
|
||||
// .{ "p.checkUndefined(undefined) === undefined;", "true" },
|
||||
|
||||
// Null
|
||||
.{ "p.checkNullEmpty(null);", "true" },
|
||||
.{ "p.checkNullEmpty(undefined);", "true" },
|
||||
.{ "p.checkNullNotEmpty(1);", "true" },
|
||||
|
||||
// Optional
|
||||
.{ "p.checkOptional(null, 3);", "3" },
|
||||
.{ "p.checkNonOptional();", "TypeError" },
|
||||
.{ "p.checkOptionalReturn() === true;", "true" },
|
||||
.{ "p.checkOptionalReturnNull() === null;", "true" },
|
||||
.{ "p.checkOptionalReturnString() === 'ok';", "true" },
|
||||
}, .{});
|
||||
}
|
||||
86
src/runtime/testing.zig
Normal file
86
src/runtime/testing.zig
Normal file
@@ -0,0 +1,86 @@
|
||||
const std = @import("std");
|
||||
const js = @import("js.zig");
|
||||
const generate = @import("generate.zig");
|
||||
|
||||
pub const allocator = std.testing.allocator;
|
||||
|
||||
// Very similar to the JSRunner in src/testing.zig, but it isn't tied to the
|
||||
// browser.Env or the browser.SessionState
|
||||
pub fn Runner(comptime State: type, comptime Global: type, comptime types: anytype) type {
|
||||
const AdjustedTypes = if (Global == void) generate.Tuple(.{ types, DefaultGlobal }) else types;
|
||||
const Env = js.Env(State, AdjustedTypes{});
|
||||
|
||||
return struct {
|
||||
env: *Env,
|
||||
executor: *Env.Executor,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn init(state: State, global: Global) !*Self {
|
||||
const runner = try allocator.create(Self);
|
||||
errdefer allocator.destroy(runner);
|
||||
|
||||
runner.env = try Env.init(allocator, .{});
|
||||
errdefer runner.env.deinit();
|
||||
|
||||
const G = if (Global == void) DefaultGlobal else Global;
|
||||
|
||||
runner.executor = try runner.env.startExecutor(G, state, runner);
|
||||
errdefer runner.env.stopExecutor(runner.executor);
|
||||
|
||||
try runner.executor.startScope(if (Global == void) &default_global else global);
|
||||
return runner;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.executor.endScope();
|
||||
self.env.stopExecutor(self.executor);
|
||||
self.env.deinit();
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
const RunOpts = struct {};
|
||||
pub const Case = std.meta.Tuple(&.{ []const u8, []const u8 });
|
||||
pub fn testCases(self: *Self, cases: []const Case, _: RunOpts) !void {
|
||||
for (cases, 0..) |case, i| {
|
||||
var try_catch: Env.TryCatch = undefined;
|
||||
try_catch.init(self.executor);
|
||||
defer try_catch.deinit();
|
||||
|
||||
const value = self.executor.exec(case.@"0", null) catch |err| {
|
||||
if (try try_catch.err(allocator)) |msg| {
|
||||
defer allocator.free(msg);
|
||||
if (isExpectedTypeError(case.@"1", msg)) {
|
||||
continue;
|
||||
}
|
||||
std.debug.print("{s}\n\nCase: {d}\n{s}\n", .{ msg, i + 1, case.@"0" });
|
||||
}
|
||||
return err;
|
||||
};
|
||||
|
||||
const actual = try value.toString(allocator);
|
||||
defer allocator.free(actual);
|
||||
if (std.mem.eql(u8, case.@"1", actual) == false) {
|
||||
std.debug.print("Expected:\n{s}\n\nGot:\n{s}\n\nCase: {d}\n{s}\n", .{ case.@"1", actual, i + 1, case.@"0" });
|
||||
return error.UnexpectedResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fetchModuleSource(ctx: *anyopaque, specifier: []const u8) ![]const u8 {
|
||||
_ = ctx;
|
||||
_ = specifier;
|
||||
return error.DummyModuleLoader;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn isExpectedTypeError(expected: []const u8, msg: []const u8) bool {
|
||||
if (!std.mem.eql(u8, expected, "TypeError")) {
|
||||
return false;
|
||||
}
|
||||
return std.mem.startsWith(u8, msg, "TypeError: ");
|
||||
}
|
||||
|
||||
var default_global = DefaultGlobal{};
|
||||
const DefaultGlobal = struct {};
|
||||
Reference in New Issue
Block a user