Skip to content

Commit

Permalink
feat(schema): add Pending status to JSON schema (#2425)
Browse files Browse the repository at this point in the history
* feat(schema): add `Pending` status to JSON schema

See #2424 for the reasoning behind this.

* fix: add `MutantStatus.Pending` where necessary

Fixes build errors in places where `MutantStatus.Pending` was missing.

* feat(metrics-scala): add `Pending` to `metrics-scala`

* fix(scala-metrics): fix compile errors

* fix: change `⏳` to `⏰` in `getEmojiForStatus`

* fix: change the correct emoji

* fix: update integration test

* feat: add pending to totalMutants metric

* fix: run prettier

* fix: compile errors

* feat(metrics-scala): add pending to totalMutants metric

* feat(elements): add pending to tooltip

* feat: make pending mutants visible by default

The user wants to see mutants if they are present in the file. To have a
filter for this would be weird, since the number in the filter would
count down as time passes.

For this reason `Pending` is not filterable.

* fix: reword tooltip
  • Loading branch information
xandervedder committed Apr 14, 2023
1 parent 1c6eeae commit c49d9a5
Show file tree
Hide file tree
Showing 16 changed files with 34 additions and 10 deletions.
3 changes: 2 additions & 1 deletion packages/elements/src/components/file/file.component.ts
Expand Up @@ -38,7 +38,8 @@ export class FileComponent extends LitElement {
private codeRef = createRef<HTMLElement>();

private readonly filtersChanged = (event: MteCustomEvent<'filters-changed'>) => {
this.selectedMutantStates = event.detail as MutantStatus[];
// Pending is not filterable, but they should still be shown to the user.
this.selectedMutantStates = (event.detail as MutantStatus[]).concat([MutantStatus.Pending]);
};

private codeClicked = (ev: MouseEvent) => {
Expand Down
1 change: 1 addition & 0 deletions packages/elements/src/components/file/file.scss
Expand Up @@ -3,6 +3,7 @@
@import '../../style/code.scss';

$mutant-themes: (
'Pending': theme('colors.neutral.400'),
'Killed': theme('colors.green.600'),
'NoCoverage': theme('colors.orange.500'),
'Survived': theme('colors.red.500'),
Expand Down
Expand Up @@ -118,7 +118,7 @@ const COLUMNS: Column<Metrics>[] = [
{
key: 'totalMutants',
label: 'Total',
tooltip: 'All mutants (valid + invalid + ignored)',
tooltip: 'All mutants (except runtimeErrors + compileErrors)',
category: 'number',
width: 'large',
isBold: true,
Expand Down
3 changes: 3 additions & 0 deletions packages/elements/src/lib/html-helpers.ts
Expand Up @@ -43,6 +43,7 @@ export function getContextClassForStatus(status: MutantStatus) {
return 'warning';
case MutantStatus.Ignored:
case MutantStatus.RuntimeError:
case MutantStatus.Pending:
case MutantStatus.CompileError:
return 'secondary';
}
Expand Down Expand Up @@ -81,6 +82,8 @@ export function getEmojiForStatus(status: MutantStatus) {
case MutantStatus.Survived:
return renderEmoji('👽', status);
case MutantStatus.Timeout:
return renderEmoji('⏰', status);
case MutantStatus.Pending:
return renderEmoji('⌛', status);
case MutantStatus.RuntimeError:
case MutantStatus.CompileError:
Expand Down
1 change: 1 addition & 0 deletions packages/elements/test/helpers/factory.ts
Expand Up @@ -81,6 +81,7 @@ export function createTestMetrics(overrides?: TestMetrics): TestMetrics {

export function createMetrics(overrides?: Metrics): Metrics {
const defaults: Metrics = {
pending: 0,
killed: 0,
survived: 0,
timeout: 0,
Expand Down
Expand Up @@ -90,7 +90,7 @@ describe(MutationTestReportMutantViewComponent.name, () => {
{
key: 'totalMutants',
label: 'Total',
tooltip: 'All mutants (valid + invalid + ignored)',
tooltip: 'All mutants (except runtimeErrors + compileErrors)',
category: 'number',
width: 'large',
isBold: true,
Expand Down
Expand Up @@ -57,7 +57,7 @@ describe(FileStateFilterComponent.name, () => {
'👽 Survived (1)',
'🙈 NoCoverage (1)',
'🤥 Ignored (1)',
' Timeout (1)',
' Timeout (1)',
'💥 CompileError (1)',
'💥 RuntimeError (1)',
]);
Expand Down
Expand Up @@ -41,7 +41,7 @@
"column": 20
}
},
"status": "Survived"
"status": "Pending"
},
{
"id": 2,
Expand Down
Expand Up @@ -127,6 +127,7 @@ object circe {
case MutantStatus.CompileError => "CompileError"
case MutantStatus.RuntimeError => "RuntimeError"
case MutantStatus.Ignored => "Ignored"
case MutantStatus.Pending => "Pending"
})

implicit lazy val testDefinitionCodec: Codec[TestDefinition] =
Expand Down
@@ -1,6 +1,9 @@
package mutationtesting

sealed trait MetricsResult {
/** The total number of mutants that are pending, meaning that they have been generated but not yet run.
*/
def pending: Int

/** At least one test failed while this mutant was active. The mutant is killed. This is what you want, good job!
*/
Expand Down Expand Up @@ -59,7 +62,7 @@ sealed trait MetricsResult {

/** All mutants.
*/
lazy val totalMutants: Int = totalValid + totalInvalid + ignored
lazy val totalMutants: Int = totalValid + totalInvalid + ignored + pending

/** The total percentage of mutants that were killed. Or a {{Double.NaN}} if there are no mutants.
*/
Expand All @@ -75,6 +78,7 @@ sealed trait MetricsResult {
sealed trait DirOps extends MetricsResult {
val files: Iterable[MetricsResult]

override lazy val pending: Int = sumOfChildrenWith(_.pending)
override lazy val killed: Int = sumOfChildrenWith(_.killed)
override lazy val timeout: Int = sumOfChildrenWith(_.timeout)
override lazy val survived: Int = sumOfChildrenWith(_.survived)
Expand All @@ -91,6 +95,7 @@ sealed trait DirOps extends MetricsResult {
sealed trait FileOps extends MetricsResult {
val mutants: Iterable[MetricMutant]

override lazy val pending: Int = countWhere(MutantStatus.Pending)
override lazy val killed: Int = countWhere(MutantStatus.Killed)
override lazy val timeout: Int = countWhere(MutantStatus.Timeout)
override lazy val survived: Int = countWhere(MutantStatus.Survived)
Expand Down
Expand Up @@ -12,5 +12,6 @@ object MutantStatus {
case object CompileError extends MutantStatus
case object RuntimeError extends MutantStatus
case object Ignored extends MutantStatus
case object Pending extends MutantStatus

}
Expand Up @@ -4,6 +4,7 @@ class MetricsResultTest extends munit.FunSuite {
test("all should be 0 on empty root") {
val sut = MetricsResultRoot(Nil)

assertEquals(sut.pending, 0)
assertEquals(sut.killed, 0)
assertEquals(sut.survived, 0)
assertEquals(sut.timeout, 0)
Expand Down Expand Up @@ -31,6 +32,7 @@ class MetricsResultTest extends munit.FunSuite {
}

private lazy val expectedSet: List[(String, MetricsResult => Number, Number)] = List(
("pending", _.pending, 1),
("killed", _.killed, 2),
("survived", _.survived, 2),
("timeout", _.timeout, 2),
Expand All @@ -43,7 +45,7 @@ class MetricsResultTest extends munit.FunSuite {
("totalCovered", _.totalCovered, 6),
("totalValid", _.totalValid, 9),
("totalInvalid", _.totalInvalid, 2),
("totalMutants", _.totalMutants, 12),
("totalMutants", _.totalMutants, 13),
("mutationScore", _.mutationScore, (4d / 9d) * 100),
(
"mutationScoreBasedOnCoveredCode",
Expand All @@ -60,6 +62,7 @@ class MetricsResultTest extends munit.FunSuite {
MetricsFile(
"bar.scala",
List(
MetricMutant(MutantStatus.Pending),
MetricMutant(MutantStatus.Killed),
MetricMutant(MutantStatus.Killed),
MetricMutant(MutantStatus.Survived),
Expand Down
4 changes: 3 additions & 1 deletion packages/metrics/src/calculateMetrics.ts
Expand Up @@ -142,6 +142,7 @@ function countTestFileMetrics(testFile: TestFileModel[]): TestMetrics {
function countFileMetrics(fileResult: FileUnderTestModel[]): Metrics {
const mutants = fileResult.flatMap((_) => _.mutants);
const count = (status: MutantStatus) => mutants.filter((_) => _.status === status).length;
const pending = count(MutantStatus.Pending);
const killed = count(MutantStatus.Killed);
const timeout = count(MutantStatus.Timeout);
const survived = count(MutantStatus.Survived);
Expand All @@ -155,6 +156,7 @@ function countFileMetrics(fileResult: FileUnderTestModel[]): Metrics {
const totalValid = totalUndetected + totalDetected;
const totalInvalid = runtimeErrors + compileErrors;
return {
pending,
killed,
timeout,
survived,
Expand All @@ -168,7 +170,7 @@ function countFileMetrics(fileResult: FileUnderTestModel[]): Metrics {
totalValid,
totalInvalid,
mutationScore: totalValid > 0 ? (totalDetected / totalValid) * 100 : DEFAULT_SCORE,
totalMutants: totalValid + totalInvalid + ignored,
totalMutants: totalValid + totalInvalid + ignored + pending,
mutationScoreBasedOnCoveredCode: totalValid > 0 ? (totalDetected / totalCovered) * 100 || 0 : DEFAULT_SCORE,
};
}
4 changes: 4 additions & 0 deletions packages/metrics/src/model/metrics.ts
Expand Up @@ -2,6 +2,10 @@
* Container for the metrics of mutation testing
*/
export interface Metrics {
/**
* The total number of mutants that are pending, meaning that they have been generated but not yet run
*/
pending: number;
/**
* The total number of mutants that were killed
*/
Expand Down
4 changes: 3 additions & 1 deletion packages/metrics/test/unit/calculateMetrics.spec.ts
Expand Up @@ -128,6 +128,7 @@ describe(calculateMetrics.name, () => {
const input: FileResultDictionary = {
'foo.js': createFileResult({
mutants: [
createMutantResult({ status: MutantStatus.Pending }),
createMutantResult({ status: MutantStatus.RuntimeError }),
createMutantResult({ status: MutantStatus.Killed }),
createMutantResult({ status: MutantStatus.CompileError }),
Expand All @@ -144,6 +145,7 @@ describe(calculateMetrics.name, () => {
const actual = calculateMetrics(input);

// Assert
expect(actual.metrics.pending, 'pending').to.eq(1);
expect(actual.metrics.killed, 'killed').to.eq(2);
expect(actual.metrics.compileErrors, 'compileErrors').eq(1);
expect(actual.metrics.runtimeErrors, 'runtimeErrors').eq(1);
Expand All @@ -153,7 +155,7 @@ describe(calculateMetrics.name, () => {
expect(actual.metrics.noCoverage, 'ignored').to.eq(1);
expect(actual.metrics.totalCovered, 'totalCovered').to.eq(4);
expect(actual.metrics.totalDetected, 'detected').to.eq(3);
expect(actual.metrics.totalMutants, 'mutants').to.eq(8);
expect(actual.metrics.totalMutants, 'mutants').to.eq(9);
expect(actual.metrics.totalValid, 'mutants').to.eq(5);
expect(actual.metrics.totalInvalid, 'mutants').to.eq(2);
expect(actual.metrics.totalUndetected, 'undetected').to.eq(2);
Expand Down
Expand Up @@ -89,7 +89,7 @@
"type": "string",
"title": "MutantStatus",
"description": "Result of the mutation.",
"enum": ["Killed", "Survived", "NoCoverage", "CompileError", "RuntimeError", "Timeout", "Ignored"]
"enum": ["Killed", "Survived", "NoCoverage", "CompileError", "RuntimeError", "Timeout", "Ignored", "Pending"]
},
"statusReason": {
"type": "string",
Expand Down

0 comments on commit c49d9a5

Please sign in to comment.