Skip to content

Commit 00bbf54

Browse files
authored
Fix out-of-order subcase logs (#4396)
- First commit fixes a bug where we were capturing a variable too late which prevented the subcase logs from actually being re-sorted the way they were supposed to be - Second commit simplifies the code a bunch and moves the prefix to the beginning of the log line
1 parent be765eb commit 00bbf54

5 files changed

Lines changed: 174 additions & 161 deletions

File tree

src/common/internal/logging/test_case_recorder.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,4 +181,43 @@ export class TestCaseRecorder {
181181

182182
this.logs.push(logMessage);
183183
}
184+
185+
/**
186+
* Make a recorder that will defer all calls until `deferUntilPromise` resolves.
187+
* This is used for running subcases, which run concurrently, to ensure that
188+
* logs from all the previous subcases have been flushed before flushing new logs,
189+
* so all logs are in order as if the subcases had not been concurrent.
190+
*/
191+
makeDeferredSubRecorder(prefix: string, deferUntilPromise: Promise<void>) {
192+
return new Proxy(this, {
193+
get: (target, k) => {
194+
switch (k) {
195+
case 'logImpl':
196+
return function (level: LogSeverity, name: string, baseException: unknown) {
197+
globalTestConfig.testHeartbeatCallback();
198+
void deferUntilPromise.then(() => {
199+
target.logImpl(level, prefix + name, baseException);
200+
});
201+
};
202+
case 'beginSubCase':
203+
return function () {
204+
globalTestConfig.testHeartbeatCallback();
205+
void deferUntilPromise.then(() => {
206+
target.beginSubCase();
207+
});
208+
};
209+
case 'endSubCase':
210+
return function (expectedStatus: Expectation) {
211+
globalTestConfig.testHeartbeatCallback();
212+
void deferUntilPromise.then(() => {
213+
target.endSubCase(expectedStatus);
214+
});
215+
};
216+
default:
217+
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
218+
return (target as any)[k];
219+
}
220+
},
221+
});
222+
}
184223
}

src/common/internal/test_group.ts

Lines changed: 9 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -615,46 +615,11 @@ class RunCaseSpecific implements RunCase {
615615
};
616616

617617
for (const subParams of this.subcases) {
618-
// Make a recorder that will defer all calls until `allPreviousSubcasesFinalizedPromise`
619-
// resolves. Waiting on `allPreviousSubcasesFinalizedPromise` ensures that
620-
// logs from all the previous subcases have been flushed before flushing new logs.
621-
const subcasePrefix = 'subcase: ' + stringifyPublicParams(subParams);
622-
const subRec = new Proxy(rec, {
623-
get: (target, k: keyof TestCaseRecorder) => {
624-
const prop = rec[k] ?? TestCaseRecorder.prototype[k];
625-
if (typeof prop === 'function') {
626-
testHeartbeatCallback();
627-
return function (...args: Parameters<typeof prop>) {
628-
void allPreviousSubcasesFinalizedPromise.then(() => {
629-
// Prepend the subcase name to all error messages.
630-
for (const arg of args) {
631-
if (arg instanceof Error) {
632-
try {
633-
arg.message = subcasePrefix + '\n' + arg.message;
634-
} catch {
635-
// If that fails (e.g. on DOMException), try to put it in the stack:
636-
let stack = subcasePrefix;
637-
if (arg.stack) stack += '\n' + arg.stack;
638-
try {
639-
arg.stack = stack;
640-
} catch {
641-
// If that fails too, just silence it.
642-
}
643-
}
644-
}
645-
}
646-
647-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
648-
const rv = (prop as any).apply(target, args);
649-
// Because this proxy executes functions in a deferred manner,
650-
// it should never be used for functions that need to return a value.
651-
assert(rv === undefined);
652-
});
653-
};
654-
}
655-
return prop;
656-
},
657-
});
618+
// Defer subcase logs so that they appear in the correct order.
619+
const subRec = rec.makeDeferredSubRecorder(
620+
`(in subcase: ${stringifyPublicParams(subParams)}) `,
621+
allPreviousSubcasesFinalizedPromise
622+
);
658623

659624
const params = mergeParams(this.params, subParams);
660625
const subcaseQuery = new TestQuerySingleCase(
@@ -684,16 +649,16 @@ class RunCaseSpecific implements RunCase {
684649
getExpectedStatus(subcaseQuery)
685650
)
686651
.then(() => {
687-
subRec.info(new Error('OK'));
652+
subRec.info(new Error('subcase ran'));
688653
})
689654
.catch(ex => {
690655
if (ex instanceof SkipTestCase) {
691-
// Convert SkipTestCase to info messages
656+
// Convert SkipTestCase to an info message so it won't skip the whole test
692657
ex.message = 'subcase skipped: ' + ex.message;
693-
subRec.info(ex);
658+
subRec.info(new Error('subcase skipped'));
694659
++skipCount;
695660
} else {
696-
// Since we are catching all error inside runTest(), this should never happen
661+
// We are catching all other errors inside runTest(), so this should never happen
697662
subRec.threw(ex);
698663
}
699664
})

src/demo/subcases.spec.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,28 @@ import { UnitTest } from '../unittests/unit_test.js';
55

66
export const g = makeTestGroup(UnitTest);
77

8-
g.test('pass_warn_fail')
8+
g.test('skip')
9+
.paramsSubcasesOnly(u => u.combine('y', [1, 2]))
10+
.fn(t => {
11+
t.skip('I skip!');
12+
});
13+
14+
g.test('pass_warn_fail_skip')
915
.params(u =>
1016
u
11-
.combine('x', [1, 2, 3]) //
17+
.combine('x', [0, 1, 2, 3]) //
1218
.beginSubcases()
1319
.combine('y', [1, 2, 3])
1420
)
1521
.fn(t => {
1622
const { x, y } = t.params;
17-
if (x + y > 5) {
18-
t.fail();
19-
} else if (x + y > 4) {
20-
t.warn();
23+
if (x + y >= 5) {
24+
t.fail('I fail!');
25+
} else if (x + y >= 4) {
26+
t.warn('I warn!');
27+
}
28+
if (x + y === 1 || x + y === 6) {
29+
t.skip('I skip!');
2130
}
2231
});
2332

src/resources/cache/hashes.json

Lines changed: 110 additions & 110 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/unittests/test_group.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ g.test('subcases,skip')
343343
const expectedStatus = allSkip ? 'skip' : 'pass';
344344
t0.expect(
345345
values[0].status === expectedStatus,
346-
`expect: ${values[0].status} === ${expectedStatus}}, allSkip: ${allSkip}`
346+
`expect: ${values[0].status} === ${expectedStatus}, allSkip: ${allSkip}`
347347
);
348348
});
349349

0 commit comments

Comments
 (0)