Quellcode durchsuchen

Implement object.fromEntries

Richard vor 7 Jahren
Ursprung
Commit
3d930362c2

+ 2 - 1
lib/Parser/rterrors.h

@@ -303,7 +303,8 @@ RT_ERROR_MSG(JSERR_ObjectIsNotInitialized, 5617, "%s: Object internal state is n
 
 RT_ERROR_MSG(JSERR_GeneratorAlreadyExecuting, 5618, "%s: Cannot execute generator function because it is currently executing", "", kjstTypeError, 0)
 RT_ERROR_MSG(JSERR_LengthIsTooBig, 5619, "Length property would exceed maximum value in output from '%s'", "", kjstTypeError, 0)
-// 5620-5626 Unused
+RT_ERROR_MSG(JSERR_NonObjectFromIterable, 5620, "Iterable provided to %s must not return non-object or null value.", "", kjstTypeError, 0)
+// 5621-5626 Unused
 RT_ERROR_MSG(JSERR_NeedConstructor, 5627, "'%s' is not a constructor", "Constructor expected", kjstTypeError, 0)
 
 RT_ERROR_MSG(VBSERR_CantDisplayDate, 32812, "", "The specified date is not available in the current locale's calendar", kjstRangeError, 0)

+ 3 - 0
lib/Runtime/Base/JnDirectFields.h

@@ -156,6 +156,7 @@ ENTRY(freeze)
 ENTRY(from)
 ENTRY(fromCharCode)
 ENTRY(fromCodePoint)
+ENTRY(fromEntries)
 ENTRY(function)
 ENTRY(Function)
 ENTRY(getDate)
@@ -611,6 +612,7 @@ ENTRY(InitInternalProperties)
 ENTRY(methodName)
 ENTRY(registerChakraLibraryFunction)
 ENTRY(registerFunction)
+ENTRY(staticMethod)
 ENTRY(toLength)
 ENTRY(toInteger)
 ENTRY(arraySpeciesCreate)
@@ -672,6 +674,7 @@ ENTRY(raiseNeedObject)
 ENTRY(raiseNeedObjectOfType)
 ENTRY(raiseNeedObjectOrString)
 ENTRY(raiseNotAConstructor)
+ENTRY(raiseNonObjectFromIterable)
 ENTRY(raiseObjectIsAlreadyInitialized)
 ENTRY(raiseObjectIsNonExtensible)
 ENTRY(raiseOptionValueOutOfRange_3)

+ 1 - 0
lib/Runtime/Library/EngineInterfaceObjectBuiltIns.h

@@ -58,6 +58,7 @@ BuiltInRaiseException1(TypeError, This_NullOrUndefined)
 BuiltInRaiseException1(TypeError, NotAConstructor)
 BuiltInRaiseException1(TypeError, ObjectIsNonExtensible)
 BuiltInRaiseException1(TypeError, LengthIsTooBig)
+BuiltInRaiseException1(TypeError, NonObjectFromIterable)
 BuiltInRaiseException2(TypeError, NeedObjectOfType)
 BuiltInRaiseException1(RangeError, InvalidCurrencyCode)
 BuiltInRaiseException(TypeError, MissingCurrencyCode)

+ 19 - 0
lib/Runtime/Library/JavascriptLibrary.cpp

@@ -3651,6 +3651,18 @@ namespace Js
             propertyCount++;
         }
 
+        if (scriptContext->GetConfig()->IsES7ValuesEntriesEnabled())
+        {
+            propertyCount += 2;
+        }
+
+#ifdef ENABLE_JS_BUILTINS
+        if (scriptContext->IsJsBuiltInEnabled())
+        {
+            propertyCount++;
+        }
+#endif
+
         typeHandler->Convert(objectConstructor, mode, propertyCount);
 
         library->AddMember(objectConstructor, PropertyIds::length, TaggedInt::ToVarUnchecked(1), PropertyConfigurable);
@@ -3707,6 +3719,13 @@ namespace Js
                 library->AddFunctionToLibraryObject(objectConstructor, PropertyIds::entries, &JavascriptObject::EntryInfo::Entries, 1));
         }
 
+#ifdef ENABLE_JS_BUILTINS
+        if (scriptContext->IsJsBuiltInEnabled())
+        {
+            library->EnsureBuiltInEngineIsReady();
+        }
+#endif
+
         objectConstructor->SetHasNoEnumerableProperties(true);
 
         return true;

+ 32 - 0
lib/Runtime/Library/JsBuiltIn/JsBuiltIn.js

@@ -17,6 +17,7 @@
         ArrayFlat: { className: "Array", methodName: "flat", argumentsCount: 0, forceInline: true /*optional*/ },
         ArrayFlatMap: { className: "Array", methodName: "flatMap", argumentsCount: 1, forceInline: true /*optional*/ },
         ArrayForEach: { className: "Array", methodName: "forEach", argumentsCount: 1, forceInline: true /*optional*/ },
+        ObjectFromEntries: { className: "Object", staticMethod: true, methodName: "fromEntries", argumentsCount: 1, forceInline: true /*optional*/ },
     };
 
     var setPrototype = platform.builtInSetPrototype;
@@ -39,10 +40,13 @@
     __chakraLibrary.ArrayIterator.prototype = CreateObject(iteratorPrototype);
     __chakraLibrary.raiseNeedObjectOfType = platform.raiseNeedObjectOfType;
     __chakraLibrary.raiseThis_NullOrUndefined = platform.raiseThis_NullOrUndefined;
+    __chakraLibrary.raiseNeedObject = platform.raiseNeedObject;
+    __chakraLibrary.raiseNonObjectFromIterable = platform.raiseNonObjectFromIterable;
     __chakraLibrary.raiseLengthIsTooBig = platform.raiseLengthIsTooBig;
     __chakraLibrary.raiseFunctionArgument_NeedFunction = platform.raiseFunctionArgument_NeedFunction;
     __chakraLibrary.callInstanceFunc = platform.builtInCallInstanceFunction;
     __chakraLibrary.functionBind = platform.builtInJavascriptFunctionEntryBind;
+    __chakraLibrary.objectDefineProperty = _objectDefineProperty;
 
     _objectDefineProperty(__chakraLibrary.ArrayIterator.prototype, 'next',
         // Object's getter and setter can get overriden on the prototype, in that case while setting the value attributes, we will end up with TypeError
@@ -440,4 +444,32 @@
 
         return undefined;
     });
+
+    platform.registerFunction(FunctionsEnum.ObjectFromEntries, function (iterable) {
+        // #sec-object.fromentries
+        "use strict";
+        if (iterable === null || iterable === undefined) {
+            __chakraLibrary.raiseNeedObject("Object.fromEntries");
+        }
+
+        const o = {};
+        const propDescriptor = {
+            enumerable : true,
+            configurable : true,
+            writable : true,
+            value : undefined
+        };
+
+        let key;
+        for (const entry of iterable) {
+            if (typeof entry !== "object" || entry === null) {
+                __chakraLibrary.raiseNonObjectFromIterable("Object.fromEntries");
+            }
+
+            key = entry[0];
+            propDescriptor.value = entry[1];
+            __chakraLibrary.objectDefineProperty(o, key, propDescriptor);
+        }
+        return o;
+    });
 });

+ 27 - 4
lib/Runtime/Library/JsBuiltInEngineInterfaceExtensionObject.cpp

@@ -224,14 +224,35 @@ namespace Js
         }
     }
 
-    DynamicObject* JsBuiltInEngineInterfaceExtensionObject::GetPrototypeFromName(Js::PropertyIds propertyId, ScriptContext* scriptContext)
+    DynamicObject* JsBuiltInEngineInterfaceExtensionObject::GetPrototypeFromName(Js::PropertyIds propertyId, bool staticMethod, ScriptContext* scriptContext)
     {
         JavascriptLibrary* library = scriptContext->GetLibrary();
 
+        if (staticMethod)
+        {
+            switch (propertyId) {
+            case PropertyIds::Array:
+                return library->arrayConstructor;
+
+            case PropertyIds::Object:
+                return library->objectConstructor;
+
+            case PropertyIds::String:
+                return library->stringConstructor;
+
+            default:
+                AssertOrFailFastMsg(false, "Unable to find a constructor that match with this className.");
+                return nullptr;
+            }
+        }
+
         switch (propertyId) {
         case PropertyIds::Array:
             return library->arrayPrototype;
 
+        case PropertyIds::Object:
+            return library->objectPrototype;
+
         case PropertyIds::String:
             return library->stringPrototype;
 
@@ -239,7 +260,7 @@ namespace Js
             return library->GetChakraLib();
 
         default:
-            AssertMsg(false, "Unable to find a prototype that match with this className.");
+            AssertOrFailFastMsg(false, "Unable to find a prototype that match with this className.");
             return nullptr;
         }
     }
@@ -301,7 +322,7 @@ namespace Js
         func->GetFunctionProxy()->SetIsPublicLibraryCode();
         func->GetFunctionProxy()->EnsureDeserialized()->SetDisplayName(methodName->GetString(), methodName->GetLength(), 0);
 
-        DynamicObject* chakraLibraryObject = GetPrototypeFromName(PropertyIds::__chakraLibrary, scriptContext);
+        DynamicObject* chakraLibraryObject = GetPrototypeFromName(PropertyIds::__chakraLibrary, false, scriptContext);
         PropertyIds functionIdentifier = JavascriptOperators::GetPropertyId(methodName, scriptContext);
 
         // Link the function to __chakraLibrary.
@@ -339,6 +360,7 @@ namespace Js
         Var argumentsCountProperty = JavascriptOperators::OP_GetProperty(funcInfo, Js::PropertyIds::argumentsCount, scriptContext);
         Var forceInlineProperty = JavascriptOperators::OP_GetProperty(funcInfo, Js::PropertyIds::forceInline, scriptContext);
         Var aliasProperty = JavascriptOperators::OP_GetProperty(funcInfo, Js::PropertyIds::alias, scriptContext);
+        Var staticMethodProperty = JavascriptOperators::OP_GetProperty(funcInfo, Js::PropertyIds::staticMethod, scriptContext);
 
         Assert(JavascriptString::Is(classNameProperty));
         Assert(JavascriptString::Is(methodNameProperty));
@@ -348,6 +370,7 @@ namespace Js
         JavascriptString* methodName = JavascriptString::FromVar(methodNameProperty);
         int argumentsCount = TaggedInt::ToInt32(argumentsCountProperty);
 
+        BOOL staticMethod = JavascriptConversion::ToBoolean(staticMethodProperty, scriptContext);
         BOOL forceInline = JavascriptConversion::ToBoolean(forceInlineProperty, scriptContext);
 
         JavascriptFunction* func = JavascriptFunction::FromVar(args.Values[2]);
@@ -356,7 +379,7 @@ namespace Js
         func->GetFunctionProxy()->SetIsPublicLibraryCode();
         func->GetFunctionProxy()->EnsureDeserialized()->SetDisplayName(methodName->GetString(), methodName->GetLength(), 0);
 
-        DynamicObject* prototype = GetPrototypeFromName(JavascriptOperators::GetPropertyId(className, scriptContext), scriptContext);
+        DynamicObject* prototype = GetPrototypeFromName(JavascriptOperators::GetPropertyId(className, scriptContext), staticMethod, scriptContext);
         PropertyIds functionIdentifier = methodName->BufferEquals(_u("Symbol.iterator"), 15)? PropertyIds::_symbolIterator :
             JavascriptOperators::GetPropertyId(methodName, scriptContext);
 

+ 1 - 1
lib/Runtime/Library/JsBuiltInEngineInterfaceExtensionObject.h

@@ -42,7 +42,7 @@ namespace Js
 
         void EnsureJsBuiltInByteCode(ScriptContext * scriptContext);
 
-        static DynamicObject* GetPrototypeFromName(Js::PropertyIds propertyId, ScriptContext* scriptContext);
+        static DynamicObject* GetPrototypeFromName(Js::PropertyIds propertyId, bool staticMethod, ScriptContext* scriptContext);
         static void RecordDefaultIteratorFunctions(Js::PropertyIds propertyId, ScriptContext * scriptContext, JavascriptFunction* iteratorFunc);
         static void RecordCommonNativeInterfaceBuiltIns(Js::PropertyIds propertyId, ScriptContext * scriptContext, JavascriptFunction * scriptFunction);
         static Var EntryJsBuiltIn_RegisterChakraLibraryFunction(RecyclableObject* function, CallInfo callInfo, ...);

+ 28 - 24
test/AsmJs/params.js

@@ -27,6 +27,8 @@ function wrapType(type, name) {
   }
 }
 
+let done = false;
+
 const tested = {};
 function test(n) {
   if (n in tested) {
@@ -80,36 +82,38 @@ const [forceTest] = WScript.Arguments;
 if (forceTest !== undefined) {
   const res = test(forceTest);
   print(res ? "Module is valid" : "Module is invalid");
-  WScript.Quit(0);
+  done = true;
 }
 
-let nParams = 8201;
-let inc = 100;
-let direction = true;
+if (done === false) {
+  let nParams = 8201;
+  let inc = 100;
+  let direction = true;
 
-while (inc !== 0) {
-  if (test(nParams)) {
-    if (direction) {
-      nParams += inc;
+  while (inc !== 0) {
+    if (test(nParams)) {
+      if (direction) {
+        nParams += inc;
+      } else {
+        direction = true;
+        inc >>= 1;
+        nParams += inc;
+      }
     } else {
-      direction = true;
-      inc >>= 1;
-      nParams += inc;
+      if (!direction) {
+        nParams -= inc;
+      } else {
+        direction = false;
+        inc >>= 1;
+        nParams -= inc;
+      }
     }
-  } else {
-    if (!direction) {
-      nParams -= inc;
-    } else {
-      direction = false;
-      inc >>= 1;
-      nParams -= inc;
+
+    if (nParams > 100000 || nParams < 0) {
+      print(`FAILED. Params reached ${nParams} long. Expected an error by now`);
+      break;
     }
   }
 
-  if (nParams > 100000 || nParams < 0) {
-    print(`FAILED. Params reached ${nParams} long. Expected an error by now`);
-    break;
-  }
+  print(`Support at most ${nParams} params`);
 }
-
-print(`Support at most ${nParams} params`);

+ 6 - 3
test/DebuggerCommon/ES6_intl_simple_attach.js.dbg.baseline

@@ -57,7 +57,8 @@
               "is": "function <large string>",
               "assign": "function <large string>",
               "values": "function <large string>",
-              "entries": "function <large string>"
+              "entries": "function <large string>",
+              "fromEntries": "function <large string>"
             },
             "hasOwnProperty": {
               "#__proto__": "function <large string>",
@@ -205,7 +206,8 @@
               "is": "function <large string>",
               "assign": "function <large string>",
               "values": "function <large string>",
-              "entries": "function <large string>"
+              "entries": "function <large string>",
+              "fromEntries": "function <large string>"
             },
             "hasOwnProperty": {
               "#__proto__": "function <large string>",
@@ -353,7 +355,8 @@
               "is": "function <large string>",
               "assign": "function <large string>",
               "values": "function <large string>",
-              "entries": "function <large string>"
+              "entries": "function <large string>",
+              "fromEntries": "function <large string>"
             },
             "hasOwnProperty": {
               "#__proto__": "function <large string>",

+ 2 - 1
test/DebuggerCommon/symbols.js.dbg.baseline

@@ -32,7 +32,8 @@
                 "is": "function <large string>",
                 "assign": "function <large string>",
                 "values": "function <large string>",
-                "entries": "function <large string>"
+                "entries": "function <large string>",
+                "fromEntries": "function <large string>"
               },
               "hasOwnProperty": {
                 "#__proto__": "function <large string>",

+ 213 - 0
test/Object/fromEntries.js

@@ -0,0 +1,213 @@
+//-------------------------------------------------------------------------------------------------------
+// Copyright (C) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
+//-------------------------------------------------------------------------------------------------------
+
+WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
+
+function verifyProperties(obj, property, value)
+{
+    const descriptor = Object.getOwnPropertyDescriptor(obj, property);
+    assert.areEqual(value, obj[property], "Object.fromEntries should set correct valued");
+    assert.isTrue(descriptor.enumerable, "Object.fromEntries should create enumerable properties");
+    assert.isTrue(descriptor.configurable, "Object.fromEntries should create configurable properties");
+    assert.isTrue(descriptor.writable, "Object.fromEntries should create writable properties");
+    obj[property] = "other value";
+    assert.areEqual("other value", obj[property], "should actually be able to write to properties created by Object.fromEntries");
+    assert.doesNotThrow(()=>{"use strict"; delete obj[property];}, "deleting properties created by Object.fromEntries should not throw");
+    assert.isUndefined(obj[property], "deleting properties created by Object.fromEntries should succeed");
+}
+
+function verifyObject(expected, actual)
+{
+    for (let i in actual)
+    {
+        assert.isTrue(expected.hasOwnProperty(i), "Object.fromEntries shouldn't create unexpected properties");
+    }
+    for (let i in expected)
+    {
+        verifyProperties(actual, i, expected[i]);
+    }
+}
+
+
+const tests = [
+    {
+        name : "Object.fromEntries invalid parameters",
+        body : function () {
+            assert.throws(()=>{Object.fromEntries(null);}, TypeError, "Object.fromEntries throws when called with null parameter");
+            assert.throws(()=>{Object.fromEntries(undefined);}, TypeError, "Object.fromEntries throws when called with undefined parameter");
+            assert.throws(()=>{Object.fromEntries("something");}, TypeError, "Object.fromEntries throws when called with string literal parameter");
+            assert.throws(()=>{Object.fromEntries(456);}, TypeError, "Object.fromEntries throws when called with number literal parameter");
+            assert.throws(()=>{Object.fromEntries(Number());}, TypeError, "Object.fromEntries throws when called with Number Object parameter");
+            assert.doesNotThrow(()=>{Object.fromEntries(String());}, "Object.fromEntries does not throw when called with String Object parameter with length 0");
+            assert.throws(()=>{Object.fromEntries(String("anything"));}, TypeError, "Object.fromEntries throws when called with String Object parameter with length > 0");
+            assert.throws(()=>{Object.fromEntries({});}, TypeError, "Object.fromEntries throws when called with Object literal");
+            assert.throws(()=>{Object.fromEntries({a : "5", b : "10"});}, TypeError, "Object.fromEntries throws when called with Object literal");
+        }
+    },
+    {
+        name : "Object.fromEntries basic cases",
+        body : function () {
+            const obj1 = Object.fromEntries([["first", 50], ["second", 30], ["third", 60], ["fourth", 70]]);
+            verifyObject({first : 50, second : 30, third : 60, fourth : 70}, obj1);
+            const obj2 =  Object.fromEntries([Object("a12234"),Object("b2kls"),Object("c3deg")]);
+            verifyObject({a : "1", b : "2", c : "3"}, obj2);
+            function testArguments()
+            {
+                verifyObject(expected, Object.fromEntries(arguments));
+            }
+            const expected = { abc : "one", bcd : "two", hsa : "three"};
+            testArguments(["abc", "one"], ["bcd", "two"], ["hsa", "three"]);
+        }
+    },
+    {
+        name : "Object.fromEntries with array-like object",
+        body : function ()
+        {
+            const arrayLike = {0 : ["abc", "one"], 1 : ["bcd", "two"], 2 : ["hsa", "three"], length : 3, current : 0}
+            assert.throws(()=>{ Object.fromEntries(arrayLike); }, TypeError, "Object.fromEntries throws when parameter has no iterator");
+            arrayLike[Symbol.iterator] = function () {
+                const array = this;
+                return  {
+                    next : function () {
+                        const value = array[String(array.current)];
+                        ++array.current;
+                        return {
+                            value : value,
+                            done : array.length < array.current
+                        };
+                    }
+                };
+            };
+            verifyObject({ abc : "one", bcd : "two", hsa : "three"}, Object.fromEntries(arrayLike));
+        }
+    },
+    {
+        name : "Object.fromEntries does not call setters",
+        body : function () {
+            let calledSet = false;
+            Object.defineProperty(Object.prototype, "prop", {
+                set : function () { calledSet = true; }
+            });
+            const obj = Object.fromEntries([["prop", 10]]);
+            verifyProperties(obj, "prop", 10);
+            assert.isFalse(calledSet, "Object.fromEntries should not call setters");
+        }
+    },
+    {
+        name : "Object.fromEntries iterates over generators",
+        body : function () {
+            function* gen1 ()
+            {
+                yield ["val1", 10];
+                yield ["val2", 50];
+                yield ["val3", 60, "other stuff"];
+            }
+            const obj = Object.fromEntries(gen1());
+            verifyObject({val1 : 10, val2 : 50, val3: 60}, obj);
+            let unreachable = false;
+            function* gen2 ()
+            {
+                yield ["val1", 10];
+                yield "val2";
+                unreachable = true;
+                yield ["val3", 60, "other stuff"];
+            }
+            assert.throws(()=>{Object.fromEntries(gen2())}, TypeError, "When generator provides invalid case Object.fromEntries should throw");
+            assert.isFalse(unreachable, "Object.fromEntries does not continue after invalid case provided");
+        }
+    },
+    {
+        name : "Object.fromEntries accesses properties in correct order from generator",
+        body : function () {
+            const accessedProps = [];
+            const handler = {
+                get : function (target, prop, receiver) {
+                    accessedProps.push(prop + Reflect.get(target, prop));
+                    return Reflect.get(target, prop);
+                }
+            }
+
+            function* gen () {
+                yield new Proxy(["a", "b", "c"], handler);
+                yield new Proxy(["e", "g", "h", "j"], handler);
+            }
+            const obj = Object.fromEntries(gen());
+            verifyObject({a : "b", e : "g"}, obj);
+            const expected = ["0a", "1b", "0e", "1g"];
+            const len = accessedProps.length;
+            assert.areEqual(4, len, "Object.fromEntries accesses correct number of properties");
+            for (let i = 0; i < len; ++i)
+            {
+                assert.areEqual(expected[i], accessedProps[i], "Object.fromEntries accesses the correct properties");
+            }
+        }
+    },
+    {
+        name : "Object.fromEntries accesses Proxy properties correctly",
+        body : function () {
+            const accessedProps = [];
+            const handler = {
+                get : function (target, prop, receiver) {
+                    accessedProps.push(String(prop));
+                    return Reflect.get(target, prop);
+                },
+                set : function () {
+                    throw new Error ("Should not be called");
+                }
+            }
+            let result;
+            assert.doesNotThrow(()=>{result = Object.fromEntries(new Proxy([["a", 5], ["b", 2], ["c", 4]], handler)); });
+            verifyObject({a : 5, b : 2, c : 4}, result);
+            expected = ["Symbol(Symbol.iterator)", "length", "0", "length", "1", "length", "2", "length"];
+            for (let i = 0; i < 3; ++i)
+            {
+                assert.areEqual(expected[i], accessedProps[i], "Object.fromEntries accesses the correct properties");
+            }
+        }
+    },
+    {
+        name : "Object.fromEntries uses overridden array iterator",
+        body : function () {
+            let calls = 0;
+            Array.prototype[Symbol.iterator] = function () {
+                return {
+                    next : function () {
+                        switch (calls)
+                        {
+                            case 0:
+                                calls = 1;
+                                return { done : false, value : ["key", "value"]}
+                            case 1:
+                                calls = 2;
+                                return { done : true, value : null }
+                            case 2:
+                            throw new Error ("Should not be reached");
+                        }
+                    }
+                }
+            }
+            let result;
+            assert.doesNotThrow(()=>{ result = Object.fromEntries([1, 2, 3, 4]);}, "Once iterator is done should not be called again");
+            verifyObject({key : "value"}, result);
+        }
+    },
+    {
+        name : "Object.fromEntries properties",
+        body : function () {
+            assert.areEqual("fromEntries", Object.fromEntries.name, "Object.fromEntries.name should be 'fromEntries'");
+            assert.areEqual(1, Object.fromEntries.length, "Object.fromEntries.length should be 1");
+            const descriptor = Object.getOwnPropertyDescriptor(Object, "fromEntries");
+            assert.isFalse(descriptor.enumerable, "Object.fromEntries should be enumerable");
+            assert.isTrue(descriptor.writable, "Object.fromEntries should be writable");
+            assert.isTrue(descriptor.configurable, "Object.fromEntries should be configurable");
+            assert.doesNotThrow(()=>{"use strict"; delete Object.fromEntries.length;}, "Deleting Object.fromEntries.length should succeed");
+            assert.areEqual(0, Object.fromEntries.length, "After deletion Object.fromEntries.length should be 0");
+            assert.doesNotThrow(()=>{"use strict"; delete Object.fromEntries;}, "Deleting Object.fromEntries should succeed");
+            assert.isUndefined(Object.fromEntries, "After deletion Object.fromEntries should be undefined");
+        }
+    }
+];
+
+testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });

+ 6 - 0
test/Object/rlexe.xml

@@ -24,6 +24,12 @@
       <baseline>hasOwnProperty.baseline</baseline>
     </default>
   </test>
+  <test>
+    <default>
+      <files>fromEntries.js</files>
+      <compile-flags>-args summary -endargs</compile-flags>
+    </default>
+  </test>
   <test>
     <default>
       <files>isEnumerable.js</files>