@travetto/test 5.0.0-rc.0 → 5.0.0-rc.10
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/README.md +12 -13
- package/package.json +8 -8
- package/src/assert/capture.ts +5 -4
- package/src/assert/check.ts +24 -33
- package/src/assert/util.ts +32 -17
- package/src/consumer/registry.ts +2 -3
- package/src/consumer/{error.ts → serialize.ts} +13 -22
- package/src/consumer/types/cumulative.ts +15 -22
- package/src/consumer/types/delegating.ts +58 -0
- package/src/consumer/types/event.ts +2 -4
- package/src/consumer/types/execution.ts +2 -4
- package/src/consumer/types/runnable.ts +12 -41
- package/src/consumer/types/tap-streamed.ts +9 -6
- package/src/consumer/types/tap.ts +5 -5
- package/src/consumer/types/xunit.ts +4 -2
- package/src/decorator/suite.ts +5 -7
- package/src/decorator/test.ts +2 -1
- package/src/execute/console.ts +1 -1
- package/src/execute/executor.ts +84 -104
- package/src/execute/phase.ts +20 -30
- package/src/execute/promise.ts +4 -4
- package/src/execute/runner.ts +34 -24
- package/src/execute/types.ts +12 -10
- package/src/execute/util.ts +61 -34
- package/src/execute/watcher.ts +34 -36
- package/src/fixture.ts +7 -2
- package/src/model/common.ts +11 -7
- package/src/model/event.ts +9 -5
- package/src/model/suite.ts +14 -4
- package/src/model/test.ts +30 -4
- package/src/registry/suite.ts +42 -39
- package/src/trv.d.ts +3 -3
- package/src/worker/child.ts +11 -18
- package/src/worker/standard.ts +18 -21
- package/src/worker/types.ts +13 -10
- package/support/cli.test.ts +20 -6
- package/support/cli.test_child.ts +1 -1
- package/support/cli.test_digest.ts +43 -0
- package/support/cli.test_direct.ts +10 -3
- package/support/cli.test_watch.ts +1 -1
- package/support/transformer.assert.ts +12 -12
- package/support/cli.test_count.ts +0 -39
- package/support/transformer.annotate.ts +0 -103
|
@@ -2,7 +2,7 @@ import { ChildCommChannel } from '@travetto/worker';
|
|
|
2
2
|
|
|
3
3
|
import { TestEvent } from '../../model/event';
|
|
4
4
|
import { TestConsumer } from '../types';
|
|
5
|
-
import {
|
|
5
|
+
import { SerializeUtil } from '../serialize';
|
|
6
6
|
import { Consumable } from '../registry';
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -12,8 +12,6 @@ import { Consumable } from '../registry';
|
|
|
12
12
|
export class ExecutionEmitter extends ChildCommChannel<TestEvent> implements TestConsumer {
|
|
13
13
|
|
|
14
14
|
onEvent(event: TestEvent): void {
|
|
15
|
-
|
|
16
|
-
ErrorUtil.serializeTestErrors(out);
|
|
17
|
-
this.send(event.type, out);
|
|
15
|
+
this.send(event.type, JSON.parse(SerializeUtil.serializeToJSON(event)));
|
|
18
16
|
}
|
|
19
17
|
}
|
|
@@ -1,62 +1,33 @@
|
|
|
1
|
-
import { TestConsumer
|
|
1
|
+
import { TestConsumer } from '../types';
|
|
2
2
|
import { TestResultsSummarizer } from './summarizer';
|
|
3
3
|
import { TestConsumerRegistry } from '../registry';
|
|
4
4
|
import { TestEvent } from '../../model/event';
|
|
5
|
+
import { DelegatingConsumer } from './delegating';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Test consumer with support for multiple nested consumers, and summarization
|
|
8
9
|
*/
|
|
9
|
-
export class RunnableTestConsumer
|
|
10
|
+
export class RunnableTestConsumer extends DelegatingConsumer {
|
|
10
11
|
/**
|
|
11
12
|
* Build a runnable test consumer given a format or a full consumer
|
|
12
13
|
*/
|
|
13
14
|
static async get(consumer: string | TestConsumer): Promise<RunnableTestConsumer> {
|
|
14
|
-
return new RunnableTestConsumer(await TestConsumerRegistry.getInstance(consumer));
|
|
15
|
+
return new RunnableTestConsumer([await TestConsumerRegistry.getInstance(consumer)]);
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
#
|
|
18
|
-
#results: TestResultsSummarizer | undefined;
|
|
18
|
+
#results?: TestResultsSummarizer;
|
|
19
19
|
|
|
20
|
-
constructor(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (!this.#results && c.onSummary) { // If expecting summary
|
|
24
|
-
this.#results = new TestResultsSummarizer();
|
|
25
|
-
}
|
|
26
|
-
c.onEvent = c.onEvent.bind(c);
|
|
27
|
-
}
|
|
20
|
+
constructor(consumers: TestConsumer[]) {
|
|
21
|
+
super(consumers);
|
|
22
|
+
this.#results = consumers.find(x => !!x.onSummary) ? new TestResultsSummarizer() : undefined;
|
|
28
23
|
}
|
|
29
24
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
await c.onStart?.(state);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
onEvent(e: TestEvent): void {
|
|
37
|
-
if (this.#results) {
|
|
38
|
-
this.#results.onEvent(e);
|
|
39
|
-
}
|
|
40
|
-
for (const c of this.#consumers) {
|
|
41
|
-
c.onEvent(e);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async summarize(): Promise<TestResultsSummarizer | undefined> {
|
|
46
|
-
if (this.#results) {
|
|
47
|
-
for (const c of this.#consumers) {
|
|
48
|
-
await c.onSummary?.(this.#results.summary);
|
|
49
|
-
}
|
|
50
|
-
return this.#results;
|
|
51
|
-
}
|
|
25
|
+
onEventDone(e: TestEvent): void {
|
|
26
|
+
this.#results?.onEvent(e);
|
|
52
27
|
}
|
|
53
28
|
|
|
54
29
|
async summarizeAsBoolean(): Promise<boolean> {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return result.summary.failed <= 0;
|
|
58
|
-
} else {
|
|
59
|
-
return true;
|
|
60
|
-
}
|
|
30
|
+
await this.summarize(this.#results?.summary);
|
|
31
|
+
return (this.#results?.summary.failed ?? 0) <= 0;
|
|
61
32
|
}
|
|
62
33
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { Util } from '@travetto/
|
|
1
|
+
import { Util, AsyncQueue } from '@travetto/runtime';
|
|
2
2
|
import { StyleUtil, Terminal, TerminalUtil } from '@travetto/terminal';
|
|
3
|
-
import { WorkQueue } from '@travetto/worker';
|
|
4
3
|
|
|
5
4
|
import { TestEvent } from '../../model/event';
|
|
6
5
|
import { TestResult } from '../../model/test';
|
|
@@ -17,7 +16,7 @@ import { TapEmitter } from './tap';
|
|
|
17
16
|
export class TapStreamedEmitter implements TestConsumer {
|
|
18
17
|
|
|
19
18
|
#terminal: Terminal;
|
|
20
|
-
#results = new
|
|
19
|
+
#results = new AsyncQueue<TestResult>();
|
|
21
20
|
#progress: Promise<unknown> | undefined;
|
|
22
21
|
#consumer: TapEmitter;
|
|
23
22
|
|
|
@@ -30,16 +29,20 @@ export class TapStreamedEmitter implements TestConsumer {
|
|
|
30
29
|
this.#consumer.onStart();
|
|
31
30
|
|
|
32
31
|
let failed = 0;
|
|
33
|
-
|
|
32
|
+
let skipped = 0;
|
|
33
|
+
let completed = 0;
|
|
34
|
+
const success = StyleUtil.getStyle({ text: '#e5e5e5', background: '#026020' }); // White on dark green
|
|
34
35
|
const fail = StyleUtil.getStyle({ text: '#e5e5e5', background: '#8b0000' }); // White on dark red
|
|
35
36
|
this.#progress = this.#terminal.streamToBottom(
|
|
36
37
|
Util.mapAsyncItr(
|
|
37
38
|
this.#results,
|
|
38
39
|
(value, idx) => {
|
|
39
40
|
failed += (value.status === 'failed' ? 1 : 0);
|
|
40
|
-
|
|
41
|
+
skipped += (value.status === 'skipped' ? 1 : 0);
|
|
42
|
+
completed += (value.status !== 'skipped' ? 1 : 0);
|
|
43
|
+
return { value: `Tests %idx/%total [${failed} failed, ${skipped} skipped] -- ${value.classId}`, total: state.testCount, idx: completed };
|
|
41
44
|
},
|
|
42
|
-
TerminalUtil.progressBarUpdater(this.#terminal, { style: () => ({ complete: failed ? fail :
|
|
45
|
+
TerminalUtil.progressBarUpdater(this.#terminal, { style: () => ({ complete: failed ? fail : success }) })
|
|
43
46
|
),
|
|
44
47
|
{ minDelay: 100 }
|
|
45
48
|
);
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { RuntimeIndex } from '@travetto/manifest';
|
|
2
1
|
import { Terminal } from '@travetto/terminal';
|
|
3
|
-
import { AppError, TimeUtil } from '@travetto/
|
|
2
|
+
import { AppError, TimeUtil, Runtime, RuntimeIndex } from '@travetto/runtime';
|
|
4
3
|
import { stringify } from 'yaml';
|
|
5
4
|
|
|
6
5
|
import { TestEvent } from '../../model/event';
|
|
7
6
|
import { SuitesSummary, TestConsumer } from '../types';
|
|
8
7
|
import { Consumable } from '../registry';
|
|
9
|
-
import {
|
|
8
|
+
import { SerializeUtil } from '../serialize';
|
|
10
9
|
import { TestResultsEnhancer, CONSOLE_ENHANCER } from '../enhancer';
|
|
11
10
|
|
|
12
11
|
/**
|
|
@@ -67,11 +66,12 @@ export class TapEmitter implements TestConsumer {
|
|
|
67
66
|
let subCount = 0;
|
|
68
67
|
for (const asrt of test.assertions) {
|
|
69
68
|
const text = asrt.message ? `${asrt.text} (${this.#enhancer.failure(asrt.message)})` : asrt.text;
|
|
69
|
+
const pth = RuntimeIndex.getFromImport(asrt.import)!.sourceFile.replace(Runtime.mainSourcePath, '.');
|
|
70
70
|
let subMessage = [
|
|
71
71
|
this.#enhancer.assertNumber(++subCount),
|
|
72
72
|
'-',
|
|
73
73
|
this.#enhancer.assertDescription(text),
|
|
74
|
-
`${this.#enhancer.assertFile(
|
|
74
|
+
`${this.#enhancer.assertFile(pth)}:${this.#enhancer.assertLine(asrt.line)}`
|
|
75
75
|
].join(' ');
|
|
76
76
|
|
|
77
77
|
if (asrt.error) {
|
|
@@ -102,7 +102,7 @@ export class TapEmitter implements TestConsumer {
|
|
|
102
102
|
// Handle error
|
|
103
103
|
if (test.status === 'failed') {
|
|
104
104
|
if (test.error && test.error.name !== 'AssertionError') {
|
|
105
|
-
const err =
|
|
105
|
+
const err = SerializeUtil.deserializeError(test.error);
|
|
106
106
|
this.logMeta({ error: err instanceof AppError ? err.toJSON() : err });
|
|
107
107
|
}
|
|
108
108
|
}
|
|
@@ -2,6 +2,8 @@ import { Writable } from 'node:stream';
|
|
|
2
2
|
|
|
3
3
|
import { stringify } from 'yaml';
|
|
4
4
|
|
|
5
|
+
import { RuntimeIndex } from '@travetto/runtime';
|
|
6
|
+
|
|
5
7
|
import { TestEvent } from '../../model/event';
|
|
6
8
|
import { SuitesSummary, TestConsumer } from '../types';
|
|
7
9
|
import { Consumable } from '../registry';
|
|
@@ -85,7 +87,7 @@ export class XunitEmitter implements TestConsumer {
|
|
|
85
87
|
failures="${suite.failed}"
|
|
86
88
|
errors="${suite.failed}"
|
|
87
89
|
skipped="${suite.skipped}"
|
|
88
|
-
file="${suite.
|
|
90
|
+
file="${RuntimeIndex.getFromImport(suite.import)!.sourceFile}"
|
|
89
91
|
>
|
|
90
92
|
${testBodies.join('\n')}
|
|
91
93
|
</testsuite>
|
|
@@ -101,7 +103,7 @@ export class XunitEmitter implements TestConsumer {
|
|
|
101
103
|
this.#stream.write(`
|
|
102
104
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
103
105
|
<testsuites
|
|
104
|
-
name="${summary.suites.length ? summary.suites[0].
|
|
106
|
+
name="${summary.suites.length ? RuntimeIndex.getFromImport(summary.suites[0].import)?.sourceFile : 'nameless'}"
|
|
105
107
|
time="${summary.duration}"
|
|
106
108
|
tests="${summary.total}"
|
|
107
109
|
failures="${summary.failed}"
|
package/src/decorator/suite.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Class, ClassInstance } from '@travetto/base';
|
|
1
|
+
import { castTo, Class, ClassInstance, describeFunction } from '@travetto/runtime';
|
|
3
2
|
|
|
4
3
|
import { SuiteRegistry } from '../registry/suite';
|
|
5
4
|
import { SuiteConfig } from '../model/suite';
|
|
@@ -24,17 +23,16 @@ export function Suite(description?: string | Partial<SuiteConfig>, ...rest: Part
|
|
|
24
23
|
Object.assign(extra, r);
|
|
25
24
|
}
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
const decorator = ((target: Class) => {
|
|
26
|
+
const dec = (target: Class): typeof target => {
|
|
29
27
|
const cfg = { description: descriptionString, ...extra };
|
|
30
|
-
if (
|
|
28
|
+
if (describeFunction(target).abstract) {
|
|
31
29
|
cfg.skip = true;
|
|
32
30
|
}
|
|
33
31
|
SuiteRegistry.register(target, cfg);
|
|
34
32
|
return target;
|
|
35
|
-
}
|
|
33
|
+
};
|
|
36
34
|
|
|
37
|
-
return
|
|
35
|
+
return castTo(dec);
|
|
38
36
|
}
|
|
39
37
|
|
|
40
38
|
function listener(phase: SuitePhase) {
|
package/src/decorator/test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ClassInstance } from '@travetto/
|
|
1
|
+
import { ClassInstance } from '@travetto/runtime';
|
|
2
2
|
|
|
3
3
|
import { SuiteRegistry } from '../registry/suite';
|
|
4
4
|
import { TestConfig } from '../model/test';
|
|
@@ -15,6 +15,7 @@ export function AssertCheck(): MethodDecorator {
|
|
|
15
15
|
* @param description The test description
|
|
16
16
|
* @augments `@travetto/test:Test`
|
|
17
17
|
* @augments `@travetto/test:AssertCheck`
|
|
18
|
+
* @augments `@travetto/runtime:DebugBreak`
|
|
18
19
|
*/
|
|
19
20
|
export function Test(): MethodDecorator;
|
|
20
21
|
export function Test(...rest: Partial<TestConfig>[]): MethodDecorator;
|
package/src/execute/console.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import util from 'node:util';
|
|
2
2
|
|
|
3
|
-
import { ConsoleEvent, ConsoleListener, ConsoleManager } from '@travetto/
|
|
3
|
+
import { ConsoleEvent, ConsoleListener, ConsoleManager } from '@travetto/runtime';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Console capturer. Hooks into the Console manager, and collects the
|
package/src/execute/executor.ts
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { AssertionError } from 'node:assert';
|
|
2
|
-
import path from 'node:path';
|
|
3
2
|
|
|
4
|
-
import {
|
|
5
|
-
import { Env, TimeUtil } from '@travetto/base';
|
|
3
|
+
import { Env, TimeUtil, Runtime, castTo } from '@travetto/runtime';
|
|
6
4
|
import { Barrier, ExecutionError } from '@travetto/worker';
|
|
7
5
|
|
|
8
6
|
import { SuiteRegistry } from '../registry/suite';
|
|
9
|
-
import { TestConfig, TestResult } from '../model/test';
|
|
10
|
-
import { SuiteConfig, SuiteResult } from '../model/suite';
|
|
7
|
+
import { TestConfig, TestResult, TestRun } from '../model/test';
|
|
8
|
+
import { SuiteConfig, SuiteFailure, SuiteResult } from '../model/suite';
|
|
11
9
|
import { TestConsumer } from '../consumer/types';
|
|
12
10
|
import { AssertCheck } from '../assert/check';
|
|
13
11
|
import { AssertCapture } from '../assert/capture';
|
|
@@ -23,12 +21,42 @@ const TEST_TIMEOUT = TimeUtil.fromValue(Env.TRV_TEST_TIMEOUT.val) ?? 5000;
|
|
|
23
21
|
*/
|
|
24
22
|
export class TestExecutor {
|
|
25
23
|
|
|
24
|
+
#consumer: TestConsumer;
|
|
25
|
+
#testFilter: (config: TestConfig) => boolean;
|
|
26
|
+
|
|
27
|
+
constructor(consumer: TestConsumer, testFilter?: (config: TestConfig) => boolean) {
|
|
28
|
+
this.#consumer = consumer;
|
|
29
|
+
this.#testFilter = testFilter || ((): boolean => true);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Handles communicating a suite-level error
|
|
34
|
+
* @param failure
|
|
35
|
+
* @param withSuite
|
|
36
|
+
*/
|
|
37
|
+
#onSuiteFailure(failure: SuiteFailure, triggerSuite?: boolean): void {
|
|
38
|
+
if (triggerSuite) {
|
|
39
|
+
this.#consumer.onEvent({ type: 'suite', phase: 'before', suite: failure.suite });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.#consumer.onEvent({ type: 'test', phase: 'before', test: failure.test });
|
|
43
|
+
this.#consumer.onEvent({ type: 'assertion', phase: 'after', assertion: failure.assert });
|
|
44
|
+
this.#consumer.onEvent({ type: 'test', phase: 'after', test: failure.testResult });
|
|
45
|
+
|
|
46
|
+
if (triggerSuite) {
|
|
47
|
+
this.#consumer.onEvent({
|
|
48
|
+
type: 'suite', phase: 'after',
|
|
49
|
+
suite: { ...castTo(failure.suite), failed: 1, passed: 0, total: 1, skipped: 0 }
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
26
54
|
/**
|
|
27
55
|
* Raw execution, runs the method and then returns any thrown errors as the result.
|
|
28
56
|
*
|
|
29
57
|
* This method should never throw under any circumstances.
|
|
30
58
|
*/
|
|
31
|
-
|
|
59
|
+
async #executeTestMethod(test: TestConfig): Promise<Error | undefined> {
|
|
32
60
|
const suite = SuiteRegistry.get(test.class);
|
|
33
61
|
|
|
34
62
|
// Ensure all the criteria below are satisfied before moving forward
|
|
@@ -40,8 +68,7 @@ export class TestExecutor {
|
|
|
40
68
|
|
|
41
69
|
try {
|
|
42
70
|
await pCap.run(() =>
|
|
43
|
-
|
|
44
|
-
(suite.instance as Record<string, Function>)[test.methodName]()
|
|
71
|
+
castTo<Record<string, Function>>(suite.instance)[test.methodName]()
|
|
45
72
|
);
|
|
46
73
|
} finally {
|
|
47
74
|
process.env = env; // Restore
|
|
@@ -55,35 +82,27 @@ export class TestExecutor {
|
|
|
55
82
|
/**
|
|
56
83
|
* Determining if we should skip
|
|
57
84
|
*/
|
|
58
|
-
|
|
59
|
-
if (cfg
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
85
|
+
async #shouldSkip(cfg: TestConfig | SuiteConfig, inst: unknown): Promise<boolean | undefined> {
|
|
86
|
+
if ('methodName' in cfg && !this.#testFilter(cfg)) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (typeof cfg.skip === 'function' ? await cfg.skip(inst) : cfg.skip) {
|
|
91
|
+
return true;
|
|
63
92
|
}
|
|
64
93
|
}
|
|
65
94
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const classId = RuntimeIndex.getId(file, name);
|
|
72
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
73
|
-
const suite = { class: { name }, classId, duration: 0, lineStart: 1, lineEnd: 1, file, } as SuiteConfig & SuiteResult;
|
|
74
|
-
err.message = err.message.replaceAll(RuntimeIndex.mainModule.sourcePath, '.');
|
|
75
|
-
const res = AssertUtil.generateSuiteError(suite, 'require', err);
|
|
76
|
-
consumer.onEvent({ type: 'suite', phase: 'before', suite });
|
|
77
|
-
consumer.onEvent({ type: 'test', phase: 'before', test: res.testConfig });
|
|
78
|
-
consumer.onEvent({ type: 'assertion', phase: 'after', assertion: res.assert });
|
|
79
|
-
consumer.onEvent({ type: 'test', phase: 'after', test: res.testResult });
|
|
80
|
-
consumer.onEvent({ type: 'suite', phase: 'after', suite: { ...suite, failed: 1, passed: 0, total: 1, skipped: 0 } });
|
|
95
|
+
#skipTest(test: TestConfig, result: SuiteResult): void {
|
|
96
|
+
// Mark test start
|
|
97
|
+
this.#consumer.onEvent({ type: 'test', phase: 'before', test });
|
|
98
|
+
result.skipped++;
|
|
99
|
+
this.#consumer.onEvent({ type: 'test', phase: 'after', test: { ...test, assertions: [], duration: 0, durationTotal: 0, output: {}, status: 'skipped' } });
|
|
81
100
|
}
|
|
82
101
|
|
|
83
102
|
/**
|
|
84
103
|
* An empty suite result based on a suite config
|
|
85
104
|
*/
|
|
86
|
-
|
|
105
|
+
createSuiteResult(suite: SuiteConfig): SuiteResult {
|
|
87
106
|
return {
|
|
88
107
|
passed: 0,
|
|
89
108
|
failed: 0,
|
|
@@ -91,7 +110,7 @@ export class TestExecutor {
|
|
|
91
110
|
total: 0,
|
|
92
111
|
lineStart: suite.lineStart,
|
|
93
112
|
lineEnd: suite.lineEnd,
|
|
94
|
-
|
|
113
|
+
import: suite.import,
|
|
95
114
|
classId: suite.classId,
|
|
96
115
|
duration: 0,
|
|
97
116
|
tests: []
|
|
@@ -101,22 +120,22 @@ export class TestExecutor {
|
|
|
101
120
|
/**
|
|
102
121
|
* Execute the test, capture output, assertions and promises
|
|
103
122
|
*/
|
|
104
|
-
|
|
123
|
+
async executeTest(test: TestConfig): Promise<TestResult> {
|
|
105
124
|
|
|
106
125
|
// Mark test start
|
|
107
|
-
consumer.onEvent({ type: 'test', phase: 'before', test });
|
|
126
|
+
this.#consumer.onEvent({ type: 'test', phase: 'before', test });
|
|
108
127
|
|
|
109
128
|
const startTime = Date.now();
|
|
110
129
|
|
|
111
130
|
const result: TestResult = {
|
|
112
131
|
methodName: test.methodName,
|
|
113
|
-
module: RuntimeContext.main.name,
|
|
114
132
|
description: test.description,
|
|
115
133
|
classId: test.classId,
|
|
116
134
|
lineStart: test.lineStart,
|
|
117
135
|
lineEnd: test.lineEnd,
|
|
118
136
|
lineBodyStart: test.lineBodyStart,
|
|
119
|
-
|
|
137
|
+
import: test.import,
|
|
138
|
+
sourceImport: test.sourceImport,
|
|
120
139
|
status: 'skipped',
|
|
121
140
|
assertions: [],
|
|
122
141
|
duration: 0,
|
|
@@ -124,16 +143,12 @@ export class TestExecutor {
|
|
|
124
143
|
output: {},
|
|
125
144
|
};
|
|
126
145
|
|
|
127
|
-
if (await this.#skip(test, suite.instance)) {
|
|
128
|
-
return result;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
146
|
// Emit every assertion as it occurs
|
|
132
|
-
const getAssertions = AssertCapture.collector(test,
|
|
133
|
-
consumer.onEvent({
|
|
147
|
+
const getAssertions = AssertCapture.collector(test, asrt =>
|
|
148
|
+
this.#consumer.onEvent({
|
|
134
149
|
type: 'assertion',
|
|
135
150
|
phase: 'after',
|
|
136
|
-
assertion:
|
|
151
|
+
assertion: asrt
|
|
137
152
|
})
|
|
138
153
|
);
|
|
139
154
|
|
|
@@ -165,47 +180,27 @@ export class TestExecutor {
|
|
|
165
180
|
});
|
|
166
181
|
|
|
167
182
|
// Mark completion
|
|
168
|
-
consumer.onEvent({ type: 'test', phase: 'after', test: result });
|
|
183
|
+
this.#consumer.onEvent({ type: 'test', phase: 'after', test: result });
|
|
169
184
|
|
|
170
185
|
return result;
|
|
171
186
|
}
|
|
172
187
|
|
|
173
188
|
/**
|
|
174
|
-
* Execute
|
|
189
|
+
* Execute an entire suite
|
|
175
190
|
*/
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const mgr = new TestPhaseManager(consumer, suite, result);
|
|
180
|
-
|
|
181
|
-
try {
|
|
182
|
-
await mgr.startPhase('all');
|
|
183
|
-
const skip = await this.#skip(test, suite.instance);
|
|
184
|
-
if (!skip) {
|
|
185
|
-
await mgr.startPhase('each');
|
|
186
|
-
}
|
|
187
|
-
await this.executeTest(consumer, test, suite);
|
|
188
|
-
if (!skip) {
|
|
189
|
-
await mgr.endPhase('each');
|
|
190
|
-
}
|
|
191
|
-
await mgr.endPhase('all');
|
|
192
|
-
} catch (err) {
|
|
193
|
-
await mgr.onError(err);
|
|
191
|
+
async executeSuite(suite: SuiteConfig, tests: TestConfig[]): Promise<void> {
|
|
192
|
+
if (!tests.length || await this.#shouldSkip(suite, suite.instance)) {
|
|
193
|
+
return;
|
|
194
194
|
}
|
|
195
|
-
}
|
|
196
195
|
|
|
197
|
-
/**
|
|
198
|
-
* Execute an entire suite
|
|
199
|
-
*/
|
|
200
|
-
static async executeSuite(consumer: TestConsumer, suite: SuiteConfig): Promise<SuiteResult> {
|
|
201
196
|
const result: SuiteResult = this.createSuiteResult(suite);
|
|
202
197
|
|
|
203
198
|
const startTime = Date.now();
|
|
204
199
|
|
|
205
200
|
// Mark suite start
|
|
206
|
-
consumer.onEvent({ phase: 'before', type: 'suite', suite });
|
|
201
|
+
this.#consumer.onEvent({ phase: 'before', type: 'suite', suite });
|
|
207
202
|
|
|
208
|
-
const mgr = new TestPhaseManager(
|
|
203
|
+
const mgr = new TestPhaseManager(suite, result, e => this.#onSuiteFailure(e));
|
|
209
204
|
|
|
210
205
|
const originalEnv = { ...process.env };
|
|
211
206
|
|
|
@@ -215,28 +210,30 @@ export class TestExecutor {
|
|
|
215
210
|
|
|
216
211
|
const suiteEnv = { ...process.env };
|
|
217
212
|
|
|
218
|
-
for (const test of suite.tests) {
|
|
213
|
+
for (const test of tests ?? suite.tests) {
|
|
214
|
+
if (await this.#shouldSkip(test, suite.instance)) {
|
|
215
|
+
this.#skipTest(test, result);
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
219
|
// Reset env before each test
|
|
220
220
|
process.env = { ...suiteEnv };
|
|
221
|
+
|
|
221
222
|
const testStart = Date.now();
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
await mgr.startPhase('each');
|
|
226
|
-
}
|
|
223
|
+
|
|
224
|
+
// Handle BeforeEach
|
|
225
|
+
await mgr.startPhase('each');
|
|
227
226
|
|
|
228
227
|
// Run test
|
|
229
|
-
const ret = await this.executeTest(
|
|
228
|
+
const ret = await this.executeTest(test);
|
|
230
229
|
result[ret.status]++;
|
|
231
|
-
|
|
232
|
-
if (!skip) {
|
|
233
|
-
result.tests.push(ret);
|
|
234
|
-
}
|
|
230
|
+
result.tests.push(ret);
|
|
235
231
|
|
|
236
232
|
// Handle after each
|
|
237
233
|
await mgr.endPhase('each');
|
|
238
234
|
ret.durationTotal = Date.now() - testStart;
|
|
239
235
|
}
|
|
236
|
+
|
|
240
237
|
// Handle after all
|
|
241
238
|
await mgr.endPhase('all');
|
|
242
239
|
} catch (err) {
|
|
@@ -247,31 +244,23 @@ export class TestExecutor {
|
|
|
247
244
|
process.env = { ...originalEnv };
|
|
248
245
|
|
|
249
246
|
result.duration = Date.now() - startTime;
|
|
250
|
-
|
|
251
|
-
// Mark suite complete
|
|
252
|
-
consumer.onEvent({ phase: 'after', type: 'suite', suite: result });
|
|
253
|
-
|
|
254
247
|
result.total = result.passed + result.failed;
|
|
255
248
|
|
|
256
|
-
|
|
249
|
+
// Mark suite complete
|
|
250
|
+
this.#consumer.onEvent({ phase: 'after', type: 'suite', suite: result });
|
|
257
251
|
}
|
|
258
252
|
|
|
259
253
|
/**
|
|
260
254
|
* Handle executing a suite's test/tests based on command line inputs
|
|
261
255
|
*/
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
file = path.resolve(file);
|
|
265
|
-
|
|
266
|
-
const entry = RuntimeIndex.getEntry(file)!;
|
|
267
|
-
|
|
256
|
+
async execute(run: TestRun): Promise<void> {
|
|
268
257
|
try {
|
|
269
|
-
await
|
|
258
|
+
await Runtime.importFrom(run.import);
|
|
270
259
|
} catch (err) {
|
|
271
260
|
if (!(err instanceof Error)) {
|
|
272
261
|
throw err;
|
|
273
262
|
}
|
|
274
|
-
this.
|
|
263
|
+
this.#onSuiteFailure(AssertUtil.gernerateImportFailure(run.import, err));
|
|
275
264
|
return;
|
|
276
265
|
}
|
|
277
266
|
|
|
@@ -279,19 +268,10 @@ export class TestExecutor {
|
|
|
279
268
|
await SuiteRegistry.init();
|
|
280
269
|
|
|
281
270
|
// Convert inbound arguments to specific tests to run
|
|
282
|
-
const
|
|
271
|
+
const suites = SuiteRegistry.getSuiteTests(run);
|
|
283
272
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
for (const suite of params.suites) {
|
|
287
|
-
if (!(await this.#skip(suite, suite.instance)) && suite.tests.length) {
|
|
288
|
-
await this.executeSuite(consumer, suite);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
} else if (params.test) { // If running a single test
|
|
292
|
-
await this.executeSuiteTest(consumer, params.suite, params.test);
|
|
293
|
-
} else { // Running the suite
|
|
294
|
-
await this.executeSuite(consumer, params.suite);
|
|
273
|
+
for (const { suite, tests } of suites) {
|
|
274
|
+
await this.executeSuite(suite, tests);
|
|
295
275
|
}
|
|
296
276
|
}
|
|
297
277
|
}
|
package/src/execute/phase.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Barrier } from '@travetto/worker';
|
|
2
|
-
import { Env, TimeUtil } from '@travetto/
|
|
2
|
+
import { Env, TimeUtil } from '@travetto/runtime';
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import { SuiteConfig, SuiteResult } from '../model/suite';
|
|
4
|
+
import { SuiteConfig, SuiteFailure, SuiteResult } from '../model/suite';
|
|
6
5
|
import { AssertUtil } from '../assert/util';
|
|
7
|
-
import { TestResult } from '../model/test';
|
|
8
6
|
|
|
9
|
-
class TestBreakout extends Error {
|
|
7
|
+
class TestBreakout extends Error {
|
|
8
|
+
source?: Error;
|
|
9
|
+
}
|
|
10
10
|
|
|
11
11
|
const TEST_PHASE_TIMEOUT = TimeUtil.fromValue(Env.TRV_TEST_PHASE_TIMEOUT.val) ?? 15000;
|
|
12
12
|
|
|
@@ -17,27 +17,14 @@ const TEST_PHASE_TIMEOUT = TimeUtil.fromValue(Env.TRV_TEST_PHASE_TIMEOUT.val) ??
|
|
|
17
17
|
*/
|
|
18
18
|
export class TestPhaseManager {
|
|
19
19
|
#progress: ('all' | 'each')[] = [];
|
|
20
|
-
#consumer: TestConsumer;
|
|
21
20
|
#suite: SuiteConfig;
|
|
22
21
|
#result: SuiteResult;
|
|
22
|
+
#onSuiteFailure: (fail: SuiteFailure) => void;
|
|
23
23
|
|
|
24
|
-
constructor(
|
|
25
|
-
this.#consumer = consumer;
|
|
24
|
+
constructor(suite: SuiteConfig, result: SuiteResult, onSuiteFailure: (fail: SuiteFailure) => void) {
|
|
26
25
|
this.#suite = suite;
|
|
27
26
|
this.#result = result;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Create the appropriate events when a suite has an error
|
|
32
|
-
*/
|
|
33
|
-
async triggerSuiteError(methodName: string, error: Error): Promise<TestResult> {
|
|
34
|
-
const bad = AssertUtil.generateSuiteError(this.#suite, methodName, error);
|
|
35
|
-
|
|
36
|
-
this.#consumer.onEvent({ type: 'test', phase: 'before', test: bad.testConfig });
|
|
37
|
-
this.#consumer.onEvent({ type: 'assertion', phase: 'after', assertion: bad.assert });
|
|
38
|
-
this.#consumer.onEvent({ type: 'test', phase: 'after', test: bad.testResult });
|
|
39
|
-
|
|
40
|
-
return bad.testResult;
|
|
27
|
+
this.#onSuiteFailure = onSuiteFailure;
|
|
41
28
|
}
|
|
42
29
|
|
|
43
30
|
/**
|
|
@@ -57,10 +44,9 @@ export class TestPhaseManager {
|
|
|
57
44
|
}
|
|
58
45
|
}
|
|
59
46
|
if (error) {
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
throw new TestBreakout();
|
|
47
|
+
const tbo = new TestBreakout(`[[${phase}]]`);
|
|
48
|
+
tbo.source = error;
|
|
49
|
+
throw tbo;
|
|
64
50
|
}
|
|
65
51
|
}
|
|
66
52
|
|
|
@@ -96,10 +82,14 @@ export class TestPhaseManager {
|
|
|
96
82
|
|
|
97
83
|
this.#progress = [];
|
|
98
84
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
85
|
+
const failure = AssertUtil.generateSuiteFailure(
|
|
86
|
+
this.#suite,
|
|
87
|
+
err instanceof TestBreakout ? err.message : 'all',
|
|
88
|
+
err instanceof TestBreakout ? err.source! : err
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
this.#onSuiteFailure(failure);
|
|
92
|
+
this.#result.tests.push(failure.testResult);
|
|
93
|
+
this.#result.failed++;
|
|
104
94
|
}
|
|
105
95
|
}
|