Array.fromAsync various remaining coverage (#3809)

This contains a few more tests for Array.fromAsync, in addition to what
has already been merged and what is under review at #3791.

This covers the following items from the testing plan at #3725:

- Success cases
  - Creates promise
  - Create new array/arraylike in promise (with length = length property)
- Input
  - Invalid input values
    - nonconforming object (arraylike without length, missing keys)
  - Covered by polyfill tests
    - Result promise rejects if length access fails (non-iterable input)
    - Unaffected by globalThis.Symbol mutation (non-iterable)
- this-value
  - this-value is a constructor
  - this-value is not a constructor
  - If this is a constructor, and items doesn't have a Symbol.iterator,
    returns a new instance of this
  - Iterator closed when property creation on this fails
  - Returned promise rejects when ^
- Other tests
  - Error is thrown for all CreateDataProperty fails
  - Non-writable properties are overwritten by CreateDataProperty
  - Input with missing values

Co-authored-by: Ms2ger <Ms2ger@igalia.com>
This commit is contained in:
Philip Chimento 2023-04-04 02:20:25 -07:00 committed by GitHub
parent daefac0814
commit 92500bfffb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 611 additions and 0 deletions

View File

@ -0,0 +1,24 @@
// Copyright (C) 2023 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-array.fromasync
description: Array-like object with holes treats the holes as undefined
info: |
3.k.vii.2. Let _kValue_ be ? Get(_arrayLike_, _Pk_).
features: [Array.fromAsync]
flags: [async]
includes: [asyncHelpers.js, compareArray.js]
---*/
asyncTest(async function () {
const arrayLike = Object.create(null);
arrayLike.length = 5;
arrayLike[0] = 0;
arrayLike[1] = 1;
arrayLike[2] = 2;
arrayLike[4] = 4;
const array = await Array.fromAsync(arrayLike);
assert.compareArray(array, [0, 1, 2, undefined, 4], "holes in array-like treated as undefined");
});

View File

@ -0,0 +1,25 @@
// Copyright (C) 2023 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-array.fromasync
description: Rejects on array-like object whose length cannot be gotten
info: |
3.k.iii. Let _len_ be ? LengthOfArrayLike(_arrayLike_).
features: [Array.fromAsync]
flags: [async]
includes: [asyncHelpers.js]
---*/
asyncTest(async function () {
await assert.throwsAsync(Test262Error, () => Array.fromAsync({
get length() {
throw new Test262Error('accessing length property fails');
}
}), "Promise should be rejected if array-like length getter throws");
await assert.throwsAsync(TypeError, () => Array.fromAsync({
length: 1n,
0: 0
}), "Promise should be rejected if array-like length can't be converted to a number");
});

View File

@ -0,0 +1,31 @@
// Copyright (C) 2023 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-array.fromasync
description: >
Promise is rejected if the length of the array-like to copy is out of range
info: |
j. If _iteratorRecord_ is not *undefined*, then
...
k. Else,
...
iv. If IsConstructor(_C_) is *true*, then
...
v. Else,
1. Let _A_ be ? ArrayCreate(_len_).
ArrayCreate, step 1:
1. If _length_ > 2³² - 1, throw a *RangeError* exception.
includes: [asyncHelpers.js]
flags: [async]
features: [Array.fromAsync]
---*/
asyncTest(async function () {
const notConstructor = {};
await assert.throwsAsync(RangeError, () => Array.fromAsync.call(notConstructor, {
length: 4294967296 // 2³²
}), "Array-like with excessive length");
});

View File

@ -0,0 +1,23 @@
// Copyright (C) 2023 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-array.fromasync
description: >
Treats an asyncItems object that isn't an array-like as a 0-length array-like
info: |
3.k.iii. Let _len_ be ? LengthOfArrayLike(_arrayLike_).
features: [Array.fromAsync]
flags: [async]
includes: [asyncHelpers.js, compareArray.js]
---*/
asyncTest(async function () {
const notArrayLike = Object.create(null);
notArrayLike[0] = 0;
notArrayLike[1] = 1;
notArrayLike[2] = 2;
const array = await Array.fromAsync(notArrayLike);
assert.compareArray(array, [], "non-array-like object is treated as 0-length array-like");
});

View File

@ -0,0 +1,40 @@
// Copyright (C) 2023 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-array.fromasync
description: >
Use the intrinsic @@iterator and @@asyncIterator to check iterability
includes: [compareArray.js, asyncHelpers.js]
flags: [async]
features: [Array.fromAsync]
---*/
asyncTest(async function () {
// Replace the user-reachable Symbol.iterator and Symbol.asyncIterator with
// fake symbol keys
const originalSymbol = globalThis.Symbol;
const fakeIteratorSymbol = Symbol("iterator");
const fakeAsyncIteratorSymbol = Symbol("asyncIterator");
globalThis.Symbol = {
iterator: fakeIteratorSymbol,
asyncIterator: fakeAsyncIteratorSymbol,
};
const input = {
length: 3,
0: 0,
1: 1,
2: 2,
[fakeIteratorSymbol]() {
throw new Test262Error("The fake Symbol.iterator method should not be called");
},
[fakeAsyncIteratorSymbol]() {
throw new Test262Error("The fake Symbol.asyncIterator method should not be called");
}
};
const output = await Array.fromAsync(input);
assert.compareArray(output, [0, 1, 2]);
globalThis.Symbol = originalSymbol;
});

View File

@ -0,0 +1,22 @@
// Copyright (C) 2023 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-array.fromasync
description: >
Array.fromAsync returns a Promise that resolves to an Array in the normal case
info: |
1. Let _C_ be the *this* value.
...
3.e. If IsConstructor(_C_) is *true*, then
i. Let _A_ be ? Construct(_C_).
features: [Array.fromAsync]
flags: [async]
includes: [asyncHelpers.js]
---*/
asyncTest(async function () {
const promise = Array.fromAsync([0, 1, 2]);
const array = await promise;
assert(Array.isArray(array), "Array.fromAsync returns a Promise that resolves to an Array");
});

View File

@ -0,0 +1,35 @@
// Copyright (C) 2023 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-array.fromasync
description: Array.fromAsync returns a Promise
info: |
5. Return _promiseCapability_.[[Promise]].
flags: [async]
includes: [asyncHelpers.js]
features: [Array.fromAsync]
---*/
asyncTest(async function () {
let p = Array.fromAsync([0, 1, 2]);
assert(p instanceof Promise, "Array.fromAsync returns a Promise");
assert.sameValue(
Object.getPrototypeOf(p),
Promise.prototype,
"Array.fromAsync returns an object with prototype Promise.prototype"
);
p = Array.fromAsync([0, 1, 2], () => {
throw new Test262Error("this will make the Promise reject");
})
assert(p instanceof Promise, "Array.fromAsync returns a Promise even on error");
assert.sameValue(
Object.getPrototypeOf(p),
Promise.prototype,
"Array.fromAsync returns an object with prototype Promise.prototype even on error"
);
await assert.throwsAsync(Test262Error, () => p, "Prevent unhandled rejection");
});

View File

@ -0,0 +1,75 @@
// Copyright (C) 2023 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-array.fromasync
description: >
Order of user-observable operations on a custom this-value and its instances
includes: [compareArray.js, asyncHelpers.js]
flags: [async]
features: [Array.fromAsync]
---*/
function formatPropertyName(propertyKey, objectName = "") {
switch (typeof propertyKey) {
case "symbol":
if (Symbol.keyFor(propertyKey) !== undefined) {
return `${objectName}[Symbol.for('${Symbol.keyFor(propertyKey)}')]`;
} else if (propertyKey.description.startsWith('Symbol.')) {
return `${objectName}[${propertyKey.description}]`;
} else {
return `${objectName}[Symbol('${propertyKey.description}')]`
}
case "string":
if (propertyKey !== String(Number(propertyKey)))
return objectName ? `${objectName}.${propertyKey}` : propertyKey;
// fall through
default:
// integer or string integer-index
return `${objectName}[${propertyKey}]`;
}
}
asyncTest(async function () {
const expectedCalls = [
"construct MyArray",
"defineProperty A[0]",
"defineProperty A[1]",
"set A.length"
];
const actualCalls = [];
function MyArray(...args) {
actualCalls.push("construct MyArray");
return new Proxy(Object.create(null), {
set(target, key, value) {
actualCalls.push(`set ${formatPropertyName(key, "A")}`);
return Reflect.set(target, key, value);
},
defineProperty(target, key, descriptor) {
actualCalls.push(`defineProperty ${formatPropertyName(key, "A")}`);
return Reflect.defineProperty(target, key, descriptor);
}
});
}
let result = await Array.fromAsync.call(MyArray, [1, 2]);
assert.compareArray(expectedCalls, actualCalls, "order of operations for array argument");
actualCalls.splice(0); // reset
// Note https://github.com/tc39/proposal-array-from-async/issues/35
const expectedCallsForArrayLike = [
"construct MyArray",
"construct MyArray",
"defineProperty A[0]",
"defineProperty A[1]",
"set A.length"
];
result = await Array.fromAsync.call(MyArray, {
length: 2,
0: 1,
1: 2
});
assert.compareArray(expectedCallsForArrayLike, actualCalls, "order of operations for array-like argument");
});

View File

@ -0,0 +1,33 @@
// Copyright (C) 2023 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-array.fromasync
description: >
Rejects the promise if setting the length fails on an instance of a custom
this-value
info: |
3.j.ii.4.a. Perform ? Set(_A_, *"length"*, 𝔽(_k_), *true*).
...
3.k.viii. Perform ? Set(_A_, *"length"*, 𝔽(_len_), *true*)
includes: [asyncHelpers.js]
flags: [async]
features: [Array.fromAsync]
---*/
asyncTest(async function () {
class MyArray {
set length(v) {
throw new Test262Error("setter of length property throws")
}
}
await assert.throwsAsync(Test262Error, () => Array.fromAsync.call(MyArray, [0, 1, 2]), "Promise rejected if setting length fails");
await assert.throwsAsync(Test262Error, () => Array.fromAsync.call(MyArray, {
length: 3,
0: 0,
1: 1,
2: 2
}), "Promise rejected if setting length from array-like fails");
});

View File

@ -0,0 +1,50 @@
// Copyright (C) 2023 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-array.fromasync
description: >
Overwrites non-writable element properties on an instance of a custom
this-value
info: |
3.j.ii.8. Let _defineStatus_ be CreateDataPropertyOrThrow(_A_, _Pk_, _mappedValue_).
...
3.k.vii.6. Perform ? CreateDataPropertyOrThrow(_A_, _Pk_, _mappedValue_).
includes: [asyncHelpers.js]
flags: [async]
features: [Array.fromAsync]
---*/
asyncTest(async function () {
function MyArray() {
this.length = 4;
for (let ix = 0; ix < this.length; ix++) {
Object.defineProperty(this, ix, {
enumerable: true,
writable: false,
configurable: true,
value: 99
});
}
}
let result = await Array.fromAsync.call(MyArray, [0, 1, 2]);
assert.sameValue(result.length, 3, "Length property is overwritten");
assert.sameValue(result[0], 0, "Read-only element 0 is overwritten");
assert.sameValue(result[1], 1, "Read-only element 1 is overwritten");
assert.sameValue(result[2], 2, "Read-only element 2 is overwritten");
assert.sameValue(result[3], 99, "Element 3 is not overwritten");
result = await Array.fromAsync.call(MyArray, {
length: 3,
0: 0,
1: 1,
2: 2,
3: 3 // ignored
});
assert.sameValue(result.length, 3, "Length property is overwritten");
assert.sameValue(result[0], 0, "Read-only element 0 is overwritten");
assert.sameValue(result[1], 1, "Read-only element 1 is overwritten");
assert.sameValue(result[2], 2, "Read-only element 2 is overwritten");
assert.sameValue(result[3], 99, "Element 3 is not overwritten");
});

View File

@ -0,0 +1,38 @@
// Copyright (C) 2023 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-array.fromasync
description: >
Promise is rejected if length property on an instance of a custom this-value
is non-writable
info: |
3.j.ii.4.a. Perform ? Set(_A_, *"length"*, 𝔽(_k_), *true*).
...
3.k.viii. Perform ? Set(_A_, *"length"*, 𝔽(_len_), *true*).
Note that there is no difference between strict mode and sloppy mode, because
we are not following runtime evaluation semantics.
includes: [asyncHelpers.js]
flags: [async]
features: [Array.fromAsync]
---*/
asyncTest(async function () {
function MyArray() {
Object.defineProperty(this, "length", {
enumerable: true,
writable: false,
configurable: true,
value: 99
});
}
await assert.throwsAsync(TypeError, () => Array.fromAsync.call(MyArray, [0, 1, 2]), "Setting read-only length fails");
await assert.throwsAsync(TypeError, () => Array.fromAsync.call(MyArray, {
length: 3,
0: 0,
1: 1,
2: 2
}), "Setting read-only length fails in array-like case");
});

View File

@ -0,0 +1,43 @@
// Copyright (C) 2023 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-array.fromasync
description: >
Closes an async iterator if setting an element fails on an instance of a
custom this-value
info: |
3.j.ii.8. Let _defineStatus_ be CreateDataPropertyOrThrow(_A_, _Pk_, _mappedValue_).
9. If _defineStatus_ is an abrupt completion, return ? AsyncIteratorClose(_iteratorRecord_, _defineStatus_).
includes: [asyncHelpers.js]
flags: [async]
features: [Array.fromAsync]
---*/
asyncTest(async function () {
function MyArray() {
Object.defineProperty(this, 0, {
enumerable: true,
writable: true,
configurable: false,
value: 0
});
}
let closed = false;
const iterator = {
next() {
return Promise.resolve({ value: 1, done: false });
},
return() {
closed = true;
return Promise.resolve({ done: true });
},
[Symbol.asyncIterator]() {
return this;
}
}
await assert.throwsAsync(TypeError, () => Array.fromAsync.call(MyArray, iterator), "Promise rejected if defining element fails");
assert(closed, "element define failure should close iterator");
});

View File

@ -0,0 +1,43 @@
// Copyright (C) 2023 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-array.fromasync
description: >
Closes a sync iterator if setting an element fails on an instance of a custom
this-value
info: |
3.j.ii.8. Let _defineStatus_ be CreateDataPropertyOrThrow(_A_, _Pk_, _mappedValue_).
9. If _defineStatus_ is an abrupt completion, return ? AsyncIteratorClose(_iteratorRecord_, _defineStatus_).
includes: [asyncHelpers.js]
flags: [async]
features: [Array.fromAsync]
---*/
asyncTest(async function () {
function MyArray() {
Object.defineProperty(this, 0, {
enumerable: true,
writable: true,
configurable: false,
value: 0
});
}
let closed = false;
const iterator = {
next() {
return { value: 1, done: false };
},
return() {
closed = true;
return { done: true };
},
[Symbol.iterator]() {
return this;
}
}
await assert.throwsAsync(TypeError, () => Array.fromAsync.call(MyArray, iterator), "Promise rejected if defining element fails");
assert(closed, "element define failure should close iterator");
});

View File

@ -0,0 +1,28 @@
// Copyright (C) 2023 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-array.fromasync
description: >
Rejects the promise if setting an element fails on an instance of a custom
this-value
info: |
3.j.ii.8. Let _defineStatus_ be CreateDataPropertyOrThrow(_A_, _Pk_, _mappedValue_).
9. If _defineStatus_ is an abrupt completion, return ? AsyncIteratorClose(_iteratorRecord_, _defineStatus_).
includes: [asyncHelpers.js]
flags: [async]
features: [Array.fromAsync]
---*/
asyncTest(async function () {
function MyArray() {
Object.defineProperty(this, 0, {
enumerable: true,
writable: true,
configurable: false,
value: 0
});
}
await assert.throwsAsync(TypeError, () => Array.fromAsync.call(MyArray, [0, 1, 2]), "Promise rejected if defining element fails");
});

View File

@ -0,0 +1,53 @@
// Copyright (C) 2023 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-array.fromasync
description: >
Constructs the this-value once if asyncItems is iterable, twice if not, and
length and element properties are set correctly on the result
info: |
3.e. If IsConstructor(_C_) is *true*, then
i. Let _A_ be ? Construct(_C_).
...
j. If _iteratorRecord_ is not *undefined*, then
...
k. Else,
...
iv. If IsConstructor(_C_) is *true*, then
1. Let _A_ be ? Construct(_C_, « 𝔽(_len_) »).
includes: [compareArray.js, asyncHelpers.js]
flags: [async]
features: [Array.fromAsync]
---*/
asyncTest(async function () {
const constructorCalls = [];
function MyArray(...args) {
constructorCalls.push(args);
}
let result = await Array.fromAsync.call(MyArray, [1, 2]);
assert(result instanceof MyArray, "result is an instance of the constructor this-value");
assert.sameValue(result.length, 2, "length is set on result");
assert.sameValue(result[0], 1, "element 0 is set on result");
assert.sameValue(result[1], 2, "element 1 is set on result");
assert.sameValue(constructorCalls.length, 1, "constructor is called once");
assert.compareArray(constructorCalls[0], [], "constructor is called with no arguments");
constructorCalls.splice(0); // reset
result = await Array.fromAsync.call(MyArray, {
length: 2,
0: 1,
1: 2
});
assert(result instanceof MyArray, "result is an instance of the constructor this-value");
assert.sameValue(result.length, 2, "length is set on result");
assert.sameValue(result[0], 1, "element 0 is set on result");
assert.sameValue(result[1], 2, "element 1 is set on result");
assert.sameValue(constructorCalls.length, 2, "constructor is called twice");
assert.compareArray(constructorCalls[0], [], "constructor is called first with no arguments");
assert.compareArray(constructorCalls[1], [2], "constructor is called second with a length argument");
});

View File

@ -0,0 +1,48 @@
// Copyright (C) 2023 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
esid: sec-array.fromasync
description: >
Constructs an intrinsic Array if this-value is not a constructor, and length
and element properties are set accordingly.
info: |
3.e. If IsConstructor(_C_) is *true*, then
...
f. Else,
i. Let _A_ be ! ArrayCreate(0).
...
j. If _iteratorRecord_ is not *undefined*, then
...
k. Else,
...
iv. If IsConstructor(_C_) is *true*, then
...
v. Else,
1. Let _A_ be ? ArrayCreate(_len_).
includes: [compareArray.js, asyncHelpers.js]
flags: [async]
features: [Array.fromAsync]
---*/
asyncTest(async function () {
const thisValue = {
length: 4000,
0: 32,
1: 64,
2: 128
};
let result = await Array.fromAsync.call(thisValue, [1, 2]);
assert(Array.isArray(result), "result is an intrinsic Array");
assert.compareArray(result, [1, 2], "result is not disrupted by properties of this-value");
result = await Array.fromAsync.call(thisValue, {
length: 2,
0: 1,
1: 2
});
assert(Array.isArray(result), "result is an intrinsic Array");
assert.compareArray(result, [1, 2], "result is not disrupted by properties of this-value");
});