Skip to content

Commit

Permalink
feat(core): implement compare reports functions
Browse files Browse the repository at this point in the history
  • Loading branch information
matejchalk committed Mar 11, 2024
1 parent 72d6c14 commit 5493cc4
Show file tree
Hide file tree
Showing 7 changed files with 213 additions and 2 deletions.
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export {
CollectAndPersistReportsOptions,
collectAndPersistReports,
} from './lib/collect-and-persist';
export { compareReportFiles, compareReports } from './lib/compare';
export { CollectOptions, collect } from './lib/implementation/collect';
export {
PluginOutputMissingAuditError,
Expand Down
65 changes: 65 additions & 0 deletions packages/core/src/lib/compare.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { writeFile } from 'node:fs/promises';
import { Report, ReportsDiff, reportSchema } from '@code-pushup/models';
import {
Diff,
calcDuration,
readJsonFile,
scoreReport,
} from '@code-pushup/utils';
import { name as packageName, version } from '../../package.json';
import {
ReportsToCompare,
compareAudits,
compareCategories,
compareGroups,
} from './implementation/compare-scorables';

export function compareReports(reports: Diff<Report>): ReportsDiff {
const start = performance.now();
const date = new Date().toISOString();

const commits: ReportsDiff['commits'] =
reports.before.commit != null && reports.after.commit != null
? { before: reports.before.commit, after: reports.after.commit }
: null;

const scoredReports: ReportsToCompare = {
before: scoreReport(reports.before),
after: scoreReport(reports.after),
};

const categories = compareCategories(scoredReports);
const groups = compareGroups(scoredReports);
const audits = compareAudits(scoredReports);

const duration = calcDuration(start);

return {
commits,
categories,
groups,
audits,
packageName,
version,
date,
duration,
};
}

export async function compareReportFiles(
inputPaths: Diff<string>,
outputPath: string,
): Promise<void> {
const [reportBefore, reportAfter] = await Promise.all([
readJsonFile(inputPaths.before),
readJsonFile(inputPaths.after),
]);
const reports: Diff<Report> = {
before: reportSchema.parse(reportBefore),
after: reportSchema.parse(reportAfter),
};

const reportsDiff = compareReports(reports);

await writeFile(outputPath, JSON.stringify(reportsDiff, null, 2));
}
137 changes: 137 additions & 0 deletions packages/core/src/lib/compare.unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { vol } from 'memfs';
import { join } from 'node:path';
import { Commit, Report, reportsDiffSchema } from '@code-pushup/models';
import {
COMMIT_ALT_MOCK,
COMMIT_MOCK,
MEMFS_VOLUME,
MINIMAL_REPORT_MOCK,
REPORT_MOCK,
reportMock,
} from '@code-pushup/test-utils';
import { Diff, readJsonFile } from '@code-pushup/utils';
import { compareReportFiles, compareReports } from './compare';

describe('compareReports', () => {
const mockCommits: Diff<Commit> = {
before: COMMIT_MOCK,
after: COMMIT_ALT_MOCK,
};

describe('unchanged reports', () => {
const mockReport = reportMock();
const mockReports: Diff<Report> = {
before: { ...mockReport, commit: mockCommits.before },
after: { ...mockReport, commit: mockCommits.after },
};

it('should create valid report diff', () => {
const reportsDiff = compareReports(mockReports);
expect(() => reportsDiffSchema.parse(reportsDiff)).not.toThrow();
});

it('should include commits from both reports', () => {
const reportsDiff = compareReports(mockReports);
expect(reportsDiff.commits).toEqual(mockCommits);
});

it('should have no changes, additions or removals', () => {
const reportsDiff = compareReports(mockReports);
expect(reportsDiff.categories.changed).toHaveLength(0);
expect(reportsDiff.categories.added).toHaveLength(0);
expect(reportsDiff.categories.removed).toHaveLength(0);
expect(reportsDiff.groups.changed).toHaveLength(0);
expect(reportsDiff.groups.added).toHaveLength(0);
expect(reportsDiff.groups.removed).toHaveLength(0);
expect(reportsDiff.audits.changed).toHaveLength(0);
expect(reportsDiff.audits.added).toHaveLength(0);
expect(reportsDiff.audits.removed).toHaveLength(0);
});

it('should contain all categories/groups/audits in unchanged arrays', () => {
const reportsDiff = compareReports(mockReports);
expect(reportsDiff.categories.unchanged).toHaveLength(
mockReport.categories.length,
);
expect(reportsDiff.groups.unchanged).toHaveLength(
mockReport.plugins.reduce((acc, { groups }) => acc + groups!.length, 0),
);
expect(reportsDiff.audits.unchanged).toHaveLength(
mockReport.plugins.reduce((acc, { audits }) => acc + audits.length, 0),
);
});
});

describe('changed reports', () => {
const mockReports: Diff<Report> = {
before: { ...MINIMAL_REPORT_MOCK, commit: mockCommits.before },
after: { ...REPORT_MOCK, commit: mockCommits.after },
};

it('should create valid report diff', () => {
const reportsDiff = compareReports(mockReports);
expect(() => reportsDiffSchema.parse(reportsDiff)).not.toThrow();
});

it('should include commits from both reports', () => {
const reportsDiff = compareReports(mockReports);
expect(reportsDiff.commits).toEqual(mockCommits);
});

it('should only have added categories (minimal report has none)', () => {
const reportsDiff = compareReports(mockReports);
expect(reportsDiff.categories.added).toHaveLength(
REPORT_MOCK.categories.length,
);
expect(reportsDiff.categories.removed).toHaveLength(0);
expect(reportsDiff.categories.changed).toHaveLength(0);
expect(reportsDiff.categories.unchanged).toHaveLength(0);
});

it('should only have added groups (minimal report has none)', () => {
const reportsDiff = compareReports(mockReports);
expect(reportsDiff.groups.added.length).toBeGreaterThan(0);
expect(reportsDiff.groups.removed).toHaveLength(0);
expect(reportsDiff.groups.changed).toHaveLength(0);
expect(reportsDiff.groups.unchanged).toHaveLength(0);
});

it('should mark multiple audits as added and 1 as removed (single audit from minimal report unmatched)', () => {
const reportsDiff = compareReports(mockReports);
expect(reportsDiff.audits.added.length).toBeGreaterThan(1);
expect(reportsDiff.audits.removed).toHaveLength(1);
expect(reportsDiff.audits.changed).toHaveLength(0);
expect(reportsDiff.audits.unchanged).toHaveLength(0);
});
});
});

describe('compareReportFiles', () => {
beforeEach(() => {
vol.fromJSON(
{
'source-report.json': JSON.stringify(MINIMAL_REPORT_MOCK),
'target-report.json': JSON.stringify(REPORT_MOCK),
},
MEMFS_VOLUME,
);
});

it('should create valid reports-diff.json from report.json files', async () => {
await compareReportFiles(
{
before: join(MEMFS_VOLUME, 'source-report.json'),
after: join(MEMFS_VOLUME, 'target-report.json'),
},
join(MEMFS_VOLUME, 'reports-diff.json'),
);

const reportsDiffPromise = readJsonFile(
join(MEMFS_VOLUME, 'reports-diff.json'),
);
await expect(reportsDiffPromise).resolves.toBeTruthy();

const reportsDiff = await reportsDiffPromise;
expect(() => reportsDiffSchema.parse(reportsDiff)).not.toThrow();
});
});
1 change: 1 addition & 0 deletions testing/test-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from './lib/utils/execute-process-helper.mock';
export * from './lib/utils/os-agnostic-paths';

// static mocks
export * from './lib/utils/commit.mock';
export * from './lib/utils/core-config.mock';
export * from './lib/utils/minimal-config.mock';
export * from './lib/utils/report.mock';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,10 @@ export const COMMIT_MOCK: Commit = {
author: 'John Doe',
date: new Date('2023-08-16T08:30:00.000Z'),
};

export const COMMIT_ALT_MOCK: Commit = {
hash: '0123456789abcdef0123456789abcdef01234567',
message: 'Major fixes',
author: 'Jane Doe',
date: new Date('2023-08-16T10:00:00.000Z'),
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Report, reportSchema } from '@code-pushup/models';
import { COMMIT_MOCK } from '../commit.mock';
import { categoryConfigsMock } from './categories.mock';
import { COMMIT_MOCK } from './commit.mock';
import { eslintPluginReportMock } from './eslint-plugin.mock';
import { lighthousePluginReportMock } from './lighthouse-plugin.mock';

Expand Down
2 changes: 1 addition & 1 deletion testing/test-utils/src/lib/utils/report.mock.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { PluginConfig, PluginReport, Report } from '@code-pushup/models';
import { COMMIT_MOCK } from './dynamic-mocks/commit.mock';
import { COMMIT_MOCK } from './commit.mock';
import {
auditReportMock,
pluginConfigMock,
Expand Down

0 comments on commit 5493cc4

Please sign in to comment.