Skip to content

Commit

Permalink
refactor: handle attempt number explicitly
Browse files Browse the repository at this point in the history
  • Loading branch information
shadowusr committed Nov 26, 2023
1 parent 0feb081 commit 97b034a
Show file tree
Hide file tree
Showing 13 changed files with 163 additions and 107 deletions.
7 changes: 5 additions & 2 deletions hermione.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ async function prepare(hermione: Hermione, reportBuilder: StaticReportBuilder, p
const {imageHandler} = reportBuilder;

const failHandler = async (testResult: HermioneTestResult): Promise<ReporterTestResult> => {
const formattedResult = formatTestResult(testResult, FAIL, reportBuilder);
const attempt = reportBuilder.testAttemptManager.registerAttempt({fullName: testResult.fullTitle(), browserId: testResult.browserId}, FAIL);
const formattedResult = formatTestResult(testResult, FAIL, attempt, reportBuilder);

const actions: Promise<unknown>[] = [imageHandler.saveTestImages(formattedResult, workers)];

if (pluginConfig.saveErrorDetails && formattedResult.errorDetails) {
Expand All @@ -62,7 +64,8 @@ async function prepare(hermione: Hermione, reportBuilder: StaticReportBuilder, p

hermione.on(hermione.events.TEST_PASS, testResult => {
promises.push(queue.add(async () => {
const formattedResult = formatTestResult(testResult, SUCCESS, reportBuilder);
const attempt = reportBuilder.testAttemptManager.registerAttempt({fullName: testResult.fullTitle(), browserId: testResult.browserId}, FAIL);
const formattedResult = formatTestResult(testResult, SUCCESS, attempt, reportBuilder);
await imageHandler.saveTestImages(formattedResult, workers);

return reportBuilder.addSuccess(formattedResult);
Expand Down
38 changes: 25 additions & 13 deletions lib/gui/tool-runner/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {TestBranch, TestEqualDiffsData, TestRefUpdateData} from '../../tests-tre
import {ReporterTestResult} from '../../test-adapter';
import {ImagesInfoFormatter} from '../../image-handler';
import {SqliteClient} from '../../sqlite-client';
import {TestAttemptManager} from '../../test-attempt-manager';

type ToolRunnerArgs = [paths: string[], hermione: Hermione & HtmlReporterApi, configs: GuiConfigs];

Expand All @@ -53,8 +54,13 @@ export interface UndoAcceptImagesResult {
}

// TODO: get rid of this function. It allows to format raw test, but is type-unsafe.
const formatTestResultUnsafe = (test: HermioneTest | HermioneTestExtended | HermioneTestPlain, status: TestStatus, {imageHandler}: {imageHandler: ImagesInfoFormatter}): ReporterTestResult => {
return formatTestResult(test as HermioneTestResult, status, {imageHandler});
const formatTestResultUnsafe = (
test: HermioneTest | HermioneTestExtended | HermioneTestPlain,
status: TestStatus,
attempt: number,
{imageHandler}: {imageHandler: ImagesInfoFormatter}
): ReporterTestResult => {
return formatTestResult(test as HermioneTestResult, status, attempt, {imageHandler});
};

export class ToolRunner {
Expand Down Expand Up @@ -103,8 +109,9 @@ export class ToolRunner {
await mergeDatabasesForReuse(this._reportPath);

const dbClient = await SqliteClient.create({htmlReporter: this._hermione.htmlReporter, reportPath: this._reportPath, reuse: true});
const testAttemptManager = new TestAttemptManager();

this._reportBuilder = GuiReportBuilder.create(this._hermione.htmlReporter, this._pluginConfig, {dbClient});
this._reportBuilder = GuiReportBuilder.create(this._hermione.htmlReporter, this._pluginConfig, {dbClient, testAttemptManager});
this._subscribeOnEvents();

this._collection = await this._readTests();
Expand Down Expand Up @@ -167,11 +174,12 @@ export class ToolRunner {

return Promise.all(tests.map(async (test): Promise<TestBranch> => {
const updateResult = this._prepareTestResult(test);
const formattedResult = formatTestResultUnsafe(updateResult, UPDATED, reportBuilder);

const fullName = test.suite.path.join(' ');
const updateAttempt = reportBuilder.testAttemptManager.registerAttempt({fullName, browserId: test.browserId}, UPDATED);
const formattedResult = formatTestResultUnsafe(updateResult, UPDATED, updateAttempt, reportBuilder);
const failResultId = formattedResult.id;
const updateAttempt = reportBuilder.getUpdatedAttempt(formattedResult);

formattedResult.attempt = updateAttempt;
updateResult.attempt = updateAttempt;

await Promise.all(updateResult.imagesInfo.map(async (imageInfo) => {
Expand All @@ -183,7 +191,7 @@ export class ToolRunner {
this._emitUpdateReference(result, stateName);
}));

reportBuilder.addUpdated(formatTestResultUnsafe(updateResult, UPDATED, reportBuilder), failResultId);
reportBuilder.addUpdated(formattedResult, failResultId);

return reportBuilder.getTestBranch(formattedResult.id);
}));
Expand All @@ -195,9 +203,9 @@ export class ToolRunner {

await Promise.all(tests.map(async (test) => {
const updateResult = this._prepareTestResult(test);
const formattedResult = formatTestResultUnsafe(updateResult, UPDATED, reportBuilder);

formattedResult.attempt = updateResult.attempt;
const fullName = test.suite.path.join(' ');
const attempt = reportBuilder.testAttemptManager.removeAttempt({fullName, browserId: test.browserId});
const formattedResult = formatTestResultUnsafe(updateResult, UPDATED, attempt, reportBuilder);

await Promise.all(updateResult.imagesInfo.map(async (imageInfo) => {
const {stateName} = imageInfo;
Expand Down Expand Up @@ -300,9 +308,13 @@ export class ToolRunner {
const testId = formatId(test.id.toString(), browserId);
this._tests[testId] = _.extend(test, {browserId});

test.pending
? reportBuilder.addSkipped(formatTestResultUnsafe(test, SKIPPED, reportBuilder))
: reportBuilder.addIdle(formatTestResultUnsafe(test, IDLE, reportBuilder));
if (test.pending) {
const attempt = reportBuilder.testAttemptManager.registerAttempt({fullName: test.fullTitle(), browserId: test.browserId}, SKIPPED);
reportBuilder.addSkipped(formatTestResultUnsafe(test, SKIPPED, attempt, reportBuilder));
} else {
const attempt = reportBuilder.testAttemptManager.registerAttempt({fullName: test.fullTitle(), browserId: test.browserId}, IDLE);
reportBuilder.addIdle(formatTestResultUnsafe(test, IDLE, attempt, reportBuilder));
}
});

await this._fillTestsTree();
Expand Down
24 changes: 13 additions & 11 deletions lib/gui/tool-runner/report-subscriber.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ let workers: ReturnType<typeof createWorkers>;

export const subscribeOnToolEvents = (hermione: Hermione, reportBuilder: GuiReportBuilder, client: EventSource, reportPath: string): void => {
const queue = new PQueue({concurrency: os.cpus().length});
const {imageHandler} = reportBuilder;
const {imageHandler, testAttemptManager} = reportBuilder;

async function failHandler(formattedResult: ReporterTestResult): Promise<void> {
const actions: Promise<unknown>[] = [imageHandler.saveTestImages(formattedResult, workers)];
Expand Down Expand Up @@ -45,8 +45,8 @@ export const subscribeOnToolEvents = (hermione: Hermione, reportBuilder: GuiRepo
});

hermione.on(hermione.events.TEST_BEGIN, (data) => {
const formattedResult = formatTestResult(data as HermioneTestResult, RUNNING, reportBuilder);
formattedResult.attempt = reportBuilder.getCurrAttempt(formattedResult);
const attempt = testAttemptManager.registerAttempt({fullName: data.fullTitle(), browserId: data.browserId}, RUNNING);
const formattedResult = formatTestResult(data as HermioneTestResult, RUNNING, attempt, reportBuilder);

reportBuilder.addRunning(formattedResult);
const testBranch = reportBuilder.getTestBranch(formattedResult.id);
Expand All @@ -56,8 +56,8 @@ export const subscribeOnToolEvents = (hermione: Hermione, reportBuilder: GuiRepo

hermione.on(hermione.events.TEST_PASS, (testResult) => {
queue.add(async () => {
const formattedResult = formatTestResult(testResult, SUCCESS, reportBuilder);
formattedResult.attempt = reportBuilder.getCurrAttempt(formattedResult);
const attempt = testAttemptManager.registerAttempt({fullName: testResult.fullTitle(), browserId: testResult.browserId}, SUCCESS);
const formattedResult = formatTestResult(testResult, SUCCESS, attempt, reportBuilder);

await imageHandler.saveTestImages(formattedResult, workers);
reportBuilder.addSuccess(formattedResult);
Expand All @@ -70,8 +70,9 @@ export const subscribeOnToolEvents = (hermione: Hermione, reportBuilder: GuiRepo
hermione.on(hermione.events.RETRY, (testResult) => {
queue.add(async () => {
const status = hasDiff(testResult.assertViewResults as ImageDiffError[]) ? TestStatus.FAIL : TestStatus.ERROR;
const formattedResult = formatTestResult(testResult, status, reportBuilder);
formattedResult.attempt = reportBuilder.getCurrAttempt(formattedResult);
const attempt = testAttemptManager.registerAttempt({fullName: testResult.fullTitle(), browserId: testResult.browserId}, status);

const formattedResult = formatTestResult(testResult, status, attempt, reportBuilder);

await failHandler(formattedResult);
reportBuilder.addRetry(formattedResult);
Expand All @@ -84,8 +85,9 @@ export const subscribeOnToolEvents = (hermione: Hermione, reportBuilder: GuiRepo
hermione.on(hermione.events.TEST_FAIL, (testResult) => {
queue.add(async () => {
const status = hasDiff(testResult.assertViewResults as ImageDiffError[]) ? TestStatus.FAIL : TestStatus.ERROR;
const formattedResult = formatTestResult(testResult, status, reportBuilder);
formattedResult.attempt = reportBuilder.getCurrAttempt(formattedResult);
const attempt = testAttemptManager.registerAttempt({fullName: testResult.fullTitle(), browserId: testResult.browserId}, status);

const formattedResult = formatTestResult(testResult, status, attempt, reportBuilder);

await failHandler(formattedResult);
status === TestStatus.FAIL
Expand All @@ -99,8 +101,8 @@ export const subscribeOnToolEvents = (hermione: Hermione, reportBuilder: GuiRepo

hermione.on(hermione.events.TEST_PENDING, async (testResult) => {
queue.add(async () => {
const formattedResult = formatTestResult(testResult as HermioneTestResult, SKIPPED, reportBuilder);
formattedResult.attempt = reportBuilder.getCurrAttempt(formattedResult);
const attempt = testAttemptManager.registerAttempt({fullName: testResult.fullTitle(), browserId: testResult.browserId}, SKIPPED);
const formattedResult = formatTestResult(testResult as HermioneTestResult, SKIPPED, attempt, reportBuilder);

await failHandler(formattedResult);
reportBuilder.addSkipped(formattedResult);
Expand Down
4 changes: 3 additions & 1 deletion lib/plugin-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {HtmlReporter} from './plugin-api';
import {HtmlReporterApi, ReporterConfig, ReporterOptions} from './types';
import {ToolName} from './constants';
import {SqliteClient} from './sqlite-client';
import {TestAttemptManager} from './test-attempt-manager';

type PrepareFn = (hermione: Hermione & HtmlReporterApi, reportBuilder: StaticReportBuilder, config: ReporterConfig) => Promise<void>;

Expand Down Expand Up @@ -62,8 +63,9 @@ export class PluginAdapter {
}

const dbClient = await SqliteClient.create({htmlReporter: this._hermione.htmlReporter, reportPath: this._config.path});
const testAttemptManager = new TestAttemptManager();

const staticReportBuilder = StaticReportBuilder.create(this._hermione.htmlReporter, this._config, {dbClient});
const staticReportBuilder = StaticReportBuilder.create(this._hermione.htmlReporter, this._config, {dbClient, testAttemptManager});

return Promise
.all([
Expand Down
43 changes: 14 additions & 29 deletions lib/report-builder/gui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as _ from 'lodash';
import {StaticReportBuilder} from './static';
import {GuiTestsTreeBuilder, TestBranch, TestEqualDiffsData, TestRefUpdateData} from '../tests-tree-builder/gui';
import {
IDLE, RUNNING, SKIPPED, FAIL, SUCCESS, UPDATED, TestStatus, DB_COLUMNS, ToolName, ERROR
IDLE, RUNNING, FAIL, SUCCESS, UPDATED, TestStatus, DB_COLUMNS, ToolName, ERROR
} from '../constants';
import {ConfigForStaticFile, getConfigForStaticFile} from '../server-utils';
import {ReporterTestResult} from '../test-adapter';
Expand All @@ -12,13 +12,15 @@ import {ImageInfoWithState, ReporterConfig} from '../types';
import {hasDiff, hasNoRefImageErrors, hasResultFails, isSkippedStatus, isUpdatedStatus} from '../common-utils';
import {HtmlReporterValues} from '../plugin-api';
import {SkipItem} from '../tests-tree-builder/static';
import {copyAndUpdate} from '../test-adapter/utils';

interface UndoAcceptImageResult {
updatedImage: TreeImage | undefined;
removedResult: string | undefined;
previousExpectedPath: string | null;
shouldRemoveReference: boolean;
shouldRevertReference: boolean;
newTestResult: ReporterTestResult;
}

export interface GuiReportBuilderResult {
Expand Down Expand Up @@ -102,31 +104,10 @@ export class GuiReportBuilder extends StaticReportBuilder {
return this._testsTree.getImageDataToFindEqualDiffs(imageIds);
}

getCurrAttempt(formattedResult: ReporterTestResult): number {
const lastResult = this._testsTree.getLastResult(formattedResult);
this._checkResult(lastResult, formattedResult);

const {status, attempt} = lastResult;

return [IDLE, RUNNING, SKIPPED].includes(status) ? attempt : attempt + 1;
}

getUpdatedAttempt(formattedResult: ReporterTestResult): number {
const lastResult = this._testsTree.getLastResult(formattedResult);
this._checkResult(lastResult, formattedResult);

const {attempt} = lastResult;

const imagesInfo = this._testsTree.getImagesInfo(formattedResult.id);
const isUpdated = imagesInfo.some((image) => image.status === UPDATED);

return isUpdated ? attempt : attempt + 1;
}

undoAcceptImage(formattedResult: ReporterTestResult, stateName: string): UndoAcceptImageResult | null {
const resultId = formattedResult.id;
const suitePath = formattedResult.testPath;
const browserName = formattedResult.browserId;
undoAcceptImage(testResult: ReporterTestResult, stateName: string): UndoAcceptImageResult | null {
const resultId = testResult.id;
const suitePath = testResult.testPath;
const browserName = testResult.browserId;
const resultData = this._testsTree.getResultDataToUnacceptImage(resultId, stateName);

if (!resultData || !isUpdatedStatus(resultData.status)) {
Expand All @@ -146,15 +127,19 @@ export class GuiReportBuilder extends StaticReportBuilder {
const shouldRemoveReference = _.isNull(previousImageRefImgSize);
const shouldRevertReference = !shouldRemoveReference;

let updatedImage, removedResult;
let updatedImage: TreeImage | undefined, removedResult: string | undefined, newTestResult: ReporterTestResult;

if (shouldRemoveResult) {
this._testsTree.removeTestResult(resultId);
formattedResult.attempt = formattedResult.attempt - 1;
this._testAttemptManager.removeAttempt(testResult);

newTestResult = copyAndUpdate(testResult, {attempt: this._testAttemptManager.getCurrentAttempt(testResult)});

removedResult = resultId;
} else {
updatedImage = this._testsTree.updateImageInfo(imageId, previousImage);

newTestResult = testResult;
}

this._deleteTestResultFromDb({where: [
Expand All @@ -165,7 +150,7 @@ export class GuiReportBuilder extends StaticReportBuilder {
`json_extract(${DB_COLUMNS.IMAGES_INFO}, '$[0].stateName') = ?`
].join(' AND ')}, JSON.stringify(suitePath), browserName, status, timestamp.toString(), stateName);

return {updatedImage, removedResult, previousExpectedPath, shouldRemoveReference, shouldRevertReference};
return {updatedImage, removedResult, previousExpectedPath, shouldRemoveReference, shouldRevertReference, newTestResult};
}

protected override _addTestResult(formattedResult: ReporterTestResult, props: {status: TestStatus} & Partial<PreparedTestResult>, opts: {failResultId?: string} = {}): ReporterTestResult {
Expand Down
13 changes: 11 additions & 2 deletions lib/report-builder/static.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,21 @@ import {SqliteImageStore} from '../image-store';
import {getUrlWithBase, getError, getRelativeUrl, hasDiff, hasNoRefImageErrors} from '../common-utils';
import {getTestFromDb} from '../db-utils/server';
import {ImageDiffError} from '../errors';
import {TestAttemptManager} from '../test-attempt-manager';

const ignoredStatuses = [RUNNING, IDLE];

interface StaticReportBuilderOptions {
dbClient: SqliteClient;
testAttemptManager: TestAttemptManager;
}

export class StaticReportBuilder {
protected _htmlReporter: HtmlReporter;
protected _pluginConfig: ReporterConfig;
protected _dbClient: SqliteClient;
protected _imageHandler: ImageHandler;
protected _testAttemptManager: TestAttemptManager;

static create<T extends StaticReportBuilder>(
this: new (htmlReporter: HtmlReporter, pluginConfig: ReporterConfig, options: StaticReportBuilderOptions) => T,
Expand All @@ -46,12 +49,14 @@ export class StaticReportBuilder {
return new this(htmlReporter, pluginConfig, options);
}

constructor(htmlReporter: HtmlReporter, pluginConfig: ReporterConfig, {dbClient}: StaticReportBuilderOptions) {
constructor(htmlReporter: HtmlReporter, pluginConfig: ReporterConfig, {dbClient, testAttemptManager}: StaticReportBuilderOptions) {
this._htmlReporter = htmlReporter;
this._pluginConfig = pluginConfig;

this._dbClient = dbClient;

this._testAttemptManager = testAttemptManager;

const imageStore = new SqliteImageStore(this._dbClient);
this._imageHandler = new ImageHandler(imageStore, htmlReporter.imagesSaver, {reportPath: pluginConfig.path});

Expand All @@ -66,6 +71,10 @@ export class StaticReportBuilder {
return this._imageHandler;
}

get testAttemptManager(): TestAttemptManager {
return this._testAttemptManager;
}

async saveStaticFiles(): Promise<void> {
const destPath = this._pluginConfig.path;

Expand Down Expand Up @@ -131,7 +140,7 @@ export class StaticReportBuilder {
return formattedResult;
}

protected _createTestResult(result: ReporterTestResult, props: {attempt?: number, status: TestStatus, timestamp: number;} & Partial<PreparedTestResult>): PreparedTestResult {
protected _createTestResult(result: ReporterTestResult, props: {attempt?: number | null, status: TestStatus, timestamp: number;} & Partial<PreparedTestResult>): PreparedTestResult {
const {
browserId, file, sessionId, description, history,
imagesInfo = [], screenshot, multipleTabs, errorDetails
Expand Down
9 changes: 7 additions & 2 deletions lib/server-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,11 @@ export function mapPlugins<T>(plugins: ReporterConfig['plugins'], callback: (nam
return result;
}

export const formatTestResult = (rawResult: HermioneTestResult, status: TestStatus, {imageHandler}: {imageHandler: ImagesInfoFormatter}): ReporterTestResult => {
return new HermioneTestAdapter(rawResult, {status, imagesInfoFormatter: imageHandler});
export const formatTestResult = (
rawResult: HermioneTestResult,
status: TestStatus,
attempt: number,
{imageHandler}: {imageHandler: ImagesInfoFormatter}
): ReporterTestResult => {
return new HermioneTestAdapter(rawResult, {attempt, status, imagesInfoFormatter: imageHandler});
};
Loading

0 comments on commit 97b034a

Please sign in to comment.