risei 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +8 -0
- package/README.md +642 -0
- package/index.js +122 -0
- package/package.json +39 -0
- package/public/javascript/AComparer.js +9 -0
- package/public/javascript/ASpoofingFixture.js +84 -0
- package/public/javascript/ATestCaller.js +61 -0
- package/public/javascript/ATestFinder.js +25 -0
- package/public/javascript/ATestFixture.js +44 -0
- package/public/javascript/ATestReporter.js +25 -0
- package/public/javascript/ATestSource.js +8 -0
- package/public/javascript/ChosenTestFinder.js +30 -0
- package/public/javascript/ClassTestGroup.js +8 -0
- package/public/javascript/LocalCaller.js +22 -0
- package/public/javascript/MethodTestGroup.js +8 -0
- package/public/javascript/Moment.js +29 -0
- package/public/javascript/Risei.js +88 -0
- package/public/javascript/SpoofClassMethodsFixture.js +165 -0
- package/public/javascript/SpoofObjectMethodsFixture.js +52 -0
- package/public/javascript/SpoofTuple.js +238 -0
- package/public/javascript/TerminalReporter.js +222 -0
- package/public/javascript/TestFinder.js +140 -0
- package/public/javascript/TestGroup.js +25 -0
- package/public/javascript/TestResult.js +338 -0
- package/public/javascript/TestRunner.js +476 -0
- package/public/javascript/TestSummary.js +37 -0
- package/public/javascript/TestTuple.js +244 -0
- package/public/javascript/TotalComparer.js +229 -0
- package/usage-examples/Output-example.png +0 -0
- package/usage-examples/Summary-example.png +0 -0
- package/usage-examples/Syntax-example.png +0 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**/
|
|
2
|
+
|
|
3
|
+
/* Defines what is found in a test-definition tuple (TDT). Actual TDTs don't need to be instances of this class. */
|
|
4
|
+
|
|
5
|
+
export class TestTuple {
|
|
6
|
+
// region Static fields
|
|
7
|
+
|
|
8
|
+
static longsByShort = new Map([
|
|
9
|
+
[ "for", "nature" ],
|
|
10
|
+
[ "on", "type" ],
|
|
11
|
+
[ "with", "initors" ],
|
|
12
|
+
[ "of", "method" ],
|
|
13
|
+
[ "plus", "spoofed" ],
|
|
14
|
+
[ "in", "inputs" ],
|
|
15
|
+
[ "out", "output" ],
|
|
16
|
+
[ "from", "source" ],
|
|
17
|
+
[ "and", "factors" ]
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
// endregion Static fields
|
|
21
|
+
|
|
22
|
+
// region Private fields
|
|
23
|
+
|
|
24
|
+
/* These longer names match the constructor-arg names.
|
|
25
|
+
The short-name properties use the same fields. */
|
|
26
|
+
|
|
27
|
+
nature;
|
|
28
|
+
type;
|
|
29
|
+
initors;
|
|
30
|
+
method;
|
|
31
|
+
spoofed;
|
|
32
|
+
inputs;
|
|
33
|
+
output;
|
|
34
|
+
source;
|
|
35
|
+
factors;
|
|
36
|
+
|
|
37
|
+
// endregion Private fields
|
|
38
|
+
|
|
39
|
+
// region Properties
|
|
40
|
+
|
|
41
|
+
// region Test definition, short names
|
|
42
|
+
|
|
43
|
+
/* These short names make JSON-like definitions easier. */
|
|
44
|
+
|
|
45
|
+
get for() {
|
|
46
|
+
return this.nature;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
set for(value) {
|
|
50
|
+
this.nature = value;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
get on() {
|
|
54
|
+
return this.type;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
set on(value) {
|
|
58
|
+
this.type = value;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
get with() {
|
|
62
|
+
return this.initors;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
set with(value) {
|
|
66
|
+
this.initors = value;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
get of() {
|
|
70
|
+
return this.method;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
set of(value) {
|
|
74
|
+
this.method = value;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
get plus() {
|
|
78
|
+
return this.spoofed;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
set plus(value) {
|
|
82
|
+
this.spoofed = value;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
get in() {
|
|
86
|
+
return this.inputs;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
set in(value) {
|
|
90
|
+
this.inputs = value;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
get out() {
|
|
94
|
+
return this.output;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
set out(value) {
|
|
98
|
+
this.output = value;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
get from() {
|
|
102
|
+
return this.source;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
set from(value) {
|
|
106
|
+
this.source = value;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
get and() {
|
|
110
|
+
return this.factors;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
set and(value) {
|
|
114
|
+
this.factors = value;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// endregion Test definition, short names
|
|
118
|
+
|
|
119
|
+
// region Tuple state
|
|
120
|
+
|
|
121
|
+
get isRunnable() {
|
|
122
|
+
let isRunnable
|
|
123
|
+
= this.nature !== undefined
|
|
124
|
+
&& this.type !== undefined
|
|
125
|
+
&& this.initors !== undefined
|
|
126
|
+
&& this.method !== undefined
|
|
127
|
+
&& this.inputs !== undefined
|
|
128
|
+
&& this.output !== undefined;
|
|
129
|
+
|
|
130
|
+
return isRunnable;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// endregion Tuple state
|
|
134
|
+
|
|
135
|
+
// endregion Properties
|
|
136
|
+
|
|
137
|
+
// region Initing, including constructor()
|
|
138
|
+
|
|
139
|
+
/* Constructor can't use param names like `for`, because when not .-prefixed, they are keywords. */
|
|
140
|
+
|
|
141
|
+
constructor(nature, type, spoofed, initors, method, inputs, output, source, factors) {
|
|
142
|
+
this.nature = nature;
|
|
143
|
+
this.type = type;
|
|
144
|
+
this.spoofed = spoofed;
|
|
145
|
+
this.initors = initors;
|
|
146
|
+
this.method = method;
|
|
147
|
+
this.inputs = inputs;
|
|
148
|
+
this.output = output;
|
|
149
|
+
this.source = source;
|
|
150
|
+
this.factors = factors;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
static fromNonceTuples(nonces) {
|
|
154
|
+
// Throughput and output.
|
|
155
|
+
let full = new TestTuple();
|
|
156
|
+
let tuples = [];
|
|
157
|
+
|
|
158
|
+
// Looping over all.
|
|
159
|
+
for (let nonce of nonces) {
|
|
160
|
+
// Get latest test definition.
|
|
161
|
+
let latest = TestTuple.fromNonceTuple(nonce);
|
|
162
|
+
|
|
163
|
+
// Restarting test definitions when desired.
|
|
164
|
+
full = TestTuple.maybeRestartFull(full, latest);
|
|
165
|
+
|
|
166
|
+
// Merge any previous
|
|
167
|
+
// values into latest.
|
|
168
|
+
latest.combineWith(full);
|
|
169
|
+
|
|
170
|
+
// Make latest the tuple for
|
|
171
|
+
// combining with next time.
|
|
172
|
+
full = latest;
|
|
173
|
+
|
|
174
|
+
// Add latest only if it's
|
|
175
|
+
// ready to run as a test.
|
|
176
|
+
if (latest.isRunnable) {
|
|
177
|
+
tuples.push(latest);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Back to caller.
|
|
182
|
+
return tuples;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
static fromNonceTuple(nonce) {
|
|
186
|
+
// Empty, since properties can be set
|
|
187
|
+
// by either of two nonce naming styles.
|
|
188
|
+
let tuple = new TestTuple();
|
|
189
|
+
|
|
190
|
+
let shortNames = TestTuple.longsByShort.keys();
|
|
191
|
+
|
|
192
|
+
// Traversing matching pairs of names and applying
|
|
193
|
+
// whichever one is present as the tuple property;
|
|
194
|
+
// if neither is present, the property is undefined.
|
|
195
|
+
for (let shortName of shortNames) {
|
|
196
|
+
let longName = TestTuple.longsByShort.get(shortName);
|
|
197
|
+
tuple[shortName] = shortName in nonce ? nonce[shortName] : nonce[longName];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Back to caller.
|
|
201
|
+
return tuple;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// region Dependencies of nonce-tuple initing methods
|
|
205
|
+
|
|
206
|
+
static maybeRestartFull(full, latest) {
|
|
207
|
+
// When a (new) model class is named,
|
|
208
|
+
// wipe out all reused test values.
|
|
209
|
+
if (latest.type !== undefined) {
|
|
210
|
+
full = new TestTuple();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// When a (new) model method is named, wipe out reused
|
|
214
|
+
// test values except the general ones for its class.
|
|
215
|
+
if (latest.method !== undefined) {
|
|
216
|
+
full = new TestTuple(
|
|
217
|
+
full.nature, full.type,
|
|
218
|
+
full.spoofed, full.initors
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return full;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
combineWith(other) {
|
|
226
|
+
let shortNames = TestTuple.longsByShort.keys();
|
|
227
|
+
|
|
228
|
+
for (let shortName of shortNames) {
|
|
229
|
+
TestTuple.combineValues(this, other, shortName);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
static combineValues(self, other, name) {
|
|
234
|
+
// Property may still end up undefined.
|
|
235
|
+
if (self[name] === undefined) {
|
|
236
|
+
self[name] = other[name];
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// endregion Dependencies of nonce-tuple initing methods
|
|
241
|
+
|
|
242
|
+
// endregion Initing, including constructor()
|
|
243
|
+
|
|
244
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**/
|
|
2
|
+
|
|
3
|
+
import { AComparer } from "./AComparer.js";
|
|
4
|
+
|
|
5
|
+
export class TotalComparer extends AComparer {
|
|
6
|
+
constructor() {
|
|
7
|
+
super();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
compare(expected, actual) /* passed */ {
|
|
11
|
+
return this.#recurse(expected, actual);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// region Dependencies of compare()
|
|
15
|
+
|
|
16
|
+
#recurse(expected, actual) /* verified */ {
|
|
17
|
+
// These have to be tried in this order,
|
|
18
|
+
// given how JS type identities overlap.
|
|
19
|
+
if (Array.isArray(expected)) {
|
|
20
|
+
/* Traverse / recurse to leaves. */
|
|
21
|
+
return this.#recurseOverArrays(expected, actual);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (expected instanceof Map) {
|
|
25
|
+
/* Traverse / recurse to leaves. */
|
|
26
|
+
return this.#recurseOverMaps(expected, actual);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (expected instanceof Set) {
|
|
30
|
+
/* Traverse / recurse to leaves. */
|
|
31
|
+
return this.#recurseOverSets(expected, actual);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (expected instanceof Function) {
|
|
35
|
+
/* Leaf to compare directly. */
|
|
36
|
+
return this.#compareFunctions(expected, actual);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (expected instanceof Object) {
|
|
40
|
+
/* Traverse / recurse to leaves. */
|
|
41
|
+
return this.#recurseOverObjects(expected, actual);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* If neither a function leaf nor any
|
|
45
|
+
fork type, compare as a leaf value. */
|
|
46
|
+
return this.#compareValues(expected, actual);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
#recurseOverObjects(expected, actual) /* verified */ {
|
|
50
|
+
// Both must be objects.
|
|
51
|
+
if (!(actual instanceof Object)) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// There must not be any extra members
|
|
56
|
+
// of `actual`, its own or inherited.
|
|
57
|
+
for (let propertyName in actual) {
|
|
58
|
+
if (!(propertyName in expected)) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// All members of `expected`, its own or inherited,
|
|
64
|
+
// must exist in `actual` and match it recursively.
|
|
65
|
+
for (let propertyName in expected) {
|
|
66
|
+
// Members present must match.
|
|
67
|
+
if (!(propertyName in actual)) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Cross-recursion.
|
|
72
|
+
let areEqual = this.#recurse(expected[propertyName], actual[propertyName]);
|
|
73
|
+
|
|
74
|
+
// Exit only at first false.
|
|
75
|
+
switch (areEqual) {
|
|
76
|
+
case true: continue;
|
|
77
|
+
case false: return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// No mismatched or missing found.
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
#recurseOverMaps(expected, actual) /* verified */ {
|
|
86
|
+
// Both must be Maps.
|
|
87
|
+
if (!(actual instanceof Map)) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Must have same number of elements.
|
|
92
|
+
if (expected.size !== actual.size) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let expectedKeys = expected.keys();
|
|
97
|
+
|
|
98
|
+
// Looking at content found for each key.
|
|
99
|
+
for (let key of expectedKeys) {
|
|
100
|
+
// Get expected and any matching actual.
|
|
101
|
+
let expValue = expected.get(key);
|
|
102
|
+
let acValue = actual.get(key);
|
|
103
|
+
|
|
104
|
+
// Cross-recursion.
|
|
105
|
+
let areEqual = this.#recurse(expValue, acValue);
|
|
106
|
+
|
|
107
|
+
// Exit only at first false.
|
|
108
|
+
switch (areEqual) {
|
|
109
|
+
case true: continue;
|
|
110
|
+
case false: return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// No mismatched or missing found.
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
#recurseOverSets(expected, actual) /* verified */ {
|
|
119
|
+
// Both must be Sets.
|
|
120
|
+
if (!(actual instanceof Set)) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Must have same number of elements.
|
|
125
|
+
if (expected.size !== actual.size) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Local copies allow ablative approach.
|
|
130
|
+
let localExpected = new Set(expected);
|
|
131
|
+
let localActual = new Set(actual);
|
|
132
|
+
|
|
133
|
+
// Looking at each element.
|
|
134
|
+
for (let item of localExpected) {
|
|
135
|
+
// If this one matches, remove it from both Sets
|
|
136
|
+
// so that it isn't unnecessarily traversed later.
|
|
137
|
+
if (localActual.has(item)) {
|
|
138
|
+
localExpected.delete(item);
|
|
139
|
+
localActual.delete(item);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// If this element doesn't match by value / identity,
|
|
144
|
+
// try to match it against all remaining by content.
|
|
145
|
+
for (let against of localActual) {
|
|
146
|
+
let areEqual = this.#recurse(item, against);
|
|
147
|
+
|
|
148
|
+
// If matched by content, remove it from
|
|
149
|
+
// both Sets for fewer later comparisons.
|
|
150
|
+
if (areEqual) {
|
|
151
|
+
localExpected.delete(item);
|
|
152
|
+
localActual.delete(against);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// If all matched, all were removed
|
|
158
|
+
// from both of the Sets by now.
|
|
159
|
+
if (localExpected.size === 0 && localActual.size === 0) {
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// One or more mismatched or never found.
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
#recurseOverArrays(expected, actual) /* verified */ {
|
|
168
|
+
// Both must be arrays.
|
|
169
|
+
if (!Array.isArray(actual)) {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Must have same number of elements.
|
|
174
|
+
if (actual.length !== expected.length) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Looking at content found at each index.
|
|
179
|
+
for (let at = 0; at < expected.length; at++) {
|
|
180
|
+
// Cross-recursion.
|
|
181
|
+
let areEqual = this.#recurse(expected[at], actual[at]);
|
|
182
|
+
|
|
183
|
+
// Exit only at first false.
|
|
184
|
+
switch (areEqual) {
|
|
185
|
+
case true: continue;
|
|
186
|
+
case false: return false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// No mismatched found.
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
#compareValues(expected, actual) /* verified */ {
|
|
195
|
+
if (actual === expected) {
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
#compareFunctions(expected, actual) /* verified */ {
|
|
203
|
+
// `expected` is already known to be a function / method.
|
|
204
|
+
if (!(actual instanceof Function)) {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// The code contents, name, and `function` vs. lambda
|
|
209
|
+
// style are compared here, for pretty exact matching.
|
|
210
|
+
let expectedCode = expected.toString();
|
|
211
|
+
let actualCode = actual.toString();
|
|
212
|
+
|
|
213
|
+
// The leading keyword `function` is needed to declare a non-lambda function in a test,
|
|
214
|
+
// but may not be present in an otherwise-equal actual, so it's removed if present.
|
|
215
|
+
expectedCode = expectedCode.replace(/^function(?=\s|\()/, "");
|
|
216
|
+
actualCode = actualCode.replace(/^function(?=\s|\()/, "");
|
|
217
|
+
|
|
218
|
+
// Any leading and trailing whitespace is removed, since it's meaningless.
|
|
219
|
+
expectedCode = expectedCode.trim();
|
|
220
|
+
actualCode = actualCode.trim();
|
|
221
|
+
|
|
222
|
+
// Actually comparing.
|
|
223
|
+
return expectedCode === actualCode;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// endregion Dependencies of compare()
|
|
227
|
+
|
|
228
|
+
}
|
|
229
|
+
|
|
Binary file
|
|
Binary file
|
|
Binary file
|