Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
b40abf8
add test demonstrating runtime exit on failing to marshal task result…
ArcadeMode Jan 23, 2026
c5358b7
fix by marshalling error to dotnet task
ArcadeMode Jan 23, 2026
5004c25
add jsimport test
ArcadeMode Jan 23, 2026
ab6efdd
test another range limited type
ArcadeMode Jan 23, 2026
c2bfc28
demonstrate with a different assertion
ArcadeMode Jan 23, 2026
5aed026
remove leftover console writes
ArcadeMode Jan 23, 2026
d049f55
colocate tests, add datetime overflow im+export tests that crash runtime
ArcadeMode Jan 23, 2026
bc4ff51
add range assert for date unitTime that .net accepts
ArcadeMode Jan 23, 2026
3e42d91
move range check to marshal.ts
ArcadeMode Jan 24, 2026
51cb10a
line up error message with mono impl
ArcadeMode Jan 24, 2026
4534795
marshal exception if task result conversion fails in native interop impl
ArcadeMode Jan 25, 2026
baf1281
assert supported date range in mono and native
ArcadeMode Jan 25, 2026
593e417
update tests to reflect friendly messages
ArcadeMode Jan 25, 2026
9c7afea
test correctness of date boundary value marshalling
ArcadeMode Jan 25, 2026
253aa3c
min/max boundary tests
ArcadeMode Jan 25, 2026
e341358
add test for date precision loss
ArcadeMode Jan 25, 2026
b6ce6bc
add datetime and delegate datetime/long overflow tests
ArcadeMode Jan 25, 2026
0c15303
naming convention
ArcadeMode Jan 25, 2026
ffa1535
Merge branch 'main' into wasm-runtime-exit-outofrange-task-value
pavelsavara Jan 26, 2026
3078e9d
mono check instead of assert
ArcadeMode Jan 28, 2026
78ba06c
demonstrate array elements value overflow for int32
ArcadeMode Jan 28, 2026
bb43231
test name
ArcadeMode Jan 28, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -392,10 +392,10 @@ public async Task JsExportTaskOfInt(int value)

[Theory]
[MemberData(nameof(MarshalBigInt64Cases))]
public async Task JsExportTaskOfLong(long value)
public async Task JsExportTaskOfBigLong(long value)
{
TaskCompletionSource<long> tcs = new TaskCompletionSource<long>();
var res = JavaScriptTestHelper.invoke1_TaskOfLong(tcs.Task, nameof(JavaScriptTestHelper.AwaitTaskOfInt64));
var res = JavaScriptTestHelper.invoke1_TaskOfBigLong(tcs.Task, nameof(JavaScriptTestHelper.AwaitTaskOfInt64));
tcs.SetResult(value); // unresolved task marshalls promise and resolves on completion
await Task.Yield();
var rr = await res;
Expand All @@ -405,11 +405,11 @@ public async Task JsExportTaskOfLong(long value)

[Theory]
[MemberData(nameof(MarshalBigInt64Cases))]
public async Task JsExportCompletedTaskOfLong(long value)
public async Task JsExportCompletedTaskOfBigLong(long value)
{
TaskCompletionSource<long> tcs = new TaskCompletionSource<long>();
tcs.SetResult(value); // completed task marshalls value immediately
var res = JavaScriptTestHelper.invoke1_TaskOfLong(tcs.Task, nameof(JavaScriptTestHelper.AwaitTaskOfInt64));
var res = JavaScriptTestHelper.invoke1_TaskOfBigLong(tcs.Task, nameof(JavaScriptTestHelper.AwaitTaskOfInt64));
await Task.Yield();
var rr = await res;
await Task.Yield();
Expand Down Expand Up @@ -449,6 +449,18 @@ public void JsExportCallback_FunctionIntIntThrow()
Assert.Same(expected, actual);
}

[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))]
public async Task JsExportFunctionDateTimeDateTime()
{
DateTime input = DateTime.Now;
DateTime receivedArg = DateTime.MinValue;
DateTime returnVal = JavaScriptTestHelper.invokeDelegateOfDateTime((DateTime date) => {
return receivedArg = date;
}, input, 0);
Assert.Equal(input, receivedArg);
Assert.Equal(input, returnVal);
}

private void JsExportTest<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] T>(T value
, Func<T, string, T> invoke, string echoName, string jsType, string? jsClass = null)
{
Expand All @@ -469,5 +481,90 @@ public async Task JSExportCompletedTaskReturnsResolvedPromise()
string result = await JavaScriptTestHelper.InvokeReturnCompletedTask();
Assert.Equal("resolved", result);
}

#region Assertion Errors
[Fact]
public async Task JsExportTaskOfShortOutOfRange_ThrowsAssertionInTaskContinuation()
{
// 1<<16 is out of range, passed to js and back, marshalling ts code asserts out of range and throws
Task<short> res = JavaScriptTestHelper.invoke1_TaskOfOutOfRangeShort(Task.FromResult(1 << 16), nameof(JavaScriptTestHelper.AwaitTaskOfShort));
JSException ex = await Assert.ThrowsAsync<JSException>(() => res);
Assert.Equal("Error: Assert failed: Overflow: value 65536 is out of -32768 32767 range", ex.Message);
}

[Fact]
public async Task JsExportTaskOfStringTypeAssertion_ThrowsAssertionInTaskContinuation()
{
// long value cannot be converted to string, error thrown through continuation in CS
Task<string> res = JavaScriptTestHelper.invoke1_TaskOfLong_ExceptionReturnTypeAssert(Task.FromResult(1L << 32), nameof(JavaScriptTestHelper.AwaitTaskOfString));
JSException ex = await Assert.ThrowsAsync<JSException>(() => res);
Assert.Equal("Error: Assert failed: Value is not a String", ex.Message);
}

[Fact]
public async Task JsExportTaskOfLong_TaskReturnValue_OverflowInt52()
{
long value = 1L << 53;
TaskCompletionSource<long> tcs = new TaskCompletionSource<long>();
var res = JavaScriptTestHelper.invoke1_TaskOfLong(tcs.Task, nameof(JavaScriptTestHelper.AwaitTaskOfInt64));
tcs.SetResult(value);
JSException ex = await Assert.ThrowsAsync<JSException>(() => res);
Assert.Equal("Error: Assert failed: Value is not an integer: 9007199254740992 (bigint)", ex.Message);
}

[Fact]
public async Task JsExportTaskOfDateTime_TaskReturnValue_OverflowNETDateTime()
{
var res = JavaScriptTestHelper.invokeExportWithTaskOfMaxJSDateTime(nameof(JavaScriptTestHelper.AwaitTaskOfDateTime));
JSException ex = await Assert.ThrowsAsync<JSException>(() => res);
Assert.Equal("Error: Assert failed: Overflow: value +275760-09-13T00:00:00.000Z is out of 0001-01-01T00:00:00.000Z 9999-12-31T23:59:59.999Z range", ex.Message);
}

[Fact]
public async Task JsExportDateTime_ReturnValue_OverflowNETDateTime()
{
JSException ex = Assert.Throws<JSException>(() => JavaScriptTestHelper.invokeExportWithMaxJSDateTime(nameof(JavaScriptTestHelper.EchoDateTime)));
Assert.Equal("Error: Assert failed: Overflow: value +275760-09-13T00:00:00.000Z is out of 0001-01-01T00:00:00.000Z 9999-12-31T23:59:59.999Z range", ex.Message);
}

[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))]
public async Task JsExportFuncOfDateTime_Argument_OverflowNETDateTime()
{
DateTime receivedArg = DateTime.MinValue;
JSException ex = Assert.Throws<JSException>(() => JavaScriptTestHelper.invokeDelegateOfDateTime((DateTime date) => {
return receivedArg = date;
}, DateTime.MaxValue, 60_001));
Assert.Equal("Error: Assert failed: Overflow: value +010000-01-01T00:01:00.000Z is out of 0001-01-01T00:00:00.000Z 9999-12-31T23:59:59.999Z range", ex.Message);
Assert.Equal(DateTime.MinValue, receivedArg); // delegate invoke failed, no change to arg
}

[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))]
public void JsExportCallback_FunctionLongLong_OverflowInt52_JSSide()
{
long called = -1;
Assert.Equal(-1, called);
JSException ex = Assert.Throws<JSException>(() => JavaScriptTestHelper.invokeFuncOfLongLong((long a) =>
{
return called = a;
}, 9007199254740991, offset: 1));
Assert.Equal(-1, called);
Assert.Equal("Error: Assert failed: Value is not an integer: 9007199254740992 (number)", ex.Message);
}

[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))]
public void JsExportCallback_FunctionLongLong_OverflowInt52_NETSide()
{
long called = -1;
var chain = JavaScriptTestHelper.invoke1_FuncOfLongLong((long a) =>
{
return called = a;
}, nameof(JavaScriptTestHelper.BackFuncOfLongLong));

Assert.Equal(-1, called);
OverflowException ex = Assert.Throws<OverflowException>(() => chain(long.MaxValue));
Assert.Equal(-1, called);
Assert.Equal("Overflow: value 9223372036854775807 is out of -9007199254740991 9007199254740991 range.", ex.Message);
}
#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,42 @@ public unsafe void DotnetInstance()
Assert.Equal("Yoda", JSHost.DotnetInstance.GetPropertyAsString("testString"));
}

[Fact]
public async Task RejectString()
{
var ex = await Assert.ThrowsAsync<JSException>(() => JavaScriptTestHelper.Reject("noodles"));
Assert.Contains("noodles", ex.Message);
}

[Fact]
public async Task RejectException()
{
var expected = new Exception("noodles");
var actual = await Assert.ThrowsAsync<Exception>(() => JavaScriptTestHelper.Reject(expected));
Assert.Equal(expected, actual);
}

[Fact]
public async Task RejectNull()
{
var ex = await Assert.ThrowsAsync<JSException>(() => JavaScriptTestHelper.Reject(null));
}

[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))]
public unsafe void OptimizedPaths()
{
JavaScriptTestHelper.optimizedReached = 0;
JavaScriptTestHelper.invoke0V();
Assert.Equal(1, JavaScriptTestHelper.optimizedReached);
JavaScriptTestHelper.invoke1V(42);
Assert.Equal(43, JavaScriptTestHelper.optimizedReached);
Assert.Equal(124, JavaScriptTestHelper.invoke1R(123));
Assert.Equal(43 + 123, JavaScriptTestHelper.optimizedReached);
Assert.Equal(32, JavaScriptTestHelper.invoke2R(15, 16));
Assert.Equal(43 + 123 + 31, JavaScriptTestHelper.optimizedReached);
}

#region Assertion Errors
[Fact]
public unsafe void BadCast()
{
Expand Down Expand Up @@ -136,40 +172,62 @@ public unsafe void OutOfRange()
}

[Fact]
public async Task RejectString()
public async Task TaskOfShortOutOfRange_ThrowsAssertionInTaskContinuation()
{
var ex = await Assert.ThrowsAsync<JSException>(() => JavaScriptTestHelper.Reject("noodles"));
Assert.Contains("noodles", ex.Message);
Task<short> res = JavaScriptTestHelper.ReturnResolvedPromiseWithIntMaxValue_AsShortToBeOutOfRange();
JSException ex = await Assert.ThrowsAsync<JSException>(() => res);
Assert.Equal("Error: Assert failed: Overflow: value 2147483647 is out of -32768 32767 range", ex.Message);
}

[Fact]
public async Task RejectException()
public async Task TaskOfByteOutOfRange_ThrowsAssertionInTaskContinuation()
{
var expected = new Exception("noodles");
var actual = await Assert.ThrowsAsync<Exception>(() => JavaScriptTestHelper.Reject(expected));
Assert.Equal(expected, actual);
Task<byte> res = JavaScriptTestHelper.ReturnResolvedPromiseWithIntMaxValue_AsByteToBeOutOfRange();
JSException ex = await Assert.ThrowsAsync<JSException>(() => res);
Assert.Equal("Error: Assert failed: Overflow: value 2147483647 is out of 0 255 range", ex.Message);
}

[Fact]
public async Task RejectNull()
public async Task TaskOfDateTimeOutOfRange_ThrowsAssertionInTaskContinuation()
{
var ex = await Assert.ThrowsAsync<JSException>(() => JavaScriptTestHelper.Reject(null));
Task<DateTime> res = JavaScriptTestHelper.ReturnResolvedPromiseWithDateMaxValue();
JSException ex = await Assert.ThrowsAsync<JSException>(() => res);
Assert.Equal("Error: Assert failed: Overflow: value +275760-09-13T00:00:00.000Z is out of 0001-01-01T00:00:00.000Z 9999-12-31T23:59:59.999Z range", ex.Message);
}

[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))]
public unsafe void OptimizedPaths()
[Fact]
public async Task DateTimeMaxValueBoundaryCondition()
{
JavaScriptTestHelper.optimizedReached = 0;
JavaScriptTestHelper.invoke0V();
Assert.Equal(1, JavaScriptTestHelper.optimizedReached);
JavaScriptTestHelper.invoke1V(42);
Assert.Equal(43, JavaScriptTestHelper.optimizedReached);
Assert.Equal(124, JavaScriptTestHelper.invoke1R(123));
Assert.Equal(43 + 123, JavaScriptTestHelper.optimizedReached);
Assert.Equal(32, JavaScriptTestHelper.invoke2R(15, 16));
Assert.Equal(43 + 123 + 31, JavaScriptTestHelper.optimizedReached);
DateTime t = JavaScriptTestHelper.ReturnDateTimeWithOffset(DateTime.MaxValue, 0);
Assert.Equal(DateTime.MaxValue.AddTicks(-9_999), t); // Microseconds are lost during marshalling
JSException ex = Assert.Throws<JSException>(() => JavaScriptTestHelper.ReturnDateTimeWithOffset(DateTime.MaxValue, 1));
Assert.Equal("Error: Assert failed: Overflow: value +010000-01-01T00:00:00.000Z is out of 0001-01-01T00:00:00.000Z 9999-12-31T23:59:59.999Z range", ex.Message);
}

[Fact]
public async Task DateTimeMinValueBoundaryCondition()
{
DateTime t = JavaScriptTestHelper.ReturnDateTimeWithOffset(DateTime.MinValue, 0);
Assert.Equal(DateTime.MinValue, t);
JSException ex = Assert.Throws<JSException>(() => JavaScriptTestHelper.ReturnDateTimeWithOffset(DateTime.MinValue, -1));
Assert.Equal("Error: Assert failed: Overflow: value 0000-12-31T23:59:59.999Z is out of 0001-01-01T00:00:00.000Z 9999-12-31T23:59:59.999Z range", ex.Message);
}

[Fact]
public async Task Int32ArrayWithOutOfRangeValues()
{
int[] arr = JavaScriptTestHelper.getInt32ArrayWithOutOfRangeValues();
Assert.Equal(0, arr[0]);
Assert.Equal(1, arr[1]);
Assert.Equal(-2147483648, arr[2]);
Assert.Equal(-1147483648, arr[3]);
Assert.Equal(-1, arr[4]);
Assert.Equal(0, arr[5]);
// Currently, values > int32.MaxValue are wrapped around when marshaled from JS to C#.
// TODO: Instead throw OverflowException when out of range value is encountered.
}

#endregion

#region Get/Set Property

Expand Down Expand Up @@ -718,6 +776,15 @@ public void JSImportDateTime(DateTime value)
"object", "Date");
}

[Fact] // JavaScript Date has millisecond precision, the microseconds are lost during marshalling
public async Task DateTimeMarshallingLosesMicrosecondComponentPrecisionLoss()
{
DateTime now = new DateTime(1995, 4, 1, 10, 43, 6, 94, microsecond: 20);
DateTime t = JavaScriptTestHelper.ReturnDateTimeWithOffset(now, 0);
Assert.NotEqual(now, t);
DateTime nowWithJSPrecision = now.AddMicroseconds(-now.Microsecond);
Assert.Equal(nowWithJSPrecision, t);
}
#endregion Datetime

#region DateTimeOffset
Expand Down
Loading
Loading