-
Notifications
You must be signed in to change notification settings - Fork 54
/
constructor-ordering.js
129 lines (115 loc) · 4.68 KB
/
constructor-ordering.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
'use strict';
// Helpers for tests that constructors perform getting and validation of properties in the standard order.
// See ../readable-streams/constructor.js for an example of how to use them.
// Describes an operation on a property. |type| is "get", "validate" or "tonumber". |name| is the name of the property
// in question. |side| is usually undefined, but is used by TransformStream to distinguish between the readable and
// writable strategies.
class Op {
constructor(type, name, side) {
this.type = type;
this.name = name;
this.side = side;
}
toString() {
return this.side === undefined ? `${this.type} on ${this.name}` : `${this.type} on ${this.name} (${this.side})`;
}
equals(otherOp) {
return this.type === otherOp.type && this.name === otherOp.name && this.side === otherOp.side;
}
}
// Provides a concise syntax to create an Op object. |side| is used by TransformStream to distinguish between the two
// strategies.
function op(type, name, side = undefined) {
return new Op(type, name, side);
}
// Records a sequence of operations. Also checks each operation against |failureOp| to see if it should fail.
class OpRecorder {
constructor(failureOp) {
this.ops = [];
this.failureOp = failureOp;
this.matched = false;
}
// Record an operation. Returns true if this operation should fail.
recordAndCheck(type, name, side = undefined) {
const recordedOp = op(type, name, side);
this.ops.push(recordedOp);
return this.failureOp.equals(recordedOp);
}
// Returns true if validation of this property should fail.
check(name, side = undefined) {
return this.failureOp.equals(op('validate', name, side));
}
// Returns the sequence of recorded operations as a string.
actual() {
return this.ops.toString();
}
}
// Creates an object with the list of properties named in |properties|. Every property access will be recorded in
// |record|, which will also be used to determine whether a particular property access should fail, or whether it should
// return an invalid value that will fail validation.
function createRecordingObjectWithProperties(record, properties) {
const recordingObject = {};
for (const property of properties) {
defineCheckedProperty(record, recordingObject, property, () => record.check(property) ? 'invalid' : undefined);
}
return recordingObject;
}
// Add a getter to |object| named |property| which throws if op('get', property) should fail, and otherwise calls
// getter() to get the return value.
function defineCheckedProperty(record, object, property, getter) {
Object.defineProperty(object, property, {
get() {
if (record.recordAndCheck('get', property)) {
throw new Error(`intentional failure of get ${property}`);
}
return getter();
}
});
}
// Similar to createRecordingObjectWithProperties(), but with specific functionality for "highWaterMark" so that numeric
// conversion can be recorded. Permits |side| to be specified so that TransformStream can distinguish between its two
// strategies.
function createRecordingStrategy(record, side = undefined) {
return {
get size() {
if (record.recordAndCheck('get', 'size', side)) {
throw new Error(`intentional failure of get size`);
}
return record.check('size', side) ? 'invalid' : undefined;
},
get highWaterMark() {
if (record.recordAndCheck('get', 'highWaterMark', side)) {
throw new Error(`intentional failure of get highWaterMark`);
}
return createRecordingNumberObject(record, 'highWaterMark', side);
}
};
}
// Creates an object which will record when it is converted to a number. It will assert if the conversion is to some
// other type, and will fail if op('tonumber', property, side) is set as the failure step. The object will convert to -1
// if 'validate' is set as the failure step, and 1 otherwise.
function createRecordingNumberObject(record, property, side = undefined) {
return {
[Symbol.toPrimitive](hint) {
assert_equals(hint, 'number', `hint for ${property} should be 'number'`);
if (record.recordAndCheck('tonumber', property, side)) {
throw new Error(`intentional failure of ${op('tonumber', property, side)}`);
}
return record.check(property, side) ? -1 : 1;
}
};
}
// Creates a string from everything in |operations| up to and including |failureOp|. "validate" steps are excluded from
// the output, as we cannot record them except by making them fail.
function expectedAsString(operations, failureOp) {
const expected = [];
for (const step of operations) {
if (step.type !== 'validate') {
expected.push(step);
}
if (step.equals(failureOp)) {
break;
}
}
return expected.toString();
}