Skip to content

Commit 02dfd40

Browse files
authored
♻️ Refactor reporter code (#31)
* Add example test * Refactor reporter code * Fix verbosity levels again * Fix eslint warnings
1 parent 66f715c commit 02dfd40

15 files changed

Lines changed: 630 additions & 345 deletions

File tree

.eslint.config.mjs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,17 @@ import stylisticJs from '@stylistic/eslint-plugin-js'
66

77
/** @type {import('eslint').Linter.Config[]} */
88
export default [
9+
pluginJs.configs.recommended,
10+
...tseslint.configs.recommended,
911
{ plugins: {
1012
'@stylistic/js': stylisticJs
1113
}, rules: {
1214
'@stylistic/js/indent': ['error', 4],
15+
'@typescript-eslint/no-wrapper-object-types': 'off'
1316
}
1417
},
1518
{files: ["**/*.{js,mjs,cjs,ts}"]},
1619
{files: ["**/*.js"], languageOptions: {sourceType: "commonjs"}},
1720
{languageOptions: { globals: globals.browser }},
18-
pluginJs.configs.recommended,
19-
...tseslint.configs.recommended,
21+
2022
];

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"test:prebuild": "npm run build && npm run build:tests",
2424
"test:all": "npm run test:prebuild && npm run test:ava",
2525
"test:ava": "ava",
26+
"test:example": "npx ts-node ./tests/examples/example.ts",
2627
"coverage:test:ava": "c8 --src src/ --all ava"
2728
},
2829
"dependencies": {

src/framework/Framework.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ import {HybridScheduler, Scheduler} from './Scheduler';
33
import {TestScenario} from './scenario/TestScenario';
44

55
import {TestbedSpecification} from '../testbeds/TestbedSpecification';
6-
import {Reporter, SuiteResults} from '../reporter/Reporter';
76

87
import {StyleType} from '../reporter';
98
import {styling} from '../reporter/Style';
9+
import {SuiteResult} from '../reporter/Results';
10+
import {Reporter} from '../reporter/Reporter';
1011

1112
export interface Suite {
1213

@@ -90,10 +91,10 @@ export class Framework {
9091
for (const suite of suites) {
9192
for (const testee of suite.testees) {
9293
const order: TestScenario[] = suite.scheduler.sequential(suite);
93-
const result: SuiteResults = new SuiteResults(suite, testee);
94+
const result: SuiteResult = new SuiteResult(suite);
9495

9596
const first: TestScenario = order[0];
96-
await timeout<Object | void>('Initialize testbed', testee.connector.timeout, testee.initialize(first.program, first.args ?? []).catch((e: Error) => result.error = e));
97+
await timeout<Object | void>('Initialize testbed', testee.connector.timeout, testee.initialize(first.program, first.args ?? []).catch((e: Error) => result.error(e.message)));
9798

9899
await this.runSuite(result, testee, order);
99100
this.reporter.report(result);
@@ -114,10 +115,10 @@ export class Framework {
114115
await Promise.all(suites.map(async (suite: Suite) => {
115116
await Promise.all(suite.testees.map(async (testee: Testee) => {
116117
const order: TestScenario[] = suite.scheduler.sequential(suite);
117-
const result: SuiteResults = new SuiteResults(suite, testee);
118+
const result: SuiteResult = new SuiteResult(suite);
118119

119120
const first: TestScenario = order[0];
120-
await timeout<Object | void>('Initialize testbed', testee.connector.timeout, testee.initialize(first.program, first.args ?? []).catch((e: Error) => result.error = e));
121+
await timeout<Object | void>('Initialize testbed', testee.connector.timeout, testee.initialize(first.program, first.args ?? []).catch((e: Error) => result.error(e.message)));
121122

122123
await this.runSuite(result, testee, order);
123124
this.reporter.report(result);
@@ -139,10 +140,10 @@ export class Framework {
139140
const order: TestScenario[][] = suite.scheduler.parallel(suite, suite.testees.length);
140141
await Promise.all(suite.testees.map(async (testee: Testee, i: number) => {
141142
// console.log(`scheduling on ${testee.name}`)
142-
const result: SuiteResults = new SuiteResults(suite, testee);
143+
const result: SuiteResult = new SuiteResult(suite);
143144

144145
const first: TestScenario = order[i][0];
145-
await timeout<Object | void>('Initialize testbed', testee.connector.timeout, testee.initialize(first.program, first.args ?? []).catch((e: Error) => result.error = e));
146+
await timeout<Object | void>('Initialize testbed', testee.connector.timeout, testee.initialize(first.program, first.args ?? []).catch((e: Error) => result.error(e.message)));
146147

147148
for (let j = i; j < order.length; j += suite.testees.length) {
148149
await this.runSuite(result, testee, order[j]);
@@ -159,7 +160,7 @@ export class Framework {
159160
this.reporter.results(t1 - t0);
160161
}
161162

162-
private async runSuite(result: SuiteResults, testee: Testee, order: TestScenario[]) {
163+
private async runSuite(result: SuiteResult, testee: Testee, order: TestScenario[]) {
163164
for (const test of order) {
164165
await testee.describe(test, result, this.runs);
165166
}

src/framework/Testee.ts

Lines changed: 39 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ import {TestScenario} from './scenario/TestScenario';
99
import {OutofPlaceSpecification, PlatformType, TestbedSpecification} from '../testbeds/TestbedSpecification';
1010
import {CompileOutput, CompilerFactory} from '../manage/Compiler';
1111
import {WABT} from '../util/env';
12-
import {Completion, expect, ScenarioResult, SuiteResults} from '../reporter/Reporter';
12+
import {Outcome} from '../reporter/describers/Describer';
1313
import {WASM} from '../sourcemap/Wasm';
1414
import {DummyProxy} from '../testbeds/Emulator';
15-
import {Result} from '../reporter/Result';
15+
import {ScenarioResult, Skipped, StepOutcome, SuiteResult} from '../reporter/Results';
16+
import {Verifier} from './Verifier';
1617

1718
export function timeout<T>(label: string, time: number, promise: Promise<T>): Promise<T> {
1819
if (time === 0) {
@@ -46,14 +47,14 @@ export function getValue(object: any, field: string): any {
4647
}
4748

4849
export enum Target {
49-
supervisor,
50-
proxy
50+
supervisor = 'supervisor',
51+
proxy = 'proxy'
5152
}
5253

5354
export class Testee { // TODO unified with testbed interface
5455

5556
/** The current state for each described test */
56-
private states: Map<string, Result> = new Map<string, Result>();
57+
private states: Map<string, StepOutcome> = new Map<string, StepOutcome>();
5758

5859
/** Factory to establish new connections to VMs */
5960
public readonly connector: TestbedFactory;
@@ -132,16 +133,16 @@ export class Testee { // TODO unified with testbed interface
132133
}
133134

134135
private run(name: string, limit: number, fn: () => Promise<any>) {
135-
return timeout<Object | void>(name, limit, fn());
136+
return timeout<object | void>(name, limit, fn());
136137
}
137138

138139
private step(name: string, limit: number, fn: () => Promise<any>) {
139-
return timeout<Object | void>(name, limit, fn());
140+
return timeout<object | void>(name, limit, fn());
140141
}
141142

142-
public async describe(description: TestScenario, suiteResult: SuiteResults, runs: number = 1) {
143+
public async describe(description: TestScenario, suiteResult: SuiteResult, runs: number = 1) {
143144
const testee = this;
144-
const scenarioResult: ScenarioResult = new ScenarioResult(description, testee);
145+
const scenarioResult: ScenarioResult = new ScenarioResult(description);
145146

146147
if (description.skip) {
147148
return;
@@ -154,11 +155,11 @@ export class Testee { // TODO unified with testbed interface
154155
await this.run('Check for failing dependencies', testee.timeout, async function () {
155156
const failedDependencies: TestScenario[] = testee.failedDependencies(description);
156157
if (failedDependencies.length > 0) {
157-
testee.states.set(description.title, new Result('Skipping', 'Test has failing dependencies', Completion.skipped));
158+
testee.states.set(description.title, new Skipped('Skipping', 'Test has failing dependencies'));
158159
throw new Error(`Skipped: failed dependent tests: ${failedDependencies.map(dependence => dependence.title)}`);
159160
}
160161
}).catch((e: Error) => {
161-
scenarioResult.error = e;
162+
scenarioResult.error(e.message);
162163
});
163164

164165
await this.run('Compile and upload program', testee.connector.timeout, async function () {
@@ -169,31 +170,31 @@ export class Testee { // TODO unified with testbed interface
169170

170171
const compiled: CompileOutput = await new CompilerFactory(WABT).pickCompiler(description.program).compile(description.program);
171172
try {
172-
await timeout<Object | void>(`uploading module`, testee.timeout, testee.bed()!.sendRequest(new SourceMap.Mapping(), Message.updateModule(compiled.file))).catch((e) => Promise.reject(e));
173+
await timeout<object | void>(`uploading module`, testee.timeout, testee.bed()!.sendRequest(new SourceMap.Mapping(), Message.updateModule(compiled.file))).catch((e) => Promise.reject(e));
173174
testee.current = description.program;
174175
} catch (e) {
175176
await testee.initialize(description.program, description.args ?? []).catch((o) => Promise.reject(o));
176177
}
177178
}).catch((e: Error | string) => {
178179
if (typeof e === 'string') {
179-
scenarioResult.error = new Error(e);
180+
scenarioResult.error(e);
180181
} else {
181-
scenarioResult.error = e;
182+
scenarioResult.error(e.toString());
182183
}
183184
});
184185

185186
await this.run('Get source mapping', testee.connector.timeout, async function () {
186187
map = await testee.mapper.map(description.program);
187188
}).catch((e: Error | string) => {
188189
if (typeof e === 'string') {
189-
scenarioResult.error = new Error(e);
190+
scenarioResult.error(e);
190191
} else {
191-
scenarioResult.error = e;
192+
scenarioResult.error(e.toString());
192193
}
193194
});
194195

195-
if (scenarioResult.error) {
196-
suiteResult.scenarios.push(scenarioResult);
196+
if (scenarioResult.outcome === Outcome.error) {
197+
suiteResult.add(scenarioResult);
197198
return;
198199
}
199200

@@ -205,62 +206,60 @@ export class Testee { // TODO unified with testbed interface
205206
await this.run('resetting before retry', testee.timeout, async function () {
206207
await testee.reset(testee.testbed);
207208
}).catch((e: Error) => {
208-
scenarioResult.error = e;
209+
scenarioResult.error(e.toString());
209210
});
210211
}
211212

212213
for (const step of description.steps ?? []) {
213214
/** Perform the step and check if expectations were met */
214215
await this.step(step.title, testee.timeout, async function () {
215-
let result: Result = new Result(step.title, 'incomplete');
216+
const verifier: Verifier = new Verifier(step);
216217
if (testee.bed(step.target ?? Target.supervisor) === undefined) {
217-
testee.states.set(description.title, result);
218-
result.error('Cannot run test: no debugger connection.');
219-
testee.states.set(description.title, result);
218+
testee.states.set(description.title, verifier.error('Cannot run test: no debugger connection.'));
220219
return;
221220
}
222221

223-
let actual: Object | void;
222+
let actual: object | void;
224223
if (step.instruction.kind === Kind.Action) {
225-
actual = await timeout<Object | void>(`performing action . ${step.title}`, testee.timeout,
224+
actual = await timeout<object | void>(`performing action . ${step.title}`, testee.timeout,
226225
step.instruction.value.act(testee)).catch((err) => {
227-
result.error(err);
226+
testee.states.set(description.title, verifier.error(err));
227+
return;
228228
});
229229
} else {
230230
actual = await testee.recoverable(testee, step.instruction.value, map,
231-
(testee, req, map) => timeout<Object | void>(`sending instruction ${req.type}`, testee.timeout,
231+
(testee, req, map) => timeout<object | void>(`sending instruction ${req.type}`, testee.timeout,
232232
testee.bed(step.target ?? Target.supervisor)!.sendRequest(map, req)),
233233
(testee) => testee.run(`Recover: re-initialize ${testee.testbed?.name}`, testee.connector.timeout, async function () {
234234
await testee.initialize(description.program, description.args ?? []).catch((o) => {
235235
return Promise.reject(o)
236236
});
237237
}), 1).catch((e: Error) => {
238-
result.completion = (e.message.includes('timeout')) ? Completion.timedout : Completion.error;
239-
result.description = e.message;
238+
const result = new StepOutcome(step);
239+
testee.states.set(description.title, result.update((e.message.includes('timeout')) ? Outcome.timedout : Outcome.error, e.message));
240240
});
241241
}
242242

243-
if (result.completion === Completion.uncommenced) {
244-
result = expect(step, actual, previous);
245-
}
243+
const result = verifier.verify(actual, previous);
246244

247245
if (actual !== undefined) {
248246
previous = actual;
249247
}
250248

251249
testee.states.set(description.title, result);
252-
scenarioResult.results.push(result);
250+
scenarioResult.add(result);
253251
});
254252
}
255-
suiteResult.scenarios.push(scenarioResult);
253+
suiteResult.add(scenarioResult);
256254
}
257255
}
258256

257+
/* eslint @typescript-eslint/no-explicit-any: off */
259258
private async recoverable(testee: Testee, step: Request<any>, map: SourceMap.Mapping,
260-
attempt: (t: Testee, req: Request<any>, m: SourceMap.Mapping) => Promise<Object | void>,
259+
attempt: (t: Testee, req: Request<any>, m: SourceMap.Mapping) => Promise<object | void>,
261260
recover: (t: Testee) => Promise<any>,
262-
retries: number = 0): Promise<Object | void> {
263-
let result: Object | void = undefined;
261+
retries: number = 0): Promise<object | void> {
262+
let result: object | void = undefined;
264263
let error;
265264
while (0 <= retries && result === undefined) {
266265
result = await attempt(testee, step, map).catch(async (err) => {
@@ -281,7 +280,7 @@ export class Testee { // TODO unified with testbed interface
281280
if (instance === undefined) {
282281
this.framework.reporter.error('Cannot run test: no debugger connection.'); // todo
283282
} else {
284-
await timeout<Object | void>('resetting vm', this.timeout, this.testbed!.sendRequest(new SourceMap.Mapping(), Message.reset));
283+
await timeout<object | void>('resetting vm', this.timeout, this.testbed!.sendRequest(new SourceMap.Mapping(), Message.reset));
285284
}
286285
}
287286

@@ -293,8 +292,8 @@ export class Testee { // TODO unified with testbed interface
293292
private failedDependencies(description: TestScenario): TestScenario[] {
294293
return (description?.dependencies ?? []).filter(dependence => {
295294
if (this.states.get(dependence.title)) {
296-
const c = this.states.get(dependence.title)!.completion;
297-
return !(c === Completion.succeeded || c === Completion.uncommenced);
295+
const c = this.states.get(dependence.title)!.outcome;
296+
return !(c === Outcome.succeeded || c === Outcome.uncommenced);
298297
} else {
299298
return false;
300299
}

0 commit comments

Comments
 (0)