From fc0860ba3b814396441a9953f8ad1e6c729ca178 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Thu, 6 Jan 2022 20:48:43 -0800 Subject: [PATCH] Add more automated test coverage for the wasm bindings to prepare for the custom marshaler system --- .../JavaScript/HelperMarshal.cs | 189 +++++++++++++- .../JavaScript/JavaScriptTests.cs | 4 +- .../JavaScript/MarshalTests.cs | 245 ++++++++++++++++-- 3 files changed, 398 insertions(+), 40 deletions(-) diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/HelperMarshal.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/HelperMarshal.cs index f612dcb76be527..95c0290e9c6f62 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/HelperMarshal.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/HelperMarshal.cs @@ -1,17 +1,127 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Runtime.InteropServices; using System.Runtime.InteropServices.JavaScript; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using System.Runtime.CompilerServices; using Xunit; namespace System.Runtime.InteropServices.JavaScript.Tests { public static class HelperMarshal { + public static class CustomClassMarshaler { + public static CustomClass FromJavaScript (double d) { + return new CustomClass { D = d }; + } + + public static double ToJavaScript (CustomClass ct) { + return ct?.D ?? -1; + } + } + + public class CustomClass { + public double D; + } + + public static class CustomStructMarshaler { + public static CustomStruct FromJavaScript (double d) { + return new CustomStruct { D = d }; + } + + public static double ToJavaScript (in CustomStruct ct) { + return ct.D; + } + } + + public struct CustomStruct { + public double D; + } + + public static class CustomDateMarshaler { + public static string JavaScriptToInterchangeTransform => "return value.toISOString()"; + public static string InterchangeToJavaScriptTransform => "return new Date(value)"; + + public static CustomDate FromJavaScript (string s) { + var newDate = DateTime.Parse(s).ToUniversalTime(); + return new CustomDate { + Date = newDate + }; + } + + public static string ToJavaScript (in CustomDate cd) { + var result = cd.Date.ToString("o"); + return result; + } + } + + public struct CustomDate { + public DateTime Date; + } + + [StructLayout(LayoutKind.Sequential)] + public struct CustomVector3 { + public float X, Y, Z; + + public override string ToString () { + return $"[{X}, {Y}, {Z}]"; + } + } + + public static unsafe class CustomVector3Marshaler { + public static int ScratchBufferSize => sizeof(CustomVector3); + public static string JavaScriptToInterchangeTransform => + @" + if (bufferSize !== 12) + throw new Error('Invalid buffer size'); + if (!Array.isArray(value) || (value.length !== 3)) + throw new Error('Invalid vector3, expected [f, f, f]'); + setF32(buffer + 0, value[0]); + setF32(buffer + 4, value[1]); + setF32(buffer + 8, value[2]); + "; + + public static string InterchangeToJavaScriptTransform => + @" + if (bufferSize !== 12) + throw new Error('Invalid buffer size'); + return [getF32(buffer + 0), getF32(buffer + 4), getF32(buffer + 8)]; + "; + + public static void ToJavaScript (ref CustomVector3 value, Span buffer) { + MemoryMarshal.Write(buffer, ref value); + } + + public static CustomVector3 FromJavaScript (ReadOnlySpan buffer) { + return MemoryMarshal.AsRef(buffer); + } + } + internal const string INTEROP_CLASS = "[System.Private.Runtime.InteropServices.JavaScript.Tests]System.Runtime.InteropServices.JavaScript.Tests.HelperMarshal:"; + + internal static CustomClass _ccValue; + private static void InvokeCustomClass(CustomClass cc) + { + _ccValue = cc; + } + private static CustomClass ReturnCustomClass(CustomClass cc) + { + return cc; + } + + internal static CustomStruct _csValue; + private unsafe static void InvokeCustomStruct(CustomStruct cs) + { + _csValue = cs; + } + private static CustomStruct ReturnCustomStruct(CustomStruct cs) + { + return cs; + } + internal static int _i32Value; private static void InvokeI32(int a, int b) { @@ -104,6 +214,61 @@ private static object InvokeObj2(object obj) return obj; } + internal static DateTime _dateTimeValue; + private static void InvokeDateTime(object boxed) + { + _dateTimeValue = (DateTime)boxed; + } + private static void InvokeDateTimeOffset(DateTimeOffset dto) + { + // FIXME + _dateTimeValue = dto.DateTime; + } + private static void InvokeDateTimeByValue(DateTime dt) + { + _dateTimeValue = dt; + } + private static void InvokeCustomDate(CustomDate cd) + { + _dateTimeValue = cd.Date; + } + private static CustomDate ReturnCustomDate(CustomDate cd) + { + return cd; + } + + internal static CustomVector3 _vec3Value; + private static void InvokeCustomVector3(CustomVector3 cv3) + { + _vec3Value = cv3; + } + private static CustomVector3 MakeCustomVector3(float x, float y, float z) + { + return new CustomVector3 { + X = x, + Y = y, + Z = z + }; + } + private static CustomVector3 ReturnCustomVector3(CustomVector3 cv3) + { + return cv3; + } + private static CustomVector3 AddCustomVector3(CustomVector3 lhs, CustomVector3 rhs) + { + return new CustomVector3 { + X = lhs.X + rhs.X, + Y = lhs.Y + rhs.Y, + Z = lhs.Z + rhs.Z + }; + } + + internal static System.Uri _uriValue; + private static void InvokeUri(System.Uri uri) + { + _uriValue = uri; + } + internal static object _marshalledObject; private static object InvokeMarshalObj() { @@ -642,65 +807,65 @@ private static Func> CreateFunctionAcceptingArray() }; } - public static Task SynchronousTask() + public static Task SynchronousTask() { return Task.CompletedTask; } - public static async Task AsynchronousTask() + public static async Task AsynchronousTask() { await Task.Yield(); } - public static Task SynchronousTaskInt(int i) + public static Task SynchronousTaskInt(int i) { return Task.FromResult(i); } - public static async Task AsynchronousTaskInt(int i) + public static async Task AsynchronousTaskInt(int i) { await Task.Yield(); return i; } - public static Task FailedSynchronousTask() + public static Task FailedSynchronousTask() { return Task.FromException(new Exception()); } - public static async Task FailedAsynchronousTask() + public static async Task FailedAsynchronousTask() { await Task.Yield(); throw new Exception(); } - public static async ValueTask AsynchronousValueTask() + public static async ValueTask AsynchronousValueTask() { await Task.Yield(); } - public static ValueTask SynchronousValueTask() + public static ValueTask SynchronousValueTask() { return ValueTask.CompletedTask; } - public static ValueTask SynchronousValueTaskInt(int i) + public static ValueTask SynchronousValueTaskInt(int i) { return ValueTask.FromResult(i); } - public static async ValueTask AsynchronousValueTaskInt(int i) + public static async ValueTask AsynchronousValueTaskInt(int i) { await Task.Yield(); return i; } - public static ValueTask FailedSynchronousValueTask() + public static ValueTask FailedSynchronousValueTask() { return ValueTask.FromException(new Exception()); } - public static async ValueTask FailedAsynchronousValueTask() + public static async ValueTask FailedAsynchronousValueTask() { await Task.Yield(); throw new Exception(); diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/JavaScriptTests.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/JavaScriptTests.cs index 7158c38899c13a..d97a0b807b83b5 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/JavaScriptTests.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/JavaScriptTests.cs @@ -464,7 +464,9 @@ public static void RoundtripCSDate() var obj = (JSObject)factory.Call(null, date); var dummy = (DateTime)obj.GetObjectProperty("dummy"); - Assert.Equal(date, dummy); + // HACK: JS Dates do not contain timezone information, so date marshaling converts all dates to + // UTC dates. + Assert.Equal(date.ToUniversalTime(), dummy.ToUniversalTime()); } [Fact] diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/MarshalTests.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/MarshalTests.cs index f8955384981e0e..9eebf697c3f445 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/MarshalTests.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/MarshalTests.cs @@ -69,7 +69,7 @@ public static void MarshalArrayBuffer2Int2() for (var i = 0; i < int32View.length; i++) { int32View[i] = i * 2; } - App.call_test_method (""MarshalByteBufferToInts"", [ buffer ]); + App.call_test_method (""MarshalByteBufferToInts"", [ buffer ]); "); Assert.Equal(4, HelperMarshal._intBuffer.Length); @@ -344,7 +344,7 @@ public static void GetObjectProperties() { Runtime.InvokeJS(@" var obj = {myInt: 100, myDouble: 4.5, myString: ""Hic Sunt Dracones"", myBoolean: true}; - App.call_test_method (""RetrieveObjectProperties"", [ obj ]); + App.call_test_method (""RetrieveObjectProperties"", [ obj ]); "); Assert.Equal(100, HelperMarshal._jsProperties[0]); @@ -358,8 +358,8 @@ public static void SetObjectProperties() { Runtime.InvokeJS(@" var obj = {myInt: 200, myDouble: 0, myString: ""foo"", myBoolean: false}; - App.call_test_method (""PopulateObjectProperties"", [ obj, false ]); - App.call_test_method (""RetrieveObjectProperties"", [ obj ]); + App.call_test_method (""PopulateObjectProperties"", [ obj, false ]); + App.call_test_method (""RetrieveObjectProperties"", [ obj ]); "); Assert.Equal(100, HelperMarshal._jsProperties[0]); @@ -374,8 +374,8 @@ public static void SetObjectPropertiesIfNotExistsFalse() // This test will not create the properties if they do not already exist Runtime.InvokeJS(@" var obj = {myInt: 200}; - App.call_test_method (""PopulateObjectProperties"", [ obj, false ]); - App.call_test_method (""RetrieveObjectProperties"", [ obj ]); + App.call_test_method (""PopulateObjectProperties"", [ obj, false ]); + App.call_test_method (""RetrieveObjectProperties"", [ obj ]); "); Assert.Equal(100, HelperMarshal._jsProperties[0]); @@ -387,7 +387,7 @@ public static void SetObjectPropertiesIfNotExistsFalse() [Fact] public static void SetObjectPropertiesIfNotExistsTrue() { - // This test will set the value of the property if it exists and will create and + // This test will set the value of the property if it exists and will create and // set the value if it does not exists Runtime.InvokeJS(@" var obj = {myInt: 200}; @@ -407,7 +407,7 @@ public static void MarshalTypedArray() Runtime.InvokeJS(@" var buffer = new ArrayBuffer(16); var uint8View = new Uint8Array(buffer); - App.call_test_method (""MarshalByteBuffer"", [ uint8View ]); + App.call_test_method (""MarshalByteBuffer"", [ uint8View ]); "); Assert.Equal(16, HelperMarshal._byteBuffer.Length); @@ -437,7 +437,7 @@ public static void MarshalTypedArray2Float() { Runtime.InvokeJS(@" var typedArray = new Float32Array([1, 2.1334, 3, 4.2, 5]); - App.call_test_method (""MarshalFloat32Array"", [ typedArray ]); + App.call_test_method (""MarshalFloat32Array"", [ typedArray ]); "); Assert.Equal(1, HelperMarshal._floatBuffer[0]); @@ -456,7 +456,7 @@ public static void MarshalArrayBuffer2Float2() for (var i = 0; i < float32View.length; i++) { float32View[i] = i * 2.5; } - App.call_test_method (""MarshalArrayBufferToFloat32Array"", [ buffer ]); + App.call_test_method (""MarshalArrayBufferToFloat32Array"", [ buffer ]); "); Assert.Equal(4, HelperMarshal._floatBuffer.Length); @@ -471,7 +471,7 @@ public static void MarshalTypedArray2Double() { Runtime.InvokeJS(@" var typedArray = new Float64Array([1, 2.1334, 3, 4.2, 5]); - App.call_test_method (""MarshalFloat64Array"", [ typedArray ]); + App.call_test_method (""MarshalFloat64Array"", [ typedArray ]); "); Assert.Equal(1, HelperMarshal._doubleBuffer[0]); @@ -490,7 +490,7 @@ public static void MarshalArrayBuffer2Double() for (var i = 0; i < float64View.length; i++) { float64View[i] = i * 2.5; } - App.call_test_method (""MarshalByteBufferToDoubles"", [ buffer ]); + App.call_test_method (""MarshalByteBufferToDoubles"", [ buffer ]); "); Assert.Equal(4, HelperMarshal._doubleBuffer.Length); @@ -509,7 +509,7 @@ public static void MarshalArrayBuffer2Double2() for (var i = 0; i < float64View.length; i++) { float64View[i] = i * 2.5; } - App.call_test_method (""MarshalArrayBufferToFloat64Array"", [ buffer ]); + App.call_test_method (""MarshalArrayBufferToFloat64Array"", [ buffer ]); "); Assert.Equal(4, HelperMarshal._doubleBuffer.Length); @@ -621,7 +621,7 @@ public static void TestFunctionApply() "); Assert.Equal(2, HelperMarshal._minValue); } - + [Fact] public static void BoundStaticMethodMissingArgs() { @@ -636,7 +636,7 @@ public static void BoundStaticMethodMissingArgs() "); Assert.Equal(0, HelperMarshal._intValue); } - + [Fact] public static void BoundStaticMethodExtraArgs() { @@ -647,7 +647,7 @@ public static void BoundStaticMethodExtraArgs() "); Assert.Equal(200, HelperMarshal._intValue); } - + [Fact] public static void BoundStaticMethodArgumentTypeCoercion() { @@ -667,7 +667,7 @@ public static void BoundStaticMethodArgumentTypeCoercion() "); Assert.Equal(400, HelperMarshal._intValue); } - + [Fact] public static void BoundStaticMethodUnpleasantArgumentTypeCoercion() { @@ -697,7 +697,7 @@ public static void PassUintArgument() Assert.Equal(0xFFFFFFFEu, HelperMarshal._uintValue); } - + [Fact] public static void ReturnUintEnum () { @@ -711,7 +711,7 @@ public static void ReturnUintEnum () "); Assert.Equal((uint)TestEnum.BigValue, HelperMarshal._uintValue); } - + [Fact] public static void PassUintEnumByValue () { @@ -722,7 +722,7 @@ public static void PassUintEnumByValue () "); Assert.Equal(TestEnum.BigValue, HelperMarshal._enumValue); } - + [Fact] public static void PassUintEnumByValueMasqueradingAsInt () { @@ -735,12 +735,12 @@ public static void PassUintEnumByValueMasqueradingAsInt () "); Assert.Equal(TestEnum.BigValue, HelperMarshal._enumValue); } - + [Fact] public static void PassUintEnumByNameIsNotImplemented () { HelperMarshal._enumValue = TestEnum.Zero; - var exc = Assert.Throws( () => + var exc = Assert.Throws( () => Runtime.InvokeJS(@$" var set_enum = INTERNAL.mono_bind_static_method (""{HelperMarshal.INTEROP_CLASS}SetEnumValue"", ""j""); set_enum (""BigValue""); @@ -748,11 +748,11 @@ public static void PassUintEnumByNameIsNotImplemented () ); Assert.StartsWith("Error: Expected numeric value for enum argument, got 'BigValue'", exc.Message); } - + [Fact] public static void CannotUnboxUint64 () { - var exc = Assert.Throws( () => + var exc = Assert.Throws( () => Runtime.InvokeJS(@$" var get_u64 = INTERNAL.mono_bind_static_method (""{HelperMarshal.INTEROP_CLASS}GetUInt64"", """"); var u64 = get_u64(); @@ -860,6 +860,9 @@ public static void SymbolsAreMarshaledAsStrings() Assert.True(Object.ReferenceEquals(HelperMarshal._stringResource, HelperMarshal._stringResource2)); } + const string ExpectedDateString = "1937-07-02T05:35:02.0000000Z"; + static readonly DateTime ExpectedDateTime = DateTime.Parse(ExpectedDateString).ToUniversalTime(); + [Fact] public static void InternedStringReturnValuesWork() { @@ -902,16 +905,16 @@ public static void InvokeJSNotInGlobalScope() Assert.Null(result); } - private static async Task MarshalTask(string helperMethodName, string helperMethodArgs = "", string resolvedBody = "") + private static async Task MarshalTask(string helperMethodName, string helperMethodArgs = "", string resolvedBody = "") { Runtime.InvokeJS( @"globalThis.__test_promise_completed = false; " + @"globalThis.__test_promise_resolved = false; " + @"globalThis.__test_promise_failed = false; " + $@"var t = App.call_test_method ('{helperMethodName}', [ {helperMethodArgs} ], 'i'); " + - "t.then(result => { globalThis.__test_promise_resolved = true; " + resolvedBody + " })" + + "t.then(result => { globalThis.__test_promise_resolved = true; " + resolvedBody + " })" + " .catch(e => { globalThis.__test_promise_failed = true; })" + - " .finally(result => { globalThis.__test_promise_completed = true; }); " + + " .finally(result => { globalThis.__test_promise_completed = true; }); " + "" ); @@ -1019,5 +1022,193 @@ public static async Task MarshalFailedAsynchronousValueTaskDoesNotWorkYet() bool success = await MarshalTask("FailedAsynchronousValueTask"); Assert.False(success, "FailedAsynchronousValueTask didn't failed."); } + + [Fact] + public static void MarshalDateTime() + { + HelperMarshal._dateTimeValue = default(DateTime); + Runtime.InvokeJS( + $"var dt = new Date('{ExpectedDateString}');\r\n" + + "App.call_test_method ('InvokeDateTime', [ dt ], 'o');" + ); + Assert.Equal(ExpectedDateTime, HelperMarshal._dateTimeValue); + } + + [Fact] + public static void MarshalDateTimeDefault() + { + HelperMarshal._dateTimeValue = default(DateTime); + Runtime.InvokeJS( + $"var dt = new Date('{ExpectedDateString}');\r\n" + + "App.call_test_method ('InvokeDateTime', [ dt ]);" + ); + Assert.Equal(ExpectedDateTime, HelperMarshal._dateTimeValue); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/pull/47640")] + public static void MarshalDateTimeAutomatic() + { + HelperMarshal._dateTimeValue = default(DateTime); + Runtime.InvokeJS( + $"var dt = new Date('{ExpectedDateString}');\r\n" + + "App.call_test_method ('InvokeDateTime', [ dt ], 'a');" + ); + Assert.Equal(ExpectedDateTime, HelperMarshal._dateTimeValue); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/pull/47640")] + public static void MarshalDateTimeOffsetAutomatic() + { + HelperMarshal._dateTimeValue = default(DateTime); + Runtime.InvokeJS( + $"var dt = new Date('{ExpectedDateString}');\r\n" + + "App.call_test_method ('InvokeDateTimeOffset', [ dt ], 'a');" + ); + // FIXME + Assert.Equal(ExpectedDateTime, HelperMarshal._dateTimeValue); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/pull/47640")] + public static void MarshalDateTimeByValueAutomatic() + { + HelperMarshal._dateTimeValue = default(DateTime); + Runtime.InvokeJS( + $"var dt = new Date('{ExpectedDateString}');\r\n" + + "App.call_test_method ('InvokeDateTimeByValue', [ dt.valueOf() ], 'a');" + ); + Assert.Equal(ExpectedDateTime, HelperMarshal._dateTimeValue); + } + + [Fact] + public static void MarshalUri() + { + var expected = new System.Uri("https://www.example.com/"); + HelperMarshal._uriValue = default(System.Uri); + Runtime.InvokeJS( + @"var uri = 'https://www.example.com/'; + App.call_test_method ('InvokeUri', [ uri ], 'u');" + ); + Assert.Equal(expected, HelperMarshal._uriValue); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/pull/47640")] + public static void MarshalCustomClassAutomatic() + { + HelperMarshal._ccValue = new HelperMarshal.CustomClass (); + Runtime.InvokeJS( + "App.call_test_method ('InvokeCustomClass', [ 4.13 ], 'a');" + ); + Assert.Equal(4.13, HelperMarshal._ccValue?.D); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/pull/47640")] + public static void MarshalCustomStructAutomatic() + { + HelperMarshal._csValue = default(HelperMarshal.CustomStruct); + Runtime.InvokeJS( + "App.call_test_method ('InvokeCustomStruct', [ 4.13 ], 'a');" + ); + Assert.Equal(4.13, HelperMarshal._csValue.D); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/pull/47640")] + public static void MarshalCustomDateAutomatic() + { + HelperMarshal._dateTimeValue = default(DateTime); + Runtime.InvokeJS( + $"var dt = new Date('{ExpectedDateString}');\r\n" + + "App.call_test_method ('InvokeCustomDate', [ dt ], 'a');" + ); + Assert.Equal(ExpectedDateTime, HelperMarshal._dateTimeValue); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/pull/47640")] + public static void ReturnCustomClass() + { + HelperMarshal._ccValue = new HelperMarshal.CustomClass (); + Runtime.InvokeJS( + "var cc = App.call_test_method ('ReturnCustomClass', [ 4.13 ], 'a');" + + "App.call_test_method ('InvokeCustomClass', [ cc ], 'a');" + ); + Assert.Equal(4.13, HelperMarshal._ccValue?.D); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/pull/47640")] + public static void ReturnCustomStruct() + { + HelperMarshal._csValue = default(HelperMarshal.CustomStruct); + Runtime.InvokeJS( + "var cs = App.call_test_method ('ReturnCustomStruct', [ 4.13 ], 'a');" + + "App.call_test_method ('InvokeCustomStruct', [ cs ], 'a');" + ); + Assert.Equal(4.13, HelperMarshal._csValue.D); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/pull/47640")] + public static void ReturnCustomDate() + { + HelperMarshal._dateTimeValue = default(DateTime); + Runtime.InvokeJS( + $"var dt = new Date('{ExpectedDateString}');\r\n" + + "var cd = App.call_test_method ('ReturnCustomDate', [ dt ], 'a');" + + "App.call_test_method ('InvokeCustomDate', [ cd ], 'a');" + ); + Assert.Equal(ExpectedDateTime, HelperMarshal._dateTimeValue); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/pull/47640")] + public static void InvokeCustomVector3() + { + HelperMarshal._vec3Value = default(HelperMarshal.CustomVector3); + Runtime.InvokeJS( + "App.call_test_method ('InvokeCustomVector3', [ [1, 2.5, 4] ], 'a');" + ); + Assert.Equal(1, HelperMarshal._vec3Value.X); + Assert.Equal(2.5, HelperMarshal._vec3Value.Y); + Assert.Equal(4, HelperMarshal._vec3Value.Z); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/pull/47640")] + public static void ReturnCustomVector3() + { + HelperMarshal._vec3Value = default(HelperMarshal.CustomVector3); + Runtime.InvokeJS( + "var cv3 = App.call_test_method ('ReturnCustomVector3', [ [1, 2.5, 4] ], 'a');" + + "App.call_test_method ('InvokeCustomVector3', [ cv3 ], 'a');" + ); + Assert.Equal(1, HelperMarshal._vec3Value.X); + Assert.Equal(2.5, HelperMarshal._vec3Value.Y); + Assert.Equal(4, HelperMarshal._vec3Value.Z); + } + + [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/pull/47640")] + public static void AddCustomVector3() + { + HelperMarshal._stringResource = null; + HelperMarshal._vec3Value = default(HelperMarshal.CustomVector3); + Runtime.InvokeJS( + "var cva = App.call_test_method ('MakeCustomVector3', [1, 2.5, 4], 'aaa');" + + "var cvb = App.call_test_method ('MakeCustomVector3', [4, 3, 2], 'aaa');" + + "var res = App.call_test_method ('AddCustomVector3', [ cva, cvb ], 'aa');" + + "App.call_test_method ('InvokeCustomVector3', [ res ], 'a');" + + "App.call_test_method ('InvokeString', [ String(res) ], 'a');" + ); + Assert.Equal("5,5.5,6", HelperMarshal._stringResource); + Assert.Equal(5, HelperMarshal._vec3Value.X); + Assert.Equal(5.5, HelperMarshal._vec3Value.Y); + Assert.Equal(6, HelperMarshal._vec3Value.Z); + } } }