Skip to content

Commit

Permalink
refactor: introduce adapter pattern for test results (#493)
Browse files Browse the repository at this point in the history
* chore: rename test-adapter to test-adapter/hermione

* refactor: introduce adapter pattern for test results and improve its interface
  • Loading branch information
shadowusr authored Sep 6, 2023
1 parent 854c9f0 commit 517ab15
Show file tree
Hide file tree
Showing 30 changed files with 30,246 additions and 24,543 deletions.
13 changes: 8 additions & 5 deletions hermione.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ const os = require('os');
const PQueue = require('p-queue');
const {PluginAdapter} = require('./lib/plugin-adapter');
const {createWorkers} = require('./lib/workers/create-workers');
const {FAIL, SUCCESS} = require('./lib/constants');
const {hasDiff} = require('./lib/common-utils');
const {formatTestResult} = require('./lib/server-utils');

let workers;

Expand Down Expand Up @@ -32,8 +35,8 @@ async function prepare(hermione, reportBuilder, pluginConfig) {
const {imageHandler} = reportBuilder;

const failHandler = async (testResult) => {
const formattedResult = reportBuilder.format(testResult);
const actions = [imageHandler.saveTestImages(testResult, formattedResult.attempt, workers)];
const formattedResult = formatTestResult(testResult, FAIL, reportBuilder);
const actions = [imageHandler.saveTestImages(formattedResult, workers)];

if (formattedResult.errorDetails) {
actions.push(formattedResult.saveErrorDetails(reportPath));
Expand All @@ -45,7 +48,7 @@ async function prepare(hermione, reportBuilder, pluginConfig) {
};

const addFail = (formattedResult) => {
return formattedResult.hasDiff()
return hasDiff(formattedResult.assertViewResults)
? reportBuilder.addFail(formattedResult)
: reportBuilder.addError(formattedResult);
};
Expand All @@ -56,8 +59,8 @@ async function prepare(hermione, reportBuilder, pluginConfig) {

hermione.on(hermione.events.TEST_PASS, testResult => {
promises.push(queue.add(async () => {
const formattedResult = reportBuilder.format(testResult);
await imageHandler.saveTestImages(testResult, formattedResult.attempt, workers);
const formattedResult = formatTestResult(testResult, SUCCESS, reportBuilder);
await imageHandler.saveTestImages(formattedResult, workers);

return reportBuilder.addSuccess(formattedResult);
}).catch(reject));
Expand Down
42 changes: 35 additions & 7 deletions lib/common-utils.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import crypto from 'crypto';
import {pick} from 'lodash';
import {pick, isEmpty} from 'lodash';
import url from 'url';
import axios, {AxiosRequestConfig} from 'axios';
import {SUCCESS, FAIL, ERROR, SKIPPED, UPDATED, IDLE, RUNNING, QUEUED, TestStatus} from './constants';

import {UNCHECKED, INDETERMINATE, CHECKED} from './constants/checked-statuses';
import {AssertViewResult, TestResult} from './types';
import {AssertViewResult, TestError} from './types';
import {ErrorName, ImageDiffError, NoRefImageError} from './errors';
export const getShortMD5 = (str: string): string => {
return crypto.createHash('md5').update(str, 'ascii').digest('hex').substr(0, 7);
Expand Down Expand Up @@ -46,24 +46,52 @@ export const determineStatus = (statuses: TestStatus[]): TestStatus | null => {
return null;
};

export const getAbsoluteUrl = (url: string | undefined, baseUrl: string | undefined): string => {
try {
return new URL(url ?? '', isEmpty(baseUrl) ? undefined : baseUrl).href;
} catch {
return baseUrl || url || '';
}
};

export const getRelativeUrl = (absoluteUrl: string): string => {
try {
const urlObj = new URL(absoluteUrl);

return urlObj.pathname + urlObj.search;
} catch {
return absoluteUrl;
}
};

export const wrapLinkByTag = (text: string): string => {
return text.replace(/https?:\/\/[^\s]*/g, (url) => {
return `<a target="_blank" href="${url}">${url}</a>`;
});
};

export const mkTestId = (fullTitle: string, browserId: string): string => {
return fullTitle + '.' + browserId;
};

export const isImageDiffError = (assertResult: AssertViewResult): assertResult is ImageDiffError => {
return assertResult.name === ErrorName.IMAGE_DIFF;
return (assertResult as ImageDiffError).name === ErrorName.IMAGE_DIFF;
};

export const isNoRefImageError = (assertResult: AssertViewResult): assertResult is NoRefImageError => {
return assertResult.name === ErrorName.NO_REF_IMAGE;
return (assertResult as NoRefImageError).name === ErrorName.NO_REF_IMAGE;
};

export const getError = (testResult: TestResult): undefined | {message?: string; stack?: string; stateName?: string} => {
if (!testResult.err) {
export const getError = (error?: TestError): undefined | Pick<TestError, 'message' | 'stack' | 'stateName'> => {
if (!error) {
return undefined;
}

return pick(testResult.err, ['message', 'stack', 'stateName']);
return pick(error, ['message', 'stack', 'stateName']);
};

export const hasDiff = (assertViewResults: AssertViewResult[]): boolean => {
return assertViewResults.some((result) => isImageDiffError(result));
};

export const isUrl = (str: string): boolean => {
Expand Down
14 changes: 13 additions & 1 deletion lib/db-utils/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import NestedError from 'nested-error-stacks';
import {StaticTestsTreeBuilder} from '../tests-tree-builder/static';
import * as commonSqliteUtils from './common';
import {isUrl, fetchFile, normalizeUrls, logger} from '../common-utils';
import {DATABASE_URLS_JSON_NAME, LOCAL_DATABASE_NAME} from '../constants';
import {DATABASE_URLS_JSON_NAME, DB_COLUMNS, LOCAL_DATABASE_NAME, TestStatus} from '../constants';
import {DbLoadResult, HandleDatabasesOptions} from './common';
import {DbUrlsJsonData, RawSuitesRow, ReporterConfig} from '../types';
import {Tree} from '../tests-tree-builder/base';
import {ReporterTestResult} from '../test-adapter';
import {SqliteAdapter} from '../sqlite-adapter';

export * from './common';

Expand Down Expand Up @@ -117,3 +119,13 @@ async function rewriteDatabaseUrls(dbPaths: string[], mainDatabaseUrls: string,
jsonUrls: []
});
}

export const getTestFromDb = <T = unknown>(sqliteAdapter: SqliteAdapter, testResult: ReporterTestResult): T | undefined => {
return sqliteAdapter.query<T>({
select: '*',
where: `${DB_COLUMNS.SUITE_PATH} = ? AND ${DB_COLUMNS.NAME} = ? AND ${DB_COLUMNS.STATUS} = ?`,
orderBy: DB_COLUMNS.TIMESTAMP,
orderDescending: true,
limit: 1
}, JSON.stringify(testResult.testPath), testResult.browserId, TestStatus.SKIPPED);
};
15 changes: 9 additions & 6 deletions lib/errors/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import {CoordBounds} from 'looks-same';
import {DiffOptions, ImageData} from '../types';
import {ValueOf} from 'type-fest';

export enum ErrorName {
IMAGE_DIFF = 'ImageDiffError',
NO_REF_IMAGE = 'NoRefImageError'
}
export const ErrorName = {
IMAGE_DIFF: 'ImageDiffError',
NO_REF_IMAGE: 'NoRefImageError'
} as const;
export type ErrorName = ValueOf<typeof ErrorName>;
export type ErrorNames = typeof ErrorName;

export interface ImageDiffError {
name: ErrorName.IMAGE_DIFF;
name: ErrorNames['IMAGE_DIFF'];
message: string;
stack: string;
stateName: string;
Expand All @@ -19,7 +22,7 @@ export interface ImageDiffError {
}

export interface NoRefImageError {
name: ErrorName.NO_REF_IMAGE;
name: ErrorNames['NO_REF_IMAGE'];
stateName: string;
message: string;
stack: string;
Expand Down
13 changes: 7 additions & 6 deletions lib/gui/tool-runner/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ const GuiReportBuilder = require('../../report-builder/gui');
const EventSource = require('../event-source');
const {logger} = require('../../common-utils');
const reporterHelper = require('../../reporter-helpers');
const {UPDATED} = require('../../constants/test-statuses');
const {UPDATED, SKIPPED, IDLE} = require('../../constants/test-statuses');
const {DATABASE_URLS_JSON_NAME, LOCAL_DATABASE_NAME} = require('../../constants/database');
const {getShortMD5} = require('../../common-utils');
const {formatId, mkFullTitle, mergeDatabasesForReuse, filterByEqualDiffSizes} = require('./utils');
const {getTestsTreeFromDatabase} = require('../../db-utils/server');
const {formatTestResult} = require('../../server-utils');

module.exports = class ToolRunner {
static create(paths, hermione, configs) {
Expand Down Expand Up @@ -98,7 +99,7 @@ module.exports = class ToolRunner {
updateReferenceImage(tests) {
return Promise.map(tests, (test) => {
const updateResult = this._prepareTestResult(test);
const formattedResult = this._reportBuilder.format(updateResult, UPDATED);
const formattedResult = formatTestResult(updateResult, UPDATED, this._reportBuilder);
const failResultId = formattedResult.id;
const updateAttempt = this._reportBuilder.getUpdatedAttempt(formattedResult);

Expand All @@ -115,7 +116,7 @@ module.exports = class ToolRunner {
});
})
.then(() => {
this._reportBuilder.addUpdated(updateResult, failResultId);
this._reportBuilder.addUpdated(formatTestResult(updateResult, UPDATED, this._reportBuilder), failResultId);
return this._reportBuilder.getTestBranch(formattedResult.id);
});
});
Expand All @@ -126,7 +127,7 @@ module.exports = class ToolRunner {

await Promise.map(tests, async (test) => {
const updateResult = this._prepareTestResult(test);
const formattedResult = this._reportBuilder.format(updateResult);
const formattedResult = formatTestResult(updateResult, UPDATED, this._reportBuilder);

formattedResult.attempt = updateResult.attempt;

Expand Down Expand Up @@ -215,8 +216,8 @@ module.exports = class ToolRunner {
this._tests[testId] = _.extend(test, {browserId});

test.pending
? this._reportBuilder.addSkipped(test)
: this._reportBuilder.addIdle(test);
? this._reportBuilder.addSkipped(formatTestResult(test, SKIPPED, this._reportBuilder))
: this._reportBuilder.addIdle(formatTestResult(test, IDLE, this._reportBuilder));
});

await this._fillTestsTree();
Expand Down
25 changes: 14 additions & 11 deletions lib/gui/tool-runner/report-subscriber.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
const os = require('os');
const PQueue = require('p-queue');
const clientEvents = require('../constants/client-events');
const {RUNNING} = require('../../constants/test-statuses');
const {getSuitePath} = require('../../plugin-utils');
const {createWorkers} = require('../../workers/create-workers');
const {logError} = require('../../server-utils');
const {logError, formatTestResult} = require('../../server-utils');
const {hasDiff} = require('../../common-utils');
const {TestStatus, RUNNING, SUCCESS, SKIPPED} = require('../../constants');

let workers;

Expand All @@ -15,7 +16,7 @@ module.exports = (hermione, reportBuilder, client, reportPath) => {
const {imageHandler} = reportBuilder;

function failHandler(testResult, formattedResult) {
const actions = [imageHandler.saveTestImages(testResult, formattedResult.attempt, workers)];
const actions = [imageHandler.saveTestImages(formattedResult, workers)];

if (formattedResult.errorDetails) {
actions.push(formattedResult.saveErrorDetails(reportPath));
Expand All @@ -35,12 +36,12 @@ module.exports = (hermione, reportBuilder, client, reportPath) => {

client.emit(clientEvents.BEGIN_SUITE, {
suiteId: getSuitePath(suite).join(' '),
status: RUNNING
status: TestStatus.RUNNING
});
});

hermione.on(hermione.events.TEST_BEGIN, (data) => {
const formattedResult = reportBuilder.format(data, RUNNING);
const formattedResult = formatTestResult(data, RUNNING, reportBuilder);
formattedResult.attempt = reportBuilder.getCurrAttempt(formattedResult);

reportBuilder.addRunning(formattedResult);
Expand All @@ -51,10 +52,10 @@ module.exports = (hermione, reportBuilder, client, reportPath) => {

hermione.on(hermione.events.TEST_PASS, (testResult) => {
queue.add(async () => {
const formattedResult = reportBuilder.format(testResult, hermione.events.TEST_PASS);
const formattedResult = formatTestResult(testResult, SUCCESS, reportBuilder);
formattedResult.attempt = reportBuilder.getCurrAttempt(formattedResult);

await imageHandler.saveTestImages(testResult, formattedResult.attempt, workers);
await imageHandler.saveTestImages(formattedResult, workers);
reportBuilder.addSuccess(formattedResult);

const testBranch = reportBuilder.getTestBranch(formattedResult.id);
Expand All @@ -64,7 +65,8 @@ module.exports = (hermione, reportBuilder, client, reportPath) => {

hermione.on(hermione.events.RETRY, (testResult) => {
queue.add(async () => {
const formattedResult = reportBuilder.format(testResult, hermione.events.RETRY);
const status = hasDiff(testResult.assertViewResults) ? TestStatus.FAIL : TestStatus.ERROR;
const formattedResult = formatTestResult(testResult, status, reportBuilder);
formattedResult.attempt = reportBuilder.getCurrAttempt(formattedResult);

await failHandler(testResult, formattedResult);
Expand All @@ -77,11 +79,12 @@ module.exports = (hermione, reportBuilder, client, reportPath) => {

hermione.on(hermione.events.TEST_FAIL, (testResult) => {
queue.add(async () => {
const formattedResult = reportBuilder.format(testResult, hermione.events.TEST_FAIL);
const status = hasDiff(testResult.assertViewResults) ? TestStatus.FAIL : TestStatus.ERROR;
const formattedResult = formatTestResult(testResult, status, reportBuilder);
formattedResult.attempt = reportBuilder.getCurrAttempt(formattedResult);

await failHandler(testResult, formattedResult);
formattedResult.hasDiff()
status === TestStatus.FAIL
? reportBuilder.addFail(formattedResult)
: reportBuilder.addError(formattedResult);

Expand All @@ -92,7 +95,7 @@ module.exports = (hermione, reportBuilder, client, reportPath) => {

hermione.on(hermione.events.TEST_PENDING, async (testResult) => {
queue.add(async () => {
const formattedResult = reportBuilder.format(testResult, hermione.events.TEST_PENDING);
const formattedResult = formatTestResult(testResult, SKIPPED, reportBuilder);
formattedResult.attempt = reportBuilder.getCurrAttempt(formattedResult);

await failHandler(testResult, formattedResult);
Expand Down
Loading

0 comments on commit 517ab15

Please sign in to comment.