// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.ClearScript.JavaScript;
using Microsoft.ClearScript.V8;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.ClearScript.Test
{
[TestClass]
[SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "Test classes use TestCleanupAttribute for deterministic teardown.")]
public class V8ModuleTest : ClearScriptTest
{
#region setup / teardown
private V8ScriptEngine engine;
[TestInitialize]
public void TestInitialize()
{
BaseTestInitialize();
engine = new V8ScriptEngine(V8ScriptEngineFlags.EnableDynamicModuleImports | V8ScriptEngineFlags.EnableDebugging);
}
[TestCleanup]
public void TestCleanup()
{
engine.Dispose();
BaseTestCleanup();
}
#endregion
#region test methods
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_File()
{
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableFileLoading;
Assert.AreEqual(123 + 456, engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import * as Arithmetic from 'JavaScript/StandardModule/Arithmetic/Arithmetic.js';
import.meta.setResult(Arithmetic.Add(123, 456));
"));
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_File_ForeignExtension()
{
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableFileLoading;
Assert.AreEqual(123 + 456, engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import * as Arithmetic from 'JavaScript/StandardModule/Arithmetic/Arithmetic.bogus';
import.meta.setResult(Arithmetic.BogusAdd(123, 456));
"));
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_File_MixedImport()
{
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableFileLoading;
Assert.AreEqual(25 * 25, engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import * as Geometry from 'JavaScript/StandardModule/Geometry/GeometryWithDynamicImport.js';
import.meta.setResult(new Geometry.Square(25).Area);
"));
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_File_Nested()
{
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableFileLoading;
Assert.AreEqual(25 * 25, engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import * as Geometry from 'JavaScript/StandardModule/Geometry/Geometry.js';
import.meta.setResult(new Geometry.Square(25).Area);
"));
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_File_Disabled()
{
TestUtil.AssertException(() => engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import * as Geometry from 'JavaScript/StandardModule/Geometry/Geometry.js';
new Geometry.Square(25).Area;
"));
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_File_PathlessImport()
{
engine.DocumentSettings.SearchPath = string.Join(";",
Path.Combine(Directory.GetCurrentDirectory(), "JavaScript", "StandardModule", "Arithmetic"),
Path.Combine(Directory.GetCurrentDirectory(), "JavaScript", "StandardModule", "Geometry")
);
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableFileLoading;
Assert.AreEqual(123 + 456, engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import * as Arithmetic from 'Arithmetic.js';
import.meta.setResult(Arithmetic.Add(123, 456));
"));
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_File_PathlessImport_MixedImport()
{
engine.DocumentSettings.SearchPath = string.Join(";",
Path.Combine(Directory.GetCurrentDirectory(), "JavaScript", "StandardModule", "Arithmetic"),
Path.Combine(Directory.GetCurrentDirectory(), "JavaScript", "StandardModule", "Geometry")
);
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableFileLoading;
Assert.AreEqual(25 * 25, engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import * as Geometry from 'GeometryWithDynamicImport.js';
import.meta.setResult(new Geometry.Square(25).Area);
"));
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_File_PathlessImport_Nested()
{
engine.DocumentSettings.SearchPath = string.Join(";",
Path.Combine(Directory.GetCurrentDirectory(), "JavaScript", "StandardModule", "Arithmetic"),
Path.Combine(Directory.GetCurrentDirectory(), "JavaScript", "StandardModule", "Geometry")
);
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableFileLoading;
Assert.AreEqual(25 * 25, engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import * as Geometry from 'GeometryWithPathlessImport.js';
import.meta.setResult(new Geometry.Square(25).Area);
"));
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_File_PathlessImport_Disabled()
{
engine.DocumentSettings.SearchPath = string.Join(";",
Path.Combine(Directory.GetCurrentDirectory(), "JavaScript", "StandardModule", "Arithmetic"),
Path.Combine(Directory.GetCurrentDirectory(), "JavaScript", "StandardModule", "Geometry")
);
TestUtil.AssertException(() => engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import * as Geometry from 'Geometry.js';
new Geometry.Square(25).Area;
"));
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_File_DynamicImport()
{
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableFileLoading;
engine.Evaluate(@"
(async function () {
try {
let Arithmetic = await import('JavaScript/StandardModule/Arithmetic/Arithmetic.js');
result = Arithmetic.Add(123, 456);
}
catch (exception) {
caughtException = exception;
}
})();
");
Assert.IsInstanceOfType(engine.Script.caughtException, typeof(Undefined));
Assert.AreEqual(123 + 456, engine.Script.result);
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_File_DynamicImport_MixedImport()
{
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableFileLoading;
engine.Evaluate(@"
(async function () {
try {
let Geometry = await import('JavaScript/StandardModule/Geometry/Geometry.js');
result = new Geometry.Square(25).Area;
}
catch (exception) {
caughtException = exception;
}
})();
");
Assert.IsInstanceOfType(engine.Script.caughtException, typeof(Undefined));
Assert.AreEqual(25 * 25, engine.Script.result);
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_File_DynamicImport_Nested()
{
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableFileLoading;
engine.Evaluate(@"
(async function () {
try {
let Geometry = await import('JavaScript/StandardModule/Geometry/GeometryWithDynamicImport.js');
result = new Geometry.Square(25).Area;
}
catch (exception) {
caughtException = exception;
}
})();
");
Assert.IsInstanceOfType(engine.Script.caughtException, typeof(Undefined));
Assert.AreEqual(25 * 25, engine.Script.result);
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_File_DynamicImport_Disabled()
{
engine.Evaluate(@"
(async function () {
try {
let Geometry = await import('JavaScript/StandardModule/Geometry/GeometryWithDynamicImport.js');
result = new Geometry.Square(25).Area;
}
catch (exception) {
caughtException = exception;
}
})();
");
Assert.IsNotInstanceOfType(engine.Script.caughtException, typeof(Undefined));
TestUtil.AssertException(() => engine.Execute("throw caughtException"));
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_File_FileNameExtensions()
{
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableFileLoading;
Assert.AreEqual(25 * 25, engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import * as Geometry from 'JavaScript/StandardModule/Geometry/Geometry';
import.meta.setResult(new Geometry.Square(25).Area);
"));
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_Web()
{
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableWebLoading;
Assert.AreEqual(123 + 456, engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import * as Arithmetic from 'https://raw.githubusercontent.com/ClearFoundry/ClearScript/master/ClearScriptTest/JavaScript/StandardModule/Arithmetic/Arithmetic.js';
import.meta.setResult(Arithmetic.Add(123, 456));
"));
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_Web_MixedImport()
{
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableWebLoading;
Assert.AreEqual(25 * 25, engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import * as Geometry from 'https://raw.githubusercontent.com/ClearFoundry/ClearScript/master/ClearScriptTest/JavaScript/StandardModule/Geometry/GeometryWithDynamicImport.js';
import.meta.setResult(new Geometry.Square(25).Area);
"));
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_Web_Nested()
{
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableWebLoading;
Assert.AreEqual(25 * 25, engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import * as Geometry from 'https://raw.githubusercontent.com/ClearFoundry/ClearScript/master/ClearScriptTest/JavaScript/StandardModule/Geometry/Geometry.js';
import.meta.setResult(new Geometry.Square(25).Area);
"));
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_Web_Disabled()
{
TestUtil.AssertException(() => engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import * as Geometry from 'https://raw.githubusercontent.com/ClearFoundry/ClearScript/master/ClearScriptTest/JavaScript/StandardModule/Geometry/Geometry.js';
new Geometry.Square(25).Area;
"));
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_Web_PathlessImport()
{
engine.DocumentSettings.SearchPath = string.Join(";",
"https://raw.githubusercontent.com/ClearFoundry/ClearScript/master/ClearScriptTest/JavaScript/StandardModule/Arithmetic",
"https://raw.githubusercontent.com/ClearFoundry/ClearScript/master/ClearScriptTest/JavaScript/StandardModule/Geometry"
);
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableWebLoading;
Assert.AreEqual(123 + 456, engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import * as Arithmetic from 'Arithmetic.js';
import.meta.setResult(Arithmetic.Add(123, 456));
"));
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_Web_PathlessImport_MixedImport()
{
engine.DocumentSettings.SearchPath = string.Join(";",
"https://raw.githubusercontent.com/ClearFoundry/ClearScript/master/ClearScriptTest/JavaScript/StandardModule/Arithmetic",
"https://raw.githubusercontent.com/ClearFoundry/ClearScript/master/ClearScriptTest/JavaScript/StandardModule/Geometry"
);
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableWebLoading;
Assert.AreEqual(25 * 25, engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import * as Geometry from 'GeometryWithDynamicImport.js';
import.meta.setResult(new Geometry.Square(25).Area);
"));
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_Web_PathlessImport_Nested()
{
engine.DocumentSettings.SearchPath = string.Join(";",
"https://raw.githubusercontent.com/ClearFoundry/ClearScript/master/ClearScriptTest/JavaScript/StandardModule/Arithmetic",
"https://raw.githubusercontent.com/ClearFoundry/ClearScript/master/ClearScriptTest/JavaScript/StandardModule/Geometry"
);
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableWebLoading;
Assert.AreEqual(25 * 25, engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import * as Geometry from 'GeometryWithPathlessImport.js';
import.meta.setResult(new Geometry.Square(25).Area);
"));
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_Web_PathlessImport_Disabled()
{
engine.DocumentSettings.SearchPath = string.Join(";",
"https://raw.githubusercontent.com/ClearFoundry/ClearScript/master/ClearScriptTest/JavaScript/StandardModule/Arithmetic",
"https://raw.githubusercontent.com/ClearFoundry/ClearScript/master/ClearScriptTest/JavaScript/StandardModule/Geometry"
);
TestUtil.AssertException(() => engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import * as Geometry from 'Geometry.js';
new Geometry.Square(25).Area;
"));
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_Web_DynamicImport()
{
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableWebLoading;
engine.Evaluate(@"
(async function () {
try {
let Arithmetic = await import('https://raw.githubusercontent.com/ClearFoundry/ClearScript/master/ClearScriptTest/JavaScript/StandardModule/Arithmetic/Arithmetic.js');
result = Arithmetic.Add(123, 456);
}
catch (exception) {
caughtException = exception;
}
})();
");
Assert.IsInstanceOfType(engine.Script.caughtException, typeof(Undefined));
Assert.AreEqual(123 + 456, engine.Script.result);
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_Web_DynamicImport_MixedImport()
{
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableWebLoading;
engine.Evaluate(@"
(async function () {
try {
let Geometry = await import('https://raw.githubusercontent.com/ClearFoundry/ClearScript/master/ClearScriptTest/JavaScript/StandardModule/Geometry/Geometry.js');
result = new Geometry.Square(25).Area;
}
catch (exception) {
caughtException = exception;
}
})();
");
Assert.IsInstanceOfType(engine.Script.caughtException, typeof(Undefined));
Assert.AreEqual(25 * 25, engine.Script.result);
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_Web_DynamicImport_Nested()
{
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableWebLoading;
engine.Evaluate(@"
(async function () {
try {
let Geometry = await import('https://raw.githubusercontent.com/ClearFoundry/ClearScript/master/ClearScriptTest/JavaScript/StandardModule/Geometry/GeometryWithDynamicImport.js');
result = new Geometry.Square(25).Area;
}
catch (exception) {
caughtException = exception;
}
})();
");
Assert.IsInstanceOfType(engine.Script.caughtException, typeof(Undefined));
Assert.AreEqual(25 * 25, engine.Script.result);
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_Web_DynamicImport_Disabled()
{
engine.Evaluate(@"
(async function () {
try {
let Geometry = await import('https://raw.githubusercontent.com/ClearFoundry/ClearScript/master/ClearScriptTest/JavaScript/StandardModule/Geometry/GeometryWithDynamicImport.js');
result = new Geometry.Square(25).Area;
}
catch (exception) {
caughtException = exception;
}
})();
");
Assert.IsNotInstanceOfType(engine.Script.caughtException, typeof(Undefined));
TestUtil.AssertException(() => engine.Execute("throw caughtException"));
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_Web_FileNameExtensions()
{
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableWebLoading;
Assert.AreEqual(25 * 25, engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import * as Geometry from 'https://raw.githubusercontent.com/ClearFoundry/ClearScript/master/ClearScriptTest/JavaScript/StandardModule/Geometry/Geometry';
import.meta.setResult(new Geometry.Square(25).Area);
"));
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_Compilation()
{
var module = engine.Compile(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import * as Geometry from 'JavaScript/StandardModule/Geometry/Geometry.js';
import.meta.setResult(new Geometry.Square(25).Area);
");
using (module)
{
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableFileLoading;
Assert.AreEqual(25 * 25, engine.Evaluate(module));
// re-evaluating a module is a no-op
Assert.IsInstanceOfType(engine.Evaluate(module), typeof(Undefined));
}
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_Compilation_CodeCache()
{
var code = @"
import * as Geometry from 'JavaScript/StandardModule/Geometry/Geometry.js';
import.meta.setResult(new Geometry.Square(25).Area);
";
byte[] cacheBytes;
using (engine.Compile(new DocumentInfo { Category = ModuleCategory.Standard }, code, V8CacheKind.Code, out cacheBytes))
{
}
Assert.IsNotNull(cacheBytes);
Assert.IsTrue(cacheBytes.Length > 350); // typical size is ~700
engine.Dispose();
engine = new V8ScriptEngine(V8ScriptEngineFlags.EnableDynamicModuleImports | V8ScriptEngineFlags.EnableDebugging);
var module = engine.Compile(new DocumentInfo { Category = ModuleCategory.Standard }, code, V8CacheKind.Code, cacheBytes, out var cacheAccepted);
Assert.IsTrue(cacheAccepted);
using (module)
{
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableFileLoading;
Assert.AreEqual(25 * 25, engine.Evaluate(module));
// re-evaluating a module is a no-op
Assert.IsInstanceOfType(engine.Evaluate(module), typeof(Undefined));
}
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_Compilation_Runtime()
{
engine.Dispose();
using (var runtime = new V8Runtime(V8RuntimeFlags.EnableDebugging))
{
var module = runtime.Compile(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import * as Geometry from 'JavaScript/StandardModule/Geometry/Geometry.js';
import.meta.setResult(new Geometry.Square(25).Area);
");
using (module)
{
engine = runtime.CreateScriptEngine();
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableFileLoading;
Assert.AreEqual(25 * 25, engine.Evaluate(module));
// re-evaluating a module is a no-op
Assert.IsInstanceOfType(engine.Evaluate(module), typeof(Undefined));
}
}
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_Compilation_Runtime_CodeCache()
{
var code = @"
import * as Geometry from 'JavaScript/StandardModule/Geometry/Geometry.js';
import.meta.setResult(new Geometry.Square(25).Area);
";
byte[] cacheBytes;
using (engine.Compile(new DocumentInfo { Category = ModuleCategory.Standard }, code, V8CacheKind.Code, out cacheBytes))
{
}
Assert.IsNotNull(cacheBytes);
Assert.IsTrue(cacheBytes.Length > 350); // typical size is ~700
engine.Dispose();
using (var runtime = new V8Runtime(V8RuntimeFlags.EnableDebugging))
{
engine = runtime.CreateScriptEngine();
var module = runtime.Compile(new DocumentInfo { Category = ModuleCategory.Standard }, code, V8CacheKind.Code, cacheBytes, out var cacheAccepted);
Assert.IsTrue(cacheAccepted);
using (module)
{
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableFileLoading;
Assert.AreEqual(25 * 25, engine.Evaluate(module));
// re-evaluating a module is a no-op
Assert.IsInstanceOfType(engine.Evaluate(module), typeof(Undefined));
}
}
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_SideEffects()
{
using (var runtime = new V8Runtime())
{
runtime.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableAllLoading;
var module = runtime.CompileDocument("JavaScript/StandardModule/ModuleWithSideEffects.js", ModuleCategory.Standard);
using (var testEngine = runtime.CreateScriptEngine())
{
testEngine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableAllLoading;
testEngine.Execute("foo = {}");
Assert.AreEqual(625, testEngine.Evaluate(module));
Assert.AreEqual(625, testEngine.Evaluate("foo.bar"));
// re-evaluating a module is a no-op
Assert.IsInstanceOfType(testEngine.Evaluate(module), typeof(Undefined));
}
using (var testEngine = runtime.CreateScriptEngine())
{
testEngine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableAllLoading;
testEngine.Execute("foo = {}");
Assert.AreEqual(625, testEngine.Evaluate(module));
Assert.AreEqual(625, testEngine.Evaluate("foo.bar"));
// re-evaluating a module is a no-op
Assert.IsInstanceOfType(testEngine.Evaluate(module), typeof(Undefined));
}
}
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_Caching()
{
Assert.AreEqual(0UL, engine.GetRuntimeStatistics().ModuleCount);
var info = new DocumentInfo { Category = ModuleCategory.Standard };
Assert.AreEqual(Math.PI, engine.Evaluate(info, "import.meta.setResult(Math.PI)"));
Assert.AreEqual(Math.PI, engine.Evaluate(info, "import.meta.setResult(Math.PI)"));
Assert.AreEqual(2UL, engine.GetRuntimeStatistics().ModuleCount);
info = new DocumentInfo("Test") { Category = ModuleCategory.Standard };
Assert.AreEqual(Math.E, engine.Evaluate(info, "import.meta.setResult(Math.E)"));
Assert.IsInstanceOfType(engine.Evaluate(info, "import.meta.setResult(Math.E)"), typeof(Undefined));
Assert.AreEqual(3UL, engine.GetRuntimeStatistics().ModuleCount);
Assert.AreEqual(Math.PI, engine.Evaluate(info, "import.meta.setResult(Math.PI)"));
Assert.IsInstanceOfType(engine.Evaluate(info, "import.meta.setResult(Math.PI)"), typeof(Undefined));
Assert.AreEqual(4UL, engine.GetRuntimeStatistics().ModuleCount);
using (var runtime = new V8Runtime())
{
for (var i = 0; i < 10; i++)
{
using (var testEngine = runtime.CreateScriptEngine())
{
Assert.AreEqual(Math.PI, testEngine.Evaluate(info, "import.meta.setResult(Math.PI)"));
Assert.AreEqual(Math.E, testEngine.Evaluate(info, "import.meta.setResult(Math.E)"));
Assert.AreEqual(2UL, testEngine.GetStatistics().ModuleCount);
}
}
Assert.AreEqual(20UL, runtime.GetStatistics().ModuleCount);
}
using (var runtime = new V8Runtime())
{
for (var i = 0; i < 300; i++)
{
using (var testEngine = runtime.CreateScriptEngine())
{
Assert.AreEqual(Math.PI, testEngine.Evaluate(info, "import.meta.setResult(Math.PI)"));
Assert.IsInstanceOfType(testEngine.Evaluate(info, "import.meta.setResult(Math.PI)"), typeof(Undefined));
}
}
Assert.AreEqual(300UL, runtime.GetStatistics().ModuleCount);
}
using (var runtime = new V8Runtime())
{
using (var testEngine = runtime.CreateScriptEngine())
{
for (var i = 0; i < 300; i++)
{
Assert.AreEqual(Math.PI + i, testEngine.Evaluate(info, "import.meta.setResult(Math.PI" + "+" + i + ")"));
}
Assert.AreEqual(300UL, testEngine.GetStatistics().ModuleCount);
Assert.AreEqual(300UL, testEngine.GetStatistics().ModuleCacheSize);
}
Assert.AreEqual(300UL, runtime.GetStatistics().ModuleCount);
}
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_Context()
{
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableFileLoading;
engine.DocumentSettings.Loader = new CustomLoader();
Assert.AreEqual(123, engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import * as Geometry from 'JavaScript/StandardModule/Geometry/Geometry.js';
import.meta.setResult(Geometry.Meta.foo)
"));
Assert.AreEqual(456.789, engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import * as Geometry from 'JavaScript/StandardModule/Geometry/Geometry.js';
import.meta.setResult(Geometry.Meta.bar)
"));
Assert.AreEqual("bogus", engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import * as Geometry from 'JavaScript/StandardModule/Geometry/Geometry.js';
import.meta.setResult(Geometry.Meta.baz)
"));
Assert.IsInstanceOfType(engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import * as Geometry from 'JavaScript/StandardModule/Geometry/Geometry.js';
import.meta.setResult(new Geometry.Meta.qux())
"), typeof(Random));
Assert.IsInstanceOfType(engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import * as Geometry from 'JavaScript/StandardModule/Geometry/Geometry.js';
import.meta.setResult(Geometry.Meta.quux)
"), typeof(Undefined));
Assert.AreEqual(Math.PI, engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import * as Arithmetic from 'JavaScript/StandardModule/Arithmetic/Arithmetic.js';
import.meta.setResult(Arithmetic.Meta.foo)
"));
Assert.IsInstanceOfType(engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import * as Arithmetic from 'JavaScript/StandardModule/Arithmetic/Arithmetic.js';
import.meta.setResult(Arithmetic.Meta.bar)
"), typeof(Undefined));
Assert.AreEqual(0, engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import * as Geometry from 'JavaScript/StandardModule/Geometry/GeometryWithDynamicImport.js';
import.meta.setResult(Object.keys(Geometry.Meta).length);
"));
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_SystemDocument()
{
engine.DocumentSettings.AddSystemDocument("test", ModuleCategory.Standard, @"
export function Add(a, b) {
return a + b;
}
");
dynamic add = engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import * as Test from 'test';
import.meta.setResult(Test.Add)
");
Assert.AreEqual(579, add(123, 456));
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_CircularReference()
{
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableFileLoading;
Assert.AreEqual(25 * 25, engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import * as Geometry from 'JavaScript/StandardModuleWithCycles/Geometry/Geometry.js';
import.meta.setResult(new Geometry.Square(25).Area);
"));
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_CircularReference_MixedImport()
{
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableFileLoading;
Assert.AreEqual(25 * 25, engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import * as Geometry from 'JavaScript/StandardModuleWithCycles/Geometry/GeometryWithDynamicImport.js';
import.meta.setResult(new Geometry.Square(25).Area);
"));
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_ImportCommonJS()
{
void OnDocumentLoad(ref DocumentInfo info)
{
var uri = info.Uri;
if (uri.IsFile)
{
var path = Path.GetDirectoryName(uri.AbsolutePath);
if (path.Split(Path.DirectorySeparatorChar).Contains("CommonJS"))
{
info.Category = ModuleCategory.CommonJS;
}
}
}
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableFileLoading | DocumentAccessFlags.AllowCategoryMismatch;
engine.DocumentSettings.LoadCallback = OnDocumentLoad;
Assert.AreEqual(12, engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import { Rectangle } from 'JavaScript/CommonJS/Geometry/Geometry';
globalThis.Rectangle1 = Rectangle;
import.meta.setResult(new Rectangle(3, 4).Area);
"));
Assert.AreEqual(30, engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import { Rectangle } from 'JavaScript/CommonJS/Geometry/Geometry';
globalThis.Rectangle2 = Rectangle;
import.meta.setResult(new Rectangle(5, 6).Area);
"));
Assert.AreEqual(56, engine.Evaluate(@"
(async function() {
let Rectangle = (await import('JavaScript/CommonJS/Geometry/Geometry')).Rectangle;
Rectangle3 = Rectangle;
return new Rectangle(7, 8).Area;
})()
").ToTask().Result);
Assert.AreEqual(engine.Script.Rectangle1, engine.Script.Rectangle2);
Assert.AreEqual(engine.Script.Rectangle2, engine.Script.Rectangle3);
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_ImportCommonJS_AsyncLoadCallback()
{
async Task OnDocumentLoadAsync(ValueRef info, Stream contents)
{
await Task.Delay(100).ConfigureAwait(false);
var uri = info.Value.Uri;
if (uri.IsFile)
{
var path = Path.GetDirectoryName(uri.AbsolutePath);
if (path.Split(Path.DirectorySeparatorChar).Contains("CommonJS"))
{
info.Value.Category = ModuleCategory.CommonJS;
}
}
}
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableFileLoading | DocumentAccessFlags.AllowCategoryMismatch | DocumentAccessFlags.UseAsyncLoadCallback;
engine.DocumentSettings.AsyncLoadCallback = OnDocumentLoadAsync;
Assert.AreEqual(12, engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import { Rectangle } from 'JavaScript/CommonJS/Geometry/Geometry';
globalThis.Rectangle1 = Rectangle;
import.meta.setResult(new Rectangle(3, 4).Area);
"));
Assert.AreEqual(30, engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import { Rectangle } from 'JavaScript/CommonJS/Geometry/Geometry';
globalThis.Rectangle2 = Rectangle;
import.meta.setResult(new Rectangle(5, 6).Area);
"));
Assert.AreEqual(56, engine.Evaluate(@"
(async function() {
let Rectangle = (await import('JavaScript/CommonJS/Geometry/Geometry')).Rectangle;
Rectangle3 = Rectangle;
return new Rectangle(7, 8).Area;
})()
").ToTask().Result);
Assert.AreEqual(engine.Script.Rectangle1, engine.Script.Rectangle2);
Assert.AreEqual(engine.Script.Rectangle2, engine.Script.Rectangle3);
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_ImportCommonJS_SystemDocument()
{
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableFileLoading | DocumentAccessFlags.AllowCategoryMismatch;
engine.DocumentSettings.AddSystemDocument("Geometry", ModuleCategory.CommonJS, @"
exports.Rectangle = class {
constructor(width, height) {
this.width = width;
this.height = height;
}
get Area() {
return this.width * this.height;
}
}
");
Assert.AreEqual(12, engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import { Rectangle } from 'Geometry';
globalThis.Rectangle1 = Rectangle;
import.meta.setResult(new Rectangle(3, 4).Area);
"));
Assert.AreEqual(30, engine.Evaluate(new DocumentInfo { Category = ModuleCategory.Standard }, @"
import { Rectangle } from 'Geometry';
globalThis.Rectangle2 = Rectangle;
import.meta.setResult(new Rectangle(5, 6).Area);
"));
Assert.AreEqual(56, engine.Evaluate(@"
(async function() {
let Rectangle = (await import('Geometry')).Rectangle;
Rectangle3 = Rectangle;
return new Rectangle(7, 8).Area;
})()
").ToTask().Result);
Assert.AreEqual(engine.Script.Rectangle1, engine.Script.Rectangle2);
Assert.AreEqual(engine.Script.Rectangle2, engine.Script.Rectangle3);
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_Json_Object()
{
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableFileLoading | DocumentAccessFlags.AllowCategoryMismatch;
engine.DocumentSettings.LoadCallback = (ref DocumentInfo info) =>
{
if (Path.GetExtension(info.Uri.AbsolutePath).Equals(".json", StringComparison.OrdinalIgnoreCase))
{
info.Category = DocumentCategory.Json;
}
};
engine.Execute(new DocumentInfo { Category = ModuleCategory.Standard }, "import result from 'JavaScript/Object.json'; globalThis.result = result;");
var result = (ScriptObject)engine.Global.GetProperty("result");
Assert.AreEqual(2, result.PropertyNames.Count());
Assert.AreEqual(123, result.GetProperty("foo"));
Assert.AreEqual("baz", result.GetProperty("bar"));
engine.DocumentSettings.AddSystemDocument("ObjectWithFunction.json", DocumentCategory.Json, "{ \"foo\": 123, \"bar\": \"baz\", \"qux\": function(){} }");
TestUtil.AssertException(() => engine.Execute(new DocumentInfo { Category = ModuleCategory.Standard }, "import result from 'ObjectWithFunction.json'; globalThis.result = result;"));
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_Json_Array()
{
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableFileLoading | DocumentAccessFlags.AllowCategoryMismatch;
engine.DocumentSettings.LoadCallback = (ref DocumentInfo info) =>
{
if (Path.GetExtension(info.Uri.AbsolutePath).Equals(".json", StringComparison.OrdinalIgnoreCase))
{
info.Category = DocumentCategory.Json;
}
};
engine.Execute(new DocumentInfo { Category = ModuleCategory.Standard }, "import result from 'JavaScript/Array.json'; globalThis.result = result;");
var result = (ScriptObject)engine.Global.GetProperty("result");
Assert.AreEqual(4, result.PropertyIndices.Count());
Assert.AreEqual(123, result.GetProperty(0));
Assert.AreEqual("foo", result.GetProperty(1));
Assert.AreEqual(4.56, result.GetProperty(2));
Assert.AreEqual("bar", result.GetProperty(3));
engine.DocumentSettings.AddSystemDocument("ArrayWithFunction.json", DocumentCategory.Json, "[ 123, \"foo\", 4.56, \"bar\", function(){} ]");
TestUtil.AssertException(() => engine.Execute(new DocumentInfo { Category = ModuleCategory.Standard }, "import result from 'ArrayWithFunction.json'; globalThis.result = result;"));
}
[TestMethod, TestCategory("V8Module")]
public void V8Module_Standard_Json_Malformed()
{
engine.DocumentSettings.AccessFlags = DocumentAccessFlags.EnableFileLoading | DocumentAccessFlags.AllowCategoryMismatch;
engine.DocumentSettings.LoadCallback = (ref DocumentInfo info) =>
{
if (Path.GetExtension(info.Uri.AbsolutePath).Equals(".json", StringComparison.OrdinalIgnoreCase))
{
info.Category = DocumentCategory.Json;
}
};
// ReSharper disable once AccessToDisposedClosure
TestUtil.AssertException(() => engine.Execute(new DocumentInfo { Category = ModuleCategory.Standard }, "import result from 'JavaScript/Malformed.json'; globalThis.result = result;"));
}
[TestMethod, TestCategory("V8Module")]
public async Task V8Module_Standard_TopLevelAwait_Result()
{
engine.Dispose();
engine = new V8ScriptEngine(V8ScriptEngineFlags.EnableDebugging | V8ScriptEngineFlags.EnableTaskPromiseConversion);
engine.AddHostType(typeof(Task));
Assert.AreEqual(123, await (Task