testeranto 0.38.0 → 0.38.1

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,65 @@
1
+ declare const DirectedGraph: any, UndirectedGraph: any;
2
+ declare abstract class TesterantoGraph {
3
+ name: string;
4
+ abstract graph: any;
5
+ constructor(name: string);
6
+ }
7
+ export declare class BaseFeature {
8
+ name: string;
9
+ constructor(name: string);
10
+ }
11
+ export declare class TesterantoGraphUndirected implements TesterantoGraph {
12
+ name: string;
13
+ graph: typeof UndirectedGraph;
14
+ constructor(name: string);
15
+ connect(a: any, b: any, relation?: string): void;
16
+ }
17
+ export declare class TesterantoGraphDirected implements TesterantoGraph {
18
+ name: string;
19
+ graph: typeof DirectedGraph;
20
+ constructor(name: string);
21
+ connect(to: any, from: any, relation?: string): void;
22
+ }
23
+ export declare class TesterantoGraphDirectedAcyclic implements TesterantoGraph {
24
+ name: string;
25
+ graph: typeof DirectedGraph;
26
+ constructor(name: string);
27
+ connect(to: any, from: any, relation?: string): void;
28
+ }
29
+ export declare class TesterantoFeatures {
30
+ features: Record<string, BaseFeature>;
31
+ graphs: {
32
+ undirected: TesterantoGraphUndirected[];
33
+ directed: TesterantoGraphDirected[];
34
+ dags: TesterantoGraphDirectedAcyclic[];
35
+ };
36
+ constructor(features: Record<string, BaseFeature>, graphs: {
37
+ undirected: TesterantoGraphUndirected[];
38
+ directed: TesterantoGraphDirected[];
39
+ dags: TesterantoGraphDirectedAcyclic[];
40
+ });
41
+ networks(): (TesterantoGraphUndirected | TesterantoGraphDirected | TesterantoGraphDirectedAcyclic)[];
42
+ toObj(): {
43
+ features: {
44
+ inNetworks: {
45
+ network: string;
46
+ neighbors: any;
47
+ }[];
48
+ name: string;
49
+ }[];
50
+ networks: ({
51
+ name: string;
52
+ graph: any;
53
+ } | {
54
+ name: string;
55
+ graph: any;
56
+ } | {
57
+ name: string;
58
+ graph: any;
59
+ })[];
60
+ };
61
+ }
62
+ export declare type IT_FeatureNetwork = {
63
+ name: string;
64
+ };
65
+ export {};
@@ -0,0 +1,71 @@
1
+ import pkg from 'graphology';
2
+ /* @ts-ignore:next-line */
3
+ const { DirectedGraph, UndirectedGraph } = pkg;
4
+ class TesterantoGraph {
5
+ constructor(name) {
6
+ this.name = name;
7
+ }
8
+ }
9
+ export class BaseFeature {
10
+ constructor(name) {
11
+ this.name = name;
12
+ }
13
+ }
14
+ ;
15
+ export class TesterantoGraphUndirected {
16
+ constructor(name) {
17
+ this.name = name;
18
+ this.graph = new UndirectedGraph();
19
+ }
20
+ connect(a, b, relation) {
21
+ this.graph.mergeEdge(a, b, { type: relation });
22
+ }
23
+ }
24
+ export class TesterantoGraphDirected {
25
+ constructor(name) {
26
+ this.name = name;
27
+ this.graph = new DirectedGraph();
28
+ }
29
+ connect(to, from, relation) {
30
+ this.graph.mergeEdge(to, from, { type: relation });
31
+ }
32
+ }
33
+ export class TesterantoGraphDirectedAcyclic {
34
+ constructor(name) {
35
+ this.name = name;
36
+ this.graph = new DirectedGraph();
37
+ }
38
+ connect(to, from, relation) {
39
+ this.graph.mergeEdge(to, from, { type: relation });
40
+ }
41
+ }
42
+ export class TesterantoFeatures {
43
+ constructor(features, graphs) {
44
+ this.features = features;
45
+ this.graphs = graphs;
46
+ }
47
+ networks() {
48
+ return [
49
+ ...this.graphs.undirected.values(),
50
+ ...this.graphs.directed.values(),
51
+ ...this.graphs.dags.values()
52
+ ];
53
+ }
54
+ toObj() {
55
+ return {
56
+ features: Object.entries(this.features).map(([name, feature]) => {
57
+ return Object.assign(Object.assign({}, feature), { inNetworks: this.networks().filter((network) => {
58
+ return network.graph.hasNode(feature.name);
59
+ }).map((network) => {
60
+ return {
61
+ network: network.name,
62
+ neighbors: network.graph.neighbors(feature.name)
63
+ };
64
+ }) });
65
+ }),
66
+ networks: this.networks().map((network) => {
67
+ return Object.assign({}, network);
68
+ })
69
+ };
70
+ }
71
+ }
@@ -0,0 +1,16 @@
1
+ import { TesterantoFeatures } from "./Features";
2
+ export declare type ICollateMode = 'on' | 'off' | 'watch' | `serve` | `watch+serve` | `dev`;
3
+ export declare type IBaseConfig = {
4
+ clearScreen: boolean;
5
+ collateMode: ICollateMode;
6
+ features: TesterantoFeatures;
7
+ loaders: any[];
8
+ minify: boolean;
9
+ outbase: string;
10
+ outdir: string;
11
+ ports: string[];
12
+ collateEntry: string;
13
+ runMode: boolean;
14
+ tests: string[];
15
+ buildMode: 'on' | 'off' | 'watch';
16
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,57 @@
1
+ import pm2 from 'pm2';
2
+ import { TesterantoFeatures } from "./Features";
3
+ import { ICollateMode } from "./IBaseConfig";
4
+ import { IBaseConfig } from "./index.mjs";
5
+ declare type IPm2Process = {
6
+ process: {
7
+ namespace: string;
8
+ versioning: object;
9
+ name: string;
10
+ pm_id: number;
11
+ };
12
+ data: {
13
+ testResourceRequirement: {
14
+ ports: number;
15
+ };
16
+ };
17
+ at: string;
18
+ };
19
+ export default class Scheduler {
20
+ project: ITProject;
21
+ ports: Record<string, string>;
22
+ jobs: Record<string, {
23
+ aborter: () => any;
24
+ cancellablePromise: string;
25
+ }>;
26
+ queue: IPm2Process[];
27
+ spinCycle: number;
28
+ spinAnimation: string;
29
+ pm2: typeof pm2;
30
+ summary: Record<string, boolean | undefined>;
31
+ mode: `up` | `down`;
32
+ constructor(project: ITProject);
33
+ private checkForShutDown;
34
+ abort(pm2Proc: IPm2Process): Promise<void>;
35
+ private spinner;
36
+ private push;
37
+ private pop;
38
+ private releaseTestResources;
39
+ shutdown(): void;
40
+ }
41
+ export declare class ITProject {
42
+ buildMode: 'on' | 'off' | 'watch';
43
+ clearScreen: boolean;
44
+ collateEntry: string;
45
+ collateMode: ICollateMode;
46
+ features: TesterantoFeatures;
47
+ loaders: any[];
48
+ minify: boolean;
49
+ outbase: string;
50
+ outdir: string;
51
+ ports: string[];
52
+ runMode: boolean;
53
+ tests: string[];
54
+ getEntryPoints(): string[];
55
+ constructor(config: IBaseConfig);
56
+ }
57
+ export {};
@@ -0,0 +1,391 @@
1
+ import fsExists from 'fs.promises.exists';
2
+ import pm2 from 'pm2';
3
+ import { spawn } from 'child_process';
4
+ import path from "path";
5
+ import esbuild from "esbuild";
6
+ import { createServer, request } from 'http';
7
+ const TIMEOUT = 2000;
8
+ const OPEN_PORT = '';
9
+ const clients = [];
10
+ const hotReload = (ectx, collateDir, port) => {
11
+ ectx.serve({ servedir: collateDir, host: "localhost" }).then(() => {
12
+ if (port) {
13
+ createServer((req, res) => {
14
+ const { url, method, headers } = req;
15
+ if (req.url === '/esbuild')
16
+ return clients.push(
17
+ /* @ts-ignore:next-line */
18
+ res.writeHead(200, {
19
+ 'Content-Type': 'text/event-stream',
20
+ 'Cache-Control': 'no-cache',
21
+ Connection: 'keep-alive',
22
+ }));
23
+ /* @ts-ignore:next-line */
24
+ const path = ~url.split('/').pop().indexOf('.') ? url : `/index.html`; //for PWA with router
25
+ req.pipe(request({ hostname: '0.0.0.0', port, path, method, headers }, (prxRes) => {
26
+ /* @ts-ignore:next-line */
27
+ res.writeHead(prxRes.statusCode, prxRes.headers);
28
+ prxRes.pipe(res, { end: true });
29
+ }), { end: true });
30
+ }).listen(port);
31
+ setTimeout(() => {
32
+ console.log("tick");
33
+ const op = { darwin: ['open'], linux: ['xdg-open'], win32: ['cmd', '/c', 'start'] };
34
+ const ptf = process.platform;
35
+ if (clients.length === 0)
36
+ spawn(op[ptf][0], [...[op[ptf].slice(1)], `http://localhost:${port}`]);
37
+ }, 1000); //open the default browser only if it is not opened yet
38
+ }
39
+ });
40
+ };
41
+ export default class Scheduler {
42
+ constructor(project) {
43
+ this.spinCycle = 0;
44
+ this.spinAnimation = "←↖↑↗→↘↓↙";
45
+ this.project = project;
46
+ this.queue = [];
47
+ this.jobs = {};
48
+ this.ports = {};
49
+ Object.values(this.project.ports).forEach((port) => {
50
+ this.ports[port] = OPEN_PORT;
51
+ });
52
+ this.mode = `up`;
53
+ this.summary = {};
54
+ pm2.connect(async (err) => {
55
+ if (err) {
56
+ console.error(err);
57
+ process.exit(-1);
58
+ }
59
+ else {
60
+ console.log(`pm2 is connected`);
61
+ }
62
+ this.pm2 = pm2;
63
+ const makePath = (fPath) => {
64
+ const ext = path.extname(fPath);
65
+ const x = "./" + project.outdir + "/" + fPath.replace(ext, '') + ".mjs";
66
+ return path.resolve(x);
67
+ };
68
+ const bootInterval = setInterval(async () => {
69
+ const filesToLookup = this.project.getEntryPoints().map((f) => {
70
+ const filepath = makePath(f);
71
+ return {
72
+ filepath,
73
+ exists: fsExists(filepath)
74
+ };
75
+ });
76
+ const allFilesExist = (await Promise.all(filesToLookup.map((f) => f.exists))).every((b) => b);
77
+ if (!allFilesExist) {
78
+ console.log(this.spinner(), "waiting for files to be bundled...", filesToLookup);
79
+ }
80
+ else {
81
+ clearInterval(bootInterval);
82
+ this.project.getEntryPoints().reduce((m, inputFilePath) => {
83
+ const script = makePath(inputFilePath);
84
+ this.summary[inputFilePath] = undefined;
85
+ const partialTestResourceByCommandLineArg = `'${JSON.stringify({
86
+ fs: path.resolve(process.cwd(), project.outdir, inputFilePath)
87
+ })}'`;
88
+ m[inputFilePath] = pm2.start({
89
+ script,
90
+ name: inputFilePath,
91
+ autorestart: false,
92
+ watch: [script],
93
+ args: partialTestResourceByCommandLineArg
94
+ }, (err, proc) => {
95
+ if (err) {
96
+ console.error(err);
97
+ return pm2.disconnect();
98
+ }
99
+ else {
100
+ }
101
+ });
102
+ return m;
103
+ }, {});
104
+ pm2.launchBus((err, pm2_bus) => {
105
+ pm2_bus.on('testeranto:hola', (packet) => {
106
+ // console.log("hola", packet);
107
+ this.push(packet);
108
+ });
109
+ pm2_bus.on('testeranto:adios', (packet) => {
110
+ // console.log("adios", packet);
111
+ this.releaseTestResources(packet);
112
+ });
113
+ });
114
+ setInterval(async () => {
115
+ if (this.project.clearScreen) {
116
+ console.clear();
117
+ }
118
+ console.log(`# of processes in queue:`, this.queue.length, "/", this.project.getEntryPoints().length);
119
+ console.log(`summary:`, this.summary);
120
+ console.log(`ports:`, this.ports);
121
+ // pm2.list((err, procs) => {
122
+ // procs.forEach((proc) => {
123
+ // console.log(proc.name, proc.pid, proc.pm_id, proc.monit)
124
+ // })
125
+ // });
126
+ this.pop();
127
+ this.checkForShutDown();
128
+ console.log(this.spinner(), this.mode === `up` ? `press "q" to initiate graceful shutdown` : `please wait while testeranto shuts down gracefully...`);
129
+ }, TIMEOUT).unref();
130
+ }
131
+ }, TIMEOUT).unref();
132
+ });
133
+ }
134
+ // this is called every cycle
135
+ // if there are no running processes, none waiting and we are in shutdown mode,
136
+ // write the summary to a file and end self with summarized exit code
137
+ checkForShutDown() {
138
+ const sums = Object.entries(this.summary).filter((s) => s[1] !== undefined);
139
+ }
140
+ async abort(pm2Proc) {
141
+ this.pm2.stop(pm2Proc.process.pm_id, (err, proc) => {
142
+ console.error(err);
143
+ });
144
+ }
145
+ spinner() {
146
+ this.spinCycle = (this.spinCycle + 1) % this.spinAnimation.length;
147
+ return this.spinAnimation[this.spinCycle];
148
+ }
149
+ push(process) {
150
+ this.queue.push(process);
151
+ }
152
+ pop() {
153
+ const p = this.queue.pop();
154
+ if (!p) {
155
+ console.log('feed me a test');
156
+ return;
157
+ }
158
+ const pid = p.process.pm_id;
159
+ const testResourceRequirement = p.data.testResourceRequirement;
160
+ // const fPath = path.resolve(this.project.resultsdir + `/` + p.process.name);
161
+ const message = {
162
+ // these fields must be present
163
+ id: p.process.pm_id,
164
+ topic: 'some topic',
165
+ type: 'process:msg',
166
+ // Data to be sent
167
+ data: {
168
+ testResourceConfiguration: {
169
+ ports: [],
170
+ // fs: fPath,
171
+ },
172
+ id: p.process.pm_id,
173
+ }
174
+ };
175
+ if ((testResourceRequirement === null || testResourceRequirement === void 0 ? void 0 : testResourceRequirement.ports) === 0) {
176
+ this.pm2.sendDataToProcessId(p.process.pm_id, message, function (err, res) {
177
+ // console.log("sendDataToProcessId", err, res, message);
178
+ });
179
+ }
180
+ if (((testResourceRequirement === null || testResourceRequirement === void 0 ? void 0 : testResourceRequirement.ports) || 0) > 0) {
181
+ // clear any port-slots associated with this job
182
+ Object.values(this.ports).forEach((jobMaybe, portNumber) => {
183
+ if (jobMaybe && jobMaybe === pid.toString()) {
184
+ this.ports[portNumber] = OPEN_PORT;
185
+ }
186
+ });
187
+ // find a list of open ports
188
+ const foundOpenPorts = Object.keys(this.ports)
189
+ .filter((p) => this.ports[p] === OPEN_PORT);
190
+ // if there are enough open port-slots...
191
+ if (foundOpenPorts.length >= testResourceRequirement.ports) {
192
+ const selectionOfPorts = foundOpenPorts.slice(0, testResourceRequirement.ports);
193
+ const message = {
194
+ // these fields must be present
195
+ id: p.process.pm_id,
196
+ topic: 'some topic',
197
+ // process:msg will be send as 'message' on target process
198
+ type: 'process:msg',
199
+ // Data to be sent
200
+ data: {
201
+ testResourceConfiguration: {
202
+ // fs: fPath,
203
+ ports: selectionOfPorts
204
+ },
205
+ id: p.process.pm_id,
206
+ }
207
+ };
208
+ console.log("mark1");
209
+ this.pm2.sendDataToProcessId(p.process.pm_id, message, function (err, res) {
210
+ });
211
+ // mark the selected ports as occupied
212
+ for (const foundOpenPort of selectionOfPorts) {
213
+ this.ports[foundOpenPort] = pid.toString();
214
+ }
215
+ }
216
+ else {
217
+ console.log(`no port was open so send the ${pid} job to the back of the queue`);
218
+ this.queue.push(p);
219
+ }
220
+ }
221
+ }
222
+ async releaseTestResources(pm2Proc) {
223
+ if (pm2Proc) {
224
+ (pm2Proc.data.testResource || [])
225
+ .forEach((p, k) => {
226
+ const jobExistsAndMatches = this.ports[p] === pm2Proc.process.pm_id.toString();
227
+ if (jobExistsAndMatches) {
228
+ this.ports[p] = OPEN_PORT;
229
+ }
230
+ });
231
+ }
232
+ else {
233
+ console.error("idk?!");
234
+ }
235
+ }
236
+ shutdown() {
237
+ this.mode = `down`;
238
+ pm2.list(((err, processes) => {
239
+ processes.forEach((proc) => {
240
+ proc.pm_id && this.pm2.stop(proc.pm_id, (err, proc) => {
241
+ console.error(err);
242
+ });
243
+ });
244
+ }));
245
+ }
246
+ }
247
+ export class ITProject {
248
+ constructor(config) {
249
+ this.buildMode = config.buildMode;
250
+ this.clearScreen = config.clearScreen;
251
+ this.collateEntry = config.collateEntry;
252
+ this.collateMode = config.collateMode;
253
+ this.features = config.features;
254
+ this.loaders = config.loaders;
255
+ this.minify = config.minify;
256
+ this.outbase = config.outbase;
257
+ this.outdir = config.outdir;
258
+ this.ports = config.ports;
259
+ this.runMode = config.runMode;
260
+ this.tests = config.tests;
261
+ const collateDir = '.';
262
+ const collateOpts = {
263
+ format: "iife",
264
+ outbase: this.outbase,
265
+ outdir: collateDir,
266
+ jsx: `transform`,
267
+ entryPoints: [
268
+ config.collateEntry
269
+ ],
270
+ bundle: true,
271
+ minify: this.minify === true,
272
+ write: true,
273
+ banner: { js: ' (() => new EventSource("/esbuild").onmessage = () => location.reload())();' },
274
+ plugins: [
275
+ {
276
+ name: 'hot-refresh',
277
+ setup(build) {
278
+ build.onEnd(result => {
279
+ console.log(`collation traspilation`, result);
280
+ /* @ts-ignore:next-line */
281
+ clients.forEach((res) => res.write('data: update\n\n'));
282
+ clients.length = 0;
283
+ // console.log(error ? error : '...')
284
+ });
285
+ },
286
+ }
287
+ ]
288
+ };
289
+ const testOpts = {
290
+ platform: 'node',
291
+ format: "esm",
292
+ outbase: this.outbase,
293
+ outdir: this.outdir,
294
+ jsx: `transform`,
295
+ entryPoints: this.getEntryPoints().map((sourcefile) => sourcefile),
296
+ bundle: true,
297
+ minify: this.minify === true,
298
+ write: true,
299
+ outExtension: { '.js': '.mjs' },
300
+ packages: 'external',
301
+ plugins: [
302
+ ...(this.loaders || []),
303
+ {
304
+ name: 'testeranto-redirect',
305
+ setup(build) {
306
+ build.onResolve({ filter: /^.*\/testeranto\/$/ }, args => {
307
+ return { path: path.join(process.cwd(), `..`, 'node_modules', `testeranto`) };
308
+ });
309
+ },
310
+ }
311
+ ],
312
+ };
313
+ console.log("buildMode -", this.buildMode);
314
+ console.log("runMode -", this.runMode);
315
+ console.log("collateMode -", this.collateMode);
316
+ if (this.buildMode === 'on') {
317
+ /* @ts-ignore:next-line */
318
+ esbuild.build(testOpts).then(async (eBuildResult) => {
319
+ console.log("ts tests", eBuildResult);
320
+ });
321
+ }
322
+ else if (this.buildMode === 'watch') {
323
+ /* @ts-ignore:next-line */
324
+ esbuild.context(testOpts).then(async (ectx) => {
325
+ ectx.watch();
326
+ });
327
+ }
328
+ else {
329
+ console.log("skipping 'build' phase");
330
+ }
331
+ if (this.runMode) {
332
+ const scheduler = new Scheduler(this);
333
+ process.stdin.on('keypress', (str, key) => {
334
+ if (key.name === 'q') {
335
+ // process.stdin.setRawMode(false);
336
+ console.log("Shutting down gracefully...");
337
+ scheduler.shutdown();
338
+ }
339
+ if (key.ctrl && key.name === 'c') {
340
+ console.log("Shutting down ungracefully!");
341
+ process.exit(-1);
342
+ }
343
+ });
344
+ }
345
+ else {
346
+ console.log("skipping 'run' phase");
347
+ }
348
+ if (this.collateMode === 'on') {
349
+ esbuild.build(collateOpts).then(async (eBuildResult) => {
350
+ console.log("ts collation", eBuildResult);
351
+ });
352
+ }
353
+ else if (this.collateMode === 'watch') {
354
+ esbuild.context(collateOpts).then(async (ectx) => {
355
+ ectx.watch();
356
+ });
357
+ }
358
+ else if (this.collateMode === 'serve') {
359
+ esbuild.context(collateOpts).then((esbuildContext) => {
360
+ hotReload(esbuildContext, collateDir);
361
+ });
362
+ }
363
+ else if (this.collateMode === 'watch+serve') {
364
+ esbuild.context(collateOpts).then((esbuildContext) => {
365
+ hotReload(esbuildContext, collateDir);
366
+ esbuildContext.watch();
367
+ console.log(`serving collated reports @ ${"http://localhost:8000/"}`);
368
+ });
369
+ }
370
+ else if (this.collateMode === 'dev') {
371
+ console.log("mark2", process.cwd());
372
+ esbuild.build({
373
+ bundle: true,
374
+ entryPoints: [config.collateEntry],
375
+ format: "iife",
376
+ jsx: `transform`,
377
+ minify: this.minify === true,
378
+ outbase: this.outbase,
379
+ outdir: collateDir,
380
+ write: true,
381
+ });
382
+ }
383
+ else {
384
+ console.log("skipping 'collate' phase");
385
+ }
386
+ }
387
+ getEntryPoints() {
388
+ return Object.values(this.tests);
389
+ }
390
+ }
391
+ ;