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.
@@ -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
+ }