[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/ClearFoundry/ClearScript/master/ClearScriptTest/BugFixTest.cs [Back]  [Original]

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using Microsoft.ClearScript.Util;
using Microsoft.ClearScript.V8;
using Microsoft.CSharp.RuntimeBinder;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
using Microsoft.ClearScript.JavaScript;

namespace Microsoft.ClearScript.Test
{
    // ReSharper disable once PartialTypeWithSinglePart

    [TestClass]
    [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "Test classes use TestCleanupAttribute for deterministic teardown.")]
    [SuppressMessage("ReSharper", "StringLiteralTypo", Justification = "Typos in test code are acceptable.")]
    [SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Typos in test code are acceptable.")]
    public partial class BugFixTest : ClearScriptTest
    {
        #region setup / teardown

        private ScriptEngine engine;

        [TestInitialize]
        public void TestInitialize()
        {
            BaseTestInitialize();
            engine = new V8ScriptEngine(V8ScriptEngineFlags.EnableDebugging);
        }

        [TestCleanup]
        public void TestCleanup()
        {
            engine.Dispose();
            BaseTestCleanup();
        }

        #endregion

        #region test methods

        // ReSharper disable InconsistentNaming

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_NullArgBinding()
        {
            var value = 123.456 as IConvertible;
            engine.AddRestrictedHostObject("value", value);
            Assert.AreEqual(value.ToString(null), engine.Evaluate("value.ToString(null)"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_NullArgBinding_Ambiguous()
        {
            engine.AddHostObject("lib", new HostTypeCollection("mscorlib"));
            TestUtil.AssertException(() => engine.Execute("lib.System.Console.WriteLine(null)"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_DelegateConstructionSyntax()
        {
            engine.AddHostObject("lib", HostItemFlags.GlobalMembers, new HostTypeCollection("mscorlib"));
            var func = (Func)engine.Evaluate("new (System.Func(System.Int32, System.Int32))(function (x) {return x * x; })");
            Assert.AreEqual(25, func(5));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_DelegateConstructionSyntax_DoubleWrap()
        {
            engine.AddHostObject("lib", HostItemFlags.GlobalMembers, new HostTypeCollection("mscorlib"));
            engine.Execute("cb = new (System.Func(System.Int32, System.Int32))(function (x) {return x * x; })");
            var func = (Func)engine.Evaluate("new (System.Func(System.Int32, System.Int32))(cb)");
            Assert.AreEqual(25, func(5));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_V8ScriptInterruptCrash()
        {
            // run the test several times to verify post-interrupt engine functionality

            for (var iteration = 0; iteration < 16; iteration++)
            {
                var context = new PropertyBag();
                engine.AddHostObject("context", context);

                using (var startEvent = new ManualResetEventSlim(false))
                {
                    context["startEvent"] = startEvent;

                    var interrupted = false;
                    var thread = new Thread(() =>
                    {
                        try
                        {
                            engine.Execute("while (true) { context.startEvent.Set(); }");
                        }
                        catch (ScriptInterruptedException)
                        {
                            interrupted = true;
                        }
                    });

                    thread.Start();
                    startEvent.Wait();
                    engine.Interrupt();
                    thread.Join();
                    Assert.IsTrue(interrupted);
                }
            }
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_InheritedInterfaceMethod()
        {
            var list = new List { 123 };
            using (var enumerator = list.AsEnumerable().GetEnumerator())
            {
                engine.AddRestrictedHostObject("enumerator", enumerator);
                Assert.IsTrue((bool)engine.Evaluate("enumerator.MoveNext()"));
                Assert.AreEqual(123, engine.Evaluate("enumerator.Current"));
                Assert.IsFalse((bool)engine.Evaluate("enumerator.MoveNext()"));
                engine.Execute("enumerator.Dispose()");
            }
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_EnumEquality()
        {
            engine.AddHostObject("host", new HostFunctions());
            engine.AddHostType("DayOfWeek", typeof(DayOfWeek));
            engine.AddHostType("BindingFlags", typeof(BindingFlags));
            Assert.IsTrue((bool)engine.Evaluate("DayOfWeek.Wednesday == DayOfWeek.Wednesday"));
            Assert.IsTrue((bool)engine.Evaluate("host.flags(BindingFlags.Public, BindingFlags.Instance) == host.flags(BindingFlags.Public, BindingFlags.Instance)"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_FloatParameterBinding()
        {
            engine.AddHostType("Convert", typeof(Convert));
            engine.Script.list = new List();
            const float floatPi = (float)Math.PI;
            engine.Execute("list.Add(Convert.ToSingle(Math.PI))");
            Assert.AreEqual(floatPi, engine.Script.list[0]);
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_UInt32RoundTrip()
        {
            engine.AddHostType("UInt32", typeof(uint));
            Assert.AreEqual((long)uint.MaxValue, engine.Evaluate("UInt32.MaxValue"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_AutoInt64FromDouble()
        {
            engine.AddHostType("Int32", typeof(int));
            Assert.AreEqual((long)int.MaxValue * 123 + 1, engine.Evaluate("Int32.MaxValue * 123 + 1"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_V8CompiledScriptResult()
        {
            engine.Script.host = new HostFunctions();
            using (var script = ((V8ScriptEngine)engine).Compile("host"))
            {
                Assert.IsInstanceOfType(((V8ScriptEngine)engine).Evaluate(script), typeof(HostFunctions));
            }
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_GenericDelegateConstructorWithArgument()
        {
            engine.AddHostType("Func", "System.Func");
            engine.AddHostType("StringT", typeof(string));
            engine.AddHostType("IntT", typeof(int));
            var func = (Func)engine.Evaluate("new Func(StringT, IntT, function (s) { return s.length; })");
            const string testString = "floccinaucinihilipilification";
            Assert.AreEqual(testString.Length, func(testString));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_HostMethodAsArgument()
        {
            var testObject = new TestObject();
            engine.Script.testObject = testObject;
            engine.Script.expando = new ExpandoObject();
            engine.AddHostType("DateTime", typeof(DateTime));
            engine.Execute("expando.method = testObject.Method");
            Assert.AreEqual(testObject.Method("foo", 123), engine.Evaluate("expando.method('foo', 123)"));
            Assert.AreEqual(testObject.Method(456), engine.Evaluate("expando.method(DateTime, 456)"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_HostIndexedPropertyAsArgument()
        {
            var testObject = new TestObject();
            engine.Script.testObject = testObject;
            engine.Script.expando = new ExpandoObject();
            engine.Execute("expando.property = testObject.Item");
            Assert.AreEqual("foo", engine.Evaluate("expando.property.set(123, 'foo')"));
            Assert.AreEqual("foo", engine.Evaluate("expando.property.get(123)"));
            Assert.AreEqual("foo", engine.Evaluate("expando.property(123)"));
            Assert.AreEqual(456, engine.Evaluate("expando.property.set('bar', 456)"));
            Assert.AreEqual(456, engine.Evaluate("expando.property.get('bar')"));
            Assert.AreEqual(456, engine.Evaluate("expando.property('bar')"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_Nullable_Field()
        {
            engine.Script.test = new NullableTest();
            Assert.IsNull(engine.Evaluate("test.Field"));
            Assert.IsTrue((bool)engine.Evaluate("test.Field === null"));
            engine.Execute("test.Field = 123");
            Assert.AreEqual(123, engine.Evaluate("test.Field"));
            Assert.IsTrue((bool)engine.Evaluate("test.Field === 123"));
            engine.Execute("test.Field = null");
            Assert.IsNull(engine.Evaluate("test.Field"));
            Assert.IsTrue((bool)engine.Evaluate("test.Field === null"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_Nullable_Property()
        {
            engine.Script.test = new NullableTest();
            Assert.IsNull(engine.Evaluate("test.Property"));
            Assert.IsTrue((bool)engine.Evaluate("test.Property === null"));
            engine.Execute("test.Property = 123");
            Assert.AreEqual(123, engine.Evaluate("test.Property"));
            Assert.IsTrue((bool)engine.Evaluate("test.Property === 123"));
            engine.Execute("test.Property = null");
            Assert.IsNull(engine.Evaluate("test.Property"));
            Assert.IsTrue((bool)engine.Evaluate("test.Property === null"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_Nullable_ArgBinding()
        {
            var test = new NullableTest();
            engine.Script.test = test;
            Assert.IsNull(engine.Evaluate("test.Method(null)"));
            Assert.AreEqual(test.Method(5), engine.Evaluate("test.Method(5)"));
            Assert.AreEqual(test.Method(5.1), engine.Evaluate("test.Method(5.1)"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_GlobalObjectCrash()
        {
            engine.AddHostObject("random", HostItemFlags.GlobalMembers, new Random());
            Assert.AreEqual("[object Object]", engine.ExecuteCommand("this"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_VBDynamicArgMarshaling_Numeric()
        {
            var result = TestUtil.InvokeVBTestFunction(@"
                Using engine As New V8ScriptEngine
                    engine.Execute(""data = [5, 4, 'qux', 2, 1]; function getElement(i1, i2, i3) { return data[i1 + i2 - i3]; }"")
                    TestFunction = engine.Script.getElement(CShort(1), CLng(99), CStr(98))
                End Using
            ");

            Assert.AreEqual("qux", result);
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_VBDynamicArgMarshaling_String()
        {
            var result = TestUtil.InvokeVBTestFunction(@"
                Using engine As New V8ScriptEngine
                    engine.Execute(""data = { foo26: 123, bar97: 456.789 }; function getElement(i1, i2) { return data[i1 + i2]; }"")
                    TestFunction = engine.Script.getElement(""bar"", CLng(97))
                End Using
            ");

            Assert.AreEqual(456.789, result);
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_AmbiguousAttribute()
        {
            engine.Script.foo = new AmbiguousAttributeTest();
            engine.Execute("foo.Foo()");
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_CoreBindCache()
        {
            HostItem.ClearCoreBindCache();

            engine.Dispose();
            for (var i = 0; i < 10; i++)
            {
                using (engine = new V8ScriptEngine(V8ScriptEngineFlags.EnableDebugging))
                {
                    engine.Script.host = new HostFunctions();
                    Assert.AreEqual(' ', engine.Evaluate("host.toChar(32)"));
                    Assert.AreEqual('A', engine.Evaluate("host.toChar(65)"));
                    Assert.AreEqual(0.0F, engine.Evaluate("host.toSingle(0)"));
                    Assert.AreEqual(1.0F, engine.Evaluate("host.toSingle(1)"));
                    Assert.AreEqual(123.0F, engine.Evaluate("host.toSingle(123)"));
                }
            }

            Assert.AreEqual(3L, HostItem.GetCoreBindCount());
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_StringWithNullChar()
        {
            const string value = "abc\0def";
            const string value2 = "ghi\0jkl";
            dynamic func = engine.Evaluate("(function (x) { return x.length; }).valueOf()");
            Assert.AreEqual(value.Length, func(value));
            Assert.AreEqual(value, engine.Evaluate(@"'abc\0def'"));
            dynamic obj = engine.Evaluate(@"({ 'abc\0def': 'ghi\0jkl' })");
            Assert.IsTrue(((DynamicObject)obj).GetDynamicMemberNames().Contains(value));
            Assert.AreEqual(value2, obj[value]);
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_V8ScriptItemWeakBinding()
        {
            // This test verifies that V8 script items no longer prevent their isolates from being
            // destroyed. Previously it exhausted address space and crashed in 32-bit mode.

            for (var i = 0; i < 128; i++)
            {
                using (var tempEngine = new V8ScriptEngine(V8ScriptEngineFlags.EnableDebugging))
                {
                    tempEngine.Evaluate("(function () {}).valueOf()");
                }
            }
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_V8ScriptItemDispose()
        {
            dynamic item1;
            using (var tempEngine = new V8ScriptEngine(V8ScriptEngineFlags.EnableDebugging))
            {
                item1 = tempEngine.Evaluate("(function () { return 123; }).valueOf()");
                Assert.AreEqual(123, item1());

                dynamic item2 = tempEngine.Evaluate("(function () { return 456; }).valueOf()");
                using (item2 as IDisposable)
                {
                    Assert.AreEqual(456, item2());
                }
            }

            using (item1 as IDisposable)
            {
            }
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_V8CompiledScriptWeakBinding()
        {
            // This test verifies that V8 compiled scripts no longer prevent their isolates from
            // being destroyed. Previously it exhausted address space and crashed in 32-bit mode.

            for (var i = 0; i < 128; i++)
            {
                using (var tempEngine = new V8ScriptEngine(V8ScriptEngineFlags.EnableDebugging))
                {
                    tempEngine.Compile("(function () {}).valueOf()");
                }
            }
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_V8CompiledScriptDispose()
        {
            V8Script script1;
            using (var tempEngine = new V8ScriptEngine(V8ScriptEngineFlags.EnableDebugging))
            {
                script1 = tempEngine.Compile("(function () { return 123; })()");
                Assert.AreEqual(123, tempEngine.Evaluate(script1));

                V8Script script2 = tempEngine.Compile("(function () { return 456; })()");
                using (script2)
                {
                    Assert.AreEqual(456, tempEngine.Evaluate(script2));

                }
            }

            using (script1)
            {
            }
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_V8ArrayBufferAllocator()
        {
            engine.Execute("buffer = new ArrayBuffer(12345)");
            Assert.AreEqual(12345, engine.Script.buffer.byteLength);
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_V8CachedObjectLeak()
        {
            WeakReference wr = null;

            new Action(() =>
            {
                // ReSharper disable RedundantAssignment

                object x = null;

                using (var tempEngine = new V8ScriptEngine())
                {
                    tempEngine.AddHostType("Action", typeof(Action));
                    x = tempEngine.Evaluate("action = new Action(function () {})");
                    wr = new WeakReference(tempEngine);
                }

                Assert.IsInstanceOfType(x, typeof(Action));
                TestUtil.AssertException((Action)x);

                x = null;

                // ReSharper restore RedundantAssignment
            })();

            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
            GC.WaitForPendingFinalizers();
            Assert.IsFalse(wr.IsAlive);
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_Resurrection_V8ScriptEngine()
        {
            new Action(static () =>
            {
                for (var index = 0; index < 256; index++)
                {
                    // ReSharper disable UnusedVariable

                    var wrapper = new ResurrectionTestWrapper(new V8ScriptEngine());
                    GC.Collect();

                    // ReSharper restore UnusedVariable
                }
            })();

            GC.Collect();
            GC.WaitForPendingFinalizers();
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_Resurrection_V8Runtime()
        {
            new Action(static () =>
            {
                for (var index = 0; index < 256; index++)
                {
                    // ReSharper disable UnusedVariable

                    var wrapper = new ResurrectionTestWrapper(new V8Runtime());
                    GC.Collect();

                    // ReSharper restore UnusedVariable
                }
            })();

            GC.Collect();
            GC.WaitForPendingFinalizers();
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_Resurrection_V8Script()
        {
            new Action(static () =>
            {
                for (var index = 0; index < 256; index++)
                {
                    // ReSharper disable UnusedVariable

                    var tempEngine = new V8ScriptEngine();
                    var wrapper = new ResurrectionTestWrapper(tempEngine.Compile("function foo() {}"));
                    GC.Collect();

                    // ReSharper restore UnusedVariable
                }
            })();

            GC.Collect();
            GC.WaitForPendingFinalizers();
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_Resurrection_V8ScriptItem()
        {
            new Action(static () =>
            {
                for (var index = 0; index < 256; index++)
                {
                    // ReSharper disable UnusedVariable

                    var tempEngine = new V8ScriptEngine();
                    var wrapper = new ResurrectionTestWrapper(tempEngine.Script.Math);
                    GC.Collect();

                    // ReSharper restore UnusedVariable
                }
            })();

            GC.Collect();
            GC.WaitForPendingFinalizers();
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_V8GlobalMembers_ReadOnlyPropertyCrash()
        {
            // this test is for a crash that occurred only on debug V8 builds
            engine.AddHostObject("bag", HostItemFlags.GlobalMembers, new PropertyBag());
            engine.AddHostObject("test", HostItemFlags.GlobalMembers, new { foo = 123 });
            TestUtil.AssertException(() => engine.Execute("foo = 456"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_V8GlobalMembers_ReadOnlyPropertyCrash_Index()
        {
            // this test is for a crash that occurred only on debug V8 builds
            engine.AddHostObject("bag", HostItemFlags.GlobalMembers, new PropertyBag());
            engine.AddHostObject("test", HostItemFlags.GlobalMembers, new ReadOnlyCollection(new[] { 5, 4, 3, 2, 1 }));
            TestUtil.AssertException(() => engine.Execute("this[2] = 123"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_V8GlobalMembers_NativeFunctionHiding()
        {
            engine.Execute("function toString() { return 'ABC'; }");
            engine.AddHostObject("bag", HostItemFlags.GlobalMembers, new PropertyBag());
            Assert.AreEqual("ABC", engine.Evaluate("toString()"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_CallHostObjectFunctionAsConstructor()
        {
            engine.Script.random = new Random();
            engine.AddHostType("Random", typeof(Random));
            var result = engine.Evaluate(@"
                (function () {
                    var x = new Random().NextDouble();
                    try {
                        return new random.constructor();
                    }
                    catch (ex) {
                        return new Random().NextDouble() * x;
                    }
                    return false;
                })()
            ");
            Assert.IsInstanceOfType(result, typeof(double));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_HostItemCachingForHostVariables()
        {
            var foo = new HostFunctions().newVar(new object());
            engine.Script.foo1 = foo;
            engine.Script.foo2 = foo;
            Assert.IsTrue(Convert.ToBoolean(engine.Evaluate("foo1 === foo2")));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_MetaScriptItem_GetDynamicMemberNames()
        {
            var dmop = (IDynamicMetaObjectProvider)engine.Evaluate("({ foo: 123, bar: 456, baz: 789 })");
            var dmo = dmop.GetMetaObject(Expression.Constant(dmop));
            var names = dmo.GetDynamicMemberNames().ToArray();
            Assert.IsTrue(names.Contains("foo"));
            Assert.IsTrue(names.Contains("bar"));
            Assert.IsTrue(names.Contains("baz"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_AmbiguousIndexer()
        {
            IAmbiguousIndexer indexer = new AmbiguousIndexer();
            engine.AddRestrictedHostObject("indexer", indexer);
            engine.AddHostType("DayOfWeek", typeof(DayOfWeek));

            engine.Execute("indexer.Item.set(123, 456)");
            Assert.AreEqual(456, engine.Evaluate("indexer.Item(123)"));
            Assert.IsNull(engine.Evaluate("indexer.Item(789)"));

            engine.Execute("indexer.Item.set(DayOfWeek.Thursday, DayOfWeek.Sunday)");
            Assert.AreEqual(DayOfWeek.Sunday, engine.Evaluate("indexer.Item(DayOfWeek.Thursday)"));
            Assert.IsNull(engine.Evaluate("indexer.Item(DayOfWeek.Tuesday)"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_AmbiguousIndexer_ImplicitConversion()
        {
            IAmbiguousIndexer indexer = new AmbiguousIndexer();
            engine.AddRestrictedHostObject("indexer", indexer);
            engine.AddHostType("DayOfWeek", typeof(MyDayOfWeek));

            engine.Execute("indexer.Item.set(123, 456)");
            Assert.AreEqual(456, engine.Evaluate("indexer.Item(123)"));
            Assert.IsNull(engine.Evaluate("indexer.Item(789)"));

            engine.Execute("indexer.Item.set(DayOfWeek.Thursday, DayOfWeek.Sunday)");
            Assert.AreEqual(MyDayOfWeek.Sunday, engine.Evaluate("indexer.Item(DayOfWeek.Thursday)"));
            Assert.IsNull(engine.Evaluate("indexer.Item(DayOfWeek.Tuesday)"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_AmbiguousIndexer_ImplicitConversion_Nullable()
        {
            IAmbiguousIndexer2 indexer = new AmbiguousIndexer();
            engine.AddRestrictedHostObject("indexer", indexer);
            engine.AddHostType("DayOfWeek", typeof(MyDayOfWeek));

            engine.Execute("indexer.Item.set(123, 456)");
            Assert.AreEqual(456, engine.Evaluate("indexer.Item(123)"));
            Assert.IsNull(engine.Evaluate("indexer.Item(789)"));

            engine.Execute("indexer.Item.set(DayOfWeek.Thursday, DayOfWeek.Sunday)");
            Assert.AreEqual(MyDayOfWeek.Sunday, engine.Evaluate("indexer.Item(DayOfWeek.Thursday)"));
            Assert.IsNull(engine.Evaluate("indexer.Item(DayOfWeek.Tuesday)"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_InaccessiblePropertyAccessors()
        {
            engine.Script.foo = new InaccessiblePropertyAccessors();

            TestUtil.AssertException(() => engine.Evaluate("foo.NoGetter"));
            Assert.AreEqual(123, engine.Evaluate("foo.NoGetter = 123"));

            TestUtil.AssertException(() => engine.Evaluate("foo.PrivateGetter"));
            Assert.AreEqual(456, engine.Evaluate("foo.PrivateGetter = 456"));

            Assert.AreEqual(456, engine.Evaluate("foo.NoSetter"));
            TestUtil.AssertException(() => engine.Evaluate("foo.NoSetter = 789"));

            Assert.AreEqual(456, engine.Evaluate("foo.PrivateSetter"));
            TestUtil.AssertException(() => engine.Evaluate("foo.PrivateSetter = 789"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_InaccessiblePropertyAccessors_Static()
        {
            engine.AddHostType("foo", typeof(InaccessiblePropertyAccessorsStatic));

            TestUtil.AssertException(() => engine.Evaluate("foo.NoGetter"));
            Assert.AreEqual(123, engine.Evaluate("foo.NoGetter = 123"));

            TestUtil.AssertException(() => engine.Evaluate("foo.PrivateGetter"));
            Assert.AreEqual(456, engine.Evaluate("foo.PrivateGetter = 456"));

            Assert.AreEqual(456, engine.Evaluate("foo.NoSetter"));
            TestUtil.AssertException(() => engine.Evaluate("foo.NoSetter = 789"));

            Assert.AreEqual(456, engine.Evaluate("foo.PrivateSetter"));
            TestUtil.AssertException(() => engine.Evaluate("foo.PrivateSetter = 789"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_V8RuntimeConstraintScale()
        {
            const int maxNewSpaceSize = 16;
            const int maxOldSpaceSize = 512;
            const double tolerance = .05;

            var constraints = new V8RuntimeConstraints
            {
                MaxNewSpaceSize = maxNewSpaceSize,
                MaxOldSpaceSize = maxOldSpaceSize
            };

            using (var tempEngine = new V8ScriptEngine(constraints))
            {
                Assert.AreEqual(Math.PI, tempEngine.Evaluate("Math.PI"));
                var expected = Convert.ToDouble(maxNewSpaceSize * 2 + maxOldSpaceSize);
                var actual = Convert.ToDouble(tempEngine.GetRuntimeHeapInfo().HeapSizeLimit / (1024 * 1024));
                var ratio = actual / expected;
                Assert.IsTrue((ratio >= 1 - tolerance) && (ratio = 1 - tolerance) && (ratio  {});
            engine.Script.bar = new Action(() =>
            {
                GC.Collect();
                GC.WaitForPendingFinalizers();
            });

            engine.Execute(@"
                for (var i = 0; i < 100; i++) {
                    foo({ index: i });
                }
                bar();
            ");
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_FinalizerHang_V8Script()
        {
            engine.Script.bar = new Action(() =>
            {
                GC.Collect();
                GC.WaitForPendingFinalizers();
            });

            for (var index = 0; index < 100; index++)
            {
                ((V8ScriptEngine)engine).Compile("function foo() { return " + index + "; }");
            }

            engine.Execute("bar()");
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_FinalizerHang_V8ScriptEngine()
        {
            engine.Dispose();

            using (var runtime = new V8Runtime(V8RuntimeFlags.EnableDebugging))
            {
                engine = runtime.CreateScriptEngine();
                engine.Script.bar = new Action(() =>
                {
                    GC.Collect();
                    GC.WaitForPendingFinalizers();
                });

                for (var index = 0; index < 100; index++)
                {
                    runtime.CreateScriptEngine();
                }

                engine.Execute("bar()");
            }
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_DynamicMethodArgs()
        {
            engine.Script.foo = new DynamicMethodArgTest();
            Assert.AreEqual("123 456.789 hello", engine.Evaluate("foo.RunTest(123, 456.789, 'hello')"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_NestedInterrupt()
        {
            var context = new PropertyBag();
            engine.AddHostObject("context", context);

            using (var startEvent = new ManualResetEventSlim(false))
            {
                object result = null;
                var interruptedInner = false;
                var interruptedOuter = false;

                context["startEvent"] = startEvent;
                context["foo"] = new Action(() =>
                {
                    try
                    {
                        engine.Execute("while (true) { context.startEvent.Set(); }");
                    }
                    catch (ScriptInterruptedException)
                    {
                        interruptedInner = true;
                    }
                });

                var thread = new Thread(() =>
                {
                    try
                    {
                        result = engine.Evaluate("context.foo(); 123");
                    }
                    catch (ScriptInterruptedException)
                    {
                        interruptedOuter = true;
                    }
                });

                thread.Start();
                startEvent.Wait();
                engine.Interrupt();
                thread.Join();

                Assert.IsTrue(interruptedInner);
                Assert.IsFalse(interruptedOuter);
                Assert.AreEqual(123, result);
            }
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_NumericArgConversion_Delegate()
        {
            engine.Script.host = new HostFunctions();
            engine.Script.sbyteFunc = new Func(arg => arg);
            engine.Script.nullableSByteFunc = new Func(arg => arg);
            engine.Script.floatFunc = new Func(arg => arg);
            engine.Script.nullableFloatFunc = new Func(arg => arg);
            engine.Script.doubleFunc = new Func(arg => arg);
            engine.Script.nullableDoubleFunc = new Func(arg => arg);
            engine.Script.decimalFunc = new Func(arg => arg);
            engine.Script.nullableDecimalFunc = new Func(arg => arg);

            Assert.AreEqual(123, engine.Evaluate("sbyteFunc(123)"));
            TestUtil.AssertException(() => engine.Execute("sbyteFunc(234)"));
            TestUtil.AssertException(() => engine.Execute("sbyteFunc(123.5)"));
            TestUtil.AssertException(() => engine.Execute("sbyteFunc(Math.PI)"));
            TestUtil.AssertException(() => engine.Execute("sbyteFunc(host.toDecimal(Math.PI))"));

            Assert.AreEqual(123, engine.Evaluate("nullableSByteFunc(123)"));
            TestUtil.AssertException(() => engine.Execute("nullableSByteFunc(234)"));
            TestUtil.AssertException(() => engine.Execute("nullableSByteFunc(123.5)"));
            TestUtil.AssertException(() => engine.Execute("nullableSByteFunc(Math.PI)"));
            TestUtil.AssertException(() => engine.Execute("nullableSByteFunc(host.toDecimal(Math.PI))"));
            Assert.IsNull(engine.Evaluate("nullableSByteFunc(null)"));

            Assert.AreEqual(123, engine.Evaluate("floatFunc(123)"));
            Assert.AreEqual(123.5f, engine.Evaluate("floatFunc(123.5)"));
            Assert.AreEqual((float)Math.PI, engine.Evaluate("floatFunc(Math.PI)"));
            Assert.AreEqual((float)Math.PI, engine.Evaluate("floatFunc(host.toDecimal(Math.PI))"));

            Assert.AreEqual(123, engine.Evaluate("nullableFloatFunc(123)"));
            Assert.AreEqual(123.5f, engine.Evaluate("nullableFloatFunc(123.5)"));
            Assert.AreEqual((float)Math.PI, engine.Evaluate("nullableFloatFunc(Math.PI)"));
            Assert.AreEqual((float)Math.PI, engine.Evaluate("nullableFloatFunc(host.toDecimal(Math.PI))"));
            Assert.IsNull(engine.Evaluate("nullableFloatFunc(null)"));

            Assert.AreEqual(123, engine.Evaluate("doubleFunc(123)"));
            Assert.AreEqual(123.5f, engine.Evaluate("doubleFunc(123.5)"));
            Assert.AreEqual(Math.PI, engine.Evaluate("doubleFunc(Math.PI)"));
            Assert.AreEqual((double)(decimal)Math.PI, engine.Evaluate("doubleFunc(host.toDecimal(Math.PI))"));

            Assert.AreEqual(123, engine.Evaluate("nullableDoubleFunc(123)"));
            Assert.AreEqual(123.5f, engine.Evaluate("nullableDoubleFunc(123.5)"));
            Assert.AreEqual(Math.PI, engine.Evaluate("nullableDoubleFunc(Math.PI)"));
            Assert.AreEqual((double)(decimal)Math.PI, engine.Evaluate("nullableDoubleFunc(host.toDecimal(Math.PI))"));
            Assert.IsNull(engine.Evaluate("nullableDoubleFunc(null)"));

            Assert.AreEqual(123, engine.Evaluate("decimalFunc(123)"));
            Assert.AreEqual(123.5f, engine.Evaluate("decimalFunc(123.5)"));
            Assert.AreEqual((double)(decimal)Math.PI, engine.Evaluate("decimalFunc(Math.PI)"));
            Assert.AreEqual((double)(decimal)Math.PI, engine.Evaluate("decimalFunc(host.toDecimal(Math.PI))"));

            Assert.AreEqual(123, engine.Evaluate("nullableDecimalFunc(123)"));
            Assert.AreEqual(123.5f, engine.Evaluate("nullableDecimalFunc(123.5)"));
            Assert.AreEqual((double)(decimal)Math.PI, engine.Evaluate("nullableDecimalFunc(Math.PI)"));
            Assert.AreEqual((double)(decimal)Math.PI, engine.Evaluate("nullableDecimalFunc(host.toDecimal(Math.PI))"));
            Assert.IsNull(engine.Evaluate("nullableDecimalFunc(null)"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_NumericArgConversion_Field()
        {
            engine.Script.host = new HostFunctions();
            engine.Script.test = new NumericArgConversionTest();

            Assert.AreEqual(123, engine.Evaluate("test.SByteField = 123; test.SByteField"));
            TestUtil.AssertException(() => engine.Execute("test.SByteField = 234"));
            TestUtil.AssertException(() => engine.Execute("test.SByteField = 123.5"));
            TestUtil.AssertException(() => engine.Execute("test.SByteField = Math.PI"));
            TestUtil.AssertException(() => engine.Execute("test.SByteField = host.toDecimal(Math.PI)"));

            Assert.AreEqual(123, engine.Evaluate("test.NullableSByteField = 123; test.NullableSByteField"));
            TestUtil.AssertException(() => engine.Execute("test.NullableSByteField = 234"));
            TestUtil.AssertException(() => engine.Execute("test.NullableSByteField = 123.5"));
            TestUtil.AssertException(() => engine.Execute("test.NullableSByteField = Math.PI"));
            TestUtil.AssertException(() => engine.Execute("test.NullableSByteField = host.toDecimal(Math.PI)"));
            Assert.IsNull(engine.Evaluate("test.NullableSByteField = null; test.NullableSByteField"));

            Assert.AreEqual(123, engine.Evaluate("test.FloatField = 123; test.FloatField"));
            Assert.AreEqual(123.5f, engine.Evaluate("test.FloatField = 123.5; test.FloatField"));
            Assert.AreEqual((float)Math.PI, engine.Evaluate("test.FloatField = Math.PI; test.FloatField"));
            Assert.AreEqual((float)Math.PI, engine.Evaluate("test.FloatField = host.toDecimal(Math.PI); test.FloatField"));

            Assert.AreEqual(123, engine.Evaluate("test.NullableFloatField = 123; test.NullableFloatField"));
            Assert.AreEqual(123.5f, engine.Evaluate("test.NullableFloatField = 123.5; test.NullableFloatField"));
            Assert.AreEqual((float)Math.PI, engine.Evaluate("test.NullableFloatField = Math.PI; test.NullableFloatField"));
            Assert.AreEqual((float)Math.PI, engine.Evaluate("test.NullableFloatField = host.toDecimal(Math.PI); test.NullableFloatField"));
            Assert.IsNull(engine.Evaluate("test.NullableFloatField = null; test.NullableFloatField"));

            Assert.AreEqual(123, engine.Evaluate("test.DoubleField = 123; test.DoubleField"));
            Assert.AreEqual(123.5f, engine.Evaluate("test.DoubleField = 123.5; test.DoubleField"));
            Assert.AreEqual(Math.PI, engine.Evaluate("test.DoubleField = Math.PI; test.DoubleField"));
            Assert.AreEqual((double)(decimal)Math.PI, engine.Evaluate("test.DoubleField = host.toDecimal(Math.PI); test.DoubleField"));

            Assert.AreEqual(123, engine.Evaluate("test.NullableDoubleField = 123; test.NullableDoubleField"));
            Assert.AreEqual(123.5f, engine.Evaluate("test.NullableDoubleField = 123.5; test.NullableDoubleField"));
            Assert.AreEqual(Math.PI, engine.Evaluate("test.NullableDoubleField = Math.PI; test.NullableDoubleField"));
            Assert.AreEqual((double)(decimal)Math.PI, engine.Evaluate("test.NullableDoubleField = host.toDecimal(Math.PI); test.NullableDoubleField"));
            Assert.IsNull(engine.Evaluate("test.NullableDoubleField = null; test.NullableDoubleField"));

            Assert.AreEqual(123, engine.Evaluate("test.DecimalField = 123; test.DecimalField"));
            Assert.AreEqual(123.5f, engine.Evaluate("test.DecimalField = 123.5; test.DecimalField"));
            Assert.AreEqual((double)(decimal)Math.PI, engine.Evaluate("test.DecimalField = Math.PI; test.DecimalField"));
            Assert.AreEqual((double)(decimal)Math.PI, engine.Evaluate("test.DecimalField = host.toDecimal(Math.PI); test.DecimalField"));

            Assert.AreEqual(123, engine.Evaluate("test.NullableDecimalField = 123; test.NullableDecimalField"));
            Assert.AreEqual(123.5f, engine.Evaluate("test.NullableDecimalField = 123.5; test.NullableDecimalField"));
            Assert.AreEqual((double)(decimal)Math.PI, engine.Evaluate("test.NullableDecimalField = Math.PI; test.NullableDecimalField"));
            Assert.AreEqual((double)(decimal)Math.PI, engine.Evaluate("test.NullableDecimalField = host.toDecimal(Math.PI); test.NullableDecimalField"));
            Assert.IsNull(engine.Evaluate("test.NullableDecimalField = null; test.NullableDecimalField"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_NumericArgConversion_Method()
        {
            engine.Script.host = new HostFunctions();
            engine.Script.test = new NumericArgConversionTest();

            Assert.AreEqual(123, engine.Evaluate("test.SByteMethod(123)"));
            TestUtil.AssertException(() => engine.Execute("test.SByteMethod(234)"));
            TestUtil.AssertMethodBindException(() => engine.Execute("test.SByteMethod(123.5)"));
            TestUtil.AssertMethodBindException(() => engine.Execute("test.SByteMethod(Math.PI)"));
            TestUtil.AssertMethodBindException(() => engine.Execute("test.SByteMethod(host.toDecimal(Math.PI))"));

            Assert.AreEqual(123, engine.Evaluate("test.NullableSByteMethod(123)"));
            TestUtil.AssertException(() => engine.Execute("test.NullableSByteMethod(234)"));
            TestUtil.AssertMethodBindException(() => engine.Execute("test.NullableSByteMethod(123.5)"));
            TestUtil.AssertMethodBindException(() => engine.Execute("test.NullableSByteMethod(Math.PI)"));
            TestUtil.AssertMethodBindException(() => engine.Execute("test.NullableSByteMethod(host.toDecimal(Math.PI))"));
            Assert.IsNull(engine.Evaluate("test.NullableSByteMethod(null)"));

            Assert.AreEqual(123, engine.Evaluate("test.FloatMethod(123)"));
            Assert.AreEqual(123.5f, engine.Evaluate("test.FloatMethod(123.5)"));
            TestUtil.AssertMethodBindException(() => engine.Execute("test.FloatMethod(Math.PI)"));
            TestUtil.AssertMethodBindException(() => engine.Execute("test.FloatMethod(host.toDecimal(Math.PI))"));

            Assert.AreEqual(123, engine.Evaluate("test.NullableFloatMethod(123)"));
            Assert.AreEqual(123.5f, engine.Evaluate("test.NullableFloatMethod(123.5)"));
            TestUtil.AssertMethodBindException(() => engine.Execute("test.NullableFloatMethod(Math.PI)"));
            TestUtil.AssertMethodBindException(() => engine.Execute("test.NullableFloatMethod(host.toDecimal(Math.PI))"));
            Assert.IsNull(engine.Evaluate("test.NullableFloatMethod(null)"));

            Assert.AreEqual(123, engine.Evaluate("test.DoubleMethod(123)"));
            Assert.AreEqual(123.5f, engine.Evaluate("test.DoubleMethod(123.5)"));
            Assert.AreEqual(Math.PI, engine.Evaluate("test.DoubleMethod(Math.PI)"));
            TestUtil.AssertMethodBindException(() => engine.Execute("test.DoubleMethod(host.toDecimal(Math.PI))"));

            Assert.AreEqual(123, engine.Evaluate("test.NullableDoubleMethod(123)"));
            Assert.AreEqual(123.5f, engine.Evaluate("test.NullableDoubleMethod(123.5)"));
            Assert.AreEqual(Math.PI, engine.Evaluate("test.NullableDoubleMethod(Math.PI)"));
            TestUtil.AssertMethodBindException(() => engine.Execute("test.NullableDoubleMethod(host.toDecimal(Math.PI))"));
            Assert.IsNull(engine.Evaluate("test.NullableDoubleMethod(null)"));

            Assert.AreEqual(123, engine.Evaluate("test.DecimalMethod(123)"));
            TestUtil.AssertMethodBindException(() => engine.Execute("test.DecimalMethod(123.5)"));
            TestUtil.AssertMethodBindException(() => engine.Execute("test.DecimalMethod(Math.PI)"));
            Assert.AreEqual((double)(decimal)Math.PI, engine.Evaluate("test.DecimalMethod(host.toDecimal(Math.PI))"));

            Assert.AreEqual(123, engine.Evaluate("test.NullableDecimalMethod(123)"));
            TestUtil.AssertMethodBindException(() => engine.Execute("test.NullableDecimalMethod(123.5)"));
            TestUtil.AssertMethodBindException(() => engine.Execute("test.NullableDecimalMethod(Math.PI)"));
            Assert.AreEqual((double)(decimal)Math.PI, engine.Evaluate("test.NullableDecimalMethod(host.toDecimal(Math.PI))"));
            Assert.IsNull(engine.Evaluate("test.NullableDecimalMethod(null)"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_NumericArgConversion_Property()
        {
            engine.Script.host = new HostFunctions();
            engine.Script.test = new NumericArgConversionTest();

            Assert.AreEqual(123, engine.Evaluate("test.SByteProperty = 123; test.SByteProperty"));
            TestUtil.AssertException(() => engine.Execute("test.SByteProperty = 234"));
            TestUtil.AssertException(() => engine.Execute("test.SByteProperty = 123.5"));
            TestUtil.AssertException(() => engine.Execute("test.SByteProperty = Math.PI"));
            TestUtil.AssertException(() => engine.Execute("test.SByteProperty = host.toDecimal(Math.PI)"));

            Assert.AreEqual(123, engine.Evaluate("test.NullableSByteProperty = 123; test.NullableSByteProperty"));
            TestUtil.AssertException(() => engine.Execute("test.NullableSByteProperty = 234"));
            TestUtil.AssertException(() => engine.Execute("test.NullableSByteProperty = 123.5"));
            TestUtil.AssertException(() => engine.Execute("test.NullableSByteProperty = Math.PI"));
            TestUtil.AssertException(() => engine.Execute("test.NullableSByteProperty = host.toDecimal(Math.PI)"));
            Assert.IsNull(engine.Evaluate("test.NullableSByteProperty = null; test.NullableSByteProperty"));

            Assert.AreEqual(123, engine.Evaluate("test.FloatProperty = 123; test.FloatProperty"));
            Assert.AreEqual(123.5f, engine.Evaluate("test.FloatProperty = 123.5; test.FloatProperty"));
            Assert.AreEqual((float)Math.PI, engine.Evaluate("test.FloatProperty = Math.PI; test.FloatProperty"));
            Assert.AreEqual((float)Math.PI, engine.Evaluate("test.FloatProperty = host.toDecimal(Math.PI); test.FloatProperty"));

            Assert.AreEqual(123, engine.Evaluate("test.NullableFloatProperty = 123; test.NullableFloatProperty"));
            Assert.AreEqual(123.5f, engine.Evaluate("test.NullableFloatProperty = 123.5; test.NullableFloatProperty"));
            Assert.AreEqual((float)Math.PI, engine.Evaluate("test.NullableFloatProperty = Math.PI; test.NullableFloatProperty"));
            Assert.AreEqual((float)Math.PI, engine.Evaluate("test.NullableFloatProperty = host.toDecimal(Math.PI); test.NullableFloatProperty"));
            Assert.IsNull(engine.Evaluate("test.NullableFloatProperty = null; test.NullableFloatProperty"));

            Assert.AreEqual(123, engine.Evaluate("test.DoubleProperty = 123; test.DoubleProperty"));
            Assert.AreEqual(123.5f, engine.Evaluate("test.DoubleProperty = 123.5; test.DoubleProperty"));
            Assert.AreEqual(Math.PI, engine.Evaluate("test.DoubleProperty = Math.PI; test.DoubleProperty"));
            Assert.AreEqual((double)(decimal)Math.PI, engine.Evaluate("test.DoubleProperty = host.toDecimal(Math.PI); test.DoubleProperty"));

            Assert.AreEqual(123, engine.Evaluate("test.NullableDoubleProperty = 123; test.NullableDoubleProperty"));
            Assert.AreEqual(123.5f, engine.Evaluate("test.NullableDoubleProperty = 123.5; test.NullableDoubleProperty"));
            Assert.AreEqual(Math.PI, engine.Evaluate("test.NullableDoubleProperty = Math.PI; test.NullableDoubleProperty"));
            Assert.AreEqual((double)(decimal)Math.PI, engine.Evaluate("test.NullableDoubleProperty = host.toDecimal(Math.PI); test.NullableDoubleProperty"));
            Assert.IsNull(engine.Evaluate("test.NullableDoubleProperty = null; test.NullableDoubleProperty"));

            Assert.AreEqual(123, engine.Evaluate("test.DecimalProperty = 123; test.DecimalProperty"));
            Assert.AreEqual(123.5f, engine.Evaluate("test.DecimalProperty = 123.5; test.DecimalProperty"));
            Assert.AreEqual((double)(decimal)Math.PI, engine.Evaluate("test.DecimalProperty = Math.PI; test.DecimalProperty"));
            Assert.AreEqual((double)(decimal)Math.PI, engine.Evaluate("test.DecimalProperty = host.toDecimal(Math.PI); test.DecimalProperty"));

            Assert.AreEqual(123, engine.Evaluate("test.NullableDecimalProperty = 123; test.NullableDecimalProperty"));
            Assert.AreEqual(123.5f, engine.Evaluate("test.NullableDecimalProperty = 123.5; test.NullableDecimalProperty"));
            Assert.AreEqual((double)(decimal)Math.PI, engine.Evaluate("test.NullableDecimalProperty = Math.PI; test.NullableDecimalProperty"));
            Assert.AreEqual((double)(decimal)Math.PI, engine.Evaluate("test.NullableDecimalProperty = host.toDecimal(Math.PI); test.NullableDecimalProperty"));
            Assert.IsNull(engine.Evaluate("test.NullableDecimalProperty = null; test.NullableDecimalProperty"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_ScriptObjectInHostVariable()
        {
            engine.Script.host = new HostFunctions();
            var documentInfo = new DocumentInfo("Expression") { Flags = DocumentFlags.IsTransient };
            Assert.IsTrue(engine.Evaluate(documentInfo.MakeUnique(engine), "host.newVar({})", false).ToString().StartsWith("[HostVariable:", StringComparison.Ordinal));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_V8HostMethodClobbering()
        {
            engine.Script.host = new HostFunctions();
            Assert.IsInstanceOfType(engine.Evaluate("host.newObj()"), typeof(PropertyBag));
            TestUtil.AssertException(() => engine.Execute("host.newObj = 123"));
            Assert.IsInstanceOfType(engine.Evaluate("host.newObj()"), typeof(PropertyBag));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_V8NativePropertyHiding()
        {
            var foo = new { toString = new Func(() => "testValue") };
            engine.Script.foo = foo;
            Assert.AreEqual(foo.toString(), engine.Evaluate("foo.toString()"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_V8NativePropertyHiding_Method()
        {
            var foo = new HideNativeJavaScriptMethod();
            engine.Script.foo = foo;
            Assert.AreEqual(foo.toString(), engine.Evaluate("foo.toString()"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_EquatableComparison_DateTime()
        {
            engine.Script.x = new DateTime(2007, 5, 22);
            engine.Script.y = new DateTime(2007, 5, 22);
            engine.Script.z = new DateTime(2008, 5, 22);
            Assert.IsTrue((bool)engine.Evaluate("(x == y) && (x === y)"));
            Assert.IsFalse((bool)engine.Evaluate("(x != y) || (x !== y)"));
            Assert.IsTrue((bool)engine.Evaluate("(x != z) && (x !== z)"));
            Assert.IsFalse((bool)engine.Evaluate("(x == z) || (x === z)"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_EquatableComparison_TimeSpan()
        {
            engine.Script.x = TimeSpan.FromSeconds(3.5);
            engine.Script.y = TimeSpan.FromSeconds(3.5);
            engine.Script.z = TimeSpan.FromSeconds(3.6);
            Assert.IsTrue((bool)engine.Evaluate("(x == y) && (x === y)"));
            Assert.IsFalse((bool)engine.Evaluate("(x != y) || (x !== y)"));
            Assert.IsTrue((bool)engine.Evaluate("(x != z) && (x !== z)"));
            Assert.IsFalse((bool)engine.Evaluate("(x == z) || (x === z)"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_EquatableComparison_WrappedNumeric()
        {
            var host = new HostFunctions();
            engine.Script.x = host.toUInt16(25);
            engine.Script.y = host.toUInt16(25);
            engine.Script.z = host.toUInt16(26);
            Assert.IsTrue((bool)engine.Evaluate("(x == y) && (x === y)"));
            Assert.IsFalse((bool)engine.Evaluate("(x != y) || (x !== y)"));
            Assert.IsTrue((bool)engine.Evaluate("(x != z) && (x !== z)"));
            Assert.IsFalse((bool)engine.Evaluate("(x == z) || (x === z)"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_AddHostType_AssemblyQualifiedName()
        {
            engine.AddHostType("Random", typeof(Random).AssemblyQualifiedName);
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_DoubleExecution()
        {
            engine.Execute(@"
                var num = 0;
                function foo() {
                    num += 1;
                    throw new Error();
                }
            ");

            try
            {
                engine.Script.foo();
            }
            catch (ScriptEngineException)
            {
            }

            Assert.AreEqual(1, engine.Script.num);

            engine.Script.num = 0;
            dynamic foo = engine.Script.foo;

            try
            {
                foo();
            }
            catch (ScriptEngineException)
            {
            }

            Assert.AreEqual(1, engine.Script.num);
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_DoubleExecution_Delegate()
        {
            var num = 0;
            engine.Script.foo = new Action(() =>
            {
                // ReSharper disable AccessToModifiedClosure

                num += 1;
                throw new Exception();

                // ReSharper restore AccessToModifiedClosure
            });

            try
            {
                engine.Script.foo();
            }
            catch (ScriptEngineException)
            {
            }

            Assert.AreEqual(1, num);

            num = 0;
            dynamic foo = engine.Script.foo;

            // ReSharper disable EmptyGeneralCatchClause

            try
            {
                foo();
            }
            catch
            {
            }

            Assert.AreEqual(1, num);

            // ReSharper restore EmptyGeneralCatchClause
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_SharedV8RuntimeLeak()
        {
            using (var runtime = new V8Runtime())
            {
                for (var i = 0; i < 5; i++)
                {
                    using (runtime.CreateScriptEngine())
                    {
                    }
                }

                runtime.CollectGarbage(true);
                var heapSize = runtime.GetHeapInfo().TotalHeapSize;

                for (var i = 0; i < 500; i++)
                {
                    using (runtime.CreateScriptEngine())
                    {
                    }

                    if ((i % 25) == 24)
                    {
                        runtime.CollectGarbage(true);
                    }
                }

                runtime.CollectGarbage(true);
                
                var newHeapSize = runtime.GetHeapInfo().TotalHeapSize;
                Assert.IsFalse(newHeapSize > (heapSize * 2.5));
            }
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_V8ExceptionWhileInterrupting()
        {
            var context = new PropertyBag();
            engine.Script.context = context;

            var startEvent = new ManualResetEventSlim();
            var waitEvent = new ManualResetEventSlim();
            var tokenSource = new CancellationTokenSource();

            context["count"] = 0;
            context["startEvent"] = startEvent;
            context["waitForCancel"] = new Action(() => waitEvent.Wait(tokenSource.Token));

            var thread = new Thread(() =>
            {
                try
                {
                    engine.Execute(@"
                        context.count = 1;
                        context.startEvent.Set();
                        context.waitForCancel();
                        context.count = 2;
                    ");
                }
                catch (ScriptEngineException)
                {
                }
                catch (ScriptInterruptedException)
                {
                }
            });

            thread.Start();
            startEvent.Wait(Timeout.Infinite);

            tokenSource.Cancel();
            engine.Interrupt();
            thread.Join();

            Assert.AreEqual(1, context["count"]);
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_NestedType()
        {
            engine.AddHostType(typeof(NestedTypeTest));
            Assert.AreEqual(NestedTypeTest.NestedType.Foo, engine.Evaluate("NestedTypeTest.NestedType.Foo"));
            Assert.AreEqual(NestedTypeTest.NestedType.Bar, engine.Evaluate("NestedTypeTest.NestedType.Bar"));
            Assert.AreEqual(NestedTypeTest.NestedType.Baz, engine.Evaluate("NestedTypeTest.NestedType.Baz"));
            Assert.AreEqual(NestedTypeTest.NestedType.Qux, engine.Evaluate("NestedTypeTest.NestedType.Qux"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_V8_Date_now()
        {
            var value = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds;
            var scriptValue = Convert.ToDouble(engine.Evaluate("Date.now()"));
            Assert.IsTrue(Math.Abs(scriptValue - value) < 25.0);
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_NonEnumerablePropertyAccess()
        {
            engine.Script.dump = new Action((obj, value) =>
            {
                Assert.AreEqual(value, obj.message);
            });

            engine.Execute(@"
                message = 'hello';
                dump({ message: message }, message);
                message = 'world';
                dump(new Error(message), message);
            ");
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_NonEnumerablePropertyAccess_VB()
        {
            TestUtil.InvokeVBTestSub(@"
                Using engine As New V8ScriptEngine
                    engine.Script.dump = Sub(obj As Object, value As String)
                        Assert.AreEqual(value, obj.message)
                    End Sub
                    engine.Execute(
                        ""message = 'hello';"" & _
                        ""dump({ message: message }, message);"" & _
                        ""message = 'world';"" & _
                        ""dump(new Error(message), message);""
                    )
                End Using
            ");
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_NonexistentPropertyAccess_VB()
        {
            TestUtil.InvokeVBTestSub(@"
                Using engine As New V8ScriptEngine
                    engine.Script.dump = Sub(obj As Object, message As Object, stack As Object)
                        Assert.AreEqual(message, obj.message)
                        Assert.AreEqual(stack, obj.stack)
                    End Sub
                    engine.Execute(
                        ""message = 'hello';"" & _
                        ""stack = 'world';"" & _
                        ""dump({ message: message, stack: stack }, message, stack);"" & _
                        ""dump({ message: message }, message, undefined);""
                    )
                End Using
            ");
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_JsonDotNetSerialization()
        {
            var obj = engine.Evaluate("({foo:123,bar:'baz'})");
            Assert.AreEqual("{\"foo\":123,\"bar\":\"baz\"}", JsonConvert.SerializeObject(obj));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_RuntimeCompileException()
        {
            using (var runtime = new V8Runtime())
            {
                // ReSharper disable once AccessToDisposedClosure
                TestUtil.AssertException(() => runtime.Compile("function foo bar () {}"));
            }
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_PropertyAccessorScriptability()
        {
            engine.Script.testObject = new PropertyAccessorScriptability();

            engine.Execute("testObject.Foo = 123");
            Assert.AreEqual(123, engine.Evaluate("testObject.Foo"));
            TestUtil.AssertException(() => engine.Execute("testObject.Bar = 123"));
            TestUtil.AssertException(() => engine.Evaluate("testObject.Bar"));

            engine.DefaultAccess = ScriptAccess.None;
            engine.Execute("testObject.Foo = 456");
            Assert.AreEqual(456, engine.Evaluate("testObject.Foo"));
            TestUtil.AssertException(() => engine.Execute("testObject.Bar = 456"));
            TestUtil.AssertException(() => engine.Evaluate("testObject.Bar"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_PropertyAccessorScriptability_Static()
        {
            engine.AddHostType("TestObject", typeof(PropertyAccessorScriptabilityStatic));

            engine.Execute("TestObject.Foo = 123");
            Assert.AreEqual(123, engine.Evaluate("TestObject.Foo"));
            TestUtil.AssertException(() => engine.Execute("TestObject.Bar = 123"));
            TestUtil.AssertException(() => engine.Evaluate("TestObject.Bar"));

            engine.DefaultAccess = ScriptAccess.None;
            engine.Execute("TestObject.Foo = 456");
            Assert.AreEqual(456, engine.Evaluate("TestObject.Foo"));
            TestUtil.AssertException(() => engine.Execute("TestObject.Bar = 456"));
            TestUtil.AssertException(() => engine.Evaluate("TestObject.Bar"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_PublicOverrideInInternalClass()
        {
            engine.Script.foo = new PublicOverrideTest();
            Assert.AreEqual(789, engine.Evaluate("foo.AbstractMethod()"));
            Assert.AreEqual(456, engine.Evaluate("foo.VirtualMethod()"));
            Assert.AreEqual("baz", engine.Evaluate("foo.AbstractProperty"));
            Assert.AreEqual("bar", engine.Evaluate("foo.VirtualProperty"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_ImplicitConversion()
        {
            engine.Script.doc = XDocument.Parse("barqux");
            engine.AddHostType(typeof(Enumerable));
            engine.AddHostType(typeof(XmlName));
            Assert.AreEqual(1, engine.Evaluate("doc.Root.Elements('foo').Count()"));
            Assert.AreEqual("bar", engine.Evaluate("doc.Root.Elements('foo').First().ToString()"));
            Assert.AreEqual(1, engine.Evaluate("doc.Root.Elements(new XmlName('foo')).Count()"));
            Assert.AreEqual("bar", engine.Evaluate("doc.Root.Elements(new XmlName('foo')).First().ToString()"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_ImplicitConversion_Constructor()
        {
            engine.AddHostType(typeof(ImplicitConversionTest));
            engine.AddHostType(typeof(MyDayOfWeek));
            engine.Execute("test = new ImplicitConversionTest(123)");
            Assert.AreEqual(123, engine.Evaluate("test.Data"));
            engine.Execute("test = new ImplicitConversionTest(MyDayOfWeek.Thursday)");
            Assert.AreEqual(DayOfWeek.Thursday, engine.Evaluate("test.Data"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_ImplicitConversion_ReflectionBoundMethod()
        {
            engine.DisableDynamicBinding = true;
            engine.Script.test = new ImplicitConversionTest(0);
            engine.AddHostType(typeof(MyDayOfWeek));
            engine.Execute("test.SetData(123)");
            Assert.AreEqual(123, engine.Evaluate("test.Data"));
            engine.Execute("test.SetData(MyDayOfWeek.Thursday)");
            Assert.AreEqual(DayOfWeek.Thursday, engine.Evaluate("test.Data"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_ImplicitConversion_Invalid()
        {
            Func testMethod = arg => arg;
            var testArg = new XmlName("foo");
            engine.Script.testMethod = testMethod;
            engine.Script.testArg = testArg;
            Assert.AreEqual(testMethod(testArg), engine.Evaluate("testMethod(testArg)"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_TextDigest()
        {
            const string allChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
            var random = new Random();

            var proxy = V8TestProxy.Create();
            for (var i = 0; i < 1024; i++)
            {
                var chars = new char[random.Next(8, 256)];
                for (var j = 0; j < chars.Length; j++)
                {
                    chars[j] = allChars[random.Next(allChars.Length - 1)];
                }

                var value = new string(chars);
                Assert.AreEqual(value.GetDigest(), proxy.GetNativeDigest(value));
            }
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_EventAfterEngineDispose()
        {
            var testObject = new TestObject();
            engine.Script.testObject = testObject;
            engine.Execute("testObject.Event.connect((sender, args) => eventArg = args.Arg)");
            testObject.FireEvent(123);
            Assert.AreEqual(123, Convert.ToInt32(engine.Script.eventArg));
            engine.Dispose();
            engine = new V8ScriptEngine(V8ScriptEngineFlags.EnableDebugging);
            testObject.FireEvent(456);
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_V8ScriptEngine_TaskPromiseConversion()
        {
            engine.Dispose();
            engine = new V8ScriptEngine(V8ScriptEngineFlags.EnableDebugging | V8ScriptEngineFlags.EnableTaskPromiseConversion);

            engine.AddHostType(typeof(TaskPromiseConversionTest));
            engine.Execute("value = TaskPromiseConversionTest.GetStringAsync();");
            Assert.AreEqual("Promise", engine.Evaluate("value.constructor.name"));
            Task.Delay(200).Wait();

            engine.Execute("value.then(value => result = value);");
            Assert.AreEqual("foo", engine.Script.result);

            engine.Execute("TaskPromiseConversionTest.GetStringAsync().then(async function () { result = TaskPromiseConversionTest.GetStringQuicklyAsync() instanceof Promise; });");
            Task.Delay(200).Wait();
            Assert.AreEqual(true, engine.Script.result);
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_V8ScriptEngine_PropertyNamesAndIndices()
        {
            engine.Execute(@"
                obj = {};
                obj['0'] = 'foo';
                obj['01'] = 'bar';
                obj['2'] = 'baz';
                obj[2.5] = 'qux';
                obj[3] = 'quux';
            ");

            var obj = engine.Script.obj as ScriptObject;
            Assert.AreEqual("01,2.5", string.Join(",", obj.PropertyNames));
            Assert.AreEqual("0,2,3", string.Join(",", obj.PropertyIndices));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_RestrictedPrototypeObject()
        {
            var test = new RestrictedPrototypeObject(true);
            engine.Script.test = test;
            Assert.AreEqual("bar", engine.Evaluate("test.Foo"));
            engine.Execute("test.Foo = 'qux'");
            Assert.AreEqual("qux", engine.Evaluate("test.Foo"));
            Assert.IsTrue(Convert.ToBoolean(engine.Evaluate("delete test.Foo")));
            Assert.AreEqual("bar", engine.Evaluate("test.Foo"));

            test = new RestrictedPrototypeObject(false);
            engine.Script.test = test;
            Assert.IsInstanceOfType(engine.Evaluate("test.Foo"), typeof(Undefined));
            Assert.IsTrue(test.SetPrototype(engine));
            Assert.AreEqual("bar", engine.Evaluate("test.Foo"));
            engine.Execute("test.Foo = 'qux'");
            Assert.AreEqual("qux", engine.Evaluate("test.Foo"));
            Assert.IsTrue(Convert.ToBoolean(engine.Evaluate("delete test.Foo")));
            Assert.AreEqual("bar", engine.Evaluate("test.Foo"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_RestrictedPrototypeObject_ProxyPreservation()
        {
            engine.Script.test = new { obj = new RestrictedPrototypeObject(true) };
            Assert.AreEqual("function", engine.Evaluate("typeof test.obj.Add"));
            engine.CollectGarbage(true);
            Assert.AreEqual("function", engine.Evaluate("typeof test.obj.Add"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_DefaultDocumentLoader_RedundantFileChecks()
        {
            var info = new DocumentInfo(new Uri(@"X:\Bogus\Example\Docs\Nothing.txt"));
            DocumentLoader.Default.CacheDocument(new StringDocument(info, "Move along."), false);

            var settings = new DocumentSettings { AccessFlags = DocumentAccessFlags.EnableFileLoading };
            settings.SearchPath = @"Q:\Heinous\Example\Docs;X:\Bogus\Example\Docs";
            settings.FileNameExtensions = "foo;bar;baz;txt";

            var statistics = (DocumentLoader.IStatistics)DocumentLoader.Default;
            statistics.ResetCheckCounts();

            TestUtil.AssertException(() => DocumentLoader.Default.LoadDocument(settings, null, "Nonsense", null, null));
            var count = statistics.FileCheckCount;
            Assert.IsTrue(count >= 10);
            Assert.AreEqual(0, statistics.WebCheckCount);

            Assert.AreEqual("Move along.", DocumentLoader.Default.LoadDocument(settings, null, "Nothing", null, null).GetTextContents());
            Assert.AreEqual(count, statistics.FileCheckCount);
            Assert.AreEqual(0, statistics.WebCheckCount);
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_DefaultDocumentLoader_RedundantWebChecks()
        {
            var info = new DocumentInfo(new Uri("http://bogus.example.com/docs/nothing.txt"));
            DocumentLoader.Default.CacheDocument(new StringDocument(info, "Nothing to see here."), false);

            var settings = new DocumentSettings { AccessFlags = DocumentAccessFlags.EnableWebLoading };
            settings.SearchPath = "http://heinous.example.com/docs;http://bogus.example.com/docs";
            settings.FileNameExtensions = "foo;bar;baz;txt";

            var statistics = (DocumentLoader.IStatistics)DocumentLoader.Default;
            statistics.ResetCheckCounts();

            TestUtil.AssertException(() => DocumentLoader.Default.LoadDocument(settings, null, "nonsense", null, null));
            Assert.AreEqual(0, statistics.FileCheckCount);
            Assert.AreEqual(10, statistics.WebCheckCount);

            Assert.AreEqual("Nothing to see here.", DocumentLoader.Default.LoadDocument(settings, null, "nothing", null, null).GetTextContents());
            Assert.AreEqual(0, statistics.FileCheckCount);
            Assert.AreEqual(10, statistics.WebCheckCount);
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_V8_DisableGlobalMembers_Iteration()
        {
            engine.Script.test = new[] { "one", "two", "three" };
            Assert.AreEqual(3, ((dynamic)engine.Evaluate("Array.from(test)")).length);
            Assert.AreEqual("one", ((dynamic)engine.Evaluate("Array.from(test)"))[0]);
            Assert.AreEqual("two", ((dynamic)engine.Evaluate("Array.from(test)"))[1]);
            Assert.AreEqual("three", ((dynamic)engine.Evaluate("Array.from(test)"))[2]);

            engine.Dispose();
            engine = new V8ScriptEngine(V8ScriptEngineFlags.EnableDebugging | V8ScriptEngineFlags.DisableGlobalMembers);

            engine.Script.data = new[] { "foo", "bar", "baz" };
            Assert.AreEqual(3, ((dynamic)engine.Evaluate("Array.from(data)")).length);
            Assert.AreEqual("foo", ((dynamic)engine.Evaluate("Array.from(data)"))[0]);
            Assert.AreEqual("bar", ((dynamic)engine.Evaluate("Array.from(data)"))[1]);
            Assert.AreEqual("baz", ((dynamic)engine.Evaluate("Array.from(data)"))[2]);
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_V8_HostPrototypes()
        {
            engine.Script.random = new Random();
            engine.Script.cb = new Func(() => Math.E);
            engine.AddHostType(typeof(Console));
            engine.Execute(@"
                function extendHostObject(hostObject) {
                    let proto = {};
                    proto._proto = proto;
                    Object.setPrototypeOf(proto, Object.getPrototypeOf(hostObject));
                    Object.setPrototypeOf(hostObject, proto);
                }
                extendHostObject(random);
                random._proto.E = Math.E
                extendHostObject(cb);
                cb._proto.SQRT2 = Math.SQRT2;
                extendHostObject(Console);
                Console._proto.PI = Math.PI;
            ");

            Assert.AreEqual(Math.E, engine.Evaluate("random.E"));
            Assert.AreEqual(Math.Sqrt(2), engine.Evaluate("cb.SQRT2"));
            Assert.AreEqual(Math.PI, engine.Evaluate("Console.PI"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_V8CompiledScript_WrongIsolate()
        {
            using (var script = ((V8ScriptEngine)engine).Compile("function foo() {}"))
            {
                using (var tempEngine = new V8ScriptEngine(V8ScriptEngineFlags.EnableDebugging))
                {
                    // ReSharper disable AccessToDisposedClosure
                    TestUtil.AssertException(() => tempEngine.Execute(script), false);
                    // ReSharper restore AccessToDisposedClosure
                }
            }
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_InaccessibleEventHandlerType()
        {
            var testObject = new InaccessibleEventHandlerTypeTestObject();
            engine.AddHostObject("testObject", HostItemFlags.PrivateAccess, testObject);
            engine.Execute("connection = testObject.TestEvent.connect(value => eventValue = value);");
            testObject.FireTestEvent(12345);
            Assert.AreEqual(12345, engine.Script.eventValue);
            engine.Execute("connection.disconnect()");
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_Exception_TargetSite_Reflection()
        {
            engine.AddHostType(typeof(SystemException));
            TestUtil.AssertException(() => engine.Evaluate("new SystemException().TargetSite"));
            engine.AllowReflection = true;
            Assert.IsNull(engine.Evaluate("new SystemException().TargetSite"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_EnumBindings_ZeroFirst()
        {
            engine.Script.test = new BindingTestObject();
            Assert.AreEqual(DayOfWeek.Sunday, engine.Evaluate("test.GetDay(0)"));
            TestUtil.AssertMethodBindException(() => engine.Evaluate("test.GetDay(1)"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_EnumBindings_NonZeroFirst()
        {
            engine.Script.test = new BindingTestObject();
            TestUtil.AssertMethodBindException(() => engine.Evaluate("test.GetDay(1)"));
            Assert.AreEqual(DayOfWeek.Sunday, engine.Evaluate("test.GetDay(0)"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_EnumBindings_ManyZeroes()
        {
            engine.Script.test = new BindingTestObject();
            engine.Script.host = new HostFunctions();
            Assert.AreEqual(DayOfWeek.Sunday, engine.Evaluate("test.GetDay(0)"));
            Assert.AreEqual(DayOfWeek.Sunday, engine.Evaluate("test.GetDay(host.toSByte(0))"));
            Assert.AreEqual(DayOfWeek.Sunday, engine.Evaluate("test.GetDay(host.toByte(0))"));
            Assert.AreEqual(DayOfWeek.Sunday, engine.Evaluate("test.GetDay(host.toInt16(0))"));
            Assert.AreEqual(DayOfWeek.Sunday, engine.Evaluate("test.GetDay(host.toUInt16(0))"));
            Assert.AreEqual(DayOfWeek.Sunday, engine.Evaluate("test.GetDay(host.toInt32(0))"));
            Assert.AreEqual(DayOfWeek.Sunday, engine.Evaluate("test.GetDay(host.toUInt32(0))"));
            Assert.AreEqual(DayOfWeek.Sunday, engine.Evaluate("test.GetDay(host.toInt64(0))"));
            Assert.AreEqual(DayOfWeek.Sunday, engine.Evaluate("test.GetDay(host.toUInt64(0))"));
            Assert.AreEqual(DayOfWeek.Sunday, engine.Evaluate("test.GetDay(host.toSingle(0))"));
            Assert.AreEqual(DayOfWeek.Sunday, engine.Evaluate("test.GetDay(host.toDouble(0))"));
            Assert.AreEqual(DayOfWeek.Sunday, engine.Evaluate("test.GetDay(host.toDecimal(0))"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_EnumBindings_ManyNonZeroes()
        {
            engine.Script.test = new BindingTestObject();
            engine.Script.host = new HostFunctions();
            TestUtil.AssertMethodBindException(() => engine.Evaluate("test.GetDay(1)"));
            TestUtil.AssertMethodBindException(() => engine.Evaluate("test.GetDay(host.toSByte(1))"));
            TestUtil.AssertMethodBindException(() => engine.Evaluate("test.GetDay(host.toByte(1))"));
            TestUtil.AssertMethodBindException(() => engine.Evaluate("test.GetDay(host.toInt16(1))"));
            TestUtil.AssertMethodBindException(() => engine.Evaluate("test.GetDay(host.toUInt16(1))"));
            TestUtil.AssertMethodBindException(() => engine.Evaluate("test.GetDay(host.toInt32(1))"));
            TestUtil.AssertMethodBindException(() => engine.Evaluate("test.GetDay(host.toUInt32(1))"));
            TestUtil.AssertMethodBindException(() => engine.Evaluate("test.GetDay(host.toInt64(1))"));
            TestUtil.AssertMethodBindException(() => engine.Evaluate("test.GetDay(host.toUInt64(1))"));
            TestUtil.AssertMethodBindException(() => engine.Evaluate("test.GetDay(host.toSingle(1))"));
            TestUtil.AssertMethodBindException(() => engine.Evaluate("test.GetDay(host.toDouble(1))"));
            TestUtil.AssertMethodBindException(() => engine.Evaluate("test.GetDay(host.toDecimal(1))"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_Binding_DefaultArgsAndParamArray()
        {
            engine.Script.test = new BindingTestObject();
            Assert.AreEqual("123, 456, ", engine.Evaluate("test.Format(123)"));
            Assert.AreEqual("123, 789, ", engine.Evaluate("test.Format(123, 789)"));
            Assert.AreEqual("123, 789, 456", engine.Evaluate("test.Format(123, 789, 456)"));
            Assert.AreEqual("123, 789, 456, 987", engine.Evaluate("test.Format(123, 789, 456, 987)"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_DefaultArgs_Constructor()
        {
            engine.AddHostType(typeof(DefaultArgsTestObject));
            Assert.AreEqual("123 foo", engine.Evaluate("new DefaultArgsTestObject().Value"));
            Assert.AreEqual("456 foo", engine.Evaluate("new DefaultArgsTestObject(456).Value"));
            Assert.AreEqual("789 bar", engine.Evaluate("new DefaultArgsTestObject(789, 'bar').Value"));
            TestUtil.AssertMethodBindException(() => engine.Evaluate("new DefaultArgsTestObject(789, 'bar', true).Value"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_DefaultArgs_Indexer()
        {
            engine.Script.test = new DefaultArgsTestObject();
            engine.Execute("test.Item.set(Math.PI)");
            Assert.AreEqual(Math.PI, engine.Evaluate("test.Item()"));
            Assert.AreEqual(Math.PI, engine.Evaluate("test.Item.get()"));
            engine.Execute("test.Item.set(456, Math.E)");
            Assert.AreEqual(Math.E, engine.Evaluate("test.Item(456)"));
            Assert.AreEqual(Math.E, engine.Evaluate("test.Item.get(456)"));
            engine.Execute("test.Item.set(789, 'bar', Math.PI * Math.E)");
            Assert.AreEqual(Math.PI * Math.E, engine.Evaluate("test.Item(789, 'bar')"));
            Assert.AreEqual(Math.PI * Math.E, engine.Evaluate("test.Item.get(789, 'bar')"));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_BadV8DebugPort()
        {
            engine.Dispose();
            engine = new V8ScriptEngine(V8ScriptEngineFlags.EnableDebugging, 70000);
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_EnumerationWithNoDefaultScriptAccess()
        {
            engine.DefaultAccess = ScriptAccess.None;
            engine.AddHostObject("test", new DefaultScriptAccessOverrideTestObject());
            engine.Execute("Array.from(test.Values)");
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_V8HostPropertyEnumeration()
        {
            var foo = new PropertyBag { { "foo", 123 }, { "bar", "baz" } };

            engine.Script.foo = foo;
            engine.Execute(@"
                var keys = [];
                for (let key in foo) {
                    keys.push(key);
                }
            ");

            Assert.AreEqual(foo.Keys.Count, engine.Script.keys.length);
            Assert.IsTrue(foo.Keys.All(key => engine.Script.keys.includes(key)));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_AnonymousTypeMemberEnumeration()
        {
            var foo = new { foo = 123, bar = "baz" };
            ((V8ScriptEngine)engine).SuppressInstanceMethodEnumeration = true;

            Assert.AreEqual(2, engine.Script.Object.keys(foo).length);
            Assert.IsTrue(new[] { "foo", "bar" }.All(key => engine.Script.Object.keys(foo).includes(key)));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_AnonymousTypeMemberEnumeration_VB()
        {
            TestUtil.InvokeVBTestSub(@"
                Using engine As New V8ScriptEngine
                    engine.Script.foo = New With {.foo = 123, .bar = ""baz""}
                    engine.SuppressInstanceMethodEnumeration = True
                    Assert.AreEqual(2, engine.Evaluate(""Object.keys(foo).length""))
                    Assert.IsTrue((New String() {""foo"", ""bar""}).All(Function (key) engine.Evaluate(""Object.keys(foo).includes('"" & key & ""')"")))
                End Using
            ");
        }

    #if NET6_0_OR_GREATER

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_StructConstructors()
        {
            engine.AddHostType(typeof(StructConstructorTest));
            Assert.AreEqual(123, engine.Evaluate("new StructConstructorTest().Value"));
            Assert.AreEqual(789, engine.Evaluate("new StructConstructorTest(789).Value"));

            engine.Dispose();
            engine = new V8ScriptEngine(V8ScriptEngineFlags.EnableDebugging);
            engine.AddHostType(typeof(StructConstructorTest));

            using (Scope.Create(() => HostSettings.CustomAttributeLoader, loader => HostSettings.CustomAttributeLoader = loader))
            {
                HostSettings.CustomAttributeLoader = new StructConstructorTestCustomAttributeLoader();
                Assert.AreEqual(456, engine.Evaluate("new StructConstructorTest().Value"));
                Assert.AreEqual(789, engine.Evaluate("new StructConstructorTest(789).Value"));
            }
        }

    #else // !NET6_0_OR_GREATER

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_StructConstructors()
        {
            engine.AddHostType(typeof(StructConstructorTest));
            Assert.AreEqual(0, engine.Evaluate("new StructConstructorTest().Value"));
            Assert.AreEqual(789, engine.Evaluate("new StructConstructorTest(789).Value"));

            engine.Dispose();
            engine = new V8ScriptEngine(V8ScriptEngineFlags.EnableDebugging);
            engine.AddHostType(typeof(StructConstructorTest));

            using (Scope.Create(() => HostSettings.CustomAttributeLoader, loader => HostSettings.CustomAttributeLoader = loader))
            {
                HostSettings.CustomAttributeLoader = new StructConstructorTestCustomAttributeLoader();
                Assert.AreEqual(0, engine.Evaluate("new StructConstructorTest().Value"));
                Assert.AreEqual(789, engine.Evaluate("new StructConstructorTest(789).Value"));
            }
        }

    #endif // !NET6_0_OR_GREATER

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_PropertyAccessorRecursion()
        {
            engine.Script.test = new PropertyAccessorRecursionTest { Engine = engine };
            var result = engine.Evaluate(@"
                (function() {
                    let count = 0;
                    globalThis.onGetProperty = function () {
                        if (count++ < 5) {
                            const value = test.Property;
                        }
                    };
                    globalThis.onSetProperty = function () {
                        if (test.Property == 123) {
                            test.Property = 456;
                        } else {
                            test.Property = 789;
                        }
                    };
                })();
                test.Property = 123;
                test.Property
            ");
            Assert.AreEqual(789, result);
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_V8Runtime_CreateScriptEngineAfterNoopInterrupt()
        {
            using (var runtime = new V8Runtime())
            {
                using (var tempEngine = runtime.CreateScriptEngine())
                {
                    tempEngine.Interrupt();
                    using (runtime.CreateScriptEngine())
                    {
                    }
                }
            }
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_V8Runtime_CreateScriptEngineWithPendingInterrupt()
        {
            using (var runtime = new V8Runtime())
            {
                using (var tempEngine = runtime.CreateScriptEngine())
                {
                    // ReSharper disable AccessToDisposedClosure

                    tempEngine.Script.foo = new Action(() =>
                    {
                        tempEngine.Interrupt();
                        TestUtil.AssertException(() => runtime.CreateScriptEngine(), false);
                        tempEngine.CancelInterrupt();
                    });

                    // ReSharper restore AccessToDisposedClosure

                    tempEngine.Execute("foo()");
                }
            }
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_V8_MaxRuntimeHeapSize_SetBelowCurrent()
        {
            ((V8ScriptEngine)engine).MaxRuntimeHeapSize = new UIntPtr(256 * 1024);
            TestUtil.AssertException(() => engine.Execute(""));
            TestUtil.AssertException(() => engine.Execute(""));
        }

        [TestMethod, TestCategory("BugFix")]
        public void BugFix_ZeroLengthDataTransfer()
        {
            var buffer = (IArrayBuffer)engine.Evaluate("new ArrayBuffer(0)");

            Assert.AreEqual(0UL, buffer.ReadBytes(0, 0, new byte[0], 0));
            TestUtil.AssertException(() => buffer.ReadBytes(1, 0, new byte[0], 0));
            TestUtil.AssertException(() => buffer.ReadBytes(0, 1, new byte[0], 0));
            TestUtil.AssertException(() => buffer.ReadBytes(0, 0, new byte[0], 1));

            Assert.AreEqual(0UL, buffer.ReadBytes(0, 0, new byte[0].AsSpan(), 0));
            TestUtil.AssertException(() => buffer.ReadBytes(1, 0, new byte[0].AsSpan(), 0));
            TestUtil.AssertException(() => buffer.ReadBytes(0, 1, new byte[0].AsSpan(), 0));
            TestUtil.AssertException(() => buffer.ReadBytes(0, 0, new byte[0].AsSpan(), 1));

            Assert.AreEqual(0UL, buffer.WriteBytes(new byte[0], 0, 0, 0));
            TestUtil.AssertException(() => buffer.WriteBytes(new byte[0], 1, 0, 0));
            TestUtil.AssertException(() => buffer.WriteBytes(new byte[0], 0, 1, 0));
            TestUtil.AssertException(() => buffer.WriteBytes(new byte[0], 0, 0, 1));

            Assert.AreEqual(0UL, buffer.WriteBytes(new byte[0].AsSpan(), 0, 0, 0));
            TestUtil.AssertException(() => buffer.WriteBytes(new byte[0].AsSpan(), 1, 0, 0));
            TestUtil.AssertException(() => buffer.WriteBytes(new byte[0].AsSpan(), 0, 1, 0));
            TestUtil.AssertException(() => buffer.WriteBytes(new byte[0].AsSpan(), 0, 0, 1));

            var view = (IDataView)engine.Evaluate("new DataView(new ArrayBuffer(0))");

            Assert.AreEqual(0UL, view.ReadBytes(0, 0, new byte[0], 0));
            TestUtil.AssertException(() => view.ReadBytes(1, 0, new byte[0], 0));
            TestUtil.AssertException(() => view.ReadBytes(0, 1, new byte[0], 0));
            TestUtil.AssertException(() => view.ReadBytes(0, 0, new byte[0], 1));

            Assert.AreEqual(0UL, view.ReadBytes(0, 0, new byte[0].AsSpan(), 0));
            TestUtil.AssertException(() => view.ReadBytes(1, 0, new byte[0].AsSpan(), 0));
            TestUtil.AssertException(() => view.ReadBytes(0, 1, new byte[0].AsSpan(), 0));
            TestUtil.AssertException(() => view.ReadBytes(0, 0, new byte[0].AsSpan(), 1));

            Assert.AreEqual(0UL, view.WriteBytes(new byte[0], 0, 0, 0));
            TestUtil.AssertException(() => view.WriteBytes(new byte[0], 1, 0, 0));
            TestUtil.AssertException(() => view.WriteBytes(new byte[0], 0, 1, 0));
            TestUtil.AssertException(() => view.WriteBytes(new byte[0], 0, 0, 1));

            Assert.AreEqual(0UL, view.WriteBytes(new byte[0].AsSpan(), 0, 0, 0));
            TestUtil.AssertException(() => view.WriteBytes(new byte[0].AsSpan(), 1, 0, 0));
            TestUtil.AssertException(() => view.WriteBytes(new byte[0].AsSpan(), 0, 1, 0));
            TestUtil.AssertException(() => view.WriteBytes(new byte[0].AsSpan(), 0, 0, 1));

            void TestTypedArray(string name) where T : unmanaged
            {
                var typedArray = (ITypedArray)engine.Evaluate($"new {name}Array(0)");

                Assert.AreEqual(0UL, typedArray.ReadBytes(0, 0, new byte[0], 0));
                TestUtil.AssertException(() => typedArray.ReadBytes(1, 0, new byte[0], 0));
                TestUtil.AssertException(() => typedArray.ReadBytes(0, 1, new byte[0], 0));
                TestUtil.AssertException(() => typedArray.ReadBytes(0, 0, new byte[0], 1));

                Assert.AreEqual(0UL, typedArray.ReadBytes(0, 0, new byte[0].AsSpan(), 0));
                TestUtil.AssertException(() => typedArray.ReadBytes(1, 0, new byte[0].AsSpan(), 0));
                TestUtil.AssertException(() => typedArray.ReadBytes(0, 1, new byte[0].AsSpan(), 0));
                TestUtil.AssertException(() => typedArray.ReadBytes(0, 0, new byte[0].AsSpan(), 1));

                Assert.AreEqual(0UL, typedArray.Read(0, 0, new T[0], 0));
                TestUtil.AssertException(() => typedArray.Read(1, 0, new T[0], 0));
                TestUtil.AssertException(() => typedArray.Read(0, 1, new T[0], 0));
                TestUtil.AssertException(() => typedArray.Read(0, 0, new T[0], 1));

                Assert.AreEqual(0UL, typedArray.Read(0, 0, new T[0].AsSpan(), 0));
                TestUtil.AssertException(() => typedArray.Read(1, 0, new T[0].AsSpan(), 0));
                TestUtil.AssertException(() => typedArray.Read(0, 1, new T[0].AsSpan(), 0));
                TestUtil.AssertException(() => typedArray.Read(0, 0, new T[0].AsSpan(), 1));

                Assert.AreEqual(0UL, typedArray.WriteBytes(new byte[0], 0, 0, 0));
                TestUtil.AssertException(() => typedArray.WriteBytes(new byte[0], 1, 0, 0));
                TestUtil.AssertException(() => typedArray.WriteBytes(new byte[0], 0, 1, 0));
                TestUtil.AssertException(() => typedArray.WriteBytes(new byte[0], 0, 0, 1));

                Assert.AreEqual(0UL, typedArray.WriteBytes(new byte[0].AsSpan(), 0, 0, 0));
                TestUtil.AssertException(() => typedArray.WriteBytes(new byte[0].AsSpan(), 1, 0, 0));
                TestUtil.AssertException(() => typedArray.WriteBytes(new byte[0].AsSpan(), 0, 1, 0));
                TestUtil.AssertException(() => typedArray.WriteBytes(new byte[0].AsSpan(), 0, 0, 1));

                Assert.AreEqual(0UL, typedArray.Write(new T[0], 0, 0, 0));
                TestUtil.AssertException(() => typedArray.Write(new T[0], 1, 0, 0));
                TestUtil.AssertException(() => typedArray.Write(new T[0], 0, 1, 0));
                TestUtil.AssertException(() => typedArray.Write(new T[0], 0, 0, 1));

                Assert.AreEqual(0UL, typedArray.Write(new T[0].AsSpan(), 0, 0, 0));
                TestUtil.AssertException(() => typedArray.Write(new T[0].AsSpan(), 1, 0, 0));
                TestUtil.AssertException(() => typedArray.Write(new T[0].AsSpan(), 0, 1, 0));
                TestUtil.AssertException(() => typedArray.Write(new T[0].AsSpan(), 0, 0, 1));
            }

            TestTypedArray("Uint8");
            TestTypedArray("Uint8Clamped");
            TestTypedArray("Int8");
            TestTypedArray("Uint16");
            TestTypedArray("Uint16");
            TestTypedArray("Int16");
            TestTypedArray("Uint32");
            TestTypedArray("Int32");
            TestTypedArray("BigUint64");
            TestTypedArray("BigInt64");
            TestTypedArray("Float32");
            TestTypedArray("Float64");
        }

        // ReSharper restore InconsistentNaming

        #endregion

        #region miscellaneous

        public class NullableTest
        {
            public int? Field;

            public double? Property;

            public object Method(int? value)
            {
                return value + 1;
            }

            public object Method(double? value)
            {
                return value * 2.0;
            }
        }

        public abstract class AmbiguousAttributeTestBase
        {
            public abstract object this[int index] { get; }

            public abstract object this[string key] { get; }
        }

        public class AmbiguousAttributeTest : AmbiguousAttributeTestBase
        {
            public override object this[int index] => null;

            public override object this[string key] => null;

            public void Foo() {}
        }

        public class ResurrectionTestWrapper
        {
            private readonly IDisposable target;

            public ResurrectionTestWrapper(IDisposable target)
            {
                this.target = target;
            }

            ~ResurrectionTestWrapper()
            {
                target.Dispose();
            }
        }

        public interface IAmbiguousIndexerBase1
        {
            object this[int i] { get; set; }
        }

        public interface IAmbiguousIndexerBase2
        {
            object this[int i] { get; set; }
            object this[DayOfWeek d] { get; set; }
        }

        public interface IAmbiguousIndexerBase3
        {
            object this[int i] { get; set; }
            object this[char? c] { get; set; }
        }

        public interface IAmbiguousIndexer : IAmbiguousIndexerBase1, IAmbiguousIndexerBase2
        {
            new object this[int i] { get; set; }
        }

        public interface IAmbiguousIndexer2 : IAmbiguousIndexerBase1, IAmbiguousIndexerBase3
        {
            new object this[int i] { get; set; }
        }

        public class AmbiguousIndexer : IAmbiguousIndexer, IAmbiguousIndexer2
        {
            private readonly IDictionary byInteger = new ListDictionary();
            private readonly IDictionary byDayOfWeek = new ListDictionary();
            private readonly IDictionary byNullableChar = new ListDictionary();

            public object this[int key]
            {
                get => byInteger[key];
                set => byInteger[key] = value;
            }

            public object this[DayOfWeek key]
            {
                get => byDayOfWeek[key];
                set => byDayOfWeek[key] = value;
            }

            public object this[char? key]
            {
                get => byNullableChar[key];
                set => byNullableChar[key] = value;
            }
        }

        [ImmutableValue]
        public struct MyDayOfWeek
        {
            private readonly DayOfWeek day;

            public static MyDayOfWeek Sunday => DayOfWeek.Sunday;
            public static MyDayOfWeek Monday => DayOfWeek.Monday;
            public static MyDayOfWeek Tuesday => DayOfWeek.Tuesday;
            public static MyDayOfWeek Wednesday => DayOfWeek.Wednesday;
            public static MyDayOfWeek Thursday => DayOfWeek.Thursday;
            public static MyDayOfWeek Friday => DayOfWeek.Friday;
            public static MyDayOfWeek Saturday => DayOfWeek.Saturday;

            private MyDayOfWeek(DayOfWeek day) => this.day = day;

            public static implicit operator DayOfWeek(MyDayOfWeek self) => self.day;

            public static implicit operator MyDayOfWeek(DayOfWeek day) => new(day);

            public static implicit operator char?(MyDayOfWeek self) => (self.day == DayOfWeek.Sunday) ? null : (char?)self.day;

            public static implicit operator MyDayOfWeek(char? value) => new((DayOfWeek)value.GetValueOrDefault());
        }

        public class ImplicitConversionTest
        {
            public object Data { get; private set; }

            public ImplicitConversionTest(int data)
            {
                Data = data;
            }

            public ImplicitConversionTest(DayOfWeek data)
            {
                Data = data;
            }

            public void SetData(int data)
            {
                Data = data;
            }

            public void SetData(DayOfWeek data)
            {
                Data = data;
            }
        }

        public class InaccessiblePropertyAccessors
        {
            // ReSharper disable UnusedMember.Local

            private int dummy;

            public int NoGetter
            {
                set => dummy = value;
            }

            public int PrivateGetter
            {
                private get => dummy;
                set => dummy = value;
            }

            public int NoSetter => dummy;

            public int PrivateSetter
            {
                get => dummy;
                private set => dummy = value;
            }

            // ReSharper restore UnusedMember.Local
        }

        public static class InaccessiblePropertyAccessorsStatic
        {
            // ReSharper disable UnusedMember.Local

            private static int dummy = 12345;

            public static int NoGetter
            {
                set => dummy = value;
            }

            public static int PrivateGetter
            {
                private get => dummy;
                set => dummy = value;
            }

            public static int NoSetter => dummy;

            public static int PrivateSetter
            {
                get => dummy;
                private set => dummy = value;
            }

            // ReSharper restore UnusedMember.Local
        }

        [NoDefaultScriptAccess]
        public class PropertyAccessorScriptability
        {
            [ScriptMember]
            public int Foo { get; set; }

            [ScriptMember]
            public int Bar { [NoScriptAccess] get; [NoScriptAccess] set; }
        }

        [NoDefaultScriptAccess]
        public static class PropertyAccessorScriptabilityStatic
        {
            [ScriptMember]
            public static int Foo { get; set; }

            [ScriptMember]
            public static int Bar { [NoScriptAccess] get; [NoScriptAccess] set; }
        }

        public class DynamicMethodArgTest : DynamicObject
        {
            public override IEnumerable GetDynamicMemberNames()
            {
                yield return "RunTest";
            }

            // ReSharper disable RedundantOverridenMember

            public override bool TryGetMember(GetMemberBinder binder, out object result)
            {
                // this override is redundant, but required for the test
                return base.TryGetMember(binder, out result);
            }

            // ReSharper restore RedundantOverridenMember

            public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
            {
                if (binder.Name == "RunTest")
                {
                    result = string.Join(" ", args);
                    return true;
                }

                return base.TryInvokeMember(binder, args, out result);
            }
        }

        public class NumericArgConversionTest
        {
            public sbyte SByteField;
            public sbyte? NullableSByteField;
            public float FloatField;
            public float? NullableFloatField;
            public double DoubleField;
            public double? NullableDoubleField;
            public decimal DecimalField;
            public decimal? NullableDecimalField;

            public sbyte SByteMethod(sbyte arg) { return arg; }
            public sbyte? NullableSByteMethod(sbyte? arg) { return arg; }
            public float FloatMethod(float arg) { return arg; }
            public float? NullableFloatMethod(float? arg) { return arg; }
            public double DoubleMethod(double arg) { return arg; }
            public double? NullableDoubleMethod(double? arg) { return arg; }
            public decimal DecimalMethod(decimal arg) { return arg; }
            public decimal? NullableDecimalMethod(decimal? arg) { return arg; }

            public sbyte SByteProperty { get; set; }
            public sbyte? NullableSByteProperty { get; set; }
            public float FloatProperty { get; set; }
            public float? NullableFloatProperty { get; set; }
            public double DoubleProperty { get; set; }
            public double? NullableDoubleProperty { get; set; }
            public decimal DecimalProperty { get; set; }
            public decimal? NullableDecimalProperty { get; set; }
        }

        public class HideNativeJavaScriptMethod
        {
            // ReSharper disable InconsistentNaming

            public string toString()
            {
                return ToString();
            }

            // ReSharper restore InconsistentNaming
        }

        public class NestedTypeTest
        {
            public enum NestedType
            {
                Foo, Bar, Baz, Qux
            }
        }

        public abstract class PublicOverrideTestBase
        {
            public abstract int AbstractMethod();

            public virtual int VirtualMethod()
            {
                return 123;
            }

            public abstract string AbstractProperty { get; }

            public virtual string VirtualProperty => "foo";
        }

        internal sealed class PublicOverrideTest : PublicOverrideTestBase
        {
            public override int AbstractMethod()
            {
                return 789;
            }

            public override int VirtualMethod()
            {
                return 456;
            }

            public override string AbstractProperty => "baz";

            public override string VirtualProperty => "bar";
        }

        public class XmlName
        {
            private readonly string name;

            public XmlName(string name)
            {
                this.name = name;
            }

            public static implicit operator XName(XmlName xmlName)
            {
                return xmlName.name;
            }
        }

        [DefaultMember("Value")]
        [ComVisible(true)]
        public class Variable
        {
            public int Value { get; set; }
            public bool IsValid { get; set; }
            public string Description => "Test variable object";
        }

        public class TaskPromiseConversionTest
        {
            public static async Task GetStringAsync()
            {
                await Task.Delay(10);
                return "foo";
            }

        #pragma warning disable 1998 // This async method lacks 'await' operators and will run synchronously

            public static async Task GetStringQuicklyAsync()
            {
                return "foo";
            }

        #pragma warning restore 1998 // This async method lacks 'await' operators and will run synchronously

        }

        public class RestrictedPrototypeObjectBase : Dictionary
        {
            public string Foo => "bar";
        }

        public class RestrictedPrototypeObject : RestrictedPrototypeObjectBase, IPropertyBag, IScriptableObject
        {
            private readonly ConditionalWeakTable map = new();
            private readonly bool setPrototypeOnExpose;

            public RestrictedPrototypeObject(bool setPrototypeOnExpose)
            {
                this.setPrototypeOnExpose = setPrototypeOnExpose;
            }

            public bool SetPrototype(ScriptEngine engine)
            {
                var entry = map.GetOrCreateValue(engine);

                if (entry.Prototype is null)
                {
                    entry.Prototype = this.ToRestrictedHostObject(engine);
                }

                if (entry.ProxyHolder is null)
                {
                    engine.Script.Object.setPrototypeOf(this, entry.Prototype);
                    entry.ProxyHolder = engine.Script.Object.create(this);
                    return true;
                }

                return false;
            }

            void IScriptableObject.OnExposedToScriptCode(ScriptEngine engine)
            {
                if (setPrototypeOnExpose)
                {
                    SetPrototype(engine);
                }
            }

            // ReSharper disable ClassNeverInstantiated.Local
            // ReSharper disable NotAccessedField.Local

            private sealed class Entry
            {
                public object Prototype;
                public object ProxyHolder;
            }

            // ReSharper restore NotAccessedField.Local
            // ReSharper restore ClassNeverInstantiated.Local
        }

        private sealed class InaccessibleEventHandlerTypeTestObject
        {
            private delegate void TestEventHandler(int value);

            // ReSharper disable once EventNeverSubscribedTo.Local
            private event TestEventHandler TestEvent;

            public void FireTestEvent(int value)
            {
                TestEvent?.Invoke(value);
            }
        }

        public class BindingTestObject
        {
            public DayOfWeek GetDay(DayOfWeek day) => day;

            public string Format(int a, int b = 456, params int[] args)
            {
                var argsString = string.Join(", ", args);
                return $"{a}, {b}, {argsString}";
            }
        }

        public class DefaultArgsTestObject
        {
            private readonly Dictionary map = new();

            public DefaultArgsTestObject(int arg1 = 123, string arg2 = "foo")
            {
                Value = MiscHelpers.FormatInvariant("{0} {1}", arg1, arg2);
            }

            public object this[int arg1 = 123, string arg2 = "foo"]
            {
                get => map[MiscHelpers.FormatInvariant("{0} {1}", arg1, arg2)];
                set => map[MiscHelpers.FormatInvariant("{0} {1}", arg1, arg2)] = value;
            }

            public string Value { get; }
        }

        [DefaultScriptUsage(ScriptAccess.Full)]
        public class DefaultScriptAccessOverrideTestObject
        {
            public List Values { get; set; } = new() { 123, 456, 789 };
        }

    #if NET6_0_OR_GREATER

        public struct StructConstructorTest
        {
            public int Value { get; }

            public StructConstructorTest()
            {
                Value = 123;
            }

            public StructConstructorTest(int value = 456)
            {
                Value = value;
            }
        }

    #else // !NET6_0_OR_GREATER

        public struct StructConstructorTest
        {
            public int Value { get; }

            public StructConstructorTest(int value = 456)
            {
                Value = value;
            }
        }

    #endif // !NET6_0_OR_GREATER

        private sealed class StructConstructorTestCustomAttributeLoader : CustomAttributeLoader
        {
            public override T[] LoadCustomAttributes(ICustomAttributeProvider resource, bool inherit)
            {
                if ((resource is ConstructorInfo constructor) && (constructor.DeclaringType == typeof(StructConstructorTest)) && (constructor.GetParameters().Length < 1) && (typeof(T) == typeof(ScriptUsageAttribute)))
                {
                    return new[] { new ScriptUsageAttribute(ScriptAccess.None) } as T[];
                }

                return base.LoadCustomAttributes(resource, inherit);
            }
        }

        public class PropertyAccessorRecursionTest
        {
            public ScriptEngine Engine;
            private int property;

            public int Property
            {
                get
                {
                    Engine.Script.onGetProperty();
                    return property;
                }

                set
                {
                    if (value != property)
                    {
                        property = value;
                        Engine.Script.onSetProperty();
                    }
                }
            }
        }

        #endregion
    }
}

Web Proxy Viewer  |  New URL  |  Original Page