Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .betterer.results
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
// If this file contains merge conflicts, use `betterer merge` to automatically resolve them:
// https://phenomnomnominal.github.io/betterer/docs/results-file/#merge
//
exports[`no hack comments`] = {
value: `{}`
};

exports[`no knip errors`] = {
value: `{
".betterer.ts:194472954": [
Expand Down
4 changes: 4 additions & 0 deletions goldens/api/betterer.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@ export interface BettererRun {
readonly expected: BettererResult | null;
readonly filePaths: BettererFilePaths | null;
readonly isNew: boolean;
readonly isObsolete: boolean;
readonly isSkipped: boolean;
readonly name: string;
}
Expand Down Expand Up @@ -447,6 +448,7 @@ export interface BettererRunSummary extends BettererRun {
readonly isComplete: boolean;
readonly isExpired: boolean;
readonly isFailed: boolean;
readonly isRemoved: boolean;
readonly isSame: boolean;
readonly isUpdated: boolean;
readonly isWorse: boolean;
Expand Down Expand Up @@ -480,7 +482,9 @@ export interface BettererSuiteSummary extends BettererSuite {
readonly expired: BettererRunSummaries;
readonly failed: BettererRunSummaries;
readonly new: BettererRunSummaries;
readonly obsolete: BettererRunSummaries;
readonly ran: BettererRunSummaries;
readonly removed: BettererRunSummaries;
readonly runSummaries: BettererRunSummaries;
readonly same: BettererRunSummaries;
readonly skipped: BettererRunSummaries;
Expand Down
3 changes: 2 additions & 1 deletion packages/betterer/src/context/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ export class BettererContextΩ implements BettererContext {
const suiteSummary = await suite.run();

if (!isRunOnce && !ci) {
await results.api.write(suiteSummary.result);
const suiteSummaryΩ = suiteSummary as BettererSuiteSummaryΩ;
await results.api.write(suiteSummaryΩ.result);
}

this._suiteSummaries = [...this._suiteSummaries, suiteSummary];
Expand Down
50 changes: 50 additions & 0 deletions packages/betterer/src/run/run-obsolete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { BettererResult } from '../results/index.js';
import type { BettererRun, BettererRunSummary } from './types.js';

import { BettererRunSummaryΩ } from './run-summary.js';

export class BettererRunObsoleteΩ implements BettererRun {
public readonly expected: BettererResult;
public readonly isNew = false;
public readonly isObsolete = true;
public readonly isOnly = false;
public readonly isRemoved: boolean;
public readonly isSkipped = false;
public readonly filePaths = null;

constructor(
public readonly name: string,
public readonly baseline: BettererResult,
update = false
) {
this.isRemoved = update;
this.expected = baseline;
}

public async run(): Promise<BettererRunSummary> {
return await Promise.resolve(
new BettererRunSummaryΩ({
baseline: this.baseline,
delta: null,
diff: null,
error: null,
expected: this.expected,
filePaths: this.filePaths,
isBetter: false,
isComplete: false,
isExpired: false,
isFailed: false,
isNew: false,
isObsolete: this.isObsolete,
isRemoved: this.isRemoved,
isSame: false,
isSkipped: false,
isUpdated: false,
isWorse: false,
name: this.name,
result: null,
timestamp: -Infinity
})
);
}
}
4 changes: 4 additions & 0 deletions packages/betterer/src/run/run-summary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export class BettererRunSummaryΩ implements BettererRunSummary {
public readonly isExpired: boolean;
public readonly isFailed: boolean;
public readonly isNew: boolean;
public readonly isObsolete: boolean;
public readonly isRemoved: boolean;
public readonly isSame: boolean;
public readonly isSkipped: boolean;
public readonly isUpdated: boolean;
Expand All @@ -35,6 +37,8 @@ export class BettererRunSummaryΩ implements BettererRunSummary {
this.isExpired = summary.isExpired;
this.isFailed = summary.isFailed;
this.isNew = summary.isNew;
this.isObsolete = summary.isObsolete;
this.isRemoved = summary.isRemoved;
this.isSame = summary.isSame;
this.isSkipped = summary.isSkipped;
this.isUpdated = summary.isUpdated;
Expand Down
6 changes: 5 additions & 1 deletion packages/betterer/src/run/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import { BettererRunSummaryΩ } from './run-summary.js';

export class BettererRunΩ implements BettererRun {
public readonly isNew: boolean;
public readonly isObsolete = false;
public readonly isOnly: boolean;
public readonly isRemoved = false;
public readonly isSkipped: boolean;
public readonly name: string;

Expand Down Expand Up @@ -95,8 +97,10 @@ export class BettererRunΩ implements BettererRun {
isExpired: false,
isFailed: true,
isNew: this.isNew,
isObsolete: this.isObsolete,
isRemoved: this.isRemoved,
isSame: false,
isSkipped: this.isSkipped || isFiltered,
isSkipped: false,
isUpdated: false,
isWorse: false,
name: this.name,
Expand Down
10 changes: 10 additions & 0 deletions packages/betterer/src/run/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ export interface BettererRun {
* will be set to `null`. The default reporter will show that this test is new.
*/
readonly isNew: boolean;
/**
* When `true`, Betterer found a previous result for a test that is no longer relevant. Both `baseline` and `expected`
* will be set to previous result. The default reporter will show that this test is obsolete, and suggest that it can be removed.
*/
readonly isObsolete: boolean;
/**
* When `true`, this test has been skipped and the test function will not run. The default
* reporter will show that this test has been skipped.
Expand Down Expand Up @@ -203,6 +208,11 @@ export interface BettererRunSummary extends BettererRun {
* reporter will show that this test has failed.
*/
readonly isFailed: boolean;
/**
* When `true`, this test is "obsolete", due to a test being delete or renamed, but the `--update`
* option was used. is enabled. The previous result will be deleted from the {@link https://phenomnomnominal.github.io/betterer/docs/results-file | results file}.
*/
readonly isRemoved: boolean;
/**
* When `true`, this test is "the same", based on the result of the `constraint` function.
* The {@link https://phenomnomnominal.github.io/betterer/docs/results-file | results file} will
Expand Down
4 changes: 4 additions & 0 deletions packages/betterer/src/run/worker-run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import { getGlobals, setGlobals } from '../globals.js';

export class BettererWorkerRunΩ implements BettererRun {
public readonly isNew: boolean;
public readonly isObsolete = false;
public readonly isRemoved = false;
public readonly isSkipped: boolean;
public readonly name: string;

Expand Down Expand Up @@ -208,6 +210,8 @@ export class BettererWorkerRunΩ implements BettererRun {
isExpired,
isFailed: false,
isNew: this.isNew,
isObsolete: this.isObsolete,
isRemoved: this.isRemoved,
isSame: comparison === BettererConstraintResult.same,
isSkipped: !!isSkipped,
isUpdated,
Expand Down
48 changes: 27 additions & 21 deletions packages/betterer/src/suite/suite-summary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@ export class BettererSuiteSummaryΩ implements BettererSuiteSummary {
public readonly result: BettererResultsSerialised;

constructor(
private readonly _expectedTestNames: BettererTestNames,
public readonly filePaths: BettererFilePaths,
public readonly runs: BettererRuns,
public readonly runSummaries: BettererRunSummaries
) {
this.result = this._mergeResult();
}
public get better(): BettererRunSummaries {
return this.runSummaries.filter((runSummary) => runSummary.isBetter);
}

public get changed(): BettererTestNames {
return this._getChanged();
}

public get completed(): BettererRunSummaries {
return this.runSummaries.filter((runSummary) => runSummary.isComplete);
Expand All @@ -26,10 +32,6 @@ export class BettererSuiteSummaryΩ implements BettererSuiteSummary {
return this.runSummaries.filter((runSummary) => runSummary.isExpired);
}

public get better(): BettererRunSummaries {
return this.runSummaries.filter((runSummary) => runSummary.isBetter);
}

public get failed(): BettererRunSummaries {
return this.runSummaries.filter((runSummary) => runSummary.isFailed);
}
Expand All @@ -40,12 +42,22 @@ export class BettererSuiteSummaryΩ implements BettererSuiteSummary {
);
}

public get obsolete(): BettererRunSummaries {
return this.runSummaries.filter((runSummary) => runSummary.isObsolete && !runSummary.isRemoved);
}

public get ran(): BettererRunSummaries {
return this.runSummaries.filter((runSummary) => !(runSummary.isSkipped || runSummary.isFailed));
return this.runSummaries.filter(
(runSummary) => !(runSummary.isSkipped || runSummary.isFailed || runSummary.isObsolete || runSummary.isRemoved)
);
}

public get removed(): BettererRunSummaries {
return this.runSummaries.filter((runSummary) => runSummary.isRemoved);
}

public get same(): BettererRunSummaries {
return this.runSummaries.filter((runSummary) => runSummary.isSame);
return this.runSummaries.filter((runSummary) => runSummary.isSame && !runSummary.isComplete);
}

public get skipped(): BettererRunSummaries {
Expand All @@ -57,17 +69,11 @@ export class BettererSuiteSummaryΩ implements BettererSuiteSummary {
}

public get worse(): BettererRunSummaries {
return this.runSummaries.filter((runSummary) => runSummary.isWorse);
}

public get changed(): BettererTestNames {
return this._getChanged();
return this.runSummaries.filter((runSummary) => runSummary.isWorse && !runSummary.isUpdated);
}

private _getChanged(): BettererTestNames {
const missingRunNames = this._expectedTestNames.filter(
(name) => !this.runSummaries.find((runSummary) => runSummary.name === name)
);
const obsoleteRunNames = this.obsolete.map((runSummary) => runSummary.name);
const notFailedOrSkipped = this.runSummaries.filter((runSummary) => !runSummary.isFailed && !runSummary.isSkipped);
const changedRuns = notFailedOrSkipped
.filter((runSummary) => !runSummary.isNew)
Expand All @@ -79,23 +85,23 @@ export class BettererSuiteSummaryΩ implements BettererSuiteSummary {
});
const newRuns = notFailedOrSkipped.filter((runSummary) => runSummary.isNew && !runSummary.isComplete);
const newOrChangedRunNames = [...changedRuns, ...newRuns].map((runSummary) => runSummary.name);
return [...missingRunNames, ...newOrChangedRunNames];
return [...obsoleteRunNames, ...newOrChangedRunNames];
}

private _mergeResult(): BettererResultsSerialised {
return this.runSummaries.reduce<BettererResultsSerialised>((results, runSummary) => {
const { isComplete, isFailed, isSkipped, isNew, isWorse } = runSummary;
const { isFailed, isSkipped, isNew, isObsolete, isRemoved, isWorse } = runSummary;
const isSkippedOrFailed = isSkipped || isFailed;
if (isComplete || (isSkippedOrFailed && isNew)) {
if (isRemoved || (isSkippedOrFailed && isNew)) {
return results;
}
const { expected, name, result } = runSummary;
if ((isSkippedOrFailed && !isNew) || isWorse) {
invariantΔ(expected, 'Test is not _new_, or _worse_ so it must have an expected result!');
if ((isSkippedOrFailed && !isNew) || isWorse || isObsolete) {
invariantΔ(expected, 'Test has successfully run in the past so it must have an expected result!');
results[name] = { value: expected.printed };
return results;
}
invariantΔ(result, 'Test is not _completed_, _skipped_ or _failed_ so it must have a new result!');
invariantΔ(result, 'Test has successfully run so it must have a new result!');
results[name] = { value: result.printed };
return results;
}, {});
Expand Down
28 changes: 20 additions & 8 deletions packages/betterer/src/suite/suite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ import type { BettererError } from '@betterer/errors';
import type { BettererFilePaths } from '../fs/index.js';
import type { BettererReporterΩ } from '../reporters/index.js';
import type { BettererReporterRun, BettererRunSummary, BettererRuns } from '../run/index.js';
import type { BettererSuite } from './types.js';
import type { BettererSuite, BettererSuiteSummary } from './types.js';

import { invariantΔ } from '@betterer/errors';

import { defer } from '../utils.js';
import { BettererSuiteSummaryΩ } from './suite-summary.js';
import { BettererRunΩ } from '../run/index.js';
import { getGlobals } from '../globals.js';
import { BettererRunObsoleteΩ } from '../run/run-obsolete.js';
import { BettererResultΩ } from '../results/result.js';

const NEGATIVE_FILTER_TOKEN = '!';

Expand All @@ -21,20 +23,32 @@ export class BettererSuiteΩ implements BettererSuite {
) {}

public static async create(filePaths: BettererFilePaths): Promise<BettererSuiteΩ> {
const { config, testMetaLoader } = getGlobals();
const { configPaths } = config;
const { config, results, testMetaLoader } = getGlobals();
const { configPaths, update } = config;

const expectedTestNames = await results.api.getExpectedTestNames();

const testsMeta = await testMetaLoader.api.loadTestsMeta(configPaths);
const runsΩ = await Promise.all(
testsMeta.map(async (testMeta) => {
return await BettererRunΩ.create(testMeta, filePaths);
})
);
const testNames = testsMeta.map((testMeta) => testMeta.name);
const obsoleteTestNames = expectedTestNames.filter((expectedTestName) => !testNames.includes(expectedTestName));

const obsoleteRuns = await Promise.all(
obsoleteTestNames.map(async (testName) => {
const baselineJSON = await results.api.getBaseline(testName);
const baseline = new BettererResultΩ(JSON.parse(baselineJSON), baselineJSON);
return new BettererRunObsoleteΩ(testName, baseline, update);
})
);

return new BettererSuiteΩ(filePaths, runsΩ);
return new BettererSuiteΩ(filePaths, [...obsoleteRuns, ...runsΩ]);
}

public async run(): Promise<BettererSuiteSummaryΩ> {
public async run(): Promise<BettererSuiteSummary> {
const hasOnly = !!this.runs.find((run) => {
const runΩ = run as BettererRunΩ;
return runΩ.isOnly;
Expand Down Expand Up @@ -109,8 +123,6 @@ export class BettererSuiteΩ implements BettererSuite {
})
);

const { results } = getGlobals();
const expectedTestNames = await results.api.getExpectedTestNames();
return new BettererSuiteSummaryΩ(expectedTestNames, this.filePaths, this.runs, runSummaries);
return new BettererSuiteSummaryΩ(this.filePaths, this.runs, runSummaries);
}
}
10 changes: 10 additions & 0 deletions packages/betterer/src/suite/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,21 @@ export interface BettererSuiteSummary extends BettererSuite {
* for each test that was run for the first time.
*/
readonly new: BettererRunSummaries;
/**
* An array containing a {@link @betterer/betterer#BettererRunSummary | `BettererRunSummary`}
* for each test that has a previous saved result but is no longer defined.
*/
readonly obsolete: BettererRunSummaries;
/**
* An array containing a {@link @betterer/betterer#BettererRunSummary | `BettererRunSummary`}
* for each test that didn't fail and wasn't skipped.
*/
readonly ran: BettererRunSummaries;
/**
* An array containing a {@link @betterer/betterer#BettererRunSummary | `BettererRunSummary`}
* for each test that is obsolete, but the `--update` option was enabled.
*/
readonly removed: BettererRunSummaries;
/**
* An array containing a {@link @betterer/betterer#BettererRunSummary | `BettererRunSummary`}
* for each test that stayed the same.
Expand Down
6 changes: 5 additions & 1 deletion packages/betterer/src/test/file-test/printer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ import type { BettererFileTestResultSerialised } from './types.js';
import { isKey } from './serialiser.js';

export function printer(serialised: BettererFileTestResultSerialised): string {
const keys = Object.keys(serialised);
if (keys.length === 0) {
return '{}';
}
let printed = '{\n';
Object.keys(serialised)
keys
.sort()
.filter(isKey)
.forEach((filePath, index) => {
Expand Down
Loading