├── .gitignore ├── .sonarsource.properties ├── .travis.yml ├── README.md ├── pom.xml └── src ├── main ├── java │ └── org │ │ └── sonar │ │ └── plugins │ │ └── pitest │ │ ├── PitestComputer.java │ │ ├── PitestConstants.java │ │ ├── PitestMetrics.java │ │ ├── PitestPlugin.java │ │ ├── PitestRulesDefinition.java │ │ ├── domain │ │ ├── Mutant.java │ │ ├── MutantLocation.java │ │ ├── MutantStatus.java │ │ ├── Mutator.java │ │ ├── MutatorConstants.java │ │ └── package-info.java │ │ ├── package-info.java │ │ └── scanner │ │ ├── PitestSensor.java │ │ ├── ProjectReport.java │ │ ├── SourceFileReport.java │ │ ├── XmlReportFinder.java │ │ ├── XmlReportParser.java │ │ └── package-info.java └── resources │ ├── org │ └── sonar │ │ └── plugins │ │ └── pitest │ │ └── pitest_dashboard_widget.html.erb │ └── static │ └── pitest.css └── test ├── java └── org │ └── sonar │ └── plugins │ └── pitest │ ├── PitestComputerTest.java │ ├── PitestMetricsTest.java │ ├── PitestPluginTest.java │ ├── PitestRulesDefinitionTest.java │ ├── domain │ ├── MutantLocationTest.java │ ├── MutantStatusTest.java │ ├── MutantTest.java │ ├── MutatorTest.java │ ├── TestMutantBuilder.java │ └── TestMutantLocationBuilder.java │ └── scanner │ ├── PitestSensorTest.java │ ├── ProjectReportTest.java │ ├── SourceFileReportTest.java │ ├── XmlReportFinderTest.java │ └── XmlReportParserTest.java └── resources ├── Maze.kt ├── fake_libs ├── compile.fake.jar ├── runtime.fake.jar ├── system.fake.jar └── test.fake.jar ├── mutations-elements-out-of-order.xml ├── mutations-invalid-format-line-number.xml ├── mutations-invalid-format.xml ├── mutations-kotlin.xml ├── mutations.xml ├── pit-reports ├── 201710212128 │ ├── index.html │ ├── mutations.xml │ ├── org.sonar.plugins.pitest.domain │ │ ├── Mutant.java.html │ │ ├── MutantStatus.java.html │ │ ├── Mutator.java.html │ │ └── index.html │ ├── org.sonar.plugins.pitest.scanner │ │ ├── PitestSensor.java.html │ │ ├── ProjectReport.java.html │ │ ├── SourceFileReport.java.html │ │ ├── XmlReportFinder.java.html │ │ ├── XmlReportParser.java.html │ │ └── index.html │ └── org.sonar.plugins.pitest │ │ ├── PitestComputer.java.html │ │ ├── PitestMetrics.java.html │ │ ├── PitestRulesDefinition.java.html │ │ └── index.html └── 201710281222 │ ├── index.html │ ├── mutations-small.xml │ ├── mutations.xml │ ├── org.sonar.plugins.pitest.domain │ ├── Mutant.java.html │ ├── MutantStatus.java.html │ ├── Mutator.java.html │ └── index.html │ ├── org.sonar.plugins.pitest.scanner │ ├── PitestSensor.java.html │ ├── ProjectReport.java.html │ ├── SourceFileReport.java.html │ ├── XmlReportFinder.java.html │ ├── XmlReportParser.java.html │ └── index.html │ └── org.sonar.plugins.pitest │ ├── PitestComputer.java.html │ ├── PitestMetrics.java.html │ ├── PitestRulesDefinition.java.html │ └── index.html ├── pitest-sensor-tests ├── Maze.kt └── com │ └── foo │ ├── Bar.java │ ├── KilledClazz.java │ ├── MemoryErrorClazz.java │ ├── NoCoverageClazz.java │ ├── SurvivedClazz.java │ └── UnknownClazz.java ├── sonar-pitest-plugin.jar ├── test-pit-reports-1 ├── 123 │ ├── misnamed-mutations.xml │ └── mutations.xml └── fluff ├── test-pit-reports-2 ├── 123 │ └── mutations.xml └── 124 │ └── mutations.xml └── xml-report-parser-test ├── mutations-unordered.xml └── mutations.xml /.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 | /.DS_Store 15 | -------------------------------------------------------------------------------- /.sonarsource.properties: -------------------------------------------------------------------------------- 1 | # do not display status of this project in wallboards of SonarSource offices 2 | wallboard.teamAtSonarSource=other 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | sudo: false 3 | install: true 4 | addons: 5 | sonarcloud: 6 | organization: vinodanandan-github 7 | token: 8 | secure: $SONAR_TOKEN_PITEST 9 | 10 | jdk: 11 | - oraclejdk8 12 | script: 13 | - mvn clean package org.pitest:pitest-maven:mutationCoverage sonar:sonar 14 | 15 | cache: 16 | directories: 17 | - $HOME/.m2/repository 18 | - $HOME/.sonar/cache 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Sonar Pitest Plugin 2 | =================== 3 | 4 | [![Build Status](https://travis-ci.org/VinodAnandan/sonar-pitest.svg?branch=master)](https://travis-ci.org/VinodAnandan/sonar-pitest) 5 | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/quality_gate?project=org.sonarsource.pitest%3Asonar-pitest-plugin)](https://sonarcloud.io/dashboard?id=org.sonarsource.pitest%3Asonar-pitest-plugin) 6 | 7 | 8 | Compatibility Matrix 9 | -------------------- 10 | | Sonarqube version | Pitest plugin version | 11 | |-------------------|-----------------------| 12 | | Sonarqube 7.1 | sonar pitest 1.0-SNAPSHOT | 13 | | Sonarqube 6.7 (LTS) | sonar pitest 0.9 ([download](https://github.com/VinodAnandan/sonar-pitest/releases/download/v0.9/sonar-pitest-plugin-0.9.jar)) | 14 | | Sonarqube 6.5 | sonar pitest 0.8 ([download](https://github.com/VinodAnandan/sonar-pitest/releases/download/0.8/sonar-pitest-plugin-0.8.jar)) | 15 | | Sonarqube 5.6.X | sonar pitest 0.7 ([download](https://github.com/VinodAnandan/sonar-pitest/releases/download/0.7/sonar-pitest-plugin-0.7.jar)) | 16 | 17 | 18 | 19 | Contributing 20 | ------------ 21 | ### Pull Request (PR) 22 | 23 | To submit a contribution, create a pull request for this repository. Please make sure that you follow the SonarQube Developer Guidelines [code style](https://github.com/SonarSource/sonar-developer-toolset#code-style) and all tests are passing. 24 | 25 | Description / Features 26 | ---------------------- 27 | PIT is a mutation testing tool for java. You can check out the official pitest web site for more details on mutation testing and PIT. 28 | Long story short, mutation testing is a very smart way to check the relevance of unit tests. The main idea is to alter the tested code and check that at least one unit test fails. An alteration of the code is called a "mutant". A mutant has "survived" the tests if there is no test failure. A mutant is "killed" if there is a test failure when the tests are executed on the mutated code. 29 | 30 | The goal of this plugin is to bring PIT results to SonarQube. "Survived mutants" are seen as SonarQube issues. "Killed mutants" show as a coverage measure in the class containing the mutant. If code is not covered by any test, that code will not be mutated; prior test coverage is a precondition for mutation testing. Finally, if the percentage of "Survived mutants" in a source file exceeds a configurable threshold, the plugin creates a SonarQube issue on the source file. 31 | 32 | 33 | Usage 34 | ----- 35 | ### Notes on mutation testing 36 | Mutation testing should only be executed on true unit tests. Do not try to use it on tests accessing resources such as a database or filesystem, as the mutation may can unexpected consequences. 37 | Mutation testing can be computationally expensive. Henry Coles, creator of PIT, provides the following tips to manage PIT execution time: 38 | * Target only specific portions of your codebase (using the class filters) 39 | * Limit the mutation operators used 40 | * Limit the number of mutations per class 41 | * Tweak the number of threads 42 | 43 | ### Configuration 44 | The sonar-pitest plugin exposes two rules: 45 | * "Survived mutant", which creates an issue (of TYPE BUG and SEVERITY MAJOR) whenever Mutated code does not result in a test failure 46 | * "Insufficient Mutation Coverage", which creates an issue (of TYPE BUG and SEVERITY MAJOR) whenever the percentage of Survived mutants exceeds a configurable threshold (default: 65%) 47 | 48 | Both rules are inactive by default 49 | 50 | ### Project build setup 51 | **PIT needs to be launched before SonarQube** 52 | You can launch PIT using the PIT maven plugin or the command line runner. PIT execution must be done before SonarQube analysis. You also need to specify the "reuseReport" mode of the PIT SonarQube plugin. 53 | Pit needs to be configured in order to generate XML reports. Be aware that PIT default behavior is to generate HTML reports. Below a simple configuration example for maven : 54 | 55 | 56 | 57 | org.pitest 58 | pitest-maven 59 | LATEST 60 | 61 | 62 | com.acme.tools.commons* 63 | 64 | 65 | com.acme.tools.commons* 66 | 67 | 68 | XML 69 | 70 | 71 | 72 | 73 | inScopeClasses and targetClasses parameters indicated the classes of the system under test where mutations can be performed. In the example above, all the classes from the com.acme.tools.commons package and sub packages may be altered. 74 | Once configured in the maven pom file, you need to run PITusing the following command: 75 | 76 | mvn org.pitest:pitest-maven:mutationCoverage 77 | 78 | Note : Of course, all the configuration options are clearly explained in the official pitest documentation. 79 | Last but not least, you need to run a SonarQube analysis with the PIT plugin activated in "reuseReport" mode (the default). The following command would do the job: 80 | 81 | mvn sonar:sonar 82 | 83 | By default SonarQube will search the latest PIT report in "target/pit-reports". You can specify another location using property "sonar.pitest.reportsDirectory". 84 | You will find below the list of all the available configuration parameters. 85 | 86 | ### Basic configuration properties 87 | Below the exhaustive list of configuration properties of the SonarQube pitest plugin: 88 | 89 | | Name | Key | Default value | Description | 90 | |------|-----|---------------|-------------| 91 | | Pitest activation mode | sonar.pitest.mode | reuseReport | Possible values : 'skip' and 'reuseReport' | 92 | | Path to the pitest reports | sonar.pitest.reportsDirectory | target/pit-reports |Path used to locate pitest xml reports. Pitest creates a new subfolder "timestamp" at each shot. The SonarQube plugin will explore these subfolders and find the newest xml reports generated. | 93 | 94 | You can check out the quickstart section of the official pitest web site for detailed instructions. 95 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | org.sonarsource.parent 8 | parent 9 | 41 10 | 11 | 12 | org.sonarsource.pitest 13 | sonar-pitest-plugin 14 | 1.0-SNAPSHOT 15 | sonar-plugin 16 | 17 | Sonar Pitest Plugin 18 | Plugin for the mutation testing tool Pitest 19 | https://github.com/SonarQubeCommunity/sonar-pitest 20 | 2009 21 | 22 | Vinod Anandan 23 | https://github.com/VinodAnandan 24 | 25 | 26 | 27 | GNU LGPL 3 28 | http://www.gnu.org/licenses/lgpl.txt 29 | repo 30 | 31 | 32 | 33 | 34 | 35 | VinodAnandan 36 | Vinod Anandan 37 | vinod@owasp.org 38 | OWASP 39 | 40 | 41 | bradflood 42 | Brad Flood 43 | bflood@keyholesoftware.com 44 | Keyhole Software 45 | -5 46 | 47 | 48 | 49 | 50 | Alexandre Victoor 51 | 52 | 53 | 54 | 55 | scm:git:git@github.com:SonarQubeCommunity/sonar-pitest.git 56 | scm:git:git@github.com:SonarQubeCommunity/sonar-pitest.git 57 | https://github.com/SonarQubeCommunity/sonar-pitest 58 | HEAD 59 | 60 | 61 | 62 | GitHub Issues 63 | https://github.com/SonarQubeCommunity/sonar-pitest/issues 64 | 65 | 66 | 67 | travis-ci 68 | https://travis-ci.org/VinodAnandan/sonar-pitest 69 | 70 | 71 | 72 | 7.1 73 | 1.4.0 74 | 75 | 0.8.1 76 | 4.12 77 | 2.18.3 78 | 3.10.0 79 | 11.0.2 80 | 81 | 82 | reuseReport 83 | target/surefire-reports 84 | 85 | 86 | ${project.organization.name} 87 | Sonar Pitest Plugin 88 | 2009-2018 89 | vinod@owasp.org 90 | 91 | 92 | 93 | 94 | org.sonarsource.sonarqube 95 | sonar-plugin-api 96 | ${sonar.buildVersion} 97 | provided 98 | 99 | 100 | 101 | com.google.guava 102 | guava 103 | ${guava.version} 104 | 105 | 106 | 107 | 108 | org.sonarsource.sonarqube 109 | sonar-testing-harness 110 | ${sonar.buildVersion} 111 | test 112 | 113 | 114 | junit 115 | junit 116 | ${junit.version} 117 | test 118 | 119 | 120 | org.mockito 121 | mockito-core 122 | ${mockito-core.version} 123 | test 124 | 125 | 126 | org.assertj 127 | assertj-core 128 | ${assertj-core.version} 129 | test 130 | 131 | 132 | 133 | 134 | 135 | 136 | maven-surefire-plugin 137 | 138 | @{argLine} 139 | 140 | 141 | 142 | org.jacoco 143 | jacoco-maven-plugin 144 | ${jacoco.version} 145 | 146 | 147 | *_javassist_* 148 | 149 | 150 | 151 | 152 | default-prepare-agent 153 | 154 | prepare-agent 155 | 156 | 157 | 158 | 159 | 160 | org.sonarsource.sonar-packaging-maven-plugin 161 | sonar-packaging-maven-plugin 162 | true 163 | 164 | 6.0 165 | org.sonar.plugins.pitest.PitestPlugin 166 | Pitest 167 | 168 | 169 | 170 | 172 | org.codehaus.mojo 173 | native2ascii-maven-plugin 174 | 175 | 176 | 177 | native2ascii 178 | 179 | 180 | UTF8 181 | ${basedir}/src/main/resources 182 | ${project.build.outputDirectory} 183 | 184 | **/*.properties 185 | 186 | 187 | 188 | 189 | 190 | 191 | org.pitest 192 | pitest-maven 193 | ${pitest.version} 194 | 195 | 196 | org.sonar.plugins.pitest* 197 | 198 | 199 | org.sonar.plugins.pitest* 200 | 201 | 202 | XML 203 | HTML 204 | 205 | 206 | 207 | 208 | 209 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/pitest/PitestComputer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Pitest Plugin 3 | * Copyright (C) 2009-2018 Vinod Anandan 4 | * vinod@owasp.org 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 org.sonar.plugins.pitest; 21 | 22 | import java.util.stream.Stream; 23 | import org.sonar.api.ExtensionPoint; 24 | import org.sonar.api.ce.ComputeEngineSide; 25 | import org.sonar.api.ce.measure.Measure; 26 | import org.sonar.api.ce.measure.MeasureComputer; 27 | import org.sonar.api.utils.log.Logger; 28 | import org.sonar.api.utils.log.Loggers; 29 | 30 | /** 31 | * MeasureComputer that processes the aggregated quantitative metric for a component from all the quantitative metrics 32 | * of its children. 33 | */ 34 | @ComputeEngineSide 35 | @ExtensionPoint 36 | public class PitestComputer implements MeasureComputer { 37 | 38 | private static final Logger log = Loggers.get(PitestComputer.class); 39 | 40 | private static final String[] measureKeys = {PitestMetrics.MUTATIONS_NOT_COVERED_KEY, 41 | PitestMetrics.MUTATIONS_GENERATED_KEY, 42 | PitestMetrics.MUTATIONS_KILLED_KEY, 43 | PitestMetrics.MUTATIONS_SURVIVED_KEY, 44 | PitestMetrics.MUTATIONS_ERROR_KEY, 45 | PitestMetrics.MUTATIONS_UNKNOWN_KEY 46 | }; 47 | 48 | private static final String[] derivedKeys = {PitestMetrics.MUTATIONS_DATA_KEY, 49 | PitestMetrics.MUTATIONS_KILLED_PERCENT_KEY}; 50 | 51 | @Override 52 | public MeasureComputerDefinition define(final MeasureComputerDefinitionContext defContext) { 53 | return defContext.newDefinitionBuilder() 54 | .setOutputMetrics(Stream.of(measureKeys, derivedKeys).flatMap(Stream::of).toArray(String[]::new)) 55 | .build(); 56 | } 57 | 58 | @Override 59 | public void compute(final MeasureComputerContext context) { 60 | for (String metricKey : measureKeys) { 61 | if (context.getMeasure(metricKey) == null) { 62 | computeChildrenMeasurements(context, metricKey); 63 | } 64 | } 65 | computeDerived(context) ; 66 | } 67 | 68 | private void computeChildrenMeasurements(final MeasureComputerContext context, String key) { 69 | if (context.getMeasure(key) == null) { 70 | int sum = 0; 71 | for (Measure m : context.getChildrenMeasures(key)) { 72 | try { 73 | sum += m.getIntValue(); 74 | } catch (IllegalStateException e) { 75 | log.error("Failed to compute value for {}.", key, e); 76 | } 77 | } 78 | context.addMeasure(key, sum); 79 | } 80 | 81 | } 82 | private void computeDerived(final MeasureComputerContext context) { 83 | final Measure mutationsTotal = context.getMeasure(PitestMetrics.MUTATIONS_GENERATED_KEY); 84 | if (mutationsTotal != null) { 85 | final Integer elements = mutationsTotal.getIntValue(); 86 | final Measure killed = context.getMeasure(PitestMetrics.MUTATIONS_KILLED_KEY); 87 | if (elements > 0 && killed != null) { 88 | final Integer coveredElements = killed.getIntValue(); 89 | final Double coverage = 100.0 * coveredElements / elements; 90 | context.addMeasure(PitestMetrics.MUTATIONS_KILLED_PERCENT_KEY, coverage); 91 | } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/pitest/PitestConstants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Pitest Plugin 3 | * Copyright (C) 2009-2018 Vinod Anandan 4 | * vinod@owasp.org 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 org.sonar.plugins.pitest; 21 | 22 | /** 23 | * Constants for the PIT plugins 24 | * There is a constant for each configuration key. 25 | * Most of these configuration keys, and the javadoc comments are 26 | * strongly inspired by the maven PIT plugin 27 | * 28 | */ 29 | public final class PitestConstants { 30 | 31 | private PitestConstants() { 32 | } 33 | 34 | public static final String REPOSITORY_KEY = "pitest"; 35 | public static final String REPOSITORY_NAME = "Pitest"; 36 | 37 | public static final String SURVIVED_MUTANT_RULE_KEY = "pitest.survived.mutant"; 38 | 39 | public static final String INSUFFICIENT_MUTATION_COVERAGE_RULE_KEY = "pitest.insufficient.mutation.coverage"; 40 | 41 | public static final String COVERAGE_RATIO_PARAM = "minimumMutationCoverageRatio"; 42 | 43 | public static final String MODE_KEY = "sonar.pitest.mode"; 44 | 45 | public static final String MODE_SKIP = "skip"; 46 | 47 | public static final String MODE_REUSE_REPORT = "reuseReport"; 48 | 49 | public static final String REPORT_DIRECTORY_KEY = "sonar.pitest.reportsDirectory"; 50 | 51 | public static final String REPORT_DIRECTORY_DEF = "target/pit-reports"; 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/pitest/PitestMetrics.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Pitest Plugin 3 | * Copyright (C) 2009-2018 Vinod Anandan 4 | * vinod@owasp.org 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 org.sonar.plugins.pitest; 21 | 22 | import java.io.Serializable; 23 | import java.util.LinkedList; 24 | import java.util.List; 25 | import java.util.NoSuchElementException; 26 | import org.sonar.api.measures.Metric; 27 | import org.sonar.api.measures.Metrics; 28 | 29 | /** 30 | * Metrics for the sonar pitest plugin. 31 | * 32 | */ 33 | public class PitestMetrics implements Metrics { 34 | 35 | public static final String PITEST_DOMAIN = "Mutation analysis"; 36 | 37 | public static final String MUTATIONS_NOT_COVERED_KEY = "pitest_mutations_noCoverage"; 38 | public static final Metric MUTATIONS_NOT_COVERED = new Metric.Builder(MUTATIONS_NOT_COVERED_KEY, "Non Covered Mutations", Metric.ValueType.INT) 39 | .setDescription("Number of mutations not covered by any test.") 40 | .setDirection(Metric.DIRECTION_WORST) 41 | .setQualitative(false) 42 | .setDomain(PITEST_DOMAIN) 43 | .create(); 44 | 45 | public static final String MUTATIONS_GENERATED_KEY = "pitest_mutations_total"; 46 | public static final Metric MUTATIONS_GENERATED = new Metric.Builder(MUTATIONS_GENERATED_KEY, "Total Mutations", Metric.ValueType.INT) 47 | .setDescription("Total number of mutations generated") 48 | .setDirection(Metric.DIRECTION_BETTER) 49 | .setQualitative(false) 50 | .setDomain(PITEST_DOMAIN) 51 | .create(); 52 | 53 | public static final String MUTATIONS_KILLED_KEY = "pitest_mutations_killed"; 54 | public static final Metric MUTATIONS_KILLED = new Metric.Builder(MUTATIONS_KILLED_KEY, "Killed Mutations", Metric.ValueType.INT) 55 | .setDescription("Number of mutations killed by a test.") 56 | .setDirection(Metric.DIRECTION_BETTER) 57 | .setQualitative(false) 58 | .setDomain(PITEST_DOMAIN) 59 | .create(); 60 | 61 | public static final String MUTATIONS_SURVIVED_KEY = "pitest_mutations_survived"; 62 | public static final Metric MUTATIONS_SURVIVED = new Metric.Builder(MUTATIONS_SURVIVED_KEY, "Survived Mutations", Metric.ValueType.INT) 63 | .setDescription("Number of mutations survived") 64 | .setDirection(Metric.DIRECTION_WORST) 65 | .setQualitative(false) 66 | .setDomain(PITEST_DOMAIN) 67 | .create(); 68 | 69 | public static final String MUTATIONS_ERROR_KEY = "pitest_mutations_error"; 70 | public static final Metric MUTATIONS_ERROR = new Metric.Builder(MUTATIONS_ERROR_KEY, "Error Mutations", Metric.ValueType.INT) 71 | .setDescription("Number of mutations that caused an error") 72 | .setDirection(Metric.DIRECTION_WORST) 73 | .setQualitative(false) 74 | .setDomain(PITEST_DOMAIN) 75 | .create(); 76 | 77 | public static final String MUTATIONS_UNKNOWN_KEY = "pitest_mutations_unknown"; 78 | public static final Metric MUTATIONS_UNKNOWN = new Metric.Builder(MUTATIONS_UNKNOWN_KEY, "Mutations with unknown status", Metric.ValueType.INT) 79 | .setDescription("Number of mutations for which status is unknown") 80 | .setDirection(Metric.DIRECTION_WORST) 81 | .setQualitative(false) 82 | .setDomain(PITEST_DOMAIN) 83 | .create(); 84 | 85 | public static final String MUTATIONS_DATA_KEY = "pitest_mutations_data"; // needed? 86 | public static final Metric MUTATIONS_DATA = new Metric.Builder(MUTATIONS_DATA_KEY, "Mutations Data", Metric.ValueType.DATA) 87 | .setDescription("Mutations Data") 88 | .setDirection(Metric.DIRECTION_NONE) 89 | .setQualitative(true) 90 | .setDomain(PITEST_DOMAIN) 91 | .create(); 92 | 93 | public static final String MUTATIONS_KILLED_PERCENT_KEY = "pitest_mutations_killed_percent"; 94 | public static final Metric MUTATIONS_KILLED_RATIO = new Metric.Builder(MUTATIONS_KILLED_PERCENT_KEY, "Mutations Coverage Ratio", Metric.ValueType.PERCENT) 95 | .setDescription("Ratio of mutations found by tests") 96 | .setDirection(Metric.DIRECTION_BETTER) 97 | .setQualitative(true) 98 | .setDomain(PITEST_DOMAIN) 99 | .setBestValue(100d) 100 | .setWorstValue(0d) 101 | .create(); 102 | 103 | private static final List METRICS; 104 | 105 | static { 106 | METRICS = new LinkedList<>(); 107 | METRICS.add(MUTATIONS_NOT_COVERED); 108 | METRICS.add(MUTATIONS_GENERATED); 109 | METRICS.add(MUTATIONS_KILLED); 110 | METRICS.add(MUTATIONS_SURVIVED); 111 | METRICS.add(MUTATIONS_ERROR); 112 | METRICS.add(MUTATIONS_UNKNOWN); 113 | METRICS.add(MUTATIONS_DATA); 114 | METRICS.add(MUTATIONS_KILLED_RATIO); 115 | } 116 | 117 | @Override 118 | public List getMetrics() { 119 | return METRICS; 120 | } 121 | 122 | public static Metric getMetric(final String key) { 123 | return METRICS.stream().filter(metric -> metric != null && metric.getKey().equals(key)).findFirst().orElseThrow(NoSuchElementException::new); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/pitest/PitestPlugin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Pitest Plugin 3 | * Copyright (C) 2009-2018 Vinod Anandan 4 | * vinod@owasp.org 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 org.sonar.plugins.pitest; 21 | 22 | import com.google.common.collect.ImmutableList; 23 | import org.sonar.api.Plugin; 24 | import org.sonar.api.config.PropertyDefinition; 25 | import org.sonar.api.resources.Qualifiers; 26 | import org.sonar.plugins.pitest.scanner.PitestSensor; 27 | import org.sonar.plugins.pitest.scanner.ProjectReport; 28 | import org.sonar.plugins.pitest.scanner.XmlReportFinder; 29 | import org.sonar.plugins.pitest.scanner.XmlReportParser; 30 | 31 | import static org.sonar.plugins.pitest.PitestConstants.MODE_KEY; 32 | import static org.sonar.plugins.pitest.PitestConstants.MODE_REUSE_REPORT; 33 | import static org.sonar.plugins.pitest.PitestConstants.REPORT_DIRECTORY_DEF; 34 | import static org.sonar.plugins.pitest.PitestConstants.REPORT_DIRECTORY_KEY; 35 | 36 | /** 37 | * This class is the entry point for all PIT extensions 38 | */ 39 | public final class PitestPlugin implements Plugin { 40 | 41 | @Override 42 | public void define(Context context) { 43 | 44 | ImmutableList.Builder builder = ImmutableList.builder(); 45 | 46 | builder.add( 47 | PropertyDefinition.builder(MODE_KEY) 48 | .defaultValue(MODE_REUSE_REPORT) 49 | .name("PIT activation mode") 50 | .description("Possible values: 'reuseReport' and 'skip'") 51 | .onQualifiers(Qualifiers.PROJECT) 52 | .build(), 53 | PropertyDefinition.builder(REPORT_DIRECTORY_KEY) 54 | .defaultValue(REPORT_DIRECTORY_DEF) 55 | .name("Output directory for the PIT reports") 56 | .description("This property is needed when the 'reuseReport' mode is activated and the reports are not " + 57 | "located in the default directory (i.e. target/pit-reports)") 58 | .onQualifiers(Qualifiers.PROJECT) 59 | .build(), 60 | 61 | PitestRulesDefinition.class, 62 | PitestMetrics.class, 63 | PitestSensor.class, 64 | PitestComputer.class, 65 | ProjectReport.class, 66 | XmlReportParser.class, 67 | XmlReportFinder.class); 68 | 69 | context.addExtensions(builder.build()); 70 | 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/pitest/PitestRulesDefinition.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Pitest Plugin 3 | * Copyright (C) 2009-2018 Vinod Anandan 4 | * vinod@owasp.org 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 org.sonar.plugins.pitest; 21 | 22 | import org.sonar.api.rule.RuleStatus; 23 | import org.sonar.api.rule.Severity; 24 | import org.sonar.api.rules.RuleType; 25 | import org.sonar.api.server.rule.RulesDefinition; 26 | 27 | import static org.sonar.plugins.pitest.PitestConstants.COVERAGE_RATIO_PARAM; 28 | import static org.sonar.plugins.pitest.PitestConstants.INSUFFICIENT_MUTATION_COVERAGE_RULE_KEY; 29 | import static org.sonar.plugins.pitest.PitestConstants.REPOSITORY_KEY; 30 | import static org.sonar.plugins.pitest.PitestConstants.REPOSITORY_NAME; 31 | import static org.sonar.plugins.pitest.PitestConstants.SURVIVED_MUTANT_RULE_KEY; 32 | 33 | public class PitestRulesDefinition implements RulesDefinition { 34 | 35 | public static final String TAG_TEST_QUALITY = "test-quality"; 36 | public static final String TAG_TEST_COVERAGE = "test-coverage"; 37 | 38 | @Override 39 | public void define(Context context) { 40 | NewRepository repository = context 41 | .createRepository(REPOSITORY_KEY, "java") 42 | .setName(REPOSITORY_NAME); 43 | 44 | /* 45 | * Rule: Survived Mutant 46 | * Current thinking is that a survived mutant is at least as severe as missing code coverage, probably more severe. 47 | * Reason for more severe: a test covers this code, so there may be a false sense of security regarding test coverage 48 | */ 49 | repository.createRule(SURVIVED_MUTANT_RULE_KEY) 50 | .setName("Survived mutant") 51 | .setHtmlDescription( 52 | "An issue is created when an existing test fails to identify a mutation in the code. For more information, review the PIT documentation") 53 | .setStatus(RuleStatus.READY) 54 | .setSeverity(Severity.MAJOR) 55 | .setType(RuleType.BUG) 56 | .setTags(TAG_TEST_QUALITY) 57 | .setActivatedByDefault(false); 58 | 59 | /* 60 | * Rule: Insufficient Mutation coverage 61 | */ 62 | NewRule insufficientMutationCoverageRule = repository.createRule(INSUFFICIENT_MUTATION_COVERAGE_RULE_KEY) 63 | .setName("Insufficient mutation coverage") 64 | .setHtmlDescription( 65 | "An issue is created on a file as soon as the mutation coverage on this file is less than the required threshold. It gives the number of mutations to be covered in order to reach the required threshold.") 66 | .setStatus(RuleStatus.READY) 67 | .setSeverity(Severity.MAJOR) 68 | .setType(RuleType.BUG) 69 | .setTags(TAG_TEST_QUALITY, TAG_TEST_COVERAGE) 70 | .setActivatedByDefault(false); 71 | 72 | insufficientMutationCoverageRule 73 | .createParam(COVERAGE_RATIO_PARAM) 74 | .setDefaultValue("65") 75 | .setDescription("The minimum required mutation coverage ratio"); 76 | 77 | repository.done(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/pitest/domain/Mutant.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Pitest Plugin 3 | * Copyright (C) 2009-2018 Vinod Anandan 4 | * vinod@owasp.org 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 org.sonar.plugins.pitest.domain; 21 | 22 | import javax.annotation.Nullable; 23 | 24 | /** 25 | * Mutation information from the pitest report. 26 | * 27 | */ 28 | public final class Mutant { 29 | 30 | public final boolean detected; 31 | public final MutantStatus mutantStatus; 32 | public final MutantLocation mutantLocation; 33 | public final Mutator mutator; 34 | public final int index; 35 | public final String description; 36 | public final String killingTest; 37 | 38 | public Mutant(boolean detected, MutantStatus mutantStatus, MutantLocation mutantLocation, String mutatorKey, int index, String description, @Nullable String killingTest) { 39 | this.detected = detected; 40 | this.mutantStatus = mutantStatus; 41 | this.mutantLocation = mutantLocation; 42 | this.mutator = Mutator.parse(mutatorKey); 43 | this.index = index; 44 | this.description = description; 45 | this.killingTest = killingTest; 46 | } 47 | 48 | public String sourceRelativePath() { 49 | return mutantLocation.getRelativePath(); 50 | } 51 | 52 | public String violationDescription() { 53 | StringBuilder builder = new StringBuilder(mutator.getDescription()); 54 | builder.append(" without breaking the tests"); 55 | builder.append(" [").append(description).append("]"); 56 | return builder.toString(); 57 | } 58 | 59 | public int lineNumber() { 60 | return mutantLocation.lineNumber; 61 | } 62 | 63 | @Override 64 | public String toString() { 65 | StringBuilder builder = new StringBuilder() 66 | .append("{ \"d\" : ").append(detected) 67 | .append(", \"s\" : \"").append(mutantStatus).append("\"") 68 | .append(", \"c\" : \"").append(mutantLocation.getClassName()).append("\"") 69 | .append(", \"mname\" : \"").append(mutator.getName()).append("\"") 70 | .append(", \"mdesc\" : \"").append(mutator.getDescription()).append("\"") 71 | .append(", \"sourceFile\" : \"").append(mutantLocation.getSourceFile()).append("\"") 72 | .append(", \"mmethod\" : \"").append(mutantLocation.getMutatedMethod()).append("\"") 73 | .append(", \"l\" : \"").append(mutantLocation.getLineNumber()).append("\""); 74 | 75 | if (killingTest != null) { 76 | builder.append(", \"killtest\" : \"").append(killingTest).append("\""); 77 | } 78 | 79 | builder.append(" }"); 80 | return builder.toString(); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/pitest/domain/MutantLocation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Pitest Plugin 3 | * Copyright (C) 2009-2018 Vinod Anandan 4 | * vinod@owasp.org 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 org.sonar.plugins.pitest.domain; 21 | 22 | import java.util.StringTokenizer; 23 | 24 | public final class MutantLocation { 25 | 26 | public final String className; 27 | public final String sourceFile; 28 | public final String mutatedMethod; 29 | public final String methodDescription; 30 | public final int lineNumber; 31 | public final String relativePath; 32 | 33 | public MutantLocation(String className, String sourceFile, String mutatedMethod, String methodDescription, int lineNumber) { 34 | this.className = className; 35 | this.sourceFile = sourceFile; 36 | this.mutatedMethod = mutatedMethod; 37 | this.methodDescription = methodDescription; 38 | this.lineNumber = lineNumber; 39 | String extension = sourceFile.substring(sourceFile.indexOf('.') + 1); 40 | if ("kt".equals(extension)) { 41 | this.relativePath = sourceFile; 42 | } else if ("java".equals(extension)) { 43 | this.relativePath = calculateJavaRelativePath(className); 44 | } else { 45 | throw new IllegalStateException("unrecognized extension: " + extension); 46 | } 47 | 48 | } 49 | 50 | public String getClassName() { 51 | return className; 52 | } 53 | 54 | public String getSourceFile() { 55 | return sourceFile; 56 | } 57 | 58 | public String getMutatedMethod() { 59 | return mutatedMethod; 60 | } 61 | 62 | public String getMethodDescription() { 63 | return methodDescription; 64 | } 65 | 66 | public int getLineNumber() { 67 | return lineNumber; 68 | } 69 | 70 | public String getRelativePath() { 71 | return relativePath; 72 | } 73 | 74 | private static String calculateJavaRelativePath(String javaClassName) { 75 | final StringTokenizer tok = new StringTokenizer(javaClassName, "$"); 76 | final String classNameFiltered = tok.nextToken(); 77 | return classNameFiltered.replace('.', '/') + ".java"; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/pitest/domain/MutantStatus.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Pitest Plugin 3 | * Copyright (C) 2009-2018 Vinod Anandan 4 | * vinod@owasp.org 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 org.sonar.plugins.pitest.domain; 21 | 22 | import java.util.Arrays; 23 | import java.util.List; 24 | 25 | /* 26 | * Note: this is an incomplete list of DetectionStatus values. 27 | * The complete list is here: https://github.com/hcoles/pitest/blob/master/pitest/src/main/java/org/pitest/mutationtest/DetectionStatus.java 28 | * 29 | * OTHER is used for TIMED_OUT, NON_VIABLE, MEMORY_ERROR, RUN_ERROR, as these have less to say about Test Quality, more indicative of problems with the test fixture 30 | */ 31 | public enum MutantStatus { 32 | NO_COVERAGE("NO_COVERAGE"), 33 | KILLED("KILLED"), 34 | SURVIVED("SURVIVED"), 35 | OTHER("TIMED_OUT", "NON_VIABLE", "MEMORY_ERROR", "RUN_ERROR"), 36 | UNKNOWN; 37 | 38 | private final List pitestDetectionStatus; 39 | 40 | MutantStatus(final String... pitestDetectionStatus) { 41 | this.pitestDetectionStatus = Arrays.asList(pitestDetectionStatus); 42 | 43 | } 44 | 45 | public static MutantStatus fromPitestDetectionStatus(String pitestDetectionStatus) { 46 | for (MutantStatus mutantStatus : MutantStatus.values()) { 47 | if (mutantStatus.pitestDetectionStatus.contains(pitestDetectionStatus)) { 48 | return mutantStatus; 49 | } 50 | } 51 | return UNKNOWN; 52 | } 53 | 54 | public static MutantStatus parse(String statusName) { 55 | for (MutantStatus mutantStatus : MutantStatus.values()) { 56 | if (mutantStatus.name().equals(statusName)) { 57 | return mutantStatus; 58 | } 59 | } 60 | return UNKNOWN; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/pitest/domain/Mutator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Pitest Plugin 3 | * Copyright (C) 2009-2018 Vinod Anandan 4 | * vinod@owasp.org 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 org.sonar.plugins.pitest.domain; 21 | 22 | /** 23 | * https://github.com/hcoles/pitest/blob/master/pitest/src/main/java/org/pitest/mutationtest/engine/gregor/config/Mutator.java 24 | https://github.com/hcoles/pitest/tree/master/pitest/src/main/java/org/pitest/mutationtest/engine/gregor/mutators * 25 | * @author bwflood 26 | * 27 | */ 28 | public enum Mutator { 29 | 30 | INVERT_NEGS( 31 | "org.pitest.mutationtest.engine.gregor.mutators.InvertNegsMutator", "Invert Negatives Mutator", 32 | "A number has been replaced by its opposite"), 33 | RETURN_VALS( 34 | "org.pitest.mutationtest.engine.gregor.mutators.ReturnValsMutator", "Return Values Mutator", 35 | "The return value of a method call has been replaced"), 36 | INLINE_CONSTS("org.pitest.mutationtest.engine.gregor.mutators.InlineConstantMutator", 37 | "Inline Constant Mutator", "An inline constant has been changed"), 38 | MATH("org.pitest.mutationtest.engine.gregor.mutators.MathMutator", "Math Mutator", 39 | "A binary arithmetic operation has been replaced by another one"), 40 | VOID_METHOD_CALLS( 41 | "org.pitest.mutationtest.engine.gregor.mutators.VoidMethodCallMutator", "Void Method Calls Mutator", 42 | "A method call has been removed"), 43 | NEGATE_CONDITIONALS( 44 | "org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator", "Negate Conditionals Mutator", 45 | "A conditional expression has been negated"), 46 | CONDITIONALS_BOUNDARY("org.pitest.mutationtest.engine.gregor.mutators.ConditionalsBoundaryMutator", "Conditionals Boundary Mutator", 47 | "A relational operator has been replaced by a boundary counterpart"), 48 | INCREMENTS( 49 | "org.pitest.mutationtest.engine.gregor.mutators.IncrementsMutator", "Increments Mutator", 50 | "A local variable increment/decrement has been replaced"), 51 | // Note: the Key indicates it's NOT experimental, but the class resides in experimental package 52 | REMOVE_INCREMENTS("org.pitest.mutationtest.engine.gregor.mutators.experimental.RemoveIncrementsMutator", "Remove Increments Mutator", 53 | "An increment operation was removed"), 54 | NON_VOID_METHOD_CALLS("org.pitest.mutationtest.engine.gregor.mutators.NonVoidMethodCallMutator", 55 | "Non Void Method Calls Mutator", "A method call has been removed"), 56 | CONSTRUCTOR_CALLS( 57 | "org.pitest.mutationtest.engine.gregor.mutators.ConstructorCallMutator", "Constructor Calls Mutator", 58 | "A constructor call has been removed"), 59 | 60 | REMOVE_CONDITIONALS_EQ_IF(MutatorConstants.REMOVE_COND_MUTATOR_KEY, MutatorConstants.REMOVE_COND_MUTATOR_NAME, "A conditional statement has been removed - EQ IF"), 61 | REMOVE_CONDITIONALS_EQ_ELSE(MutatorConstants.REMOVE_COND_MUTATOR_KEY, MutatorConstants.REMOVE_COND_MUTATOR_NAME, 62 | "A conditional statement has been removed - EQ ELSE"), 63 | REMOVE_CONDITIONALS_ORD_IF(MutatorConstants.REMOVE_COND_MUTATOR_KEY, MutatorConstants.REMOVE_COND_MUTATOR_NAME, 64 | "A conditional statement has been removed - EQ ORD IF"), 65 | REMOVE_CONDITIONALS_ORD_ELSE(MutatorConstants.REMOVE_COND_MUTATOR_KEY, MutatorConstants.REMOVE_COND_MUTATOR_NAME, 66 | "A conditional statement has been removed - EQ ORD ELSE"), 67 | 68 | TRUE_RETURNS( 69 | "org.pitest.mutationtest.engine.gregor.mutators.BooleanTrueReturnValsMutator", "Boolean True ReturnVals Mutator", 70 | "Replaced Boolean return with True"), 71 | FALSE_RETURNS( 72 | "org.pitest.mutationtest.engine.gregor.mutators.BooleanFalseReturnValsMutator", "Boolean False ReturnVals Mutator", 73 | "Replaced Boolean return with False"), 74 | PRIMITIVE_RETURNS( 75 | "org.pitest.mutationtest.engine.gregor.mutators.PrimitiveReturnsMutator", "Primitive Returns Mutator", 76 | "Replaced primitive return value with 0"), 77 | EMPTY_RETURNS( 78 | "org.pitest.mutationtest.engine.gregor.mutators.EmptyObjectReturnValsMutator", "Empty Object Returns Mutator", 79 | "Replaced return value with empty object"), 80 | NULL_RETURNS( 81 | "org.pitest.mutationtest.engine.gregor.mutators.NullReturnValsMutator", "Null Returns Mutator", 82 | "Replaced return value with null"), 83 | 84 | EXPERIMENTAL_MEMBER_VARIABLE( 85 | "org.pitest.mutationtest.engine.gregor.mutators.experimental.MemberVariableMutator", "Experimental Member Variable Mutator", 86 | "A member variable assignment has been replaced"), 87 | EXPERIMENTAL_SWITCH("org.pitest.mutationtest.engine.gregor.mutators.experimental.SwitchMutator", "Experimental Switch Mutator", "A switch label has been swapped with another"), 88 | // Note: the Key indicates it's experimental, but the class resides in mutators package 89 | EXPERIMENTAL_ARGUMENT_PROPAGATION("org.pitest.mutationtest.engine.gregor.mutators.ArgumentPropagationMutator", "Experimental Argument Propagation Mutator", 90 | "A method return value was replaced with a method parameter"), 91 | EXPERIMENTAL_NAKED_RECEIVER("org.pitest.mutationtest.engine.gregor.mutators.experimental.NakedReceiverMutator", "Experimental Naked ReceiverMutator", 92 | "A method return value was replaced with receiver"), 93 | 94 | // Note: the Key indicates it's NOT experimental, but the class resides in experimental package 95 | REMOVE_SWITCH("org.pitest.mutationtest.engine.gregor.mutators.experimental.RemoveSwitchMutator", "Experimental Remove Switch Mutator", "A switch statement was removed"), 96 | 97 | UNKNOWN("", "Unknown mutator", "An unknown mutator has been applied"); 98 | 99 | private String key; 100 | private String name; 101 | private String description; 102 | 103 | Mutator(String key, String name, String description) { 104 | this.key = key; 105 | this.name = name; 106 | this.description = description; 107 | } 108 | 109 | String getKey() { 110 | return key; 111 | } 112 | 113 | String getName() { 114 | return name; 115 | } 116 | 117 | String getDescription() { 118 | return description; 119 | } 120 | 121 | static Mutator parse(String mutatorKey) { 122 | 123 | for (Mutator mutantStatus : Mutator.values()) { 124 | if (mutantStatus.getKey().equals(mutatorKey)) { 125 | return mutantStatus; 126 | } 127 | } 128 | return unknown(); 129 | } 130 | 131 | private static Mutator unknown() { 132 | return UNKNOWN; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/pitest/domain/MutatorConstants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Pitest Plugin 3 | * Copyright (C) 2009-2018 Vinod Anandan 4 | * vinod@owasp.org 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 org.sonar.plugins.pitest.domain; 21 | 22 | /** 23 | * 24 | * Constants for Strings used multiple times in enum Mutator 25 | * 26 | */ 27 | public class MutatorConstants { 28 | private MutatorConstants() { 29 | } 30 | 31 | public static final String REMOVE_COND_MUTATOR_KEY = "org.pitest.mutationtest.engine.gregor.mutators.RemoveConditionalMutator"; 32 | public static final String REMOVE_COND_MUTATOR_NAME = "Remove Conditional Mutator"; 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/pitest/domain/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Pitest Plugin 3 | * Copyright (C) 2009-2018 Vinod Anandan 4 | * vinod@owasp.org 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 | @ParametersAreNonnullByDefault 21 | package org.sonar.plugins.pitest.domain; 22 | 23 | import javax.annotation.ParametersAreNonnullByDefault; 24 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/pitest/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Pitest Plugin 3 | * Copyright (C) 2009-2018 Vinod Anandan 4 | * vinod@owasp.org 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 | @ParametersAreNonnullByDefault 21 | package org.sonar.plugins.pitest; 22 | 23 | import javax.annotation.ParametersAreNonnullByDefault; 24 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/pitest/scanner/PitestSensor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Pitest Plugin 3 | * Copyright (C) 2009-2018 Vinod Anandan 4 | * vinod@owasp.org 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 org.sonar.plugins.pitest.scanner; 21 | 22 | import java.io.Serializable; 23 | import java.util.Collection; 24 | import org.sonar.api.batch.fs.FilePredicate; 25 | import org.sonar.api.batch.fs.FileSystem; 26 | import org.sonar.api.batch.fs.InputFile; 27 | import org.sonar.api.batch.sensor.Sensor; 28 | import org.sonar.api.batch.sensor.SensorContext; 29 | import org.sonar.api.batch.sensor.SensorDescriptor; 30 | import org.sonar.api.batch.sensor.issue.NewIssue; 31 | import org.sonar.api.batch.sensor.issue.NewIssueLocation; 32 | import org.sonar.api.config.Configuration; 33 | import org.sonar.api.measures.Metric; 34 | import org.sonar.api.profiles.RulesProfile; 35 | import org.sonar.api.rule.RuleKey; 36 | import org.sonar.api.rules.ActiveRule; 37 | import org.sonar.api.utils.log.Logger; 38 | import org.sonar.api.utils.log.Loggers; 39 | import org.sonar.plugins.pitest.PitestMetrics; 40 | import org.sonar.plugins.pitest.domain.Mutant; 41 | import org.sonar.plugins.pitest.domain.MutantStatus; 42 | 43 | import static org.sonar.plugins.pitest.PitestConstants.COVERAGE_RATIO_PARAM; 44 | import static org.sonar.plugins.pitest.PitestConstants.INSUFFICIENT_MUTATION_COVERAGE_RULE_KEY; 45 | import static org.sonar.plugins.pitest.PitestConstants.MODE_KEY; 46 | import static org.sonar.plugins.pitest.PitestConstants.MODE_SKIP; 47 | import static org.sonar.plugins.pitest.PitestConstants.REPORT_DIRECTORY_KEY; 48 | import static org.sonar.plugins.pitest.PitestConstants.REPOSITORY_KEY; 49 | import static org.sonar.plugins.pitest.PitestConstants.SURVIVED_MUTANT_RULE_KEY; 50 | 51 | /** 52 | * Sonar sensor for pitest mutation coverage analysis. 53 | */ 54 | public class PitestSensor implements Sensor { 55 | 56 | private static final Logger LOGGER = Loggers.get(PitestSensor.class); 57 | static final String SENSOR_NAME = "Pitest Sensor"; 58 | 59 | private final Configuration configuration; 60 | private final XmlReportParser parser; 61 | private final RulesProfile rulesProfile; 62 | private final XmlReportFinder xmlReportFinder; 63 | private final FileSystem fileSystem; 64 | private final String executionMode; 65 | private final FilePredicate fileSystemExecutionPredicate; 66 | 67 | public PitestSensor(Configuration configuration, XmlReportParser parser, RulesProfile rulesProfile, XmlReportFinder xmlReportFinder, FileSystem fileSystem) { 68 | this.configuration = configuration; 69 | this.parser = parser; 70 | this.rulesProfile = rulesProfile; 71 | this.xmlReportFinder = xmlReportFinder; 72 | this.fileSystem = fileSystem; 73 | this.executionMode = configuration.get(MODE_KEY).orElse(null); 74 | this.fileSystemExecutionPredicate = fileSystem.predicates().and( 75 | fileSystem.predicates().hasType(InputFile.Type.MAIN), 76 | fileSystem.predicates().hasLanguages("java")); 77 | } 78 | 79 | @Override 80 | public void describe(SensorDescriptor descriptor) { 81 | descriptor.name(SENSOR_NAME); 82 | descriptor.createIssuesForRuleRepository(REPOSITORY_KEY); 83 | descriptor.onlyOnFileType(InputFile.Type.MAIN); 84 | descriptor.onlyOnLanguages("java"); 85 | } 86 | 87 | @Override 88 | public void execute(SensorContext context) { 89 | if (!fileSystem.hasFiles(fileSystemExecutionPredicate)) { 90 | LOGGER.debug("file system execution predicate not satisfied {}. returning", fileSystemExecutionPredicate); 91 | return; 92 | } 93 | 94 | if (MODE_SKIP.equals(executionMode)) { 95 | LOGGER.debug("executionMode is skip. returning"); 96 | return; 97 | } 98 | 99 | java.io.File projectDirectory = fileSystem.baseDir(); 100 | String reportDirectoryPath = configuration.get(REPORT_DIRECTORY_KEY).orElse(null); 101 | 102 | java.io.File reportDirectory = new java.io.File(projectDirectory, reportDirectoryPath); 103 | java.io.File xmlReport = xmlReportFinder.findReport(reportDirectory); 104 | if (xmlReport == null) { 105 | LOGGER.warn("No XML PIT report found in directory {} !", reportDirectory); 106 | LOGGER.warn("Checkout plugin documentation for more detailed explanations: https://github.com/SonarQubeCommunity/sonar-pitest"); 107 | return; 108 | } 109 | 110 | Collection mutants = parser.parse(xmlReport); 111 | processProjectReport(new ProjectReport(mutants), context); 112 | } 113 | 114 | private void processProjectReport(ProjectReport projectReport, SensorContext context) { 115 | Collection sourceFileReports = projectReport.getSourceFileReports(); 116 | 117 | for (SourceFileReport sourceFileReport : sourceFileReports) { 118 | InputFile inputFile = locateFile(sourceFileReport.getRelativePath()); 119 | if (inputFile == null) { 120 | LOGGER.warn("Mutation in an unknown resource: {}", sourceFileReport.getRelativePath()); 121 | continue; 122 | } 123 | 124 | /* 125 | * report Coverage and Measures regardless of whether rules are active 126 | * FIXME: investigate further whether anything should be reported if rules are inactive 127 | */ 128 | if (sourceFileReport.getMutationsKilled() > 0) { 129 | addCoverageForKilledMutants(context, inputFile, sourceFileReport); 130 | } 131 | 132 | saveMeasureOnFile(context, inputFile, PitestMetrics.MUTATIONS_NOT_COVERED, sourceFileReport.getMutationsNoCoverage()); 133 | saveMeasureOnFile(context, inputFile, PitestMetrics.MUTATIONS_GENERATED, sourceFileReport.getMutationsTotal()); 134 | saveMeasureOnFile(context, inputFile, PitestMetrics.MUTATIONS_KILLED, sourceFileReport.getMutationsKilled()); 135 | saveMeasureOnFile(context, inputFile, PitestMetrics.MUTATIONS_SURVIVED, sourceFileReport.getMutationsSurvived()); 136 | saveMeasureOnFile(context, inputFile, PitestMetrics.MUTATIONS_ERROR, sourceFileReport.getMutationsOther()); 137 | saveMeasureOnFile(context, inputFile, PitestMetrics.MUTATIONS_UNKNOWN, sourceFileReport.getMutationsUnknown()); 138 | saveMeasureOnFile(context, inputFile, PitestMetrics.MUTATIONS_DATA, sourceFileReport.toJSON()); 139 | 140 | /* 141 | * Rules-sensitive reporting 142 | */ 143 | if (isSurvivedMutantRuleActive(rulesProfile)) { 144 | addIssueForSurvivingMutants(context, inputFile, sourceFileReport); 145 | } 146 | 147 | if (isInsufficientMutationCoverageRuleActive(rulesProfile)) { 148 | ActiveRule coverageRule = rulesProfile.getActiveRule(REPOSITORY_KEY, INSUFFICIENT_MUTATION_COVERAGE_RULE_KEY); 149 | if (!isMutantCoverageThresholdReached(sourceFileReport, coverageRule)) { 150 | addIssueForMutantKilledThresholdNotReached(context, inputFile, coverageRule.getParameter(COVERAGE_RATIO_PARAM)); 151 | } 152 | } 153 | } 154 | } 155 | 156 | private void saveMeasureOnFile(SensorContext context, InputFile inputFile, Metric metric, T value) { 157 | context.newMeasure() 158 | .withValue(value) 159 | .forMetric(metric) 160 | .on(inputFile) 161 | .save(); 162 | } 163 | 164 | private boolean isMutantCoverageThresholdReached(SourceFileReport sourceFileReport, ActiveRule coverageRule) { 165 | int killed = sourceFileReport.getMutationsKilled(); 166 | int total = sourceFileReport.getMutationsTotal(); 167 | int threshold = Integer.parseInt(coverageRule.getParameter(COVERAGE_RATIO_PARAM)); 168 | 169 | return (killed * 100d / total) >= threshold; 170 | } 171 | 172 | private void addIssueForMutantKilledThresholdNotReached(SensorContext context, InputFile inputFile, String threshold) { 173 | String issueMsg = "More mutants need to be covered by unit tests to reach the minimum threshold of " + threshold + "% mutant coverage"; 174 | 175 | NewIssue newIssue = context.newIssue(); 176 | 177 | NewIssueLocation location = newIssue.newLocation() 178 | .on(inputFile) 179 | .message(issueMsg); 180 | 181 | newIssue.at(location); 182 | 183 | newIssue.forRule(RuleKey.of(REPOSITORY_KEY, INSUFFICIENT_MUTATION_COVERAGE_RULE_KEY)); 184 | newIssue.save(); 185 | } 186 | 187 | private void addIssueForSurvivingMutants(SensorContext context, InputFile inputFile, SourceFileReport sourceFileReport) { 188 | Collection mutants = sourceFileReport.getMutants(); 189 | for (Mutant mutant : mutants) { 190 | 191 | if (MutantStatus.SURVIVED.equals(mutant.mutantStatus)) { 192 | NewIssue newIssue = context.newIssue() 193 | .forRule(RuleKey.of(REPOSITORY_KEY, SURVIVED_MUTANT_RULE_KEY)); 194 | 195 | NewIssueLocation location = newIssue.newLocation() 196 | .on(inputFile) 197 | .at(inputFile.selectLine(mutant.lineNumber())) 198 | .message(mutant.violationDescription()); 199 | 200 | newIssue.at(location); 201 | newIssue.save(); 202 | } 203 | } 204 | } 205 | 206 | private void addCoverageForKilledMutants(SensorContext context, InputFile inputFile, SourceFileReport sourceFileReport) { 207 | Collection mutants = sourceFileReport.getMutants(); 208 | for (Mutant mutant : mutants) { 209 | if (MutantStatus.KILLED.equals(mutant.mutantStatus)) { 210 | context.newCoverage() 211 | .onFile(inputFile) 212 | .lineHits(mutant.lineNumber(), 1) 213 | .save(); 214 | } 215 | } 216 | } 217 | 218 | private InputFile locateFile(String sourceFileRelativePath) { 219 | FilePredicate filePredicate = fileSystem.predicates().and( 220 | fileSystem.predicates().hasType(InputFile.Type.MAIN), 221 | fileSystem.predicates().matchesPathPattern("**/" + sourceFileRelativePath)); 222 | return fileSystem.inputFile(filePredicate); 223 | } 224 | 225 | private boolean isSurvivedMutantRuleActive(RulesProfile qualityProfile) { 226 | return (qualityProfile.getActiveRule(REPOSITORY_KEY, SURVIVED_MUTANT_RULE_KEY) != null); 227 | } 228 | 229 | private boolean isInsufficientMutationCoverageRuleActive(RulesProfile qualityProfile) { 230 | return (qualityProfile.getActiveRule(REPOSITORY_KEY, INSUFFICIENT_MUTATION_COVERAGE_RULE_KEY) != null); 231 | } 232 | 233 | @Override 234 | public String toString() { 235 | return getClass().getSimpleName(); 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/pitest/scanner/ProjectReport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Pitest Plugin 3 | * Copyright (C) 2009-2018 Vinod Anandan 4 | * vinod@owasp.org 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 org.sonar.plugins.pitest.scanner; 21 | 22 | import java.util.Collection; 23 | import java.util.HashMap; 24 | import java.util.Map; 25 | import org.sonar.api.batch.ScannerSide; 26 | import org.sonar.plugins.pitest.domain.Mutant; 27 | 28 | @ScannerSide 29 | public class ProjectReport { 30 | 31 | private final Map sourceFileReports = new HashMap<>(); 32 | 33 | public ProjectReport(Collection mutants) { 34 | for (Mutant mutant : mutants) { 35 | String relativePath = mutant.sourceRelativePath(); 36 | final SourceFileReport sourceFileReport; 37 | if (sourceFileReports.containsKey(relativePath)) { 38 | sourceFileReport = sourceFileReports.get(relativePath); 39 | } else { 40 | sourceFileReport = new SourceFileReport(relativePath); 41 | sourceFileReports.put(relativePath, sourceFileReport); 42 | } 43 | sourceFileReport.addMutant(mutant); 44 | } 45 | } 46 | 47 | public Collection getSourceFileReports() { 48 | return sourceFileReports.values(); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/pitest/scanner/SourceFileReport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Pitest Plugin 3 | * Copyright (C) 2009-2018 Vinod Anandan 4 | * vinod@owasp.org 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 org.sonar.plugins.pitest.scanner; 21 | 22 | import java.util.ArrayList; 23 | import java.util.Collection; 24 | import java.util.Collections; 25 | import java.util.HashMap; 26 | import java.util.List; 27 | import java.util.Map; 28 | import java.util.Map.Entry; 29 | import org.sonar.plugins.pitest.domain.Mutant; 30 | 31 | /** 32 | * Mutants for a given java source file 33 | */ 34 | public class SourceFileReport { 35 | private final String sourceFileRelativePath; 36 | private final List mutants = new ArrayList<>(); 37 | private int mutationsNoCoverage = 0; 38 | private int mutationsKilled = 0; 39 | private int mutationsSurvived = 0; 40 | private int mutationsOther = 0; 41 | private int mutationsUnknown = 0; 42 | 43 | public SourceFileReport(String sourceFileRelativePath) { 44 | this.sourceFileRelativePath = sourceFileRelativePath; 45 | } 46 | 47 | public String toJSON() { 48 | if (mutants.isEmpty()) { 49 | return null; 50 | } 51 | Map> mutantsByLine = new HashMap<>(); 52 | 53 | for (Mutant mutant : mutants) { 54 | if (!mutantsByLine.containsKey(mutant.lineNumber())) { 55 | mutantsByLine.put(mutant.lineNumber(), new ArrayList()); 56 | } 57 | mutantsByLine.get(mutant.lineNumber()).add(mutant.toString()); 58 | } 59 | 60 | StringBuilder builder = new StringBuilder(); 61 | builder.append("{"); 62 | boolean first = true; 63 | for (Entry> entry : mutantsByLine.entrySet()) { 64 | if (!first) { 65 | builder.append(","); 66 | } 67 | first = false; 68 | builder.append("\"").append(entry.getKey()).append("\":"); 69 | builder.append("["); 70 | for (String mutant : entry.getValue()) { 71 | builder.append(mutant).append(','); 72 | } 73 | builder.deleteCharAt(builder.length() - 1); // remove last ',' 74 | builder.append("]"); 75 | } 76 | builder.append("}"); 77 | 78 | return builder.toString(); 79 | } 80 | 81 | public void addMutant(Mutant mutant) { 82 | if (!sourceFileRelativePath.equals(mutant.sourceRelativePath())) { 83 | throw new IllegalArgumentException("Relative paths do not match: " 84 | + sourceFileRelativePath 85 | + " vs " 86 | + mutant.sourceRelativePath()); 87 | } 88 | mutants.add(mutant); 89 | switch (mutant.mutantStatus) { 90 | case NO_COVERAGE: 91 | mutationsNoCoverage++; 92 | break; 93 | case KILLED: 94 | mutationsKilled++; 95 | break; 96 | case SURVIVED: // Only survived mutations are saved as violations 97 | mutationsSurvived++; 98 | break; 99 | case OTHER: 100 | mutationsOther++; 101 | break; 102 | case UNKNOWN: 103 | mutationsUnknown++; 104 | break; 105 | } 106 | } 107 | 108 | public String getRelativePath() { 109 | return sourceFileRelativePath; 110 | } 111 | 112 | public Collection getMutants() { 113 | return Collections.unmodifiableList(mutants); 114 | } 115 | 116 | public Integer getMutationsTotal() { 117 | return mutants.size(); 118 | } 119 | 120 | Integer getMutationsNoCoverage() { 121 | return mutationsNoCoverage; 122 | } 123 | 124 | Integer getMutationsKilled() { 125 | return mutationsKilled; 126 | } 127 | 128 | Integer getMutationsSurvived() { 129 | return mutationsSurvived; 130 | } 131 | 132 | Integer getMutationsOther() { 133 | return mutationsOther; 134 | } 135 | 136 | Integer getMutationsUnknown() { 137 | return mutationsUnknown; 138 | } 139 | 140 | @Override 141 | public String toString() { 142 | return "SourceFileReport [sourceFileRelativePath=" + sourceFileRelativePath + ", mutants=" + mutants + ", mutationsNoCoverage=" + mutationsNoCoverage + ", mutationsKilled=" 143 | + mutationsKilled + ", mutationsSurvived=" + mutationsSurvived + ", mutationsOther=" + mutationsOther + ", mutationsUnknown=" + mutationsUnknown + "]"; 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/pitest/scanner/XmlReportFinder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Pitest Plugin 3 | * Copyright (C) 2009-2018 Vinod Anandan 4 | * vinod@owasp.org 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 org.sonar.plugins.pitest.scanner; 21 | 22 | import java.io.File; 23 | import java.io.IOException; 24 | import java.nio.file.FileVisitResult; 25 | import java.nio.file.FileVisitor; 26 | import java.nio.file.Files; 27 | import java.nio.file.Path; 28 | import java.nio.file.attribute.BasicFileAttributes; 29 | import java.util.concurrent.atomic.AtomicReference; 30 | import org.sonar.api.ExtensionPoint; 31 | import org.sonar.api.batch.ScannerSide; 32 | import org.sonar.api.utils.log.Logger; 33 | import org.sonar.api.utils.log.Loggers; 34 | 35 | @ScannerSide 36 | @ExtensionPoint 37 | public class XmlReportFinder { 38 | 39 | private static final Logger LOG = Loggers.get(XmlReportFinder.class); 40 | 41 | public File findReport(File reportDirectory) { 42 | if (!reportDirectory.exists() || !reportDirectory.isDirectory()) { 43 | LOG.error("reportDirectory does not exist or is not a Directory: " + reportDirectory.getAbsolutePath()); 44 | return null; 45 | } 46 | 47 | final AtomicReference latestReport = new AtomicReference<>(); 48 | try { 49 | Files.walkFileTree(reportDirectory.toPath(), new FileVisitor() { 50 | 51 | @Override 52 | public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) throws IOException { 53 | 54 | return FileVisitResult.CONTINUE; 55 | } 56 | 57 | @Override 58 | public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException { 59 | if (!file.toFile().isFile()) { 60 | return FileVisitResult.CONTINUE; 61 | } 62 | if (!file.toString().endsWith("mutations.xml")) { 63 | return FileVisitResult.CONTINUE; 64 | } 65 | 66 | if (latestReport.get() == null 67 | || Files.getLastModifiedTime(file).compareTo(Files.getLastModifiedTime(latestReport.get())) > 0) { 68 | latestReport.set(file); 69 | } 70 | return FileVisitResult.CONTINUE; 71 | } 72 | 73 | @Override 74 | public FileVisitResult visitFileFailed(final Path file, final IOException exc) throws IOException { 75 | 76 | return FileVisitResult.CONTINUE; 77 | } 78 | 79 | @Override 80 | public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException { 81 | 82 | return FileVisitResult.CONTINUE; 83 | } 84 | }); 85 | } catch (IOException e) { 86 | LOG.error("unable to find pitest report file in reportDirectory: " + reportDirectory.getAbsolutePath()); 87 | return null; 88 | } 89 | if (latestReport.get() != null) { 90 | return latestReport.get().toFile(); 91 | } 92 | return null; 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/pitest/scanner/XmlReportParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Pitest Plugin 3 | * Copyright (C) 2009-2018 Vinod Anandan 4 | * vinod@owasp.org 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 org.sonar.plugins.pitest.scanner; 21 | 22 | import com.google.common.base.Charsets; 23 | import com.google.common.base.Throwables; 24 | import java.io.File; 25 | import java.io.FileInputStream; 26 | import java.io.IOException; 27 | import java.io.InputStream; 28 | import java.io.InputStreamReader; 29 | import java.util.ArrayList; 30 | import java.util.Collection; 31 | import javax.xml.stream.XMLInputFactory; 32 | import javax.xml.stream.XMLStreamConstants; 33 | import javax.xml.stream.XMLStreamException; 34 | import javax.xml.stream.XMLStreamReader; 35 | import org.sonar.api.ExtensionPoint; 36 | import org.sonar.api.batch.ScannerSide; 37 | import org.sonar.api.utils.log.Logger; 38 | import org.sonar.api.utils.log.Loggers; 39 | import org.sonar.plugins.pitest.domain.Mutant; 40 | import org.sonar.plugins.pitest.domain.MutantLocation; 41 | import org.sonar.plugins.pitest.domain.MutantStatus; 42 | 43 | @ScannerSide 44 | @ExtensionPoint 45 | public class XmlReportParser { 46 | 47 | private static final Logger LOG = Loggers.get(XmlReportParser.class); 48 | 49 | public Collection parse(File report) { 50 | return new Parser().parse(report); 51 | } 52 | 53 | private class Parser { 54 | 55 | private XMLStreamReader stream; 56 | private final Collection mutants = new ArrayList<>(); 57 | 58 | private boolean detected; 59 | private MutantStatus mutantStatus; 60 | private String sourceFile; 61 | private String mutatedClass; 62 | private String mutatedMethod; 63 | private String methodDescription; 64 | private int lineNumber; 65 | private String mutator; 66 | private int index; 67 | private String description; 68 | private String killingTest; 69 | 70 | private void reset() { 71 | detected = false; 72 | mutantStatus = null; 73 | sourceFile = null; 74 | mutatedClass = null; 75 | mutatedMethod = null; 76 | methodDescription = null; 77 | lineNumber = 0; 78 | mutator = null; 79 | index = 0; 80 | description = null; 81 | killingTest = null; 82 | } 83 | 84 | public Collection parse(File file) { 85 | 86 | XMLInputFactory xmlFactory = XMLInputFactory.newInstance(); 87 | 88 | try (InputStream is = new FileInputStream(file); 89 | InputStreamReader reader = new InputStreamReader(is, Charsets.UTF_8)) { 90 | stream = xmlFactory.createXMLStreamReader(reader); 91 | 92 | while (stream.hasNext()) { 93 | int next = stream.next(); 94 | if (next == XMLStreamConstants.START_ELEMENT) { 95 | parseStartElement(); 96 | } else if (next == XMLStreamConstants.END_ELEMENT) { 97 | processEndElement(); 98 | } 99 | } 100 | } catch (IOException | XMLStreamException | IllegalArgumentException e) { 101 | throw new IllegalStateException("XML is not valid", e); 102 | } finally { 103 | closeXmlStream(); 104 | } 105 | 106 | return mutants; 107 | } 108 | 109 | private void parseStartElement() { 110 | String tagName = stream.getLocalName(); 111 | 112 | if ("mutation".equals(tagName)) { 113 | reset(); 114 | handleMutationTag(); 115 | } else if ("sourceFile".equals(tagName)) { 116 | handleSourceFileTag(); 117 | } else if ("mutatedClass".equals(tagName)) { 118 | handleMutatedClassTag(); 119 | } else if ("mutatedMethod".equals(tagName)) { 120 | handleMutatedMethod(); 121 | } else if ("methodDescription".equals(tagName)) { 122 | handleMethodDescription(); 123 | } else if ("lineNumber".equals(tagName)) { 124 | handleLineNumber(); 125 | } else if ("mutator".equals(tagName)) { 126 | handleMutator(); 127 | } else if ("index".equals(tagName)) { 128 | handleIndex(); 129 | } else if ("killingTest".equals(tagName)) { 130 | handleKillingTest(); 131 | } else if ("description".equals(tagName)) { 132 | handleDescription(); 133 | } else { 134 | if (LOG.isDebugEnabled()) { 135 | // all are processed now, so this is a new element added by pitest 136 | LOG.debug("Ignoring tag {}", tagName); 137 | } 138 | } 139 | } 140 | 141 | private void handleMutationTag() { 142 | detected = Boolean.parseBoolean(getAttribute("detected")); 143 | mutantStatus = MutantStatus.fromPitestDetectionStatus(getAttribute("status")); 144 | } 145 | 146 | private void handleSourceFileTag() { 147 | try { 148 | sourceFile = stream.getElementText(); 149 | } catch (Exception e) { 150 | logException(e.getClass().getSimpleName(), "processing tag sourceFile"); 151 | } 152 | } 153 | 154 | private void handleMutatedClassTag() { 155 | try { 156 | mutatedClass = stream.getElementText(); 157 | } catch (Exception e) { 158 | logException(e.getClass().getSimpleName(), "processing tag MutatedClass"); 159 | } 160 | } 161 | 162 | private void handleMutatedMethod() { 163 | try { 164 | mutatedMethod = stream.getElementText(); 165 | } catch (Exception e) { 166 | logException(e.getClass().getSimpleName(), "processing tag mutatedMethod"); 167 | } 168 | } 169 | 170 | private void handleMethodDescription() { 171 | try { 172 | methodDescription = stream.getElementText(); 173 | } catch (Exception e) { 174 | logException(e.getClass().getSimpleName(), "processing tag methodDescription"); 175 | } 176 | } 177 | 178 | private void handleLineNumber() { 179 | try { 180 | lineNumber = Integer.parseInt(stream.getElementText().trim()); 181 | } catch (Exception e) { 182 | logException(e.getClass().getSimpleName(), "processing tag lineNumber"); 183 | } 184 | } 185 | 186 | private void handleMutator() { 187 | try { 188 | mutator = stream.getElementText(); 189 | } catch (Exception e) { 190 | logException(e.getClass().getSimpleName(), "processing tag mutator"); 191 | } 192 | } 193 | 194 | private void handleIndex() { 195 | try { 196 | index = Integer.parseInt(stream.getElementText().trim()); 197 | } catch (Exception e) { 198 | logException(e.getClass().getSimpleName(), "processing tag index"); 199 | } 200 | } 201 | 202 | private void handleKillingTest() { 203 | try { 204 | killingTest = stream.getElementText(); 205 | } catch (Exception e) { 206 | logException(e.getClass().getSimpleName(), "processing tag killingTest"); 207 | } 208 | } 209 | 210 | private void handleDescription() { 211 | try { 212 | description = stream.getElementText(); 213 | } catch (Exception e) { 214 | logException(e.getClass().getSimpleName(), "processing tag description"); 215 | } 216 | } 217 | 218 | private void processEndElement() { 219 | String tagName = stream.getLocalName(); 220 | if ("mutation".equals(tagName)) { 221 | MutantLocation location = new MutantLocation(mutatedClass, sourceFile, mutatedMethod, methodDescription, lineNumber); 222 | mutants.add(new Mutant(detected, mutantStatus, location, mutator, index, description, killingTest)); 223 | } 224 | } 225 | 226 | private void logException(String exceptionName, String activity) { 227 | LOG.warn("caught {} {}.. ignoring ", exceptionName, activity); 228 | } 229 | 230 | private void closeXmlStream() { 231 | if (stream != null) { 232 | try { 233 | stream.close(); 234 | } catch (XMLStreamException e) { 235 | throw Throwables.propagate(e); 236 | } 237 | } 238 | } 239 | 240 | private String getAttribute(String name) { 241 | for (int i = 0; i < stream.getAttributeCount(); i++) { 242 | if (name.equals(stream.getAttributeLocalName(i))) { 243 | return stream.getAttributeValue(i); 244 | } 245 | } 246 | 247 | return null; 248 | } 249 | 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/main/java/org/sonar/plugins/pitest/scanner/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Pitest Plugin 3 | * Copyright (C) 2009-2018 Vinod Anandan 4 | * vinod@owasp.org 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 | @ParametersAreNonnullByDefault 21 | package org.sonar.plugins.pitest.scanner; 22 | 23 | import javax.annotation.ParametersAreNonnullByDefault; 24 | -------------------------------------------------------------------------------- /src/main/resources/org/sonar/plugins/pitest/pitest_dashboard_widget.html.erb: -------------------------------------------------------------------------------- 1 | <% if measure('pitest_mutations_total') and measure('pitest_mutations_total').value > 0 %> 2 |
3 |
4 |
5 |

<%= metric('pitest_mutations_coverage').short_name -%>

6 |

<%= format_measure('pitest_mutations_coverage', :suffix => '', :url => url_for_drilldown('pitest_mutations_coverage')) -%>

7 |

<%= format_measure('pitest_mutations_total', :suffix => ' total mutations', :url => url_for_drilldown('pitest_mutations_total')) -%>

8 |

<%= format_measure('pitest_mutations_detected', :suffix => ' detected mutations', :url => url_for_drilldown('pitest_mutations_detected')) -%>

9 |
10 |
11 |
12 |
13 |

Killed mutations

14 |

<%= format_measure('pitest_mutations_killed', :suffix => ' mutations killed by tests ', :url => url_for_drilldown('pitest_mutations_killed')) -%><%= tendency_icon(measure('pitest_mutations_killed')) %>

15 |

<%= format_measure('pitest_mutations_memoryError', :suffix => ' mutations killed by memory errors ', :url => url_for_drilldown('pitest_mutations_memoryError')) -%><%= tendency_icon(measure('pitest_mutations_memoryError')) %>

16 |

<%= format_measure('pitest_mutations_timedOut', :suffix => ' mutations killed by time outs ', :url => url_for_drilldown('pitest_mutations_timedOut')) -%><%= tendency_icon(measure('pitest_mutations_timedOut')) %>

17 |
18 |
19 |
20 |
21 |

Non killed mutations

22 |

<%= format_measure('pitest_mutations_survived', :suffix => ' survived mutations ', :url => url_for_drilldown('pitest_mutations_survived')) -%><%= tendency_icon(measure('pitest_mutations_survived')) %>

23 |

<%= format_measure('pitest_mutations_noCoverage', :suffix => ' non covered mutations ', :url => url_for_drilldown('pitest_mutations_noCoverage')) -%><%= tendency_icon(measure('pitest_mutations_noCoverage')) %>

24 |

<%= format_measure('pitest_mutations_unknown', :suffix => ' mutations with unknown status ', :url => url_for_drilldown('pitest_mutations_unknown')) -%><%= tendency_icon(measure('pitest_mutations_unknown')) %>

25 |
26 |
27 |
28 | <% end %> 29 | -------------------------------------------------------------------------------- /src/main/resources/static/pitest.css: -------------------------------------------------------------------------------- 1 | div.mutations { 2 | background-color: #FFFFFF; 3 | border-bottom: 1px solid #DDDDDD; 4 | border-right: 1px solid #DDDDDD; 5 | border-top: 1px solid #DDDDDD; 6 | padding: 10px; 7 | } 8 | 9 | div.mutation { 10 | background-color: #FFFFFF; 11 | border: 1px solid #DDDDDD; 12 | margin: 0; 13 | } 14 | 15 | span.mutationname { 16 | color: #4183C4; 17 | font-weight: bold; 18 | text-decoration: none; 19 | } 20 | 21 | span.mutationkilled { 22 | color: #00BB00; 23 | font-weight: bold; 24 | text-decoration: none; 25 | } 26 | 27 | span.mutationsurvived { 28 | color: #BB0000; 29 | font-weight: bold; 30 | text-decoration: none; 31 | } 32 | 33 | span.mutationnocoverage { 34 | color: #FFA500; 35 | font-weight: bold; 36 | text-decoration: none; 37 | } 38 | 39 | div.mutationDescription { 40 | background-color: #F4F4F4; 41 | border-top: 1px solid #DDDDDD; 42 | line-height: 1.5em; 43 | margin: 0; 44 | padding: 5px 10px; 45 | border-top: medium none; 46 | } -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/pitest/PitestComputerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Pitest Plugin 3 | * Copyright (C) 2009-2018 Vinod Anandan 4 | * vinod@owasp.org 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 org.sonar.plugins.pitest; 21 | 22 | import org.junit.Test; 23 | import org.sonar.api.ce.measure.Component; 24 | import org.sonar.api.ce.measure.MeasureComputer.MeasureComputerDefinition; 25 | import org.sonar.api.ce.measure.Settings; 26 | import org.sonar.api.ce.measure.test.TestMeasureComputerContext; 27 | import org.sonar.api.ce.measure.test.TestMeasureComputerDefinitionContext; 28 | 29 | import static org.assertj.core.api.Assertions.assertThat; 30 | import static org.mockito.Mockito.mock; 31 | 32 | public class PitestComputerTest { 33 | 34 | @Test 35 | public void definition_should_have_no_input_metrics_eight_output_metrics() { 36 | // given 37 | TestMeasureComputerDefinitionContext context = new TestMeasureComputerDefinitionContext(); 38 | PitestComputer computer = new PitestComputer(); 39 | 40 | // when 41 | MeasureComputerDefinition def = computer.define(context); 42 | 43 | // then 44 | assertThat(def).isNotNull(); 45 | assertThat(def.getInputMetrics()).isEmpty(); 46 | assertThat(def.getOutputMetrics()).containsOnly("pitest_mutations_noCoverage", "pitest_mutations_total", "pitest_mutations_killed", "pitest_mutations_survived", 47 | "pitest_mutations_error", "pitest_mutations_unknown", "pitest_mutations_data", "pitest_mutations_killed_percent"); 48 | 49 | } 50 | 51 | @Test 52 | public void top_level_measures_are_preserved() { 53 | // given 54 | PitestComputer sut = new PitestComputer(); 55 | MeasureComputerDefinition measureComputerDefinition = sut.define(new TestMeasureComputerDefinitionContext()); 56 | TestMeasureComputerContext context = new TestMeasureComputerContext(null, null, measureComputerDefinition); 57 | context.addMeasure("pitest_mutations_killed", 3); 58 | 59 | // when 60 | sut.compute(context); 61 | 62 | // then 63 | assertThat(context.getMeasure("pitest_mutations_killed").getIntValue()).isEqualTo(3); 64 | 65 | } 66 | 67 | @Test 68 | public void children_measures_are_calculated_for_integer_metrics() { 69 | // given 70 | PitestComputer sut = new PitestComputer(); 71 | MeasureComputerDefinition measureComputerDefinition = sut.define(new TestMeasureComputerDefinitionContext()); 72 | TestMeasureComputerContext context = new TestMeasureComputerContext(null, null, measureComputerDefinition); 73 | context.addChildrenMeasures("pitest_mutations_killed", 3, 5, 7, 9); 74 | 75 | // when 76 | sut.compute(context); 77 | 78 | // then 79 | assertThat(context.getMeasure("pitest_mutations_killed").getIntValue()).isEqualTo(24); 80 | 81 | } 82 | 83 | 84 | @Test 85 | public void calculateCoveragePercent() { 86 | // given 87 | PitestComputer coverageComputer = new PitestComputer(); 88 | TestMeasureComputerDefinitionContext defContext = new TestMeasureComputerDefinitionContext(); 89 | TestMeasureComputerContext context = new TestMeasureComputerContext(mock(Component.class), mock(Settings.class), coverageComputer.define(defContext)); 90 | context.addMeasure(PitestMetrics.MUTATIONS_GENERATED_KEY, 10); 91 | context.addMeasure(PitestMetrics.MUTATIONS_KILLED_KEY, 2); 92 | 93 | // when 94 | coverageComputer.compute(context); 95 | 96 | // then 97 | assertThat(context.getMeasure(PitestMetrics.MUTATIONS_KILLED_PERCENT_KEY).getDoubleValue()).isEqualTo(20); 98 | 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/pitest/PitestMetricsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Pitest Plugin 3 | * Copyright (C) 2009-2018 Vinod Anandan 4 | * vinod@owasp.org 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 org.sonar.plugins.pitest; 21 | 22 | import java.io.Serializable; 23 | import java.util.List; 24 | import org.junit.Test; 25 | import org.sonar.api.measures.Metric; 26 | 27 | import static org.assertj.core.api.Assertions.assertThat; 28 | 29 | public class PitestMetricsTest { 30 | 31 | @Test 32 | public void mutationsNotCoveredMetricIsCreatedCorrectly() { 33 | // given 34 | 35 | // when 36 | Metric mutationsNotCovered = PitestMetrics.MUTATIONS_NOT_COVERED; 37 | 38 | // then 39 | assertThat(mutationsNotCovered.getDescription()).contains("not covered"); 40 | assertThat(mutationsNotCovered.getDirection()).isEqualTo(Metric.DIRECTION_WORST); 41 | assertThat(mutationsNotCovered.getQualitative()).isFalse(); 42 | assertThat(mutationsNotCovered.getDomain()).isEqualTo(PitestMetrics.PITEST_DOMAIN); 43 | } 44 | 45 | @Test 46 | public void verifyMetricsSize() { 47 | // given 48 | 49 | // when 50 | List metrics = new PitestMetrics().getMetrics(); 51 | 52 | // then 53 | assertThat(metrics).hasSize(8); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/pitest/PitestPluginTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Pitest Plugin 3 | * Copyright (C) 2009-2018 Vinod Anandan 4 | * vinod@owasp.org 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 org.sonar.plugins.pitest; 21 | 22 | import org.junit.Test; 23 | import org.sonar.api.Plugin; 24 | import org.sonar.api.SonarQubeSide; 25 | import org.sonar.api.SonarRuntime; 26 | import org.sonar.api.internal.SonarRuntimeImpl; 27 | import org.sonar.api.utils.Version; 28 | 29 | import static org.assertj.core.api.Assertions.assertThat; 30 | 31 | public class PitestPluginTest { 32 | 33 | @Test 34 | public void test_scanner_side_plugin_extensions_compatible_with_6_7() { 35 | 36 | PitestPlugin underTest = new PitestPlugin(); 37 | SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.create(6, 7), SonarQubeSide.SCANNER); 38 | Plugin.Context context = new Plugin.Context(runtime); 39 | underTest.define(context); 40 | assertThat(context.getExtensions()).hasSize(9); 41 | } 42 | 43 | @Test 44 | public void test_compute_engine_side_plugin_extensions_compatible_with_6_7() { 45 | 46 | PitestPlugin underTest = new PitestPlugin(); 47 | SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.create(6, 7), SonarQubeSide.COMPUTE_ENGINE); 48 | Plugin.Context context = new Plugin.Context(runtime); 49 | underTest.define(context); 50 | assertThat(context.getExtensions()).hasSize(9); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/pitest/PitestRulesDefinitionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Pitest Plugin 3 | * Copyright (C) 2009-2018 Vinod Anandan 4 | * vinod@owasp.org 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 org.sonar.plugins.pitest; 21 | 22 | import java.util.List; 23 | import org.junit.Test; 24 | import org.sonar.api.server.rule.RulesDefinition; 25 | import org.sonar.api.server.rule.RulesDefinition.Repository; 26 | import org.sonar.api.server.rule.RulesDefinition.Rule; 27 | 28 | import static org.assertj.core.api.Assertions.assertThat; 29 | 30 | public class PitestRulesDefinitionTest { 31 | 32 | @Test 33 | public void contextContainsPitestRulesRepository() { 34 | // given 35 | RulesDefinition.Context context = createContext(); 36 | PitestRulesDefinition rulesDefinition = new PitestRulesDefinition(); 37 | 38 | // when 39 | // PitestSensor sensor = new PitestSensor(configuration, mockXmlReportParserOnJavaFiles(), mockRulesProfile(true, false), 40 | // mockXmlReportFinder(), context.fileSystem()); 41 | rulesDefinition.define(context); 42 | 43 | // then 44 | Repository repository = context.repository(PitestConstants.REPOSITORY_KEY); 45 | 46 | assertThat(repository.key()).isEqualTo(PitestConstants.REPOSITORY_KEY); 47 | } 48 | 49 | @Test 50 | public void pitestRepositoryContainsTwo_Rules() { 51 | // given 52 | RulesDefinition.Context context = createContext(); 53 | PitestRulesDefinition rulesDefinition = new PitestRulesDefinition(); 54 | 55 | // when 56 | rulesDefinition.define(context); 57 | Repository repository = context.repository(PitestConstants.REPOSITORY_KEY); 58 | 59 | // then 60 | assertThat(repository.rules()).hasSize(2); 61 | } 62 | 63 | @Test 64 | public void rulesContainTestQualityTag() { 65 | // given 66 | RulesDefinition.Context context = createContext(); 67 | PitestRulesDefinition rulesDefinition = new PitestRulesDefinition(); 68 | 69 | // when 70 | rulesDefinition.define(context); 71 | List rules = context.repository(PitestConstants.REPOSITORY_KEY).rules(); 72 | 73 | // then 74 | for (Rule rule : rules) { 75 | assertThat(rule.tags()).contains(PitestRulesDefinition.TAG_TEST_QUALITY); 76 | } 77 | 78 | } 79 | 80 | private RulesDefinition.Context createContext() { 81 | RulesDefinition.Context context = new RulesDefinition.Context(); 82 | return context; 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/pitest/domain/MutantLocationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Pitest Plugin 3 | * Copyright (C) 2009-2018 Vinod Anandan 4 | * vinod@owasp.org 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 org.sonar.plugins.pitest.domain; 21 | 22 | import org.junit.Test; 23 | 24 | import static org.assertj.core.api.Assertions.assertThat; 25 | 26 | public class MutantLocationTest { 27 | 28 | @Test 29 | public void should_calculate_relative_path_java() { 30 | // given 31 | MutantLocation mutantLocation = new TestMutantLocationBuilder().sourceFile("Bar.java").className("com.foo.Bar").build(); 32 | 33 | // when 34 | String path = mutantLocation.getRelativePath(); 35 | 36 | // then 37 | assertThat(path).isEqualTo("com/foo/Bar.java"); 38 | } 39 | 40 | @Test 41 | public void should_calculate_relative_path_java_inner_class() { 42 | // given 43 | MutantLocation mutantLocation = new TestMutantLocationBuilder().sourceFile("Bar.java").className("com.foo.Bar$1").build(); 44 | 45 | // when 46 | String path = mutantLocation.getRelativePath(); 47 | 48 | // then 49 | assertThat(path).isEqualTo("com/foo/Bar.java"); 50 | } 51 | 52 | @Test 53 | public void should_calculate_relative_path_kotlin() { 54 | // given 55 | MutantLocation mutantLocation = new TestMutantLocationBuilder().sourceFile("MainKotlin.kt").className("some.Hello").build(); 56 | 57 | // when 58 | String path = mutantLocation.getRelativePath(); 59 | 60 | // then 61 | assertThat(path).isEqualTo("MainKotlin.kt"); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/pitest/domain/MutantStatusTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Pitest Plugin 3 | * Copyright (C) 2009-2018 Vinod Anandan 4 | * vinod@owasp.org 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 org.sonar.plugins.pitest.domain; 21 | 22 | import org.junit.Test; 23 | 24 | import static org.assertj.core.api.Assertions.assertThat; 25 | 26 | public class MutantStatusTest { 27 | /* 28 | * creation from PitestDetectionStatus 29 | */ 30 | @Test 31 | public void other_is_returned_for_timed_out() { 32 | MutantStatus status = MutantStatus.fromPitestDetectionStatus("TIMED_OUT"); 33 | assertThat(status).isEqualTo(MutantStatus.OTHER); 34 | } 35 | 36 | @Test 37 | public void other_is_returned_for_memory_error() { 38 | MutantStatus status = MutantStatus.fromPitestDetectionStatus("MEMORY_ERROR"); 39 | assertThat(status).isEqualTo(MutantStatus.OTHER); 40 | } 41 | 42 | @Test 43 | public void killed_is_created_correctly() { 44 | MutantStatus status = MutantStatus.fromPitestDetectionStatus("KILLED"); 45 | assertThat(status).isEqualTo(MutantStatus.KILLED); 46 | } 47 | 48 | /* 49 | * parse 50 | */ 51 | @Test 52 | public void parse_upper_case_KILLED_succeeds() { 53 | MutantStatus status = MutantStatus.parse("KILLED"); 54 | assertThat(status).isEqualTo(MutantStatus.KILLED); 55 | } 56 | 57 | @Test 58 | public void parse_lower_case_killed_returns_unknown() { 59 | MutantStatus status = MutantStatus.parse("killed"); 60 | assertThat(status).isEqualTo(MutantStatus.UNKNOWN); 61 | } 62 | 63 | @Test 64 | public void parse_empty_string_returns_unknown() { 65 | MutantStatus status = MutantStatus.parse(""); 66 | assertThat(status).isEqualTo(MutantStatus.UNKNOWN); 67 | } 68 | 69 | @Test 70 | public void parse_null_returns_unknown() { 71 | MutantStatus status = MutantStatus.parse(null); 72 | assertThat(status).isEqualTo(MutantStatus.UNKNOWN); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/pitest/domain/MutantTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Pitest Plugin 3 | * Copyright (C) 2009-2018 Vinod Anandan 4 | * vinod@owasp.org 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 org.sonar.plugins.pitest.domain; 21 | 22 | import org.junit.Test; 23 | 24 | import static org.assertj.core.api.Assertions.assertThat; 25 | 26 | public class MutantTest { 27 | 28 | @Test 29 | public void should_get_path_to_java_source_file() { 30 | // given 31 | Mutant mutant = new TestMutantBuilder().className("com.foo.Bar").sourceFile("Bar.java").build(); 32 | // when 33 | String path = mutant.sourceRelativePath(); 34 | // then 35 | assertThat(path).isEqualTo("com/foo/Bar.java"); 36 | } 37 | 38 | @Test 39 | public void should_get_path_to_source_file_for_an_inner_class() { 40 | // given 41 | Mutant mutant = new TestMutantBuilder().className("com.foo.Bar$1").sourceFile("Bar.java").build(); 42 | // when 43 | String path = mutant.sourceRelativePath(); 44 | // then 45 | assertThat(path).isEqualTo("com/foo/Bar.java"); 46 | } 47 | 48 | @Test 49 | public void verify_description() { 50 | // given 51 | Mutant mutant = new TestMutantBuilder().mutator(Mutator.CONSTRUCTOR_CALLS).description("description").build(); 52 | // when 53 | String path = mutant.violationDescription(); 54 | // then 55 | assertThat(path).isEqualTo("A constructor call has been removed without breaking the tests [description]"); 56 | } 57 | 58 | @Test 59 | public void verify_json() { 60 | // given 61 | Mutant mutant = new TestMutantBuilder().mutantStatus(MutantStatus.SURVIVED).className("com.foo.Bar").mutatedMethod("mutatedMethod").lineNumber(17).mutator(Mutator.CONSTRUCTOR_CALLS) 62 | .sourceFile("Bar.kt").build(); 63 | // when 64 | String string = mutant.toString(); 65 | // then 66 | assertThat(string).isEqualTo( 67 | "{ \"d\" : true, \"s\" : \"SURVIVED\", \"c\" : \"com.foo.Bar\", \"mname\" : \"Constructor Calls Mutator\", \"mdesc\" : \"A constructor call has been removed\", \"sourceFile\" : \"Bar.kt\", \"mmethod\" : \"mutatedMethod\", \"l\" : \"17\" }"); 68 | } 69 | 70 | @Test 71 | public void verify_json_with_killing_test() { 72 | // given 73 | Mutant mutant = new TestMutantBuilder().mutantStatus(MutantStatus.KILLED).className("com.foo.Bar").mutatedMethod("mutatedMethod").lineNumber(17).mutator(Mutator.CONSTRUCTOR_CALLS) 74 | .sourceFile("Bar.kt").killingTest("killingTest").build(); 75 | // when 76 | String string = mutant.toString(); 77 | // then 78 | assertThat(string).isEqualTo( 79 | "{ \"d\" : true, \"s\" : \"KILLED\", \"c\" : \"com.foo.Bar\", \"mname\" : \"Constructor Calls Mutator\", \"mdesc\" : \"A constructor call has been removed\", \"sourceFile\" : \"Bar.kt\", \"mmethod\" : \"mutatedMethod\", \"l\" : \"17\", \"killtest\" : \"killingTest\" }"); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/pitest/domain/MutatorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Pitest Plugin 3 | * Copyright (C) 2009-2018 Vinod Anandan 4 | * vinod@owasp.org 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 org.sonar.plugins.pitest.domain; 21 | 22 | import java.util.Arrays; 23 | import java.util.List; 24 | import org.junit.Test; 25 | 26 | import static org.assertj.core.api.Assertions.assertThat; 27 | 28 | public class MutatorTest { 29 | 30 | @Test 31 | public void parse_of_invalid_key_returns_unknown() { 32 | // given 33 | 34 | // when 35 | Mutator mutator = Mutator.parse("invalid-key"); 36 | 37 | // then 38 | assertThat(mutator).isEqualTo(Mutator.UNKNOWN); 39 | } 40 | 41 | @Test 42 | public void parse_of_valid_keys_succeeds() { 43 | List validMutatorKeys = Arrays.asList( 44 | "org.pitest.mutationtest.engine.gregor.mutators.ArgumentPropagationMutator", 45 | "org.pitest.mutationtest.engine.gregor.mutators.BooleanFalseReturnValsMutator", 46 | "org.pitest.mutationtest.engine.gregor.mutators.BooleanTrueReturnValsMutator", 47 | "org.pitest.mutationtest.engine.gregor.mutators.ConditionalsBoundaryMutator", 48 | "org.pitest.mutationtest.engine.gregor.mutators.ConstructorCallMutator", 49 | "org.pitest.mutationtest.engine.gregor.mutators.EmptyObjectReturnValsMutator", 50 | "org.pitest.mutationtest.engine.gregor.mutators.IncrementsMutator", 51 | "org.pitest.mutationtest.engine.gregor.mutators.InlineConstantMutator", 52 | "org.pitest.mutationtest.engine.gregor.mutators.InvertNegsMutator", 53 | "org.pitest.mutationtest.engine.gregor.mutators.MathMutator", 54 | "org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator", 55 | "org.pitest.mutationtest.engine.gregor.mutators.NonVoidMethodCallMutator", 56 | "org.pitest.mutationtest.engine.gregor.mutators.NullReturnValsMutator", 57 | "org.pitest.mutationtest.engine.gregor.mutators.PrimitiveReturnsMutator", 58 | "org.pitest.mutationtest.engine.gregor.mutators.RemoveConditionalMutator", 59 | "org.pitest.mutationtest.engine.gregor.mutators.ReturnValsMutator", 60 | "org.pitest.mutationtest.engine.gregor.mutators.VoidMethodCallMutator", 61 | 62 | "org.pitest.mutationtest.engine.gregor.mutators.experimental.MemberVariableMutator", 63 | "org.pitest.mutationtest.engine.gregor.mutators.experimental.NakedReceiverMutator", 64 | "org.pitest.mutationtest.engine.gregor.mutators.experimental.RemoveIncrementsMutator", 65 | "org.pitest.mutationtest.engine.gregor.mutators.experimental.RemoveSwitchMutator", 66 | "org.pitest.mutationtest.engine.gregor.mutators.experimental.SwitchMutator"); 67 | 68 | 69 | for (String key : validMutatorKeys) { 70 | assertThat(Mutator.parse(key)).isNotEqualTo(Mutator.UNKNOWN); 71 | } 72 | 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/pitest/domain/TestMutantBuilder.java: -------------------------------------------------------------------------------- 1 | package org.sonar.plugins.pitest.domain; 2 | 3 | import java.util.concurrent.ThreadLocalRandom; 4 | 5 | /* 6 | * Sonar Pitest Plugin 7 | * Copyright (C) 2009-2018 Vinod Anandan 8 | * vinod@owasp.org 9 | * 10 | * This program is free software; you can redistribute it and/or 11 | * modify it under the terms of the GNU Lesser General Public 12 | * License as published by the Free Software Foundation; either 13 | * version 3 of the License, or (at your option) any later version. 14 | * 15 | * This program is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | * Lesser General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU Lesser General Public License 21 | * along with this program; if not, write to the Free Software Foundation, 22 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 23 | */ 24 | public class TestMutantBuilder { 25 | private static final int mutatorKeyLength = Mutator.values().length; 26 | 27 | private boolean detected = true; 28 | private MutantStatus mutantStatus = MutantStatus.values()[ThreadLocalRandom.current().nextInt(0, 4)]; 29 | private TestMutantLocationBuilder mutantLocationBuilder = new TestMutantLocationBuilder(); 30 | private Mutator mutator = Mutator.values()[ThreadLocalRandom.current().nextInt(0, mutatorKeyLength)]; 31 | private int index = ThreadLocalRandom.current().nextInt(0, 10); 32 | private String killingTest = null; 33 | private String description = random("description"); 34 | 35 | public TestMutantBuilder detected(boolean detected) { 36 | this.detected = detected; 37 | return this; 38 | } 39 | 40 | public TestMutantBuilder mutantStatus(MutantStatus mutantStatus) { 41 | this.mutantStatus = mutantStatus; 42 | return this; 43 | } 44 | 45 | public TestMutantBuilder className(String className) { 46 | this.mutantLocationBuilder.className(className); 47 | return this; 48 | } 49 | 50 | public TestMutantBuilder sourceFile(String sourceFile) { 51 | this.mutantLocationBuilder.sourceFile(sourceFile); 52 | return this; 53 | } 54 | 55 | public TestMutantBuilder mutatedMethod(String mutatedMethod) { 56 | this.mutantLocationBuilder.mutatedMethod(mutatedMethod); 57 | return this; 58 | } 59 | 60 | public TestMutantBuilder methodDescription(String methodDescription) { 61 | this.mutantLocationBuilder.methodDescription(methodDescription); 62 | return this; 63 | } 64 | 65 | public TestMutantBuilder lineNumber(int lineNumber) { 66 | this.mutantLocationBuilder.lineNumber(lineNumber); 67 | return this; 68 | } 69 | 70 | public TestMutantBuilder mutator(Mutator mutator) { 71 | this.mutator = mutator; 72 | return this; 73 | } 74 | 75 | public TestMutantBuilder mutator(String mutatorStr) { 76 | this.mutator = Mutator.parse(mutatorStr); 77 | return this; 78 | } 79 | 80 | public TestMutantBuilder index(int index) { 81 | this.index = index; 82 | return this; 83 | } 84 | 85 | public TestMutantBuilder killingTest(String killingTest) { 86 | this.killingTest = killingTest; 87 | return this; 88 | } 89 | 90 | public TestMutantBuilder description(String description) { 91 | this.description = description; 92 | return this; 93 | } 94 | 95 | public Mutant build() { 96 | if (mutantStatus.equals(MutantStatus.KILLED)) { 97 | if (killingTest == null) { 98 | killingTest = random("killingtest"); 99 | } 100 | } 101 | return new Mutant(detected, mutantStatus, mutantLocationBuilder.build(), mutator.getKey(), index, description, killingTest); 102 | } 103 | 104 | private static String random(String in) { 105 | return in + ThreadLocalRandom.current().nextInt(0, 101); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/pitest/domain/TestMutantLocationBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Pitest Plugin 3 | * Copyright (C) 2009-2018 Vinod Anandan 4 | * vinod@owasp.org 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 org.sonar.plugins.pitest.domain; 21 | 22 | import java.util.concurrent.ThreadLocalRandom; 23 | 24 | public class TestMutantLocationBuilder { 25 | private String className = random("className"); 26 | private String sourceFile = randomSourceFile("sourceFile"); 27 | private String mutatedMethod = random("mutatedMethod"); 28 | private String methodDescription = random("methodDescription"); 29 | 30 | private int lineNumber = ThreadLocalRandom.current().nextInt(1, 101); 31 | 32 | public TestMutantLocationBuilder() { 33 | 34 | } 35 | 36 | public TestMutantLocationBuilder className(String className) { 37 | this.className = className; 38 | return this; 39 | } 40 | 41 | public TestMutantLocationBuilder sourceFile(String sourceFile) { 42 | this.sourceFile = sourceFile; 43 | return this; 44 | } 45 | 46 | public TestMutantLocationBuilder mutatedMethod(String mutatedMethod) { 47 | this.mutatedMethod = mutatedMethod; 48 | return this; 49 | } 50 | 51 | public TestMutantLocationBuilder methodDescription(String methodDescription) { 52 | this.methodDescription = methodDescription; 53 | return this; 54 | } 55 | 56 | public TestMutantLocationBuilder lineNumber(int lineNumber) { 57 | this.lineNumber = lineNumber; 58 | return this; 59 | } 60 | 61 | public MutantLocation build() { 62 | return new MutantLocation(this.className, this.sourceFile, this.mutatedMethod, this.methodDescription, this.lineNumber); 63 | } 64 | 65 | private String random(String in) { 66 | return in + ThreadLocalRandom.current().nextInt(0, 101); 67 | } 68 | 69 | private String randomSourceFile(String in) { 70 | StringBuilder builder = new StringBuilder(random(in)); 71 | return builder.append(randomExtension()).toString(); 72 | } 73 | 74 | private String randomExtension() { 75 | return ThreadLocalRandom.current().nextInt(0, 1) == 0 ? ".java" : ".kt"; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/pitest/scanner/ProjectReportTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Pitest Plugin 3 | * Copyright (C) 2009-2018 Vinod Anandan 4 | * vinod@owasp.org 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 org.sonar.plugins.pitest.scanner; 21 | 22 | import java.util.Arrays; 23 | import java.util.Collection; 24 | import org.junit.Test; 25 | import org.sonar.plugins.pitest.domain.Mutant; 26 | import org.sonar.plugins.pitest.domain.TestMutantBuilder; 27 | 28 | import static org.assertj.core.api.Assertions.assertThat; 29 | 30 | public class ProjectReportTest { 31 | 32 | @Test 33 | public void should_organize_by_relative_path() { 34 | // given 35 | Mutant m1 = new TestMutantBuilder().className("com.foo.bar.Toto").sourceFile("Toto.java").build(); 36 | Mutant m2 = new TestMutantBuilder().className("com.foo.bar.Toto").sourceFile("Toto.java").build(); 37 | Mutant m3 = new TestMutantBuilder().className("com.foo.bar.differentPackage.Toto").sourceFile("Toto.java").build(); 38 | Mutant m4 = new TestMutantBuilder().className("com.foo.bar.qix.Tata").sourceFile("Tata.java").build(); 39 | Mutant m5 = new TestMutantBuilder().className("bar.Toto").sourceFile("Toto.kt").build(); 40 | 41 | // when 42 | ProjectReport report = new ProjectReport(Arrays.asList(m1, m2, m3, m4, m5)); 43 | 44 | // then 45 | Collection sourceFileReports = report.getSourceFileReports(); 46 | assertThat(sourceFileReports).hasSize(4); 47 | assertThat(sourceFileReports) 48 | .usingElementComparatorOnFields("sourceFileRelativePath") 49 | .containsOnly( 50 | new SourceFileReport("com/foo/bar/Toto.java"), 51 | new SourceFileReport("com/foo/bar/differentPackage/Toto.java"), 52 | new SourceFileReport("com/foo/bar/qix/Tata.java"), 53 | new SourceFileReport("Toto.kt")); 54 | } 55 | 56 | @Test 57 | public void should_collect_in_same_report_when_in_same_file() { 58 | // given 59 | Mutant m1 = new TestMutantBuilder().className("com.foo.bar.Toto").sourceFile("Toto.java").build(); 60 | Mutant m2 = new TestMutantBuilder().className("com.foo.bar.Toto").sourceFile("Toto.java").build(); 61 | 62 | // when 63 | ProjectReport report = new ProjectReport(Arrays.asList(m1, m2)); 64 | 65 | // then 66 | Collection sourceFileReports = report.getSourceFileReports(); 67 | assertThat(sourceFileReports).hasSize(1); 68 | assertThat(sourceFileReports) 69 | .usingElementComparatorOnFields("sourceFileRelativePath") 70 | .containsOnly( 71 | new SourceFileReport("com/foo/bar/Toto.java")); 72 | 73 | SourceFileReport sourceFileReport = sourceFileReports.iterator().next(); 74 | assertThat(sourceFileReport.getMutationsTotal()).isEqualTo(2); 75 | 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/pitest/scanner/SourceFileReportTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Pitest Plugin 3 | * Copyright (C) 2009-2018 Vinod Anandan 4 | * vinod@owasp.org 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 org.sonar.plugins.pitest.scanner; 21 | 22 | import org.junit.Test; 23 | import org.sonar.plugins.pitest.domain.Mutant; 24 | import org.sonar.plugins.pitest.domain.MutantStatus; 25 | import org.sonar.plugins.pitest.domain.Mutator; 26 | import org.sonar.plugins.pitest.domain.TestMutantBuilder; 27 | 28 | import static org.assertj.core.api.Assertions.assertThat; 29 | 30 | public class SourceFileReportTest { 31 | 32 | public static final String INLINE_CONSTANT_MUTATOR = "org.pitest.mutationtest.engine.gregor.mutators.InlineConstantMutator"; 33 | public static final String RETURN_VALS_MUTATOR = "org.pitest.mutationtest.engine.gregor.mutators.ReturnValsMutator"; 34 | 35 | @Test 36 | public void should_have_correct_relative_path() { 37 | // given 38 | Mutant m1 = new TestMutantBuilder().detected(true).mutantStatus(MutantStatus.KILLED).className("com.foo.bar.Qix").mutatedMethod("mutatedMethod").lineNumber(17) 39 | .mutator(Mutator.INLINE_CONSTS).sourceFile("Qix.java").killingTest("killingtest93").build(); 40 | 41 | // when 42 | SourceFileReport fileMutants = new SourceFileReport("com/foo/bar/Qix.java"); 43 | fileMutants.addMutant(m1); 44 | 45 | // then 46 | assertThat(fileMutants.getRelativePath()).isEqualTo("com/foo/bar/Qix.java"); 47 | } 48 | 49 | @Test 50 | public void should_generate_a_json_string_with_all_data_from_one_line() { 51 | // given 52 | Mutant m1 = new TestMutantBuilder().detected(true).mutantStatus(MutantStatus.KILLED).className("com.foo.bar.Qix").mutatedMethod("mutatedMethod").lineNumber(17) 53 | .mutator(Mutator.INLINE_CONSTS).sourceFile("Qix.java").killingTest("killingtest93").build(); 54 | Mutant m2 = new TestMutantBuilder().detected(false).mutantStatus(MutantStatus.SURVIVED).className("com.foo.bar.Qix").mutatedMethod("anotherMutatedMethod").lineNumber(17) 55 | .mutator(Mutator.RETURN_VALS).sourceFile("Qix.java").build(); 56 | 57 | // when 58 | SourceFileReport fileMutants = new SourceFileReport("com/foo/bar/Qix.java"); 59 | fileMutants.addMutant(m1); 60 | fileMutants.addMutant(m2); 61 | // then 62 | String result = fileMutants.toJSON(); 63 | assertThat(result) 64 | .isEqualTo("{\"17\":[" + 65 | "{ \"d\" : true, \"s\" : \"KILLED\", \"c\" : \"com.foo.bar.Qix\", \"mname\" : \"Inline Constant Mutator\", \"mdesc\" : \"An inline constant has been changed\", \"sourceFile\" : \"Qix.java\", \"mmethod\" : \"mutatedMethod\", \"l\" : \"17\", \"killtest\" : \"killingtest93\" }," 66 | + 67 | "{ \"d\" : false, \"s\" : \"SURVIVED\", \"c\" : \"com.foo.bar.Qix\", \"mname\" : \"Return Values Mutator\", \"mdesc\" : \"The return value of a method call has been replaced\", \"sourceFile\" : \"Qix.java\", \"mmethod\" : \"anotherMutatedMethod\", \"l\" : \"17\" }" 68 | + 69 | "]}"); 70 | } 71 | 72 | @Test 73 | public void should_generate_a_json_string_with_one_mutant() { 74 | // given 75 | Mutant m1 = new TestMutantBuilder().detected(true).mutantStatus(MutantStatus.KILLED).className("com.foo.bar.Qix").mutatedMethod("mutatedMethod").lineNumber(17) 76 | .mutator(Mutator.INLINE_CONSTS).sourceFile("Qix.java").killingTest("killingtest93").build(); 77 | 78 | // when 79 | SourceFileReport fileMutants = new SourceFileReport("com/foo/bar/Qix.java"); 80 | fileMutants.addMutant(m1); 81 | // then 82 | String result = fileMutants.toJSON(); 83 | assertThat(result) 84 | .isEqualTo("{\"17\":[" + 85 | "{ \"d\" : true, \"s\" : \"KILLED\", \"c\" : \"com.foo.bar.Qix\", \"mname\" : \"Inline Constant Mutator\", \"mdesc\" : \"An inline constant has been changed\", \"sourceFile\" : \"Qix.java\", \"mmethod\" : \"mutatedMethod\", \"l\" : \"17\", \"killtest\" : \"killingtest93\" }" 86 | + "]}"); 87 | } 88 | 89 | @Test 90 | public void should_generate_an_empty_json_string_when_no_mutant_found() { 91 | // given 92 | SourceFileReport fileMutants = new SourceFileReport("FooBar.java"); 93 | // when 94 | String json = fileMutants.toJSON(); 95 | // then 96 | assertThat(json).isNullOrEmpty(); 97 | } 98 | 99 | @Test(expected = IllegalArgumentException.class) 100 | public void fails_if_relative_paths_dont_match() { 101 | // given 102 | Mutant m1 = new TestMutantBuilder().mutantStatus(MutantStatus.KILLED).className("com.foo.bar.Foo").mutatedMethod("mutatedMethod").lineNumber(42).mutator(Mutator.EXPERIMENTAL_MEMBER_VARIABLE) 103 | .sourceFile("Foo.kt").build(); 104 | 105 | // when 106 | SourceFileReport fileMutants = new SourceFileReport("com/foo/bar/Qix.java"); 107 | fileMutants.addMutant(m1); 108 | } 109 | 110 | @Test 111 | public void should_collect_mutant_metrics() { 112 | // given 113 | 114 | Mutant m1 = new TestMutantBuilder().mutantStatus(MutantStatus.KILLED).className("com.foo.bar.Qix").mutatedMethod("mutatedMethod").lineNumber(17).mutator(Mutator.INLINE_CONSTS) 115 | .sourceFile("Qix.java").build(); 116 | Mutant m2 = new TestMutantBuilder().mutantStatus(MutantStatus.SURVIVED).className("com.foo.bar.Qix").mutatedMethod("mutatedMethod").lineNumber(17).mutator(Mutator.RETURN_VALS) 117 | .sourceFile("Qix.java").build(); 118 | Mutant m3 = new TestMutantBuilder().mutantStatus(MutantStatus.KILLED).className("com.foo.bar.Qix").mutatedMethod("mutatedMethod").lineNumber(42).mutator(Mutator.EXPERIMENTAL_MEMBER_VARIABLE) 119 | .sourceFile("Qix.java").build(); 120 | Mutant m4 = new TestMutantBuilder().mutantStatus(MutantStatus.NO_COVERAGE).className("com.foo.bar.Qix").mutatedMethod("mutatedMethod").lineNumber(42) 121 | .mutator(Mutator.EXPERIMENTAL_MEMBER_VARIABLE).sourceFile("Qix.java").build(); 122 | Mutant m5 = new TestMutantBuilder().mutantStatus(MutantStatus.UNKNOWN).className("com.foo.bar.Qix").mutatedMethod("mutatedMethod").lineNumber(42) 123 | .mutator(Mutator.EXPERIMENTAL_MEMBER_VARIABLE).sourceFile("Qix.java").build(); 124 | Mutant m6 = new TestMutantBuilder().mutantStatus(MutantStatus.OTHER).className("com.foo.bar.Qix").mutatedMethod("mutatedMethod").lineNumber(42).mutator(Mutator.EXPERIMENTAL_MEMBER_VARIABLE) 125 | .sourceFile("Qix.java").build(); 126 | 127 | SourceFileReport sourceFileReport = new SourceFileReport("com/foo/bar/Qix.java"); 128 | 129 | // when 130 | sourceFileReport.addMutant(m1); 131 | sourceFileReport.addMutant(m2); 132 | sourceFileReport.addMutant(m3); 133 | sourceFileReport.addMutant(m4); 134 | sourceFileReport.addMutant(m5); 135 | sourceFileReport.addMutant(m6); 136 | 137 | // then 138 | assertThat(sourceFileReport.getMutationsTotal()).isEqualTo(6); 139 | assertThat(sourceFileReport.getMutants()).hasSize(6); 140 | assertThat(sourceFileReport.getMutationsKilled()).isEqualTo(2); 141 | assertThat(sourceFileReport.getMutationsSurvived()).isEqualTo(1); 142 | assertThat(sourceFileReport.getMutationsNoCoverage()).isEqualTo(1); 143 | assertThat(sourceFileReport.getMutationsUnknown()).isEqualTo(1); 144 | assertThat(sourceFileReport.getMutationsOther()).isEqualTo(1); 145 | /* 146 | * NO_COVERAGE("NO_COVERAGE"), 147 | * KILLED("KILLED"), 148 | * SURVIVED("SURVIVED"), 149 | * OTHER("TIMED_OUT", "NON_VIABLE", "MEMORY_ERROR", "RUN_ERROR"), 150 | * UNKNOWN; 151 | */ 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/pitest/scanner/XmlReportFinderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Pitest Plugin 3 | * Copyright (C) 2009-2018 Vinod Anandan 4 | * vinod@owasp.org 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 org.sonar.plugins.pitest.scanner; 21 | 22 | import com.google.common.io.Resources; 23 | import java.io.File; 24 | import org.junit.Test; 25 | 26 | import static org.assertj.core.api.Assertions.assertThat; 27 | 28 | public class XmlReportFinderTest { 29 | 30 | @Test 31 | public void should_find_latest_report_file_with_one_timestamped_folder() { 32 | // given 33 | XmlReportFinder finder = new XmlReportFinder(); 34 | File reportDirectory = new File(Resources.getResource("test-pit-reports-1").getFile()); 35 | 36 | // when 37 | File report = finder.findReport(reportDirectory); 38 | 39 | // then 40 | assertThat(report).isNotNull(); 41 | report.getAbsolutePath().endsWith("123/mutations.xml"); 42 | } 43 | 44 | @Test 45 | public void should_find_latest_report_file_with_two_timestamped_folders() { 46 | // given 47 | XmlReportFinder finder = new XmlReportFinder(); 48 | File reportDirectory = new File(Resources.getResource("test-pit-reports-2").getFile()); 49 | new File(Resources.getResource("test-pit-reports-2/123/mutations.xml").getFile()).setLastModified(400); 50 | new File(Resources.getResource("test-pit-reports-2/124/mutations.xml").getFile()).setLastModified(500); 51 | 52 | // when 53 | File report = finder.findReport(reportDirectory); 54 | 55 | // then 56 | assertThat(report).isNotNull(); 57 | report.getAbsolutePath().endsWith("124/mutations.xml"); 58 | } 59 | 60 | @Test 61 | public void should_find_latest_report_file_with_inconsistent_timestamp() { 62 | // given 63 | XmlReportFinder finder = new XmlReportFinder(); 64 | File reportDirectory = new File(Resources.getResource("test-pit-reports-2").getFile()); 65 | new File(Resources.getResource("test-pit-reports-2/123/mutations.xml").getFile()).setLastModified(600); 66 | new File(Resources.getResource("test-pit-reports-2/124/mutations.xml").getFile()).setLastModified(500); 67 | 68 | // when 69 | File report = finder.findReport(reportDirectory); 70 | 71 | // then 72 | assertThat(report).isNotNull(); 73 | report.getAbsolutePath().endsWith("123/mutations.xml"); 74 | } 75 | 76 | @Test 77 | public void should_return_null_if_no_report() { 78 | // given 79 | XmlReportFinder finder = new XmlReportFinder(); 80 | File directory = new File(Resources.getResource("fake_libs").getFile()); 81 | 82 | // when 83 | File report = finder.findReport(directory); 84 | 85 | // then 86 | assertThat(report).isNull(); 87 | } 88 | 89 | @Test 90 | public void should_return_null_if_input_is_not_a_directory() { 91 | // given 92 | XmlReportFinder finder = new XmlReportFinder(); 93 | File directory = new File(Resources.getResource("Maze.kt").getFile()); 94 | 95 | // when 96 | File report = finder.findReport(directory); 97 | 98 | // then 99 | assertThat(report).isNull(); 100 | } 101 | 102 | @Test 103 | public void should_return_null_if_directory_does_not_exist() { 104 | // given 105 | XmlReportFinder finder = new XmlReportFinder(); 106 | File directory = new File("imaginary"); 107 | 108 | // when 109 | File report = finder.findReport(directory); 110 | 111 | // then 112 | assertThat(report).isNull(); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/test/java/org/sonar/plugins/pitest/scanner/XmlReportParserTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar Pitest Plugin 3 | * Copyright (C) 2009-2018 Vinod Anandan 4 | * vinod@owasp.org 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 org.sonar.plugins.pitest.scanner; 21 | 22 | import com.google.common.io.Resources; 23 | import java.io.File; 24 | import java.util.Collection; 25 | import org.junit.Before; 26 | import org.junit.Test; 27 | import org.sonar.plugins.pitest.domain.Mutant; 28 | import org.sonar.plugins.pitest.domain.MutantStatus; 29 | import org.sonar.plugins.pitest.domain.TestMutantBuilder; 30 | 31 | import static org.assertj.core.api.Assertions.assertThat; 32 | import static org.assertj.core.api.Fail.failBecauseExceptionWasNotThrown; 33 | 34 | public class XmlReportParserTest { 35 | private static final String MODULE_BASE_DIR = "src/test/resources/xml-report-parser-test"; 36 | 37 | private XmlReportParser parser; 38 | 39 | @Before 40 | public void setUp() { 41 | parser = new XmlReportParser(); 42 | } 43 | 44 | @Test 45 | public void should_parse_report_and_find_mutants() { 46 | // given 47 | File report = new File(MODULE_BASE_DIR, "mutations.xml"); 48 | Mutant targetMutant = new TestMutantBuilder().detected(false).mutantStatus(MutantStatus.SURVIVED).sourceFile("PitestSensor.java") 49 | .className("org.sonar.plugins.pitest.scanner.PitestSensor") 50 | .mutatedMethod("addCoverageForKilledMutants") 51 | .methodDescription("(Lorg/sonar/api/batch/sensor/SensorContext;Lorg/sonar/api/batch/fs/InputFile;Lorg/sonar/plugins/pitest/scanner/SourceFileReport;)V") 52 | .lineNumber(212).mutator("org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator").index(15).killingTest("").description("negated conditional") 53 | .build(); 54 | 55 | // when 56 | Collection mutants = parser.parse(report); 57 | 58 | // then 59 | assertThat(mutants).hasSize(1); 60 | // FIXME: find out why mutantLocation comparison fails 61 | assertThat(mutants).usingElementComparatorIgnoringFields("mutantLocation").contains(targetMutant); 62 | } 63 | 64 | @Test 65 | public void should_parse_report_and_find_mutants_if_elements_are_unordered() { 66 | // given 67 | File report = new File(MODULE_BASE_DIR, "mutations-unordered.xml"); 68 | Mutant targetMutant = new TestMutantBuilder().detected(false).mutantStatus(MutantStatus.SURVIVED).sourceFile("PitestSensor.java") 69 | .className("org.sonar.plugins.pitest.scanner.PitestSensor") 70 | .mutatedMethod("addCoverageForKilledMutants") 71 | .methodDescription("(Lorg/sonar/api/batch/sensor/SensorContext;Lorg/sonar/api/batch/fs/InputFile;Lorg/sonar/plugins/pitest/scanner/SourceFileReport;)V") 72 | .lineNumber(212).mutator("org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator").index(15).killingTest("").description("negated conditional") 73 | .build(); 74 | 75 | // when 76 | Collection mutants = parser.parse(report); 77 | 78 | // then 79 | assertThat(mutants).hasSize(1); 80 | assertThat(mutants).usingElementComparatorIgnoringFields("mutantLocation").contains(targetMutant); 81 | } 82 | 83 | @Test(expected = IllegalArgumentException.class) 84 | public void should_throw_exception_if_file_is_missing() { 85 | // given 86 | 87 | // when 88 | new File(Resources.getResource("imaginary").getFile()); 89 | 90 | // then 91 | failBecauseExceptionWasNotThrown(IllegalArgumentException.class); 92 | } 93 | 94 | @Test(expected = IllegalStateException.class) 95 | public void should_throw_exception_if_file_is_invalid() { 96 | // given 97 | 98 | // when 99 | File report = new File(Resources.getResource("mutations-invalid-format.xml").getFile()); 100 | parser.parse(report); 101 | 102 | // then 103 | failBecauseExceptionWasNotThrown(IllegalStateException.class); 104 | } 105 | 106 | @Test 107 | public void should_log_but_not_throw_exception_if_line_number_parsing_fails() { 108 | // given 109 | 110 | // when 111 | File report = new File(Resources.getResource("mutations-invalid-format-line-number.xml").getFile()); 112 | parser.parse(report); 113 | 114 | // then 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/test/resources/Maze.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Ref: https://try.kotlinlang.org/#/Examples/Longer%20examples/Maze/Maze.kt 3 | * Let's Walk Through a Maze. 4 | * 5 | * Imagine there is a maze whose walls are the big 'O' letters. 6 | * Now, I stand where a big 'I' stands and some cool prize lies 7 | * somewhere marked with a '$' sign. Like this: 8 | * 9 | * OOOOOOOOOOOOOOOOO 10 | * O O 11 | * O$ O O 12 | * OOOOO O 13 | * O O 14 | * O OOOOOOOOOOOOOO 15 | * O O I O 16 | * O O 17 | * OOOOOOOOOOOOOOOOO 18 | * 19 | * I want to get the prize, and this program helps me do so as soon 20 | * as I possibly can by finding a shortest path through the maze. 21 | */ 22 | package maze 23 | 24 | import java.util.* 25 | 26 | /** 27 | * Declare a point class. 28 | */ 29 | data class Point(val i: Int, val j: Int) 30 | 31 | /** 32 | * This function looks for a path from max.start to maze.end through 33 | * free space (a path does not go through walls). One can move only 34 | * straight up, down, left or right, no diagonal moves allowed. 35 | */ 36 | fun findPath(maze: Maze): List? { 37 | val previous = hashMapOf() 38 | 39 | val queue = LinkedList() 40 | val visited = hashSetOf() 41 | 42 | queue.offer(maze.start) 43 | visited.add(maze.start) 44 | while (!queue.isEmpty()) { 45 | val cell = queue.poll() 46 | if (cell == maze.end) break 47 | 48 | for (newCell in maze.neighbors(cell.i, cell.j)) { 49 | if (newCell in visited) continue 50 | previous.put(newCell, cell) 51 | queue.offer(newCell) 52 | visited.add(cell) 53 | } 54 | } 55 | 56 | if (previous[maze.end] == null) return null 57 | 58 | val path = arrayListOf() 59 | var current = previous[maze.end]!! 60 | while (current != maze.start) { 61 | path.add(0, current) 62 | current = previous[current]!! 63 | } 64 | return path 65 | } 66 | 67 | /** 68 | * Find neighbors of the (i, j) cell that are not walls 69 | */ 70 | fun Maze.neighbors(i: Int, j: Int): List { 71 | val result = arrayListOf() 72 | addIfFree(i - 1, j, result) 73 | addIfFree(i, j - 1, result) 74 | addIfFree(i + 1, j, result) 75 | addIfFree(i, j + 1, result) 76 | return result 77 | } 78 | 79 | fun Maze.addIfFree(i: Int, j: Int, result: MutableList) { 80 | if (i !in 0..height - 1) return 81 | if (j !in 0..width - 1) return 82 | if (walls[i][j]) return 83 | 84 | result.add(Point(i, j)) 85 | } 86 | 87 | /** 88 | * A data class that represents a maze 89 | */ 90 | class Maze( 91 | // Number or columns 92 | val width: Int, 93 | // Number of rows 94 | val height: Int, 95 | // true for a wall, false for free space 96 | val walls: Array, 97 | // The starting point (must not be a wall) 98 | val start: Point, 99 | // The target point (must not be a wall) 100 | val end: Point 101 | ) { 102 | } 103 | 104 | /** A few maze examples here */ 105 | fun main(args: Array) { 106 | walkThroughMaze("I $") 107 | walkThroughMaze("I O $") 108 | walkThroughMaze(""" 109 | O $ 110 | O 111 | O 112 | O 113 | O I 114 | """) 115 | walkThroughMaze(""" 116 | OOOOOOOOOOO 117 | O $ O 118 | OOOOOOO OOO 119 | O O 120 | OOOOO OOOOO 121 | O O 122 | O OOOOOOOOO 123 | O OO 124 | OOOOOO IO 125 | """) 126 | walkThroughMaze(""" 127 | OOOOOOOOOOOOOOOOO 128 | O O 129 | O$ O O 130 | OOOOO O 131 | O O 132 | O OOOOOOOOOOOOOO 133 | O O I O 134 | O O 135 | OOOOOOOOOOOOOOOOO 136 | """) 137 | } 138 | 139 | // UTILITIES 140 | 141 | fun walkThroughMaze(str: String) { 142 | val maze = makeMaze(str) 143 | 144 | println("Maze:") 145 | val path = findPath(maze) 146 | for (i in 0..maze.height - 1) { 147 | for (j in 0..maze.width - 1) { 148 | val cell = Point(i, j) 149 | print( 150 | if (maze.walls[i][j]) "O" 151 | else if (cell == maze.start) "I" 152 | else if (cell == maze.end) "$" 153 | else if (path != null && path.contains(cell)) "~" 154 | else " " 155 | ) 156 | } 157 | println("") 158 | } 159 | println("Result: " + if (path == null) "No path" else "Path found") 160 | println("") 161 | } 162 | 163 | 164 | /** 165 | * A maze is encoded in the string s: the big 'O' letters are walls. 166 | * I stand where a big 'I' stands and the prize is marked with 167 | * a '$' sign. 168 | * 169 | * Example: 170 | * 171 | * OOOOOOOOOOOOOOOOO 172 | * O O 173 | * O$ O O 174 | * OOOOO O 175 | * O O 176 | * O OOOOOOOOOOOOOO 177 | * O O I O 178 | * O O 179 | * OOOOOOOOOOOOOOOOO 180 | */ 181 | fun makeMaze(s: String): Maze { 182 | val lines = s.split('\n') 183 | val longestLine = lines.toList().maxBy { it.length } ?: "" 184 | val data = Array(lines.size) { BooleanArray(longestLine.length) } 185 | 186 | var start: Point? = null 187 | var end: Point? = null 188 | 189 | for (line in lines.indices) { 190 | for (x in lines[line].indices) { 191 | val c = lines[line][x] 192 | when (c) { 193 | 'O' -> data[line][x] = true 194 | 'I' -> start = Point(line, x) 195 | '$' -> end = Point(line, x) 196 | } 197 | } 198 | } 199 | 200 | return Maze(longestLine.length, lines.size, data, 201 | start ?: throw IllegalArgumentException("No starting point in the maze (should be indicated with 'I')"), 202 | end ?: throw IllegalArgumentException("No goal point in the maze (should be indicated with a '$' sign)")) 203 | } 204 | -------------------------------------------------------------------------------- /src/test/resources/fake_libs/compile.fake.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VinodAnandan/sonar-pitest/495b2bb19e30870474a4e9e6043648a1a34ad6ba/src/test/resources/fake_libs/compile.fake.jar -------------------------------------------------------------------------------- /src/test/resources/fake_libs/runtime.fake.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VinodAnandan/sonar-pitest/495b2bb19e30870474a4e9e6043648a1a34ad6ba/src/test/resources/fake_libs/runtime.fake.jar -------------------------------------------------------------------------------- /src/test/resources/fake_libs/system.fake.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VinodAnandan/sonar-pitest/495b2bb19e30870474a4e9e6043648a1a34ad6ba/src/test/resources/fake_libs/system.fake.jar -------------------------------------------------------------------------------- /src/test/resources/fake_libs/test.fake.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VinodAnandan/sonar-pitest/495b2bb19e30870474a4e9e6043648a1a34ad6ba/src/test/resources/fake_libs/test.fake.jar -------------------------------------------------------------------------------- /src/test/resources/mutations-elements-out-of-order.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator 5 | GallioSensor.java 6 | org.sonar.plugins.csharp.gallio.GallioSensor 7 | shouldExecuteOnProject 8 | 85 9 | 0 10 | org.sonar.plugins.csharp.gallio.GallioSensorTest.testShouldNotExecuteOnProjectIfReuseReports(org.sonar.plugins.csharp.gallio.GallioSensorTest) 11 | 12 | -------------------------------------------------------------------------------- /src/test/resources/mutations-invalid-format-line-number.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | GallioSensor.java 5 | org.sonar.plugins.csharp.gallio.GallioSensor 6 | shouldExecuteOnProject 7 | not a number 8 | org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator 9 | 0 10 | org.sonar.plugins.csharp.gallio.GallioSensorTest.testShouldNotExecuteOnProjectIfReuseReports(org.sonar.plugins.csharp.gallio.GallioSensorTest) 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/test/resources/mutations-invalid-format.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/test/resources/mutations-kotlin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Main.java 5 | some.Main 6 | main 7 | ([Ljava/lang/String;)V 8 | 12 9 | org.pitest.mutationtest.engine.gregor.mutators.ConditionalsBoundaryMutator 10 | 11 | 14 12 | 13 | changed conditional boundary 14 | 15 | 16 | Main.java 17 | some.Main 18 | main 19 | ([Ljava/lang/String;)V 20 | 13 21 | org.pitest.mutationtest.engine.gregor.mutators.IncrementsMutator 22 | 23 | 17 24 | 25 | Changed increment from 1 to -1 26 | 27 | 28 | Main.java 29 | some.Main 30 | main 31 | ([Ljava/lang/String;)V 32 | 12 33 | org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator 34 | 35 | 14 36 | 37 | negated conditional 38 | 39 | 40 | Main.java 41 | some.Main 42 | main 43 | ([Ljava/lang/String;)V 44 | 10 45 | org.pitest.mutationtest.engine.gregor.mutators.VoidMethodCallMutator 46 | 47 | 5 48 | 49 | removed call to java/io/PrintStream::println 50 | 51 | 52 | 53 | MainKotlin.kt 54 | some.Workd 55 | world 56 | ()V 57 | 17 58 | org.pitest.mutationtest.engine.gregor.mutators.VoidMethodCallMutator 59 | 60 | 7 61 | 62 | removed call to java/io/PrintStream::print 63 | 64 | 65 | MainKotlin.kt 66 | some.World 67 | world 68 | ()V 69 | 17 70 | org.pitest.mutationtest.engine.gregor.mutators.VoidMethodCallMutator 71 | 72 | 7 73 | 74 | removed call to java/io/PrintStream::print 75 | 76 | 77 | MainKotlin.kt 78 | some.Hello 79 | hello 80 | ()V 81 | 6 82 | org.pitest.mutationtest.engine.gregor.mutators.VoidMethodCallMutator 83 | 84 | 7 85 | 86 | removed call to java/io/PrintStream::print 87 | 88 | -------------------------------------------------------------------------------- /src/test/resources/pit-reports/201710212128/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 58 | 59 | 60 | 61 | 62 |

Pit Test Coverage Report

63 | 64 |

Project Summary

65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 |
Number of ClassesLine CoverageMutation Coverage
1185%
375/440
68%
111/163
81 | 82 | 83 |

Breakdown by Package

84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 |
NameNumber of ClassesLine CoverageMutation Coverage
org.sonar.plugins.pitest3
100%
102/102
95%
19/20
org.sonar.plugins.pitest.domain3
64%
49/77
92%
23/25
org.sonar.plugins.pitest.scanner5
86%
224/261
58%
69/118
118 |
119 | 120 | 121 | 122 |
123 | 124 | Report generated by PIT 1.2.2 125 | 126 | 127 | -------------------------------------------------------------------------------- /src/test/resources/pit-reports/201710212128/org.sonar.plugins.pitest.domain/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 58 | 59 | 60 | 61 | 62 |

Pit Test Coverage Report

63 |

Package Summary

64 |

org.sonar.plugins.pitest.domain

65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 |
Number of ClassesLine CoverageMutation Coverage
364%
49/77
92%
23/25
81 | 82 | 83 |

Breakdown by Class

84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 |
NameLine CoverageMutation Coverage
Mutant.java
100%
24/24
100%
3/3
MutantStatus.java
94%
16/17
83%
10/12
Mutator.java
25%
9/36
100%
10/10
114 |
115 | 116 | 117 | 118 |
119 | 120 | Report generated by PIT 1.2.2 121 | 122 | 123 | -------------------------------------------------------------------------------- /src/test/resources/pit-reports/201710212128/org.sonar.plugins.pitest.scanner/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 58 | 59 | 60 | 61 | 62 |

Pit Test Coverage Report

63 |

Package Summary

64 |

org.sonar.plugins.pitest.scanner

65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 |
Number of ClassesLine CoverageMutation Coverage
586%
224/261
58%
69/118
81 | 82 | 83 |

Breakdown by Class

84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 |
NameLine CoverageMutation Coverage
PitestSensor.java
87%
90/104
51%
23/45
ProjectReport.java
100%
12/12
100%
4/4
SourceFileReport.java
89%
51/57
73%
16/22
XmlReportFinder.java
67%
14/21
44%
7/16
XmlReportParser.java
85%
57/67
61%
19/31
126 |
127 | 128 | 129 | 130 |
131 | 132 | Report generated by PIT 1.2.2 133 | 134 | 135 | -------------------------------------------------------------------------------- /src/test/resources/pit-reports/201710212128/org.sonar.plugins.pitest/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 58 | 59 | 60 | 61 | 62 |

Pit Test Coverage Report

63 |

Package Summary

64 |

org.sonar.plugins.pitest

65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 |
Number of ClassesLine CoverageMutation Coverage
3100%
102/102
95%
19/20
81 | 82 | 83 |

Breakdown by Class

84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 |
NameLine CoverageMutation Coverage
PitestComputer.java
100%
24/24
94%
17/18
PitestMetrics.java
100%
52/52
100%
1/1
PitestRulesDefinition.java
100%
26/26
100%
1/1
114 |
115 | 116 | 117 | 118 |
119 | 120 | Report generated by PIT 1.2.2 121 | 122 | 123 | -------------------------------------------------------------------------------- /src/test/resources/pit-reports/201710281222/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 58 | 59 | 60 | 61 | 62 |

Pit Test Coverage Report

63 | 64 |

Project Summary

65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 |
Number of ClassesLine CoverageMutation Coverage
1185%
375/440
68%
111/163
81 | 82 | 83 |

Breakdown by Package

84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 |
NameNumber of ClassesLine CoverageMutation Coverage
org.sonar.plugins.pitest3
100%
102/102
95%
19/20
org.sonar.plugins.pitest.domain3
64%
49/77
92%
23/25
org.sonar.plugins.pitest.scanner5
86%
224/261
58%
69/118
118 |
119 | 120 | 121 | 122 |
123 | 124 | Report generated by PIT 1.2.2 125 | 126 | 127 | -------------------------------------------------------------------------------- /src/test/resources/pit-reports/201710281222/mutations-small.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | PitestSensor.javaorg.sonar.plugins.pitest.scanner.PitestSensoraddCoverageForKilledMutants(Lorg/sonar/api/batch/sensor/SensorContext;Lorg/sonar/api/batch/fs/InputFile;Lorg/sonar/plugins/pitest/scanner/SourceFileReport;)V212org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator15negated conditional 4 | PitestSensor.javaorg.sonar.plugins.pitest.scanner.PitestSensoraddCoverageForKilledMutants(Lorg/sonar/api/batch/sensor/SensorContext;Lorg/sonar/api/batch/fs/InputFile;Lorg/sonar/plugins/pitest/scanner/SourceFileReport;)V214org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator26org.sonar.plugins.pitest.scanner.PitestSensorTest.verifyMeasures(org.sonar.plugins.pitest.scanner.PitestSensorTest)negated conditional 5 | PitestSensor.javaorg.sonar.plugins.pitest.scanner.PitestSensoraddCoverageForKilledMutants(Lorg/sonar/api/batch/sensor/SensorContext;Lorg/sonar/api/batch/fs/InputFile;Lorg/sonar/plugins/pitest/scanner/SourceFileReport;)V215org.pitest.mutationtest.engine.gregor.mutators.VoidMethodCallMutator31removed call to org/sonar/api/utils/log/Logger::debug 6 | PitestSensor.javaorg.sonar.plugins.pitest.scanner.PitestSensoraddCoverageForKilledMutants(Lorg/sonar/api/batch/sensor/SensorContext;Lorg/sonar/api/batch/fs/InputFile;Lorg/sonar/plugins/pitest/scanner/SourceFileReport;)V219org.pitest.mutationtest.engine.gregor.mutators.VoidMethodCallMutator48removed call to org/sonar/api/batch/sensor/coverage/NewCoverage::save 7 | PitestSensor.javaorg.sonar.plugins.pitest.scanner.PitestSensoraddIssueForMutantKilledThresholdNotReached(Lorg/sonar/api/batch/sensor/SensorContext;Lorg/sonar/api/batch/fs/InputFile;Ljava/lang/String;)V186org.pitest.mutationtest.engine.gregor.mutators.VoidMethodCallMutator49org.sonar.plugins.pitest.scanner.PitestSensorTest.should_create_issue_for_coverage_not_met(org.sonar.plugins.pitest.scanner.PitestSensorTest)removed call to org/sonar/api/batch/sensor/issue/NewIssue::save 8 | PitestSensor.javaorg.sonar.plugins.pitest.scanner.PitestSensorexecute(Lorg/sonar/api/batch/sensor/SensorContext;)V93org.pitest.mutationtest.engine.gregor.mutators.VoidMethodCallMutator15removed call to org/sonar/api/utils/log/Logger::debug 9 | 10 | Maze.ktorg.sonar.plugins.pitest.scanner.PitestSensoraddCoverageForKilledMutants(Lorg/sonar/api/batch/sensor/SensorContext;Lorg/sonar/api/batch/fs/InputFile;Lorg/sonar/plugins/pitest/scanner/SourceFileReport;)V212org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator15negated conditional 11 | 12 | -------------------------------------------------------------------------------- /src/test/resources/pit-reports/201710281222/org.sonar.plugins.pitest.domain/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 58 | 59 | 60 | 61 | 62 |

Pit Test Coverage Report

63 |

Package Summary

64 |

org.sonar.plugins.pitest.domain

65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 |
Number of ClassesLine CoverageMutation Coverage
364%
49/77
92%
23/25
81 | 82 | 83 |

Breakdown by Class

84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 |
NameLine CoverageMutation Coverage
Mutant.java
100%
24/24
100%
3/3
MutantStatus.java
94%
16/17
83%
10/12
Mutator.java
25%
9/36
100%
10/10
114 |
115 | 116 | 117 | 118 |
119 | 120 | Report generated by PIT 1.2.2 121 | 122 | 123 | -------------------------------------------------------------------------------- /src/test/resources/pit-reports/201710281222/org.sonar.plugins.pitest.scanner/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 58 | 59 | 60 | 61 | 62 |

Pit Test Coverage Report

63 |

Package Summary

64 |

org.sonar.plugins.pitest.scanner

65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 |
Number of ClassesLine CoverageMutation Coverage
586%
224/261
58%
69/118
81 | 82 | 83 |

Breakdown by Class

84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 |
NameLine CoverageMutation Coverage
PitestSensor.java
87%
90/104
51%
23/45
ProjectReport.java
100%
12/12
100%
4/4
SourceFileReport.java
89%
51/57
73%
16/22
XmlReportFinder.java
67%
14/21
44%
7/16
XmlReportParser.java
85%
57/67
61%
19/31
126 |
127 | 128 | 129 | 130 |
131 | 132 | Report generated by PIT 1.2.2 133 | 134 | 135 | -------------------------------------------------------------------------------- /src/test/resources/pit-reports/201710281222/org.sonar.plugins.pitest/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 58 | 59 | 60 | 61 | 62 |

Pit Test Coverage Report

63 |

Package Summary

64 |

org.sonar.plugins.pitest

65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 |
Number of ClassesLine CoverageMutation Coverage
3100%
102/102
95%
19/20
81 | 82 | 83 |

Breakdown by Class

84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 |
NameLine CoverageMutation Coverage
PitestComputer.java
100%
24/24
94%
17/18
PitestMetrics.java
100%
52/52
100%
1/1
PitestRulesDefinition.java
100%
26/26
100%
1/1
114 |
115 | 116 | 117 | 118 |
119 | 120 | Report generated by PIT 1.2.2 121 | 122 | 123 | -------------------------------------------------------------------------------- /src/test/resources/pitest-sensor-tests/Maze.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Ref: https://try.kotlinlang.org/#/Examples/Longer%20examples/Maze/Maze.kt 3 | * Let's Walk Through a Maze. 4 | * 5 | * Imagine there is a maze whose walls are the big 'O' letters. 6 | * Now, I stand where a big 'I' stands and some cool prize lies 7 | * somewhere marked with a '$' sign. Like this: 8 | * 9 | * OOOOOOOOOOOOOOOOO 10 | * O O 11 | * O$ O O 12 | * OOOOO O 13 | * O O 14 | * O OOOOOOOOOOOOOO 15 | * O O I O 16 | * O O 17 | * OOOOOOOOOOOOOOOOO 18 | * 19 | * I want to get the prize, and this program helps me do so as soon 20 | * as I possibly can by finding a shortest path through the maze. 21 | */ 22 | package maze 23 | 24 | import java.util.* 25 | 26 | /** 27 | * Declare a point class. 28 | */ 29 | data class Point(val i: Int, val j: Int) 30 | 31 | /** 32 | * This function looks for a path from max.start to maze.end through 33 | * free space (a path does not go through walls). One can move only 34 | * straight up, down, left or right, no diagonal moves allowed. 35 | */ 36 | fun findPath(maze: Maze): List? { 37 | val previous = hashMapOf() 38 | 39 | val queue = LinkedList() 40 | val visited = hashSetOf() 41 | 42 | queue.offer(maze.start) 43 | visited.add(maze.start) 44 | while (!queue.isEmpty()) { 45 | val cell = queue.poll() 46 | if (cell == maze.end) break 47 | 48 | for (newCell in maze.neighbors(cell.i, cell.j)) { 49 | if (newCell in visited) continue 50 | previous.put(newCell, cell) 51 | queue.offer(newCell) 52 | visited.add(cell) 53 | } 54 | } 55 | 56 | if (previous[maze.end] == null) return null 57 | 58 | val path = arrayListOf() 59 | var current = previous[maze.end]!! 60 | while (current != maze.start) { 61 | path.add(0, current) 62 | current = previous[current]!! 63 | } 64 | return path 65 | } 66 | 67 | /** 68 | * Find neighbors of the (i, j) cell that are not walls 69 | */ 70 | fun Maze.neighbors(i: Int, j: Int): List { 71 | val result = arrayListOf() 72 | addIfFree(i - 1, j, result) 73 | addIfFree(i, j - 1, result) 74 | addIfFree(i + 1, j, result) 75 | addIfFree(i, j + 1, result) 76 | return result 77 | } 78 | 79 | fun Maze.addIfFree(i: Int, j: Int, result: MutableList) { 80 | if (i !in 0..height - 1) return 81 | if (j !in 0..width - 1) return 82 | if (walls[i][j]) return 83 | 84 | result.add(Point(i, j)) 85 | } 86 | 87 | /** 88 | * A data class that represents a maze 89 | */ 90 | class Maze( 91 | // Number or columns 92 | val width: Int, 93 | // Number of rows 94 | val height: Int, 95 | // true for a wall, false for free space 96 | val walls: Array, 97 | // The starting point (must not be a wall) 98 | val start: Point, 99 | // The target point (must not be a wall) 100 | val end: Point 101 | ) { 102 | } 103 | 104 | /** A few maze examples here */ 105 | fun main(args: Array) { 106 | walkThroughMaze("I $") 107 | walkThroughMaze("I O $") 108 | walkThroughMaze(""" 109 | O $ 110 | O 111 | O 112 | O 113 | O I 114 | """) 115 | walkThroughMaze(""" 116 | OOOOOOOOOOO 117 | O $ O 118 | OOOOOOO OOO 119 | O O 120 | OOOOO OOOOO 121 | O O 122 | O OOOOOOOOO 123 | O OO 124 | OOOOOO IO 125 | """) 126 | walkThroughMaze(""" 127 | OOOOOOOOOOOOOOOOO 128 | O O 129 | O$ O O 130 | OOOOO O 131 | O O 132 | O OOOOOOOOOOOOOO 133 | O O I O 134 | O O 135 | OOOOOOOOOOOOOOOOO 136 | """) 137 | } 138 | 139 | // UTILITIES 140 | 141 | fun walkThroughMaze(str: String) { 142 | val maze = makeMaze(str) 143 | 144 | println("Maze:") 145 | val path = findPath(maze) 146 | for (i in 0..maze.height - 1) { 147 | for (j in 0..maze.width - 1) { 148 | val cell = Point(i, j) 149 | print( 150 | if (maze.walls[i][j]) "O" 151 | else if (cell == maze.start) "I" 152 | else if (cell == maze.end) "$" 153 | else if (path != null && path.contains(cell)) "~" 154 | else " " 155 | ) 156 | } 157 | println("") 158 | } 159 | println("Result: " + if (path == null) "No path" else "Path found") 160 | println("") 161 | } 162 | 163 | 164 | /** 165 | * A maze is encoded in the string s: the big 'O' letters are walls. 166 | * I stand where a big 'I' stands and the prize is marked with 167 | * a '$' sign. 168 | * 169 | * Example: 170 | * 171 | * OOOOOOOOOOOOOOOOO 172 | * O O 173 | * O$ O O 174 | * OOOOO O 175 | * O O 176 | * O OOOOOOOOOOOOOO 177 | * O O I O 178 | * O O 179 | * OOOOOOOOOOOOOOOOO 180 | */ 181 | fun makeMaze(s: String): Maze { 182 | val lines = s.split('\n') 183 | val longestLine = lines.toList().maxBy { it.length } ?: "" 184 | val data = Array(lines.size) { BooleanArray(longestLine.length) } 185 | 186 | var start: Point? = null 187 | var end: Point? = null 188 | 189 | for (line in lines.indices) { 190 | for (x in lines[line].indices) { 191 | val c = lines[line][x] 192 | when (c) { 193 | 'O' -> data[line][x] = true 194 | 'I' -> start = Point(line, x) 195 | '$' -> end = Point(line, x) 196 | } 197 | } 198 | } 199 | 200 | return Maze(longestLine.length, lines.size, data, 201 | start ?: throw IllegalArgumentException("No starting point in the maze (should be indicated with 'I')"), 202 | end ?: throw IllegalArgumentException("No goal point in the maze (should be indicated with a '$' sign)")) 203 | } 204 | -------------------------------------------------------------------------------- /src/test/resources/pitest-sensor-tests/com/foo/Bar.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Sonar .NET Plugin :: Gallio 3 | * Copyright (C) 2010 Jose Chillan, Alexandre Victoor and SonarSource 4 | * dev@sonar.codehaus.org 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 17 | * License along with this program; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 19 | */ 20 | package org.sonar.plugins.csharp.gallio; 21 | 22 | import com.google.common.collect.Lists; 23 | import com.google.common.collect.Sets; 24 | import org.slf4j.Logger; 25 | import org.slf4j.LoggerFactory; 26 | import org.sonar.api.batch.DependedUpon; 27 | import org.sonar.api.batch.DependsUpon; 28 | import org.sonar.api.batch.SensorContext; 29 | import org.sonar.api.resources.Project; 30 | import org.sonar.api.utils.SonarException; 31 | import org.sonar.dotnet.tools.gallio.GallioCommandBuilder; 32 | import org.sonar.dotnet.tools.gallio.GallioException; 33 | import org.sonar.dotnet.tools.gallio.GallioRunner; 34 | import org.sonar.plugins.dotnet.api.DotNetConfiguration; 35 | import org.sonar.plugins.dotnet.api.DotNetConstants; 36 | import org.sonar.plugins.dotnet.api.microsoft.MicrosoftWindowsEnvironment; 37 | import org.sonar.plugins.dotnet.api.microsoft.VisualStudioProject; 38 | import org.sonar.plugins.dotnet.api.microsoft.VisualStudioSolution; 39 | import org.sonar.plugins.dotnet.api.sensor.AbstractDotNetSensor; 40 | import org.sonar.plugins.dotnet.api.utils.FileFinder; 41 | 42 | import java.io.File; 43 | import java.util.Collection; 44 | import java.util.Collections; 45 | import java.util.List; 46 | import java.util.Set; 47 | 48 | /** 49 | * Executes Gallio only once in the Solution directory to generate test execution and coverage reports. 50 | */ 51 | @DependsUpon(DotNetConstants.CORE_PLUGIN_EXECUTED) 52 | @DependedUpon(GallioConstants.BARRIER_GALLIO_EXECUTED) 53 | public class GallioSensor extends AbstractDotNetSensor { 54 | 55 | private static final Logger LOG = LoggerFactory.getLogger(GallioSensor.class); 56 | 57 | private DotNetConfiguration configuration; 58 | 59 | private VisualStudioSolution solution; 60 | 61 | private File workDir; 62 | 63 | private boolean safeMode; 64 | 65 | private int timeout; 66 | 67 | /** 68 | * Constructs a {@link GallioSensor}. 69 | * 70 | * @param fileSystem 71 | * @param configuration 72 | * @param microsoftWindowsEnvironment 73 | */ 74 | public GallioSensor(DotNetConfiguration configuration, MicrosoftWindowsEnvironment microsoftWindowsEnvironment) { 75 | super(microsoftWindowsEnvironment, "Gallio", configuration.getString(GallioConstants.MODE)); 76 | this.configuration = configuration; 77 | } 78 | 79 | /** 80 | * {@inheritDoc} 81 | */ 82 | public boolean shouldExecuteOnProject(Project project) { 83 | if (MODE_REUSE_REPORT.equals(getExecutionMode())) { 84 | LOG.info("Gallio won't execute as it is set to 'reuseReport' mode."); 85 | return false; 86 | } 87 | if (getMicrosoftWindowsEnvironment().isTestExecutionDone()) { 88 | LOG.info("Gallio won't execute as test execution has already been done."); 89 | return false; 90 | } 91 | if (getMicrosoftWindowsEnvironment().getCurrentSolution() != null 92 | && getMicrosoftWindowsEnvironment().getCurrentSolution().getUnitTestProjects().isEmpty()) { 93 | LOG.info("Gallio won't execute as there are no test projects."); 94 | return false; 95 | } 96 | 97 | return super.shouldExecuteOnProject(project); 98 | } 99 | 100 | private void addAssembly(Collection assemblyFileList, VisualStudioProject visualStudioProject) { 101 | String buildConfiguration = configuration.getString(DotNetConstants.BUILD_CONFIGURATION_KEY); 102 | String buildPlatform = configuration.getString(DotNetConstants.BUILD_PLATFORM_KEY); 103 | File assembly = visualStudioProject.getArtifact(buildConfiguration, buildPlatform); 104 | if (assembly != null && assembly.isFile()) { 105 | assemblyFileList.add(assembly); 106 | } else { 107 | LOG.warn("Test assembly not found at the following location: {}" 108 | + "\n, using the following configuration:\n - csproj file: {}\n - build configuration: {}\n - platform: {}", 109 | new Object[] {assembly, visualStudioProject.getProjectFile(), buildConfiguration, buildPlatform}); 110 | } 111 | } 112 | 113 | private List findTestAssemblies(boolean it, String[] testAssemblyPatterns) throws GallioException { 114 | Set assemblyFiles = Sets.newHashSet(); 115 | if (solution != null) { 116 | 117 | Collection testProjects = it ? solution.getIntegTestProjects() : solution.getUnitTestProjects(); 118 | if (testAssemblyPatterns.length == 0) { 119 | for (VisualStudioProject visualStudioProject : testProjects) { 120 | addAssembly(assemblyFiles, visualStudioProject); 121 | } 122 | } else { 123 | for (VisualStudioProject visualStudioProject : testProjects) { 124 | Collection projectTestAssemblies = FileFinder.findFiles(solution, visualStudioProject, testAssemblyPatterns); 125 | if (projectTestAssemblies.isEmpty()) { 126 | LOG.info("Test assembly not found using pattern {}, fallback to visual studio default assembly location", testAssemblyPatterns); 127 | addAssembly(assemblyFiles, visualStudioProject); 128 | } else { 129 | assemblyFiles.addAll(projectTestAssemblies); 130 | } 131 | } 132 | } 133 | 134 | } else { 135 | throw new GallioException("No .NET solution or project has been given to the Gallio command builder."); 136 | } 137 | return Lists.newArrayList(assemblyFiles); 138 | } 139 | 140 | /** 141 | * {@inheritDoc} 142 | */ 143 | @Override 144 | public String[] getSupportedLanguages() { 145 | return GallioConstants.SUPPORTED_LANGUAGES; 146 | } 147 | 148 | /** 149 | * {@inheritDoc} 150 | */ 151 | @Override 152 | public void analyse(Project project, SensorContext context) { 153 | 154 | solution = getMicrosoftWindowsEnvironment().getCurrentSolution(); 155 | 156 | workDir = new File(solution.getSolutionDir(), getMicrosoftWindowsEnvironment().getWorkingDirectory()); 157 | if (!workDir.exists()) { 158 | workDir.mkdirs(); 159 | } 160 | 161 | safeMode = configuration.getBoolean(GallioConstants.SAFE_MODE_KEY); 162 | timeout = configuration.getInt(GallioConstants.TIMEOUT_MINUTES_KEY); 163 | 164 | String[] testAssemblyPatterns = configuration.getStringArray(DotNetConstants.TEST_ASSEMBLIES_KEY); 165 | String gallioFilter = configuration.getString(GallioConstants.FILTER_KEY); 166 | 167 | executeRunner(false, testAssemblyPatterns, gallioFilter, GallioConstants.GALLIO_REPORT_XML, GallioConstants.GALLIO_COVERAGE_REPORT_XML); 168 | 169 | String itExecutionMode = configuration.getString(GallioConstants.IT_MODE_KEY); 170 | if (GallioConstants.IT_MODE_ACTIVE.equals(itExecutionMode)) { 171 | String[] itAssemblyPatterns = configuration.getStringArray(GallioConstants.IT_TEST_ASSEMBLIES_KEY); 172 | String itGallioFilter = configuration.getString(GallioConstants.IT_FILTER_KEY); 173 | executeRunner(true, itAssemblyPatterns, itGallioFilter, GallioConstants.IT_GALLIO_REPORT_XML, GallioConstants.IT_GALLIO_COVERAGE_REPORT_XML); 174 | } 175 | 176 | // tell that tests were executed so that no other project tries to launch them a second time 177 | getMicrosoftWindowsEnvironment().setTestExecutionDone(); 178 | 179 | } 180 | 181 | private void executeRunner(boolean it, String[] assemblyPatterns, String gallioFilter, String reportFileName, String coverageReportFileName) { 182 | try { 183 | 184 | List testAssemblies = findTestAssemblies(it, assemblyPatterns); 185 | 186 | if (safeMode) { 187 | for (File assembly : testAssemblies) { 188 | File gallioReportFile = new File(workDir, assembly.getName() + "." + reportFileName); 189 | File coverageReportFile = new File(workDir, assembly.getName() + "." + coverageReportFileName); 190 | GallioRunner runner = createRunner(workDir); 191 | GallioCommandBuilder builder = createBuilder(runner, Collections.singletonList(assembly), gallioFilter, gallioReportFile, coverageReportFile); 192 | runner.execute(builder, timeout); 193 | } 194 | } else { 195 | File gallioReportFile = new File(workDir, reportFileName); 196 | File coverageReportFile = new File(workDir, coverageReportFileName); 197 | GallioRunner runner = createRunner(workDir); 198 | GallioCommandBuilder builder = createBuilder(runner, testAssemblies, gallioFilter, gallioReportFile, coverageReportFile); 199 | runner.execute(builder, timeout); 200 | } 201 | } catch (GallioException e) { 202 | throw new SonarException("Gallio execution failed.", e); 203 | } 204 | } 205 | 206 | private GallioRunner createRunner(File workDir) { 207 | // create runner 208 | File gallioInstallDir = new File(configuration.getString(GallioConstants.INSTALL_FOLDER_KEY)); 209 | return GallioRunner.create(gallioInstallDir.getAbsolutePath(), workDir.getAbsolutePath(), true); 210 | } 211 | 212 | private GallioCommandBuilder createBuilder(GallioRunner runner, List testAssemblies, String gallioFilter, File gallioReportFile, File coverageReportFile) { 213 | GallioCommandBuilder builder = runner.createCommandBuilder(getMicrosoftWindowsEnvironment().getCurrentSolution()); 214 | 215 | // Add info for Gallio execution 216 | builder.setReportFile(gallioReportFile); 217 | builder.setFilter(gallioFilter); 218 | 219 | builder.setGallioRunnerType(configuration.getString(GallioConstants.RUNNER_TYPE_KEY)); 220 | builder.setTestAssemblies(testAssemblies); 221 | 222 | // Add info for coverage execution 223 | builder.setCoverageReportFile(coverageReportFile); 224 | builder.setCoverageTool(configuration.getString(GallioConstants.COVERAGE_TOOL_KEY)); 225 | builder.setCoverageExcludes(configuration 226 | .getStringArray(GallioConstants.COVERAGE_EXCLUDES_KEY, GallioConstants.COVERAGE_EXCLUDES_DEFVALUE)); 227 | builder.setPartCoverInstallDirectory(new File(configuration.getString(GallioConstants.PART_COVER_INSTALL_KEY))); 228 | builder.setOpenCoverInstallDirectory(new File(configuration.getString(GallioConstants.OPEN_COVER_INSTALL_KEY))); 229 | builder.setDotCoverInstallDirectory(new File(configuration.getString(GallioConstants.DOT_COVER_INSTALL_KEY))); 230 | 231 | return builder; 232 | } 233 | 234 | } -------------------------------------------------------------------------------- /src/test/resources/pitest-sensor-tests/com/foo/KilledClazz.java: -------------------------------------------------------------------------------- 1 | // Fake source file -------------------------------------------------------------------------------- /src/test/resources/pitest-sensor-tests/com/foo/MemoryErrorClazz.java: -------------------------------------------------------------------------------- 1 | // Fake source file -------------------------------------------------------------------------------- /src/test/resources/pitest-sensor-tests/com/foo/NoCoverageClazz.java: -------------------------------------------------------------------------------- 1 | // Fake source file -------------------------------------------------------------------------------- /src/test/resources/pitest-sensor-tests/com/foo/SurvivedClazz.java: -------------------------------------------------------------------------------- 1 | // Fake source file -------------------------------------------------------------------------------- /src/test/resources/pitest-sensor-tests/com/foo/UnknownClazz.java: -------------------------------------------------------------------------------- 1 | // Fake source file -------------------------------------------------------------------------------- /src/test/resources/sonar-pitest-plugin.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VinodAnandan/sonar-pitest/495b2bb19e30870474a4e9e6043648a1a34ad6ba/src/test/resources/sonar-pitest-plugin.jar -------------------------------------------------------------------------------- /src/test/resources/test-pit-reports-1/123/misnamed-mutations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | GallioSensor.java 5 | org.sonar.plugins.csharp.gallio.GallioSensor 6 | shouldExecuteOnProject 7 | 85 8 | org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator 9 | 0 10 | org.sonar.plugins.csharp.gallio.GallioSensorTest.testShouldNotExecuteOnProjectIfReuseReports(org.sonar.plugins.csharp.gallio.GallioSensorTest) 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/test/resources/test-pit-reports-1/123/mutations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | GallioSensor.java 5 | org.sonar.plugins.csharp.gallio.GallioSensor 6 | shouldExecuteOnProject 7 | 85 8 | org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator 9 | 0 10 | org.sonar.plugins.csharp.gallio.GallioSensorTest.testShouldNotExecuteOnProjectIfReuseReports(org.sonar.plugins.csharp.gallio.GallioSensorTest) 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/test/resources/test-pit-reports-1/fluff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VinodAnandan/sonar-pitest/495b2bb19e30870474a4e9e6043648a1a34ad6ba/src/test/resources/test-pit-reports-1/fluff -------------------------------------------------------------------------------- /src/test/resources/test-pit-reports-2/123/mutations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | GallioSensor.java 5 | org.sonar.plugins.csharp.gallio.GallioSensor 6 | shouldExecuteOnProject 7 | 85 8 | org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator 9 | 0 10 | org.sonar.plugins.csharp.gallio.GallioSensorTest.testShouldNotExecuteOnProjectIfReuseReports(org.sonar.plugins.csharp.gallio.GallioSensorTest) 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/test/resources/test-pit-reports-2/124/mutations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | GallioSensor.java 5 | org.sonar.plugins.csharp.gallio.GallioSensor 6 | findTestAssemblies 7 | 127 8 | org.pitest.mutationtest.engine.gregor.mutators.VoidMethodCallMutator 9 | 4 10 | 11 | 12 | 13 | MainKotlin.kt 14 | some.Hello 15 | hello 16 | ()V 17 | 6 18 | org.pitest.mutationtest.engine.gregor.mutators.VoidMethodCallMutator 19 | 7 20 | 21 | removed call to java/io/PrintStream::print 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/test/resources/xml-report-parser-test/mutations-unordered.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | negated conditional 5 | PitestSensor.java 6 | addCoverageForKilledMutants 7 | (Lorg/sonar/api/batch/sensor/SensorContext;Lorg/sonar/api/batch/fs/InputFile;Lorg/sonar/plugins/pitest/scanner/SourceFileReport;)V 8 | org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator 9 | 15 10 | 11 | org.sonar.plugins.pitest.scanner.PitestSensor 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/test/resources/xml-report-parser-test/mutations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PitestSensor.java 5 | org.sonar.plugins.pitest.scanner.PitestSensor 6 | addCoverageForKilledMutants 7 | (Lorg/sonar/api/batch/sensor/SensorContext;Lorg/sonar/api/batch/fs/InputFile;Lorg/sonar/plugins/pitest/scanner/SourceFileReport;)V 8 | 212 9 | org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator 10 | 15 11 | 12 | negated conditional 13 | 14 | 15 | 16 | --------------------------------------------------------------------------------