Skip to content

Commit

Permalink
Add easier APIs for interop usage (#1351)
Browse files Browse the repository at this point in the history
  • Loading branch information
lahma authored Nov 8, 2022
1 parent 826bf4f commit c680830
Show file tree
Hide file tree
Showing 22 changed files with 303 additions and 136 deletions.
58 changes: 29 additions & 29 deletions Jint.Tests.PublicInterface/ConstraintUsageTests.cs
Original file line number Diff line number Diff line change
@@ -1,42 +1,42 @@
using Jint.Constraints;
using Jint.Runtime;

namespace Jint.Tests.PublicInterface
namespace Jint.Tests.PublicInterface;

public class ConstraintUsageTests
{
public class ConstraintUsageTests
[Fact]
public void CanFindAndResetCancellationConstraint()
{
[Fact]
public void CanFindAndResetCancellationConstraint()
{
using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(100));
var engine = new Engine(new Options().CancellationToken(cts.Token));

// expect constraint to abort execution due to timeout
Assert.Throws<ExecutionCanceledException>(WaitAndCompute);
using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(100));
var engine = new Engine(new Options().CancellationToken(cts.Token));

// ensure constraint can be obtained publicly
var cancellationConstraint = engine.FindConstraint<CancellationConstraint>();
Assert.NotNull(cancellationConstraint);
// expect constraint to abort execution due to timeout
Assert.Throws<ExecutionCanceledException>(WaitAndCompute);

// reset constraint, expect computation to finish this time
using var cts2 = new CancellationTokenSource(TimeSpan.FromMilliseconds(500));
cancellationConstraint.Reset(cts2.Token);
Assert.Equal("done", WaitAndCompute());
// ensure constraint can be obtained publicly
var cancellationConstraint = engine.FindConstraint<CancellationConstraint>();
Assert.NotNull(cancellationConstraint);

string WaitAndCompute()
{
var result = engine.Evaluate(@"
function sleep(millisecondsTimeout) {
var totalMilliseconds = new Date().getTime() + millisecondsTimeout;
// reset constraint, expect computation to finish this time
using var cts2 = new CancellationTokenSource(TimeSpan.FromMilliseconds(500));
cancellationConstraint.Reset(cts2.Token);
Assert.Equal("done", WaitAndCompute());

while (new Date() < totalMilliseconds) { }
string WaitAndCompute()
{
var result = engine.Evaluate(@"
function sleep(millisecondsTimeout) {
var x = 0;
var totalMilliseconds = new Date().getTime() + millisecondsTimeout;
while (new Date().getTime() < totalMilliseconds) {
x++;
}
sleep(200);
return 'done';
");
return result.AsString();
}
}
sleep(200);
return 'done';
");
return result.AsString();
}
}
}
47 changes: 47 additions & 0 deletions Jint.Tests.PublicInterface/RavenApiUsageTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
using Esprima.Ast;
using Jint.Constraints;
using Jint.Native;
using Jint.Native.Array;
using Jint.Native.Date;
using Jint.Native.Function;
using Jint.Native.Object;
using Jint.Runtime.Descriptors;
using Jint.Runtime.Interop;

Expand Down Expand Up @@ -78,4 +81,48 @@ public void CanGetPropertyDescriptor()
var propertyDescriptor = ObjectWrapper.GetPropertyDescriptor(engine, obj, obj.GetType().GetProperty(nameof(DirectoryInfo.Name)));
Assert.Equal("the-path", propertyDescriptor.Value);
}

[Fact]
public void CanInjectConstructedObjects()
{
var engine = new Engine();
var obj = new ObjectInstance(engine);
obj.FastSetDataProperty("name", "test");

var emptyArray = new ArrayInstance(engine);

var array = new ArrayInstance(engine, new object[]
{
JsNumber.Create(1),
JsNumber.Create(2),
JsNumber.Create(3)
});
var date = new DateInstance(engine, new DateTime(2022, 10, 20));

engine.SetValue("obj", obj);
engine.SetValue("emptyArray", emptyArray);
engine.SetValue("array", array);
engine.SetValue("date", date);

Assert.Equal("test", engine.Evaluate("obj.name"));
Assert.Equal(0, engine.Evaluate("emptyArray.length"));
Assert.Equal(1, engine.Evaluate("array.findIndex(x => x === 2)"));
Assert.Equal(2022, engine.Evaluate("date.getFullYear()"));

array.Push(4);
array.Push(new JsValue[] { 5, 6 });

Assert.Equal(4, array[3]);
Assert.Equal(5, array[4]);
Assert.Equal(6, array[5]);

var i = 0;
foreach (var entry in array.GetEntries())
{
Assert.Equal(i.ToString(), entry.Key);
Assert.Equal(i + 1, entry.Value);
i++;
}
Assert.Equal(6, i);
}
}
2 changes: 1 addition & 1 deletion Jint.Tests/Runtime/ArrayTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public void LargeArraySize()
public void ArrayLengthFromInitialState()
{
var engine = new Engine();
var array = new ArrayInstance(engine, 0);
var array = new ArrayInstance(engine);
var length = (int) array.Length;
Assert.Equal(0, length);
}
Expand Down
4 changes: 2 additions & 2 deletions Jint.Tests/Runtime/Domain/UuidConstructor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ public static UuidConstructor CreateUuidConstructor(Engine engine)

public void Configure()
{
FastAddProperty("parse", new ClrFunctionInstance(Engine, "parse", Parse), true, false, true);
FastAddProperty("Empty", JsUuid.Empty, true, false, true);
FastSetProperty("parse", new PropertyDescriptor(new ClrFunctionInstance(Engine, "parse", Parse), true, false, true));
FastSetProperty("Empty", new PropertyDescriptor(JsUuid.Empty, true, false, true));
}

public UuidInstance Construct(JsUuid uuid) => new UuidInstance(Engine) { PrimitiveValue = uuid, _prototype = PrototypeObject };
Expand Down
7 changes: 4 additions & 3 deletions Jint.Tests/Runtime/Domain/UuidPrototype.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Jint.Native;
using Jint.Runtime;
using Jint.Runtime.Descriptors;
using Jint.Runtime.Interop;

namespace Jint.Tests.Runtime.Domain
Expand Down Expand Up @@ -30,15 +31,15 @@ public static UuidPrototype CreatePrototypeObject(Engine engine, UuidConstructor
_prototype = engine.Realm.Intrinsics.Object.PrototypeObject,
};

obj.FastAddProperty("constructor", ctor, false, false, true);
obj.FastSetProperty("constructor", new PropertyDescriptor(ctor, false, false, true));

return obj;
}

public void Configure()
{
FastAddProperty("toString", new ClrFunctionInstance(Engine, "toString", ToGuidString), true, false, true);
FastAddProperty("valueOf", new ClrFunctionInstance(Engine, "valueOf", ValueOf), true, false, true);
FastSetProperty("toString", new PropertyDescriptor(new ClrFunctionInstance(Engine, "toString", ToGuidString), true, false, true));
FastSetProperty("valueOf", new PropertyDescriptor(new ClrFunctionInstance(Engine, "valueOf", ValueOf), true, false, true));
}
}
}
18 changes: 9 additions & 9 deletions Jint.Tests/Runtime/InteropTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2689,8 +2689,8 @@ static void Test(string message, object value)
Console.WriteLine(message);
}

engine.Realm.GlobalObject.FastAddProperty("global", engine.Realm.GlobalObject, true, true, true);
engine.Realm.GlobalObject.FastAddProperty("test", new DelegateWrapper(engine, (Action<string, object>) Test), true, true, true);
engine.Realm.GlobalObject.FastSetDataProperty("global", engine.Realm.GlobalObject);
engine.Realm.GlobalObject.FastSetDataProperty("test", new DelegateWrapper(engine, (Action<string, object>) Test));

{
var ex = Assert.Throws<JavaScriptException>(() => engine.Realm.GlobalObject.ToObject());
Expand Down Expand Up @@ -3237,32 +3237,32 @@ public void ShouldBeAbleToDeleteDictionaryEntries()
};

engine.SetValue("data", dictionary);

Assert.True(engine.Evaluate("Object.hasOwn(data, 'a')").AsBoolean());
Assert.True(engine.Evaluate("data['a'] === 1").AsBoolean());

engine.Evaluate("data['a'] = 42");
Assert.True(engine.Evaluate("data['a'] === 42").AsBoolean());

Assert.Equal(42, dictionary["a"]);

engine.Execute("delete data['a'];");

Assert.False(engine.Evaluate("Object.hasOwn(data, 'a')").AsBoolean());
Assert.False(engine.Evaluate("data['a'] === 42").AsBoolean());

Assert.False(dictionary.ContainsKey("a"));

var engineNoWrite = new Engine(options => options.Strict().AllowClrWrite(false));

dictionary = new Dictionary<string, int>
{
{ "a", 1 },
{ "b", 2 }
};

engineNoWrite.SetValue("data", dictionary);

var ex1 = Assert.Throws<JavaScriptException>(() => engineNoWrite.Evaluate("data['a'] = 42"));
Assert.Equal("Cannot assign to read only property 'a' of System.Collections.Generic.Dictionary`2[System.String,System.Int32]", ex1.Message);

Expand Down
4 changes: 2 additions & 2 deletions Jint.Tests/Runtime/ObjectInstanceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ public void RemovingFirstPropertyFromObjectInstancePropertiesBucketAndEnumeratin
{
var engine = new Engine();
var instance = new ObjectInstance(engine);
instance.FastAddProperty("bare", JsValue.Null, true, true, true);
instance.FastAddProperty("scope", JsValue.Null, true, true, true);
instance.FastSetDataProperty("bare", JsValue.Null);
instance.FastSetDataProperty("scope", JsValue.Null);
instance.RemoveOwnProperty("bare");
var propertyNames = instance.GetOwnProperties().Select(x => x.Key).ToList();
Assert.Equal(new JsValue[] { "scope" }, propertyNames);
Expand Down
6 changes: 3 additions & 3 deletions Jint.Tests/Runtime/PromiseTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public void RegisterPromise_CalledWithinExecute_ResolvesCorrectly()
Action<JsValue> resolveFunc = null;

var engine = new Engine();
engine.SetValue('f', new Func<JsValue>(() =>
engine.SetValue("f", new Func<JsValue>(() =>
{
var (promise, resolve, _) = engine.RegisterPromise();
resolveFunc = resolve;
Expand All @@ -36,7 +36,7 @@ public void RegisterPromise_CalledWithinExecute_RejectsCorrectly()
Action<JsValue> rejectFunc = null;

var engine = new Engine();
engine.SetValue('f', new Func<JsValue>(() =>
engine.SetValue("f", new Func<JsValue>(() =>
{
var (promise, _, reject) = engine.RegisterPromise();
rejectFunc = reject;
Expand Down Expand Up @@ -96,7 +96,7 @@ public void RegisterPromise_UsedWithRace_WorksFlawlessly()
public void Execute_ConcurrentNormalExecuteCall_WorksFine()
{
var engine = new Engine();
engine.SetValue('f', new Func<JsValue>(() => engine.RegisterPromise().Promise));
engine.SetValue("f", new Func<JsValue>(() => engine.RegisterPromise().Promise));

engine.Execute("f();");

Expand Down
16 changes: 8 additions & 8 deletions Jint/Engine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,39 +174,39 @@ internal ExecutionContext EnterExecutionContext(in ExecutionContext context)
return context;
}

public Engine SetValue(JsValue name, Delegate value)
public Engine SetValue(string name, Delegate value)
{
Realm.GlobalObject.FastAddProperty(name, new DelegateWrapper(this, value), true, false, true);
Realm.GlobalObject.FastSetProperty(name, new PropertyDescriptor(new DelegateWrapper(this, value), true, false, true));
return this;
}

public Engine SetValue(JsValue name, string value)
public Engine SetValue(string name, string value)
{
return SetValue(name, new JsString(value));
}

public Engine SetValue(JsValue name, double value)
public Engine SetValue(string name, double value)
{
return SetValue(name, JsNumber.Create(value));
}

public Engine SetValue(JsValue name, int value)
public Engine SetValue(string name, int value)
{
return SetValue(name, JsNumber.Create(value));
}

public Engine SetValue(JsValue name, bool value)
public Engine SetValue(string name, bool value)
{
return SetValue(name, value ? JsBoolean.True : JsBoolean.False);
}

public Engine SetValue(JsValue name, JsValue value)
public Engine SetValue(string name, JsValue value)
{
Realm.GlobalObject.Set(name, value);
return this;
}

public Engine SetValue(JsValue name, object obj)
public Engine SetValue(string name, object obj)
{
var value = obj is Type t
? TypeReference.CreateTypeReference(this, t)
Expand Down
47 changes: 26 additions & 21 deletions Jint/Native/Array/ArrayConstructor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -321,32 +321,38 @@ public ArrayInstance Construct(JsValue[] arguments, uint capacity)

private ArrayInstance Construct(JsValue[] arguments, ulong capacity, ObjectInstance prototypeObject)
{
var instance = ArrayCreate(capacity, prototypeObject);

if (arguments.Length == 1 && arguments.At(0).IsNumber())
{
var length = TypeConverter.ToNumber(arguments.At(0));
ValidateLength(length);
instance._length = new PropertyDescriptor(length, PropertyFlag.OnlyWritable);
}
else if (arguments.Length == 1 && arguments[0] is IObjectWrapper objectWrapper)
ArrayInstance instance;
if (arguments.Length == 1)
{
if (objectWrapper.Target is IEnumerable enumerable)
switch (arguments[0])
{
return ConstructArrayFromIEnumerable(enumerable);
case JsNumber number:
ValidateLength(number._value);
instance = ArrayCreate((ulong) number._value, prototypeObject);
break;
case IObjectWrapper objectWrapper:
instance = objectWrapper.Target is IEnumerable enumerable
? ConstructArrayFromIEnumerable(enumerable)
: ArrayCreate(0, prototypeObject);
break;
case ArrayInstance arrayInstance:
// direct copy
instance = (ArrayInstance) ConstructArrayFromArrayLike(Undefined, arrayInstance, null, this);
break;
default:
instance = ArrayCreate(capacity, prototypeObject);
instance._length!._value = JsNumber.PositiveZero;
instance.Push(arguments);
break;
}
}
else if (arguments.Length == 1 && arguments[0] is ArrayInstance arrayInstance)
{
// direct copy
return (ArrayInstance) ConstructArrayFromArrayLike(Undefined, arrayInstance, null, this);
}
else
{
instance._length = new PropertyDescriptor(0, PropertyFlag.OnlyWritable);
instance = ArrayCreate((ulong) arguments.Length, prototypeObject);
instance._length!._value = JsNumber.PositiveZero;
if (arguments.Length > 0)
{
PrototypeObject.Push(instance, arguments);
instance.Push(arguments);
}
}

Expand All @@ -364,10 +370,9 @@ internal ArrayInstance ArrayCreate(ulong length, ObjectInstance? proto = null)
}

proto ??= PrototypeObject;
var instance = new ArrayInstance(Engine, (uint) length)
var instance = new ArrayInstance(Engine, (uint) length, (uint) length)
{
_prototype = proto,
_length = new PropertyDescriptor(length, PropertyFlag.OnlyWritable)
_prototype = proto
};
return instance;
}
Expand Down
Loading

0 comments on commit c680830

Please sign in to comment.