├── .github └── workflows │ └── maven.yml ├── .gitignore ├── .travis.yml ├── COPYRIGHT ├── LICENSE ├── README.md ├── pom.xml ├── sonar-project.properties └── src ├── main ├── java │ └── ch │ │ └── devcon5 │ │ └── sonar │ │ └── plugins │ │ └── mutationanalysis │ │ ├── MutationAnalysisPlugin.java │ │ ├── Streams.java │ │ ├── metrics │ │ ├── MetricDefinitionException.java │ │ ├── MutationAnalysisMetrics.java │ │ ├── MutationDensityComputer.java │ │ ├── MutationMetrics.java │ │ ├── MutationScoreComputer.java │ │ ├── QuantitativeMeasureComputer.java │ │ ├── ResourceMutationMetrics.java │ │ ├── TestKillRatioComputer.java │ │ └── TotalMutationsComputer.java │ │ ├── model │ │ ├── Mutant.java │ │ ├── MutationOperator.java │ │ ├── MutationOperators.java │ │ └── TestDescriptor.java │ │ ├── report │ │ ├── PitestReportParser.java │ │ ├── ReportFinder.java │ │ └── Reports.java │ │ ├── rules │ │ ├── JavaProfileDefinition.java │ │ ├── JavaRulesDefinition.java │ │ ├── KotlinProfileDefinition.kt │ │ ├── KotlinRulesDefinition.kt │ │ ├── MutationAnalysisProfileDefinition.java │ │ └── MutationAnalysisRulesDefinition.java │ │ └── sensors │ │ ├── PitestSensor.java │ │ ├── ReportCollector.java │ │ ├── ResourceResolver.java │ │ ├── RulesProcessor.java │ │ ├── SourceMetricsWriter.java │ │ └── TestMetricsWriter.java └── resources │ ├── ch │ └── devcon5 │ │ └── sonar │ │ └── plugins │ │ └── mutationanalysis │ │ ├── model │ │ ├── ARGUMENT_PROPAGATION.html │ │ ├── BOOLEAN_FALSE_RETURN.html │ │ ├── BOOLEAN_TRUE_RETURN.html │ │ ├── CONDITIONALS_BOUNDARY.html │ │ ├── CONSTRUCTOR_CALLS.html │ │ ├── EMPTY_RETURN_VALUES.html │ │ ├── EXPERIMENTAL_INLINE_CONSTS.html │ │ ├── EXPERIMENTAL_MEMBER_VARIABLE.html │ │ ├── EXPERIMENTAL_NAKED_RECEIVER.html │ │ ├── EXPERIMENTAL_REMOVE_INCREMENTS.html │ │ ├── EXPERIMENTAL_REMOVE_SWITCH.html │ │ ├── EXPERIMENTAL_RETURN_VALUES.html │ │ ├── EXPERIMENTAL_SWITCH.html │ │ ├── INCREMENTS.html │ │ ├── INLINE_CONSTS.html │ │ ├── INVERT_NEGS.html │ │ ├── MATH.html │ │ ├── NEGATE_CONDITIONALS.html │ │ ├── NON_VOID_METHOD_CALLS.html │ │ ├── NULL_RETURN_VALUES.html │ │ ├── PRIMITIVE_RETURN_VALS.html │ │ ├── REMOVE_CONDITIONALS.html │ │ ├── RETURN_VALS.html │ │ ├── VOID_METHOD_CALLS.html │ │ ├── empty.html │ │ └── mutagen-def.xml │ │ └── rules.xml │ └── log4j2.xml └── test ├── java └── ch │ └── devcon5 │ └── sonar │ └── plugins │ └── mutationanalysis │ ├── MutationAnalysisPluginTest.java │ ├── StreamsTest.java │ ├── metrics │ ├── MutationAnalysisMetricsTest.java │ ├── MutationDensityComputerTest.java │ ├── MutationMetricsTest.java │ ├── MutationScoreComputerTest.java │ ├── QuantitativeMeasureComputerTest.java │ ├── ResourceMutationMetricsTest.java │ ├── TestKillRatioComputerTest.java │ └── TotalMutationsComputerTest.java │ ├── model │ ├── BuilderTest.java │ ├── MutantTest.java │ ├── MutationOperatorTest.java │ ├── MutationOperatorsTest.java │ ├── StateTest.java │ └── TestDescriptorTest.java │ ├── report │ ├── PitestReportParserTest.java │ ├── ReportFinderTest.java │ └── ReportsTest.java │ ├── rules │ ├── JavaProfileDefinitionTest.java │ ├── JavaRulesDefinitionTest.java │ ├── KotlinProfileDefinitionTest.kt │ └── KotlinRulesDefinitionTest.kt │ ├── sensors │ ├── PitestSensorTest.java │ ├── ReportCollectorTest.java │ ├── ResourceResolverTest.java │ ├── RulesProcessorTest.java │ ├── SourceMetricsWriterTest.java │ └── TestMetricsWriterTest.java │ └── testharness │ ├── LogRecordingAppender.java │ ├── MeasureComputerTestHarness.java │ ├── SensorTestHarness.java │ ├── SystemLocaleExtension.java │ ├── TestConfiguration.java │ ├── TestSensorContext.java │ └── TestUtils.java └── resources ├── ch └── devcon5 │ └── sonar │ └── plugins │ └── mutationanalysis │ ├── report │ ├── PitestReportParserTest_XXE.xml │ ├── PitestReportParserTest_broken.xml │ ├── PitestReportParserTest_mutations.xml │ ├── PitestReportParserTest_mutationsWithDescriptions.xml │ ├── PitestReportParserTest_mutationsWithNumTests.xml │ ├── ReportFinderTest_mutations.xml │ └── ReportsTest_mutations.xml │ └── sensors │ ├── PitestSensorTest_Java_mutations.xml │ ├── PitestSensorTest_KotlinJava_mutations.xml │ ├── PitestSensorTest_Kotlin_mutations.xml │ └── ReportCollectorTest_mutations.xml └── log4j2.xml /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: Java CI with Maven and Sonar 10 | 11 | on: 12 | push: 13 | branches: [ "master" ] 14 | pull_request: 15 | types: [opened, synchronize, reopened] 16 | 17 | jobs: 18 | build: 19 | 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - uses: actions/checkout@v3 24 | - name: Set up JDK 17 25 | uses: actions/setup-java@v3 26 | with: 27 | java-version: '17' 28 | distribution: 'temurin' 29 | cache: maven 30 | - name: Cache SonarCloud packages 31 | uses: actions/cache@v3 32 | with: 33 | path: ~/.sonar/cache 34 | key: ${{ runner.os }}-sonar 35 | restore-keys: ${{ runner.os }}-sonar 36 | - name: Cache Maven packages 37 | uses: actions/cache@v2 38 | with: 39 | path: ~/.m2 40 | key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} 41 | restore-keys: | 42 | ${{ runner.os }}-m2 43 | - name: Build with Maven 44 | run: mvn -B package --file pom.xml 45 | - name: Analyze with SonarCloud 46 | env: 47 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 48 | run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=devcon5io_mutation-analysis-plugin 49 | - name: Update dependency graph 50 | uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ---- Maven 2 | target/ 3 | 4 | # ---- IntelliJ IDEA 5 | *.iws 6 | *.iml 7 | *.ipr 8 | .idea/ 9 | 10 | # ---- Eclipse 11 | .classpath 12 | .project 13 | .settings 14 | 15 | # ---- MacOS 16 | .DS_Store 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | sudo: false 3 | install: true 4 | addons: 5 | sonarcloud: 6 | organization: devcon5 7 | token: $SONAR_TOKEN 8 | 9 | jdk: 10 | - openjdk11 11 | 12 | script: 13 | - mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent package org.jacoco:jacoco-maven-plugin:report 14 | - if [ type != pull_request ]; then mvn sonar:sonar; fi 15 | 16 | cache: 17 | directories: 18 | - $HOME/.m2/repository 19 | - $HOME/.sonar/cache 20 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | Mutation Analysis Plugin 2 | Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 3 | info@devcon5.ch 4 | 5 | This program is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Lesser General Public 7 | License as published by the Free Software Foundation; either 8 | version 3 of the License, or (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public License 16 | along with this program; if not, write to the Free Software Foundation, 17 | Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Mutation Analysis Plugin for Sonarqube 2 | ====================================== 3 | 4 | This plugin enables Mutation Analysis of reports generated by [Pitest](http://pitest.org/) in SonarQube 5 | 6 | Badges 7 | ------ 8 | 9 | [![Quality gate](https://sonarcloud.io/api/project_badges/quality_gate?project=devcon5io_mutation-analysis-plugin)](https://sonarcloud.io/summary/new_code?id=devcon5io_mutation-analysis-plugin) 10 | 11 | Documentation 12 | ------------- 13 | Documentation for the Plugin is located in the [Mutation-Analysis-Plugin Wiki](https://github.com/devcon5io/mutation-analysis-plugin/wiki) 14 | 15 | Contribution 16 | ------------ 17 | see https://github.com/devcon5io/mutation-analysis-plugin/wiki/Contributing -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=ch.devcon5.sonar:mutation-analysis-plugin 2 | sonar.projectName=mutation-analysis-plugin 3 | 4 | # ===================================================== 5 | # Meta-data for the project 6 | # ===================================================== 7 | 8 | sonar.links.homepage=https://github.com/devcon5io/mutation-analysis-plugin 9 | sonar.links.ci=https://travis-ci.org/devcon5io/mutation-analysis-plugin 10 | sonar.links.scm=https://github.com/devcon5io/mutation-analysis-plugin 11 | sonar.links.issue=https://github.com/devcon5io/mutation-analysis-plugin/issues 12 | 13 | # ===================================================== 14 | # Properties that will be shared amongst all modules 15 | # ===================================================== 16 | 17 | sonar.host.url=https://sonarcloud.io 18 | sonar.organization=devcon5 19 | sonar.sources=src 20 | sonar.java.binaries=**/target/classes 21 | -------------------------------------------------------------------------------- /src/main/java/ch/devcon5/sonar/plugins/mutationanalysis/Streams.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis; 22 | 23 | import java.util.stream.Stream; 24 | import java.util.stream.StreamSupport; 25 | 26 | public final class Streams { 27 | 28 | private Streams() { 29 | //to prevent instantiation of this class 30 | } 31 | 32 | public static Stream sequentialStream(Iterable elements) { 33 | return StreamSupport.stream(elements.spliterator(), false); 34 | } 35 | 36 | public static Stream parallelStream(Iterable elements) { 37 | return StreamSupport.stream(elements.spliterator(), true); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/ch/devcon5/sonar/plugins/mutationanalysis/metrics/MetricDefinitionException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.metrics; 22 | 23 | /** 24 | * 25 | */ 26 | public class MetricDefinitionException extends RuntimeException { 27 | 28 | public MetricDefinitionException(final String s, final Exception e) { 29 | super(s, e); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/ch/devcon5/sonar/plugins/mutationanalysis/metrics/MutationAnalysisMetrics.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.metrics; 22 | 23 | import java.util.Collections; 24 | import java.util.List; 25 | import org.sonar.api.measures.Metric; 26 | import org.sonar.api.measures.Metrics; 27 | 28 | /** 29 | * PitestPro Metrics Extension for the SonarQube 30 | */ 31 | public class MutationAnalysisMetrics implements Metrics { 32 | 33 | @Override 34 | public List getMetrics() { 35 | return Collections.unmodifiableList(MutationMetrics.getSensorMetrics()); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/ch/devcon5/sonar/plugins/mutationanalysis/metrics/MutationDensityComputer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.metrics; 22 | 23 | import static org.slf4j.LoggerFactory.getLogger; 24 | 25 | import ch.devcon5.sonar.plugins.mutationanalysis.MutationAnalysisPlugin; 26 | import org.slf4j.Logger; 27 | import org.sonar.api.ce.measure.Measure; 28 | import org.sonar.api.ce.measure.MeasureComputer; 29 | import org.sonar.api.config.Configuration; 30 | import org.sonar.api.measures.CoreMetrics; 31 | 32 | /** 33 | * Computer for calculating the mutation coverage of a component based on the detected vs total mutations. 34 | */ 35 | public class MutationDensityComputer implements MeasureComputer { 36 | 37 | private static final Logger LOG = getLogger(MutationDensityComputer.class); 38 | 39 | private final Configuration config; 40 | 41 | public MutationDensityComputer(final Configuration config) { 42 | this.config = config; 43 | } 44 | 45 | @Override 46 | public MeasureComputerDefinition define(final MeasureComputerDefinitionContext defContext) { 47 | return defContext.newDefinitionBuilder() 48 | .setInputMetrics(MutationMetrics.MUTATIONS_TOTAL.key(), CoreMetrics.LINES_TO_COVER_KEY) 49 | .setOutputMetrics(MutationMetrics.MUTATIONS_DENSITY.key()) 50 | .build(); 51 | } 52 | 53 | @Override 54 | public void compute(final MeasureComputerContext context) { 55 | if (!MutationAnalysisPlugin.isExperimentalFeaturesEnabled(config)) { 56 | LOG.info("Experimental Features disabled"); 57 | return; 58 | } 59 | 60 | LOG.info("Computing Mutation Density for {}", context.getComponent()); 61 | final Measure lines = context.getMeasure(CoreMetrics.LINES_TO_COVER_KEY); 62 | final Measure mutations = context.getMeasure(MutationMetrics.MUTATIONS_TOTAL.key()); 63 | if (mutations == null) { 64 | return; 65 | } 66 | 67 | final double density; 68 | if (lines == null) { 69 | density = 0d; 70 | } else { 71 | density = 100.0d * ((double) mutations.getIntValue() / (double) lines.getIntValue()); 72 | } 73 | 74 | if (!Double.isNaN(density)) { 75 | LOG.info("Computed Mutation Density of {} for {}", density, context.getComponent()); 76 | context.addMeasure(MutationMetrics.MUTATIONS_DENSITY.key(), density); 77 | } 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/ch/devcon5/sonar/plugins/mutationanalysis/metrics/MutationScoreComputer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.metrics; 22 | 23 | import static ch.devcon5.sonar.plugins.mutationanalysis.MutationAnalysisPlugin.FORCE_MISSING_COVERAGE_TO_ZERO; 24 | import static org.slf4j.LoggerFactory.getLogger; 25 | 26 | import java.util.Optional; 27 | import org.slf4j.Logger; 28 | import org.sonar.api.batch.measure.Metric; 29 | import org.sonar.api.ce.measure.Measure; 30 | import org.sonar.api.ce.measure.MeasureComputer; 31 | import org.sonar.api.config.Configuration; 32 | 33 | /** 34 | * Computer for calculating the mutation coverage of a component based on the detected vs total mutations. 35 | */ 36 | public class MutationScoreComputer implements MeasureComputer { 37 | 38 | private static final Logger LOG = getLogger(MutationScoreComputer.class); 39 | 40 | private final Configuration config; 41 | 42 | public MutationScoreComputer(final Configuration config) { 43 | this.config = config; 44 | } 45 | 46 | @Override 47 | public MeasureComputerDefinition define(final MeasureComputerDefinitionContext defContext) { 48 | return defContext.newDefinitionBuilder() 49 | .setInputMetrics(MutationMetrics.MUTATIONS_DETECTED.key(), MutationMetrics.MUTATIONS_TOTAL.key(), MutationMetrics.MUTATIONS_SURVIVED.key()) 50 | .setOutputMetrics(MutationMetrics.MUTATIONS_COVERAGE.key(), MutationMetrics.MUTATIONS_TEST_STRENGTH.key()) 51 | .build(); 52 | } 53 | 54 | @Override 55 | public void compute(final MeasureComputerContext context) { 56 | LOG.info("Computing Mutation Coverage for {}", context.getComponent()); 57 | final Measure mutationsTotalMeasure = context.getMeasure(MutationMetrics.MUTATIONS_TOTAL.key()); 58 | if (mutationsTotalMeasure != null) { 59 | final int mutationsTotal = mutationsTotalMeasure.getIntValue(); 60 | if (mutationsTotal > 0) { 61 | final int detectedMutants = getMetric(context, MutationMetrics.MUTATIONS_DETECTED); 62 | final int survivedMutants = getMetric(context, MutationMetrics.MUTATIONS_SURVIVED); 63 | computeMutationsCoverage(context, detectedMutants, mutationsTotal); 64 | computeTestStrength(context, survivedMutants, detectedMutants); 65 | } else { 66 | // modules with no mutants (0 total) are always 100% covered (0 of 0 is 100%, right?) 67 | context.addMeasure(MutationMetrics.MUTATIONS_COVERAGE.key(), 100.0); 68 | context.addMeasure(MutationMetrics.MUTATIONS_TEST_STRENGTH.key(), 100.0); 69 | } 70 | } else if (config.getBoolean(FORCE_MISSING_COVERAGE_TO_ZERO).orElse(Boolean.FALSE)) { 71 | context.addMeasure(MutationMetrics.MUTATIONS_COVERAGE.key(), 0.0); 72 | context.addMeasure(MutationMetrics.MUTATIONS_TEST_STRENGTH.key(), 0.0); 73 | } 74 | } 75 | 76 | private void computeMutationsCoverage(final MeasureComputerContext context, final int coveredMutants, final int totalMutants) { 77 | final double coverage = 100.0 * coveredMutants / totalMutants; 78 | LOG.info("Computed Mutation Coverage of {}% for {}", coverage, context.getComponent()); 79 | context.addMeasure(MutationMetrics.MUTATIONS_COVERAGE.key(), coverage); 80 | } 81 | 82 | private void computeTestStrength(final MeasureComputerContext context, final int survivedMutants, final int detectedMutants) { 83 | final double testStrength; 84 | int allMutantsQuantity = survivedMutants + detectedMutants; 85 | if (allMutantsQuantity != 0) { 86 | testStrength = 100.0 * detectedMutants / allMutantsQuantity; 87 | } else { 88 | testStrength = 0; 89 | } 90 | LOG.info("Computed Test Strength of {}% for {}", testStrength, context.getComponent()); 91 | context.addMeasure(MutationMetrics.MUTATIONS_TEST_STRENGTH.key(), testStrength); 92 | } 93 | 94 | private int getMetric(final MeasureComputerContext context, Metric metric) { 95 | return Optional.ofNullable(context.getMeasure(metric.key())).map(Measure::getIntValue).orElse(0); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/ch/devcon5/sonar/plugins/mutationanalysis/metrics/QuantitativeMeasureComputer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.metrics; 22 | 23 | import static org.slf4j.LoggerFactory.getLogger; 24 | 25 | import ch.devcon5.sonar.plugins.mutationanalysis.Streams; 26 | import org.slf4j.Logger; 27 | import org.sonar.api.ce.measure.Measure; 28 | import org.sonar.api.ce.measure.MeasureComputer; 29 | import org.sonar.api.measures.Metric; 30 | 31 | /** 32 | * Computer that processes the aggregated quantitative metric for a component from all the quantitative metrics of its 33 | * children. 34 | */ 35 | public class QuantitativeMeasureComputer implements MeasureComputer { 36 | 37 | private static final Logger LOG = getLogger(QuantitativeMeasureComputer.class); 38 | 39 | @Override 40 | public MeasureComputerDefinition define(final MeasureComputerDefinitionContext defContext) { 41 | return defContext.newDefinitionBuilder() 42 | .setOutputMetrics(MutationMetrics.getQuantitativeMetrics() 43 | .stream() 44 | .map(Metric::getKey) 45 | .toArray(String[]::new)) 46 | .build(); 47 | } 48 | 49 | @Override 50 | public void compute(final MeasureComputerContext context) { 51 | LOG.info("Computing quantitative mutation metrics for {}", context.getComponent()); 52 | MutationMetrics.getQuantitativeMetrics() 53 | .stream() 54 | .filter(m -> !m.isHidden()) //exclude hidden metrics, see below 55 | .map(Metric::getKey) 56 | .filter(metricKey -> context.getMeasure(metricKey) == null) 57 | .forEach(metricKey -> { 58 | int sum = Streams.parallelStream(context.getChildrenMeasures(metricKey)) 59 | .map(Measure::getIntValue) 60 | .reduce(0, Integer::sum); 61 | if (sum > 0) { 62 | LOG.info("Computed {} {} for {}", sum, metricKey, context.getComponent()); 63 | context.addMeasure(metricKey, sum); 64 | } 65 | }); 66 | 67 | // hidden utility metric are globally constant for all components 68 | MutationMetrics.getQuantitativeMetrics() 69 | .stream() 70 | .filter(Metric::isHidden) 71 | .map(Metric::getKey) 72 | .filter(metricKey -> context.getMeasure(metricKey) == null) 73 | .forEach(metricKey -> { 74 | int first = Streams.parallelStream(context.getChildrenMeasures(metricKey)) 75 | .map(Measure::getIntValue) 76 | .findFirst().orElse(0); 77 | if (first > 0) { 78 | LOG.info("Computed {} {} for {}", first, metricKey, context.getComponent()); 79 | context.addMeasure(metricKey, first); 80 | } 81 | }); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/ch/devcon5/sonar/plugins/mutationanalysis/metrics/TestKillRatioComputer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.metrics; 22 | 23 | import static ch.devcon5.sonar.plugins.mutationanalysis.metrics.MutationMetrics.TEST_KILLS_KEY; 24 | import static ch.devcon5.sonar.plugins.mutationanalysis.metrics.MutationMetrics.TEST_KILL_RATIO; 25 | import static ch.devcon5.sonar.plugins.mutationanalysis.metrics.MutationMetrics.TEST_KILL_RATIO_KEY; 26 | import static ch.devcon5.sonar.plugins.mutationanalysis.metrics.MutationMetrics.UTILITY_GLOBAL_MUTATIONS_KEY; 27 | import static org.slf4j.LoggerFactory.getLogger; 28 | 29 | import ch.devcon5.sonar.plugins.mutationanalysis.MutationAnalysisPlugin; 30 | import ch.devcon5.sonar.plugins.mutationanalysis.Streams; 31 | import org.slf4j.Logger; 32 | import org.sonar.api.ce.measure.Component; 33 | import org.sonar.api.ce.measure.Measure; 34 | import org.sonar.api.ce.measure.MeasureComputer; 35 | import org.sonar.api.config.Configuration; 36 | 37 | public class TestKillRatioComputer implements MeasureComputer { 38 | 39 | private static final Logger LOG = getLogger(TestKillRatioComputer.class); 40 | 41 | private final Configuration config; 42 | 43 | public TestKillRatioComputer(final Configuration config) { 44 | this.config = config; 45 | } 46 | 47 | @Override 48 | public MeasureComputerDefinition define(final MeasureComputerDefinitionContext defContext) { 49 | return defContext.newDefinitionBuilder() 50 | .setInputMetrics(UTILITY_GLOBAL_MUTATIONS_KEY, TEST_KILLS_KEY) 51 | .setOutputMetrics(TEST_KILL_RATIO_KEY) 52 | .build(); 53 | } 54 | 55 | @Override 56 | public void compute(final MeasureComputerContext context) { 57 | if (!MutationAnalysisPlugin.isExperimentalFeaturesEnabled(config)) { 58 | LOG.info("ExperimentalFeature disabled"); 59 | return; 60 | } 61 | 62 | final Component comp = context.getComponent(); 63 | if (isNotUnitTestOrDirectory(comp)) { 64 | LOG.info("Skipping {} because it's not a test resource", comp); 65 | return; 66 | } 67 | 68 | final double mutationsGlobal = getMutationsGlobal(context, UTILITY_GLOBAL_MUTATIONS_KEY); 69 | final double testKillsLocal = getTestKillsLocal(context); 70 | if (mutationsGlobal == 0.0) { 71 | return; 72 | } 73 | final double percentage = 100.0d * testKillsLocal / mutationsGlobal; 74 | LOG.info("Computed {} of {}% from ({} / {}) for {}", TEST_KILL_RATIO.getName(), percentage, testKillsLocal, mutationsGlobal, comp); 75 | context.addMeasure(TEST_KILL_RATIO_KEY, percentage); 76 | } 77 | 78 | boolean isNotUnitTestOrDirectory(final Component comp) { 79 | return comp.getType() == Component.Type.FILE && !comp.getFileAttributes().isUnitTest(); 80 | } 81 | 82 | private double getMutationsGlobal(final MeasureComputerContext context, String key) { 83 | final double mutationsGlobal; 84 | final Measure globalMutationsMeasure = context.getMeasure(key); 85 | if (globalMutationsMeasure == null) { 86 | mutationsGlobal = Streams.parallelStream(context.getChildrenMeasures(key)) 87 | .mapToInt(Measure::getIntValue) 88 | .findFirst() 89 | .orElse(0); 90 | LOG.info("Component {} has no global mutation information, using first child's: {}", context.getComponent(), mutationsGlobal); 91 | } else { 92 | mutationsGlobal = globalMutationsMeasure.getIntValue(); 93 | LOG.info("Using global mutation count: {}", mutationsGlobal); 94 | } 95 | return mutationsGlobal; 96 | } 97 | 98 | private double getTestKillsLocal(final MeasureComputerContext context) { 99 | final Measure localTestKills = context.getMeasure(TEST_KILLS_KEY); 100 | if (localTestKills == null) { 101 | return Streams.parallelStream(context.getChildrenMeasures(TEST_KILLS_KEY)) 102 | .mapToInt(Measure::getIntValue) 103 | .sum(); 104 | } else { 105 | return localTestKills.getIntValue(); 106 | } 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/ch/devcon5/sonar/plugins/mutationanalysis/model/MutationOperator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.model; 22 | 23 | import static java.util.Objects.requireNonNull; 24 | 25 | import java.io.IOException; 26 | import java.net.URL; 27 | import java.nio.charset.StandardCharsets; 28 | import java.util.Collection; 29 | import java.util.HashSet; 30 | import java.util.Optional; 31 | import java.util.Set; 32 | import org.slf4j.Logger; 33 | import org.slf4j.LoggerFactory; 34 | import org.sonar.api.internal.apachecommons.io.IOUtils; 35 | import org.sonar.api.rules.Rule; 36 | 37 | /** 38 | * Representation of a PIT MutationOperator. The mutagens are defined in a classpath located file name 39 | * mutagen-def.xml in the same package. To get an instance of a defined mutagen, use the find() method. As 40 | * key for finding a mutagen, the ID, classname or a name that contains the classname as prefix can be used. The mutagen 41 | * itself contains a violation description as well a description of the mutagen itself that is copied from the 42 | * documentation at pitest.org/quickstart/mutators 43 | */ 44 | public final class MutationOperator { 45 | 46 | private static final Logger LOG = LoggerFactory.getLogger(MutationOperator.class); 47 | 48 | private final String id; 49 | private final URL mutagenDescLoc; 50 | private final String violationDesc; 51 | private final String name; 52 | private final Set classNames; 53 | 54 | MutationOperator(final String id, final String name, final Collection classNames, final String violationDesc, final URL mutagenDescriptionLocation) { 55 | requireNonNull(id, "Id must not be null"); 56 | requireNonNull(name, "name must not be null"); 57 | requireNonNull(violationDesc, "violation description must not be null"); 58 | this.id = id; 59 | this.name = name; 60 | this.classNames = new HashSet<>(requireNonNull(classNames, "classNames must not be null")); 61 | this.violationDesc = violationDesc; 62 | this.mutagenDescLoc = mutagenDescriptionLocation; 63 | } 64 | 65 | /** 66 | * The ID of the mutagen. The ID is a String that uniquely defines the MutationOperator, written uppercase, like 67 | * {@code ARGUMENT_PROPAGATION}. 68 | * 69 | * @return the {@link MutationOperator} id as a String 70 | */ 71 | public String getId() { 72 | return id; 73 | } 74 | 75 | /** 76 | * A URL pointing to the description of the {@link MutationOperator}. The {@link MutationOperator} descriptions are 77 | * stored in the classpath as resource, for each {@link MutationOperator} a separate description. The URLs are 78 | * specified in the {@code mutagen-def.xml} in the classpath. The resources itself are html fragments that can be 79 | * embedded in a website. 80 | * 81 | * @return URL pointing to the resource containing the description. 82 | */ 83 | public Optional getMutagenDescriptionLocation() { 84 | return Optional.ofNullable(mutagenDescLoc); 85 | } 86 | 87 | /** 88 | * The violation description used for the {@link MutationOperator} specific {@link Rule} that are violated if the 89 | * mutation caused by the {@link MutationOperator} is not killed. 90 | * 91 | * @return the string description the violation. 92 | */ 93 | public String getViolationDescription() { 94 | return violationDesc; 95 | } 96 | 97 | /** 98 | * The name of the MutationOperator. Unlike the Id, it is more a display name. 99 | * 100 | * @return the name as a String 101 | */ 102 | public String getName() { 103 | return name; 104 | } 105 | 106 | /** 107 | * The fully qualified classnames of the {@link MutationOperator} class. 108 | * 109 | * @return the classnames 110 | */ 111 | public Set getClassNames() { 112 | return classNames; 113 | } 114 | 115 | /** 116 | * The description of the {@link MutationOperator}. The method loads the content defined in the resource that is 117 | * referred to by the description URL. 118 | * 119 | * @return the description as a string 120 | */ 121 | public String getMutagenDescription() { 122 | return getMutagenDescriptionLocation().map(u -> { 123 | try { 124 | return IOUtils.toString(u, StandardCharsets.UTF_8); 125 | } catch (IOException e) { 126 | LOG.warn("Cannot read mutagen description for mutagen {}", id, e); 127 | return "No description"; 128 | } 129 | }).orElse(""); 130 | } 131 | 132 | @Override 133 | public int hashCode() { 134 | return 31 + id.hashCode(); 135 | } 136 | 137 | @Override 138 | public boolean equals(final Object obj) { 139 | if (this == obj) { 140 | return true; 141 | } 142 | if (obj == null) { 143 | return false; 144 | } 145 | if (getClass() != obj.getClass()) { 146 | return false; 147 | } 148 | final MutationOperator other = (MutationOperator) obj; 149 | return id.equals(other.id); 150 | } 151 | 152 | } 153 | -------------------------------------------------------------------------------- /src/main/java/ch/devcon5/sonar/plugins/mutationanalysis/model/MutationOperators.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.model; 22 | 23 | import static javax.xml.xpath.XPathConstants.NODESET; 24 | 25 | import java.io.IOException; 26 | import java.io.InputStream; 27 | import java.net.URL; 28 | import java.util.Collection; 29 | import java.util.Collections; 30 | import java.util.HashMap; 31 | import java.util.HashSet; 32 | import java.util.Map; 33 | import java.util.Set; 34 | import javax.xml.xpath.XPath; 35 | import javax.xml.xpath.XPathExpressionException; 36 | import javax.xml.xpath.XPathFactory; 37 | import org.w3c.dom.Node; 38 | import org.w3c.dom.NodeList; 39 | import org.xml.sax.InputSource; 40 | 41 | /** 42 | * 43 | */ 44 | public final class MutationOperators { 45 | 46 | /** 47 | * Default MutationOperator definition for an unknown {@link MutationOperator} 48 | */ 49 | public static final MutationOperator UNKNOWN = new MutationOperator("UNKNOWN", "Unknown mutagen", 50 | Collections.singleton("unknown.mutation.operator"), "An unknown mutagen has been applied", null); 51 | 52 | /** 53 | * URL of the mutagen definitions. 54 | */ 55 | private static final URL MUTAGEN_DEF = MutationOperator.class.getResource("mutagen-def.xml"); 56 | 57 | /** 58 | * Contains all instances of {@link MutationOperator}s defined in the mutagen-def.xml 59 | */ 60 | private static final Map INSTANCES; 61 | static { 62 | try (InputStream stream = MUTAGEN_DEF.openStream()) { 63 | final Map mutagens = new HashMap<>(); 64 | final XPathFactory xPathFactory = XPathFactory.newInstance(); 65 | final XPath xp = xPathFactory.newXPath(); 66 | final NodeList mutagenNodes = (NodeList) xp.evaluate("//operator", new InputSource(stream), NODESET); 67 | for (int i = 0, len = mutagenNodes.getLength(); i < len; i++) { 68 | final Node mutagenNode = mutagenNodes.item(i); 69 | final MutationOperator mutationOperator = toMutagen(xPathFactory, mutagenNode); 70 | mutagens.put(mutationOperator.getId(), mutationOperator); 71 | } 72 | INSTANCES = Collections.unmodifiableMap(mutagens); 73 | } catch (IOException | XPathExpressionException e) { 74 | throw new MutationOperatorsInitializationException("Could not load mutagen definitions", e); 75 | } 76 | } 77 | 78 | private MutationOperators() {} 79 | 80 | /** 81 | * Converts a MutationOperator from the given {@link Node} 82 | * 83 | * @param xPathFactory the factory with which to create a new xpath 84 | * @param mutagenNode the node to convert to {@link ch.devcon5.sonar.plugins.mutationanalysis.model.MutationOperator} 85 | * @return a {@link MutationOperator} for the {@link Node} 86 | * @throws XPathExpressionException if there was an exception evaluating an xpath expression 87 | */ 88 | private static MutationOperator toMutagen(final XPathFactory xPathFactory, final Node mutagenNode) throws XPathExpressionException { 89 | final XPath xp = xPathFactory.newXPath(); 90 | final String id = xp.evaluate("@id", mutagenNode); 91 | 92 | final Set classNames = new HashSet<>(); 93 | final NodeList classNodes = (NodeList) xp.evaluate("classes/class", mutagenNode, NODESET); 94 | for (int i = 0, len = classNodes.getLength(); i < len; i++) { 95 | final Node classNode = classNodes.item(i); 96 | classNames.add(classNode.getTextContent()); 97 | } 98 | 99 | final String name = xp.evaluate("name", mutagenNode); 100 | final String violationDescription = xp.evaluate("violationDescription", mutagenNode).trim(); 101 | final URL mutagenDescLoc = MutationOperator.class.getResource(xp.evaluate("operatorDescription/@classpath", mutagenNode)); 102 | 103 | return new MutationOperator(id, name, classNames, violationDescription, mutagenDescLoc); 104 | } 105 | 106 | /** 107 | * Finds the {@link MutationOperator} using the specified key. The key could be the ID of the MutationOperator, 108 | * its classname or an extended classname, which is the classname with a suffix. 109 | * 110 | * @param mutagenKey the key to use when searching for the mutagen 111 | * @return a matching {@link MutationOperator} or an UNKNOWN mutagen 112 | */ 113 | public static MutationOperator find(final String mutagenKey) { 114 | MutationOperator result = UNKNOWN; 115 | for (final MutationOperator mutationOperator : INSTANCES.values()) { 116 | if (mutagenKey.equals(mutationOperator.getId()) 117 | || mutationOperator.getClassNames().stream().anyMatch(mutagenKey::startsWith)) { 118 | result = mutationOperator; 119 | break; 120 | } 121 | } 122 | return result; 123 | } 124 | 125 | /** 126 | * Retrieves all defined {@link MutationOperator}s from the mutagen-def.xml. 127 | * 128 | * @return a collection of {@link MutationOperator}s 129 | */ 130 | public static Collection allMutationOperators() { 131 | return Collections.unmodifiableCollection(INSTANCES.values()); 132 | } 133 | 134 | /** 135 | * Exception that is thrown if the mutation operators can not be loaded from configuration. 136 | */ 137 | public static class MutationOperatorsInitializationException extends RuntimeException { 138 | 139 | public MutationOperatorsInitializationException(final String message, final Throwable cause) { 140 | super(message, cause); 141 | } 142 | 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /src/main/java/ch/devcon5/sonar/plugins/mutationanalysis/model/TestDescriptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.model; 22 | 23 | import java.util.Objects; 24 | 25 | public class TestDescriptor { 26 | 27 | private final String className; 28 | private final String methodName; 29 | private final String spec; 30 | 31 | public TestDescriptor(final String spec) { 32 | this.spec = spec; 33 | 34 | final int parenthesis = spec.indexOf('('); 35 | final int methodSeparator = spec.lastIndexOf('.', parenthesis); 36 | final int nestedSeparator = spec.indexOf('$'); 37 | 38 | if (nestedSeparator != -1) { 39 | this.className = spec.substring(0, nestedSeparator); 40 | if (methodSeparator != -1) { 41 | this.methodName = spec.substring(methodSeparator + 1, parenthesis); 42 | } else { 43 | this.methodName = "unknown"; 44 | } 45 | } else if (methodSeparator != -1) { 46 | this.className = spec.substring(0, methodSeparator); 47 | this.methodName = spec.substring(methodSeparator + 1, parenthesis); 48 | } else { 49 | className = spec; 50 | methodName = "unknown"; 51 | } 52 | } 53 | 54 | public String getClassName() { 55 | return className; 56 | } 57 | 58 | public String getMethodName() { 59 | return methodName; 60 | } 61 | 62 | public String getSpec() { 63 | return spec; 64 | } 65 | 66 | @Override 67 | public String toString() { 68 | return "TestDescriptor{class='" + className + "', method='" + methodName + "'}"; 69 | } 70 | 71 | @Override 72 | public boolean equals(final Object o) { 73 | if (this == o) { 74 | return true; 75 | } 76 | if (o == null || getClass() != o.getClass()) { 77 | return false; 78 | } 79 | final TestDescriptor that = (TestDescriptor) o; 80 | return Objects.equals(className, that.className); 81 | } 82 | 83 | @Override 84 | public int hashCode() { 85 | return Objects.hash(className); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/ch/devcon5/sonar/plugins/mutationanalysis/report/ReportFinder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.report; 22 | 23 | import java.io.IOException; 24 | import java.nio.file.FileSystems; 25 | import java.nio.file.FileVisitResult; 26 | import java.nio.file.Files; 27 | import java.nio.file.Path; 28 | import java.nio.file.PathMatcher; 29 | import java.nio.file.SimpleFileVisitor; 30 | import java.nio.file.attribute.BasicFileAttributes; 31 | import java.util.ArrayList; 32 | import java.util.List; 33 | import java.util.Objects; 34 | 35 | import org.slf4j.Logger; 36 | import org.slf4j.LoggerFactory; 37 | 38 | /** 39 | * Searches the latest xml file in the reports directory. 40 | */ 41 | public class ReportFinder { 42 | 43 | /** 44 | * SLF4J Logger for this class 45 | */ 46 | private static final Logger LOG = LoggerFactory.getLogger(ReportFinder.class); 47 | 48 | /** 49 | * Finds the PIT report in the given report directory. 50 | * 51 | * @param reportDirectory 52 | * the report directory to search for the report. The report directory must not be null, must 53 | * exist and must be a directory. 54 | * 55 | * @return the Path to the found PIT report or null, if no report was found or the directory is no valid directory 56 | * 57 | * @throws IOException 58 | * if the most recent report could not be determined 59 | */ 60 | public Path findReport(final Path reportDirectory) throws IOException { 61 | if (reportDirectory == null || !Files.exists(reportDirectory)) { 62 | LOG.warn("ReportDirectory {} is not a valid directory", reportDirectory); 63 | return null; 64 | } 65 | return findMostRecentReport(reportDirectory, "*.xml"); 66 | } 67 | 68 | /** 69 | * Locates the most recent report in the report directory by searching all xml files in the reports directory and 70 | * selecting the most recent file. 71 | * 72 | * @param reportDirectory 73 | * the path to the report directory to search the report in 74 | * @param pattern 75 | * a globbing pattern, i.e. *.java or *.xml 76 | * 77 | * @return the {@link Path} to the most recent report 78 | * 79 | * @throws java.io.IOException if the report or the directory of the report can not be accessed 80 | */ 81 | protected Path findMostRecentReport(final Path reportDirectory, final String pattern) throws IOException { 82 | Path mostRecent = null; 83 | ReportFinderVisitor reportFinderVisitor = new ReportFinderVisitor(pattern); 84 | Files.walkFileTree(reportDirectory, reportFinderVisitor); 85 | for (final Path report : reportFinderVisitor.getReports()) { 86 | if (mostRecent == null || isNewer(mostRecent, report)) { 87 | mostRecent = report; 88 | } 89 | } 90 | return mostRecent; 91 | } 92 | 93 | /** 94 | * Determines if the otherPath is newer than the referencePath. 95 | * 96 | * @param referencePath 97 | * the path to compare the other path against 98 | * @param otherPath 99 | * the other path to be compared against the reference path 100 | * 101 | * @return true if the otherPath is newer than the referencePath 102 | * 103 | * @throws IOException if the last modification time can not be determined 104 | */ 105 | protected boolean isNewer(final Path referencePath, final Path otherPath) throws IOException { 106 | return Files.getLastModifiedTime(referencePath).compareTo(Files.getLastModifiedTime(otherPath)) < 0; 107 | } 108 | 109 | /** 110 | * Recursive search report xml 111 | */ 112 | static class ReportFinderVisitor extends SimpleFileVisitor { 113 | 114 | private final PathMatcher matcher; 115 | 116 | private final List reports = new ArrayList<>(); 117 | 118 | public List getReports() { 119 | return reports; 120 | } 121 | 122 | ReportFinderVisitor(String pattern) { 123 | matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern); 124 | } 125 | 126 | @Override 127 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { 128 | requireNonNull(file, "file must not be null"); 129 | final Path filename = file.getFileName(); 130 | if (Objects.nonNull(filename) && matcher.matches(filename)) { 131 | reports.add(file); 132 | } 133 | return FileVisitResult.CONTINUE; 134 | } 135 | 136 | private static void requireNonNull(T obj, String message) { 137 | if (obj == null) { 138 | throw new IllegalArgumentException(message); 139 | } 140 | } 141 | 142 | } 143 | 144 | } 145 | -------------------------------------------------------------------------------- /src/main/java/ch/devcon5/sonar/plugins/mutationanalysis/report/Reports.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.report; 22 | 23 | import ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant; 24 | import java.io.IOException; 25 | import java.nio.file.Path; 26 | import java.util.Collection; 27 | import java.util.Collections; 28 | import org.slf4j.Logger; 29 | import org.slf4j.LoggerFactory; 30 | 31 | /** 32 | * Utility class to read a {@link ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant}s from a report located in a 33 | * directory. 34 | */ 35 | public final class Reports { 36 | 37 | /** 38 | * SLF4J Logger for this class 39 | */ 40 | private static final Logger LOG = LoggerFactory.getLogger(Reports.class); 41 | 42 | private Reports() {} 43 | 44 | /** 45 | * Reads the {@link ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant}s from the report in the reports directory. 46 | * The method searches for the most recent {@code mutations.xml} report and returns its contents as a list. 47 | * 48 | * @param reportsDirectory the {@link Path} to the directory containing the report. 49 | * @return a collection of all {@link ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant}s declared in the report 50 | * or an empty list if neither report was found nor it contained any 51 | * {@link ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant}s. The method does not return null 52 | * @throws IOException if the search for the report failed or the report could not be read. 53 | */ 54 | public static Collection readMutants(final Path reportsDirectory) throws IOException { 55 | LOG.debug("Searching pit reports in {}", reportsDirectory); 56 | 57 | final Path xmlReport; 58 | if (reportsDirectory.toFile().isDirectory()) { 59 | xmlReport = new ReportFinder().findReport(reportsDirectory); 60 | } else { 61 | xmlReport = reportsDirectory; 62 | } 63 | 64 | if (xmlReport == null) { 65 | LOG.warn("No XML PIT report found in directory {} !", reportsDirectory); 66 | LOG.warn( 67 | "Checkout plugin documentation for more detailed explanations: https://github.com/devcon5io/mutation-analysis-plugin"); 68 | return Collections.emptyList(); 69 | } 70 | 71 | return new PitestReportParser().parseMutants(xmlReport); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/ch/devcon5/sonar/plugins/mutationanalysis/rules/JavaProfileDefinition.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.rules; 22 | 23 | public class JavaProfileDefinition extends MutationAnalysisProfileDefinition { 24 | 25 | @Override 26 | protected String getLanguageKey() { 27 | return "java"; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/ch/devcon5/sonar/plugins/mutationanalysis/rules/JavaRulesDefinition.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.rules; 22 | 23 | import org.sonar.api.config.Configuration; 24 | import org.sonar.api.server.rule.RulesDefinitionXmlLoader; 25 | 26 | /** 27 | * The definition of pitest rules. A new repository is created for the Pitest plugin and Java language. The rules are 28 | * defined in the rules.xml file in the classpath. The rule keys are accessible as constants. 29 | */ 30 | public class JavaRulesDefinition extends MutationAnalysisRulesDefinition { 31 | 32 | /** 33 | * Constructor to create the pitest rules definitions and repository. The constructor is invoked by Sonar. 34 | * 35 | * @param settings 36 | * the settings of the Pitest-Sensor plugin 37 | * @param xmlLoader The xml loader with which to load rules 38 | */ 39 | public JavaRulesDefinition(final Configuration settings, final RulesDefinitionXmlLoader xmlLoader) { 40 | super(settings, xmlLoader); 41 | } 42 | 43 | @Override 44 | protected String getLanguageKey() { 45 | return "java"; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/ch/devcon5/sonar/plugins/mutationanalysis/rules/KotlinProfileDefinition.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.rules 22 | 23 | 24 | class KotlinProfileDefinition : MutationAnalysisProfileDefinition() { 25 | 26 | override fun getLanguageKey(): String { 27 | return "kotlin" 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/ch/devcon5/sonar/plugins/mutationanalysis/rules/KotlinRulesDefinition.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.rules 22 | 23 | import org.sonar.api.config.Configuration 24 | import org.sonar.api.server.rule.RulesDefinitionXmlLoader 25 | 26 | 27 | /** 28 | * The definition of pitest rules. A new repository is created for the Pitest plugin and Java language. The rules are 29 | * defined in the rules.xml file in the classpath. The rule keys are accessible as constants. 30 | */ 31 | class KotlinRulesDefinition 32 | 33 | /** 34 | * Constructor to create the pitest rules definitions and repository. The constructor is invoked by Sonar. 35 | * 36 | * @param settings 37 | * the settings of the Pitest-Sensor plugin 38 | * @param xmlLoader 39 | */ 40 | (settings: Configuration, xmlLoader: RulesDefinitionXmlLoader) : MutationAnalysisRulesDefinition(settings, xmlLoader) { 41 | 42 | override fun getLanguageKey(): String { 43 | return "kotlin" 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/ch/devcon5/sonar/plugins/mutationanalysis/rules/MutationAnalysisProfileDefinition.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.rules; 22 | 23 | import static ch.devcon5.sonar.plugins.mutationanalysis.rules.MutationAnalysisRulesDefinition.MUTANT_RULES_PREFIX; 24 | import static ch.devcon5.sonar.plugins.mutationanalysis.rules.MutationAnalysisRulesDefinition.REPOSITORY_KEY; 25 | 26 | import ch.devcon5.sonar.plugins.mutationanalysis.model.MutationOperators; 27 | import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; 28 | 29 | public abstract class MutationAnalysisProfileDefinition implements BuiltInQualityProfilesDefinition { 30 | 31 | @Override 32 | public void define(final Context context) { 33 | final NewBuiltInQualityProfile mutationAnalysis = context.createBuiltInQualityProfile("Mutation Analysis", getLanguageKey()); 34 | MutationOperators.allMutationOperators().forEach( 35 | m -> mutationAnalysis.activateRule(REPOSITORY_KEY + "." + getLanguageKey(), MUTANT_RULES_PREFIX + m.getId())); 36 | mutationAnalysis.done(); 37 | } 38 | 39 | protected abstract String getLanguageKey(); 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/ch/devcon5/sonar/plugins/mutationanalysis/sensors/ResourceResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.sensors; 22 | 23 | import java.util.Optional; 24 | import org.sonar.api.batch.fs.FileSystem; 25 | import org.sonar.api.batch.fs.InputFile; 26 | 27 | /** 28 | * Resolver that resolves any given Java or Kotlin class to its source class. If the class is a nested class, 29 | * then its parent is returned 30 | */ 31 | public class ResourceResolver { 32 | 33 | private final FileSystem fs; 34 | 35 | public ResourceResolver(final FileSystem fs) { 36 | this.fs = fs; 37 | } 38 | 39 | public Optional resolve(String classname) { 40 | return Optional.ofNullable(fs.inputFile(fs.predicates() 41 | .or(fs.predicates().matchesPathPattern("**/" + getPathToSourceFile(classname, "java")), 42 | fs.predicates().matchesPathPattern("**/" + getPathToSourceFile(classname, "kt"))))); 43 | } 44 | 45 | private String getPathToSourceFile(String classname, String language) { 46 | final int nestedClass = classname.indexOf('$'); 47 | final String mainClass; 48 | if (nestedClass != -1) { 49 | mainClass = classname.substring(0, nestedClass); 50 | } else { 51 | mainClass = classname; 52 | } 53 | return mainClass.trim().replace(".", "/") + "." + language; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/ch/devcon5/sonar/plugins/mutationanalysis/sensors/SourceMetricsWriter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.sensors; 22 | 23 | import static org.slf4j.LoggerFactory.getLogger; 24 | 25 | import ch.devcon5.sonar.plugins.mutationanalysis.metrics.MutationMetrics; 26 | import ch.devcon5.sonar.plugins.mutationanalysis.metrics.ResourceMutationMetrics; 27 | import ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant; 28 | import java.util.Collection; 29 | import org.slf4j.Logger; 30 | import org.sonar.api.batch.fs.InputFile; 31 | import org.sonar.api.batch.sensor.SensorContext; 32 | import org.sonar.api.batch.sensor.coverage.NewCoverage; 33 | 34 | /** 35 | * 36 | */ 37 | public class SourceMetricsWriter { 38 | 39 | private static final Logger LOG = getLogger(SourceMetricsWriter.class); 40 | 41 | /** 42 | * Saves the information of the mutants the sensors context. 43 | * 44 | * @param metrics the mutant information parsed from the PIT report 45 | * @param context the current {@link org.sonar.api.batch.sensor.SensorContext} 46 | * @param globalMutants the global collection of mutants 47 | */ 48 | public void writeMetrics(final Collection metrics, final SensorContext context, final Collection globalMutants) { 49 | final int total = globalMutants.isEmpty() ? sumTotal(metrics) : globalMutants.size(); 50 | final int alive = total - (globalMutants.isEmpty() 51 | ? metrics.stream().mapToInt(rmm -> countDetected(rmm.getMutants())).sum() 52 | : countDetected(globalMutants)); 53 | for (final ResourceMutationMetrics resourceMetrics : metrics) { 54 | saveResourceMetrics(resourceMetrics, context); 55 | context.newMeasure().forMetric(MutationMetrics.UTILITY_GLOBAL_MUTATIONS).on(resourceMetrics.getResource()) 56 | .withValue(total).save(); 57 | context.newMeasure().forMetric(MutationMetrics.UTILITY_GLOBAL_ALIVE).on(resourceMetrics.getResource()) 58 | .withValue(alive).save(); 59 | } 60 | } 61 | 62 | private int countDetected(final Collection c) { 63 | return (int) c.stream().filter(Mutant::isDetected).count(); 64 | } 65 | 66 | private int sumTotal(final Collection metrics) { 67 | return (int) metrics.stream().mapToLong(ResourceMutationMetrics::getMutationsTotal).sum(); 68 | } 69 | 70 | /** 71 | * Saves the {@link Mutant} metrics for the given resource in the SonarContext 72 | * 73 | * @param resourceMetrics the actual metrics for the resource to persist 74 | * @param context the context to register the metrics 75 | */ 76 | private void saveResourceMetrics(final ResourceMutationMetrics resourceMetrics, final SensorContext context) { 77 | final InputFile resource = resourceMetrics.getResource(); 78 | LOG.debug("Saving resource metrics for {}", resource); 79 | if (resourceMetrics.getMutationsKilled() > 0) { 80 | final NewCoverage newCov = context.newCoverage().onFile(resource); 81 | for (Mutant m : resourceMetrics.getMutants()) { 82 | if (Mutant.State.KILLED == m.getState()) { 83 | newCov.lineHits(m.getLineNumber(), 1); 84 | } 85 | } 86 | newCov.save(); 87 | } 88 | if (resource.type() == InputFile.Type.MAIN) { 89 | context.newMeasure().on(resource).forMetric(MutationMetrics.MUTATIONS_TOTAL) 90 | .withValue(resourceMetrics.getMutationsTotal()).save(); 91 | context.newMeasure().on(resource).forMetric(MutationMetrics.MUTATIONS_NO_COVERAGE) 92 | .withValue(resourceMetrics.getMutationsNoCoverage()).save(); 93 | context.newMeasure().on(resource).forMetric(MutationMetrics.MUTATIONS_KILLED) 94 | .withValue(resourceMetrics.getMutationsKilled()).save(); 95 | context.newMeasure().on(resource).forMetric(MutationMetrics.MUTATIONS_SURVIVED) 96 | .withValue(resourceMetrics.getMutationsSurvived()).save(); 97 | context.newMeasure().on(resource).forMetric(MutationMetrics.MUTATIONS_ALIVE) 98 | .withValue(resourceMetrics.getMutationsTotal() - resourceMetrics.getMutationsDetected()).save(); 99 | context.newMeasure().on(resource).forMetric(MutationMetrics.MUTATIONS_MEMORY_ERROR) 100 | .withValue(resourceMetrics.getMutationsMemoryError()).save(); 101 | context.newMeasure().on(resource).forMetric(MutationMetrics.MUTATIONS_TIMED_OUT) 102 | .withValue(resourceMetrics.getMutationsTimedOut()).save(); 103 | context.newMeasure().on(resource).forMetric(MutationMetrics.MUTATIONS_UNKNOWN) 104 | .withValue(resourceMetrics.getMutationsUnknown()).save(); 105 | context.newMeasure().on(resource).forMetric(MutationMetrics.MUTATIONS_DETECTED) 106 | .withValue(resourceMetrics.getMutationsDetected()).save(); 107 | context.newMeasure().on(resource).forMetric(MutationMetrics.TEST_TOTAL_EXECUTED) 108 | .withValue(resourceMetrics.getNumTestsRun()).save(); 109 | } 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/ch/devcon5/sonar/plugins/mutationanalysis/sensors/TestMetricsWriter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.sensors; 22 | 23 | import static org.slf4j.LoggerFactory.getLogger; 24 | 25 | import ch.devcon5.sonar.plugins.mutationanalysis.metrics.MutationMetrics; 26 | import ch.devcon5.sonar.plugins.mutationanalysis.metrics.ResourceMutationMetrics; 27 | import ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant; 28 | import ch.devcon5.sonar.plugins.mutationanalysis.model.TestDescriptor; 29 | import java.util.Collection; 30 | import java.util.List; 31 | import java.util.Map; 32 | import java.util.stream.Collectors; 33 | import org.slf4j.Logger; 34 | import org.sonar.api.batch.fs.FileSystem; 35 | import org.sonar.api.batch.sensor.SensorContext; 36 | 37 | /** 38 | * 39 | */ 40 | public class TestMetricsWriter { 41 | 42 | private static final Logger LOG = getLogger(TestMetricsWriter.class); 43 | private final ResourceResolver resourceResolver; 44 | 45 | public TestMetricsWriter(final FileSystem fileSystem) { 46 | this.resourceResolver = new ResourceResolver(fileSystem); 47 | } 48 | 49 | public void writeMetrics(final Collection metrics, final SensorContext context, final Collection globalMutants) { 50 | Map> testKills = metrics.stream() 51 | .flatMap(rmm -> rmm.getMutants().stream()) 52 | .collect(Collectors.groupingBy(m -> new TestDescriptor(m.getKillingTest()), Collectors.toList())); 53 | 54 | final int total = globalMutants.isEmpty() ? sumTotal(metrics) : globalMutants.size(); 55 | testKills.forEach((t, m) -> { 56 | LOG.debug("Test {} kills {} mutants ", t.getClassName(), m.size()); 57 | this.resourceResolver.resolve(t.getClassName()).ifPresent(f -> { 58 | context.newMeasure().forMetric(MutationMetrics.TEST_KILLS).on(f).withValue(m.size()).save(); 59 | context.newMeasure().forMetric(MutationMetrics.UTILITY_GLOBAL_MUTATIONS).on(f).withValue(total).save(); 60 | }); 61 | }); 62 | } 63 | 64 | private int sumTotal(final Collection metrics) { 65 | return (int) metrics.stream().mapToLong(ResourceMutationMetrics::getMutationsTotal).sum(); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/resources/ch/devcon5/sonar/plugins/mutationanalysis/model/ARGUMENT_PROPAGATION.html: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 |

Argument Propagation Mutator (ARGUMENT_PROPAGATION)

23 | 24 |

25 | This operator replaces the return value of a method parameter, whose type matches the return type, with the 26 | value of the parameter. Hence, it is only applicable to non-void methods. 27 |

28 |
29 |

For example:

30 | 31 |
32 | public String aMethod() {
33 |   String aValue = "example";
34 |   return bMethod(aValue);
35 | }
36 | 
37 | private String bMethod(String param) {
38 |   return param + " not mutated";
39 | }
40 |     
41 | 42 | is mutated to 43 | 44 |
45 | public String mutatedMethod() {
46 |   String aValue = "example";
47 |   return aValue;
48 | }
49 |     
50 |

see Pitest Mutators

51 |
52 | -------------------------------------------------------------------------------- /src/main/resources/ch/devcon5/sonar/plugins/mutationanalysis/model/BOOLEAN_FALSE_RETURN.html: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 |

Boolean False Return Mutator (BOOLEAN_FALSE_RETURN)

23 | 24 |

This operator replaces a primitive or boxed boolean with false.

25 |
26 | -------------------------------------------------------------------------------- /src/main/resources/ch/devcon5/sonar/plugins/mutationanalysis/model/BOOLEAN_TRUE_RETURN.html: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 |

Boolean True Return Mutator (BOOLEAN_TRUE_RETURN)

23 | 24 |

This operator replaces a primitive or boxed boolean with true.

25 |
26 | -------------------------------------------------------------------------------- /src/main/resources/ch/devcon5/sonar/plugins/mutationanalysis/model/CONDITIONALS_BOUNDARY.html: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 |

Conditionals Boundary Mutator (CONDITIONALS_BOUNDARY)

23 | 24 |

25 | This operator replaces relational operators <, <=, >, >= with their conditional boundary 26 | counterparts according to the following table 27 |

28 | 29 | 30 | 31 | 32 | 35 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 |
33 | Original 34 | 36 | Mutation 37 |
<<=
<=<
>>=
>=>
59 | 60 |

For example

61 | 62 |
63 | if (a <= b) {
64 |   // some code
65 | }
66 |     
67 | 68 |

will be mutated to

69 | 70 |
71 | if (a < b) {
72 |   // someCode
73 | }
74 |     
75 |

see Pitest Mutators

76 |
77 | -------------------------------------------------------------------------------- /src/main/resources/ch/devcon5/sonar/plugins/mutationanalysis/model/CONSTRUCTOR_CALLS.html: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 |

Constructor Call Mutator (CONSTRUCTOR_CALLS)

23 | 24 |

25 | This operator targets constructors, where it replaces the call to the constructor with a null value 26 | assignment. This should induce NullPointerException. 27 |

28 |
29 |

For Example:

30 | 31 |
32 | public Object aMethod() {
33 |   Example x = new Example();
34 |   return x;
35 | }
36 |     
37 | 38 |

will be mutated to

39 | 40 |
41 | public Example aMethod() {
42 |   Example x = null;
43 |   return x;
44 | }
45 |     
46 | 47 |

48 | This mutation is very likely to cause NullPointerExceptions even in test suites 49 | that do not explicitly test for null values. Nevertheless, even if the test contains no assertions 50 | at all, if the test runs this code, it is likely to kill this mutation. 51 |

52 | 53 |

54 | Every other method invocation is not affected by this operator. See Void Method Call Mutator, which mutates 56 | void methods, and 57 | Non Void Method Call Mutator 58 | mutating non-void methods. 59 |

60 |

see Pitest Mutators

61 |
62 | -------------------------------------------------------------------------------- /src/main/resources/ch/devcon5/sonar/plugins/mutationanalysis/model/EMPTY_RETURN_VALUES.html: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 |

Empty Return Values Mutator (EMPTY_RETURN_VALUES)

23 | 24 |

25 | This operator mutates a return value, which is not a primitive, to return an empty value that relates to the 26 | Object's type. 27 |

28 |
29 |

For example:

30 | 31 |
    32 |
  • 0 - Integer
  • 33 |
  • Optional.empty()
  • 34 |
  • "" - String
  • 35 |
  • empty List
  • 36 |
37 |
38 | -------------------------------------------------------------------------------- /src/main/resources/ch/devcon5/sonar/plugins/mutationanalysis/model/EXPERIMENTAL_INLINE_CONSTS.html: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 |

Experimental Inline Constant Mutator (EXPERIMENTAL_INLINE_CONSTS)

23 | 24 |

This experimental operator aims to provide a more consistent behavior than the inline const operator.

25 | 26 |

27 | This operator increments the value by 1. In some cases this would lead to equivalent mutations due to JVM 28 | internals, i.e. for floating point types adding 1 would lead to equivalent mutations. 29 |

30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 47 | 48 | 49 | 58 | 59 | 60 | 61 | 67 | 68 | 69 | 70 | 77 | 78 | 79 | 80 | 87 | 88 | 89 |
TypeMutation
boolean 42 |
    43 |
  • replace true with false,
  • 44 |
  • replace false with true
  • 45 |
46 |
int byte short 50 |
    51 |
  • replace 1 with 0,
  • 52 |
  • replace -1 with 0,
  • 53 |
  • all other values are incremented by 1.
  • 54 |
  • Byte.MAX, Short.MAX and Integer.MAX are replaced with 55 | Byte.MIN, Short.MIN and Integer.MIN
  • 56 |
57 |
long 62 |
    63 |
  • 1 is replaced with 0
  • 64 |
  • all other values are incremented by 1
  • 65 |
66 |
float 71 |
    72 |
  • replace 0.0 with 1.0,
  • 73 |
  • replace 1.0 with 2.0,
  • 74 |
  • replace any other value with 1.0
  • 75 |
76 |
double 81 |
    82 |
  • replace 0.0 with 1.0,
  • 83 |
  • replace 1.0 with 2.0,
  • 84 |
  • replace any other value with 1.0
  • 85 |
86 |
90 |

see Pitest Mutators

91 |
92 | -------------------------------------------------------------------------------- /src/main/resources/ch/devcon5/sonar/plugins/mutationanalysis/model/EXPERIMENTAL_MEMBER_VARIABLE.html: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 |

Experimental Member Variable Mutator (EXPERIMENTAL_MEMBER_VARIABLE)

23 | 24 |

25 | This operator mutates assignments to member variables, including final members, by removing the assignments. 26 | Member variables will be initialized with their Java default values, depending on the type of the variable. 27 |

28 | 29 |

Java Default Values for Primitives and Reference Types

30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 43 | 44 | 45 | 46 | 52 | 53 | 54 | 55 | 59 | 60 | 61 | 62 | 65 | 66 | 67 | 68 | 71 | 72 | 73 | 74 |
TypeDefault Value
41 | boolean 42 | false
47 | int 48 | short 49 | byte 50 | long 51 | 0
56 | float 57 | double 58 | 0.0
63 | char 64 | '\u0000'
69 | Object 70 | null
75 |
76 |

For Example:

77 | 78 |
 79 | public class Example {
 80 |   private final int a = 10;
 81 |   private String text = "abc";
 82 |   //...
 83 | }
 84 |     
85 | 86 |

will be mutated to

87 | 88 |
 89 | public class Example {
 90 |   private final int a = 0;
 91 |   private String text = null;
 92 |   ...
 93 | }
 94 |     
95 | 96 |

97 | In cases where the member variable is explicitly initialized with a default value this operator will create an 98 | equivalent mutation that can not be killed. 99 |

100 | 101 |
102 | public class EquivalentExample {
103 |   private boolean b = false;
104 | }
105 |     
106 |

see Pitest Mutators

107 |
108 | -------------------------------------------------------------------------------- /src/main/resources/ch/devcon5/sonar/plugins/mutationanalysis/model/EXPERIMENTAL_NAKED_RECEIVER.html: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 |

Experimental Naked Receiver Mutator (EXPERIMENTAL_NAKED_RECEIVER)

23 | 24 |

25 | This operator replaces a call to a method with the receiver of the method (the instance on which the method 26 | is invoked), if the return type of the method is the same as the receiver. 27 |

28 |
29 |

For example:

30 | 31 |

32 |   public String originalMethod() {
33 |     String someString = "original";
34 |     return someString.toUpperCase();
35 |   }
36 |     
37 | is mutated to 38 |

39 |   public int mutatedMethod() {
40 |     String someString = "original";
41 |     return someString;
42 |   }
43 |     
44 | as the return type of toUpperCase() is the same as the type of someString 45 |
46 | -------------------------------------------------------------------------------- /src/main/resources/ch/devcon5/sonar/plugins/mutationanalysis/model/EXPERIMENTAL_REMOVE_INCREMENTS.html: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 |

Experimental Remove Increments Mutator (EXPERIMENTAL_REMOVE_INCREMENTS)

23 | 24 |

25 | This operator removes an increment operation. As the number of loop iterations are typically controlled by an 26 | incremented variable, this operator may lead to infinite loops causing timeouts. 27 |

28 |
29 | -------------------------------------------------------------------------------- /src/main/resources/ch/devcon5/sonar/plugins/mutationanalysis/model/EXPERIMENTAL_REMOVE_SWITCH.html: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 |

Experimental Remove Switch Mutator (EXPERIMENTAL_SWITCH)

23 | 24 |

This operator removes an entire switch statement and replaces it with the default label.

25 |

see Pitest Mutators

26 |
27 | -------------------------------------------------------------------------------- /src/main/resources/ch/devcon5/sonar/plugins/mutationanalysis/model/EXPERIMENTAL_RETURN_VALUES.html: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 |

Experimental Return Values (EXPERIMENTAL_RETURN_VALUES)

23 | 24 |

25 | This operator mutates the return values of method calls depending on the return type. 26 |

27 | 28 |

29 | Primitive types are replaces with rules similar to rules of the inline consts operator. Object reference 30 | replacements are more complex and handled by the ObjectReferenceReplacer. 31 |

32 |

see Pitest Mutators

33 |
34 | -------------------------------------------------------------------------------- /src/main/resources/ch/devcon5/sonar/plugins/mutationanalysis/model/EXPERIMENTAL_SWITCH.html: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 |

Experimental Switch Mutator (EXPERIMENTAL_SWITCH)

23 | 24 |

25 | This operator replaces the default label with the first label that is different from the default label, and replaces 26 | all other labels with the default label. 27 |

28 |

see Pitest Mutators

29 |
30 | -------------------------------------------------------------------------------- /src/main/resources/ch/devcon5/sonar/plugins/mutationanalysis/model/INCREMENTS.html: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 |

Increments Mutator (INCREMENTS)

23 | 24 |

25 | This operator replaces increments with decrements and vice versa. It mutates increments, decrements, assignment 26 | increments and decrements of stack variables (local variables). 27 |

28 |
29 |

For example

30 | 31 |
32 | public int aMethod(int i) {
33 |   i--;
34 |   return i;
35 | }
36 |     
37 | 38 |

will be mutated to

39 | 40 |
41 | public int aMethod(int i) {
42 |   i++;
43 |   return i;
44 | }
45 |     
46 | 47 |

48 | Only increments and decrements of local variables are mutated. Member variables and the increments and 49 | decrements thereof will be covered by the Math Mutator. 50 |

51 |

see Pitest Mutators

52 |
53 | -------------------------------------------------------------------------------- /src/main/resources/ch/devcon5/sonar/plugins/mutationanalysis/model/INLINE_CONSTS.html: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 |

Inline Constant Mutator (INLINE_CONSTS)

23 | 24 |

25 | This operator mutates inline constants assigned to variables. An inline constant is a fixed value - a literal - 26 | that is assigned to a non-final variable. 27 |

28 |
29 |

For Example:

30 | 31 |
 32 | public void aMethod() {
 33 |     long l = 16L;
 34 |     // some coding that uses l
 35 | }
 36 |     
37 | 38 |

39 | The actual mutation depends on the type of the inline constant. Because the way Java statements get converted 40 | to byte code may vary, the rule for mutating inline constant are complex. 41 |

42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 57 | 58 | 59 | 60 | 68 | 69 | 70 | 71 | 77 | 78 | 79 | 80 | 87 | 88 | 89 | 90 | 96 | 97 | 98 |
TypeMutation
boolean 54 | true is replaced with false and 55 | false is replaced with true 56 |
integer byte short 61 |
    62 |
  • 1 is replaced with 0
  • 63 |
  • -1 is replaced with 1
  • 64 |
  • 5 is replaced with -1
  • 65 |
  • all other values are incremented by 1
  • 66 |
67 |
long 72 |
    73 |
  • 1 is replaced with 0
  • 74 |
  • all other values are incremented by 1
  • 75 |
76 |
float 81 |
    82 |
  • 1.0 is replaced with 0.0
  • 83 |
  • 2.0 is replaced with 0.0
  • 84 |
  • any other value is replaced with 1.0
  • 85 |
86 |
double 91 |
    92 |
  • 1.0 is replaced with 0.0
  • 93 |
  • any other value is replaced with 1.0
  • 94 |
95 |
99 | 100 |

For example

101 | 102 |
103 | public int aMethod() {
104 |   int i = 16;
105 |   return i;
106 | }
107 |     
108 | 109 |

will be changed to

110 | 111 |
112 | public int aMethod() {
113 |   int i = 17;
114 |   return i;
115 | }
116 |     
117 | 118 |

119 | The compiler may optimize expressions with final variables - regardless of being local or member variables. 120 | In those cases the mutation is not able to mutate any variable. 121 |

122 |

Example

123 | 124 |
125 | public class Example {
126 |   private static final int CONSTANT = 4;
127 | 
128 |   public String aMethod() {
129 |     final int i = 16;
130 |     return "" + CONSTANT + ":" + i;
131 |   }
132 | }
133 |     
134 | 135 |

might get optimized to

136 | 137 |
138 | public class Example {
139 |   public String aMethod() {
140 |     return "4:16";
141 |   }
142 | }
143 |     
144 |

see Pitest Mutators

145 |
146 | -------------------------------------------------------------------------------- /src/main/resources/ch/devcon5/sonar/plugins/mutationanalysis/model/INVERT_NEGS.html: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 |

Invert Negatives Mutator (INVERT_NEGS)

23 | 24 |

This operator mutates an integer or float number by negation.

25 |
26 |

For example

27 | 28 |
29 | public float doNegate(final int i) {
30 |   return -i;
31 | }
32 |     
33 | 34 |

will be mutated to

35 | 36 |
37 | public float doNegate(final int i) {
38 |   return i;
39 | }
40 |     
41 |

see Pitest Mutators

42 |
43 | -------------------------------------------------------------------------------- /src/main/resources/ch/devcon5/sonar/plugins/mutationanalysis/model/MATH.html: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 |

Math Mutator (MATH)

23 | 24 |

25 | This operator mutates binary arithmetic operations for integer or floating point arithmetic by replacing one 26 | operator with another. Operation according the following ruleset. 27 |

28 | 29 | 30 | 31 | 32 | 35 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 |
33 | Original operation 34 | 36 | Mutated operation 37 |
+-
-+
*/
/*
%*
&|
|&
^&
<<>>
>><<
>>><<<
87 | 88 |

Example

89 | 90 |
 91 | int a = b + c;
 92 |     
93 | 94 |

will be mutated to

95 | 96 |
 97 | int a = b - c;
 98 |     
99 | 100 |

101 | The + operator on Strings is overloaded and is not mutated by this operator. 102 |

103 | 104 |
105 | String a = "a" + "Text";
106 |     
107 | 108 |

109 | is a string concatenation and not a mathematical operator. The compiler replaces this concatenation with something like 110 |

111 | 112 |
113 | String a = new StringBuilder("a").append("Text").toString();
114 |     
115 | 116 |

117 | For increments, decrements, assignment increments, and assignemnt decrements of member variables (non-local) the 118 | compiler will also use binary arithmetic operations. Although a special iinc opcode exists for 119 | increments, but it is restricted to local variables (stack variables) and is not applicable to member variables. 120 | Hence, the math mutators will also mutate. 121 | 122 | Please note that the compiler will also use binary arithmetic operations for increments, decrements and 123 | assignment increments and decrements of non-local variables (member variables) although a special iinc 124 | opcode for increments exists. This special opcode is restricted to local variables (also called stack 125 | variables) and cannot be used for member variables. That means the math operator will also mutate. 126 |

127 | 128 |
129 | public class Example {
130 |   private int a;
131 | 
132 |   public void inc() {
133 |     this.a++;
134 |   }
135 | }
136 |     
137 | 138 |

to

139 | 140 |
141 | public class Example {
142 |   private int a;
143 | 
144 |   public void inc() {
145 |     this.a = this.a - 1;
146 |   }
147 | }
148 |     
149 |

see Pitest Mutators

150 |
151 | -------------------------------------------------------------------------------- /src/main/resources/ch/devcon5/sonar/plugins/mutationanalysis/model/NEGATE_CONDITIONALS.html: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 |

Negate Conditionals Mutator (NEGATE_CONDITIONALS)

23 | 24 |

This operator mutates conditional expressions negation.

25 | 26 | 27 | 28 | 29 | 32 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 |
30 | Original 31 | 33 | Mutated 34 |
==!=
!===
<=>
>=<
<>=
><=
65 | 66 |

Example

67 | 68 |
 69 | if (a != b) {
 70 |   // some code
 71 | }
 72 |     
73 | 74 |

will be mutated to

75 | 76 |
 77 | if (a == b) {
 78 |   // some code
 79 | }
 80 |     
81 | 82 |

or

83 | 84 |
 85 | if (a <= b) {
 86 |   // some code
 87 | }
 88 |     
89 | 90 |

will be mutated to

91 | 92 |
 93 | if (a > b) {
 94 |   // some code
 95 | }
 96 |     
97 | 98 |

99 | There is an overlap with the conditional boundary operator. The conditionals negation however is more unstable and 100 | is therefore easier to be killed by a test. 101 |

102 |

see Pitest Mutators

103 |
104 | -------------------------------------------------------------------------------- /src/main/resources/ch/devcon5/sonar/plugins/mutationanalysis/model/NON_VOID_METHOD_CALLS.html: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 |

Non Void Method Call Mutator (NON_VOID_METHOD_CALLS)

23 | 24 |

25 | This operator mutates calls to a method with a non-void return value. Instead of the return value the 26 | default value according the following table is used instead. 27 |

28 | 29 |

Default Values for Primitives and Reference Types

30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 |
TypeDefault Value
booleanfalse
int byte short long0
float double0.0
char'\u0000'
Objectnull
61 | 62 |

Example

63 | 64 |
 65 | public long aMethod() {
 66 |   return 5L;
 67 | }
 68 | 
 69 | public void bMethod() {
 70 |   long l = aMethod();
 71 |   // do something l
 72 | }
 73 |     
74 | 75 |

will be mutated to

76 | 77 |
 78 | public long aMethod() {
 79 |   return 5L;
 80 | }
 81 | 
 82 | public void foo() {
 83 |   int l = 0L;
 84 |   // do something with l
 85 | }
 86 |     
87 | 88 |

or

89 | 90 |
 91 | public Object aMethod() {
 92 |   return new Object();
 93 | }
 94 | 
 95 | public void bMethod() {
 96 |   Object o = bMethod();
 97 |   // do something with o
 98 | }
 99 |     
100 | 101 |

will be mutated to

102 | 103 |
104 | public Object aMethod() {
105 |   return new Object();
106 | }
107 | 
108 | public void bMethod() {
109 |   Object o = null;
110 |   // do something o
111 | }
112 |     
113 | 114 |

115 | This operator produces mutations that are fairly easy to detect by a test, especially when Reference types 116 | are mutated causing NullPointerExceptions. Further, it may create mutations that are an equivalent 117 | of the original code because the original code already returns a default value, that make it impossible to be 118 | detected by a test. 119 |

120 | 121 |

122 | This operator does not affect void methods or constructor calls. See 123 | Void Method Call Mutator for mutations of 124 | void methods and 125 | Constructor Call Mutator for mutations of 126 | constructors. 127 |

128 |

see Pitest Mutators

129 |
130 | -------------------------------------------------------------------------------- /src/main/resources/ch/devcon5/sonar/plugins/mutationanalysis/model/NULL_RETURN_VALUES.html: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 |

Null Return Values Mutator (NULL_RETURN_VALUES)

23 | 24 |

This operator mutates all non-primitive return values by replacing them with null.

25 |
26 | As this mutator produces mutations that are very likely to be unstable - causing a NullPointerException that 27 | is even found by trivial tests - this operator does not mutate the return type, if a more stable return 28 | value mutation exists. 29 |
30 | It does further not mutate methods that are annotated with @NotNull 31 |
32 | -------------------------------------------------------------------------------- /src/main/resources/ch/devcon5/sonar/plugins/mutationanalysis/model/PRIMITIVE_RETURN_VALS.html: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 |

Primitive Return Values Mutator (PRIMITIVE_RETURN_VALS)

23 | 24 |

This operator mutates all number primitive return values with 0 or 0.0.

25 |
26 | -------------------------------------------------------------------------------- /src/main/resources/ch/devcon5/sonar/plugins/mutationanalysis/model/REMOVE_CONDITIONALS.html: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 |

Remove Conditionals Mutator (REMOVE_CONDITIONALS)

23 | 24 |

25 | This operator will replace all conditional expressions so that they always evaluate to true, so the path guarded 26 | by the condition will always be executed. 27 |

28 |
29 |

For Example

30 | 31 |
32 | if (a != b) {
33 |   // some code
34 | }
35 |     
36 | 37 |

will be mutated to

38 | 39 |
40 | if (true) {
41 |   // some code
42 | }
43 |     
44 | 45 |

46 | This operator is not enabled by default. Nevertheless, it is recommended to enable this operator to get full 47 | coverage on conditional expressions 48 |

49 |

see Pitest Mutators

50 |
51 | -------------------------------------------------------------------------------- /src/main/resources/ch/devcon5/sonar/plugins/mutationanalysis/model/RETURN_VALS.html: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 |

Return Values Mutator (RETURN_VALS)

23 | 24 |

25 | This operator mutates method returns of non-void return values. The actual mutation applied depends on the 26 | return type, according the following table: 27 |

28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 45 | 46 | 47 | 49 | 52 | 56 | 57 | 58 | 59 | 60 | 63 | 64 | 65 | 66 | 67 | 71 | 72 | 73 | 74 | 75 | 79 | 80 | 81 |
Return TypeMutation FunctionDescription
booleanf(x) -> !x 42 | true is replaced with false and 43 | false is replaced with true 44 |
48 | int byte short 50 | f(x) -> x == 0 ? 1 : 0 51 | 53 | 0 is mutated to 1 and 54 | any other value is mutated to 0 55 |
longf(x) -> x+1 61 | the original value is incremented by 1, for example 5 -> 6 62 |
float doublef(x) -> x == NAN ? 0 : -(x+1.0) 68 | if the value is NAN it is replaced with 0, otherwise - if it is a number - it is 69 | replaced with the negated increment of the value 70 |
Objectf(x) -> x == null ? java.lang.RuntimeException : null 76 | if the return value would be null a java.lang.RuntimeException is thrown, 77 | otherwise null is returned instead of the original value 78 |
82 |
83 |

For Example

84 | 85 |
 86 | public boolean aMethod() {
 87 |   return false;
 88 | }
 89 |     
90 | 91 |

will be mutated to

92 | 93 |
 94 | public boolean aMethod() {
 95 |   return true;
 96 | }
 97 |     
98 | 99 |

or

100 | 101 |
102 | public Object aMethod() {
103 |   return new Object();
104 | }
105 |     
106 | 107 |

will be mutated to

108 | 109 |
110 | public Object aMethod() {
111 |    new Object();
112 |    return null;
113 | }
114 |     
115 |

see Pitest Mutators

116 |
117 | -------------------------------------------------------------------------------- /src/main/resources/ch/devcon5/sonar/plugins/mutationanalysis/model/VOID_METHOD_CALLS.html: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 |

Void Method Call Mutator (VOIDMETHODCALLS)

23 | 24 |

25 | This operator mutates methods without a return value - void methods - that do not produce an explicit result that 26 | is assigned to a variable. This could be a call of a setter or a message to an external system or underlying 27 | component for which the effects requires more effort to verify. This operator removes such method calls. 28 |

29 | 30 |
31 | public void aMethod(String s) {
32 |   // some Code
33 | }
34 | 
35 | public String bMethod() {
36 |   String s = "example";
37 |   aMethod(s);
38 |   return s;
39 | }
40 |     
41 | 42 |

will be changed to

43 | 44 |
45 | public void aMethod(String s) {
46 |   // someCode
47 | }
48 | 
49 | public String bMethod() {
50 |   String s = "example";
51 |   return s;
52 | }
53 |     
54 | 55 |

56 | Constructor calls are not considered as method calls and are therefore not affected by this operator. 57 | To mutate constructor calls, refer to the 58 | Constructor Call Mutator or the 59 | Non Void Method Call Mutator if you 60 | want to mutate method calls that produce a return value. 61 |

62 |

see Pitest Mutators

63 |
64 | -------------------------------------------------------------------------------- /src/main/resources/ch/devcon5/sonar/plugins/mutationanalysis/model/empty.html: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 | no description yet 23 |
24 | -------------------------------------------------------------------------------- /src/main/resources/ch/devcon5/sonar/plugins/mutationanalysis/rules.xml: -------------------------------------------------------------------------------- 1 | 2 | 21 | 22 | 23 | 24 | mutant.survived 25 | Survived Mutants 26 | 27 | Survived mutant 28 |

A mutant of a class has not been killed although appearing to be covered by a test.

29 |

If you don't want to activate any mutation operator specific rule, you may use this rule which is triggered by 30 | any Mutator. You should not activate this rule, when you use mutation operator specific rules, as it may result 31 | in redundant issued.

32 |

33 | For more information check out the PIT documentation 34 |

35 |

This rule is deprecated and is likely to be removed in future releases. Instead of this rule, use one of 36 | the mutation specific rules. 37 |

38 | ]]> 39 |
40 | DEPRECATED 41 | BUG 42 | aggregate 43 | pitest 44 | test 45 | test-quality 46 | mutation 47 |
48 | 49 | mutant.lurking 50 | Lurking Mutants 51 | 52 | Uncovered mutant 54 |

A mutant of a class has not been covered by a test at all.

55 |

An uncovered mutant is also a survived mutant. If you activate this rule, it will produce more issues 56 | than the Survived Mutant rule. It's advised to have either this or the Survived Mutant rule activated, but 57 | not both, as it will result in duplicate issues.Further it is not recommended to have this rule activated 58 | with any of the mutation operator specific rules.

59 |

For more information check out the PIT documentation 60 |

61 |

This rule is deprecated and is likely to be removed in future releases. Instead of this rule, use one of 62 | the mutation specific rules. 63 |

64 | ]]> 65 |
66 | DEPRECATED 67 | BUG 68 | aggregate 69 | pitest 70 | test 71 | test-quality 72 | mutation 73 |
74 | 75 | mutant.unknownStatus 76 | Mutant with unknown Status 77 | 78 | This rule is deprecated and is likely to be removed in future releases. Instead of this rule, use one of 80 | the mutation specific rules. 81 |

82 | ]]> 83 |
84 | MINOR 85 | DEPRECATED 86 | BUG 87 | aggregate 88 | pitest 89 | test 90 | test-quality 91 | mutation 92 |
93 | 94 | mutant.coverage 95 | Mutation Coverage below threshold 96 | 97 | 98 | 99 | pitest 100 | test 101 | test-quality 102 | mutation 103 | coverage 104 | 105 | mutant.coverage.threshold 106 | 107 | 110 | 111 | 80.0 112 | 113 | 114 |
115 | -------------------------------------------------------------------------------- /src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/test/java/ch/devcon5/sonar/plugins/mutationanalysis/MutationAnalysisPluginTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis; 22 | 23 | import static org.junit.jupiter.api.Assertions.assertFalse; 24 | import static org.junit.jupiter.api.Assertions.assertTrue; 25 | 26 | import ch.devcon5.sonar.plugins.mutationanalysis.metrics.MutationAnalysisMetrics; 27 | import ch.devcon5.sonar.plugins.mutationanalysis.metrics.MutationDensityComputer; 28 | import ch.devcon5.sonar.plugins.mutationanalysis.metrics.MutationScoreComputer; 29 | import ch.devcon5.sonar.plugins.mutationanalysis.metrics.QuantitativeMeasureComputer; 30 | import ch.devcon5.sonar.plugins.mutationanalysis.metrics.TestKillRatioComputer; 31 | import ch.devcon5.sonar.plugins.mutationanalysis.metrics.TotalMutationsComputer; 32 | import ch.devcon5.sonar.plugins.mutationanalysis.report.PitestReportParser; 33 | import ch.devcon5.sonar.plugins.mutationanalysis.report.ReportFinder; 34 | import ch.devcon5.sonar.plugins.mutationanalysis.rules.JavaProfileDefinition; 35 | import ch.devcon5.sonar.plugins.mutationanalysis.rules.JavaRulesDefinition; 36 | import ch.devcon5.sonar.plugins.mutationanalysis.rules.KotlinProfileDefinition; 37 | import ch.devcon5.sonar.plugins.mutationanalysis.rules.KotlinRulesDefinition; 38 | import ch.devcon5.sonar.plugins.mutationanalysis.sensors.PitestSensor; 39 | import ch.devcon5.sonar.plugins.mutationanalysis.testharness.TestConfiguration; 40 | import org.junit.jupiter.api.Test; 41 | import org.sonar.api.Plugin; 42 | import org.sonar.api.SonarEdition; 43 | import org.sonar.api.SonarQubeSide; 44 | import org.sonar.api.SonarRuntime; 45 | import org.sonar.api.internal.SonarRuntimeImpl; 46 | import org.sonar.api.utils.Version; 47 | 48 | class MutationAnalysisPluginTest { 49 | 50 | private final SonarRuntime sonarRuntime = SonarRuntimeImpl.forSonarQube( 51 | Version.create(8, 9), SonarQubeSide.SCANNER, SonarEdition.COMMUNITY); 52 | 53 | 54 | @Test 55 | void testDefine() { 56 | MutationAnalysisPlugin plugin = new MutationAnalysisPlugin(); 57 | Plugin.Context context = new Plugin.Context(sonarRuntime); 58 | 59 | plugin.define(context); 60 | 61 | assertTrue(context.getExtensions().contains(PitestReportParser.class)); 62 | assertTrue(context.getExtensions().contains(PitestReportParser.class)); 63 | assertTrue(context.getExtensions().contains(ReportFinder.class)); 64 | assertTrue(context.getExtensions().contains(JavaRulesDefinition.class)); 65 | assertTrue(context.getExtensions().contains(KotlinRulesDefinition.class)); 66 | assertTrue(context.getExtensions().contains(JavaProfileDefinition.class)); 67 | assertTrue(context.getExtensions().contains(KotlinProfileDefinition.class)); 68 | assertTrue(context.getExtensions().contains(PitestSensor.class)); 69 | assertTrue(context.getExtensions().contains(MutationAnalysisMetrics.class)); 70 | assertTrue(context.getExtensions().contains(MutationScoreComputer.class)); 71 | assertTrue(context.getExtensions().contains(MutationDensityComputer.class)); 72 | assertTrue(context.getExtensions().contains(TotalMutationsComputer.class)); 73 | assertTrue(context.getExtensions().contains(TestKillRatioComputer.class)); 74 | assertTrue(context.getExtensions().contains(QuantitativeMeasureComputer.class)); 75 | } 76 | 77 | @Test 78 | void experimentalFeaturesEnabled_default_false() { 79 | TestConfiguration configuration = new TestConfiguration(); 80 | 81 | assertFalse(MutationAnalysisPlugin.isExperimentalFeaturesEnabled(configuration)); 82 | } 83 | 84 | @Test 85 | void experimentalFeaturesEnabled_configured_true() { 86 | TestConfiguration configuration = new TestConfiguration(); 87 | configuration.set("dc5.mutationAnalysis.experimentalFeatures.enabled", true); 88 | 89 | assertTrue(MutationAnalysisPlugin.isExperimentalFeaturesEnabled(configuration)); 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/test/java/ch/devcon5/sonar/plugins/mutationanalysis/StreamsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis; 22 | 23 | import static org.junit.jupiter.api.Assertions.assertEquals; 24 | import static org.junit.jupiter.api.Assertions.assertFalse; 25 | import static org.junit.jupiter.api.Assertions.assertTrue; 26 | 27 | import java.util.Arrays; 28 | import java.util.stream.Stream; 29 | import org.junit.jupiter.api.Test; 30 | 31 | class StreamsTest { 32 | 33 | @Test 34 | void sequentialStream() { 35 | Stream stream = Streams.sequentialStream(Arrays.asList("one", "two")); 36 | assertFalse(stream.isParallel()); 37 | assertEquals(2, stream.count()); 38 | } 39 | 40 | @Test 41 | void parallelStream() { 42 | Stream stream = Streams.parallelStream(Arrays.asList("one", "two")); 43 | assertTrue(stream.isParallel()); 44 | assertEquals(2, stream.count()); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/ch/devcon5/sonar/plugins/mutationanalysis/metrics/MutationAnalysisMetricsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.metrics; 22 | 23 | 24 | import static org.junit.jupiter.api.Assertions.assertFalse; 25 | import static org.junit.jupiter.api.Assertions.assertNotNull; 26 | 27 | import java.util.List; 28 | import org.junit.jupiter.api.Test; 29 | import org.sonar.api.measures.Metric; 30 | 31 | /** 32 | * Mutation Analysis Metrics Tests 33 | */ 34 | class MutationAnalysisMetricsTest { 35 | 36 | @Test 37 | void testGetMetrics() { 38 | final List metrics = new MutationAnalysisMetrics().getMetrics(); 39 | assertNotNull(metrics); 40 | assertFalse(metrics.isEmpty()); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/ch/devcon5/sonar/plugins/mutationanalysis/metrics/MutationDensityComputerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.metrics; 22 | 23 | import static ch.devcon5.sonar.plugins.mutationanalysis.metrics.MutationMetrics.MUTATIONS_DENSITY_KEY; 24 | import static ch.devcon5.sonar.plugins.mutationanalysis.metrics.MutationMetrics.MUTATIONS_TOTAL_KEY; 25 | import static org.junit.jupiter.api.Assertions.assertEquals; 26 | import static org.junit.jupiter.api.Assertions.assertNull; 27 | import static org.junit.jupiter.api.Assertions.assertTrue; 28 | import static org.sonar.api.measures.CoreMetrics.LINES_TO_COVER_KEY; 29 | 30 | import ch.devcon5.sonar.plugins.mutationanalysis.testharness.MeasureComputerTestHarness; 31 | import java.util.Arrays; 32 | import java.util.Collections; 33 | import org.junit.jupiter.api.BeforeEach; 34 | import org.junit.jupiter.api.Test; 35 | import org.sonar.api.ce.measure.Measure; 36 | import org.sonar.api.ce.measure.MeasureComputer; 37 | import org.sonar.api.ce.measure.test.TestMeasureComputerContext; 38 | import org.sonar.api.ce.measure.test.TestMeasureComputerDefinitionContext; 39 | 40 | /** 41 | * Mutation Density Computer Tests 42 | */ 43 | class MutationDensityComputerTest { 44 | 45 | private MeasureComputerTestHarness harness; 46 | private MutationDensityComputer computer; 47 | 48 | @BeforeEach 49 | public void setUp() throws Exception { 50 | this.harness = MeasureComputerTestHarness.createFor(MutationDensityComputer.class); 51 | this.computer = harness.getComputer(); 52 | } 53 | 54 | @Test 55 | void define() { 56 | final TestMeasureComputerDefinitionContext context = new TestMeasureComputerDefinitionContext(); 57 | 58 | final MeasureComputer.MeasureComputerDefinition def = computer.define(context); 59 | 60 | assertTrue(def.getInputMetrics() 61 | .containsAll(Arrays.asList(MUTATIONS_TOTAL_KEY, LINES_TO_COVER_KEY))); 62 | assertTrue(def.getOutputMetrics().containsAll(Collections.singletonList(MUTATIONS_DENSITY_KEY))); 63 | } 64 | 65 | @Test 66 | void compute_experimentalFeaturesDisabled_noMeasure() { 67 | harness.enableExperimentalFeatures(false); 68 | 69 | final TestMeasureComputerContext measureContext = harness.createMeasureContextForSourceFile("compKey"); 70 | 71 | measureContext.addInputMeasure(MUTATIONS_TOTAL_KEY, 30); 72 | measureContext.addInputMeasure(LINES_TO_COVER_KEY, 20); 73 | 74 | computer.compute(measureContext); 75 | 76 | assertNull(measureContext.getMeasure(MUTATIONS_DENSITY_KEY)); 77 | } 78 | 79 | @Test 80 | void compute_noMutations_noMeasure() { 81 | final TestMeasureComputerContext measureContext = harness.createMeasureContextForSourceFile("compKey"); 82 | 83 | computer.compute(measureContext); 84 | 85 | assertNull(measureContext.getMeasure(MUTATIONS_DENSITY_KEY)); 86 | } 87 | 88 | @Test 89 | void compute_withInputMeausresMutations_correctOutputMeasure() { 90 | final TestMeasureComputerContext measureContext = harness.createMeasureContextForSourceFile("compKey"); 91 | 92 | measureContext.addInputMeasure(MUTATIONS_TOTAL_KEY, 30); 93 | measureContext.addInputMeasure(LINES_TO_COVER_KEY, 20); 94 | 95 | computer.compute(measureContext); 96 | 97 | Measure density = measureContext.getMeasure(MUTATIONS_DENSITY_KEY); 98 | assertEquals(150.0, density.getDoubleValue(), 0.05); 99 | } 100 | 101 | @Test 102 | void compute_ZeroMutationsAndZeroLines_noDensityMetric() { 103 | final TestMeasureComputerContext measureContext = harness.createMeasureContextForSourceFile("compKey"); 104 | 105 | measureContext.addInputMeasure(MUTATIONS_TOTAL_KEY, 0); 106 | measureContext.addInputMeasure(LINES_TO_COVER_KEY, 0); 107 | 108 | computer.compute(measureContext); 109 | 110 | assertNull(measureContext.getMeasure(MUTATIONS_DENSITY_KEY)); 111 | } 112 | 113 | @Test 114 | void compute_noLineMetrics_densitySetToZero() { 115 | final TestMeasureComputerContext measureContext = harness.createMeasureContextForSourceFile("compKey"); 116 | 117 | measureContext.addInputMeasure(MUTATIONS_TOTAL_KEY, 30); 118 | 119 | computer.compute(measureContext); 120 | 121 | Measure density = measureContext.getMeasure(MUTATIONS_DENSITY_KEY); 122 | assertEquals(0.0, density.getDoubleValue(), 0.05); 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /src/test/java/ch/devcon5/sonar/plugins/mutationanalysis/metrics/MutationMetricsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.metrics; 22 | 23 | import static org.junit.jupiter.api.Assertions.assertFalse; 24 | import static org.junit.jupiter.api.Assertions.assertNotNull; 25 | 26 | import java.io.Serializable; 27 | import java.util.List; 28 | import org.junit.jupiter.api.Test; 29 | import org.sonar.api.measures.Metric; 30 | 31 | class MutationMetricsTest { 32 | 33 | @Test 34 | void testMetricConstants() { 35 | assertNotNull(MutationMetrics.MUTATIONS_COVERAGE); 36 | assertNotNull(MutationMetrics.MUTATIONS_TEST_STRENGTH); 37 | assertNotNull(MutationMetrics.MUTATIONS_DATA); 38 | assertNotNull(MutationMetrics.MUTATIONS_DETECTED); 39 | assertNotNull(MutationMetrics.MUTATIONS_KILLED); 40 | assertNotNull(MutationMetrics.MUTATIONS_MEMORY_ERROR); 41 | assertNotNull(MutationMetrics.MUTATIONS_NO_COVERAGE); 42 | assertNotNull(MutationMetrics.MUTATIONS_SURVIVED); 43 | assertNotNull(MutationMetrics.MUTATIONS_TIMED_OUT); 44 | assertNotNull(MutationMetrics.MUTATIONS_TOTAL); 45 | assertNotNull(MutationMetrics.MUTATIONS_UNKNOWN); 46 | } 47 | 48 | @Test 49 | void testGetQuantitativeMetrics() { 50 | final List> metrics = MutationMetrics.getQuantitativeMetrics(); 51 | assertNotNull(metrics); 52 | assertFalse(metrics.isEmpty()); 53 | } 54 | 55 | @Test 56 | void testGetSensorMetrics() { 57 | final List> metrics = MutationMetrics.getSensorMetrics(); 58 | assertNotNull(metrics); 59 | assertFalse(metrics.isEmpty()); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/ch/devcon5/sonar/plugins/mutationanalysis/model/MutationOperatorsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.model; 22 | 23 | import static org.junit.jupiter.api.Assertions.assertEquals; 24 | import static org.junit.jupiter.api.Assertions.assertFalse; 25 | import static org.junit.jupiter.api.Assertions.assertNotNull; 26 | 27 | import java.util.Collection; 28 | import java.util.HashSet; 29 | import org.junit.jupiter.api.Test; 30 | import org.junit.jupiter.params.ParameterizedTest; 31 | import org.junit.jupiter.params.provider.ValueSource; 32 | 33 | /** 34 | * Mutation Operators Test 35 | */ 36 | class MutationOperatorsTest { 37 | 38 | @ParameterizedTest 39 | @ValueSource(strings = { 40 | "ARGUMENT_PROPAGATION", 41 | "org.pitest.mutationtest.engine.gregor.mutators.ArgumentPropagationMutator", 42 | "org.pitest.mutationtest.engine.gregor.mutators.experimental.ArgumentPropagationMutator", 43 | "org.pitest.mutationtest.engine.gregor.mutators.ArgumentPropagationMutator_WITH_SUFFIX", 44 | "org.pitest.mutationtest.engine.gregor.mutators.experimental.ArgumentPropagationMutator_WITH_SUFFIX" 45 | }) 46 | void testFind_knownMutator_byID(String mutagenKey) { 47 | final MutationOperator mutationOperator = MutationOperators.find(mutagenKey); 48 | assertNotNull(mutationOperator); 49 | assertEquals("ARGUMENT_PROPAGATION", mutationOperator.getId()); 50 | assertEquals(new HashSet() {{ 51 | add("org.pitest.mutationtest.engine.gregor.mutators.ArgumentPropagationMutator"); 52 | add("org.pitest.mutationtest.engine.gregor.mutators.experimental.ArgumentPropagationMutator"); 53 | }}, mutationOperator.getClassNames()); 54 | assertNotNull(mutationOperator.getViolationDescription()); 55 | } 56 | 57 | @Test 58 | void testAllMutators() { 59 | // act 60 | final Collection mutationOperators = MutationOperators.allMutationOperators(); 61 | 62 | // assert 63 | assertNotNull(mutationOperators); 64 | assertFalse(mutationOperators.isEmpty()); 65 | assertEquals(23, mutationOperators.size()); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/ch/devcon5/sonar/plugins/mutationanalysis/model/StateTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.model; 22 | 23 | import static org.junit.jupiter.api.Assertions.assertEquals; 24 | import static org.junit.jupiter.api.Assertions.assertFalse; 25 | import static org.junit.jupiter.api.Assertions.assertTrue; 26 | 27 | import org.junit.jupiter.api.Test; 28 | 29 | class StateTest { 30 | 31 | @Test 32 | void testIsAlive_enumValues() { 33 | assertTrue(Mutant.State.NO_COVERAGE.isAlive()); 34 | assertTrue(Mutant.State.UNKNOWN.isAlive()); 35 | assertTrue(Mutant.State.SURVIVED.isAlive()); 36 | assertFalse(Mutant.State.MEMORY_ERROR.isAlive()); 37 | assertFalse(Mutant.State.TIMED_OUT.isAlive()); 38 | assertFalse(Mutant.State.KILLED.isAlive()); 39 | } 40 | 41 | @Test 42 | void testParse_enumValues() { 43 | assertEquals(Mutant.State.NO_COVERAGE, Mutant.State.parse("NO_COVERAGE")); 44 | assertEquals(Mutant.State.KILLED, Mutant.State.parse("KILLED")); 45 | assertEquals(Mutant.State.SURVIVED, Mutant.State.parse("SURVIVED")); 46 | assertEquals(Mutant.State.MEMORY_ERROR, Mutant.State.parse("MEMORY_ERROR")); 47 | assertEquals(Mutant.State.TIMED_OUT, Mutant.State.parse("TIMED_OUT")); 48 | assertEquals(Mutant.State.UNKNOWN, Mutant.State.parse("UNKNOWN")); 49 | } 50 | 51 | @Test 52 | void testParse_null_unknown() { 53 | assertEquals(Mutant.State.UNKNOWN, Mutant.State.parse(null)); 54 | } 55 | 56 | @Test 57 | void testParse_unknown_unknown() { 58 | assertEquals(Mutant.State.UNKNOWN, Mutant.State.parse("xxx")); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/ch/devcon5/sonar/plugins/mutationanalysis/model/TestDescriptorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.model; 22 | 23 | import static org.junit.jupiter.api.Assertions.assertEquals; 24 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 25 | 26 | import java.util.Objects; 27 | import org.junit.jupiter.api.Test; 28 | import org.junit.jupiter.params.ParameterizedTest; 29 | import org.junit.jupiter.params.provider.ValueSource; 30 | 31 | /** 32 | * Test Descriptor Tests 33 | */ 34 | class TestDescriptorTest { 35 | 36 | @ParameterizedTest 37 | @ValueSource(strings = { 38 | "some.package.ClassName.testMethod(some.package.ClassName)", 39 | "some.package.ClassName", 40 | "some.package.ClassName$SubTest.testMethod(some.package.ClassName$SubTest)", 41 | "some.package.ClassName$SubTest" 42 | }) 43 | void standardClassOrNested_methodOrNot(String spec) { 44 | TestDescriptor td = new TestDescriptor(spec); 45 | assertEquals("some.package.ClassName", td.getClassName()); 46 | assertEquals(spec.contains("testMethod") ? "testMethod" : "unknown", td.getMethodName()); 47 | } 48 | 49 | @Test 50 | void unknownSpec() { 51 | TestDescriptor td = new TestDescriptor("some/package/ClassName"); 52 | assertEquals("some/package/ClassName", td.getSpec()); 53 | assertEquals("some/package/ClassName", td.getClassName()); 54 | assertEquals("unknown", td.getMethodName()); 55 | } 56 | 57 | @Test 58 | void getSpec() { 59 | TestDescriptor td = new TestDescriptor("some.package.ClassName.testMethod(some.package.ClassName)"); 60 | assertEquals("some.package.ClassName.testMethod(some.package.ClassName)", td.getSpec()); 61 | } 62 | 63 | @Test 64 | void test_toString() { 65 | TestDescriptor td = new TestDescriptor("some.package.ClassName.testMethod(some.package.ClassName)"); 66 | assertEquals("TestDescriptor{class='some.package.ClassName', method='testMethod'}", td.toString()); 67 | } 68 | 69 | @Test 70 | void test_equals() { 71 | TestDescriptor td1 = new TestDescriptor("some.package.ClassName.testMethod_one(some.package.ClassName)"); 72 | TestDescriptor td2 = new TestDescriptor("some.package.ClassName.testMethod_two(some.package.ClassName)"); 73 | assertEquals(td1, td2); 74 | } 75 | 76 | @Test 77 | void test_equals_sameRef() { 78 | TestDescriptor td1 = new TestDescriptor("some.package.ClassName.testMethod_one(some.package.ClassName)"); 79 | assertEquals(td1, td1); 80 | } 81 | 82 | @Test 83 | void test_equals_null() { 84 | TestDescriptor td1 = new TestDescriptor("some.package.ClassName.testMethod_one(some.package.ClassName)"); 85 | assertNotEquals(null, td1); 86 | } 87 | 88 | @Test 89 | void test_equals_differentClass() { 90 | TestDescriptor td1 = new TestDescriptor("some.package.ClassName.testMethod_one(some.package.ClassName)"); 91 | assertNotEquals(new Object(), td1); 92 | } 93 | 94 | @Test 95 | void test_equals_not() { 96 | TestDescriptor td1 = new TestDescriptor("some.package.ClassName.testMethod_one(some.package.ClassName)"); 97 | TestDescriptor td2 = new TestDescriptor("some.otherpackage.ClassName.testMethod_two(some.package.ClassName)"); 98 | assertNotEquals(td1, td2); 99 | } 100 | 101 | @Test 102 | void test_hashCode() { 103 | TestDescriptor td = new TestDescriptor("some.package.ClassName.testMethod_one(some.package.ClassName)"); 104 | assertEquals(Objects.hash("some.package.ClassName"), td.hashCode()); 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/test/java/ch/devcon5/sonar/plugins/mutationanalysis/report/ReportsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.report; 22 | 23 | 24 | import static org.junit.jupiter.api.Assertions.assertEquals; 25 | import static org.junit.jupiter.api.Assertions.assertNotNull; 26 | import static org.junit.jupiter.api.Assertions.assertTrue; 27 | 28 | import ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant; 29 | import java.io.File; 30 | import java.io.IOException; 31 | import java.nio.file.Files; 32 | import java.nio.file.Path; 33 | import java.util.Collection; 34 | import org.apache.commons.io.IOUtils; 35 | import org.junit.jupiter.api.Test; 36 | import org.junit.jupiter.api.io.TempDir; 37 | 38 | class ReportsTest { 39 | 40 | @TempDir 41 | public Path folder; 42 | 43 | @Test 44 | void testReadMutants_fromDirectory_noReport() throws Exception { 45 | // act 46 | final Collection mutants = Reports.readMutants(folder); 47 | 48 | // assert 49 | assertNotNull(mutants); 50 | assertTrue(mutants.isEmpty()); 51 | } 52 | 53 | @Test 54 | void testReadMutants_fromDirectory_withReport() throws Exception { 55 | // prepare 56 | fileFromResource("ReportsTest_mutations.xml", "mutations.xml"); 57 | 58 | // act 59 | final Collection mutants = Reports.readMutants(folder); 60 | 61 | // assert 62 | assertNotNull(mutants); 63 | assertEquals(3, mutants.size()); 64 | } 65 | 66 | @Test 67 | void testReadMutants_fromFile() throws Exception { 68 | // prepare 69 | final File file = fileFromResource("ReportsTest_mutations.xml", "mutations.xml"); 70 | 71 | // act 72 | final Collection mutants = Reports.readMutants(file.toPath()); 73 | 74 | // assert 75 | assertNotNull(mutants); 76 | assertEquals(3, mutants.size()); 77 | } 78 | 79 | private File fileFromResource(final String resourcePath, final String fileName) throws IOException { 80 | final File newFile = Files.createFile(folder.resolve(fileName)).toFile(); 81 | IOUtils.copy(getClass().getResourceAsStream(resourcePath), Files.newOutputStream(newFile.toPath())); 82 | return newFile; 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/test/java/ch/devcon5/sonar/plugins/mutationanalysis/rules/JavaProfileDefinitionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.rules; 22 | 23 | import static ch.devcon5.sonar.plugins.mutationanalysis.rules.MutationAnalysisRulesDefinition.REPOSITORY_KEY; 24 | import static org.junit.jupiter.api.Assertions.assertEquals; 25 | 26 | import org.junit.jupiter.api.Test; 27 | import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; 28 | import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition.BuiltInQualityProfile; 29 | 30 | class JavaProfileDefinitionTest { 31 | 32 | private final BuiltInQualityProfilesDefinition.Context context = new BuiltInQualityProfilesDefinition.Context(); 33 | 34 | @Test 35 | void define_java() { 36 | MutationAnalysisProfileDefinition def = new JavaProfileDefinition(); 37 | 38 | def.define(context); 39 | 40 | BuiltInQualityProfile javaProfile = context.profile("java", "Mutation Analysis"); 41 | 42 | assertEquals(23, javaProfile.rules().stream().filter(r -> (REPOSITORY_KEY + ".java").equals(r.repoKey())).count()); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/ch/devcon5/sonar/plugins/mutationanalysis/rules/JavaRulesDefinitionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | package ch.devcon5.sonar.plugins.mutationanalysis.rules; 21 | 22 | import static ch.devcon5.sonar.plugins.mutationanalysis.rules.MutationAnalysisRulesDefinition.MUTANT_RULES_PREFIX; 23 | import static org.junit.jupiter.api.Assertions.assertEquals; 24 | import static org.junit.jupiter.api.Assertions.assertNotNull; 25 | import static org.junit.jupiter.api.Assertions.assertTrue; 26 | 27 | import ch.devcon5.sonar.plugins.mutationanalysis.testharness.TestConfiguration; 28 | import java.util.List; 29 | import org.junit.jupiter.api.BeforeEach; 30 | import org.junit.jupiter.api.Test; 31 | import org.sonar.api.config.Configuration; 32 | import org.sonar.api.rule.RuleStatus; 33 | import org.sonar.api.rules.RuleType; 34 | import org.sonar.api.server.rule.RulesDefinition; 35 | import org.sonar.api.server.rule.RulesDefinition.Context; 36 | import org.sonar.api.server.rule.RulesDefinitionXmlLoader; 37 | 38 | class JavaRulesDefinitionTest { 39 | 40 | private final Configuration configuration = new TestConfiguration(); 41 | 42 | private JavaRulesDefinition subject; 43 | 44 | @BeforeEach 45 | public void setUp() throws Exception { 46 | subject = new JavaRulesDefinition(configuration, new RulesDefinitionXmlLoader()); 47 | } 48 | 49 | @Test 50 | void testDefine() { 51 | // prepare 52 | Context context = new RulesDefinition.Context(); 53 | 54 | // act 55 | subject.define(context); 56 | 57 | // assert 58 | RulesDefinition.Repository repository = context.repository( 59 | MutationAnalysisRulesDefinition.REPOSITORY_KEY + ".java"); 60 | assertNotNull(repository); 61 | 62 | assertEquals("java", repository.language()); 63 | assertEquals(MutationAnalysisRulesDefinition.REPOSITORY_NAME, repository.name()); 64 | assertRules(repository.rules()); 65 | } 66 | 67 | private void assertRules(final List rules) { 68 | assertEquals(50, rules.size()); 69 | for (RulesDefinition.Rule rule : rules) { 70 | assertNotNull(rule.debtRemediationFunction()); 71 | assertNotNull(rule.gapDescription()); 72 | assertNotNull(rule.htmlDescription()); 73 | } 74 | 75 | //@ denotes a toString'ed object reference 76 | //"null " would be exactly 5 characters 77 | assertTrue(rules.stream() 78 | .filter(r -> r.key().startsWith(MUTANT_RULES_PREFIX) && r.key().endsWith("CODE_SMELL")) 79 | .allMatch(r -> r.name().matches("[^@]{6,}\\(Code Smell\\)"))); 80 | 81 | assertTrue(rules.stream() 82 | .filter(r -> r.key().startsWith(MUTANT_RULES_PREFIX) && !r.key().endsWith("CODE_SMELL")) 83 | .noneMatch(r -> r.name().matches("[^@]{6,}\\(Code Smell\\)"))); 84 | 85 | //all mutator rules 86 | assertEquals(26, 87 | rules.stream() 88 | .filter(rule -> rule.key().startsWith(MUTANT_RULES_PREFIX)) 89 | .filter(rule -> RuleType.BUG.equals(rule.type())) 90 | .count()); 91 | assertEquals(24, 92 | rules.stream() 93 | .filter(rule -> rule.key().startsWith(MUTANT_RULES_PREFIX)) 94 | .filter(rule -> RuleType.CODE_SMELL.equals(rule.type())) 95 | .count()); 96 | assertEquals(34, 97 | rules.stream() 98 | .filter(rule -> rule.status() == RuleStatus.READY) 99 | .filter(RulesDefinition.Rule::activatedByDefault) 100 | .count()); 101 | assertEquals(12, 102 | rules.stream() 103 | .filter(rule -> rule.status() == RuleStatus.BETA) 104 | .filter(rule -> !rule.activatedByDefault()) 105 | .count()); 106 | assertEquals(3, 107 | rules.stream() 108 | .filter(rule -> rule.status() == RuleStatus.DEPRECATED) 109 | .filter(rule -> !rule.activatedByDefault()) 110 | .count()); 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/test/java/ch/devcon5/sonar/plugins/mutationanalysis/rules/KotlinProfileDefinitionTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.rules 22 | 23 | import ch.devcon5.sonar.plugins.mutationanalysis.rules.MutationAnalysisRulesDefinition.REPOSITORY_KEY 24 | import org.junit.jupiter.api.Assertions.assertEquals 25 | import org.junit.jupiter.api.Test as test 26 | import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition 27 | 28 | 29 | class KotlinProfileDefinitionTest { 30 | 31 | val context = BuiltInQualityProfilesDefinition.Context() 32 | 33 | @test 34 | fun define() { 35 | val def = KotlinProfileDefinition() 36 | 37 | def.define(context) 38 | 39 | val kotlinProfile = context.profile("kotlin", "Mutation Analysis") 40 | 41 | assertEquals(23, kotlinProfile.rules().stream().filter { r -> "$REPOSITORY_KEY.kotlin" == r.repoKey() }.count()) 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/ch/devcon5/sonar/plugins/mutationanalysis/rules/KotlinRulesDefinitionTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.rules 22 | 23 | import ch.devcon5.sonar.plugins.mutationanalysis.rules.MutationAnalysisRulesDefinition.MUTANT_RULES_PREFIX 24 | import ch.devcon5.sonar.plugins.mutationanalysis.testharness.TestConfiguration 25 | import org.junit.jupiter.api.Assertions.assertEquals 26 | import org.junit.jupiter.api.Assertions.assertNotNull 27 | import org.sonar.api.config.Configuration 28 | import org.sonar.api.rule.RuleStatus 29 | import org.sonar.api.rules.RuleType 30 | import org.sonar.api.server.rule.RulesDefinition 31 | import org.sonar.api.server.rule.RulesDefinitionXmlLoader 32 | import org.junit.jupiter.api.BeforeEach as BeforeTest 33 | import org.junit.jupiter.api.Test as test 34 | 35 | 36 | class KotlinRulesDefinitionTest { 37 | 38 | 39 | private val configuration: Configuration = TestConfiguration() 40 | 41 | private var subject: KotlinRulesDefinition? = null 42 | 43 | @BeforeTest 44 | @Throws(Exception::class) 45 | fun setUp() { 46 | subject = KotlinRulesDefinition(configuration, RulesDefinitionXmlLoader()) 47 | } 48 | 49 | @test 50 | @Throws(Exception::class) 51 | fun testDefine() { 52 | // prepare 53 | val context = RulesDefinition.Context() 54 | 55 | // act 56 | subject!!.define(context) 57 | 58 | // assert 59 | val repository = context.repository(MutationAnalysisRulesDefinition.REPOSITORY_KEY + ".kotlin") 60 | 61 | assertEquals("kotlin", repository!!.language()) 62 | assertEquals(MutationAnalysisRulesDefinition.REPOSITORY_NAME, repository.name()) 63 | assertRules(repository.rules()) 64 | } 65 | 66 | private fun assertRules(rules: List) { 67 | assertEquals(50, rules.size.toLong()) 68 | for (rule in rules) { 69 | assertNotNull(rule.debtRemediationFunction()) 70 | assertNotNull(rule.gapDescription()) 71 | assertNotNull(rule.htmlDescription()) 72 | } 73 | 74 | assertEquals(26, rules.stream() 75 | .filter { rule -> rule.key().startsWith(MUTANT_RULES_PREFIX) } 76 | .filter { rule -> RuleType.BUG == rule.type() }.count()) 77 | assertEquals(24, rules.stream() 78 | .filter { rule -> rule.key().startsWith(MUTANT_RULES_PREFIX) } 79 | .filter { rule -> RuleType.CODE_SMELL == rule.type() }.count()) 80 | assertEquals(12, rules.stream().filter { rule -> rule.status() == RuleStatus.BETA }.count()) 81 | assertEquals(3, rules.stream().filter { rule -> rule.status() == RuleStatus.DEPRECATED }.count()) 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/test/java/ch/devcon5/sonar/plugins/mutationanalysis/sensors/ResourceResolverTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.sensors; 22 | 23 | import static org.junit.jupiter.api.Assertions.assertEquals; 24 | import static org.junit.jupiter.api.Assertions.assertTrue; 25 | 26 | import ch.devcon5.sonar.plugins.mutationanalysis.testharness.SensorTestHarness; 27 | import java.nio.file.Path; 28 | import org.junit.jupiter.api.BeforeAll; 29 | import org.junit.jupiter.api.BeforeEach; 30 | import org.junit.jupiter.api.Test; 31 | import org.junit.jupiter.api.io.TempDir; 32 | import org.sonar.api.batch.fs.FileSystem; 33 | 34 | public class ResourceResolverTest { 35 | 36 | @TempDir 37 | public static Path folder; 38 | 39 | private static FileSystem FILESYSTEM; 40 | 41 | private ResourceResolver resolver; 42 | 43 | @BeforeAll 44 | public static void setUpFilesystem() throws Exception { 45 | SensorTestHarness harness = SensorTestHarness.builder().withTempFolder(folder).build(); 46 | 47 | harness.createSourceFile("src/main/java/ch/example/java/", "Example.java"); 48 | harness.createSourceFile("src/main/java/ch/example/java/", "Example$Nested.java"); 49 | harness.createSourceFile("src/main/java/ch/example/java/", "Example$Nested$Nested.java"); 50 | harness.createSourceFile("src/main/kotlin/ch/example/kotlin/", "Example.kt"); 51 | harness.createSourceFile("src/main/kotlin/ch/example/kotlin/", "Example$Nested.kt"); 52 | harness.createSourceFile("src/main/kotlin/ch/example/kotlin/", "Example$Nested$Nested.kt"); 53 | 54 | FILESYSTEM = harness.createSensorContext().scanFiles().fileSystem(); 55 | } 56 | 57 | @BeforeEach 58 | public void setUp() throws Exception { 59 | resolver = new ResourceResolver(FILESYSTEM); 60 | } 61 | 62 | @Test 63 | void resolve_javaFile() { 64 | assertTrue(resolver.resolve("ch.example.java.Example").isPresent()); 65 | assertEquals("Example.java", resolver.resolve("ch.example.java.Example").get().filename()); 66 | } 67 | 68 | @Test 69 | void resolve_nestedJavaClass() { 70 | assertTrue(resolver.resolve("ch.example.java.Example$Nested").isPresent()); 71 | assertEquals("Example.java", resolver.resolve("ch.example.java.Example$Nested").get().filename()); 72 | } 73 | 74 | @Test 75 | void resolve_deeplyNestedJavaClass() { 76 | assertTrue(resolver.resolve("ch.example.java.Example$Nested$Nested").isPresent()); 77 | assertEquals("Example.java", resolver.resolve("ch.example.java.Example$Nested$Nested").get().filename()); 78 | } 79 | 80 | @Test 81 | void resolve_kotlinClass() { 82 | assertTrue(resolver.resolve("ch.example.kotlin.Example").isPresent()); 83 | assertEquals("Example.kt", resolver.resolve("ch.example.kotlin.Example").get().filename()); 84 | } 85 | 86 | @Test 87 | void resolve_nestedKotlinClass() { 88 | assertTrue(resolver.resolve("ch.example.kotlin.Example$Nested").isPresent()); 89 | assertEquals("Example.kt", resolver.resolve("ch.example.kotlin.Example$Nested").get().filename()); 90 | } 91 | 92 | @Test 93 | void resolve_deeplyNestedKotlinClass() { 94 | assertTrue(resolver.resolve("ch.example.kotlin.Example$Nested$Nested").isPresent()); 95 | assertEquals("Example.kt", resolver.resolve("ch.example.kotlin.Example$Nested$Nested").get().filename()); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/test/java/ch/devcon5/sonar/plugins/mutationanalysis/testharness/LogRecordingAppender.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.testharness; 22 | 23 | import java.util.List; 24 | import java.util.concurrent.CopyOnWriteArrayList; 25 | import org.apache.logging.log4j.Level; 26 | import org.apache.logging.log4j.LogManager; 27 | import org.apache.logging.log4j.core.LogEvent; 28 | import org.apache.logging.log4j.core.LoggerContext; 29 | import org.apache.logging.log4j.core.appender.AbstractAppender; 30 | import org.apache.logging.log4j.core.config.LoggerConfig; 31 | 32 | public class LogRecordingAppender extends AbstractAppender implements AutoCloseable { 33 | 34 | private final List events = new CopyOnWriteArrayList<>(); 35 | 36 | public LogRecordingAppender() { 37 | super("listAppender", null, null); 38 | getLoggerConfig().addAppender(this, Level.ALL, null); 39 | this.start(); 40 | } 41 | 42 | @Override 43 | public void append(final LogEvent logEvent) { 44 | events.add(logEvent.toImmutable()); 45 | } 46 | 47 | public List getEvents() { 48 | return events; 49 | } 50 | 51 | private LoggerConfig getLoggerConfig() { 52 | final LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false); 53 | return loggerContext.getConfiguration().getLoggerConfig(""); 54 | } 55 | 56 | @Override 57 | public void close() throws Exception { 58 | getLoggerConfig().removeAppender(getName()); 59 | this.stop(); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/ch/devcon5/sonar/plugins/mutationanalysis/testharness/MeasureComputerTestHarness.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.testharness; 22 | 23 | 24 | import ch.devcon5.sonar.plugins.mutationanalysis.MutationAnalysisPlugin; 25 | import java.lang.reflect.Constructor; 26 | import java.lang.reflect.InvocationTargetException; 27 | import org.sonar.api.ce.measure.Component; 28 | import org.sonar.api.ce.measure.MeasureComputer; 29 | import org.sonar.api.ce.measure.test.TestComponent; 30 | import org.sonar.api.ce.measure.test.TestMeasureComputerContext; 31 | import org.sonar.api.ce.measure.test.TestMeasureComputerDefinitionContext; 32 | import org.sonar.api.ce.measure.test.TestSettings; 33 | import org.sonar.api.config.Configuration; 34 | 35 | /** 36 | * 37 | */ 38 | public class MeasureComputerTestHarness { 39 | 40 | private final T computer; 41 | private final TestConfiguration config; 42 | 43 | public MeasureComputerTestHarness(final T computer) { 44 | this(computer, new TestConfiguration()); 45 | } 46 | 47 | public MeasureComputerTestHarness(final T computer, final TestConfiguration configuration) { 48 | this.computer = computer; 49 | this.config = configuration; 50 | enableExperimentalFeatures(true); 51 | } 52 | 53 | public static MeasureComputerTestHarness createFor(Class computer) { 54 | try { 55 | final Constructor c = computer.getConstructor(Configuration.class); 56 | final TestConfiguration configuration = new TestConfiguration(); 57 | return new MeasureComputerTestHarness<>(c.newInstance(configuration), configuration); 58 | } catch (NoSuchMethodException e) { 59 | try { 60 | return new MeasureComputerTestHarness<>(computer.getConstructor().newInstance()); 61 | } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e1) { 62 | throw new RuntimeException("Could not create computer from default constructor", e1); 63 | } 64 | } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) { 65 | throw new RuntimeException("Could not create computer with configuration", e); 66 | } 67 | } 68 | 69 | public T getComputer() { 70 | return computer; 71 | } 72 | 73 | public void enableExperimentalFeatures(final boolean enabled) { 74 | config.set(MutationAnalysisPlugin.EXPERIMENTAL_FEATURE_ENABLED, enabled); 75 | } 76 | 77 | public TestConfiguration getConfig() { 78 | return config; 79 | } 80 | 81 | public TestMeasureComputerContext createMeasureContextForSourceFile(String componentKey) { 82 | return createMeasureContext(componentKey, Component.Type.FILE, "java", false); 83 | } 84 | 85 | public TestMeasureComputerContext createMeasureContextForUnitTest(String componentKey) { 86 | return createMeasureContext(componentKey, Component.Type.FILE, "java", true); 87 | 88 | } 89 | 90 | public TestMeasureComputerContext createMeasureContextForDirectory(String componentKey) { 91 | return createMeasureContext(componentKey, Component.Type.DIRECTORY, "java", false); 92 | } 93 | 94 | public TestMeasureComputerContext createMeasureContextForModule(String componentKey) { 95 | return createMeasureContext(componentKey, Component.Type.MODULE, "java", false); 96 | } 97 | 98 | public TestMeasureComputerContext createMeasureContextForProject(String componentKey) { 99 | return createMeasureContext(componentKey, Component.Type.PROJECT, "java", false); 100 | } 101 | 102 | public TestMeasureComputerContext createMeasureContext(String componentKey, Component.Type type) { 103 | return createMeasureContext(componentKey, type, "java", false); 104 | } 105 | 106 | public TestMeasureComputerContext createMeasureContext(String componentKey, Component.Type type, String language, boolean unitTest) { 107 | final TestMeasureComputerDefinitionContext context = new TestMeasureComputerDefinitionContext(); 108 | final MeasureComputer.MeasureComputerDefinition def = computer.define(context); 109 | final TestSettings settings = new TestSettings(); 110 | 111 | if (type == Component.Type.FILE) { 112 | return new TestMeasureComputerContext( 113 | new TestComponent(componentKey, type, new TestComponent.FileAttributesImpl(language, unitTest)), settings, def); 114 | } 115 | return new TestMeasureComputerContext(new TestComponent(componentKey, type, null), settings, def); 116 | } 117 | 118 | public TestMeasureComputerContext createMeasureContext(String componentKey, Component.Type type, boolean unitTest) { 119 | return createMeasureContext(componentKey, type, "java", unitTest); 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /src/test/java/ch/devcon5/sonar/plugins/mutationanalysis/testharness/SystemLocaleExtension.java: -------------------------------------------------------------------------------- 1 | package ch.devcon5.sonar.plugins.mutationanalysis.testharness; 2 | 3 | import java.util.Locale; 4 | import org.junit.jupiter.api.extension.AfterEachCallback; 5 | import org.junit.jupiter.api.extension.BeforeEachCallback; 6 | import org.junit.jupiter.api.extension.ExtensionContext; 7 | 8 | public class SystemLocaleExtension implements AfterEachCallback, BeforeEachCallback { 9 | 10 | private final Locale overrideLocale; 11 | private Locale defaultLocale = Locale.getDefault(); 12 | 13 | public SystemLocaleExtension(Locale locale) { 14 | this.overrideLocale = locale; 15 | } 16 | 17 | public static SystemLocaleExtension overrideDefault(Locale locale) { 18 | return new SystemLocaleExtension(locale); 19 | } 20 | 21 | @Override 22 | public void beforeEach(ExtensionContext context) throws Exception { 23 | defaultLocale = Locale.getDefault(); 24 | Locale.setDefault(overrideLocale); 25 | } 26 | 27 | @Override 28 | public void afterEach(ExtensionContext context) throws Exception { 29 | Locale.setDefault(defaultLocale); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/ch/devcon5/sonar/plugins/mutationanalysis/testharness/TestConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Mutation Analysis Plugin 3 | * Copyright (C) 2015-2018 DevCon5 GmbH, Switzerland 4 | * info@devcon5.ch 5 | * 6 | * This program is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 3 of the License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program; if not, write to the Free Software Foundation, 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19 | */ 20 | 21 | package ch.devcon5.sonar.plugins.mutationanalysis.testharness; 22 | 23 | import java.util.Map; 24 | import java.util.Optional; 25 | import java.util.concurrent.ConcurrentHashMap; 26 | import org.sonar.api.config.Configuration; 27 | 28 | /** 29 | * Basic Implementation to be used in tests instead of using a mock 30 | */ 31 | public class TestConfiguration implements Configuration { 32 | 33 | private final Map settings = new ConcurrentHashMap<>(); 34 | 35 | public TestConfiguration() {} 36 | 37 | public TestConfiguration set(String key, Object value) { 38 | this.settings.put(key, String.valueOf(value)); 39 | return this; 40 | } 41 | 42 | @Override 43 | public Optional get(final String key) { 44 | return Optional.ofNullable(settings.get(key)); 45 | } 46 | 47 | @Override 48 | public boolean hasKey(final String key) { 49 | return settings.containsKey(key); 50 | } 51 | 52 | @Override 53 | public String[] getStringArray(final String key) { 54 | return get(key).map(v -> v.split(",")).orElse(new String[0]); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/test/resources/ch/devcon5/sonar/plugins/mutationanalysis/report/PitestReportParserTest_XXE.xml: -------------------------------------------------------------------------------- 1 | 2 | 21 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | ]> 35 | 36 | 37 | Mutant.java 38 | ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant 39 | equals 40 | &xxe; 41 | 162 42 | org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator 43 | 5 44 | ch.devcon5.sonar.plugins.mutationanalysis.model.MutantTest.testEquals_different_false(ch.devcon5.sonar.plugins.mutationanalysis.model.MutantTest) 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/test/resources/ch/devcon5/sonar/plugins/mutationanalysis/report/PitestReportParserTest_broken.xml: -------------------------------------------------------------------------------- 1 | 2 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/test/resources/ch/devcon5/sonar/plugins/mutationanalysis/report/PitestReportParserTest_mutations.xml: -------------------------------------------------------------------------------- 1 | 2 | 21 | 22 | 23 | 24 | Mutant.java 25 | ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant 26 | equals 27 | (Ljava/lang/Object;)Z 28 | 162 29 | org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator 30 | 5 31 | ch.devcon5.sonar.plugins.mutationanalysis.model.MutantTest.testEquals_different_false(ch.devcon5.sonar.plugins.mutationanalysis.model.MutantTest) 32 | 33 | 34 | Mutant.java 35 | ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant 36 | equals 37 | (Ljava/lang/Object;)Z 38 | 172 39 | org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator 40 | 43 41 | 42 | 43 | 44 | Mutant.java 45 | ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant 46 | equals 47 | (Ljava/lang/Object;)Z 48 | 175 49 | org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator 50 | 55 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/test/resources/ch/devcon5/sonar/plugins/mutationanalysis/report/PitestReportParserTest_mutationsWithDescriptions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Mutant.java 5 | ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant 6 | equals 7 | (Ljava/lang/Object;)Z 8 | 268 9 | org.pitest.mutationtest.engine.gregor.mutators.InlineConstantMutator 10 | 8 11 | 20 12 | ch.devcon5.sonar.plugins.mutationanalysis.model.MutantTest.testEquals_same_true(ch.devcon5.sonar.plugins.mutationanalysis.model.MutantTest) 13 | Substituted 1 with 0 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/test/resources/ch/devcon5/sonar/plugins/mutationanalysis/report/PitestReportParserTest_mutationsWithNumTests.xml: -------------------------------------------------------------------------------- 1 | 2 | 21 | 22 | 23 | 24 | Mutant.java 25 | ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant 26 | equals 27 | (Ljava/lang/Object;)Z 28 | 162 29 | org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator 30 | 5 31 | ch.devcon5.sonar.plugins.mutationanalysis.model.MutantTest.testEquals_different_false(ch.devcon5.sonar.plugins.mutationanalysis.model.MutantTest) 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/test/resources/ch/devcon5/sonar/plugins/mutationanalysis/report/ReportFinderTest_mutations.xml: -------------------------------------------------------------------------------- 1 | 2 | 21 | 22 | 23 | 24 | Mutant.java 25 | ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant 26 | equals 27 | (Ljava/lang/Object;)Z 28 | 162 29 | org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator 30 | 5 31 | 32 | ch.devcon5.sonar.plugins.mutationanalysis.model.MutantTest.testEquals_different_false(org.sonar.plugins.pitest.model.MutantTest) 33 | 34 | 35 | 36 | Mutant.java 37 | ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant 38 | equals 39 | (Ljava/lang/Object;)Z 40 | 172 41 | org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator 42 | 43 43 | 44 | 45 | 46 | Mutant.java 47 | ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant 48 | equals 49 | (Ljava/lang/Object;)Z 50 | 175 51 | org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator 52 | 55 53 | 54 | 55 | 56 | Mutant.java 57 | ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant 58 | equals 59 | (Ljava/lang/Object;)Z 60 | 175 61 | org.pitest.mutationtest.engine.gregor.mutators.ArgumentPropagationMutator 62 | 55 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/test/resources/ch/devcon5/sonar/plugins/mutationanalysis/report/ReportsTest_mutations.xml: -------------------------------------------------------------------------------- 1 | 2 | 21 | 22 | 23 | 24 | Mutant.java 25 | ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant 26 | equals 27 | (Ljava/lang/Object;)Z 28 | 162 29 | org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator 30 | 5 31 | 32 | ch.devcon5.sonar.plugins.mutationanalysis.model.MutantTest.testEquals_different_false(org.sonar.plugins.pitest.model.MutantTest) 33 | 34 | 35 | 36 | Mutant.java 37 | ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant 38 | equals 39 | (Ljava/lang/Object;)Z 40 | 172 41 | org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator 42 | 43 43 | 44 | 45 | 46 | Mutant.java 47 | ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant 48 | equals 49 | (Ljava/lang/Object;)Z 50 | 175 51 | org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator 52 | 55 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/test/resources/ch/devcon5/sonar/plugins/mutationanalysis/sensors/PitestSensorTest_Java_mutations.xml: -------------------------------------------------------------------------------- 1 | 2 | 21 | 22 | 23 | 24 | Mutant.java 25 | ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant 26 | equals 27 | (Ljava/lang/Object;)Z 28 | 162 29 | org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator 30 | 5 31 | 32 | ch.devcon5.sonar.plugins.mutationanalysis.model.MutantTest.testEquals_different_false(org.sonar.plugins.pitest.model.MutantTest) 33 | 34 | 35 | 36 | Mutant.java 37 | ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant 38 | equals 39 | (Ljava/lang/Object;)Z 40 | 172 41 | org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator 42 | 43 43 | 44 | 45 | 46 | Mutant.java 47 | ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant 48 | equals 49 | (Ljava/lang/Object;)Z 50 | 175 51 | org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator_WITH_SUFFIX 52 | 55 53 | 54 | 55 | 56 | Mutant.java 57 | ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant 58 | equals 59 | (Ljava/lang/Object;)Z 60 | 160 61 | org.pitest.mutationtest.engine.gregor.mutators.ConditionalsBoundaryMutator 62 | 5 63 | 64 | ch.devcon5.sonar.plugins.mutationanalysis.model.MutantTest.testEquals_different_false(org.sonar.plugins.pitest.model.MutantTest) 65 | 66 | 67 | 68 | Mutant.java 69 | ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant 70 | equals 71 | (Ljava/lang/Object;)Z 72 | 164 73 | org.pitest.mutationtest.engine.gregor.mutators.ConditionalsBoundaryMutator_WITH_SUFFIX 74 | 5 75 | 76 | ch.devcon5.sonar.plugins.mutationanalysis.model.MutantTest.testEquals_different_false(org.sonar.plugins.pitest.model.MutantTest) 77 | 78 | 79 | 80 | Mutant.java 81 | ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant 82 | equals 83 | (Ljava/lang/Object;)Z 84 | 175 85 | org.pitest.mutationtest.engine.gregor.mutators.ArgumentPropagationMutator 86 | 55 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /src/test/resources/ch/devcon5/sonar/plugins/mutationanalysis/sensors/PitestSensorTest_KotlinJava_mutations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | KotlinExample.kt 5 | ch.devcon5.sonar.plugins.mutationanalysis.KotlinExample 6 | helloWorld 7 | ()V 8 | 28 9 | org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator 10 | 8 11 | 0 12 | 13 | negated conditional 14 | 15 | 16 | Mutant.java 17 | ch.devcon5.sonar.plugins.mutationanalysis.JavaExample 18 | equals 19 | (Ljava/lang/Object;)Z 20 | 162 21 | org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator 22 | 5 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/test/resources/ch/devcon5/sonar/plugins/mutationanalysis/sensors/PitestSensorTest_Kotlin_mutations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | KotlinExample.ktch.devcon5.sonar.plugins.mutationanalysis.KotlinExamplehelloWorld()V28org.pitest.mutationtest.engine.gregor.mutators.InlineConstantMutator40Substituted 2 with 3 4 | KotlinExample.ktch.devcon5.sonar.plugins.mutationanalysis.KotlinExamplehelloWorld()V28org.pitest.mutationtest.engine.gregor.mutators.MathMutator60Replaced long modulus with multiplication 5 | KotlinExample.ktch.devcon5.sonar.plugins.mutationanalysis.KotlinExamplehelloWorld()V28org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator80negated conditional 6 | KotlinExample.ktch.devcon5.sonar.plugins.mutationanalysis.KotlinExamplehelloWorld()V28org.pitest.mutationtest.engine.gregor.mutators.NonVoidMethodCallMutator30removed call to java/lang/System::currentTimeMillis 7 | KotlinExample.ktch.devcon5.sonar.plugins.mutationanalysis.KotlinExamplehelloWorld()V28org.pitest.mutationtest.engine.gregor.mutators.RemoveConditionalMutator_EQUAL_ELSE80removed conditional - replaced equality check with false 8 | KotlinExample.ktch.devcon5.sonar.plugins.mutationanalysis.KotlinExamplehelloWorld()V28org.pitest.mutationtest.engine.gregor.mutators.RemoveConditionalMutator_EQUAL_IF80removed conditional - replaced equality check with true 9 | KotlinExample.ktch.devcon5.sonar.plugins.mutationanalysis.KotlinExamplehelloWorld()V29org.pitest.mutationtest.engine.gregor.mutators.VoidMethodCallMutator131removed call to java/io/PrintStream::print 10 | KotlinExample.ktch.devcon5.sonar.plugins.mutationanalysis.KotlinExamplemain()V34org.pitest.mutationtest.engine.gregor.mutators.ConstructorCallMutator52removed call to ch/devcon5/sonar/plugins/mutationanalysis/KotlinExample::<init> 11 | KotlinExample.ktch.devcon5.sonar.plugins.mutationanalysis.KotlinExamplemain()V34org.pitest.mutationtest.engine.gregor.mutators.VoidMethodCallMutator62removed call to ch/devcon5/sonar/plugins/mutationanalysis/KotlinExample::helloWorld 12 | 13 | -------------------------------------------------------------------------------- /src/test/resources/ch/devcon5/sonar/plugins/mutationanalysis/sensors/ReportCollectorTest_mutations.xml: -------------------------------------------------------------------------------- 1 | 2 | 21 | 22 | 23 | 24 | Mutant.java 25 | ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant 26 | equals 27 | (Ljava/lang/Object;)Z 28 | 162 29 | org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator 30 | 5 31 | 32 | ch.devcon5.sonar.plugins.mutationanalysis.model.MutantTest.testEquals_different_false(org.sonar.plugins.pitest.model.MutantTest) 33 | 34 | 35 | 36 | Mutant.java 37 | ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant 38 | equals 39 | (Ljava/lang/Object;)Z 40 | 172 41 | org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator 42 | 43 43 | 44 | 45 | 46 | Mutant.java 47 | ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant 48 | equals 49 | (Ljava/lang/Object;)Z 50 | 175 51 | org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator_WITH_SUFFIX 52 | 55 53 | 54 | 55 | 56 | Mutant.java 57 | ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant 58 | equals 59 | (Ljava/lang/Object;)Z 60 | 160 61 | org.pitest.mutationtest.engine.gregor.mutators.ConditionalsBoundaryMutator 62 | 5 63 | 64 | ch.devcon5.sonar.plugins.mutationanalysis.model.MutantTest.testEquals_different_false(org.sonar.plugins.pitest.model.MutantTest) 65 | 66 | 67 | 68 | Mutant.java 69 | ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant 70 | equals 71 | (Ljava/lang/Object;)Z 72 | 164 73 | org.pitest.mutationtest.engine.gregor.mutators.ConditionalsBoundaryMutator_WITH_SUFFIX 74 | 5 75 | 76 | ch.devcon5.sonar.plugins.mutationanalysis.model.MutantTest.testEquals_different_false(org.sonar.plugins.pitest.model.MutantTest) 77 | 78 | 79 | 80 | Mutant.java 81 | ch.devcon5.sonar.plugins.mutationanalysis.model.Mutant 82 | equals 83 | (Ljava/lang/Object;)Z 84 | 175 85 | org.pitest.mutationtest.engine.gregor.mutators.ArgumentPropagationMutator 86 | 55 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /src/test/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | --------------------------------------------------------------------------------