@locustjs/test 1.6.3 → 2.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/src/Test.js ADDED
@@ -0,0 +1,88 @@
1
+ import { isFunction } from "@locustjs/base";
2
+ import TestException from "./TestException";
3
+ import Expect from "./Expect";
4
+
5
+ class Test {
6
+ constructor(name, fn) {
7
+ this.name = name;
8
+ this.fn = fn;
9
+ }
10
+ run() {
11
+ return new Promise((res) => {
12
+ const start = new Date();
13
+
14
+ if (isFunction(this.fn)) {
15
+ const _expect = new Expect();
16
+
17
+ try {
18
+ const _result = this.fn((x) => {
19
+ _expect.value = x;
20
+
21
+ return _expect;
22
+ });
23
+
24
+ if (_result && isFunction(_result.then)) {
25
+ _result
26
+ .then((result) => {
27
+ res({
28
+ success: true,
29
+ test: this.name,
30
+ result,
31
+ time: new Date() - start,
32
+ expected: _expect.expected,
33
+ });
34
+ })
35
+ .catch((ex) => {
36
+ res({
37
+ success: false,
38
+ test: this.name,
39
+ time: new Date() - start,
40
+ expected: _expect.expected,
41
+ err: new TestException({
42
+ message: `test '${this.name}' failed.`,
43
+ code: 501,
44
+ status: "failed",
45
+ innerException: ex,
46
+ }),
47
+ });
48
+ });
49
+ } else {
50
+ res({
51
+ success: true,
52
+ test: this.name,
53
+ time: new Date() - start,
54
+ result: _result,
55
+ expected: _expect.expected,
56
+ });
57
+ }
58
+ } catch (ex) {
59
+ res({
60
+ success: false,
61
+ test: this.name,
62
+ time: new Date() - start,
63
+ expected: _expect.expected,
64
+ err: new TestException({
65
+ message: `test '${this.name}' failed.`,
66
+ code: 501,
67
+ status: "failed",
68
+ innerException: ex,
69
+ }),
70
+ });
71
+ }
72
+ } else {
73
+ res({
74
+ success: false,
75
+ test: this.name,
76
+ time: new Date() - start,
77
+ err: new TestException({
78
+ message: `test '${this.name}' does not have a function to be called.`,
79
+ code: 500,
80
+ status: "no-func",
81
+ }),
82
+ });
83
+ }
84
+ });
85
+ }
86
+ }
87
+
88
+ export default Test;
@@ -0,0 +1,5 @@
1
+ import { Exception } from "@locustjs/exception";
2
+
3
+ class TestException extends Exception {}
4
+
5
+ export default TestException;
@@ -0,0 +1,331 @@
1
+ import {
2
+ isBool,
3
+ isObject,
4
+ isFunction,
5
+ isArray,
6
+ isIterable,
7
+ } from "@locustjs/base";
8
+ import fs from "fs";
9
+ import path from "path";
10
+ import TestException from "./TestException";
11
+ import Test from "./Test";
12
+
13
+ const reset = "\x1b[0m";
14
+ const bright = "\x1b[1m";
15
+ const dim = "\x1b[2m";
16
+ const underscore = "\x1b[4m";
17
+ const blink = "\x1b[5m";
18
+ const reverse = "\x1b[7m";
19
+ const hidden = "\x1b[8m";
20
+
21
+ const fgBlack = "\x1b[30m";
22
+ const fgRed = "\x1b[31m";
23
+ const fgGreen = "\x1b[32m";
24
+ const fgYellow = "\x1b[33m";
25
+ const fgBlue = "\x1b[34m";
26
+ const fgMagenta = "\x1b[35m";
27
+ const fgCyan = "\x1b[36m";
28
+ const fgWhite = "\x1b[37m";
29
+ const fgGray = "\x1b[90m";
30
+
31
+ const bgBlack = "\x1b[40m";
32
+ const bgRed = "\x1b[41m";
33
+ const bgGreen = "\x1b[42m";
34
+ const bgYellow = "\x1b[43m";
35
+ const bgBlue = "\x1b[44m";
36
+ const bgMagenta = "\x1b[45m";
37
+ const bgCyan = "\x1b[46m";
38
+ const bgWhite = "\x1b[47m";
39
+ const bgGray = "\x1b[100m";
40
+
41
+ class TestRunner {
42
+ constructor() {
43
+ this._init();
44
+ }
45
+ _init() {
46
+ this._passed = 0;
47
+ this._failed = 0;
48
+ this._faulted = 0;
49
+ this._unknown = 0;
50
+ this._results = [];
51
+ this._errors = [];
52
+ }
53
+ async _runSingle(test, onProgress, i) {
54
+ if (isFunction(onProgress)) {
55
+ try {
56
+ onProgress({ source: this, test, index: i });
57
+ } catch (ex) {
58
+ this._errors.push({
59
+ index: i,
60
+ test: test.name,
61
+ err: new TestException({
62
+ message: `onProgress failed for test '${test.name} at index ${i}'.`,
63
+ code: 1500,
64
+ status: "progress-failed",
65
+ innerException: ex,
66
+ }),
67
+ });
68
+ }
69
+ }
70
+ if (!(test instanceof Test)) {
71
+ this._unknown++;
72
+
73
+ this._errors.push({
74
+ index: i,
75
+ err: new TestException({
76
+ message: `Given test is not an instance of '@locustjs/test:Test' class.`,
77
+ code: 1504,
78
+ status: "invalid-test",
79
+ }),
80
+ });
81
+ } else {
82
+ const tr = await test.run();
83
+ this._results.push(tr);
84
+
85
+ if (isObject(tr.err)) {
86
+ if (!tr.expected) {
87
+ this._faulted++;
88
+ } else {
89
+ this._failed++;
90
+ }
91
+ } else if (!tr.expected) {
92
+ this._unknown++;
93
+ } else if (tr.success) {
94
+ this._passed++;
95
+ } else {
96
+ this._failed++;
97
+ }
98
+ }
99
+ }
100
+ get result() {
101
+ return {
102
+ passed: this._passed,
103
+ failed: this._failed,
104
+ faulted: this._faulted,
105
+ results: this._results,
106
+ errors: this._errors,
107
+ };
108
+ }
109
+ run(tests, onProgress) {
110
+ this._init();
111
+
112
+ return new Promise((res) => {
113
+ if (tests) {
114
+ if (tests instanceof Test) {
115
+ tests = [tests];
116
+ }
117
+
118
+ if (isArray(tests)) {
119
+ const _tests = tests
120
+ .map((test) => {
121
+ let _test = test;
122
+
123
+ if (isArray(test)) {
124
+ if (test.length == 2) {
125
+ _test = new Test(test[0], test[1]);
126
+ }
127
+ }
128
+
129
+ return _test;
130
+ })
131
+ .map((test, i) => this._runSingle(test, onProgress, i));
132
+
133
+ Promise.all(_tests)
134
+ .then((_) => res(this.result))
135
+ .catch((ex) => {
136
+ this._errors.push({
137
+ err: new TestException({
138
+ message: `not all tests succeeded. check errors.`,
139
+ code: 1503,
140
+ status: "partial-finished",
141
+ innerException: ex,
142
+ }),
143
+ });
144
+
145
+ res(this.result);
146
+ });
147
+ } else {
148
+ this._errors.push({
149
+ err: new TestException({
150
+ message: `invalid tests. expected array or a single test.`,
151
+ code: 1502,
152
+ status: "invalid-tests",
153
+ }),
154
+ });
155
+
156
+ res(this.result);
157
+ }
158
+ } else {
159
+ this._errors.push({
160
+ err: new TestException({
161
+ message: `no tests given to be ran.`,
162
+ code: 1501,
163
+ status: "no-tests",
164
+ }),
165
+ });
166
+
167
+ res(this.result);
168
+ }
169
+ });
170
+ }
171
+ _getTime(time) {
172
+ return `${time / 1000} sec`;
173
+ }
174
+ report(detailed) {
175
+ let time = 0;
176
+
177
+ console.log("Finished.\n");
178
+
179
+ for (let i = 0; i < this._results.length; i++) {
180
+ const testResult = this._results[i];
181
+ const t = `(${this._getTime(testResult.time)})`;
182
+
183
+ if (detailed) {
184
+ let message = "\n" + (i + 1) + ". ";
185
+ let err = isObject(testResult.err)
186
+ ? testResult.err.toString().split("\n")
187
+ : [];
188
+
189
+ err = err
190
+ .map(
191
+ (msg, i) =>
192
+ `\t${
193
+ i == err.length - 1
194
+ ? `${fgYellow}`
195
+ : `${fgGray}error ${testResult.err.code}: `
196
+ }${msg}${reset}`
197
+ )
198
+ .join("\n");
199
+
200
+ if (isObject(testResult.err)) {
201
+ if (!testResult.expected) {
202
+ message += `${bright}${fgWhite}${testResult.test}: ${fgYellow}faulted${reset} ${t}`;
203
+ message += "\n";
204
+ message += `${fgGray}${err} ${reset}`;
205
+ } else {
206
+ message += `${bright}${fgWhite}${testResult.test}: ${fgRed}failed${reset} ${t}`;
207
+ message += "\n";
208
+ message += `${fgGray}${err} ${reset}`;
209
+ }
210
+ } else if (!testResult.expected) {
211
+ message += `${bright}${fgWhite}${testResult.test}: ${fgMagenta}expect not used${reset} ${t}`;
212
+
213
+ if (testResult.err) {
214
+ message += "\n";
215
+ message += `${fgGray}${err} ${reset}`;
216
+ }
217
+ } else if (testResult.success) {
218
+ message += `${fgWhite}${testResult.test}: ${fgGreen}passed${reset} ${t}`;
219
+ } else {
220
+ message += `${bright}${fgWhite}${testResult.test}: ${fgRed}failed${reset} ${t}`;
221
+ message += "\n";
222
+ message += `${fgGray}${err} ${reset}`;
223
+ }
224
+
225
+ console.log(message);
226
+ }
227
+
228
+ time += testResult.time;
229
+ }
230
+
231
+ if (detailed && this._errors.length) {
232
+ console.log("Errors:");
233
+
234
+ for (let error of this._errors) {
235
+ if (error.index !== undefined) {
236
+ console.log(
237
+ `${error.index}. ${
238
+ error.test
239
+ }: ${error.err.innerException.toString()}`
240
+ );
241
+ } else {
242
+ console.log(`${error.err.toString()}`);
243
+ }
244
+ }
245
+ }
246
+
247
+ const text =
248
+ (detailed ? "\n" : "") +
249
+ `${bright}Number of tests: ${reset}${this._results.length}` +
250
+ "\n" +
251
+ `${bright}Total Time: ${reset}${time / 1000} sec` +
252
+ "\n\n" +
253
+ (this._passed > 0
254
+ ? `${fgGreen} ${this._passed} test(s) passed${reset}`
255
+ : `0 tests passed${reset}`) +
256
+ ", " +
257
+ (this._failed > 0
258
+ ? `${fgRed}${this._failed} test(s) failed${reset}`
259
+ : `0 tests failed${reset}`) +
260
+ (this._faulted > 0
261
+ ? `, ${fgYellow}${this._faulted} test(s) faulted${reset}`
262
+ : ``) +
263
+ (this._unknown > 0
264
+ ? `, ${fgMagenta}${this._unknown} test(s) are unknown${reset}`
265
+ : ``) +
266
+ "\n";
267
+
268
+ console.log(text);
269
+ }
270
+ log(filename) {
271
+ const content = JSON.stringify(
272
+ {
273
+ results: this._results,
274
+ errors: this._errors,
275
+ },
276
+ null,
277
+ "\t"
278
+ );
279
+
280
+ if (filename == null) {
281
+ const d = new Date();
282
+
283
+ const year = d.getFullYear().toString().padStart(4, "0");
284
+ const month = (d.getMonth() + 1).toString().padStart(2, "0");
285
+ const day = d.getDate().toString().padStart(2, "0");
286
+ const hours = d.getHours().toString().padStart(2, "0");
287
+ const minutes = d.getMinutes().toString().padStart(2, "0");
288
+ const seconds = d.getSeconds().toString().padStart(2, "0");
289
+
290
+ filename = `test-${year}-${month}-${day}-${hours}${minutes}${seconds}.json`;
291
+ }
292
+
293
+ const filepath = path.join(process.cwd(), filename);
294
+
295
+ try {
296
+ fs.writeFileSync(filepath, content);
297
+
298
+ console.log(`tests outcome wrote in ${filename}.`);
299
+ } catch (ex) {
300
+ console.log("writing tests' report failed.\n" + ex);
301
+ }
302
+ }
303
+ async test(...tests) {
304
+ const lastArg = tests[tests.length - 1];
305
+ const detailed = tests.length && isBool(lastArg) ? lastArg : false;
306
+ let _tests = [];
307
+
308
+ for (let i = 0; i < tests.length; i++) {
309
+ const t = tests[i];
310
+
311
+ if (i != tests.length - 1 || !isBool(t)) {
312
+ if (isIterable(t)) {
313
+ _tests = [..._tests, ...t];
314
+ }
315
+ }
316
+ }
317
+
318
+ const result = await this.run(_tests);
319
+
320
+ this.report(detailed || result.failed > 0);
321
+
322
+ return { runner: this, result };
323
+ }
324
+ static start(...tests) {
325
+ const tr = new TestRunner();
326
+
327
+ return tr.test(...tests);
328
+ }
329
+ }
330
+
331
+ export default TestRunner;