testeranto 0.9.20
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/.eslintrc.js +29 -0
- package/.prettierrc +4 -0
- package/.vscode/launch.json +20 -0
- package/.vscode/settings.json +7 -0
- package/README.md +86 -0
- package/bin/build.sh +5 -0
- package/bin/report.sh +6 -0
- package/bin/watch.sh +5 -0
- package/dist/reporter.css +10326 -0
- package/dist/reporter.html +16 -0
- package/dist/reporter.js +26777 -0
- package/example-testeranto.config.json +65 -0
- package/nodemon.json +7 -0
- package/package.json +54 -0
- package/src/BaseClasses.ts +353 -0
- package/src/Features.ts +102 -0
- package/src/Project.ts +23 -0
- package/src/Report.tsx +347 -0
- package/src/build.js +88 -0
- package/src/index.ts +153 -0
- package/src/lib/Scheduler.ts +350 -0
- package/src/lib/level0.ts +157 -0
- package/src/lib/level1.ts +191 -0
- package/src/types.ts +122 -0
- package/src/watch.ts +69 -0
- package/testeranto.ts-0.0.1.tgz +0 -0
- package/truffle-config.js +124 -0
- package/tsconfig.json +33 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/yarn-error.log +3144 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import CancelablePromise, { cancelable } from 'cancelable-promise';
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import fresh from 'fresh-require';
|
|
4
|
+
import { topologicalSort } from 'graphology-dag/topological-sort';
|
|
5
|
+
|
|
6
|
+
import { TesterantoFeatures } from '../Features';
|
|
7
|
+
import { ITestJob, ITTestResourceRequirement, IT_FeatureNetwork } from '../types';
|
|
8
|
+
|
|
9
|
+
const TIMEOUT = 1000;
|
|
10
|
+
const OPEN_PORT = '';
|
|
11
|
+
const testOutPath = "./dist/results/";
|
|
12
|
+
const featureOutPath = "./dist/";
|
|
13
|
+
const reportOutPath = "./dist/report";
|
|
14
|
+
|
|
15
|
+
export class Scheduler {
|
|
16
|
+
featureTestJoin: Record<string, any>;
|
|
17
|
+
testerantoFeatures: TesterantoFeatures;
|
|
18
|
+
|
|
19
|
+
ports: Record<string, string>;
|
|
20
|
+
jobs: Record<string, {
|
|
21
|
+
aborter: () => any;
|
|
22
|
+
cancellablePromise: CancelablePromise<unknown>
|
|
23
|
+
}>;
|
|
24
|
+
queue: {
|
|
25
|
+
key: string,
|
|
26
|
+
aborter: () => any;
|
|
27
|
+
getCancellablePromise: (testResource) => CancelablePromise<unknown>,
|
|
28
|
+
testResourceRequired: ITTestResourceRequirement
|
|
29
|
+
}[];
|
|
30
|
+
testSrcMd5s: object;
|
|
31
|
+
featureSrcMd5: string;
|
|
32
|
+
spinCycle = 0;
|
|
33
|
+
spinAnimation = "←↖↑↗→↘↓↙";
|
|
34
|
+
|
|
35
|
+
constructor(portsToUse: string[]) {
|
|
36
|
+
this.ports = {};
|
|
37
|
+
portsToUse.forEach((port) => {
|
|
38
|
+
this.ports[port] = OPEN_PORT;
|
|
39
|
+
});
|
|
40
|
+
this.queue = [];
|
|
41
|
+
this.jobs = {};
|
|
42
|
+
this.testSrcMd5s = {};
|
|
43
|
+
this.featureTestJoin = {};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public async abort(key) {
|
|
47
|
+
if (this.jobs[key]) {
|
|
48
|
+
console.log("aborting...", key, this.jobs[key])
|
|
49
|
+
await this.jobs[key].aborter();
|
|
50
|
+
await this.jobs[key].cancellablePromise.cancel();
|
|
51
|
+
delete this.jobs[key];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public launch() {
|
|
56
|
+
setInterval(async () => {
|
|
57
|
+
console.log(this.spinner(), this.queue.length, this.ports);
|
|
58
|
+
this.pop();
|
|
59
|
+
}, TIMEOUT);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public testFileTouched(key, distFile, className, hash) {
|
|
63
|
+
if (hash !== this.testSrcMd5s[key]) {
|
|
64
|
+
console.log("running", key);
|
|
65
|
+
this.testSrcMd5s[key] = hash;
|
|
66
|
+
this.push(new (fresh(distFile, require)[className])()[0], key);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public featureFileTouched(distFile, hash) {
|
|
71
|
+
if (hash !== this.featureSrcMd5) {
|
|
72
|
+
console.log("running featureSrcMd5");
|
|
73
|
+
this.featureSrcMd5 = hash;
|
|
74
|
+
this.setFeatures((fresh(distFile, require)['default']));
|
|
75
|
+
} else {
|
|
76
|
+
console.log("feature file changed but md5 hash did not")
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private async setFeatures(testerantoFeatures: TesterantoFeatures) {
|
|
81
|
+
this.testerantoFeatures = testerantoFeatures;
|
|
82
|
+
await fs.promises.mkdir(featureOutPath, { recursive: true });
|
|
83
|
+
await fs.writeFile(
|
|
84
|
+
`${featureOutPath}TesterantoFeatures.json`,
|
|
85
|
+
JSON.stringify(testerantoFeatures.toObj(), null, 2),
|
|
86
|
+
(err) => {
|
|
87
|
+
if (err) {
|
|
88
|
+
console.error(err);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
this.regenerateReports();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private dumpNetworks = () => {
|
|
96
|
+
return {
|
|
97
|
+
dags: this.dumpNetworksDags(),
|
|
98
|
+
directed: this.testerantoFeatures.graphs.directed.map((g) => { return { name: g.name } }),
|
|
99
|
+
undirected: this.testerantoFeatures.graphs.undirected.map((g) => { return { name: g.name } })
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private dumpNetworksDags = () => {
|
|
104
|
+
return (this.testerantoFeatures.graphs.dags.map((network: IT_FeatureNetwork) => {
|
|
105
|
+
const graph = network.graph;
|
|
106
|
+
const topoSorted = topologicalSort(graph).reverse();
|
|
107
|
+
{
|
|
108
|
+
let i = 0;
|
|
109
|
+
do {
|
|
110
|
+
const me = topoSorted[i];
|
|
111
|
+
const feature = this.featureTestJoin[me] || {};
|
|
112
|
+
|
|
113
|
+
graph.setNodeAttribute(me, 'testResults',
|
|
114
|
+
Object.keys(feature).reduce((mm, k) => {
|
|
115
|
+
mm[k] = {
|
|
116
|
+
name: feature[k].suite.name,
|
|
117
|
+
fails: feature[k].suite.fails
|
|
118
|
+
};
|
|
119
|
+
return mm;
|
|
120
|
+
}, {})
|
|
121
|
+
);
|
|
122
|
+
i = i + 1;
|
|
123
|
+
} while (i < topoSorted.length)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
{
|
|
127
|
+
let i = 0;
|
|
128
|
+
do {
|
|
129
|
+
const me = topoSorted[i];
|
|
130
|
+
|
|
131
|
+
const myTestResults = graph.getNodeAttribute(me, 'testResults');
|
|
132
|
+
const anscestors = graph.inNeighbors(me);
|
|
133
|
+
|
|
134
|
+
for (const anscestor of anscestors) {
|
|
135
|
+
const anscestorResults = graph.getNodeAttribute(anscestor, 'testResults');
|
|
136
|
+
graph.setNodeAttribute(anscestor, 'testResults', {
|
|
137
|
+
...anscestorResults,
|
|
138
|
+
results: {
|
|
139
|
+
...anscestorResults.results,
|
|
140
|
+
[me]: {
|
|
141
|
+
results: myTestResults,
|
|
142
|
+
report: graph.getNodeAttribute(anscestor, 'testResults')
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
i = i + 1;
|
|
149
|
+
} while (i < topoSorted.length)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const report = graph.getNodeAttribute(topoSorted[topoSorted.length - 1], 'testResults');
|
|
153
|
+
const name = topoSorted[topoSorted.length - 1];
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
dagReduction: {
|
|
157
|
+
name,
|
|
158
|
+
report
|
|
159
|
+
},
|
|
160
|
+
topoSorted,
|
|
161
|
+
name: network.name
|
|
162
|
+
}
|
|
163
|
+
}));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private regenerateReports() {
|
|
167
|
+
fs.writeFile(
|
|
168
|
+
`${reportOutPath}.json`,
|
|
169
|
+
JSON.stringify(this.dumpNetworks(), null, 2),
|
|
170
|
+
(err) => {
|
|
171
|
+
if (err) {
|
|
172
|
+
console.error(err);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private spinner() {
|
|
179
|
+
this.spinCycle = (this.spinCycle + 1) % this.spinAnimation.length;
|
|
180
|
+
return this.spinAnimation[this.spinCycle]
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private push(testJob: ITestJob, key) {
|
|
184
|
+
this.queue.push({
|
|
185
|
+
key,
|
|
186
|
+
aborter: testJob.test.aborter,
|
|
187
|
+
getCancellablePromise: this.startJob(testJob, key),
|
|
188
|
+
testResourceRequired: testJob.testResource
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private pop() {
|
|
193
|
+
const qi = this.queue.pop();
|
|
194
|
+
if (!qi) {
|
|
195
|
+
console.log('feed me some tests plz')
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const { key, aborter, testResourceRequired, getCancellablePromise } = qi;
|
|
199
|
+
|
|
200
|
+
this.abort(key);
|
|
201
|
+
|
|
202
|
+
if (testResourceRequired.ports === 0) {
|
|
203
|
+
this.jobs[key] = {
|
|
204
|
+
aborter,
|
|
205
|
+
cancellablePromise: getCancellablePromise({}).then(() => delete this.jobs[key])
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (testResourceRequired.ports > 0) {
|
|
210
|
+
|
|
211
|
+
// clear any port-slots associated with this job
|
|
212
|
+
Object.values(this.ports).forEach((jobMaybe, portNumber) => {
|
|
213
|
+
if (jobMaybe && jobMaybe === key) {
|
|
214
|
+
this.ports[portNumber] = OPEN_PORT;
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// find a list of open ports
|
|
219
|
+
const foundOpenPorts = Object.keys(this.ports)
|
|
220
|
+
.filter((p) => this.ports[p] === OPEN_PORT);
|
|
221
|
+
|
|
222
|
+
// if there are enough open port-slots...
|
|
223
|
+
if (foundOpenPorts.length >= testResourceRequired.ports) {
|
|
224
|
+
|
|
225
|
+
const selectionOfPorts = foundOpenPorts.slice(0, testResourceRequired.ports);
|
|
226
|
+
|
|
227
|
+
// init the promise with ports which are open.
|
|
228
|
+
const testPromise = getCancellablePromise(selectionOfPorts)
|
|
229
|
+
|
|
230
|
+
// when the promise is done...
|
|
231
|
+
.then(() => {
|
|
232
|
+
// clear any ports which were used
|
|
233
|
+
Object.keys(this.ports)
|
|
234
|
+
.forEach((p, k) => {
|
|
235
|
+
const jobExistsAndMatches = this.ports[p] === key;
|
|
236
|
+
if (jobExistsAndMatches) {
|
|
237
|
+
this.ports[p] = OPEN_PORT;
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
delete this.jobs[key]
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// mark the selected ports as occupied
|
|
245
|
+
for (const foundOpenPort of selectionOfPorts) {
|
|
246
|
+
this.ports[foundOpenPort] = key;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
this.jobs[key] = {
|
|
250
|
+
aborter,
|
|
251
|
+
cancellablePromise: testPromise
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
} else {
|
|
255
|
+
console.log(`no port was open so send the ${key} job to the back of the queue`)
|
|
256
|
+
this.queue.push(qi);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private startJob(testJob, key) {
|
|
262
|
+
// eslint-disable-next-line no-async-promise-executor
|
|
263
|
+
return (allocatedTestResource) => cancelable(new Promise(async (resolve) => {
|
|
264
|
+
const result = {
|
|
265
|
+
test: testJob.test,
|
|
266
|
+
status: await testJob.runner(allocatedTestResource)
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
await fs.promises.mkdir(testOutPath, { recursive: true });
|
|
270
|
+
|
|
271
|
+
fs.writeFile(
|
|
272
|
+
`${testOutPath}${key}.json`,
|
|
273
|
+
JSON.stringify(testJob.test.toObj(), null, 2),
|
|
274
|
+
|
|
275
|
+
(err) => {
|
|
276
|
+
if (err) {
|
|
277
|
+
console.error(err);
|
|
278
|
+
}
|
|
279
|
+
resolve(result)
|
|
280
|
+
}
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
for (const [gNdx, g] of testJob.test.givens.entries()) {
|
|
284
|
+
for (const testArtifactKey of Object.keys(g.testArtifacts)) {
|
|
285
|
+
for (const [ndx, testArtifact] of g.testArtifacts[testArtifactKey].entries()) {
|
|
286
|
+
const artifactOutFolderPath = `${testOutPath}${key}/${gNdx}/${ndx}/`
|
|
287
|
+
const artifactOutPath = `${artifactOutFolderPath}/${testArtifactKey}.png`
|
|
288
|
+
await fs.promises.mkdir(artifactOutFolderPath, { recursive: true });
|
|
289
|
+
await fs.writeFile(
|
|
290
|
+
artifactOutPath,
|
|
291
|
+
testArtifact.binary,
|
|
292
|
+
(err) => {
|
|
293
|
+
if (err) {
|
|
294
|
+
console.error(err);
|
|
295
|
+
}
|
|
296
|
+
resolve(result)
|
|
297
|
+
}
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
for await (const [gNdx, given] of result.test.givens.entries()) {
|
|
303
|
+
for await (const givenFeature of given.features) {
|
|
304
|
+
for await (const knownFeature of this.testerantoFeatures.features) {
|
|
305
|
+
if (!this.featureTestJoin[givenFeature.name]) {
|
|
306
|
+
this.featureTestJoin[givenFeature.name] = {};
|
|
307
|
+
}
|
|
308
|
+
if (givenFeature.name === knownFeature.name) {
|
|
309
|
+
this.featureTestJoin[givenFeature.name][given.name] = {
|
|
310
|
+
suite: result.test,
|
|
311
|
+
whens: given.whens.map((w) => w.name),
|
|
312
|
+
thens: given.thens.map((t) => t.name),
|
|
313
|
+
errors: given.error,
|
|
314
|
+
}
|
|
315
|
+
} else {
|
|
316
|
+
// delete this.featureTestJoin[givenFeature.name][given.name];
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
fs.writeFile(
|
|
323
|
+
`${testOutPath}featureTestJoin.json`,
|
|
324
|
+
JSON.stringify(
|
|
325
|
+
Object.keys(this.featureTestJoin).reduce((mm, featureKey) => {
|
|
326
|
+
mm[featureKey] = Object.keys(this.featureTestJoin[featureKey]).map((testKey) => {
|
|
327
|
+
const ranJob = this.featureTestJoin[featureKey][testKey];
|
|
328
|
+
return {
|
|
329
|
+
testKey: testKey,
|
|
330
|
+
name: ranJob.suite.name,
|
|
331
|
+
whens: ranJob.whens,
|
|
332
|
+
thens: ranJob.thens,
|
|
333
|
+
errors: ranJob.errors
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
return mm;
|
|
337
|
+
}, {})
|
|
338
|
+
, null, 2),
|
|
339
|
+
(err) => {
|
|
340
|
+
if (err) {
|
|
341
|
+
console.error(err);
|
|
342
|
+
}
|
|
343
|
+
resolve(result)
|
|
344
|
+
}
|
|
345
|
+
);
|
|
346
|
+
this.regenerateReports();
|
|
347
|
+
}))
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { BaseGiven, BaseCheck, BaseSuite, BaseFeature, BaseWhen, BaseThen } from "../BaseClasses";
|
|
2
|
+
import { ITTestShape } from "../types";
|
|
3
|
+
|
|
4
|
+
export abstract class TesterantoLevelZero<
|
|
5
|
+
IInput,
|
|
6
|
+
ISubject,
|
|
7
|
+
IStore,
|
|
8
|
+
ISelection,
|
|
9
|
+
SuiteExtensions,
|
|
10
|
+
GivenExtensions,
|
|
11
|
+
WhenExtensions,
|
|
12
|
+
ThenExtensions,
|
|
13
|
+
CheckExtensions,
|
|
14
|
+
IThenShape
|
|
15
|
+
> {
|
|
16
|
+
constructorator: IStore;
|
|
17
|
+
|
|
18
|
+
suitesOverrides: Record<
|
|
19
|
+
keyof SuiteExtensions,
|
|
20
|
+
(
|
|
21
|
+
name: string,
|
|
22
|
+
givens: BaseGiven<ISubject, IStore, ISelection, IThenShape>[],
|
|
23
|
+
checks: BaseCheck<ISubject, IStore, ISelection, IThenShape, ITTestShape>[]
|
|
24
|
+
) => BaseSuite<IInput, ISubject, IStore, ISelection, IThenShape, ITTestShape>
|
|
25
|
+
>;
|
|
26
|
+
|
|
27
|
+
givenOverides: Record<
|
|
28
|
+
keyof GivenExtensions,
|
|
29
|
+
(
|
|
30
|
+
name: string,
|
|
31
|
+
features: BaseFeature[],
|
|
32
|
+
whens: BaseWhen<IStore, ISelection, IThenShape>[],
|
|
33
|
+
thens: BaseThen<ISelection, IStore, IThenShape>[],
|
|
34
|
+
...xtraArgs
|
|
35
|
+
) => BaseGiven<ISubject, IStore, ISelection, IThenShape>
|
|
36
|
+
>;
|
|
37
|
+
|
|
38
|
+
whenOverides: Record<
|
|
39
|
+
keyof WhenExtensions,
|
|
40
|
+
(any) => BaseWhen<IStore, ISelection, IThenShape>
|
|
41
|
+
>;
|
|
42
|
+
|
|
43
|
+
thenOverides: Record<
|
|
44
|
+
keyof ThenExtensions,
|
|
45
|
+
(
|
|
46
|
+
selection: ISelection,
|
|
47
|
+
expectation: any
|
|
48
|
+
) => BaseThen<ISelection, IStore, IThenShape>
|
|
49
|
+
>;
|
|
50
|
+
|
|
51
|
+
checkOverides: Record<
|
|
52
|
+
keyof CheckExtensions,
|
|
53
|
+
(
|
|
54
|
+
feature: string,
|
|
55
|
+
callback: (whens, thens) => any,
|
|
56
|
+
...xtraArgs
|
|
57
|
+
) => BaseCheck<ISubject, IStore, ISelection, IThenShape, ITTestShape>
|
|
58
|
+
>;
|
|
59
|
+
|
|
60
|
+
constructor(
|
|
61
|
+
public readonly cc: IStore,
|
|
62
|
+
suitesOverrides: Record<
|
|
63
|
+
keyof SuiteExtensions,
|
|
64
|
+
(
|
|
65
|
+
name: string,
|
|
66
|
+
givens: BaseGiven<ISubject, IStore, ISelection, IThenShape>[],
|
|
67
|
+
checks: BaseCheck<ISubject, IStore, ISelection, IThenShape, ITTestShape>[]
|
|
68
|
+
) => BaseSuite<IInput, ISubject, IStore, ISelection, IThenShape, ITTestShape>
|
|
69
|
+
>,
|
|
70
|
+
|
|
71
|
+
givenOverides: Record<
|
|
72
|
+
keyof GivenExtensions,
|
|
73
|
+
(
|
|
74
|
+
name: string,
|
|
75
|
+
features: BaseFeature[],
|
|
76
|
+
whens: BaseWhen<IStore, ISelection, IThenShape>[],
|
|
77
|
+
thens: BaseThen<ISelection, IStore, IThenShape>[],
|
|
78
|
+
...xtraArgs
|
|
79
|
+
) => BaseGiven<ISubject, IStore, ISelection, IThenShape>
|
|
80
|
+
>,
|
|
81
|
+
|
|
82
|
+
whenOverides: Record<
|
|
83
|
+
keyof WhenExtensions,
|
|
84
|
+
(c: any) => BaseWhen<IStore, ISelection, IThenShape>
|
|
85
|
+
>,
|
|
86
|
+
|
|
87
|
+
thenOverides: Record<
|
|
88
|
+
keyof ThenExtensions,
|
|
89
|
+
(
|
|
90
|
+
selection: ISelection,
|
|
91
|
+
expectation: any
|
|
92
|
+
) => BaseThen<ISelection, IStore, IThenShape>
|
|
93
|
+
>,
|
|
94
|
+
|
|
95
|
+
checkOverides: Record<
|
|
96
|
+
keyof CheckExtensions,
|
|
97
|
+
(
|
|
98
|
+
feature: string,
|
|
99
|
+
callback: (whens, thens) => any,
|
|
100
|
+
...xtraArgs
|
|
101
|
+
) => BaseCheck<ISubject, IStore, ISelection, IThenShape, ITTestShape>
|
|
102
|
+
>
|
|
103
|
+
) {
|
|
104
|
+
this.constructorator = cc;
|
|
105
|
+
this.suitesOverrides = suitesOverrides;
|
|
106
|
+
this.givenOverides = givenOverides;
|
|
107
|
+
this.whenOverides = whenOverides;
|
|
108
|
+
this.thenOverides = thenOverides;
|
|
109
|
+
this.checkOverides = checkOverides;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
Suites() {
|
|
113
|
+
return this.suitesOverrides;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
Given(): Record<
|
|
117
|
+
keyof GivenExtensions,
|
|
118
|
+
(
|
|
119
|
+
name: string,
|
|
120
|
+
features: BaseFeature[],
|
|
121
|
+
whens: BaseWhen<IStore, ISelection, IThenShape>[],
|
|
122
|
+
thens: BaseThen<ISelection, IStore, IThenShape>[],
|
|
123
|
+
...xtraArgs
|
|
124
|
+
) => BaseGiven<ISubject, IStore, ISelection, IThenShape>
|
|
125
|
+
> {
|
|
126
|
+
return this.givenOverides;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
When(): Record<
|
|
130
|
+
keyof WhenExtensions,
|
|
131
|
+
(arg0: IStore, ...arg1: any) => BaseWhen<IStore, ISelection, IThenShape>
|
|
132
|
+
> {
|
|
133
|
+
return this.whenOverides;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
Then(): Record<
|
|
137
|
+
keyof ThenExtensions,
|
|
138
|
+
(
|
|
139
|
+
selection: ISelection,
|
|
140
|
+
expectation: any
|
|
141
|
+
) => BaseThen<ISelection, IStore, IThenShape>
|
|
142
|
+
> {
|
|
143
|
+
return this.thenOverides;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
Check(): Record<
|
|
147
|
+
keyof CheckExtensions,
|
|
148
|
+
(
|
|
149
|
+
feature: string,
|
|
150
|
+
callback: (whens, thens) => any,
|
|
151
|
+
whens,
|
|
152
|
+
thens
|
|
153
|
+
) => BaseCheck<ISubject, IStore, ISelection, IThenShape, ITTestShape>
|
|
154
|
+
> {
|
|
155
|
+
return this.checkOverides;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { BaseGiven, BaseCheck, BaseSuite, BaseFeature, BaseWhen, BaseThen } from "../BaseClasses";
|
|
2
|
+
import { ITTestShape, ITestImplementation, ITestJob } from "../types";
|
|
3
|
+
|
|
4
|
+
import { TesterantoLevelZero } from "./level0";
|
|
5
|
+
|
|
6
|
+
export abstract class TesterantoLevelOne<
|
|
7
|
+
ITestShape extends ITTestShape,
|
|
8
|
+
IInitialState,
|
|
9
|
+
ISelection,
|
|
10
|
+
IStore,
|
|
11
|
+
ISubject,
|
|
12
|
+
IWhenShape,
|
|
13
|
+
IThenShape,
|
|
14
|
+
IInput
|
|
15
|
+
> {
|
|
16
|
+
constructor(
|
|
17
|
+
testImplementation: ITestImplementation<
|
|
18
|
+
IInitialState,
|
|
19
|
+
ISelection,
|
|
20
|
+
IWhenShape,
|
|
21
|
+
IThenShape,
|
|
22
|
+
ITestShape
|
|
23
|
+
>,
|
|
24
|
+
|
|
25
|
+
testSpecification: (
|
|
26
|
+
Suite: {
|
|
27
|
+
[K in keyof ITestShape["suites"]]: (
|
|
28
|
+
feature: string,
|
|
29
|
+
givens: BaseGiven<ISubject, IStore, ISelection, IThenShape>[],
|
|
30
|
+
checks: BaseCheck<ISubject, IStore, ISelection, IThenShape, ITestShape>[]
|
|
31
|
+
) => BaseSuite<IInput, ISubject, IStore, ISelection, IThenShape, ITestShape>;
|
|
32
|
+
},
|
|
33
|
+
Given: {
|
|
34
|
+
[K in keyof ITestShape["givens"]]: (
|
|
35
|
+
name: string,
|
|
36
|
+
features: BaseFeature[],
|
|
37
|
+
whens: BaseWhen<IStore, ISelection, IThenShape>[],
|
|
38
|
+
thens: BaseThen<ISelection, IStore, IThenShape>[],
|
|
39
|
+
...a: ITestShape["givens"][K]
|
|
40
|
+
) => BaseGiven<ISubject, IStore, ISelection, IThenShape>;
|
|
41
|
+
},
|
|
42
|
+
When: {
|
|
43
|
+
[K in keyof ITestShape["whens"]]: (
|
|
44
|
+
...a: ITestShape["whens"][K]
|
|
45
|
+
) => BaseWhen<IStore, ISelection, IThenShape>;
|
|
46
|
+
},
|
|
47
|
+
Then: {
|
|
48
|
+
[K in keyof ITestShape["thens"]]: (
|
|
49
|
+
...a: ITestShape["thens"][K]
|
|
50
|
+
) => BaseThen<ISelection, IStore, IThenShape>;
|
|
51
|
+
},
|
|
52
|
+
Check: {
|
|
53
|
+
[K in keyof ITestShape["checks"]]: (
|
|
54
|
+
name: string,
|
|
55
|
+
features: BaseFeature[],
|
|
56
|
+
cbz: (...any) => Promise<void>
|
|
57
|
+
) => any;
|
|
58
|
+
}
|
|
59
|
+
) => BaseSuite<IInput, ISubject, IStore, ISelection, IThenShape, ITestShape>[],
|
|
60
|
+
|
|
61
|
+
input: IInput,
|
|
62
|
+
|
|
63
|
+
suiteKlasser: (
|
|
64
|
+
name: string,
|
|
65
|
+
givens: BaseGiven<ISubject, IStore, ISelection, IThenShape>[],
|
|
66
|
+
checks: BaseCheck<ISubject, IStore, ISelection, IThenShape, ITestShape>[]
|
|
67
|
+
) =>
|
|
68
|
+
BaseSuite<IInput, ISubject, IStore, ISelection, IThenShape, ITestShape>,
|
|
69
|
+
givenKlasser: (n, f, w, t, z?) =>
|
|
70
|
+
BaseGiven<ISubject, IStore, ISelection, IThenShape>,
|
|
71
|
+
whenKlasser: (s, o) =>
|
|
72
|
+
BaseWhen<IStore, ISelection, IThenShape>,
|
|
73
|
+
thenKlasser: (s, o) =>
|
|
74
|
+
BaseThen<IStore, ISelection, IThenShape>,
|
|
75
|
+
checkKlasser: (n, f, cb, w, t) =>
|
|
76
|
+
BaseCheck<ISubject, IStore, ISelection, IThenShape, ITestShape>,
|
|
77
|
+
|
|
78
|
+
testResource
|
|
79
|
+
|
|
80
|
+
) {
|
|
81
|
+
const classySuites = Object.entries(testImplementation.Suites)
|
|
82
|
+
.reduce((a, [key]) => {
|
|
83
|
+
a[key] = (somestring, givens, checks) => {
|
|
84
|
+
return new suiteKlasser.prototype.constructor(somestring, givens, checks);
|
|
85
|
+
};
|
|
86
|
+
return a;
|
|
87
|
+
}, {}
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const classyGivens = Object.entries(testImplementation.Givens)
|
|
91
|
+
.reduce((a, [key, z]) => {
|
|
92
|
+
a[key] = (features, whens, thens, ...xtrasW) => {
|
|
93
|
+
return new givenKlasser.prototype.constructor(z.name, features, whens, thens, z(...xtrasW))
|
|
94
|
+
};
|
|
95
|
+
return a;
|
|
96
|
+
}, {}
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const classyWhens = Object.entries(testImplementation.Whens)
|
|
100
|
+
.reduce((a, [key, whEn]) => {
|
|
101
|
+
a[key] = (payload?: any) => {
|
|
102
|
+
return new whenKlasser.prototype.constructor(
|
|
103
|
+
`${whEn.name}: ${payload && payload.toString()}`,
|
|
104
|
+
whEn(payload)
|
|
105
|
+
)
|
|
106
|
+
};
|
|
107
|
+
return a;
|
|
108
|
+
}, {}
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const classyThens = Object.entries(testImplementation.Thens)
|
|
112
|
+
.reduce((a, [key, thEn]) => {
|
|
113
|
+
a[key] = (expected: any, x) => {
|
|
114
|
+
return new thenKlasser.prototype.constructor(
|
|
115
|
+
`${thEn.name}: ${expected && expected.toString()}`,
|
|
116
|
+
thEn(expected)
|
|
117
|
+
);
|
|
118
|
+
};
|
|
119
|
+
return a;
|
|
120
|
+
}, {}
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
const classyChecks = Object.entries(testImplementation.Checks)
|
|
124
|
+
.reduce((a, [key, z]) => {
|
|
125
|
+
a[key] = (somestring, features, callback) => {
|
|
126
|
+
return new checkKlasser.prototype.constructor(somestring, features, callback, classyWhens, classyThens);
|
|
127
|
+
};
|
|
128
|
+
return a;
|
|
129
|
+
}, {}
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
const classyTesteranto = new (class <
|
|
134
|
+
IInput,
|
|
135
|
+
ISubject,
|
|
136
|
+
IStore,
|
|
137
|
+
ISelection,
|
|
138
|
+
SuiteExtensions,
|
|
139
|
+
GivenExtensions,
|
|
140
|
+
WhenExtensions,
|
|
141
|
+
ThenExtensions,
|
|
142
|
+
ICheckExtensions,
|
|
143
|
+
IThenShape
|
|
144
|
+
> extends TesterantoLevelZero<
|
|
145
|
+
IInput,
|
|
146
|
+
ISubject,
|
|
147
|
+
IStore,
|
|
148
|
+
ISelection,
|
|
149
|
+
SuiteExtensions,
|
|
150
|
+
GivenExtensions,
|
|
151
|
+
WhenExtensions,
|
|
152
|
+
ThenExtensions,
|
|
153
|
+
ICheckExtensions,
|
|
154
|
+
IThenShape
|
|
155
|
+
> { })(
|
|
156
|
+
input,
|
|
157
|
+
classySuites,
|
|
158
|
+
classyGivens,
|
|
159
|
+
classyWhens,
|
|
160
|
+
classyThens,
|
|
161
|
+
classyChecks
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const suites = testSpecification(
|
|
165
|
+
/* @ts-ignore:next-line */
|
|
166
|
+
classyTesteranto.Suites(),
|
|
167
|
+
classyTesteranto.Given(),
|
|
168
|
+
classyTesteranto.When(),
|
|
169
|
+
classyTesteranto.Then(),
|
|
170
|
+
classyTesteranto.Check()
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
const toReturn: ITestJob[] = suites.map((suite) => {
|
|
174
|
+
return {
|
|
175
|
+
test: suite,
|
|
176
|
+
testResource,
|
|
177
|
+
|
|
178
|
+
toObj: () => {
|
|
179
|
+
return suite.toObj()
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
runner: async (allocatedPorts: number[]) => {
|
|
183
|
+
return suite.run(input, { ports: allocatedPorts });
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
};
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
return toReturn;
|
|
190
|
+
}
|
|
191
|
+
}
|