Skip to content

Commit a08941d

Browse files
committed
chore: retry logic redo
1 parent f5ff7bf commit a08941d

52 files changed

Lines changed: 872 additions & 369 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/cli/src/commands/commons/run.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ export const executeAllureRun = async (params: {
330330
logTests(await allureReport.store.allTestResults());
331331
}
332332

333-
const trs = await allureReport.store.allTestResults({ includeHidden: false });
333+
const trs = await allureReport.store.allTestResults({ includeRetries: false });
334334
qualityGateResults = testProcessResult?.qualityGateResults ?? [];
335335

336336
if (withQualityGate && !qualityGateResults.length) {

packages/cli/src/commands/qualityGate.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,9 @@ export class QualityGateCommand extends Command {
153153

154154
allureReport.realtimeSubscriber.onTestResults(async (trsIds) => {
155155
const trs = await Promise.all(trsIds.map((id) => allureReport.store.testResultById(id)));
156-
const notHiddenTrs = (trs as TestResult[]).filter((tr) => !tr.hidden);
156+
const nonRetryTrs = (trs as TestResult[]).filter((tr) => !tr.isRetry);
157157
const { results, fastFailed } = await allureReport.validate({
158-
trs: notHiddenTrs,
158+
trs: nonRetryTrs,
159159
environment: resolvedEnvironment?.id,
160160
knownIssues,
161161
state,
@@ -179,7 +179,7 @@ export class QualityGateCommand extends Command {
179179

180180
await allureReport.done();
181181

182-
const allTrs = await allureReport.store.allTestResults({ includeHidden: false });
182+
const allTrs = await allureReport.store.allTestResults({ includeRetries: false });
183183
const validationResults = await allureReport.validate({
184184
trs: allTrs,
185185
knownIssues,

packages/core-api/src/model.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ export interface TestResult {
6262

6363
transition?: TestStatusTransition;
6464

65-
hidden: boolean;
65+
retryHash?: string;
66+
isRetry: boolean;
6667

6768
hostId?: string;
6869
threadId?: string;

packages/core-api/test/utils/history.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ describe("history utils", () => {
8686
flaky: false,
8787
muted: false,
8888
known: false,
89-
hidden: false,
89+
isRetry: false,
9090
links: [],
9191
steps: [],
9292
sourceMetadata: { readerId: "", metadata: {} },
@@ -101,7 +101,7 @@ describe("history utils", () => {
101101
flaky: false,
102102
muted: false,
103103
known: false,
104-
hidden: false,
104+
isRetry: false,
105105
links: [],
106106
steps: [],
107107
sourceMetadata: { readerId: "", metadata: {} },

packages/core/src/report.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -405,11 +405,11 @@ export class AllureReport {
405405
indexAttachmentByTestResult = {},
406406
indexTestResultByHistoryId = {},
407407
indexTestResultByTestCase = {},
408-
indexLatestEnvTestResultByHistoryId = {},
409408
indexAttachmentByFixture = {},
410409
indexFixturesByTestResult = {},
411410
indexKnownByHistoryId = {},
412411
qualityGateResults = [],
412+
testResultIdsIngestOrder = [],
413413
}: AllureStoreDump): [AllureStoreDumpFiles, unknown][] => [
414414
[AllureStoreDumpFiles.TestResults, testResults],
415415
[AllureStoreDumpFiles.TestCases, testCases],
@@ -423,11 +423,11 @@ export class AllureReport {
423423
[AllureStoreDumpFiles.IndexAttachmentsByTestResults, indexAttachmentByTestResult],
424424
[AllureStoreDumpFiles.IndexTestResultsByHistoryId, indexTestResultByHistoryId],
425425
[AllureStoreDumpFiles.IndexTestResultsByTestCase, indexTestResultByTestCase],
426-
[AllureStoreDumpFiles.IndexLatestEnvTestResultsByHistoryId, indexLatestEnvTestResultByHistoryId],
427426
[AllureStoreDumpFiles.IndexAttachmentsByFixture, indexAttachmentByFixture],
428427
[AllureStoreDumpFiles.IndexFixturesByTestResult, indexFixturesByTestResult],
429428
[AllureStoreDumpFiles.IndexKnownByHistoryId, indexKnownByHistoryId],
430429
[AllureStoreDumpFiles.QualityGateResults, qualityGateResults],
430+
[AllureStoreDumpFiles.TestResultIngestOrder, testResultIdsIngestOrder],
431431
];
432432
let dumpError: unknown;
433433

@@ -564,9 +564,6 @@ export class AllureReport {
564564
const indexTestResultsByTestCaseEntry = await requiredEntryData(
565565
AllureStoreDumpFiles.IndexTestResultsByTestCase,
566566
);
567-
const indexLatestEnvTestResultsByHistoryIdEntry = await requiredEntryData(
568-
AllureStoreDumpFiles.IndexLatestEnvTestResultsByHistoryId,
569-
);
570567
const indexAttachmentsByFixtureEntry = await requiredEntryData(
571568
AllureStoreDumpFiles.IndexAttachmentsByFixture,
572569
);
@@ -575,6 +572,7 @@ export class AllureReport {
575572
);
576573
const indexKnownByHistoryIdEntry = await requiredEntryData(AllureStoreDumpFiles.IndexKnownByHistoryId);
577574
const qualityGateResultsEntry = await requiredEntryData(AllureStoreDumpFiles.QualityGateResults);
575+
const testResultIngestOrderEntry = await optionalEntryData(AllureStoreDumpFiles.TestResultIngestOrder);
578576
const attachmentsLinks = JSON.parse(attachmentsEntry.toString("utf8")) as AllureStoreDump["attachments"];
579577

580578
const attachmentsEntries = dumpEntriesList.reduce((acc, [entryName, entry]) => {
@@ -591,11 +589,11 @@ export class AllureReport {
591589
case AllureStoreDumpFiles.IndexAttachmentsByTestResults:
592590
case AllureStoreDumpFiles.IndexTestResultsByHistoryId:
593591
case AllureStoreDumpFiles.IndexTestResultsByTestCase:
594-
case AllureStoreDumpFiles.IndexLatestEnvTestResultsByHistoryId:
595592
case AllureStoreDumpFiles.IndexAttachmentsByFixture:
596593
case AllureStoreDumpFiles.IndexFixturesByTestResult:
597594
case AllureStoreDumpFiles.IndexKnownByHistoryId:
598595
case AllureStoreDumpFiles.QualityGateResults:
596+
case AllureStoreDumpFiles.TestResultIngestOrder:
599597
return acc;
600598
default:
601599
if (entry.isDirectory || !attachmentsLinks[entryName] || attachmentsLinks[entryName].missed) {
@@ -620,11 +618,13 @@ export class AllureReport {
620618
indexAttachmentByTestResult: JSON.parse(indexAttachmentsEntry.toString("utf8")),
621619
indexTestResultByHistoryId: JSON.parse(indexTestResultsByHistoryId.toString("utf8")),
622620
indexTestResultByTestCase: JSON.parse(indexTestResultsByTestCaseEntry.toString("utf8")),
623-
indexLatestEnvTestResultByHistoryId: JSON.parse(indexLatestEnvTestResultsByHistoryIdEntry.toString("utf8")),
624621
indexAttachmentByFixture: JSON.parse(indexAttachmentsByFixtureEntry.toString("utf8")),
625622
indexFixturesByTestResult: JSON.parse(indexFixturesByTestResultEntry.toString("utf8")),
626623
indexKnownByHistoryId: JSON.parse(indexKnownByHistoryIdEntry.toString("utf8")),
627624
qualityGateResults: JSON.parse(qualityGateResultsEntry.toString("utf8")),
625+
testResultIdsIngestOrder: testResultIngestOrderEntry
626+
? JSON.parse(testResultIngestOrderEntry.toString("utf8"))
627+
: [],
628628
};
629629
const dumpTempDir = await mkdtemp(join(tmpdir(), basename(dump, ".zip")));
630630
const resultsAttachments: Record<string, ResultFile> = {};

packages/core/src/store/convert.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export const testResultRawToState = (stateData: StateData, raw: RawTestResult, c
104104
flaky: raw.flaky ?? false,
105105
muted: raw.muted ?? false,
106106
known: raw.known ?? false,
107-
hidden: false,
107+
isRetry: false,
108108

109109
labels,
110110
steps: convertSteps(stateData, raw.steps).convertedSteps,
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import type { TestParameter, TestResult } from "@allurereport/core-api";
2+
3+
import { md5 } from "../utils/crypto.js";
4+
5+
const NO_RETRIES: TestResult[] = [];
6+
7+
const compareByNameThenValue = (first: TestParameter, second: TestParameter) =>
8+
first.name.localeCompare(second.name) || first.value.localeCompare(second.value);
9+
10+
const stringifyRetryParameters = (parameters: TestParameter[] = []): string =>
11+
parameters
12+
.filter((parameter) => !parameter.excluded)
13+
.sort(compareByNameThenValue)
14+
.map((parameter) => `${parameter.name}:${parameter.value}`)
15+
.join(",");
16+
17+
export const calculateParametersHash = (parameters: TestParameter[] = []): string =>
18+
md5(stringifyRetryParameters(parameters));
19+
20+
export const calculateRetryHash = (
21+
testCaseId: string | undefined,
22+
parametersHash: string,
23+
environmentId: string | undefined,
24+
): string | undefined => {
25+
if (!testCaseId) {
26+
return undefined;
27+
}
28+
29+
return md5(`${testCaseId}:${parametersHash}:${environmentId ?? "default"}`);
30+
};
31+
32+
export class RetrySubstore {
33+
readonly #testResultsByRetryHash = new Map<string, TestResult[]>();
34+
readonly #testResultIngestIndexById = new Map<string, number>();
35+
36+
recordIngestOrder(testResultId: string) {
37+
if (this.#testResultIngestIndexById.has(testResultId)) {
38+
return;
39+
}
40+
41+
this.#testResultIngestIndexById.set(testResultId, this.#testResultIngestIndexById.size);
42+
}
43+
44+
restoreIngestOrder(restoredIds: string[] | undefined, hasTestResult: (testResultId: string) => boolean) {
45+
this.#testResultIngestIndexById.clear();
46+
47+
for (const id of restoredIds ?? []) {
48+
if (!hasTestResult(id) || this.#testResultIngestIndexById.has(id)) {
49+
continue;
50+
}
51+
52+
this.#testResultIngestIndexById.set(id, this.#testResultIngestIndexById.size);
53+
}
54+
}
55+
56+
ingestOrderIdsForDump(): string[] {
57+
return Array.from(this.#testResultIngestIndexById.keys());
58+
}
59+
60+
#getIngestIndex(testResultId: string): number {
61+
return this.#testResultIngestIndexById.get(testResultId) ?? -1;
62+
}
63+
64+
#compareResults(first: TestResult, second: TestResult): -1 | 0 | 1 {
65+
const firstHasStart = typeof first.start === "number";
66+
const secondHasStart = typeof second.start === "number";
67+
68+
if (firstHasStart !== secondHasStart) {
69+
return firstHasStart ? 1 : -1;
70+
}
71+
72+
if (firstHasStart && secondHasStart && first.start !== second.start) {
73+
return first.start! > second.start! ? -1 : 1;
74+
}
75+
76+
const indexA = this.#getIngestIndex(first.id);
77+
const indexB = this.#getIngestIndex(second.id);
78+
79+
if (indexA !== indexB) {
80+
return indexA > indexB ? -1 : 1;
81+
}
82+
83+
return 0;
84+
}
85+
86+
upsert(testResult: TestResult) {
87+
testResult.isRetry = false;
88+
89+
if (!testResult.retryHash) {
90+
return;
91+
}
92+
93+
const results = this.#testResultsByRetryHash.get(testResult.retryHash);
94+
95+
if (!results) {
96+
this.#testResultsByRetryHash.set(testResult.retryHash, [testResult]);
97+
return;
98+
}
99+
100+
results.push(testResult);
101+
results.sort((first, second) => this.#compareResults(first, second));
102+
103+
results.forEach((attempt, index) => {
104+
attempt.isRetry = index !== 0;
105+
});
106+
}
107+
108+
retriesByTr(testResult: TestResult): TestResult[] {
109+
if (!testResult.retryHash || testResult.isRetry) {
110+
return NO_RETRIES;
111+
}
112+
113+
const attempts = this.#testResultsByRetryHash.get(testResult.retryHash) ?? [];
114+
const index = attempts.findIndex((attempt) => attempt.id === testResult.id);
115+
116+
if (index !== 0) {
117+
return NO_RETRIES;
118+
}
119+
120+
return attempts.slice(1);
121+
}
122+
123+
reset() {
124+
this.#testResultsByRetryHash.clear();
125+
}
126+
}

0 commit comments

Comments
 (0)