@git.zone/tstest 1.1.0 → 1.3.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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/index.js +42 -4
- package/dist_ts/tstest.classes.tap.combinator.d.ts +3 -0
- package/dist_ts/tstest.classes.tap.combinator.js +14 -40
- package/dist_ts/tstest.classes.tap.parser.d.ts +3 -1
- package/dist_ts/tstest.classes.tap.parser.js +43 -18
- package/dist_ts/tstest.classes.tstest.d.ts +6 -3
- package/dist_ts/tstest.classes.tstest.js +32 -33
- package/dist_ts/tstest.logging.d.ts +53 -0
- package/dist_ts/tstest.logging.js +288 -0
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/index.ts +45 -3
- package/ts/tstest.classes.tap.combinator.ts +19 -41
- package/ts/tstest.classes.tap.parser.ts +45 -47
- package/ts/tstest.classes.tstest.ts +38 -35
- package/ts/tstest.logging.ts +358 -0
- package/readme.plan.md +0 -51
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { coloredString as cs } from '@push.rocks/consolecolor';
|
|
2
|
+
import * as plugins from './tstest.plugins.js';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
export class TsTestLogger {
|
|
6
|
+
constructor(options = {}) {
|
|
7
|
+
this.fileResults = [];
|
|
8
|
+
this.currentFileResult = null;
|
|
9
|
+
this.currentTestLogFile = null;
|
|
10
|
+
this.options = options;
|
|
11
|
+
this.startTime = Date.now();
|
|
12
|
+
}
|
|
13
|
+
format(text, color) {
|
|
14
|
+
if (this.options.noColor || !color) {
|
|
15
|
+
return text;
|
|
16
|
+
}
|
|
17
|
+
return cs(text, color);
|
|
18
|
+
}
|
|
19
|
+
log(message) {
|
|
20
|
+
if (this.options.json) {
|
|
21
|
+
// For JSON mode, skip console output
|
|
22
|
+
// JSON output is handled by logJson method
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
console.log(message);
|
|
26
|
+
// Log to the current test file log if we're in a test and --logfile is specified
|
|
27
|
+
if (this.currentTestLogFile) {
|
|
28
|
+
this.logToTestFile(message);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
logToFile(message) {
|
|
32
|
+
// This method is no longer used since we use logToTestFile for individual test logs
|
|
33
|
+
// Keeping it for potential future use with a global log file
|
|
34
|
+
}
|
|
35
|
+
logToTestFile(message) {
|
|
36
|
+
try {
|
|
37
|
+
// Remove ANSI color codes for file logging
|
|
38
|
+
const cleanMessage = message.replace(/\u001b\[[0-9;]*m/g, '');
|
|
39
|
+
// Append to test log file
|
|
40
|
+
fs.appendFileSync(this.currentTestLogFile, cleanMessage + '\n');
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
// Silently fail to avoid disrupting the test run
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
logJson(data) {
|
|
47
|
+
const jsonString = JSON.stringify(data);
|
|
48
|
+
console.log(jsonString);
|
|
49
|
+
// Also log to test file if --logfile is specified
|
|
50
|
+
if (this.currentTestLogFile) {
|
|
51
|
+
this.logToTestFile(jsonString);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Section separators
|
|
55
|
+
sectionStart(title) {
|
|
56
|
+
if (this.options.quiet || this.options.json)
|
|
57
|
+
return;
|
|
58
|
+
this.log(this.format(`\n━━━ ${title} ━━━`, 'cyan'));
|
|
59
|
+
}
|
|
60
|
+
sectionEnd() {
|
|
61
|
+
if (this.options.quiet || this.options.json)
|
|
62
|
+
return;
|
|
63
|
+
this.log(this.format('─'.repeat(50), 'dim'));
|
|
64
|
+
}
|
|
65
|
+
// Progress indication
|
|
66
|
+
progress(current, total, message) {
|
|
67
|
+
if (this.options.quiet || this.options.json)
|
|
68
|
+
return;
|
|
69
|
+
const percentage = Math.round((current / total) * 100);
|
|
70
|
+
const filled = Math.round((current / total) * 20);
|
|
71
|
+
const empty = 20 - filled;
|
|
72
|
+
this.log(this.format(`\n📊 Progress: ${current}/${total} (${percentage}%)`, 'cyan'));
|
|
73
|
+
this.log(this.format(`[${'█'.repeat(filled)}${'░'.repeat(empty)}] ${message}`, 'dim'));
|
|
74
|
+
}
|
|
75
|
+
// Test discovery
|
|
76
|
+
testDiscovery(count, pattern, executionMode) {
|
|
77
|
+
if (this.options.json) {
|
|
78
|
+
this.logJson({ event: 'discovery', count, pattern, executionMode });
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (this.options.quiet) {
|
|
82
|
+
this.log(`Found ${count} tests`);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
this.log(this.format(`\n🔍 Test Discovery`, 'bold'));
|
|
86
|
+
this.log(this.format(` Mode: ${executionMode}`, 'dim'));
|
|
87
|
+
this.log(this.format(` Pattern: ${pattern}`, 'dim'));
|
|
88
|
+
this.log(this.format(` Found: ${count} test file(s)`, 'green'));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Test execution
|
|
92
|
+
testFileStart(filename, runtime, index, total) {
|
|
93
|
+
this.currentFileResult = {
|
|
94
|
+
file: filename,
|
|
95
|
+
passed: 0,
|
|
96
|
+
failed: 0,
|
|
97
|
+
total: 0,
|
|
98
|
+
duration: 0,
|
|
99
|
+
tests: []
|
|
100
|
+
};
|
|
101
|
+
// Only set up test log file if --logfile option is specified
|
|
102
|
+
if (this.options.logFile) {
|
|
103
|
+
const baseFilename = path.basename(filename, '.ts');
|
|
104
|
+
this.currentTestLogFile = path.join('.nogit', 'testlogs', `${baseFilename}.log`);
|
|
105
|
+
// Ensure the directory exists
|
|
106
|
+
const logDir = path.dirname(this.currentTestLogFile);
|
|
107
|
+
if (!fs.existsSync(logDir)) {
|
|
108
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
109
|
+
}
|
|
110
|
+
// Clear the log file for this test
|
|
111
|
+
fs.writeFileSync(this.currentTestLogFile, '');
|
|
112
|
+
}
|
|
113
|
+
if (this.options.json) {
|
|
114
|
+
this.logJson({ event: 'fileStart', filename, runtime, index, total });
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (this.options.quiet)
|
|
118
|
+
return;
|
|
119
|
+
this.log(this.format(`\n▶️ ${filename} (${index}/${total})`, 'blue'));
|
|
120
|
+
this.log(this.format(` Runtime: ${runtime}`, 'dim'));
|
|
121
|
+
}
|
|
122
|
+
testResult(testName, passed, duration, error) {
|
|
123
|
+
if (this.currentFileResult) {
|
|
124
|
+
this.currentFileResult.tests.push({ name: testName, passed, duration, error });
|
|
125
|
+
this.currentFileResult.total++;
|
|
126
|
+
if (passed) {
|
|
127
|
+
this.currentFileResult.passed++;
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
this.currentFileResult.failed++;
|
|
131
|
+
}
|
|
132
|
+
this.currentFileResult.duration += duration;
|
|
133
|
+
}
|
|
134
|
+
if (this.options.json) {
|
|
135
|
+
this.logJson({ event: 'testResult', testName, passed, duration, error });
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const icon = passed ? '✅' : '❌';
|
|
139
|
+
const color = passed ? 'green' : 'red';
|
|
140
|
+
if (this.options.quiet) {
|
|
141
|
+
this.log(`${icon} ${testName}`);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
this.log(this.format(` ${icon} ${testName} (${duration}ms)`, color));
|
|
145
|
+
if (error && !passed) {
|
|
146
|
+
this.log(this.format(` ${error}`, 'red'));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
testFileEnd(passed, failed, duration) {
|
|
151
|
+
if (this.currentFileResult) {
|
|
152
|
+
this.fileResults.push(this.currentFileResult);
|
|
153
|
+
this.currentFileResult = null;
|
|
154
|
+
}
|
|
155
|
+
if (this.options.json) {
|
|
156
|
+
this.logJson({ event: 'fileEnd', passed, failed, duration });
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (!this.options.quiet) {
|
|
160
|
+
const total = passed + failed;
|
|
161
|
+
const status = failed === 0 ? 'PASSED' : 'FAILED';
|
|
162
|
+
const color = failed === 0 ? 'green' : 'red';
|
|
163
|
+
this.log(this.format(` Summary: ${passed}/${total} ${status}`, color));
|
|
164
|
+
}
|
|
165
|
+
// Clear the current test log file reference only if using --logfile
|
|
166
|
+
if (this.options.logFile) {
|
|
167
|
+
this.currentTestLogFile = null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// TAP output forwarding (for TAP protocol messages)
|
|
171
|
+
tapOutput(message, isError = false) {
|
|
172
|
+
if (this.options.json)
|
|
173
|
+
return;
|
|
174
|
+
// Never show raw TAP protocol messages in console
|
|
175
|
+
// They are already processed by TapParser and shown in our format
|
|
176
|
+
// Always log to test file if --logfile is specified
|
|
177
|
+
if (this.currentTestLogFile) {
|
|
178
|
+
this.logToTestFile(` ${message}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// Console output from test files (non-TAP output)
|
|
182
|
+
testConsoleOutput(message) {
|
|
183
|
+
if (this.options.json)
|
|
184
|
+
return;
|
|
185
|
+
// Show console output from test files only in verbose mode
|
|
186
|
+
if (this.options.verbose) {
|
|
187
|
+
this.log(this.format(` ${message}`, 'dim'));
|
|
188
|
+
}
|
|
189
|
+
// Always log to test file if --logfile is specified
|
|
190
|
+
if (this.currentTestLogFile) {
|
|
191
|
+
this.logToTestFile(` ${message}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// Browser console
|
|
195
|
+
browserConsole(message, level = 'log') {
|
|
196
|
+
if (this.options.json) {
|
|
197
|
+
this.logJson({ event: 'browserConsole', message, level });
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (!this.options.quiet) {
|
|
201
|
+
const prefix = level === 'error' ? '🌐❌' : '🌐';
|
|
202
|
+
const color = level === 'error' ? 'red' : 'magenta';
|
|
203
|
+
this.log(this.format(` ${prefix} ${message}`, color));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// Final summary
|
|
207
|
+
summary() {
|
|
208
|
+
const totalDuration = Date.now() - this.startTime;
|
|
209
|
+
const summary = {
|
|
210
|
+
totalFiles: this.fileResults.length,
|
|
211
|
+
totalTests: this.fileResults.reduce((sum, r) => sum + r.total, 0),
|
|
212
|
+
totalPassed: this.fileResults.reduce((sum, r) => sum + r.passed, 0),
|
|
213
|
+
totalFailed: this.fileResults.reduce((sum, r) => sum + r.failed, 0),
|
|
214
|
+
totalDuration,
|
|
215
|
+
fileResults: this.fileResults
|
|
216
|
+
};
|
|
217
|
+
if (this.options.json) {
|
|
218
|
+
this.logJson({ event: 'summary', summary });
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (this.options.quiet) {
|
|
222
|
+
const status = summary.totalFailed === 0 ? 'PASSED' : 'FAILED';
|
|
223
|
+
this.log(`\nSummary: ${summary.totalPassed}/${summary.totalTests} | ${totalDuration}ms | ${status}`);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
// Detailed summary
|
|
227
|
+
this.log(this.format('\n📊 Test Summary', 'bold'));
|
|
228
|
+
this.log(this.format('┌────────────────────────────────┐', 'dim'));
|
|
229
|
+
this.log(this.format(`│ Total Files: ${summary.totalFiles.toString().padStart(14)} │`, 'white'));
|
|
230
|
+
this.log(this.format(`│ Total Tests: ${summary.totalTests.toString().padStart(14)} │`, 'white'));
|
|
231
|
+
this.log(this.format(`│ Passed: ${summary.totalPassed.toString().padStart(14)} │`, 'green'));
|
|
232
|
+
this.log(this.format(`│ Failed: ${summary.totalFailed.toString().padStart(14)} │`, summary.totalFailed > 0 ? 'red' : 'green'));
|
|
233
|
+
this.log(this.format(`│ Duration: ${totalDuration.toString().padStart(14)}ms │`, 'white'));
|
|
234
|
+
this.log(this.format('└────────────────────────────────┘', 'dim'));
|
|
235
|
+
// File results
|
|
236
|
+
if (summary.totalFailed > 0) {
|
|
237
|
+
this.log(this.format('\n❌ Failed Tests:', 'red'));
|
|
238
|
+
this.fileResults.forEach(fileResult => {
|
|
239
|
+
if (fileResult.failed > 0) {
|
|
240
|
+
this.log(this.format(`\n ${fileResult.file}`, 'yellow'));
|
|
241
|
+
fileResult.tests.filter(t => !t.passed).forEach(test => {
|
|
242
|
+
this.log(this.format(` ❌ ${test.name}`, 'red'));
|
|
243
|
+
if (test.error) {
|
|
244
|
+
this.log(this.format(` ${test.error}`, 'dim'));
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
// Performance metrics
|
|
251
|
+
if (this.options.verbose) {
|
|
252
|
+
const avgDuration = Math.round(totalDuration / summary.totalTests);
|
|
253
|
+
const slowestTest = this.fileResults
|
|
254
|
+
.flatMap(r => r.tests)
|
|
255
|
+
.sort((a, b) => b.duration - a.duration)[0];
|
|
256
|
+
this.log(this.format('\n⏱️ Performance Metrics:', 'cyan'));
|
|
257
|
+
this.log(this.format(` Average per test: ${avgDuration}ms`, 'white'));
|
|
258
|
+
if (slowestTest) {
|
|
259
|
+
this.log(this.format(` Slowest test: ${slowestTest.name} (${slowestTest.duration}ms)`, 'yellow'));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// Final status
|
|
263
|
+
const status = summary.totalFailed === 0 ? 'ALL TESTS PASSED! 🎉' : 'SOME TESTS FAILED! ❌';
|
|
264
|
+
const statusColor = summary.totalFailed === 0 ? 'green' : 'red';
|
|
265
|
+
this.log(this.format(`\n${status}`, statusColor));
|
|
266
|
+
}
|
|
267
|
+
// Error display
|
|
268
|
+
error(message, file, stack) {
|
|
269
|
+
if (this.options.json) {
|
|
270
|
+
this.logJson({ event: 'error', message, file, stack });
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (this.options.quiet) {
|
|
274
|
+
console.error(`ERROR: ${message}`);
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
this.log(this.format('\n⚠️ Error', 'red'));
|
|
278
|
+
if (file)
|
|
279
|
+
this.log(this.format(` File: ${file}`, 'yellow'));
|
|
280
|
+
this.log(this.format(` ${message}`, 'red'));
|
|
281
|
+
if (stack && this.options.verbose) {
|
|
282
|
+
this.log(this.format(` Stack:`, 'dim'));
|
|
283
|
+
this.log(this.format(stack.split('\n').map(line => ` ${line}`).join('\n'), 'dim'));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHN0ZXN0LmxvZ2dpbmcuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy90c3Rlc3QubG9nZ2luZy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsYUFBYSxJQUFJLEVBQUUsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBQy9ELE9BQU8sS0FBSyxPQUFPLE1BQU0scUJBQXFCLENBQUM7QUFDL0MsT0FBTyxLQUFLLEVBQUUsTUFBTSxJQUFJLENBQUM7QUFDekIsT0FBTyxLQUFLLElBQUksTUFBTSxNQUFNLENBQUM7QUFpQzdCLE1BQU0sT0FBTyxZQUFZO0lBT3ZCLFlBQVksVUFBc0IsRUFBRTtRQUo1QixnQkFBVyxHQUFxQixFQUFFLENBQUM7UUFDbkMsc0JBQWlCLEdBQTBCLElBQUksQ0FBQztRQUNoRCx1QkFBa0IsR0FBa0IsSUFBSSxDQUFDO1FBRy9DLElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFDO1FBQ3ZCLElBQUksQ0FBQyxTQUFTLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO0lBQzlCLENBQUM7SUFFTyxNQUFNLENBQUMsSUFBWSxFQUFFLEtBQWM7UUFDekMsSUFBSSxJQUFJLENBQUMsT0FBTyxDQUFDLE9BQU8sSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDO1lBQ25DLE9BQU8sSUFBSSxDQUFDO1FBQ2QsQ0FBQztRQUNELE9BQU8sRUFBRSxDQUFDLElBQUksRUFBRSxLQUFZLENBQUMsQ0FBQztJQUNoQyxDQUFDO0lBRU8sR0FBRyxDQUFDLE9BQWU7UUFDekIsSUFBSSxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksRUFBRSxDQUFDO1lBQ3RCLHFDQUFxQztZQUNyQywyQ0FBMkM7WUFDM0MsT0FBTztRQUNULENBQUM7UUFFRCxPQUFPLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBRXJCLGlGQUFpRjtRQUNqRixJQUFJLElBQUksQ0FBQyxrQkFBa0IsRUFBRSxDQUFDO1lBQzVCLElBQUks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
|
package/package.json
CHANGED
package/ts/00_commitinfo_data.ts
CHANGED
package/ts/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { TsTest } from './tstest.classes.tstest.js';
|
|
2
|
+
import type { LogOptions } from './tstest.logging.js';
|
|
2
3
|
|
|
3
4
|
export enum TestExecutionMode {
|
|
4
5
|
DIRECTORY = 'directory',
|
|
@@ -7,12 +8,53 @@ export enum TestExecutionMode {
|
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
export const runCli = async () => {
|
|
10
|
-
|
|
11
|
+
// Parse command line arguments
|
|
12
|
+
const args = process.argv.slice(2);
|
|
13
|
+
const logOptions: LogOptions = {};
|
|
14
|
+
let testPath: string | null = null;
|
|
15
|
+
|
|
16
|
+
// Parse options
|
|
17
|
+
for (let i = 0; i < args.length; i++) {
|
|
18
|
+
const arg = args[i];
|
|
19
|
+
|
|
20
|
+
switch (arg) {
|
|
21
|
+
case '--quiet':
|
|
22
|
+
case '-q':
|
|
23
|
+
logOptions.quiet = true;
|
|
24
|
+
break;
|
|
25
|
+
case '--verbose':
|
|
26
|
+
case '-v':
|
|
27
|
+
logOptions.verbose = true;
|
|
28
|
+
break;
|
|
29
|
+
case '--no-color':
|
|
30
|
+
logOptions.noColor = true;
|
|
31
|
+
break;
|
|
32
|
+
case '--json':
|
|
33
|
+
logOptions.json = true;
|
|
34
|
+
break;
|
|
35
|
+
case '--log-file':
|
|
36
|
+
case '--logfile':
|
|
37
|
+
logOptions.logFile = true; // Set this as a flag, not a value
|
|
38
|
+
break;
|
|
39
|
+
default:
|
|
40
|
+
if (!arg.startsWith('-')) {
|
|
41
|
+
testPath = arg;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!testPath) {
|
|
11
47
|
console.error('You must specify a test directory/file/pattern as argument. Please try again.');
|
|
48
|
+
console.error('\nUsage: tstest <path> [options]');
|
|
49
|
+
console.error('\nOptions:');
|
|
50
|
+
console.error(' --quiet, -q Minimal output');
|
|
51
|
+
console.error(' --verbose, -v Verbose output');
|
|
52
|
+
console.error(' --no-color Disable colored output');
|
|
53
|
+
console.error(' --json Output results as JSON');
|
|
54
|
+
console.error(' --logfile Write logs to .nogit/testlogs/[testfile].log');
|
|
12
55
|
process.exit(1);
|
|
13
56
|
}
|
|
14
57
|
|
|
15
|
-
const testPath = process.argv[2];
|
|
16
58
|
let executionMode: TestExecutionMode;
|
|
17
59
|
|
|
18
60
|
// Detect execution mode based on the argument
|
|
@@ -24,6 +66,6 @@ export const runCli = async () => {
|
|
|
24
66
|
executionMode = TestExecutionMode.DIRECTORY;
|
|
25
67
|
}
|
|
26
68
|
|
|
27
|
-
const tsTestInstance = new TsTest(process.cwd(), testPath, executionMode);
|
|
69
|
+
const tsTestInstance = new TsTest(process.cwd(), testPath, executionMode, logOptions);
|
|
28
70
|
await tsTestInstance.run();
|
|
29
71
|
};
|
|
@@ -6,59 +6,37 @@ import { coloredString as cs } from '@push.rocks/consolecolor';
|
|
|
6
6
|
|
|
7
7
|
import { TapParser } from './tstest.classes.tap.parser.js';
|
|
8
8
|
import * as logPrefixes from './tstest.logprefixes.js';
|
|
9
|
+
import { TsTestLogger } from './tstest.logging.js';
|
|
9
10
|
|
|
10
11
|
export class TapCombinator {
|
|
11
12
|
tapParserStore: TapParser[] = [];
|
|
13
|
+
private logger: TsTestLogger;
|
|
14
|
+
|
|
15
|
+
constructor(logger: TsTestLogger) {
|
|
16
|
+
this.logger = logger;
|
|
17
|
+
}
|
|
18
|
+
|
|
12
19
|
addTapParser(tapParserArg: TapParser) {
|
|
13
20
|
this.tapParserStore.push(tapParserArg);
|
|
14
21
|
}
|
|
15
22
|
|
|
16
23
|
evaluate() {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
let failGlobal = false;
|
|
24
|
+
// Call the logger's summary method
|
|
25
|
+
this.logger.summary();
|
|
26
|
+
|
|
27
|
+
// Check for failures
|
|
28
|
+
let failGlobal = false;
|
|
22
29
|
for (const tapParser of this.tapParserStore) {
|
|
23
|
-
if (!tapParser.expectedTests
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
logPrefixes.TsTestPrefix +
|
|
27
|
-
cs(` ${tapParser.fileName} ${plugins.figures.cross}`, 'red') +
|
|
28
|
-
` ${plugins.figures.pointer} ` +
|
|
29
|
-
`does not specify tests!`;
|
|
30
|
-
console.log(overviewString);
|
|
31
|
-
} else if (tapParser.expectedTests !== tapParser.receivedTests) {
|
|
32
|
-
failGlobal = true;
|
|
33
|
-
let overviewString =
|
|
34
|
-
logPrefixes.TsTestPrefix +
|
|
35
|
-
cs(` ${tapParser.fileName} ${plugins.figures.cross}`, 'red') +
|
|
36
|
-
` ${plugins.figures.pointer} ` +
|
|
37
|
-
tapParser.getTestOverviewAsString() +
|
|
38
|
-
`did not execute all specified tests!`;
|
|
39
|
-
console.log(overviewString);
|
|
40
|
-
} else if (tapParser.getErrorTests().length === 0) {
|
|
41
|
-
let overviewString =
|
|
42
|
-
logPrefixes.TsTestPrefix +
|
|
43
|
-
cs(` ${tapParser.fileName} ${plugins.figures.tick}`, 'green') +
|
|
44
|
-
` ${plugins.figures.pointer} ` +
|
|
45
|
-
tapParser.getTestOverviewAsString();
|
|
46
|
-
console.log(overviewString);
|
|
47
|
-
} else {
|
|
30
|
+
if (!tapParser.expectedTests ||
|
|
31
|
+
tapParser.expectedTests !== tapParser.receivedTests ||
|
|
32
|
+
tapParser.getErrorTests().length > 0) {
|
|
48
33
|
failGlobal = true;
|
|
49
|
-
|
|
50
|
-
logPrefixes.TsTestPrefix +
|
|
51
|
-
cs(` ${tapParser.fileName} ${plugins.figures.cross}`, 'red') +
|
|
52
|
-
` ${plugins.figures.pointer} ` +
|
|
53
|
-
tapParser.getTestOverviewAsString();
|
|
54
|
-
console.log(overviewString);
|
|
34
|
+
break;
|
|
55
35
|
}
|
|
56
36
|
}
|
|
57
|
-
|
|
58
|
-
if
|
|
59
|
-
|
|
60
|
-
} else {
|
|
61
|
-
console.log(cs('FINAL RESULT: FAIL!', 'red'));
|
|
37
|
+
|
|
38
|
+
// Exit with error code if tests failed
|
|
39
|
+
if (failGlobal) {
|
|
62
40
|
process.exit(1);
|
|
63
41
|
}
|
|
64
42
|
}
|
|
@@ -7,6 +7,7 @@ import { coloredString as cs } from '@push.rocks/consolecolor';
|
|
|
7
7
|
import * as plugins from './tstest.plugins.js';
|
|
8
8
|
import { TapTestResult } from './tstest.classes.tap.testresult.js';
|
|
9
9
|
import * as logPrefixes from './tstest.logprefixes.js';
|
|
10
|
+
import { TsTestLogger } from './tstest.logging.js';
|
|
10
11
|
|
|
11
12
|
export class TapParser {
|
|
12
13
|
testStore: TapTestResult[] = [];
|
|
@@ -19,11 +20,15 @@ export class TapParser {
|
|
|
19
20
|
activeTapTestResult: TapTestResult;
|
|
20
21
|
|
|
21
22
|
pretaskRegex = /^::__PRETASK:(.*)$/;
|
|
23
|
+
|
|
24
|
+
private logger: TsTestLogger;
|
|
22
25
|
|
|
23
26
|
/**
|
|
24
27
|
* the constructor for TapParser
|
|
25
28
|
*/
|
|
26
|
-
constructor(public fileName: string) {
|
|
29
|
+
constructor(public fileName: string, logger?: TsTestLogger) {
|
|
30
|
+
this.logger = logger;
|
|
31
|
+
}
|
|
27
32
|
|
|
28
33
|
private _getNewTapTestResult() {
|
|
29
34
|
this.activeTapTestResult = new TapTestResult(this.testStore.length + 1);
|
|
@@ -45,9 +50,9 @@ export class TapParser {
|
|
|
45
50
|
logLineIsTapProtocol = true;
|
|
46
51
|
const regexResult = this.expectedTestsRegex.exec(logLine);
|
|
47
52
|
this.expectedTests = parseInt(regexResult[2]);
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
if (this.logger) {
|
|
54
|
+
this.logger.tapOutput(`Expecting ${this.expectedTests} tests!`);
|
|
55
|
+
}
|
|
51
56
|
|
|
52
57
|
// initiating first TapResult
|
|
53
58
|
this._getNewTapTestResult();
|
|
@@ -55,7 +60,9 @@ export class TapParser {
|
|
|
55
60
|
logLineIsTapProtocol = true;
|
|
56
61
|
const pretaskContentMatch = this.pretaskRegex.exec(logLine);
|
|
57
62
|
if (pretaskContentMatch && pretaskContentMatch[1]) {
|
|
58
|
-
|
|
63
|
+
if (this.logger) {
|
|
64
|
+
this.logger.tapOutput(`Pretask -> ${pretaskContentMatch[1]}: Success.`);
|
|
65
|
+
}
|
|
59
66
|
}
|
|
60
67
|
} else if (this.testStatusRegex.test(logLine)) {
|
|
61
68
|
logLineIsTapProtocol = true;
|
|
@@ -73,26 +80,20 @@ export class TapParser {
|
|
|
73
80
|
|
|
74
81
|
// test for protocol error
|
|
75
82
|
if (testId !== this.activeTapTestResult.id) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
83
|
+
if (this.logger) {
|
|
84
|
+
this.logger.error('Something is strange! Test Ids are not equal!');
|
|
85
|
+
}
|
|
79
86
|
}
|
|
80
87
|
this.activeTapTestResult.setTestResult(testOk);
|
|
81
88
|
|
|
82
89
|
if (testOk) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
cs(testSubject, 'blue') +
|
|
87
|
-
` | ${cs(`${testDuration} ms`, 'orange')}`
|
|
88
|
-
);
|
|
90
|
+
if (this.logger) {
|
|
91
|
+
this.logger.testResult(testSubject, true, testDuration);
|
|
92
|
+
}
|
|
89
93
|
} else {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
cs(testSubject, 'blue') +
|
|
94
|
-
` | ${cs(`${testDuration} ms`, 'orange')}`
|
|
95
|
-
);
|
|
94
|
+
if (this.logger) {
|
|
95
|
+
this.logger.testResult(testSubject, false, testDuration);
|
|
96
|
+
}
|
|
96
97
|
}
|
|
97
98
|
}
|
|
98
99
|
|
|
@@ -100,7 +101,10 @@ export class TapParser {
|
|
|
100
101
|
if (this.activeTapTestResult) {
|
|
101
102
|
this.activeTapTestResult.addLogLine(logLine);
|
|
102
103
|
}
|
|
103
|
-
|
|
104
|
+
if (this.logger) {
|
|
105
|
+
// This is console output from the test file, not TAP protocol
|
|
106
|
+
this.logger.testConsoleOutput(logLine);
|
|
107
|
+
}
|
|
104
108
|
}
|
|
105
109
|
|
|
106
110
|
if (this.activeTapTestResult && this.activeTapTestResult.testSettled) {
|
|
@@ -172,38 +176,32 @@ export class TapParser {
|
|
|
172
176
|
|
|
173
177
|
// check wether all tests ran
|
|
174
178
|
if (this.expectedTests === this.receivedTests) {
|
|
175
|
-
|
|
176
|
-
`${
|
|
177
|
-
|
|
178
|
-
'green'
|
|
179
|
-
)}`
|
|
180
|
-
);
|
|
179
|
+
if (this.logger) {
|
|
180
|
+
this.logger.tapOutput(`${this.receivedTests} out of ${this.expectedTests} Tests completed!`);
|
|
181
|
+
}
|
|
181
182
|
} else {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
'red'
|
|
186
|
-
)}`
|
|
187
|
-
);
|
|
183
|
+
if (this.logger) {
|
|
184
|
+
this.logger.error(`Only ${this.receivedTests} out of ${this.expectedTests} completed!`);
|
|
185
|
+
}
|
|
188
186
|
}
|
|
189
187
|
if (!this.expectedTests) {
|
|
190
|
-
|
|
188
|
+
if (this.logger) {
|
|
189
|
+
this.logger.error('No tests were defined. Therefore the testfile failed!');
|
|
190
|
+
}
|
|
191
191
|
} else if (this.expectedTests !== this.receivedTests) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
'red'
|
|
196
|
-
)
|
|
197
|
-
);
|
|
192
|
+
if (this.logger) {
|
|
193
|
+
this.logger.error('The amount of received tests and expectedTests is unequal! Therefore the testfile failed');
|
|
194
|
+
}
|
|
198
195
|
} else if (this.getErrorTests().length === 0) {
|
|
199
|
-
|
|
196
|
+
if (this.logger) {
|
|
197
|
+
this.logger.tapOutput('All tests are successfull!!!');
|
|
198
|
+
this.logger.testFileEnd(this.receivedTests, 0, 0);
|
|
199
|
+
}
|
|
200
200
|
} else {
|
|
201
|
-
|
|
202
|
-
`${
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
)}`
|
|
206
|
-
);
|
|
201
|
+
if (this.logger) {
|
|
202
|
+
this.logger.tapOutput(`${this.getErrorTests().length} tests threw an error!!!`, true);
|
|
203
|
+
this.logger.testFileEnd(this.receivedTests - this.getErrorTests().length, this.getErrorTests().length, 0);
|
|
204
|
+
}
|
|
207
205
|
}
|
|
208
206
|
}
|
|
209
207
|
}
|