@api-client/core 0.3.4 → 0.3.7
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/build/browser.d.ts +6 -0
- package/build/browser.js +15 -0
- package/build/browser.js.map +1 -1
- package/build/index.d.ts +14 -2
- package/build/index.js +26 -2
- package/build/index.js.map +1 -1
- package/build/src/lib/calculators/DataCalculator.d.ts +27 -0
- package/build/src/lib/calculators/DataCalculator.js +88 -0
- package/build/src/lib/calculators/DataCalculator.js.map +1 -0
- package/build/src/lib/fs/Fs.d.ts +52 -0
- package/build/src/lib/fs/Fs.js +245 -0
- package/build/src/lib/fs/Fs.js.map +1 -0
- package/build/src/lib/parsers/UrlEncoder.d.ts +51 -0
- package/build/src/lib/parsers/UrlEncoder.js +74 -0
- package/build/src/lib/parsers/UrlEncoder.js.map +1 -0
- package/build/src/lib/parsers/UrlParser.d.ts +104 -0
- package/build/src/lib/parsers/UrlParser.js +189 -0
- package/build/src/lib/parsers/UrlParser.js.map +1 -0
- package/build/src/lib/parsers/UrlValueParser.d.ts +92 -0
- package/build/src/lib/parsers/UrlValueParser.js +172 -0
- package/build/src/lib/parsers/UrlValueParser.js.map +1 -0
- package/build/src/lib/timers/Timers.d.ts +5 -0
- package/build/src/lib/timers/Timers.js +10 -0
- package/build/src/lib/timers/Timers.js.map +1 -0
- package/build/src/mocking/ProjectMock.d.ts +13 -0
- package/build/src/mocking/ProjectMock.js +16 -0
- package/build/src/mocking/ProjectMock.js.map +1 -0
- package/build/src/mocking/lib/Request.d.ts +32 -0
- package/build/src/mocking/lib/Request.js +63 -0
- package/build/src/mocking/lib/Request.js.map +1 -0
- package/build/src/mocking/lib/Response.d.ts +33 -0
- package/build/src/mocking/lib/Response.js +79 -0
- package/build/src/mocking/lib/Response.js.map +1 -0
- package/build/src/models/ErrorResponse.d.ts +5 -4
- package/build/src/models/ErrorResponse.js +18 -5
- package/build/src/models/ErrorResponse.js.map +1 -1
- package/build/src/models/SerializableError.d.ts +30 -0
- package/build/src/models/SerializableError.js +63 -0
- package/build/src/models/SerializableError.js.map +1 -0
- package/build/src/runtime/http-engine/ArcEngine.js +8 -4
- package/build/src/runtime/http-engine/ArcEngine.js.map +1 -1
- package/build/src/runtime/http-engine/HttpEngine.d.ts +3 -3
- package/build/src/runtime/http-engine/HttpEngine.js +3 -3
- package/build/src/runtime/http-engine/HttpEngine.js.map +1 -1
- package/build/src/runtime/http-engine/NodeEngine.js +9 -4
- package/build/src/runtime/http-engine/NodeEngine.js.map +1 -1
- package/build/src/runtime/http-engine/NodeEngineDirect.js +8 -2
- package/build/src/runtime/http-engine/NodeEngineDirect.js.map +1 -1
- package/build/src/runtime/node/BaseRunner.d.ts +21 -0
- package/build/src/runtime/node/BaseRunner.js +27 -0
- package/build/src/runtime/node/BaseRunner.js.map +1 -0
- package/build/src/runtime/node/ProjectParallelRunner.d.ts +81 -0
- package/build/src/runtime/node/ProjectParallelRunner.js +173 -0
- package/build/src/runtime/node/ProjectParallelRunner.js.map +1 -0
- package/build/src/runtime/node/ProjectRequestRunner.d.ts +125 -0
- package/build/src/runtime/node/ProjectRequestRunner.js +185 -0
- package/build/src/runtime/node/ProjectRequestRunner.js.map +1 -0
- package/build/src/runtime/node/ProjectRunner.d.ts +166 -64
- package/build/src/runtime/node/ProjectRunner.js +191 -139
- package/build/src/runtime/node/ProjectRunner.js.map +1 -1
- package/build/src/runtime/node/ProjectRunnerWorker.d.ts +1 -0
- package/build/src/runtime/node/ProjectRunnerWorker.js +58 -0
- package/build/src/runtime/node/ProjectRunnerWorker.js.map +1 -0
- package/build/src/runtime/node/ProjectSerialRunner.d.ts +11 -0
- package/build/src/runtime/node/ProjectSerialRunner.js +34 -0
- package/build/src/runtime/node/ProjectSerialRunner.js.map +1 -0
- package/build/src/runtime/reporters/ProjectRunCliReporter.d.ts +7 -0
- package/build/src/runtime/reporters/ProjectRunCliReporter.js +73 -0
- package/build/src/runtime/reporters/ProjectRunCliReporter.js.map +1 -0
- package/build/src/runtime/reporters/Reporter.d.ts +62 -0
- package/build/src/runtime/reporters/Reporter.js +98 -0
- package/build/src/runtime/reporters/Reporter.js.map +1 -0
- package/build/src/testing/TestCliHelper.d.ts +29 -0
- package/build/src/testing/TestCliHelper.js +80 -0
- package/build/src/testing/TestCliHelper.js.map +1 -0
- package/build/src/testing/getPort.d.ts +52 -0
- package/build/src/testing/getPort.js +169 -0
- package/build/src/testing/getPort.js.map +1 -0
- package/package.json +2 -1
- package/src/lib/calculators/DataCalculator.ts +91 -0
- package/src/lib/fs/Fs.ts +258 -0
- package/src/lib/parsers/UrlEncoder.ts +74 -0
- package/src/lib/parsers/UrlParser.ts +201 -0
- package/src/lib/parsers/UrlValueParser.ts +211 -0
- package/src/lib/timers/Timers.ts +9 -0
- package/src/mocking/LegacyInterfaces.ts +1 -1
- package/src/mocking/ProjectMock.ts +20 -0
- package/src/mocking/lib/Request.ts +85 -0
- package/src/mocking/lib/Response.ts +101 -0
- package/src/models/ErrorResponse.ts +20 -8
- package/src/models/SerializableError.ts +80 -0
- package/src/runtime/http-engine/ArcEngine.ts +8 -4
- package/src/runtime/http-engine/HttpEngine.ts +5 -5
- package/src/runtime/http-engine/NodeEngine.ts +10 -5
- package/src/runtime/http-engine/NodeEngineDirect.ts +9 -3
- package/src/runtime/node/BaseRunner.ts +29 -0
- package/src/runtime/node/ProjectParallelRunner.ts +234 -0
- package/src/runtime/node/ProjectRequestRunner.ts +281 -0
- package/src/runtime/node/ProjectRunner.ts +281 -182
- package/src/runtime/node/ProjectRunnerWorker.ts +62 -0
- package/src/runtime/node/ProjectSerialRunner.ts +36 -0
- package/src/runtime/reporters/ProjectRunCliReporter.ts +79 -0
- package/src/runtime/reporters/Reporter.ts +142 -0
- package/src/testing/TestCliHelper.ts +87 -0
- package/src/testing/getPort.ts +212 -0
- package/build/src/runtime/http-engine/Errors.d.ts +0 -10
- package/build/src/runtime/http-engine/Errors.js +0 -14
- package/build/src/runtime/http-engine/Errors.js.map +0 -1
- package/src/runtime/http-engine/Errors.ts +0 -13
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
interface IErrorOptions {
|
|
2
|
+
cause?: Error;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export interface ISerializedError {
|
|
6
|
+
message: string;
|
|
7
|
+
stack?: string;
|
|
8
|
+
code?: string | number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* An error that serializes!
|
|
13
|
+
* It is a copy of the normal definition of an error but allows
|
|
14
|
+
* to serialize the value via the toJSON() function and restore previous values
|
|
15
|
+
* with the `new()` function.
|
|
16
|
+
*/
|
|
17
|
+
export class SerializableError {
|
|
18
|
+
message: string;
|
|
19
|
+
code?: number | string;
|
|
20
|
+
private stackValue?: string;
|
|
21
|
+
get stack(): string | undefined {
|
|
22
|
+
return this.stackValue;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get name(): string {
|
|
26
|
+
return 'SerializableError';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
constructor();
|
|
30
|
+
constructor(error: Error);
|
|
31
|
+
constructor(message: string);
|
|
32
|
+
constructor(message: string, options: IErrorOptions);
|
|
33
|
+
constructor(message: string, code?: number | string);
|
|
34
|
+
|
|
35
|
+
constructor(message?: string | Error, options: IErrorOptions | number | string = {}) {
|
|
36
|
+
if (typeof message === 'string') {
|
|
37
|
+
this.message = message;
|
|
38
|
+
} else if (message) {
|
|
39
|
+
this.message = message.message;
|
|
40
|
+
this.stackValue = message.stack;
|
|
41
|
+
} else {
|
|
42
|
+
this.message = '';
|
|
43
|
+
}
|
|
44
|
+
if (typeof options === 'string' || typeof options === 'number') {
|
|
45
|
+
this.code = options;
|
|
46
|
+
} else if (options.cause && options.cause.stack) {
|
|
47
|
+
this.stackValue = options.cause.stack;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
new(values: ISerializedError): void {
|
|
52
|
+
if (values.message) {
|
|
53
|
+
this.message = values.message;
|
|
54
|
+
}
|
|
55
|
+
if (values.stack) {
|
|
56
|
+
this.stackValue = values.stack;
|
|
57
|
+
}
|
|
58
|
+
if (values.code || values.code === 0) {
|
|
59
|
+
this.code = values.code;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
toJSON(): ISerializedError {
|
|
64
|
+
const { message, stackValue: stack, code } = this;
|
|
65
|
+
const result: ISerializedError = {
|
|
66
|
+
message,
|
|
67
|
+
};
|
|
68
|
+
if (stack) {
|
|
69
|
+
result.stack = stack;
|
|
70
|
+
}
|
|
71
|
+
if (code || code === 0) {
|
|
72
|
+
result.code = code;
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
toString(): string {
|
|
78
|
+
return this.message;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -6,10 +6,10 @@ import { HttpEngine, HttpEngineOptions, HeadersReceivedDetail } from './HttpEngi
|
|
|
6
6
|
import { IRequestLog } from 'src/models/RequestLog.js';
|
|
7
7
|
import { IHttpRequest } from '../../models/HttpRequest.js';
|
|
8
8
|
import { ArcResponse } from '../../models/ArcResponse.js';
|
|
9
|
+
import { SerializableError } from '../../models/SerializableError.js';
|
|
9
10
|
import { Headers } from '../../lib/headers/Headers.js';
|
|
10
11
|
import { PayloadSupport } from './PayloadSupport.js';
|
|
11
12
|
import { addContentLength, getPort } from './RequestUtils.js';
|
|
12
|
-
import { NetError } from './Errors.js';
|
|
13
13
|
import { INtlmAuthorization } from '../../models/Authorization.js';
|
|
14
14
|
import { NtlmAuth, INtlmAuthConfig } from './ntlm/NtlmAuth.js';
|
|
15
15
|
import { PayloadSerializer } from '../../lib/transformers/PayloadSerializer.js';
|
|
@@ -80,7 +80,11 @@ export class ArcEngine extends HttpEngine {
|
|
|
80
80
|
const message = await this.prepareMessage();
|
|
81
81
|
await this.writeMessage(message);
|
|
82
82
|
} catch (cause) {
|
|
83
|
-
const
|
|
83
|
+
const e = cause as any;
|
|
84
|
+
const err = new SerializableError(e.message, { cause: e });
|
|
85
|
+
if (e.code || e.code === 0) {
|
|
86
|
+
err.code = e.code as string;
|
|
87
|
+
}
|
|
84
88
|
this.abort();
|
|
85
89
|
this._errorRequest({
|
|
86
90
|
message: err.message,
|
|
@@ -459,7 +463,7 @@ export class ArcEngine extends HttpEngine {
|
|
|
459
463
|
if (!this.currentResponse) {
|
|
460
464
|
this.logger.error(`Connection closed without receiving any data.`);
|
|
461
465
|
// The parser haven't found the end of message so there was no message!
|
|
462
|
-
const e = new
|
|
466
|
+
const e = new SerializableError('Connection closed without receiving any data', 100);
|
|
463
467
|
this._errorRequest(e);
|
|
464
468
|
} else {
|
|
465
469
|
// There is an issue with the response. Size mismatch? Anyway,
|
|
@@ -1012,7 +1016,7 @@ export class ArcEngine extends HttpEngine {
|
|
|
1012
1016
|
} else if (res.statusCode !== 200) {
|
|
1013
1017
|
this.logger.debug(`The proxy tunnel ended with ${res.statusCode} status code. Erroring request.`);
|
|
1014
1018
|
connectRequest.destroy();
|
|
1015
|
-
const e = new
|
|
1019
|
+
const e = new SerializableError('A tunnel connection through the proxy could not be established', 111);
|
|
1016
1020
|
reject(e);
|
|
1017
1021
|
} else {
|
|
1018
1022
|
this.logger.debug(`Established a proxy tunnel.`);
|
|
@@ -19,6 +19,7 @@ import { HttpResponse } from '../../models/HttpResponse.js';
|
|
|
19
19
|
import { ResponseRedirect } from '../../models/ResponseRedirect.js';
|
|
20
20
|
import { RequestLog, IRequestLog } from '../../models/RequestLog.js';
|
|
21
21
|
import { RequestTime } from '../../models/RequestTime.js';
|
|
22
|
+
import { SerializableError } from '../../models/SerializableError.js';
|
|
22
23
|
import { ResponseAuthorization } from '../../models/ResponseAuthorization.js';
|
|
23
24
|
import { DefaultLogger } from '../../lib/logging/DefaultLogger.js';
|
|
24
25
|
import { ILogger, Logger } from '../../lib/logging/Logger.js';
|
|
@@ -26,7 +27,6 @@ import { Headers } from '../../lib/headers/Headers.js';
|
|
|
26
27
|
import * as RequestUtils from './RequestUtils.js';
|
|
27
28
|
import { Cookies } from '../../lib/cookies/Cookies.js';
|
|
28
29
|
import { HttpErrorCodes } from './HttpErrorCodes.js';
|
|
29
|
-
import { NetError } from './Errors.js';
|
|
30
30
|
|
|
31
31
|
export interface HttpEngineOptions extends IRequestBaseConfig {
|
|
32
32
|
/**
|
|
@@ -188,7 +188,7 @@ export abstract class HttpEngine extends EventEmitter {
|
|
|
188
188
|
auth?: IRequestAuthState;
|
|
189
189
|
|
|
190
190
|
protected mainResolver?: (log: IRequestLog) => void;
|
|
191
|
-
protected mainRejecter?: (err:
|
|
191
|
+
protected mainRejecter?: (err: SerializableError) => void;
|
|
192
192
|
[mainPromiseSymbol]?: Promise<IRequestLog>;
|
|
193
193
|
|
|
194
194
|
constructor(request: IHttpRequest, opts: HttpEngineOptions = {}) {
|
|
@@ -562,7 +562,7 @@ Check your request parameters.`);
|
|
|
562
562
|
message = opts.message;
|
|
563
563
|
}
|
|
564
564
|
message = message || 'Unknown error occurred';
|
|
565
|
-
const error = new
|
|
565
|
+
const error = new SerializableError(message, opts.code);
|
|
566
566
|
const log = RequestLog.fromRequest(this.sentRequest);
|
|
567
567
|
const response = ErrorResponse.fromError(error);
|
|
568
568
|
log.response = response;
|
|
@@ -855,14 +855,14 @@ Check your request parameters.`);
|
|
|
855
855
|
*
|
|
856
856
|
* @param log Either the request execution log or an error.
|
|
857
857
|
*/
|
|
858
|
-
protected finalizeRequest(log: RequestLog |
|
|
858
|
+
protected finalizeRequest(log: RequestLog | SerializableError): void {
|
|
859
859
|
const { mainRejecter, mainResolver } = this;
|
|
860
860
|
if (!mainRejecter || !mainResolver) {
|
|
861
861
|
// console.error(`Trying to finalize the request but the main resolver is not set.`);
|
|
862
862
|
return;
|
|
863
863
|
}
|
|
864
864
|
|
|
865
|
-
if (log instanceof
|
|
865
|
+
if (log instanceof SerializableError) {
|
|
866
866
|
mainRejecter(log);
|
|
867
867
|
} else {
|
|
868
868
|
mainResolver(log.toJSON());
|
|
@@ -8,10 +8,10 @@ import { HttpEngine, HttpEngineOptions, ResponseErrorInit, HeadersReceivedDetail
|
|
|
8
8
|
import { IHttpRequest } from '../../models/HttpRequest.js';
|
|
9
9
|
import { ArcResponse } from '../../models/ArcResponse.js';
|
|
10
10
|
import { IRequestLog } from '../../models/RequestLog.js';
|
|
11
|
+
import { SerializableError } from '../../models/SerializableError.js';
|
|
11
12
|
import { Headers } from '../../lib/headers/Headers.js';
|
|
12
13
|
import { PayloadSupport } from './PayloadSupport.js';
|
|
13
14
|
import { addContentLength, getPort } from './RequestUtils.js';
|
|
14
|
-
import { NetError } from './Errors.js';
|
|
15
15
|
import { INtlmAuthorization } from '../../models/Authorization.js';
|
|
16
16
|
import { NtlmAuth, INtlmAuthConfig } from './ntlm/NtlmAuth.js';
|
|
17
17
|
|
|
@@ -59,9 +59,14 @@ export class NodeEngine extends HttpEngine {
|
|
|
59
59
|
request.setTimeout(timeout);
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
-
} catch (
|
|
63
|
-
console.warn(
|
|
64
|
-
|
|
62
|
+
} catch (cause) {
|
|
63
|
+
console.warn(cause);
|
|
64
|
+
const e = cause as any;
|
|
65
|
+
const err = new SerializableError(e.message, { cause: e });
|
|
66
|
+
if (e.code || e.code === 0) {
|
|
67
|
+
err.code = e.code as string;
|
|
68
|
+
}
|
|
69
|
+
this.finalizeRequest(e);
|
|
65
70
|
}
|
|
66
71
|
}
|
|
67
72
|
|
|
@@ -593,7 +598,7 @@ export class NodeEngine extends HttpEngine {
|
|
|
593
598
|
});
|
|
594
599
|
} else {
|
|
595
600
|
connectRequest.destroy();
|
|
596
|
-
const e = new
|
|
601
|
+
const e = new SerializableError('A tunnel connection through the proxy could not be established', 111);
|
|
597
602
|
reject(e);
|
|
598
603
|
}
|
|
599
604
|
});
|
|
@@ -9,6 +9,7 @@ import { IRequestLog } from 'src/models/RequestLog.js';
|
|
|
9
9
|
import { IHttpRequest } from '../../models/HttpRequest.js';
|
|
10
10
|
import { ArcResponse } from '../../models/ArcResponse.js';
|
|
11
11
|
import { Headers } from '../../lib/headers/Headers.js';
|
|
12
|
+
import { SerializableError } from '../../models/SerializableError.js';
|
|
12
13
|
import { PayloadSupport } from './PayloadSupport.js';
|
|
13
14
|
import { addContentLength, getPort } from './RequestUtils.js';
|
|
14
15
|
|
|
@@ -85,9 +86,14 @@ export class NodeEngineDirect extends HttpEngine {
|
|
|
85
86
|
if (timeout > 0) {
|
|
86
87
|
request.setTimeout(timeout);
|
|
87
88
|
}
|
|
88
|
-
} catch (
|
|
89
|
-
console.warn(
|
|
90
|
-
|
|
89
|
+
} catch (cause) {
|
|
90
|
+
console.warn(cause);
|
|
91
|
+
const e = cause as any;
|
|
92
|
+
const err = new SerializableError(e.message, { cause: e });
|
|
93
|
+
if (e.code || e.code === 0) {
|
|
94
|
+
err.code = e.code as string;
|
|
95
|
+
}
|
|
96
|
+
this.finalizeRequest(e);
|
|
91
97
|
}
|
|
92
98
|
}
|
|
93
99
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import { IProjectExecutionIteration, IProjectExecutionLog } from '../reporters/Reporter.js';
|
|
3
|
+
|
|
4
|
+
export abstract class BaseRunner extends EventEmitter {
|
|
5
|
+
/**
|
|
6
|
+
* Iteration start time.
|
|
7
|
+
*/
|
|
8
|
+
protected startTime?: number;
|
|
9
|
+
/**
|
|
10
|
+
* Iteration end time.
|
|
11
|
+
*/
|
|
12
|
+
protected endTime?: number;
|
|
13
|
+
/**
|
|
14
|
+
* A list of already executed iterations.
|
|
15
|
+
*/
|
|
16
|
+
protected executed: IProjectExecutionIteration[] = [];
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Creates the report of the execution.
|
|
20
|
+
*/
|
|
21
|
+
protected async createReport(): Promise<IProjectExecutionLog> {
|
|
22
|
+
const log: IProjectExecutionLog = {
|
|
23
|
+
started: this.startTime as number,
|
|
24
|
+
ended: this.endTime as number,
|
|
25
|
+
iterations: this.executed,
|
|
26
|
+
};
|
|
27
|
+
return log;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import cluster, { Worker } from 'cluster';
|
|
2
|
+
import { cpus } from 'os';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { HttpProject, IHttpProject } from '../../models/HttpProject.js';
|
|
6
|
+
import { IProjectRunnerOptions } from './ProjectRunner.js';
|
|
7
|
+
import { IProjectExecutionLog, IProjectExecutionIteration } from '../reporters/Reporter.js';
|
|
8
|
+
import { BaseRunner } from './BaseRunner.js';
|
|
9
|
+
|
|
10
|
+
const numCPUs = cpus().length;
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
|
|
13
|
+
export type WorkerStatus = 'initializing' | 'ready' | 'running' | 'finished' | 'error';
|
|
14
|
+
|
|
15
|
+
export interface IWorkerInfo {
|
|
16
|
+
/**
|
|
17
|
+
* Whether the worker is online.
|
|
18
|
+
*/
|
|
19
|
+
online: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* The number of iterations the worker is performing.
|
|
22
|
+
*/
|
|
23
|
+
iterations: number;
|
|
24
|
+
/**
|
|
25
|
+
* The current status of the worker.
|
|
26
|
+
*/
|
|
27
|
+
status: WorkerStatus;
|
|
28
|
+
/**
|
|
29
|
+
* Optional error message received from the worker.
|
|
30
|
+
*/
|
|
31
|
+
message?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface WorkerInfoInternal extends IWorkerInfo {
|
|
35
|
+
worker: Worker;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface IWorkerMessage {
|
|
39
|
+
cmd: string;
|
|
40
|
+
data?: unknown;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface IProjectParallelRunnerOptions extends IProjectRunnerOptions {
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface IProjectParallelWorkerOptions extends IProjectRunnerOptions {
|
|
47
|
+
project: IHttpProject;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface ProjectParallelRunner {
|
|
51
|
+
/**
|
|
52
|
+
* Dispatched when a status of a worker change.
|
|
53
|
+
* This can be used to render the current status.
|
|
54
|
+
*/
|
|
55
|
+
on(event: 'status', listener: (info: IWorkerInfo[]) => void): this;
|
|
56
|
+
/**
|
|
57
|
+
* Dispatched when a status of a worker change.
|
|
58
|
+
* This can be used to render the current status.
|
|
59
|
+
*/
|
|
60
|
+
once(event: 'status', listener: (info: IWorkerInfo[]) => void): this;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Runs a project in parallel.
|
|
65
|
+
* It creates a number of workers determined by the number of CPUs available on the current machine
|
|
66
|
+
* and the number of iterations defined in the configuration options.
|
|
67
|
+
*
|
|
68
|
+
* When the number of iterations is greater then the number of CPUs then
|
|
69
|
+
* the program distributes the remaining iterations among created workers.
|
|
70
|
+
*
|
|
71
|
+
* The program dispatched the `status` event. It is dispatched each time when the worker status
|
|
72
|
+
* change. This event can be user to refresh the UI to reflect the newest state.
|
|
73
|
+
*/
|
|
74
|
+
export class ProjectParallelRunner extends BaseRunner {
|
|
75
|
+
project: HttpProject;
|
|
76
|
+
options: IProjectParallelRunnerOptions;
|
|
77
|
+
workers: WorkerInfoInternal[] = [];
|
|
78
|
+
private mainResolver?: (report: IProjectExecutionLog) => void;
|
|
79
|
+
private mainRejecter?: (err: Error) => void;
|
|
80
|
+
|
|
81
|
+
constructor(project: HttpProject, opts: IProjectParallelRunnerOptions = {}) {
|
|
82
|
+
super();
|
|
83
|
+
this.project = project;
|
|
84
|
+
this.options = opts || {};
|
|
85
|
+
|
|
86
|
+
this._exitHandler = this._exitHandler.bind(this);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
execute(): Promise<IProjectExecutionLog> {
|
|
90
|
+
return new Promise((resolve, reject) => {
|
|
91
|
+
this.mainResolver = resolve;
|
|
92
|
+
this.mainRejecter = reject;
|
|
93
|
+
this._execute();
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private _execute(): void {
|
|
98
|
+
try {
|
|
99
|
+
cluster.setupPrimary({
|
|
100
|
+
exec: join(__dirname, 'ProjectRunnerWorker.js'),
|
|
101
|
+
silent: true,
|
|
102
|
+
});
|
|
103
|
+
const { iterations = 1 } = this.options;
|
|
104
|
+
const poolSize = Math.min(iterations, numCPUs);
|
|
105
|
+
for (let i = 0; i < poolSize; i++) {
|
|
106
|
+
const worker = cluster.fork();
|
|
107
|
+
this.setupWorker(worker);
|
|
108
|
+
}
|
|
109
|
+
this.distributeIterations();
|
|
110
|
+
this.emit('status', this.getStatusWorkers());
|
|
111
|
+
cluster.on('exit', this._exitHandler);
|
|
112
|
+
} catch (e) {
|
|
113
|
+
const cause = e as Error;
|
|
114
|
+
this.mainRejecter!(cause);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private getStatusWorkers(): IWorkerInfo[] {
|
|
119
|
+
const { workers } = this;
|
|
120
|
+
const result: IWorkerInfo[] = [];
|
|
121
|
+
workers.forEach((info) => {
|
|
122
|
+
const cp = { ...info } as any;
|
|
123
|
+
delete cp.worker;
|
|
124
|
+
result.push(cp);
|
|
125
|
+
});
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private distributeIterations(): void {
|
|
130
|
+
const workers = this.workers.length;
|
|
131
|
+
const { iterations = 1 } = this.options;
|
|
132
|
+
let iterationsRemaining = iterations - workers;
|
|
133
|
+
let currentIndex = 0;
|
|
134
|
+
while (iterationsRemaining > 0) {
|
|
135
|
+
this.workers[currentIndex].iterations += 1;
|
|
136
|
+
iterationsRemaining--;
|
|
137
|
+
currentIndex++;
|
|
138
|
+
if (currentIndex + 1 === workers) {
|
|
139
|
+
currentIndex = 0;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private setupWorker(worker: Worker): void {
|
|
145
|
+
this.workers.push({
|
|
146
|
+
worker,
|
|
147
|
+
online: false,
|
|
148
|
+
iterations: 1,
|
|
149
|
+
status: 'initializing',
|
|
150
|
+
});
|
|
151
|
+
worker.on('message', this._messageHandler.bind(this, worker));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private _messageHandler(worker: Worker, message: IWorkerMessage): void {
|
|
155
|
+
switch (message.cmd) {
|
|
156
|
+
case 'online': this.setOnline(worker); break;
|
|
157
|
+
case 'result': this.setRunResult(worker, message); break;
|
|
158
|
+
case 'error': this.setRunError(worker, message); break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private _exitHandler(worker: Worker): void {
|
|
163
|
+
const info = this.workers.find(i => i.worker === worker);
|
|
164
|
+
if (!info) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
this.finishWhenReady();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private setOnline(worker: Worker): void {
|
|
171
|
+
const info = this.workers.find(i => i.worker === worker);
|
|
172
|
+
if (!info) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
info.online = true;
|
|
176
|
+
info.status = 'ready';
|
|
177
|
+
this.runWhenReady();
|
|
178
|
+
this.emit('status', this.getStatusWorkers());
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private setRunResult(worker: Worker, message: IWorkerMessage): void {
|
|
182
|
+
const reports = message.data as IProjectExecutionIteration[];
|
|
183
|
+
this.executed = this.executed.concat(reports);
|
|
184
|
+
worker.destroy();
|
|
185
|
+
const info = this.workers.find(i => i.worker === worker);
|
|
186
|
+
if (!info) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
info.status = 'finished';
|
|
190
|
+
this.emit('status', this.getStatusWorkers());
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private runWhenReady(): void {
|
|
194
|
+
const waiting = this.workers.some(i => !i.online);
|
|
195
|
+
if (waiting) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
this.startTime = Date.now();
|
|
199
|
+
this.workers.forEach((info) => {
|
|
200
|
+
const opts: IProjectParallelWorkerOptions = { ...this.options, project: this.project.toJSON() };
|
|
201
|
+
opts.iterations = info.iterations;
|
|
202
|
+
info.status = 'running';
|
|
203
|
+
info.worker.send({
|
|
204
|
+
cmd: 'run',
|
|
205
|
+
data: opts,
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private async finishWhenReady(): Promise<void> {
|
|
211
|
+
if (this.endTime) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const working = this.workers.some(i => !['finished', 'error'].includes(i.status));
|
|
215
|
+
if (working || !this.mainResolver) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
this.endTime = Date.now();
|
|
219
|
+
const report = await this.createReport();
|
|
220
|
+
this.mainResolver(report);
|
|
221
|
+
cluster.off('exit', this._exitHandler);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private setRunError(worker: Worker, message: IWorkerMessage): void {
|
|
225
|
+
worker.destroy();
|
|
226
|
+
const info = this.workers.find(i => i.worker === worker);
|
|
227
|
+
if (!info) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
info.status = 'error';
|
|
231
|
+
info.message = message.data as string;
|
|
232
|
+
this.emit('status', this.getStatusWorkers());
|
|
233
|
+
}
|
|
234
|
+
}
|