blob: d9381332a0858c311dc2c004194244b7b0d83bfb [file] [log] [blame]
// This test suite compares the behavior of setting the prototype on various values
// (using Object.setPrototypeOf(), obj.__proto__ assignment, and Reflect.setPrototypeOf())
// against what is specified in the ES spec. The expected behavior specified according
// to the spec is expressed in expectationsForObjectSetPrototypeOf,
// expectationsForSetUnderscoreProto, and expectationsForReflectSetPrototypeOf.
import * as namespace from "./namespace-prototype-assignment.js"
var inBrowser = (typeof window != "undefined");
// Test configuration options:
var verbose = false;
var maxIterations = 1;
var throwOnEachError = true;
var testUndefined = true;
var testNull = true;
var testTrue = true;
var testFalse = true;
var testNumbers = true;
var testString = true;
var testSymbol = true;
var testObject = true;
var testGlobal = true;
var testWindowProtos = inBrowser;
var engine;
//====================================================================================
// Error messages:
if (inBrowser) {
let userAgent = navigator.userAgent;
if (userAgent.match(/ Chrome\/[0-9]+/)) engine = "chrome";
else if (userAgent.match(/ Firefox\/[0-9]+/)) engine = "default";
else engine = "safari";
} else
engine = "jsc";
// Set default error messages and then override with engine specific ones below.
var DefaultTypeError = "TypeError: ";
var CannotSetPrototypeOfImmutablePrototypeObject = DefaultTypeError;
var CannotSetPrototypeOfThisObject = DefaultTypeError;
var CannotSetPrototypeOfUndefinedOrNull = DefaultTypeError;
var CannotSetPrototypeOfNonObject = DefaultTypeError;
var PrototypeValueCanOnlyBeAnObjectOrNull = DefaultTypeError;
var ObjectProtoCalledOnNullOrUndefinedError = DefaultTypeError;
var ReflectSetPrototypeOfRequiresTheFirstArgumentBeAnObject = DefaultTypeError;
var ReflectSetPrototypeOfRequiresTheSecondArgumentBeAnObjectOrNull = DefaultTypeError;
if (engine == "jsc" || engine === "safari") {
CannotSetPrototypeOfImmutablePrototypeObject = "TypeError: Cannot set prototype of immutable prototype object";
CannotSetPrototypeOfThisObject = "TypeError: Cannot set prototype of this object";
CannotSetPrototypeOfUndefinedOrNull = "TypeError: Cannot set prototype of undefined or null";
CannotSetPrototypeOfNonObject = "TypeError: Cannot set prototype of non-object";
PrototypeValueCanOnlyBeAnObjectOrNull = "TypeError: Prototype value can only be an object or null";
ObjectProtoCalledOnNullOrUndefinedError = "TypeError: Object.prototype.__proto__ called on null or undefined";
ReflectSetPrototypeOfRequiresTheFirstArgumentBeAnObject = "TypeError: Reflect.setPrototypeOf requires the first argument be an object";
ReflectSetPrototypeOfRequiresTheSecondArgumentBeAnObjectOrNull = "TypeError: Reflect.setPrototypeOf requires the second argument be either an object or null";
} else if (engine === "chrome") {
CannotSetPrototypeOfImmutablePrototypeObject = "TypeError: Immutable prototype object ";
}
//====================================================================================
// Utility functions:
if (inBrowser)
print = console.log;
function reportError(errorMessage) {
if (throwOnEachError)
throw errorMessage;
else
print(errorMessage);
}
function shouldEqual(testID, resultType, actual, expected) {
if (actual != expected)
reportError("ERROR in " + resultType
+ ": expect " + stringify(expected) + ", actual " + stringify(actual)
+ " in test: " + testID.signature + " on iteration " + testID.iteration);
}
function shouldThrow(testID, resultType, actual, expected) {
let actualStr = "" + actual;
if (!actualStr.startsWith(expected))
reportError("ERROR in " + resultType
+ ": expect " + expected + ", actual " + actual
+ " in test: " + testID.signature + " on iteration " + testID.iteration);
}
function stringify(value) {
if (typeof value == "string") return '"' + value + '"';
if (typeof value == "symbol") return value.toString();
if (value === origGlobalProto) return "origGlobalProto";
if (value === origObjectProto) return "origObjectProto";
if (value === newObjectProto) return "newObjectProto";
if (value === proxyObject) return "proxyObject";
if (value === null) return "null";
if (typeof value == "object") return "object";
return "" + value;
}
function makeTestID(index, iteration, targetName, protoToSet, protoSetter, expected) {
let testID = {};
testID.signature = "[" + index + "] "
+ protoSetter.actionName + "|"
+ targetName + "|"
+ stringify(protoToSet) + "|"
+ stringify(expected.result) + "|"
+ stringify(expected.proto) + "|"
+ stringify(expected.exception);
testID.iteration = iteration;
return testID;
}
//====================================================================================
// Functions to express the expectations of the ES specification:
function doInternalSetPrototypeOf(result, target, origProto, newProto) {
if (!target.setPrototypeOf) {
result.success = true;
result.exception = undefined;
return;
}
target.setPrototypeOf(result, origProto, newProto);
}
// 9.1.2.1 OrdinarySetPrototypeOf ( O, V )
// https://tc39.github.io/ecma262/#sec-ordinarysetprototypeof
function ordinarySetPrototypeOf(result, currentProto, newProto) {
// 9.1.2.1-4 If SameValue(V, current) is true, return true.
if (newProto === currentProto) {
result.success = true;
return;
}
// 9.1.2.1-5 [extensibility check not tested here]
// 9.1.2.1-8 [cycle check not tested here]
result.success = true;
}
// 9.4.7.2 SetImmutablePrototype ( O, V )
// https://tc39.github.io/ecma262/#sec-set-immutable-prototype
function setImmutablePrototype(result, currentProto, newProto) {
if (newProto === currentProto) {
result.success = true;
return;
}
result.success = false;
result.exception = CannotSetPrototypeOfImmutablePrototypeObject;
}
// HTML spec: 7.4.2 [[SetPrototypeOf]] ( V )
// https://html.spec.whatwg.org/#windowproxy-setprototypeof
function windowProxySetPrototypeOf(result, currentProto, newProto) {
result.success = false;
result.exception = CannotSetPrototypeOfThisObject;
}
var count = 0;
function initSetterExpectation(target, newProto) {
var targetValue = target.value();
var origProto = undefined;
if (targetValue != null && targetValue != undefined)
origProto = targetValue.__proto__; // Default to old proto.
var expected = {};
expected.targetValue = targetValue;
expected.origProto = origProto;
expected.exception = undefined;
expected.proto = origProto;
expected.result = undefined;
return expected;
}
// 19.1.2.21 Object.setPrototypeOf ( O, proto )
// https://tc39.github.io/ecma262/#sec-object.setprototypeof
function objectSetPrototypeOf(target, newProto) {
let expected = initSetterExpectation(target, newProto);
var targetValue = expected.targetValue;
var origProto = expected.origProto;
function throwIfNoExceptionPending(e) {
if (!expected.exception)
expected.exception = e;
}
// 19.1.2.21-1 Let O be ? RequireObjectCoercible(O).
if (targetValue == undefined || targetValue == null)
throwIfNoExceptionPending(CannotSetPrototypeOfUndefinedOrNull);
// 19.1.2.21-2 If Type(proto) is neither Object nor Null, throw a TypeError exception.
if (typeof newProto != "object")
throwIfNoExceptionPending(PrototypeValueCanOnlyBeAnObjectOrNull);
// 19.1.2.21-3 If Type(O) is not Object, return O.
else if (typeof targetValue != "object")
expected.result = targetValue;
// 19.1.2.21-4 Let status be ? O.[[SetPrototypeOf]](proto).
else {
// 19.1.2.21-5 If status is false, throw a TypeError exception.
let result = {};
doInternalSetPrototypeOf(result, target, origProto, newProto);
if (result.success)
expected.proto = newProto;
else
throwIfNoExceptionPending(result.exception);
// 19.1.2.21-6 Return O.
expected.result = targetValue;
}
return expected;
}
objectSetPrototypeOf.action = (obj, newProto) => Object.setPrototypeOf(obj, newProto);
objectSetPrototypeOf.actionName = "Object.setPrototypeOf";
// B.2.2.1.2 set Object.prototype.__proto__
// https://tc39.github.io/ecma262/#sec-set-object.prototype.__proto__
function setUnderscoreProto(target, newProto) {
let expected = initSetterExpectation(target, newProto);
var targetValue = expected.targetValue;
var origProto = expected.origProto;
function throwIfNoExceptionPending(e) {
if (!expected.exception)
expected.exception = e;
}
// B.2.2.1.2-1 Let O be ? RequireObjectCoercible(this value).
if (targetValue == undefined || targetValue == null)
throwIfNoExceptionPending(DefaultTypeError);
// B.2.2.1.2-2 If Type(proto) is neither Object nor Null, return undefined.
if (typeof newProto != "object")
expected.result = undefined;
// B.2.2.1.2-3 If Type(O) is not Object, return undefined.
else if (typeof targetValue != "object")
expected.result = undefined;
// B.2.2.1.2-4 Let status be ? O.[[SetPrototypeOf]](proto).
else {
// B.2.2.1.2-5 If status is false, throw a TypeError exception.
let result = {};
doInternalSetPrototypeOf(result, target, origProto, newProto);
if (result.success)
expected.proto = newProto;
else
throwIfNoExceptionPending(result.exception);
// B.2.2.1.2-6 Return undefined.
expected.result = undefined;
}
// Override the result to be the newProto value because the statement obj.__proto__ = value
// will produce the rhs value, not the result of the obj.__proto__ setter.
expected.result = newProto;
return expected;
}
setUnderscoreProto.action = (obj, newProto) => (obj.__proto__ = newProto);
setUnderscoreProto.actionName = "obj.__proto__";
// 26.1.13 Reflect.setPrototypeOf ( target, proto )
// https://tc39.github.io/ecma262/#sec-reflect.setprototypeof
// function expectationsForReflectSetPrototypeOf(target, newProto, targetExpectation) {
function reflectSetPrototypeOf(target, newProto) {
let expected = initSetterExpectation(target, newProto);
var targetValue = expected.targetValue;
var origProto = expected.origProto;
function throwIfNoExceptionPending(e) {
if (!expected.exception)
expected.exception = e;
}
// 26.1.13-1 If Type(target) is not Object, throw a TypeError exception.
if (targetValue === null || typeof targetValue != "object")
throwIfNoExceptionPending(ReflectSetPrototypeOfRequiresTheFirstArgumentBeAnObject);
// 26.1.13-2 If Type(proto) is not Object and proto is not null, throw a TypeError exception.
if (typeof newProto != "object")
throwIfNoExceptionPending(ReflectSetPrototypeOfRequiresTheSecondArgumentBeAnObjectOrNull);
// 26.1.13-3 Return ? target.[[SetPrototypeOf]](proto).
let result = {};
doInternalSetPrototypeOf(result, target, origProto, newProto);
expected.result = result.success;
if (result.success)
expected.proto = newProto;
return expected;
}
reflectSetPrototypeOf.action = (obj, newProto) => Reflect.setPrototypeOf(obj, newProto);
reflectSetPrototypeOf.actionName = "Reflect.setPrototypeOf";
//====================================================================================
// Test Data:
var global = new Function('return this')();
var origGlobalProto = global.__proto__;
var origObjectProto = {}.__proto__;
var proxyObject = new Proxy({ }, {
setPrototypeOf(target, value) {
throw "Thrown from proxy";
}
});
var newObjectProto = { toString() { return "newObjectProto"; } };
var origNamespaceProto = namespace.__proto__;
var targets = [];
targets.push({
name: "namespace",
value: () => origNamespaceProto,
setPrototypeOf: setImmutablePrototype
});
var newProtos = [
undefined,
null,
true,
false,
0,
11,
123.456,
"doh",
Symbol("doh"),
{},
origObjectProto,
origGlobalProto,
newObjectProto
];
var protoSetters = [
objectSetPrototypeOf,
setUnderscoreProto,
reflectSetPrototypeOf,
];
//====================================================================================
// Test driver functions:
function test(testID, targetValue, newProto, setterAction, expected) {
let exception = undefined;
let result = undefined;
try {
result = setterAction(targetValue, newProto);
} catch (e) {
exception = e;
}
shouldThrow(testID, "exception", exception, expected.exception);
if (!expected.exception) {
shouldEqual(testID, "__proto__", targetValue.__proto__, expected.proto);
shouldEqual(testID, "result", result, expected.result);
}
}
function runTests() {
let testIndex = 0;
for (let protoSetter of protoSetters) {
for (let target of targets) {
for (let newProto of newProtos) {
let currentTestIndex = testIndex++;
for (var i = 0; i < maxIterations; i++) {
let expected = protoSetter(target, newProto);
let targetValue = expected.targetValue;
let testID = makeTestID(currentTestIndex, i, target.name, newProto, protoSetter, expected);
if (verbose && i == 0)
print("test: " + testID.signature);
test(testID, targetValue, newProto, protoSetter.action, expected);
}
}
}
}
}
runTests();