summaryrefslogtreecommitdiffstats
path: root/sonar-batch/src
diff options
context:
space:
mode:
authorSimon Brandhof <simon.brandhof@sonarsource.com>2015-05-27 09:38:17 +0200
committerSimon Brandhof <simon.brandhof@sonarsource.com>2015-05-28 09:29:03 +0200
commit57c01be1943a882a5a2fe0cc0fecab2fe75a861a (patch)
tree3d1240ef670ddedd00cf6b0450e72ef418cbc78e /sonar-batch/src
parentd228fadfa0c0cc7284d4920c9bb485049ff3f68f (diff)
downloadsonarqube-57c01be1943a882a5a2fe0cc0fecab2fe75a861a.tar.gz
sonarqube-57c01be1943a882a5a2fe0cc0fecab2fe75a861a.zip
SONAR-6370 move batch extensions out of core plugin
Decorators temporarily moved to package org.sonar.batch.compute of sonar-batch.
Diffstat (limited to 'sonar-batch/src')
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java48
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/compute/AbstractCoverageDecorator.java102
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/compute/AbstractNewCoverageFileAnalyzer.java262
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/compute/ApplyProjectRolesDecorator.java60
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/compute/BranchCoverageDecorator.java72
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/compute/CommentDensityDecorator.java90
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/compute/CountFalsePositivesDecorator.java86
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/compute/CountUnresolvedIssuesDecorator.java304
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/compute/CoverageDecorator.java86
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/compute/DirectoriesDecorator.java69
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/compute/FilesDecorator.java65
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/compute/ItBranchCoverageDecorator.java73
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/compute/ItCoverageDecorator.java87
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/compute/ItLineCoverageDecorator.java73
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/compute/LineCoverageDecorator.java72
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/compute/ManualMeasureDecorator.java79
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/compute/NewCoverageAggregator.java99
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/compute/NewCoverageFileAnalyzer.java73
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/compute/NewItCoverageFileAnalyzer.java68
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/compute/NewOverallCoverageFileAnalyzer.java68
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/compute/OverallBranchCoverageDecorator.java73
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/compute/OverallCoverageDecorator.java88
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/compute/OverallLineCoverageDecorator.java73
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/compute/SeverityUtils.java66
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/compute/TimeMachineConfigurationPersister.java83
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/compute/UnitTestDecorator.java95
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/compute/VariationDecorator.java205
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/compute/package-info.java24
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/compute/ApplyProjectRolesDecoratorTest.java83
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/compute/BranchCoverageDecoratorTest.java72
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/compute/CommentDensityDecoratorTest.java76
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/compute/CountFalsePositivesDecoratorTest.java81
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/compute/CountUnresolvedIssuesDecoratorTest.java372
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/compute/CoverageDecoratorTest.java143
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/compute/DirectoriesDecoratorTest.java90
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/compute/FilesDecoratorTest.java111
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/compute/ItBranchCoverageDecoratorTest.java72
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/compute/ItCoverageDecoratorTest.java142
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/compute/ItLineCoverageDecoratorTest.java99
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/compute/LineCoverageDecoratorTest.java98
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/compute/ManualMeasureDecoratorTest.java52
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/compute/NewCoverageAggregatorTest.java90
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/compute/NewCoverageFileAnalyzerTest.java297
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/compute/OverallBranchCoverageDecoratorTest.java72
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/compute/OverallCoverageDecoratorTest.java142
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/compute/OverallLineCoverageDecoratorTest.java100
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/compute/TimeMachineConfigurationPersisterTest.java59
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/compute/UnitTestDecoratorTest.java87
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/compute/VariationDecoratorTest.java149
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/mediumtest/measures/MeasuresMediumTest.java4
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/compute/ManualMeasureDecoratorTest/testCopyManualMeasures.xml11
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/compute/ProjectLinksSensorTest/shouldDeleteMissingLinks.xml10
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/compute/ProjectLinksSensorTest/shouldSaveLinks.xml51
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/compute/TimeMachineConfigurationPersisterTest/shared.xml42
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/compute/TimeMachineConfigurationPersisterTest/shouldSaveConfigurationInSnapshotsTable-result.xml42
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/compute/ViolationPersisterDecoratorTest/shared.xml21
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/compute/ViolationPersisterDecoratorTest/shouldCopyPermanentIdFromReferenceViolation-result.xml21
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/compute/ViolationPersisterDecoratorTest/shouldSaveViolations-result.xml20
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/compute/ViolationTrackingTest/example1-v1.txt12
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/compute/ViolationTrackingTest/example1-v2.txt22
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/compute/ViolationTrackingTest/example2-v1.txt7
-rw-r--r--sonar-batch/src/test/resources/org/sonar/batch/compute/ViolationTrackingTest/example2-v2.txt16
62 files changed, 5406 insertions, 3 deletions
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java
index c584274eaf5..ba42319b7bd 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java
@@ -23,6 +23,29 @@ import com.google.common.collect.Lists;
import java.util.Collection;
import java.util.List;
import org.sonar.batch.components.TimeMachineConfiguration;
+import org.sonar.batch.compute.ApplyProjectRolesDecorator;
+import org.sonar.batch.compute.BranchCoverageDecorator;
+import org.sonar.batch.compute.CommentDensityDecorator;
+import org.sonar.batch.compute.CountFalsePositivesDecorator;
+import org.sonar.batch.compute.CountUnresolvedIssuesDecorator;
+import org.sonar.batch.compute.CoverageDecorator;
+import org.sonar.batch.compute.DirectoriesDecorator;
+import org.sonar.batch.compute.FilesDecorator;
+import org.sonar.batch.compute.ItBranchCoverageDecorator;
+import org.sonar.batch.compute.ItCoverageDecorator;
+import org.sonar.batch.compute.ItLineCoverageDecorator;
+import org.sonar.batch.compute.LineCoverageDecorator;
+import org.sonar.batch.compute.ManualMeasureDecorator;
+import org.sonar.batch.compute.NewCoverageAggregator;
+import org.sonar.batch.compute.NewCoverageFileAnalyzer;
+import org.sonar.batch.compute.NewItCoverageFileAnalyzer;
+import org.sonar.batch.compute.NewOverallCoverageFileAnalyzer;
+import org.sonar.batch.compute.OverallBranchCoverageDecorator;
+import org.sonar.batch.compute.OverallCoverageDecorator;
+import org.sonar.batch.compute.OverallLineCoverageDecorator;
+import org.sonar.batch.compute.TimeMachineConfigurationPersister;
+import org.sonar.batch.compute.UnitTestDecorator;
+import org.sonar.batch.compute.VariationDecorator;
import org.sonar.batch.cpd.CpdComponents;
import org.sonar.batch.debt.DebtDecorator;
import org.sonar.batch.debt.IssueChangelogDebtCalculator;
@@ -103,8 +126,31 @@ public class BatchComponents {
IssueHandlers.class,
InitialOpenIssuesSensor.class,
+ // to be moved to compute engine
QProfileEventsDecorator.class,
-
+ CountUnresolvedIssuesDecorator.class,
+ CountFalsePositivesDecorator.class,
+ UnitTestDecorator.class,
+ LineCoverageDecorator.class,
+ CoverageDecorator.class,
+ BranchCoverageDecorator.class,
+ ItLineCoverageDecorator.class,
+ ItCoverageDecorator.class,
+ ItBranchCoverageDecorator.class,
+ OverallLineCoverageDecorator.class,
+ OverallCoverageDecorator.class,
+ OverallBranchCoverageDecorator.class,
+ ApplyProjectRolesDecorator.class,
+ CommentDensityDecorator.class,
+ DirectoriesDecorator.class,
+ FilesDecorator.class,
+ ManualMeasureDecorator.class,
+ VariationDecorator.class,
+ TimeMachineConfigurationPersister.class,
+ NewCoverageFileAnalyzer.class,
+ NewItCoverageFileAnalyzer.class,
+ NewOverallCoverageFileAnalyzer.class,
+ NewCoverageAggregator.class,
TimeMachineConfiguration.class
);
components.addAll(CorePropertyDefinitions.all());
diff --git a/sonar-batch/src/main/java/org/sonar/batch/compute/AbstractCoverageDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/compute/AbstractCoverageDecorator.java
new file mode 100644
index 00000000000..4960f991ea2
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/compute/AbstractCoverageDecorator.java
@@ -0,0 +1,102 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import java.util.Arrays;
+import java.util.Collection;
+import org.sonar.api.batch.Decorator;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependedUpon;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.resources.ResourceUtils;
+
+public abstract class AbstractCoverageDecorator implements Decorator {
+
+ @Override
+ public boolean shouldExecuteOnProject(Project project) {
+ return true;
+ }
+
+ @DependedUpon
+ public Collection<Metric> generatedMetrics() {
+ return Arrays.asList(getGeneratedMetric(), getGeneratedMetricForNewCode());
+ }
+
+ @Override
+ public void decorate(final Resource resource, final DecoratorContext context) {
+ if (shouldDecorate(resource)) {
+ computeMeasure(context);
+ computeMeasureForNewCode(context);
+ }
+ }
+
+ protected boolean shouldDecorate(final Resource resource) {
+ return !ResourceUtils.isUnitTestFile(resource);
+ }
+
+ private void computeMeasure(DecoratorContext context) {
+ if (context.getMeasure(getGeneratedMetric()) == null) {
+ Long elements = countElements(context);
+ if (elements != null && elements > 0L) {
+ Long coveredElements = countCoveredElements(context);
+ context.saveMeasure(getGeneratedMetric(), calculateCoverage(coveredElements, elements));
+ }
+ }
+ }
+
+ private void computeMeasureForNewCode(DecoratorContext context) {
+ if (context.getMeasure(getGeneratedMetricForNewCode()) == null) {
+ Measure measure = new Measure(getGeneratedMetricForNewCode());
+ boolean hasValue = false;
+ /* TODO remove this magic number */
+ for (int periodIndex = 1; periodIndex <= 5; periodIndex++) {
+ Long elements = countElementsForNewCode(context, periodIndex);
+
+ if (elements != null && elements > 0L) {
+ long coveredElements = countCoveredElementsForNewCode(context, periodIndex);
+ measure.setVariation(periodIndex, calculateCoverage(coveredElements, elements));
+ hasValue = true;
+ }
+ }
+ if (hasValue) {
+ context.saveMeasure(measure);
+ }
+ }
+ }
+
+ private double calculateCoverage(final long coveredLines, final long lines) {
+ return (100.0 * coveredLines) / lines;
+ }
+
+ protected abstract Metric getGeneratedMetric();
+
+ protected abstract Long countElements(DecoratorContext context);
+
+ protected abstract long countCoveredElements(DecoratorContext context);
+
+ protected abstract Metric getGeneratedMetricForNewCode();
+
+ protected abstract Long countElementsForNewCode(DecoratorContext context, int periodIndex);
+
+ protected abstract long countCoveredElementsForNewCode(DecoratorContext context, int periodIndex);
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/compute/AbstractNewCoverageFileAnalyzer.java b/sonar-batch/src/main/java/org/sonar/batch/compute/AbstractNewCoverageFileAnalyzer.java
new file mode 100644
index 00000000000..446c0820244
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/compute/AbstractNewCoverageFileAnalyzer.java
@@ -0,0 +1,262 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.ObjectUtils;
+import org.sonar.api.batch.Decorator;
+import org.sonar.api.batch.DecoratorBarriers;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependedUpon;
+import org.sonar.api.batch.DependsUpon;
+import org.sonar.api.batch.RequiresDB;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.resources.Scopes;
+import org.sonar.api.utils.KeyValueFormat;
+import org.sonar.batch.components.Period;
+import org.sonar.batch.components.TimeMachineConfiguration;
+import org.sonar.batch.index.BatchComponentCache;
+import org.sonar.batch.protocol.output.BatchReport;
+import org.sonar.batch.protocol.output.BatchReport.Changesets.Changeset;
+import org.sonar.batch.protocol.output.BatchReportReader;
+import org.sonar.batch.report.ReportPublisher;
+
+/**
+ * @since 2.7
+ */
+@RequiresDB
+@DependedUpon(DecoratorBarriers.END_OF_TIME_MACHINE)
+public abstract class AbstractNewCoverageFileAnalyzer implements Decorator {
+
+ private final List<PeriodStruct> structs;
+ private final ReportPublisher publishReportJob;
+ private final BatchComponentCache resourceCache;
+
+ public AbstractNewCoverageFileAnalyzer(TimeMachineConfiguration timeMachineConfiguration, ReportPublisher publishReportJob, BatchComponentCache resourceCache) {
+ this(new ArrayList<PeriodStruct>(), publishReportJob, resourceCache);
+ for (Period period : timeMachineConfiguration.periods()) {
+ structs.add(new PeriodStruct(period.getIndex(), period.getDate()));
+ }
+ }
+
+ AbstractNewCoverageFileAnalyzer(List<PeriodStruct> structs, ReportPublisher publishReportJob, BatchComponentCache resourceCache) {
+ this.resourceCache = resourceCache;
+ this.publishReportJob = publishReportJob;
+ this.structs = structs;
+ }
+
+ public abstract Metric getCoverageLineHitsDataMetric();
+
+ public abstract Metric getConditionsByLineMetric();
+
+ public abstract Metric getCoveredConditionsByLineMetric();
+
+ public abstract Metric getNewLinesToCoverMetric();
+
+ public abstract Metric getNewUncoveredLinesMetric();
+
+ public abstract Metric getNewConditionsToCoverMetric();
+
+ public abstract Metric getNewUncoveredConditionsMetric();
+
+ @Override
+ public boolean shouldExecuteOnProject(Project project) {
+ return !structs.isEmpty();
+ }
+
+ private boolean shouldDecorate(Resource resource) {
+ return Scopes.isFile(resource) && !Qualifiers.UNIT_TEST_FILE.equals(resource.getQualifier());
+ }
+
+ @DependsUpon
+ public List<Metric> dependsOnMetrics() {
+
+ return Arrays.asList(getCoverageLineHitsDataMetric(), getConditionsByLineMetric(), getCoveredConditionsByLineMetric());
+ }
+
+ @DependedUpon
+ public List<Metric> generatesNewCoverageMetrics() {
+ return Arrays.asList(getNewLinesToCoverMetric(), getNewUncoveredLinesMetric(), getNewConditionsToCoverMetric(), getNewUncoveredConditionsMetric());
+ }
+
+ @Override
+ public void decorate(Resource resource, DecoratorContext context) {
+ if (shouldDecorate(resource)) {
+ doDecorate(context);
+ }
+ }
+
+ void doDecorate(DecoratorContext context) {
+ if (parse(context)) {
+ compute(context);
+ }
+ }
+
+ private boolean parse(DecoratorContext context) {
+ BatchReportReader reader = new BatchReportReader(publishReportJob.getReportDir());
+ BatchReport.Changesets componentScm = reader.readChangesets(resourceCache.get(context.getResource()).batchId());
+ Measure hitsByLineMeasure = context.getMeasure(getCoverageLineHitsDataMetric());
+
+ if (componentScm != null && hitsByLineMeasure != null && hitsByLineMeasure.hasData()) {
+ Map<Integer, Integer> hitsByLine = parseCountByLine(hitsByLineMeasure);
+ Map<Integer, Integer> conditionsByLine = parseCountByLine(context.getMeasure(getConditionsByLineMetric()));
+ Map<Integer, Integer> coveredConditionsByLine = parseCountByLine(context.getMeasure(getCoveredConditionsByLineMetric()));
+
+ reset();
+
+ for (Map.Entry<Integer, Integer> entry : hitsByLine.entrySet()) {
+ int lineId = entry.getKey();
+ int hits = entry.getValue();
+ int conditions = (Integer) ObjectUtils.defaultIfNull(conditionsByLine.get(lineId), 0);
+ int coveredConditions = (Integer) ObjectUtils.defaultIfNull(coveredConditionsByLine.get(lineId), 0);
+ Changeset changeset = componentScm.getChangeset(componentScm.getChangesetIndexByLine(lineId - 1));
+ Date date = changeset.hasDate() ? new Date(changeset.getDate()) : null;
+ for (PeriodStruct struct : structs) {
+ struct.analyze(date, hits, conditions, coveredConditions);
+ }
+ }
+
+ return true;
+ }
+ return false;
+ }
+
+ private void reset() {
+ for (PeriodStruct struct : structs) {
+ struct.reset();
+ }
+ }
+
+ private void compute(DecoratorContext context) {
+ Measure newLines = new Measure(getNewLinesToCoverMetric());
+ Measure newUncoveredLines = new Measure(getNewUncoveredLinesMetric());
+ Measure newConditions = new Measure(getNewConditionsToCoverMetric());
+ Measure newUncoveredConditions = new Measure(getNewUncoveredConditionsMetric());
+
+ for (PeriodStruct struct : structs) {
+ if (struct.hasNewCode()) {
+ newLines.setVariation(struct.index, (double) struct.getNewLines());
+ newUncoveredLines.setVariation(struct.index, (double) (struct.getNewLines() - struct.getNewCoveredLines()));
+ newConditions.setVariation(struct.index, (double) struct.getNewConditions());
+ newUncoveredConditions.setVariation(struct.index, (double) struct.getNewConditions() - struct.getNewCoveredConditions());
+ }
+ }
+
+ context.saveMeasure(newLines);
+ context.saveMeasure(newUncoveredLines);
+ context.saveMeasure(newConditions);
+ context.saveMeasure(newUncoveredConditions);
+ }
+
+ private Map<Integer, Integer> parseCountByLine(@Nullable Measure measure) {
+ if (measure != null && measure.hasData()) {
+ return KeyValueFormat.parseIntInt(measure.getData());
+ }
+ return new HashMap<>();
+ }
+
+ public static final class PeriodStruct {
+ int index;
+ Date date;
+ Integer newLines;
+ Integer newCoveredLines;
+ Integer newConditions;
+ Integer newCoveredConditions;
+
+ PeriodStruct(int index, @Nullable Date date) {
+ this.index = index;
+ this.date = date;
+ }
+
+ void reset() {
+ newLines = null;
+ newCoveredLines = null;
+ newConditions = null;
+ newCoveredConditions = null;
+ }
+
+ void analyze(@Nullable Date lineDate, int hits, int conditions, int coveredConditions) {
+ if (lineDate == null) {
+ // TODO warning
+
+ } else if (date == null || lineDate.after(date)) {
+ // TODO test if string comparison is faster or not
+ addLine(hits > 0);
+ addConditions(conditions, coveredConditions);
+ }
+ }
+
+ void addLine(boolean covered) {
+ if (newLines == null) {
+ newLines = 0;
+ }
+ newLines += 1;
+ if (covered) {
+ if (newCoveredLines == null) {
+ newCoveredLines = 0;
+ }
+ newCoveredLines += 1;
+ }
+ }
+
+ void addConditions(int count, int countCovered) {
+ if (newConditions == null) {
+ newConditions = 0;
+ }
+ newConditions += count;
+ if (count > 0) {
+ if (newCoveredConditions == null) {
+ newCoveredConditions = 0;
+ }
+ newCoveredConditions += countCovered;
+ }
+ }
+
+ boolean hasNewCode() {
+ return newLines != null;
+ }
+
+ public int getNewLines() {
+ return newLines != null ? newLines : 0;
+ }
+
+ public int getNewCoveredLines() {
+ return newCoveredLines != null ? newCoveredLines : 0;
+ }
+
+ public int getNewConditions() {
+ return newConditions != null ? newConditions : 0;
+ }
+
+ public int getNewCoveredConditions() {
+ return newCoveredConditions != null ? newCoveredConditions : 0;
+ }
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/compute/ApplyProjectRolesDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/compute/ApplyProjectRolesDecorator.java
new file mode 100644
index 00000000000..8861e1312f8
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/compute/ApplyProjectRolesDecorator.java
@@ -0,0 +1,60 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.Set;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.batch.Decorator;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.RequiresDB;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.security.ResourcePermissions;
+
+@RequiresDB
+public class ApplyProjectRolesDecorator implements Decorator {
+
+ private static final Set<String> QUALIFIERS = ImmutableSet.of(Qualifiers.PROJECT, Qualifiers.VIEW, Qualifiers.SUBVIEW);
+ private final ResourcePermissions resourcePermissions;
+
+ public ApplyProjectRolesDecorator(ResourcePermissions resourcePermissions) {
+ this.resourcePermissions = resourcePermissions;
+ }
+
+ @Override
+ public boolean shouldExecuteOnProject(Project project) {
+ return true;
+ }
+
+ @Override
+ public void decorate(Resource resource, DecoratorContext context) {
+ if (shouldDecorateResource(resource)) {
+ LoggerFactory.getLogger(ApplyProjectRolesDecorator.class).info("Grant default permissions to {}", resource.getKey());
+ resourcePermissions.grantDefaultRoles(resource);
+ }
+ }
+
+ private boolean shouldDecorateResource(Resource resource) {
+ return resource.getId() != null && QUALIFIERS.contains(resource.getQualifier()) && !resourcePermissions.hasRoles(resource);
+ }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/compute/BranchCoverageDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/compute/BranchCoverageDecorator.java
new file mode 100644
index 00000000000..637ea6eea44
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/compute/BranchCoverageDecorator.java
@@ -0,0 +1,72 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import java.util.Collection;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependsUpon;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.MeasureUtils;
+import org.sonar.api.measures.Metric;
+import org.sonar.batch.sensor.coverage.CoverageConstants;
+
+public final class BranchCoverageDecorator extends AbstractCoverageDecorator {
+
+ @DependsUpon
+ public Collection<Metric> dependsUponMetrics() {
+ return CoverageConstants.BRANCH_COVERAGE_METRICS;
+ }
+
+ @Override
+ protected Metric getGeneratedMetric() {
+ return CoreMetrics.BRANCH_COVERAGE;
+ }
+
+ @Override
+ protected Long countElements(DecoratorContext context) {
+ return MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.CONDITIONS_TO_COVER), 0L);
+ }
+
+ @Override
+ protected long countCoveredElements(DecoratorContext context) {
+ long uncoveredConditions = MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.UNCOVERED_CONDITIONS), 0L);
+ long conditions = MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.CONDITIONS_TO_COVER), 0L);
+
+ return conditions - uncoveredConditions;
+ }
+
+ @Override
+ protected Metric getGeneratedMetricForNewCode() {
+ return CoreMetrics.NEW_BRANCH_COVERAGE;
+ }
+
+ @Override
+ protected Long countElementsForNewCode(DecoratorContext context, int periodIndex) {
+ return MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_CONDITIONS_TO_COVER), periodIndex);
+ }
+
+ @Override
+ protected long countCoveredElementsForNewCode(DecoratorContext context, int periodIndex) {
+ long uncoveredConditions = MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_UNCOVERED_CONDITIONS), periodIndex, 0L);
+ long conditions = MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_CONDITIONS_TO_COVER), periodIndex, 0L);
+
+ return conditions - uncoveredConditions;
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/compute/CommentDensityDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/compute/CommentDensityDecorator.java
new file mode 100644
index 00000000000..0379552ffad
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/compute/CommentDensityDecorator.java
@@ -0,0 +1,90 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import java.util.Arrays;
+import java.util.List;
+import org.sonar.api.batch.Decorator;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependedUpon;
+import org.sonar.api.batch.DependsUpon;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.MeasureUtils;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+
+public class CommentDensityDecorator implements Decorator {
+
+ @DependsUpon
+ public List<Metric> dependsUponMetrics() {
+ return Arrays.<Metric>asList(CoreMetrics.NCLOC, CoreMetrics.COMMENT_LINES, CoreMetrics.PUBLIC_API, CoreMetrics.PUBLIC_UNDOCUMENTED_API);
+ }
+
+ @DependedUpon
+ public List<Metric> generatesMetrics() {
+ return Arrays.<Metric>asList(CoreMetrics.COMMENT_LINES_DENSITY, CoreMetrics.PUBLIC_DOCUMENTED_API_DENSITY);
+ }
+
+ @Override
+ public boolean shouldExecuteOnProject(Project project) {
+ return true;
+ }
+
+ @Override
+ public void decorate(Resource resource, DecoratorContext context) {
+ saveCommentsDensity(context);
+ savePublicApiDensity(context);
+ }
+
+ private void saveCommentsDensity(DecoratorContext context) {
+ if (context.getMeasure(CoreMetrics.COMMENT_LINES_DENSITY) != null) {
+ return;
+ }
+
+ Measure ncloc = context.getMeasure(CoreMetrics.NCLOC);
+ Measure comments = context.getMeasure(CoreMetrics.COMMENT_LINES);
+ if (MeasureUtils.hasValue(ncloc) && MeasureUtils.hasValue(comments) && (comments.getValue() + ncloc.getValue()) > 0) {
+ double val = 100.0 * (comments.getValue() / (comments.getValue() + ncloc.getValue()));
+ context.saveMeasure(new Measure(CoreMetrics.COMMENT_LINES_DENSITY, val));
+ }
+ }
+
+ private void savePublicApiDensity(DecoratorContext context) {
+ if (context.getMeasure(CoreMetrics.PUBLIC_DOCUMENTED_API_DENSITY) != null) {
+ return;
+ }
+
+ Measure publicApi = context.getMeasure(CoreMetrics.PUBLIC_API);
+ Measure publicUndocApi = context.getMeasure(CoreMetrics.PUBLIC_UNDOCUMENTED_API);
+
+ if (MeasureUtils.hasValue(publicApi) && MeasureUtils.hasValue(publicUndocApi) && publicApi.getValue() > 0) {
+ double documentedAPI = publicApi.getValue() - publicUndocApi.getValue();
+ Double value = 100.0 * (documentedAPI / publicApi.getValue());
+ context.saveMeasure(new Measure(CoreMetrics.PUBLIC_DOCUMENTED_API_DENSITY, value));
+ }
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/compute/CountFalsePositivesDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/compute/CountFalsePositivesDecorator.java
new file mode 100644
index 00000000000..1eb5456457d
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/compute/CountFalsePositivesDecorator.java
@@ -0,0 +1,86 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import org.sonar.api.batch.Decorator;
+import org.sonar.api.batch.DecoratorBarriers;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependedUpon;
+import org.sonar.api.batch.DependsUpon;
+import org.sonar.api.component.ResourcePerspectives;
+import org.sonar.api.issue.Issuable;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.MeasureUtils;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+
+/**
+ * Computes the number of false-positives
+ *
+ * @since 3.6
+ */
+@DependsUpon(DecoratorBarriers.END_OF_VIOLATION_TRACKING)
+public class CountFalsePositivesDecorator implements Decorator {
+
+ private final ResourcePerspectives perspectives;
+
+ public CountFalsePositivesDecorator(ResourcePerspectives perspectives) {
+ this.perspectives = perspectives;
+ }
+
+ @Override
+ public boolean shouldExecuteOnProject(Project project) {
+ return true;
+ }
+
+ @DependedUpon
+ public Metric generatesFalsePositiveMeasure() {
+ return CoreMetrics.FALSE_POSITIVE_ISSUES;
+ }
+
+ @Override
+ public void decorate(Resource resource, DecoratorContext context) {
+ Issuable issuable = perspectives.as(Issuable.class, resource);
+ if (issuable != null) {
+ int falsePositives = 0;
+ for (Issue issue : issuable.resolvedIssues()) {
+ if (Issue.RESOLUTION_FALSE_POSITIVE.equals(issue.resolution())) {
+ falsePositives++;
+ }
+ }
+ saveMeasure(context, CoreMetrics.FALSE_POSITIVE_ISSUES, falsePositives);
+ }
+ }
+
+ private void saveMeasure(DecoratorContext context, Metric metric, int value) {
+ context.saveMeasure(metric, (double) (value + sumChildren(context, metric)));
+ }
+
+ private int sumChildren(DecoratorContext context, Metric metric) {
+ return MeasureUtils.sum(true, context.getChildrenMeasures(metric)).intValue();
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/compute/CountUnresolvedIssuesDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/compute/CountUnresolvedIssuesDecorator.java
new file mode 100644
index 00000000000..cd57819911a
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/compute/CountUnresolvedIssuesDecorator.java
@@ -0,0 +1,304 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.HashMultiset;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multiset;
+import com.google.common.collect.Sets;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.time.DateUtils;
+import org.sonar.api.batch.Decorator;
+import org.sonar.api.batch.DecoratorBarriers;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependedUpon;
+import org.sonar.api.batch.DependsUpon;
+import org.sonar.api.batch.RequiresDB;
+import org.sonar.api.component.ResourcePerspectives;
+import org.sonar.api.issue.Issuable;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.MeasureUtils;
+import org.sonar.api.measures.MeasuresFilters;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.measures.RuleMeasure;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.resources.ResourceUtils;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.RulePriority;
+import org.sonar.batch.components.Period;
+import org.sonar.batch.components.TimeMachineConfiguration;
+
+/**
+ * Computes metrics related to number of issues.
+ *
+ * @since 3.6
+ */
+@DependsUpon(DecoratorBarriers.ISSUES_TRACKED)
+@RequiresDB
+public class CountUnresolvedIssuesDecorator implements Decorator {
+
+ private final ResourcePerspectives perspectives;
+ private final TimeMachineConfiguration timeMachineConfiguration;
+
+ public CountUnresolvedIssuesDecorator(ResourcePerspectives perspectives, TimeMachineConfiguration timeMachineConfiguration) {
+ this.perspectives = perspectives;
+ this.timeMachineConfiguration = timeMachineConfiguration;
+ }
+
+ @Override
+ public boolean shouldExecuteOnProject(Project project) {
+ return true;
+ }
+
+ @DependedUpon
+ public List<Metric> generatesIssuesMetrics() {
+ return ImmutableList.<Metric>of(
+ CoreMetrics.VIOLATIONS,
+ CoreMetrics.BLOCKER_VIOLATIONS,
+ CoreMetrics.CRITICAL_VIOLATIONS,
+ CoreMetrics.MAJOR_VIOLATIONS,
+ CoreMetrics.MINOR_VIOLATIONS,
+ CoreMetrics.INFO_VIOLATIONS,
+ CoreMetrics.NEW_VIOLATIONS,
+ CoreMetrics.NEW_BLOCKER_VIOLATIONS,
+ CoreMetrics.NEW_CRITICAL_VIOLATIONS,
+ CoreMetrics.NEW_MAJOR_VIOLATIONS,
+ CoreMetrics.NEW_MINOR_VIOLATIONS,
+ CoreMetrics.NEW_INFO_VIOLATIONS,
+ CoreMetrics.OPEN_ISSUES,
+ CoreMetrics.REOPENED_ISSUES,
+ CoreMetrics.CONFIRMED_ISSUES
+ );
+ }
+
+ @Override
+ public void decorate(Resource resource, DecoratorContext context) {
+ Issuable issuable = perspectives.as(Issuable.class, resource);
+ if (issuable != null) {
+ Collection<Issue> issues = issuable.issues();
+ boolean shouldSaveNewMetrics = shouldSaveNewMetrics(context);
+
+ Multiset<RulePriority> severityBag = HashMultiset.create();
+ Map<RulePriority, Multiset<RuleKey>> rulesPerSeverity = Maps.newHashMap();
+ ListMultimap<RulePriority, Issue> issuesPerSeverity = ArrayListMultimap.create();
+ int countOpen = 0;
+ int countReopened = 0;
+ int countConfirmed = 0;
+
+ for (Issue issue : issues) {
+ severityBag.add(RulePriority.valueOf(issue.severity()));
+ Multiset<RuleKey> rulesBag = initRules(rulesPerSeverity, RulePriority.valueOf(issue.severity()));
+ rulesBag.add(issue.ruleKey());
+ issuesPerSeverity.put(RulePriority.valueOf(issue.severity()), issue);
+
+ if (Issue.STATUS_OPEN.equals(issue.status())) {
+ countOpen++;
+ } else if (Issue.STATUS_REOPENED.equals(issue.status())) {
+ countReopened++;
+ } else if (Issue.STATUS_CONFIRMED.equals(issue.status())) {
+ countConfirmed++;
+ }
+ }
+
+ for (RulePriority ruleSeverity : RulePriority.values()) {
+ saveIssuesForSeverity(context, ruleSeverity, severityBag);
+ saveIssuesPerRules(context, ruleSeverity, rulesPerSeverity);
+ saveNewIssuesForSeverity(context, ruleSeverity, issuesPerSeverity, shouldSaveNewMetrics);
+ saveNewIssuesPerRule(context, ruleSeverity, issues, shouldSaveNewMetrics);
+ }
+
+ saveTotalIssues(context, issues);
+ saveNewIssues(context, issues, shouldSaveNewMetrics);
+
+ saveMeasure(context, CoreMetrics.OPEN_ISSUES, countOpen);
+ saveMeasure(context, CoreMetrics.REOPENED_ISSUES, countReopened);
+ saveMeasure(context, CoreMetrics.CONFIRMED_ISSUES, countConfirmed);
+ }
+ }
+
+ private void saveTotalIssues(DecoratorContext context, Collection<Issue> issues) {
+ if (context.getMeasure(CoreMetrics.VIOLATIONS) == null) {
+ Collection<Measure> childrenIssues = context.getChildrenMeasures(CoreMetrics.VIOLATIONS);
+ Double sum = MeasureUtils.sum(true, childrenIssues);
+ context.saveMeasure(CoreMetrics.VIOLATIONS, sum + issues.size());
+ }
+ }
+
+ private void saveNewIssues(DecoratorContext context, Collection<Issue> issues, boolean shouldSaveNewMetrics) {
+ if (shouldSaveNewMetrics) {
+ Measure measure = new Measure(CoreMetrics.NEW_VIOLATIONS);
+ saveNewIssues(context, measure, issues);
+ }
+ }
+
+ private void saveIssuesForSeverity(DecoratorContext context, RulePriority ruleSeverity, Multiset<RulePriority> severitiesBag) {
+ Metric metric = SeverityUtils.severityToIssueMetric(ruleSeverity);
+ if (context.getMeasure(metric) == null) {
+ Collection<Measure> children = context.getChildrenMeasures(MeasuresFilters.metric(metric));
+ int sum = MeasureUtils.sum(true, children).intValue() + severitiesBag.count(ruleSeverity);
+ context.saveMeasure(metric, (double) sum);
+ }
+ }
+
+ private void saveNewIssuesForSeverity(DecoratorContext context, RulePriority severity, ListMultimap<RulePriority, Issue> issuesPerSeverities, boolean shouldSaveNewMetrics) {
+ if (shouldSaveNewMetrics) {
+ Metric metric = SeverityUtils.severityToNewMetricIssue(severity);
+ Measure measure = new Measure(metric);
+ saveNewIssues(context, measure, issuesPerSeverities.get(severity));
+ }
+ }
+
+ private void saveIssuesPerRules(DecoratorContext context, RulePriority severity, Map<RulePriority, Multiset<RuleKey>> rulesPerSeverity) {
+ Metric metric = SeverityUtils.severityToIssueMetric(severity);
+
+ Collection<Measure> children = context.getChildrenMeasures(MeasuresFilters.rules(metric));
+ for (Measure child : children) {
+ RuleMeasure childRuleMeasure = (RuleMeasure) child;
+ RuleKey ruleKey = childRuleMeasure.ruleKey();
+ if (ruleKey != null && MeasureUtils.hasValue(childRuleMeasure)) {
+ Multiset<RuleKey> rulesBag = initRules(rulesPerSeverity, severity);
+ rulesBag.add(ruleKey, childRuleMeasure.getIntValue());
+ }
+ }
+
+ Multiset<RuleKey> rulesBag = rulesPerSeverity.get(severity);
+ if (rulesBag != null) {
+ for (Multiset.Entry<RuleKey> entry : rulesBag.entrySet()) {
+ RuleMeasure measure = RuleMeasure.createForRule(metric, entry.getElement(), (double) entry.getCount());
+ measure.setSeverity(severity);
+ context.saveMeasure(measure);
+ }
+ }
+ }
+
+ private void saveNewIssuesPerRule(DecoratorContext context, RulePriority severity, Collection<Issue> issues, boolean shouldSaveNewMetrics) {
+ if (shouldSaveNewMetrics) {
+ Metric metric = SeverityUtils.severityToNewMetricIssue(severity);
+ ListMultimap<RuleKey, Measure> childMeasuresPerRuleKeys = ArrayListMultimap.create();
+ ListMultimap<RuleKey, Issue> issuesPerRuleKeys = ArrayListMultimap.create();
+ Set<RuleKey> ruleKeys = Sets.newHashSet();
+
+ Collection<Measure> children = context.getChildrenMeasures(MeasuresFilters.rules(metric));
+ for (Measure child : children) {
+ RuleMeasure childRuleMeasure = (RuleMeasure) child;
+ RuleKey ruleKey = childRuleMeasure.ruleKey();
+ if (ruleKey != null) {
+ childMeasuresPerRuleKeys.put(ruleKey, childRuleMeasure);
+ ruleKeys.add(ruleKey);
+ }
+ }
+
+ for (Issue issue : issues) {
+ if (RulePriority.valueOf(issue.severity()).equals(severity)) {
+ ruleKeys.add(issue.ruleKey());
+ issuesPerRuleKeys.put(issue.ruleKey(), issue);
+ }
+ }
+
+ for (RuleKey ruleKey : ruleKeys) {
+ RuleMeasure measure = RuleMeasure.createForRule(metric, ruleKey, null);
+ measure.setSeverity(severity);
+ for (Period period : timeMachineConfiguration.periods()) {
+ int variationIndex = period.getIndex();
+ double sum = MeasureUtils.sumOnVariation(true, variationIndex, childMeasuresPerRuleKeys.get(ruleKey)) + countIssues(issuesPerRuleKeys.get(ruleKey), period);
+ measure.setVariation(variationIndex, sum);
+ }
+ context.saveMeasure(measure);
+ }
+ }
+ }
+
+ private void saveNewIssues(DecoratorContext context, Measure measure, Collection<Issue> issues) {
+ for (Period period : timeMachineConfiguration.periods()) {
+ int variationIndex = period.getIndex();
+ Collection<Measure> children = context.getChildrenMeasures(measure.getMetric());
+ double sum = MeasureUtils.sumOnVariation(true, variationIndex, children) + countIssues(issues, period);
+ measure.setVariation(variationIndex, sum);
+ }
+ context.saveMeasure(measure);
+ }
+
+ private void saveMeasure(DecoratorContext context, Metric metric, int value) {
+ context.saveMeasure(metric, (double) (value + sumChildren(context, metric)));
+ }
+
+ private int sumChildren(DecoratorContext context, Metric metric) {
+ int sum = 0;
+ if (!ResourceUtils.isFile(context.getResource())) {
+ sum = MeasureUtils.sum(true, context.getChildrenMeasures(metric)).intValue();
+ }
+ return sum;
+ }
+
+ private Multiset<RuleKey> initRules(Map<RulePriority, Multiset<RuleKey>> rulesPerSeverity, RulePriority severity) {
+ Multiset<RuleKey> rulesBag = rulesPerSeverity.get(severity);
+ if (rulesBag == null) {
+ rulesBag = HashMultiset.create();
+ rulesPerSeverity.put(severity, rulesBag);
+ }
+ return rulesBag;
+ }
+
+ private int countIssues(Collection<Issue> issues, Period period) {
+ // SONAR-3647 Use real snapshot date and not target date in order to stay consistent with other measure variations
+ Date datePlusOneSecond = period.getDate() != null ? DateUtils.addSeconds(period.getDate(), 1) : null;
+ return countIssuesAfterDate(issues, datePlusOneSecond);
+ }
+
+ @VisibleForTesting
+ int countIssuesAfterDate(Collection<Issue> issues, @Nullable Date date) {
+ if (issues == null) {
+ return 0;
+ }
+ int count = 0;
+ for (Issue issue : issues) {
+ if (isAfter(issue, date)) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ private boolean isAfter(Issue issue, @Nullable Date date) {
+ return date == null || (issue.creationDate() != null && DateUtils.truncatedCompareTo(issue.creationDate(), date, Calendar.SECOND) > 0);
+ }
+
+ private boolean shouldSaveNewMetrics(DecoratorContext context) {
+ return context.getMeasure(CoreMetrics.NEW_VIOLATIONS) == null;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/compute/CoverageDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/compute/CoverageDecorator.java
new file mode 100644
index 00000000000..7d8bed2ab61
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/compute/CoverageDecorator.java
@@ -0,0 +1,86 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import java.util.Collection;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependsUpon;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.MeasureUtils;
+import org.sonar.api.measures.Metric;
+import org.sonar.batch.sensor.coverage.CoverageConstants;
+
+public final class CoverageDecorator extends AbstractCoverageDecorator {
+
+ @DependsUpon
+ public Collection<Metric> usedMetrics() {
+ return CoverageConstants.COVERAGE_METRICS;
+ }
+
+ @Override
+ protected Metric getGeneratedMetric() {
+ return CoreMetrics.COVERAGE;
+ }
+
+ @Override
+ protected Long countElements(DecoratorContext context) {
+ long linesToCover = MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.LINES_TO_COVER), 0L);
+ long conditions = MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.CONDITIONS_TO_COVER), 0L);
+
+ return linesToCover + conditions;
+ }
+
+ @Override
+ protected long countCoveredElements(DecoratorContext context) {
+ long uncoveredLines = MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.UNCOVERED_LINES), 0L);
+ long lines = MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.LINES_TO_COVER), 0L);
+ long uncoveredConditions = MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.UNCOVERED_CONDITIONS), 0L);
+ long conditions = MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.CONDITIONS_TO_COVER), 0L);
+
+ return lines + conditions - uncoveredConditions - uncoveredLines;
+ }
+
+ @Override
+ protected Metric getGeneratedMetricForNewCode() {
+ return CoreMetrics.NEW_COVERAGE;
+ }
+
+ @Override
+ protected Long countElementsForNewCode(DecoratorContext context, int periodIndex) {
+ Long newLinesToCover = MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_LINES_TO_COVER), periodIndex);
+ if (newLinesToCover == null) {
+ return null;
+ }
+
+ long newConditionsToCover = MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_CONDITIONS_TO_COVER), periodIndex, 0L);
+
+ return newLinesToCover + newConditionsToCover;
+ }
+
+ @Override
+ protected long countCoveredElementsForNewCode(DecoratorContext context, int periodIndex) {
+ long newLines = MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_LINES_TO_COVER), periodIndex, 0L);
+ long newUncoveredLines = MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_UNCOVERED_LINES), periodIndex, 0L);
+ long newUncoveredConditions = MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_UNCOVERED_CONDITIONS), periodIndex, 0L);
+ long newConditions = MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_CONDITIONS_TO_COVER), periodIndex, 0L);
+
+ return newLines + newConditions - newUncoveredConditions - newUncoveredLines;
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/compute/DirectoriesDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/compute/DirectoriesDecorator.java
new file mode 100644
index 00000000000..e1dc25e7740
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/compute/DirectoriesDecorator.java
@@ -0,0 +1,69 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import java.util.Collection;
+import org.sonar.api.batch.Decorator;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependedUpon;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.MeasureUtils;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.resources.ResourceUtils;
+
+/**
+ * @since 2.2
+ */
+public final class DirectoriesDecorator implements Decorator {
+
+ @Override
+ public boolean shouldExecuteOnProject(Project project) {
+ return true;
+ }
+
+ @DependedUpon
+ public Metric generateDirectoriesMetric() {
+ return CoreMetrics.DIRECTORIES;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void decorate(Resource resource, DecoratorContext context) {
+ if (MeasureUtils.hasValue(context.getMeasure(CoreMetrics.DIRECTORIES))) {
+ return;
+ }
+
+ if (Resource.QUALIFIER_DIRECTORY.equals(resource.getQualifier())) {
+ context.saveMeasure(CoreMetrics.DIRECTORIES, 1.0);
+
+ } else if (ResourceUtils.isSet(resource)) {
+ Collection<Measure> childrenMeasures = context.getChildrenMeasures(CoreMetrics.DIRECTORIES);
+ Double sum = MeasureUtils.sum(false, childrenMeasures);
+ if (sum != null) {
+ context.saveMeasure(CoreMetrics.DIRECTORIES, sum);
+ }
+ }
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/compute/FilesDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/compute/FilesDecorator.java
new file mode 100644
index 00000000000..27b0cbd07f8
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/compute/FilesDecorator.java
@@ -0,0 +1,65 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import java.util.Collection;
+import org.sonar.api.batch.Decorator;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependedUpon;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.MeasureUtils;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+
+/**
+ * @since 2.2
+ */
+public final class FilesDecorator implements Decorator {
+
+ @Override
+ public boolean shouldExecuteOnProject(Project project) {
+ return true;
+ }
+
+ @DependedUpon
+ public Metric generateDirectoriesMetric() {
+ return CoreMetrics.FILES;
+ }
+
+ @Override
+ public void decorate(Resource resource, DecoratorContext context) {
+ if (MeasureUtils.hasValue(context.getMeasure(CoreMetrics.FILES))) {
+ return;
+ }
+
+ if (Resource.QUALIFIER_CLASS.equals(resource.getQualifier()) || Resource.QUALIFIER_FILE.equals(resource.getQualifier())) {
+ context.saveMeasure(CoreMetrics.FILES, 1.0);
+
+ } else {
+ Collection<Measure> childrenMeasures = context.getChildrenMeasures(CoreMetrics.FILES);
+ Double sum = MeasureUtils.sum(false, childrenMeasures);
+ if (sum != null) {
+ context.saveMeasure(CoreMetrics.FILES, sum);
+ }
+ }
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/compute/ItBranchCoverageDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/compute/ItBranchCoverageDecorator.java
new file mode 100644
index 00000000000..926a34df070
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/compute/ItBranchCoverageDecorator.java
@@ -0,0 +1,73 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependsUpon;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.MeasureUtils;
+import org.sonar.api.measures.Metric;
+
+public final class ItBranchCoverageDecorator extends AbstractCoverageDecorator {
+
+ @DependsUpon
+ public List<Metric> dependsUponMetrics() {
+ return ImmutableList.<Metric>of(CoreMetrics.IT_UNCOVERED_CONDITIONS, CoreMetrics.IT_CONDITIONS_TO_COVER,
+ CoreMetrics.NEW_IT_UNCOVERED_CONDITIONS, CoreMetrics.NEW_IT_CONDITIONS_TO_COVER);
+ }
+
+ @Override
+ protected Metric getGeneratedMetric() {
+ return CoreMetrics.IT_BRANCH_COVERAGE;
+ }
+
+ @Override
+ protected Long countElements(DecoratorContext context) {
+ return MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.IT_CONDITIONS_TO_COVER), 0L);
+ }
+
+ @Override
+ protected long countCoveredElements(DecoratorContext context) {
+ long uncoveredConditions = MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.IT_UNCOVERED_CONDITIONS), 0L);
+ long conditions = MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.IT_CONDITIONS_TO_COVER), 0L);
+
+ return conditions - uncoveredConditions;
+ }
+
+ @Override
+ protected Metric getGeneratedMetricForNewCode() {
+ return CoreMetrics.NEW_IT_BRANCH_COVERAGE;
+ }
+
+ @Override
+ protected Long countElementsForNewCode(DecoratorContext context, int periodIndex) {
+ return MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_IT_CONDITIONS_TO_COVER), periodIndex);
+ }
+
+ @Override
+ protected long countCoveredElementsForNewCode(DecoratorContext context, int periodIndex) {
+ long uncoveredConditions = MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_IT_UNCOVERED_CONDITIONS), periodIndex, 0L);
+ long conditions = MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_IT_CONDITIONS_TO_COVER), periodIndex, 0L);
+
+ return conditions - uncoveredConditions;
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/compute/ItCoverageDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/compute/ItCoverageDecorator.java
new file mode 100644
index 00000000000..4d99b759170
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/compute/ItCoverageDecorator.java
@@ -0,0 +1,87 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependsUpon;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.MeasureUtils;
+import org.sonar.api.measures.Metric;
+
+public final class ItCoverageDecorator extends AbstractCoverageDecorator {
+
+ @DependsUpon
+ public Collection<Metric> usedMetrics() {
+ return ImmutableList.<Metric>of(CoreMetrics.IT_LINES_TO_COVER, CoreMetrics.IT_UNCOVERED_LINES, CoreMetrics.NEW_IT_LINES_TO_COVER,
+ CoreMetrics.NEW_IT_UNCOVERED_LINES, CoreMetrics.IT_CONDITIONS_TO_COVER, CoreMetrics.IT_UNCOVERED_CONDITIONS,
+ CoreMetrics.NEW_IT_CONDITIONS_TO_COVER, CoreMetrics.NEW_IT_UNCOVERED_CONDITIONS);
+ }
+
+ @Override
+ protected Metric getGeneratedMetric() {
+ return CoreMetrics.IT_COVERAGE;
+ }
+
+ @Override
+ protected Long countElements(DecoratorContext context) {
+ long lines = MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.IT_LINES_TO_COVER), 0L);
+ long conditions = MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.IT_CONDITIONS_TO_COVER), 0L);
+
+ return lines + conditions;
+ }
+
+ @Override
+ protected long countCoveredElements(DecoratorContext context) {
+ long uncoveredLines = MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.IT_UNCOVERED_LINES), 0L);
+ long lines = MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.IT_LINES_TO_COVER), 0L);
+ long uncoveredConditions = MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.IT_UNCOVERED_CONDITIONS), 0L);
+ long conditions = MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.IT_CONDITIONS_TO_COVER), 0L);
+
+ return lines + conditions - uncoveredConditions - uncoveredLines;
+ }
+
+ @Override
+ protected Metric getGeneratedMetricForNewCode() {
+ return CoreMetrics.NEW_IT_COVERAGE;
+ }
+
+ @Override
+ protected Long countElementsForNewCode(DecoratorContext context, int periodIndex) {
+ Long newLinesToCover = MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_IT_LINES_TO_COVER), periodIndex);
+ if (newLinesToCover == null) {
+ return null;
+ }
+
+ long newConditionsToCover = MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_IT_CONDITIONS_TO_COVER), periodIndex, 0L);
+ return newLinesToCover + newConditionsToCover;
+ }
+
+ @Override
+ protected long countCoveredElementsForNewCode(DecoratorContext context, int periodIndex) {
+ long newLines = MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_IT_LINES_TO_COVER), periodIndex, 0L);
+ long newUncoveredLines = MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_IT_UNCOVERED_LINES), periodIndex, 0L);
+ long newUncoveredConditions = MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_IT_UNCOVERED_CONDITIONS), periodIndex, 0L);
+ long newConditions = MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_IT_CONDITIONS_TO_COVER), periodIndex, 0L);
+
+ return newLines + newConditions - newUncoveredConditions - newUncoveredLines;
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/compute/ItLineCoverageDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/compute/ItLineCoverageDecorator.java
new file mode 100644
index 00000000000..dddddd0aac4
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/compute/ItLineCoverageDecorator.java
@@ -0,0 +1,73 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependsUpon;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.MeasureUtils;
+import org.sonar.api.measures.Metric;
+
+public final class ItLineCoverageDecorator extends AbstractCoverageDecorator {
+
+ @DependsUpon
+ public List<Metric> dependsUponMetrics() {
+ return ImmutableList.<Metric>of(CoreMetrics.IT_UNCOVERED_LINES, CoreMetrics.IT_LINES_TO_COVER, CoreMetrics.NEW_IT_UNCOVERED_LINES,
+ CoreMetrics.NEW_IT_LINES_TO_COVER);
+ }
+
+ @Override
+ protected Metric getGeneratedMetric() {
+ return CoreMetrics.IT_LINE_COVERAGE;
+ }
+
+ @Override
+ protected Long countElements(DecoratorContext context) {
+ return MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.IT_LINES_TO_COVER), 0L);
+ }
+
+ @Override
+ protected long countCoveredElements(DecoratorContext context) {
+ long uncoveredLines = MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.IT_UNCOVERED_LINES), 0L);
+ long lines = MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.IT_LINES_TO_COVER), 0L);
+
+ return lines - uncoveredLines;
+ }
+
+ @Override
+ protected Metric getGeneratedMetricForNewCode() {
+ return CoreMetrics.NEW_IT_LINE_COVERAGE;
+ }
+
+ @Override
+ protected Long countElementsForNewCode(DecoratorContext context, int periodIndex) {
+ return MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_IT_LINES_TO_COVER), periodIndex);
+ }
+
+ @Override
+ protected long countCoveredElementsForNewCode(DecoratorContext context, int periodIndex) {
+ long uncoveredLines = MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_IT_UNCOVERED_LINES), periodIndex, 0L);
+ long lines = MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_IT_LINES_TO_COVER), periodIndex, 0L);
+
+ return lines - uncoveredLines;
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/compute/LineCoverageDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/compute/LineCoverageDecorator.java
new file mode 100644
index 00000000000..71c6a034ac6
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/compute/LineCoverageDecorator.java
@@ -0,0 +1,72 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import java.util.Collection;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependsUpon;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.MeasureUtils;
+import org.sonar.api.measures.Metric;
+import org.sonar.batch.sensor.coverage.CoverageConstants;
+
+public final class LineCoverageDecorator extends AbstractCoverageDecorator {
+
+ @DependsUpon
+ public Collection<Metric> dependsUponMetrics() {
+ return CoverageConstants.LINE_COVERAGE_METRICS;
+ }
+
+ @Override
+ protected Metric getGeneratedMetric() {
+ return CoreMetrics.LINE_COVERAGE;
+ }
+
+ @Override
+ protected Long countElements(DecoratorContext context) {
+ return MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.LINES_TO_COVER), 0L);
+ }
+
+ @Override
+ protected long countCoveredElements(DecoratorContext context) {
+ long uncoveredLines = MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.UNCOVERED_LINES), 0L);
+ long lines = MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.LINES_TO_COVER), 0L);
+
+ return lines - uncoveredLines;
+ }
+
+ @Override
+ protected Metric getGeneratedMetricForNewCode() {
+ return CoreMetrics.NEW_LINE_COVERAGE;
+ }
+
+ @Override
+ protected Long countElementsForNewCode(DecoratorContext context, int periodIndex) {
+ return MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_LINES_TO_COVER), periodIndex);
+ }
+
+ @Override
+ protected long countCoveredElementsForNewCode(DecoratorContext context, int periodIndex) {
+ long uncoveredLines = MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_UNCOVERED_LINES), periodIndex, 0L);
+ long lines = MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_LINES_TO_COVER), periodIndex, 0L);
+
+ return lines - uncoveredLines;
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/compute/ManualMeasureDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/compute/ManualMeasureDecorator.java
new file mode 100644
index 00000000000..94346b30b8f
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/compute/ManualMeasureDecorator.java
@@ -0,0 +1,79 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import java.util.List;
+import org.sonar.api.batch.Decorator;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.Phase;
+import org.sonar.api.batch.RequiresDB;
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.measures.MetricFinder;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.jpa.entity.ManualMeasure;
+
+import static com.google.common.base.Preconditions.checkState;
+
+@Phase(name = Phase.Name.PRE)
+@RequiresDB
+public class ManualMeasureDecorator implements Decorator {
+
+ private DatabaseSession session;
+ private MetricFinder metricFinder;
+
+ public ManualMeasureDecorator(DatabaseSession session, MetricFinder metricFinder) {
+ this.session = session;
+ this.metricFinder = metricFinder;
+ }
+
+ @Override
+ public boolean shouldExecuteOnProject(Project project) {
+ return true;
+ }
+
+ @Override
+ public void decorate(Resource resource, DecoratorContext context) {
+ if (resource.getId() != null) {
+ List<ManualMeasure> manualMeasures = session.getResults(ManualMeasure.class, "resourceId", resource.getId());
+ for (ManualMeasure manualMeasure : manualMeasures) {
+ context.saveMeasure(copy(manualMeasure));
+ }
+ }
+ }
+
+ private Measure copy(ManualMeasure manualMeasure) {
+ Metric metric = metricFinder.findById(manualMeasure.getMetricId());
+ checkState(metric != null, "Unable to find manual metric with id: " + manualMeasure.getMetricId());
+
+ Measure measure = new Measure(metric);
+ measure.setValue(manualMeasure.getValue(), 5);
+ measure.setData(manualMeasure.getTextValue());
+ measure.setDescription(manualMeasure.getDescription());
+ return measure;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/compute/NewCoverageAggregator.java b/sonar-batch/src/main/java/org/sonar/batch/compute/NewCoverageAggregator.java
new file mode 100644
index 00000000000..1923d5211a6
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/compute/NewCoverageAggregator.java
@@ -0,0 +1,99 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import java.util.Arrays;
+import java.util.List;
+import org.apache.commons.lang.ArrayUtils;
+import org.sonar.api.batch.Decorator;
+import org.sonar.api.batch.DecoratorBarriers;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependedUpon;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.resources.Scopes;
+
+@DependedUpon(DecoratorBarriers.END_OF_TIME_MACHINE)
+public final class NewCoverageAggregator implements Decorator {
+
+ @Override
+ public boolean shouldExecuteOnProject(Project project) {
+ return true;
+ }
+
+ @DependedUpon
+ public List<Metric> generatesNewCoverageMetrics() {
+ return Arrays.<Metric>asList(
+ CoreMetrics.NEW_LINES_TO_COVER, CoreMetrics.NEW_UNCOVERED_LINES, CoreMetrics.NEW_CONDITIONS_TO_COVER, CoreMetrics.NEW_UNCOVERED_CONDITIONS,
+ CoreMetrics.NEW_IT_LINES_TO_COVER, CoreMetrics.NEW_IT_UNCOVERED_LINES, CoreMetrics.NEW_IT_CONDITIONS_TO_COVER, CoreMetrics.NEW_IT_UNCOVERED_CONDITIONS,
+ CoreMetrics.NEW_OVERALL_LINES_TO_COVER, CoreMetrics.NEW_OVERALL_UNCOVERED_LINES, CoreMetrics.NEW_OVERALL_CONDITIONS_TO_COVER, CoreMetrics.NEW_OVERALL_UNCOVERED_CONDITIONS);
+ }
+
+ @Override
+ public void decorate(Resource resource, DecoratorContext context) {
+ if (shouldDecorate(resource)) {
+ int maxPeriods = Qualifiers.isView(resource, true) ? 3 : 5;
+ aggregate(context, CoreMetrics.NEW_LINES_TO_COVER, maxPeriods);
+ aggregate(context, CoreMetrics.NEW_UNCOVERED_LINES, maxPeriods);
+ aggregate(context, CoreMetrics.NEW_CONDITIONS_TO_COVER, maxPeriods);
+ aggregate(context, CoreMetrics.NEW_UNCOVERED_CONDITIONS, maxPeriods);
+ aggregate(context, CoreMetrics.NEW_IT_LINES_TO_COVER, maxPeriods);
+ aggregate(context, CoreMetrics.NEW_IT_UNCOVERED_LINES, maxPeriods);
+ aggregate(context, CoreMetrics.NEW_IT_CONDITIONS_TO_COVER, maxPeriods);
+ aggregate(context, CoreMetrics.NEW_IT_UNCOVERED_CONDITIONS, maxPeriods);
+ aggregate(context, CoreMetrics.NEW_OVERALL_LINES_TO_COVER, maxPeriods);
+ aggregate(context, CoreMetrics.NEW_OVERALL_UNCOVERED_LINES, maxPeriods);
+ aggregate(context, CoreMetrics.NEW_OVERALL_CONDITIONS_TO_COVER, maxPeriods);
+ aggregate(context, CoreMetrics.NEW_OVERALL_UNCOVERED_CONDITIONS, maxPeriods);
+ }
+ }
+
+ void aggregate(DecoratorContext context, Metric metric, int maxPeriods) {
+ int[] variations = {0, 0, 0, 0, 0};
+ boolean[] hasValues = {false, false, false, false, false};
+ for (Measure child : context.getChildrenMeasures(metric)) {
+ for (int indexPeriod = 1; indexPeriod <= maxPeriods; indexPeriod++) {
+ Double variation = child.getVariation(indexPeriod);
+ if (variation != null) {
+ variations[indexPeriod - 1] = variations[indexPeriod - 1] + variation.intValue();
+ hasValues[indexPeriod - 1] = true;
+ }
+ }
+ }
+
+ if (ArrayUtils.contains(hasValues, true)) {
+ Measure measure = new Measure(metric);
+ for (int index = 0; index < 5; index++) {
+ if (hasValues[index]) {
+ measure.setVariation(index + 1, (double) variations[index]);
+ }
+ }
+ context.saveMeasure(measure);
+ }
+ }
+
+ boolean shouldDecorate(Resource resource) {
+ return Scopes.isHigherThan(resource, Scopes.FILE);
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/compute/NewCoverageFileAnalyzer.java b/sonar-batch/src/main/java/org/sonar/batch/compute/NewCoverageFileAnalyzer.java
new file mode 100644
index 00000000000..ba36d5b782b
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/compute/NewCoverageFileAnalyzer.java
@@ -0,0 +1,73 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import java.util.List;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Metric;
+import org.sonar.batch.components.TimeMachineConfiguration;
+import org.sonar.batch.index.BatchComponentCache;
+import org.sonar.batch.report.ReportPublisher;
+
+public class NewCoverageFileAnalyzer extends AbstractNewCoverageFileAnalyzer {
+
+ public NewCoverageFileAnalyzer(TimeMachineConfiguration timeMachineConfiguration, ReportPublisher publishReportJob, BatchComponentCache resourceCache) {
+ super(timeMachineConfiguration, publishReportJob, resourceCache);
+ }
+
+ NewCoverageFileAnalyzer(List<PeriodStruct> structs, ReportPublisher publishReportJob, BatchComponentCache resourceCache) {
+ super(structs, publishReportJob, resourceCache);
+ }
+
+ @Override
+ public Metric getCoverageLineHitsDataMetric() {
+ return CoreMetrics.COVERAGE_LINE_HITS_DATA;
+ }
+
+ @Override
+ public Metric getConditionsByLineMetric() {
+ return CoreMetrics.CONDITIONS_BY_LINE;
+ }
+
+ @Override
+ public Metric getCoveredConditionsByLineMetric() {
+ return CoreMetrics.COVERED_CONDITIONS_BY_LINE;
+ }
+
+ @Override
+ public Metric getNewLinesToCoverMetric() {
+ return CoreMetrics.NEW_LINES_TO_COVER;
+ }
+
+ @Override
+ public Metric getNewUncoveredLinesMetric() {
+ return CoreMetrics.NEW_UNCOVERED_LINES;
+ }
+
+ @Override
+ public Metric getNewConditionsToCoverMetric() {
+ return CoreMetrics.NEW_CONDITIONS_TO_COVER;
+ }
+
+ @Override
+ public Metric getNewUncoveredConditionsMetric() {
+ return CoreMetrics.NEW_UNCOVERED_CONDITIONS;
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/compute/NewItCoverageFileAnalyzer.java b/sonar-batch/src/main/java/org/sonar/batch/compute/NewItCoverageFileAnalyzer.java
new file mode 100644
index 00000000000..48d62305706
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/compute/NewItCoverageFileAnalyzer.java
@@ -0,0 +1,68 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Metric;
+import org.sonar.batch.components.TimeMachineConfiguration;
+import org.sonar.batch.index.BatchComponentCache;
+import org.sonar.batch.report.ReportPublisher;
+
+public class NewItCoverageFileAnalyzer extends AbstractNewCoverageFileAnalyzer {
+
+ public NewItCoverageFileAnalyzer(TimeMachineConfiguration timeMachineConfiguration, ReportPublisher publishReportJob, BatchComponentCache resourceCache) {
+ super(timeMachineConfiguration, publishReportJob, resourceCache);
+ }
+
+ @Override
+ public Metric getCoverageLineHitsDataMetric() {
+ return CoreMetrics.IT_COVERAGE_LINE_HITS_DATA;
+ }
+
+ @Override
+ public Metric getConditionsByLineMetric() {
+ return CoreMetrics.IT_CONDITIONS_BY_LINE;
+ }
+
+ @Override
+ public Metric getCoveredConditionsByLineMetric() {
+ return CoreMetrics.IT_COVERED_CONDITIONS_BY_LINE;
+ }
+
+ @Override
+ public Metric getNewLinesToCoverMetric() {
+ return CoreMetrics.NEW_IT_LINES_TO_COVER;
+ }
+
+ @Override
+ public Metric getNewUncoveredLinesMetric() {
+ return CoreMetrics.NEW_IT_UNCOVERED_LINES;
+ }
+
+ @Override
+ public Metric getNewConditionsToCoverMetric() {
+ return CoreMetrics.NEW_IT_CONDITIONS_TO_COVER;
+ }
+
+ @Override
+ public Metric getNewUncoveredConditionsMetric() {
+ return CoreMetrics.NEW_IT_UNCOVERED_CONDITIONS;
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/compute/NewOverallCoverageFileAnalyzer.java b/sonar-batch/src/main/java/org/sonar/batch/compute/NewOverallCoverageFileAnalyzer.java
new file mode 100644
index 00000000000..1c2688c6243
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/compute/NewOverallCoverageFileAnalyzer.java
@@ -0,0 +1,68 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Metric;
+import org.sonar.batch.components.TimeMachineConfiguration;
+import org.sonar.batch.index.BatchComponentCache;
+import org.sonar.batch.report.ReportPublisher;
+
+public class NewOverallCoverageFileAnalyzer extends AbstractNewCoverageFileAnalyzer {
+
+ public NewOverallCoverageFileAnalyzer(TimeMachineConfiguration timeMachineConfiguration, ReportPublisher publishReportJob, BatchComponentCache resourceCache) {
+ super(timeMachineConfiguration, publishReportJob, resourceCache);
+ }
+
+ @Override
+ public Metric getCoverageLineHitsDataMetric() {
+ return CoreMetrics.OVERALL_COVERAGE_LINE_HITS_DATA;
+ }
+
+ @Override
+ public Metric getConditionsByLineMetric() {
+ return CoreMetrics.OVERALL_CONDITIONS_BY_LINE;
+ }
+
+ @Override
+ public Metric getCoveredConditionsByLineMetric() {
+ return CoreMetrics.OVERALL_COVERED_CONDITIONS_BY_LINE;
+ }
+
+ @Override
+ public Metric getNewLinesToCoverMetric() {
+ return CoreMetrics.NEW_OVERALL_LINES_TO_COVER;
+ }
+
+ @Override
+ public Metric getNewUncoveredLinesMetric() {
+ return CoreMetrics.NEW_OVERALL_UNCOVERED_LINES;
+ }
+
+ @Override
+ public Metric getNewConditionsToCoverMetric() {
+ return CoreMetrics.NEW_OVERALL_CONDITIONS_TO_COVER;
+ }
+
+ @Override
+ public Metric getNewUncoveredConditionsMetric() {
+ return CoreMetrics.NEW_OVERALL_UNCOVERED_CONDITIONS;
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/compute/OverallBranchCoverageDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/compute/OverallBranchCoverageDecorator.java
new file mode 100644
index 00000000000..271cfcd795f
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/compute/OverallBranchCoverageDecorator.java
@@ -0,0 +1,73 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependsUpon;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.MeasureUtils;
+import org.sonar.api.measures.Metric;
+
+public final class OverallBranchCoverageDecorator extends AbstractCoverageDecorator {
+
+ @DependsUpon
+ public List<Metric> dependsUponMetrics() {
+ return ImmutableList.<Metric>of(CoreMetrics.OVERALL_UNCOVERED_CONDITIONS, CoreMetrics.OVERALL_CONDITIONS_TO_COVER,
+ CoreMetrics.NEW_OVERALL_UNCOVERED_CONDITIONS, CoreMetrics.NEW_OVERALL_CONDITIONS_TO_COVER);
+ }
+
+ @Override
+ protected Metric getGeneratedMetric() {
+ return CoreMetrics.OVERALL_BRANCH_COVERAGE;
+ }
+
+ @Override
+ protected Long countElements(DecoratorContext context) {
+ return MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.OVERALL_CONDITIONS_TO_COVER), 0L);
+ }
+
+ @Override
+ protected long countCoveredElements(DecoratorContext context) {
+ long uncoveredConditions = MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.OVERALL_UNCOVERED_CONDITIONS), 0L);
+ long conditions = MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.OVERALL_CONDITIONS_TO_COVER), 0L);
+
+ return conditions - uncoveredConditions;
+ }
+
+ @Override
+ protected Metric getGeneratedMetricForNewCode() {
+ return CoreMetrics.NEW_OVERALL_BRANCH_COVERAGE;
+ }
+
+ @Override
+ protected Long countElementsForNewCode(DecoratorContext context, int periodIndex) {
+ return MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_OVERALL_CONDITIONS_TO_COVER), periodIndex);
+ }
+
+ @Override
+ protected long countCoveredElementsForNewCode(DecoratorContext context, int periodIndex) {
+ long uncoveredConditions = MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_OVERALL_UNCOVERED_CONDITIONS), periodIndex, 0L);
+ long conditions = MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_OVERALL_CONDITIONS_TO_COVER), periodIndex, 0L);
+
+ return conditions - uncoveredConditions;
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/compute/OverallCoverageDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/compute/OverallCoverageDecorator.java
new file mode 100644
index 00000000000..c4a4d037770
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/compute/OverallCoverageDecorator.java
@@ -0,0 +1,88 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependsUpon;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.MeasureUtils;
+import org.sonar.api.measures.Metric;
+
+public final class OverallCoverageDecorator extends AbstractCoverageDecorator {
+
+ @DependsUpon
+ public Collection<Metric> usedMetrics() {
+ return ImmutableList.<Metric>of(CoreMetrics.OVERALL_LINES_TO_COVER, CoreMetrics.OVERALL_UNCOVERED_LINES, CoreMetrics.NEW_OVERALL_LINES_TO_COVER,
+ CoreMetrics.NEW_OVERALL_UNCOVERED_LINES, CoreMetrics.OVERALL_CONDITIONS_TO_COVER, CoreMetrics.OVERALL_UNCOVERED_CONDITIONS,
+ CoreMetrics.NEW_OVERALL_CONDITIONS_TO_COVER, CoreMetrics.NEW_OVERALL_UNCOVERED_CONDITIONS);
+ }
+
+ @Override
+ protected Metric getGeneratedMetric() {
+ return CoreMetrics.OVERALL_COVERAGE;
+ }
+
+ @Override
+ protected Long countElements(DecoratorContext context) {
+ long lines = MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.OVERALL_LINES_TO_COVER), 0L);
+ long conditions = MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.OVERALL_CONDITIONS_TO_COVER), 0L);
+
+ return lines + conditions;
+ }
+
+ @Override
+ protected long countCoveredElements(DecoratorContext context) {
+ long uncoveredLines = MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.OVERALL_UNCOVERED_LINES), 0L);
+ long lines = MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.OVERALL_LINES_TO_COVER), 0L);
+ long uncoveredConditions = MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.OVERALL_UNCOVERED_CONDITIONS), 0L);
+ long conditions = MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.OVERALL_CONDITIONS_TO_COVER), 0L);
+
+ return lines + conditions - uncoveredConditions - uncoveredLines;
+ }
+
+ @Override
+ protected Metric getGeneratedMetricForNewCode() {
+ return CoreMetrics.NEW_OVERALL_COVERAGE;
+ }
+
+ @Override
+ protected Long countElementsForNewCode(DecoratorContext context, int periodIndex) {
+ Long newLinesToCover = MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_OVERALL_LINES_TO_COVER), periodIndex);
+ if (newLinesToCover == null) {
+ return null;
+ }
+
+ long newConditionsToCover = MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_OVERALL_CONDITIONS_TO_COVER), periodIndex, 0L);
+
+ return newLinesToCover + newConditionsToCover;
+ }
+
+ @Override
+ protected long countCoveredElementsForNewCode(DecoratorContext context, int periodIndex) {
+ long newLines = MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_OVERALL_LINES_TO_COVER), periodIndex, 0L);
+ long newUncoveredLines = MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_OVERALL_UNCOVERED_LINES), periodIndex, 0L);
+ long newUncoveredConditions = MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_OVERALL_UNCOVERED_CONDITIONS), periodIndex, 0L);
+ long newConditions = MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_OVERALL_CONDITIONS_TO_COVER), periodIndex, 0L);
+
+ return newLines + newConditions - newUncoveredConditions - newUncoveredLines;
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/compute/OverallLineCoverageDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/compute/OverallLineCoverageDecorator.java
new file mode 100644
index 00000000000..a41440e6314
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/compute/OverallLineCoverageDecorator.java
@@ -0,0 +1,73 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependsUpon;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.MeasureUtils;
+import org.sonar.api.measures.Metric;
+
+public final class OverallLineCoverageDecorator extends AbstractCoverageDecorator {
+
+ @DependsUpon
+ public List<Metric> dependsUponMetrics() {
+ return ImmutableList.<Metric>of(CoreMetrics.OVERALL_UNCOVERED_LINES, CoreMetrics.OVERALL_LINES_TO_COVER, CoreMetrics.NEW_OVERALL_UNCOVERED_LINES,
+ CoreMetrics.NEW_OVERALL_LINES_TO_COVER);
+ }
+
+ @Override
+ protected Metric getGeneratedMetric() {
+ return CoreMetrics.OVERALL_LINE_COVERAGE;
+ }
+
+ @Override
+ protected Long countElements(DecoratorContext context) {
+ return MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.OVERALL_LINES_TO_COVER), 0L);
+ }
+
+ @Override
+ protected long countCoveredElements(DecoratorContext context) {
+ long uncoveredLines = MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.OVERALL_UNCOVERED_LINES), 0L);
+ long lines = MeasureUtils.getValueAsLong(context.getMeasure(CoreMetrics.OVERALL_LINES_TO_COVER), 0L);
+
+ return lines - uncoveredLines;
+ }
+
+ @Override
+ protected Metric getGeneratedMetricForNewCode() {
+ return CoreMetrics.NEW_OVERALL_LINE_COVERAGE;
+ }
+
+ @Override
+ protected Long countElementsForNewCode(DecoratorContext context, int periodIndex) {
+ return MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_OVERALL_LINES_TO_COVER), periodIndex);
+ }
+
+ @Override
+ protected long countCoveredElementsForNewCode(DecoratorContext context, int periodIndex) {
+ long uncoveredLines = MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_OVERALL_UNCOVERED_LINES), periodIndex, 0L);
+ long lines = MeasureUtils.getVariationAsLong(context.getMeasure(CoreMetrics.NEW_OVERALL_LINES_TO_COVER), periodIndex, 0L);
+
+ return lines - uncoveredLines;
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/compute/SeverityUtils.java b/sonar-batch/src/main/java/org/sonar/batch/compute/SeverityUtils.java
new file mode 100644
index 00000000000..fd92039ce98
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/compute/SeverityUtils.java
@@ -0,0 +1,66 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.rules.RulePriority;
+
+final class SeverityUtils {
+ private SeverityUtils() {
+ // only static methods
+ }
+
+ static Metric severityToIssueMetric(RulePriority severity) {
+ Metric metric;
+ if (severity.equals(RulePriority.BLOCKER)) {
+ metric = CoreMetrics.BLOCKER_VIOLATIONS;
+ } else if (severity.equals(RulePriority.CRITICAL)) {
+ metric = CoreMetrics.CRITICAL_VIOLATIONS;
+ } else if (severity.equals(RulePriority.MAJOR)) {
+ metric = CoreMetrics.MAJOR_VIOLATIONS;
+ } else if (severity.equals(RulePriority.MINOR)) {
+ metric = CoreMetrics.MINOR_VIOLATIONS;
+ } else if (severity.equals(RulePriority.INFO)) {
+ metric = CoreMetrics.INFO_VIOLATIONS;
+ } else {
+ throw new IllegalArgumentException("Unsupported severity: " + severity);
+ }
+ return metric;
+ }
+
+ static Metric severityToNewMetricIssue(RulePriority severity) {
+ Metric metric;
+ if (severity.equals(RulePriority.BLOCKER)) {
+ metric = CoreMetrics.NEW_BLOCKER_VIOLATIONS;
+ } else if (severity.equals(RulePriority.CRITICAL)) {
+ metric = CoreMetrics.NEW_CRITICAL_VIOLATIONS;
+ } else if (severity.equals(RulePriority.MAJOR)) {
+ metric = CoreMetrics.NEW_MAJOR_VIOLATIONS;
+ } else if (severity.equals(RulePriority.MINOR)) {
+ metric = CoreMetrics.NEW_MINOR_VIOLATIONS;
+ } else if (severity.equals(RulePriority.INFO)) {
+ metric = CoreMetrics.NEW_INFO_VIOLATIONS;
+ } else {
+ throw new IllegalArgumentException("Unsupported severity: " + severity);
+ }
+ return metric;
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/compute/TimeMachineConfigurationPersister.java b/sonar-batch/src/main/java/org/sonar/batch/compute/TimeMachineConfigurationPersister.java
new file mode 100644
index 00000000000..1255c64a35f
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/compute/TimeMachineConfigurationPersister.java
@@ -0,0 +1,83 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import java.util.List;
+import org.sonar.api.batch.Decorator;
+import org.sonar.api.batch.DecoratorBarriers;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependedUpon;
+import org.sonar.api.batch.RequiresDB;
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.resources.ResourceUtils;
+import org.sonar.batch.components.PastSnapshot;
+import org.sonar.batch.components.TimeMachineConfiguration;
+import org.sonar.batch.index.BatchComponentCache;
+
+import static org.sonar.api.utils.DateUtils.dateToLong;
+
+@DependedUpon(DecoratorBarriers.END_OF_TIME_MACHINE)
+@RequiresDB
+public final class TimeMachineConfigurationPersister implements Decorator {
+
+ private final TimeMachineConfiguration timeMachineConfiguration;
+ private BatchComponentCache resourceCache;
+ private DatabaseSession session;
+
+ public TimeMachineConfigurationPersister(TimeMachineConfiguration timeMachineConfiguration, BatchComponentCache resourceCache, DatabaseSession session) {
+ this.timeMachineConfiguration = timeMachineConfiguration;
+ this.resourceCache = resourceCache;
+ this.session = session;
+ }
+
+ @Override
+ public void decorate(Resource resource, DecoratorContext context) {
+ if (ResourceUtils.isProject(resource)) {
+ persistConfiguration(resource);
+ }
+ }
+
+ void persistConfiguration(Resource module) {
+ List<PastSnapshot> pastSnapshots = timeMachineConfiguration.getProjectPastSnapshots();
+ Snapshot projectSnapshot = resourceCache.get(module).snapshot();
+ for (PastSnapshot pastSnapshot : pastSnapshots) {
+ Snapshot snapshot = session.reattach(Snapshot.class, projectSnapshot.getId());
+ updatePeriodParams(snapshot, pastSnapshot);
+ updatePeriodParams(projectSnapshot, pastSnapshot);
+ session.save(snapshot);
+ }
+ session.commit();
+ }
+
+ @Override
+ public boolean shouldExecuteOnProject(Project project) {
+ return true;
+ }
+
+ private void updatePeriodParams(Snapshot snapshot, PastSnapshot pastSnapshot) {
+ int periodIndex = pastSnapshot.getIndex();
+ snapshot.setPeriodMode(periodIndex, pastSnapshot.getMode());
+ snapshot.setPeriodModeParameter(periodIndex, pastSnapshot.getModeParameter());
+ snapshot.setPeriodDateMs(periodIndex, dateToLong(pastSnapshot.getDate()));
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/compute/UnitTestDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/compute/UnitTestDecorator.java
new file mode 100644
index 00000000000..3f5d641af0c
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/compute/UnitTestDecorator.java
@@ -0,0 +1,95 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import org.sonar.api.batch.Decorator;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependedUpon;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.MeasureUtils;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.resources.ResourceUtils;
+
+public class UnitTestDecorator implements Decorator {
+
+ @DependedUpon
+ public List<Metric> generatesMetrics() {
+ return Arrays.<Metric>asList(CoreMetrics.TEST_EXECUTION_TIME, CoreMetrics.TESTS, CoreMetrics.TEST_ERRORS, CoreMetrics.TEST_FAILURES, CoreMetrics.TEST_SUCCESS_DENSITY);
+ }
+
+ @Override
+ public boolean shouldExecuteOnProject(Project project) {
+ return !Project.AnalysisType.STATIC.equals(project.getAnalysisType());
+ }
+
+ public boolean shouldDecorateResource(Resource resource) {
+ return ResourceUtils.isUnitTestFile(resource) || !ResourceUtils.isEntity(resource);
+ }
+
+ @Override
+ public void decorate(Resource resource, DecoratorContext context) {
+ if (shouldDecorateResource(resource)) {
+ sumChildren(context, CoreMetrics.TEST_EXECUTION_TIME);
+ sumChildren(context, CoreMetrics.SKIPPED_TESTS);
+ Double tests = sumChildren(context, CoreMetrics.TESTS);
+ Double errors = sumChildren(context, CoreMetrics.TEST_ERRORS);
+ Double failures = sumChildren(context, CoreMetrics.TEST_FAILURES);
+
+ if (isPositive(tests, true) && isPositive(errors, false) && isPositive(failures, false)) {
+ Double errorsAndFailuresRatio = (errors + failures) * 100.0 / tests;
+ context.saveMeasure(CoreMetrics.TEST_SUCCESS_DENSITY, 100.0 - errorsAndFailuresRatio);
+ }
+ }
+ }
+
+ private boolean isPositive(Double d, boolean strict) {
+ return d != null && (strict ? d > 0.0 : d >= 0.0);
+ }
+
+ private Double sumChildren(DecoratorContext jobContext, Metric metric) {
+ Collection<Measure> childrenMeasures = jobContext.getChildrenMeasures(metric);
+ if (childrenMeasures != null && !childrenMeasures.isEmpty()) {
+ Double sum = 0.0;
+ boolean hasChildrenMeasures = false;
+ for (Measure measure : childrenMeasures) {
+ if (MeasureUtils.hasValue(measure)) {
+ sum += measure.getValue();
+ hasChildrenMeasures = true;
+ }
+ }
+ if (hasChildrenMeasures) {
+ jobContext.saveMeasure(metric, sum);
+ return sum;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/compute/VariationDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/compute/VariationDecorator.java
new file mode 100644
index 00000000000..f3885ef3f8c
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/compute/VariationDecorator.java
@@ -0,0 +1,205 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.batch.Decorator;
+import org.sonar.api.batch.DecoratorBarriers;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependedUpon;
+import org.sonar.api.batch.DependsUpon;
+import org.sonar.api.batch.RequiresDB;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.MeasuresFilters;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.measures.MetricFinder;
+import org.sonar.api.measures.RuleMeasure;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.resources.Scopes;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleFinder;
+import org.sonar.api.technicaldebt.batch.Characteristic;
+import org.sonar.batch.components.PastMeasuresLoader;
+import org.sonar.batch.components.PastSnapshot;
+import org.sonar.batch.components.TimeMachineConfiguration;
+
+@DependedUpon(DecoratorBarriers.END_OF_TIME_MACHINE)
+@RequiresDB
+public class VariationDecorator implements Decorator {
+
+ private List<PastSnapshot> projectPastSnapshots;
+ private MetricFinder metricFinder;
+ private PastMeasuresLoader pastMeasuresLoader;
+ private RuleFinder ruleFinder;
+
+ public VariationDecorator(PastMeasuresLoader pastMeasuresLoader, MetricFinder metricFinder, TimeMachineConfiguration timeMachineConfiguration, RuleFinder ruleFinder) {
+ this(pastMeasuresLoader, metricFinder, timeMachineConfiguration.getProjectPastSnapshots(), ruleFinder);
+ }
+
+ VariationDecorator(PastMeasuresLoader pastMeasuresLoader, MetricFinder metricFinder, List<PastSnapshot> projectPastSnapshots, RuleFinder ruleFinder) {
+ this.pastMeasuresLoader = pastMeasuresLoader;
+ this.projectPastSnapshots = projectPastSnapshots;
+ this.metricFinder = metricFinder;
+ this.ruleFinder = ruleFinder;
+ }
+
+ @Override
+ public boolean shouldExecuteOnProject(Project project) {
+ return true;
+ }
+
+ @DependsUpon
+ public Collection<Metric> dependsUponMetrics() {
+ return pastMeasuresLoader.getMetrics();
+ }
+
+ @Override
+ public void decorate(Resource resource, DecoratorContext context) {
+ for (PastSnapshot projectPastSnapshot : projectPastSnapshots) {
+ if (shouldComputeVariation(resource)) {
+ computeVariation(resource, context, projectPastSnapshot);
+ }
+ }
+ }
+
+ boolean shouldComputeVariation(Resource resource) {
+ if (Scopes.FILE.equals(resource.getScope()) && !Qualifiers.UNIT_TEST_FILE.equals(resource.getQualifier())) {
+ return false;
+ }
+
+ // measures on files are currently purged, so past measures are not available on files
+ return StringUtils.equals(Scopes.PROJECT, resource.getScope()) || StringUtils.equals(Scopes.DIRECTORY, resource.getScope());
+ }
+
+ private void computeVariation(Resource resource, DecoratorContext context, PastSnapshot pastSnapshot) {
+ List<Object[]> pastMeasures = pastMeasuresLoader.getPastMeasures(resource, pastSnapshot);
+ compareWithPastMeasures(context, pastSnapshot.getIndex(), pastMeasures);
+ }
+
+ private void compareWithPastMeasures(DecoratorContext context, int index, List<Object[]> pastMeasures) {
+ Map<MeasureKey, Object[]> pastMeasuresByKey = new HashMap<>();
+ for (Object[] pastMeasure : pastMeasures) {
+ pastMeasuresByKey.put(new MeasureKey(pastMeasure), pastMeasure);
+ }
+
+ // for each measure, search equivalent past measure
+ for (Measure measure : context.getMeasures(MeasuresFilters.all())) {
+ // compare with past measure
+ Integer metricId = measure.getMetric().getId();
+ if (metricId == null) {
+ Metric metric = metricFinder.findByKey(measure.getMetric().getKey());
+ if (metric == null) {
+ throw new IllegalStateException("Unknow metric with key: " + measure.getMetric().getKey());
+ }
+ metricId = metric.getId();
+ }
+ Characteristic characteristic = measure.getCharacteristic();
+ Integer characteristicId = characteristic != null ? characteristic.id() : null;
+ Integer personId = measure.getPersonId();
+ Integer ruleId = null;
+ if (measure instanceof RuleMeasure) {
+ Rule rule = ruleFinder.findByKey(((RuleMeasure) measure).ruleKey());
+ if (rule != null) {
+ ruleId = rule.getId();
+ }
+ }
+
+ Object[] pastMeasure = pastMeasuresByKey.get(new MeasureKey(metricId, characteristicId, personId, ruleId));
+ if (updateVariation(measure, pastMeasure, index)) {
+ context.saveMeasure(measure);
+ }
+ }
+ }
+
+ boolean updateVariation(Measure measure, Object[] pastMeasure, int index) {
+ if (pastMeasure != null && PastMeasuresLoader.hasValue(pastMeasure) && measure.getValue() != null) {
+ double variation = measure.getValue() - PastMeasuresLoader.getValue(pastMeasure);
+ measure.setVariation(index, variation);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+
+ static final class MeasureKey {
+ int metricId;
+ Integer characteristicId;
+ Integer personId;
+ Integer ruleId;
+
+ MeasureKey(Object[] pastFields) {
+ metricId = PastMeasuresLoader.getMetricId(pastFields);
+ characteristicId = PastMeasuresLoader.getCharacteristicId(pastFields);
+ personId = PastMeasuresLoader.getPersonId(pastFields);
+ ruleId = PastMeasuresLoader.getRuleId(pastFields);
+ }
+
+ MeasureKey(int metricId, @Nullable Integer characteristicId, @Nullable Integer personId, @Nullable Integer ruleId) {
+ this.metricId = metricId;
+ this.characteristicId = characteristicId;
+ this.personId = personId;
+ this.ruleId = ruleId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ MeasureKey that = (MeasureKey) o;
+ if (metricId != that.metricId) {
+ return false;
+ }
+ if (characteristicId != null ? !characteristicId.equals(that.characteristicId) : that.characteristicId != null) {
+ return false;
+ }
+ if (personId != null ? !personId.equals(that.personId) : that.personId != null) {
+ return false;
+ }
+ if (ruleId != null ? !ruleId.equals(that.ruleId) : that.ruleId != null) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = metricId;
+ result = 31 * result + (characteristicId != null ? characteristicId.hashCode() : 0);
+ result = 31 * result + (personId != null ? personId.hashCode() : 0);
+ result = 31 * result + (ruleId != null ? ruleId.hashCode() : 0);
+ return result;
+ }
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/compute/package-info.java b/sonar-batch/src/main/java/org/sonar/batch/compute/package-info.java
new file mode 100644
index 00000000000..7fd21f3ba7c
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/compute/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.batch.compute;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-batch/src/test/java/org/sonar/batch/compute/ApplyProjectRolesDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/compute/ApplyProjectRolesDecoratorTest.java
new file mode 100644
index 00000000000..cd48e6a315f
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/compute/ApplyProjectRolesDecoratorTest.java
@@ -0,0 +1,83 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.resources.Project;
+import org.sonar.api.security.ResourcePermissions;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class ApplyProjectRolesDecoratorTest {
+
+ private ResourcePermissions resourcePermissions;
+ private ApplyProjectRolesDecorator decorator;
+
+ @Before
+ public void init() {
+ resourcePermissions = mock(ResourcePermissions.class);
+ decorator = new ApplyProjectRolesDecorator(resourcePermissions);
+ }
+
+ @Test
+ public void alwaysExecute() {
+ assertThat(decorator.shouldExecuteOnProject(new Project("project"))).isTrue();
+ }
+
+ @Test
+ public void doNotGrantDefaultRolesWhenExistingPermissions() {
+ Project project = new Project("project");
+ project.setId(10);
+ when(resourcePermissions.hasRoles(project)).thenReturn(true);
+
+ decorator.decorate(project, null);
+
+ verify(resourcePermissions, never()).grantDefaultRoles(project);
+ }
+
+ @Test
+ public void doNotApplySecurityOnModules() {
+ Project project = new Project("project");
+ Project module = new Project("module").setParent(project);
+ module.setId(10);
+ when(resourcePermissions.hasRoles(project)).thenReturn(false);
+
+ decorator.decorate(module, null);
+
+ verify(resourcePermissions, never()).grantDefaultRoles(module);
+ }
+
+ @Test
+ public void grantDefaultRolesWhenNoPermissions() {
+ Project project = new Project("project");
+ project.setId(10);
+ when(resourcePermissions.hasRoles(project)).thenReturn(false);
+
+ decorator.decorate(project, null);
+
+ verify(resourcePermissions).grantDefaultRoles(project);
+ }
+
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/compute/BranchCoverageDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/compute/BranchCoverageDecoratorTest.java
new file mode 100644
index 00000000000..a7f9e098376
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/compute/BranchCoverageDecoratorTest.java
@@ -0,0 +1,72 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.resources.Scopes;
+
+import static org.mockito.Matchers.anyDouble;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class BranchCoverageDecoratorTest {
+ private final BranchCoverageDecorator decorator = new BranchCoverageDecorator();
+ private final Project resource = mock(Project.class);
+
+ @Before
+ public void setUp() {
+ when(resource.getScope()).thenReturn(Scopes.PROJECT);
+ when(resource.getQualifier()).thenReturn(Qualifiers.PROJECT);
+ }
+
+ @Test
+ public void shouldSaveBranchCoverage() {
+ DecoratorContext context = mockContext(20, 15);
+
+ decorator.decorate(resource, context);
+
+ verify(context).saveMeasure(CoreMetrics.BRANCH_COVERAGE, 25.0);
+ }
+
+ @Test
+ public void shouldNotSaveBranchCoverageIfMissingConditions() {
+ DecoratorContext context = mock(DecoratorContext.class);
+
+ decorator.decorate(resource, context);
+
+ verify(context, never()).saveMeasure(eq(CoreMetrics.BRANCH_COVERAGE), anyDouble());
+ }
+
+ private static DecoratorContext mockContext(int conditions, int uncoveredConditions) {
+ DecoratorContext context = mock(DecoratorContext.class);
+ when(context.getMeasure(CoreMetrics.CONDITIONS_TO_COVER)).thenReturn(new Measure(CoreMetrics.CONDITIONS_TO_COVER, (double) conditions));
+ when(context.getMeasure(CoreMetrics.UNCOVERED_CONDITIONS)).thenReturn(new Measure(CoreMetrics.UNCOVERED_CONDITIONS, (double) uncoveredConditions));
+ return context;
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/compute/CommentDensityDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/compute/CommentDensityDecoratorTest.java
new file mode 100644
index 00000000000..dadbb945e90
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/compute/CommentDensityDecoratorTest.java
@@ -0,0 +1,76 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.batch.compute;
+
+import org.junit.Test;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.test.IsMeasure;
+
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class CommentDensityDecoratorTest {
+
+ @Test
+ public void densityIsBalancedByNcloc() {
+ DecoratorContext context = mock(DecoratorContext.class);
+ when(context.getMeasure(CoreMetrics.NCLOC)).thenReturn(new Measure(CoreMetrics.NCLOC, 300.0));
+ when(context.getMeasure(CoreMetrics.COMMENT_LINES)).thenReturn(new Measure(CoreMetrics.COMMENT_LINES, 200.0));
+ CommentDensityDecorator decorator = new CommentDensityDecorator();
+ decorator.decorate(null, context);
+ // 200 / (200 + 300) = 40%
+ verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.COMMENT_LINES_DENSITY, 40.0)));
+ }
+
+ @Test
+ public void noDensityIfUnknownComments() {
+ DecoratorContext context = mock(DecoratorContext.class);
+ when(context.getMeasure(CoreMetrics.NCLOC)).thenReturn(new Measure(CoreMetrics.NCLOC, 300.0));
+ CommentDensityDecorator decorator = new CommentDensityDecorator();
+ decorator.decorate(null, context);
+ verify(context, never()).saveMeasure(argThat(new IsMeasure(CoreMetrics.COMMENT_LINES_DENSITY)));
+ }
+
+ @Test
+ public void noDensityIfZeroNcloc() {
+ DecoratorContext context = mock(DecoratorContext.class);
+ when(context.getMeasure(CoreMetrics.NCLOC)).thenReturn(new Measure(CoreMetrics.NCLOC, 0.0));
+ when(context.getMeasure(CoreMetrics.COMMENT_LINES)).thenReturn(new Measure(CoreMetrics.COMMENT_LINES, 0.0));
+ CommentDensityDecorator decorator = new CommentDensityDecorator();
+ decorator.decorate(null, context);
+ verify(context, never()).saveMeasure(argThat(new IsMeasure(CoreMetrics.COMMENT_LINES_DENSITY)));
+ }
+
+ @Test
+ public void zeroDensityWhenZeroComments() {
+ DecoratorContext context = mock(DecoratorContext.class);
+ when(context.getMeasure(CoreMetrics.NCLOC)).thenReturn(new Measure(CoreMetrics.NCLOC, 40.0));
+ when(context.getMeasure(CoreMetrics.COMMENT_LINES)).thenReturn(new Measure(CoreMetrics.COMMENT_LINES, 0.0));
+ CommentDensityDecorator decorator = new CommentDensityDecorator();
+ decorator.decorate(null, context);
+ verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.COMMENT_LINES_DENSITY, 0.0)));
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/compute/CountFalsePositivesDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/compute/CountFalsePositivesDecoratorTest.java
new file mode 100644
index 00000000000..37eea57e8e6
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/compute/CountFalsePositivesDecoratorTest.java
@@ -0,0 +1,81 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import java.util.Arrays;
+import org.junit.Test;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.component.ResourcePerspectives;
+import org.sonar.api.issue.Issuable;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.resources.File;
+import org.sonar.api.resources.Project;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.java.api.JavaClass;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+public class CountFalsePositivesDecoratorTest {
+
+ ResourcePerspectives perspectives = mock(ResourcePerspectives.class);
+ CountFalsePositivesDecorator decorator = new CountFalsePositivesDecorator(perspectives);
+
+ @Test
+ public void should_count_false_positives() {
+ DefaultIssue falsePositive = new DefaultIssue().setRuleKey(RuleKey.parse("squid:AvoidCycles"))
+ .setResolution(Issue.RESOLUTION_FALSE_POSITIVE).setStatus(Issue.STATUS_RESOLVED);
+ DefaultIssue fixed = new DefaultIssue().setRuleKey(RuleKey.parse("squid:AvoidCycles"))
+ .setResolution(Issue.RESOLUTION_FIXED).setStatus(Issue.STATUS_RESOLVED);
+
+ File file = File.create("foo.c");
+ Issuable issuable = mock(Issuable.class);
+ when(perspectives.as(Issuable.class, file)).thenReturn(issuable);
+ when(issuable.resolvedIssues()).thenReturn(Arrays.<Issue>asList(falsePositive, fixed));
+
+ DecoratorContext context = mock(DecoratorContext.class);
+ decorator.decorate(file, context);
+
+ verify(context).saveMeasure(CoreMetrics.FALSE_POSITIVE_ISSUES, 1.0);
+ }
+
+ @Test
+ public void should_declare_metadata() {
+ assertThat(decorator.shouldExecuteOnProject(new Project("foo"))).isTrue();
+ assertThat(decorator.generatesFalsePositiveMeasure()).isEqualTo(CoreMetrics.FALSE_POSITIVE_ISSUES);
+ assertThat(decorator.toString()).isEqualTo("CountFalsePositivesDecorator");
+ }
+
+ @Test
+ public void should_ignore_classes_and_methods() {
+ JavaClass javaClass = JavaClass.create("Foo.java");
+ when(perspectives.as(Issuable.class, javaClass)).thenReturn(null);
+
+ DecoratorContext context = mock(DecoratorContext.class);
+ decorator.decorate(javaClass, context);
+
+ verifyZeroInteractions(context);
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/compute/CountUnresolvedIssuesDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/compute/CountUnresolvedIssuesDecoratorTest.java
new file mode 100644
index 00000000000..8473e0a3da7
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/compute/CountUnresolvedIssuesDecoratorTest.java
@@ -0,0 +1,372 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.batch.compute;
+
+import com.google.common.collect.Lists;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import org.apache.commons.lang.ObjectUtils;
+import org.apache.commons.lang.time.DateUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentMatcher;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.component.ResourcePerspectives;
+import org.sonar.api.issue.Issuable;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.MeasuresFilter;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.measures.RuleMeasure;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.resources.Scopes;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RulePriority;
+import org.sonar.api.test.IsRuleMeasure;
+import org.sonar.batch.components.Period;
+import org.sonar.batch.components.TimeMachineConfiguration;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyDouble;
+import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+public class CountUnresolvedIssuesDecoratorTest {
+
+ CountUnresolvedIssuesDecorator decorator;
+ TimeMachineConfiguration timeMachineConfiguration;
+ Issuable issuable;
+ DecoratorContext context;
+ Resource resource;
+ Project project;
+ Rule ruleA1;
+ Rule ruleA2;
+ Rule ruleB1;
+ Date rightNow;
+ Date tenDaysAgo;
+ Date afterTenDaysAgo;
+ Date fiveDaysAgo;
+ Date afterFiveDaysAgo;
+ Date sameSecond;
+
+ @Before
+ public void before() {
+ ruleA1 = Rule.create().setRepositoryKey("ruleA1").setKey("ruleA1").setName("nameA1");
+ ruleA2 = Rule.create().setRepositoryKey("ruleA2").setKey("ruleA2").setName("nameA2");
+ ruleB1 = Rule.create().setRepositoryKey("ruleB1").setKey("ruleB1").setName("nameB1");
+
+ rightNow = new Date();
+ tenDaysAgo = DateUtils.addDays(rightNow, -10);
+ afterTenDaysAgo = DateUtils.addDays(tenDaysAgo, 1);
+ fiveDaysAgo = DateUtils.addDays(rightNow, -5);
+ afterFiveDaysAgo = DateUtils.addDays(fiveDaysAgo, 1);
+ sameSecond = DateUtils.truncate(rightNow, Calendar.SECOND);
+
+ timeMachineConfiguration = mock(TimeMachineConfiguration.class);
+ when(timeMachineConfiguration.periods()).thenReturn(newArrayList(new Period(1, afterFiveDaysAgo), new Period(2, afterTenDaysAgo)));
+
+ project = mock(Project.class);
+ resource = mock(Resource.class);
+ context = mock(DecoratorContext.class);
+ when(context.getResource()).thenReturn(resource);
+ when(context.getProject()).thenReturn(project);
+ when(context.getMeasure(CoreMetrics.NEW_VIOLATIONS)).thenReturn(null);
+
+ issuable = mock(Issuable.class);
+ ResourcePerspectives perspectives = mock(ResourcePerspectives.class);
+ when(perspectives.as(Issuable.class, resource)).thenReturn(issuable);
+ decorator = new CountUnresolvedIssuesDecorator(perspectives, timeMachineConfiguration);
+ }
+
+ @Test
+ public void should_be_depended_upon_metric() {
+ assertThat(decorator.generatesIssuesMetrics()).hasSize(15);
+ }
+
+ @Test
+ public void should_count_issues() {
+ when(resource.getScope()).thenReturn(Scopes.PROJECT);
+ when(issuable.issues()).thenReturn(createIssues());
+ when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Collections.<Measure>emptyList());
+
+ decorator.decorate(resource, context);
+
+ verify(context).saveMeasure(CoreMetrics.VIOLATIONS, 4.0);
+ }
+
+ @Test
+ public void should_do_nothing_when_issuable_is_null() {
+ ResourcePerspectives perspectives = mock(ResourcePerspectives.class);
+ when(perspectives.as(Issuable.class, resource)).thenReturn(null);
+ CountUnresolvedIssuesDecorator decorator = new CountUnresolvedIssuesDecorator(perspectives, timeMachineConfiguration);
+
+ decorator.decorate(resource, context);
+
+ verifyZeroInteractions(context);
+ }
+
+ /**
+ * See http://jira.codehaus.org/browse/SONAR-1729
+ */
+ @Test
+ public void should_not_count_issues_if_measure_already_exists() {
+ when(resource.getScope()).thenReturn(Scopes.PROJECT);
+ when(issuable.issues()).thenReturn(createIssues());
+ when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Collections.<Measure>emptyList());
+ when(context.getMeasure(CoreMetrics.VIOLATIONS)).thenReturn(new Measure(CoreMetrics.VIOLATIONS, 3000.0));
+ when(context.getMeasure(CoreMetrics.MAJOR_VIOLATIONS)).thenReturn(new Measure(CoreMetrics.MAJOR_VIOLATIONS, 500.0));
+
+ decorator.decorate(resource, context);
+
+ verify(context, never()).saveMeasure(eq(CoreMetrics.VIOLATIONS), anyDouble());// not changed
+ verify(context, never()).saveMeasure(eq(CoreMetrics.MAJOR_VIOLATIONS), anyDouble());// not changed
+ verify(context, times(1)).saveMeasure(eq(CoreMetrics.CRITICAL_VIOLATIONS), anyDouble());// did not exist
+ }
+
+ @Test
+ public void should_save_zero_on_projects() {
+ when(resource.getScope()).thenReturn(Scopes.PROJECT);
+ when(issuable.issues()).thenReturn(Lists.<Issue>newArrayList());
+ when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Collections.<Measure>emptyList());
+
+ decorator.decorate(resource, context);
+
+ verify(context).saveMeasure(CoreMetrics.VIOLATIONS, 0.0);
+ }
+
+ @Test
+ public void should_save_zero_on_directories() {
+ when(resource.getScope()).thenReturn(Scopes.DIRECTORY);
+ when(issuable.issues()).thenReturn(Lists.<Issue>newArrayList());
+ when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Collections.<Measure>emptyList());
+
+ decorator.decorate(resource, context);
+
+ verify(context).saveMeasure(CoreMetrics.VIOLATIONS, 0.0);
+ }
+
+ @Test
+ public void should_count_issues_by_severity() {
+ when(resource.getScope()).thenReturn(Scopes.PROJECT);
+ when(issuable.issues()).thenReturn(createIssues());
+ when(context.getChildrenMeasures(any(MeasuresFilter.class))).thenReturn(Collections.<Measure>emptyList());
+
+ decorator.decorate(resource, context);
+
+ verify(context).saveMeasure(CoreMetrics.BLOCKER_VIOLATIONS, 0.0);
+ verify(context).saveMeasure(CoreMetrics.CRITICAL_VIOLATIONS, 2.0);
+ verify(context).saveMeasure(CoreMetrics.MAJOR_VIOLATIONS, 1.0);
+ verify(context).saveMeasure(CoreMetrics.MINOR_VIOLATIONS, 1.0);
+ verify(context).saveMeasure(CoreMetrics.INFO_VIOLATIONS, 0.0);
+ }
+
+ @Test
+ public void should_count_issues_per_rule() {
+ List<Issue> issues = newArrayList();
+ issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(RulePriority.CRITICAL.name()));
+ issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(RulePriority.CRITICAL.name()));
+ issues.add(new DefaultIssue().setRuleKey(ruleA2.ruleKey()).setSeverity(RulePriority.MAJOR.name()));
+ when(issuable.issues()).thenReturn(issues);
+
+ decorator.decorate(resource, context);
+
+ verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.CRITICAL_VIOLATIONS, ruleA1, 2.0)));
+ verify(context, never()).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.MAJOR_VIOLATIONS, ruleA1, 0.0)));
+ verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.MAJOR_VIOLATIONS, ruleA2, 1.0)));
+ }
+
+ @Test
+ public void same_rule_should_have_different_severities() {
+ List<Issue> issues = newArrayList();
+ issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(RulePriority.CRITICAL.name()));
+ issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(RulePriority.CRITICAL.name()));
+ issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(RulePriority.MINOR.name()));
+ when(issuable.issues()).thenReturn(issues);
+
+ decorator.decorate(resource, context);
+
+ verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.CRITICAL_VIOLATIONS, ruleA1, 2.0)));
+ verify(context).saveMeasure(argThat(new IsRuleMeasure(CoreMetrics.MINOR_VIOLATIONS, ruleA1, 1.0)));
+ }
+
+ @Test
+ public void should_count_issues_after_date() {
+ List<Issue> issues = createIssuesForNewMetrics();
+
+ assertThat(decorator.countIssuesAfterDate(null, fiveDaysAgo)).isEqualTo(0);
+ assertThat(decorator.countIssuesAfterDate(issues, fiveDaysAgo)).isEqualTo(1); // 1 rightNow
+ assertThat(decorator.countIssuesAfterDate(issues, tenDaysAgo)).isEqualTo(3); // 1 rightNow + 2 fiveDaysAgo
+ assertThat(decorator.countIssuesAfterDate(issues, sameSecond)).isEqualTo(0); // 0
+ }
+
+ @Test
+ public void should_clear_cache_after_execution() {
+ Issue issue1 = new DefaultIssue().setRuleKey(RuleKey.of(ruleA1.getRepositoryKey(), ruleA1.getKey())).setSeverity(RulePriority.CRITICAL.name()).setCreationDate(rightNow);
+ Issue issue2 = new DefaultIssue().setRuleKey(RuleKey.of(ruleA2.getRepositoryKey(), ruleA2.getKey())).setSeverity(RulePriority.CRITICAL.name()).setCreationDate(rightNow);
+ when(issuable.issues()).thenReturn(newArrayList(issue1)).thenReturn(newArrayList(issue2));
+
+ decorator.decorate(resource, context);
+ decorator.decorate(resource, context);
+
+ verify(context, times(2)).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_CRITICAL_VIOLATIONS, 1.0, 1.0)));
+ verify(context, never()).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_CRITICAL_VIOLATIONS, 2.0, 2.0)));
+ }
+
+ @Test
+ public void should_save_severity_new_issues() {
+ when(issuable.issues()).thenReturn(createIssuesForNewMetrics());
+
+ decorator.decorate(resource, context);
+
+ // remember : period1 is 5daysAgo, period2 is 10daysAgo
+ verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_BLOCKER_VIOLATIONS, 0.0, 0.0)));
+ verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_CRITICAL_VIOLATIONS, 1.0, 1.0)));
+ verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_MAJOR_VIOLATIONS, 0.0, 1.0)));
+ verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_MINOR_VIOLATIONS, 0.0, 1.0)));
+ verify(context).saveMeasure(argThat(new IsVariationMeasure(CoreMetrics.NEW_INFO_VIOLATIONS, 0.0, 0.0)));
+ }
+
+ @Test
+ public void should_save_rule_new_issues() {
+ when(issuable.issues()).thenReturn(createIssuesForNewMetrics());
+
+ decorator.decorate(resource, context);
+
+ // remember : period1 is 5daysAgo, period2 is 10daysAgo
+ verify(context).saveMeasure(argThat(new IsVariationRuleMeasure(CoreMetrics.NEW_CRITICAL_VIOLATIONS, ruleA1, 1.0, 1.0)));
+ verify(context).saveMeasure(argThat(new IsVariationRuleMeasure(CoreMetrics.NEW_MAJOR_VIOLATIONS, ruleA2, 0.0, 1.0)));
+ verify(context).saveMeasure(argThat(new IsVariationRuleMeasure(CoreMetrics.NEW_MINOR_VIOLATIONS, ruleB1, 0.0, 1.0)));
+ }
+
+ @Test
+ public void should_not_save_new_issues_if_measure_already_computed() {
+ when(context.getMeasure(CoreMetrics.NEW_VIOLATIONS)).thenReturn(new Measure());
+ when(issuable.issues()).thenReturn(createIssuesForNewMetrics());
+
+ decorator.decorate(resource, context);
+
+ verify(context, never()).saveMeasure(argThat(new IsMetricMeasure(CoreMetrics.NEW_BLOCKER_VIOLATIONS)));
+ verify(context, never()).saveMeasure(argThat(new IsMetricMeasure(CoreMetrics.NEW_CRITICAL_VIOLATIONS)));
+ verify(context, never()).saveMeasure(argThat(new IsMetricMeasure(CoreMetrics.NEW_MAJOR_VIOLATIONS)));
+ verify(context, never()).saveMeasure(argThat(new IsMetricMeasure(CoreMetrics.NEW_MINOR_VIOLATIONS)));
+ verify(context, never()).saveMeasure(argThat(new IsMetricMeasure(CoreMetrics.NEW_INFO_VIOLATIONS)));
+ verify(context, never()).saveMeasure(argThat(new IsMetricMeasure(CoreMetrics.NEW_CRITICAL_VIOLATIONS)));
+ }
+
+ List<Issue> createIssues() {
+ List<Issue> issues = newArrayList();
+ issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(Severity.CRITICAL).setStatus(Issue.STATUS_OPEN));
+ issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(Severity.CRITICAL).setStatus(Issue.STATUS_REOPENED));
+ issues.add(new DefaultIssue().setRuleKey(ruleA2.ruleKey()).setSeverity(Severity.MAJOR).setStatus(Issue.STATUS_REOPENED));
+ issues.add(new DefaultIssue().setRuleKey(ruleB1.ruleKey()).setSeverity(Severity.MINOR).setStatus(Issue.STATUS_OPEN));
+ return issues;
+ }
+
+ List<Issue> createIssuesForNewMetrics() {
+ List<Issue> issues = newArrayList();
+ issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(RulePriority.CRITICAL.name()).setCreationDate(rightNow).setStatus(Issue.STATUS_OPEN));
+ issues.add(new DefaultIssue().setRuleKey(ruleA1.ruleKey()).setSeverity(RulePriority.CRITICAL.name()).setCreationDate(tenDaysAgo).setStatus(Issue.STATUS_OPEN));
+ issues.add(new DefaultIssue().setRuleKey(ruleA2.ruleKey()).setSeverity(RulePriority.MAJOR.name()).setCreationDate(fiveDaysAgo).setStatus(Issue.STATUS_REOPENED));
+ issues.add(new DefaultIssue().setRuleKey(ruleA2.ruleKey()).setSeverity(RulePriority.MAJOR.name()).setCreationDate(tenDaysAgo).setStatus(Issue.STATUS_REOPENED));
+ issues.add(new DefaultIssue().setRuleKey(ruleB1.ruleKey()).setSeverity(RulePriority.MINOR.name()).setCreationDate(fiveDaysAgo).setStatus(Issue.STATUS_OPEN));
+ issues.add(new DefaultIssue().setRuleKey(ruleB1.ruleKey()).setSeverity(RulePriority.MINOR.name()).setCreationDate(tenDaysAgo).setStatus(Issue.STATUS_OPEN));
+ return issues;
+ }
+
+ class IsVariationRuleMeasure extends ArgumentMatcher<Measure> {
+ Metric metric = null;
+ Rule rule = null;
+ Double var1 = null;
+ Double var2 = null;
+
+ public IsVariationRuleMeasure(Metric metric, Rule rule, Double var1, Double var2) {
+ this.metric = metric;
+ this.rule = rule;
+ this.var1 = var1;
+ this.var2 = var2;
+ }
+
+ public boolean matches(Object o) {
+ if (!(o instanceof RuleMeasure)) {
+ return false;
+ }
+ RuleMeasure m = (RuleMeasure) o;
+ return ObjectUtils.equals(metric, m.getMetric()) &&
+ ObjectUtils.equals(rule.ruleKey(), m.ruleKey()) &&
+ ObjectUtils.equals(var1, m.getVariation1()) &&
+ ObjectUtils.equals(var2, m.getVariation2());
+ }
+ }
+
+ class IsVariationMeasure extends ArgumentMatcher<Measure> {
+ Metric metric = null;
+ Double var1 = null;
+ Double var2 = null;
+
+ public IsVariationMeasure(Metric metric, Double var1, Double var2) {
+ this.metric = metric;
+ this.var1 = var1;
+ this.var2 = var2;
+ }
+
+ public boolean matches(Object o) {
+ if (!(o instanceof Measure)) {
+ return false;
+ }
+ Measure m = (Measure) o;
+ return ObjectUtils.equals(metric, m.getMetric()) &&
+ ObjectUtils.equals(var1, m.getVariation1()) &&
+ ObjectUtils.equals(var2, m.getVariation2()) &&
+ !(m instanceof RuleMeasure);
+ }
+ }
+
+ class IsMetricMeasure extends ArgumentMatcher<Measure> {
+ Metric metric = null;
+
+ public IsMetricMeasure(Metric metric) {
+ this.metric = metric;
+ }
+
+ public boolean matches(Object o) {
+ if (!(o instanceof Measure)) {
+ return false;
+ }
+ Measure m = (Measure) o;
+ return ObjectUtils.equals(metric, m.getMetric());
+ }
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/compute/CoverageDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/compute/CoverageDecoratorTest.java
new file mode 100644
index 00000000000..5994f3e72e6
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/compute/CoverageDecoratorTest.java
@@ -0,0 +1,143 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import java.util.Collection;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Scopes;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.anyDouble;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class CoverageDecoratorTest {
+ private CoverageDecorator decorator;
+ private final Project project = mock(Project.class);
+
+ @Before
+ public void before() {
+ when(project.getScope()).thenReturn(Scopes.PROJECT);
+ decorator = new CoverageDecorator();
+ }
+
+ @Test
+ public void should_use_metrics() {
+ Collection<Metric> metrics = decorator.usedMetrics();
+
+ assertThat(metrics).containsOnly(CoreMetrics.LINES_TO_COVER, CoreMetrics.UNCOVERED_LINES, CoreMetrics.NEW_LINES_TO_COVER,
+ CoreMetrics.NEW_UNCOVERED_LINES, CoreMetrics.CONDITIONS_TO_COVER, CoreMetrics.UNCOVERED_CONDITIONS,
+ CoreMetrics.NEW_CONDITIONS_TO_COVER, CoreMetrics.NEW_UNCOVERED_CONDITIONS);
+ }
+
+ @Test
+ public void coverage() {
+ DecoratorContext context = mockContext(50, 40, 10, 8);
+
+ decorator.decorate(project, context);
+
+ // (50-40 covered lines + 10-8 covered conditions) / (50 lines + 10 conditions)
+ verify(context).saveMeasure(CoreMetrics.COVERAGE, 20.0);
+ }
+
+ @Test
+ public void coverageCanBe0() {
+ DecoratorContext context = mockContext(50, 50, 5, 5);
+
+ decorator.decorate(project, context);
+
+ verify(context).saveMeasure(CoreMetrics.COVERAGE, 0.0);
+ }
+
+ @Test
+ public void coverageCanBe100() {
+ DecoratorContext context = mockContext(50, 0, 5, 0);
+
+ decorator.decorate(project, context);
+
+ verify(context).saveMeasure(CoreMetrics.COVERAGE, 100.0);
+ }
+
+ @Test
+ public void noCoverageIfNoElements() {
+ DecoratorContext context = mock(DecoratorContext.class);
+
+ decorator.decorate(project, context);
+
+ verify(context, never()).saveMeasure(eq(CoreMetrics.COVERAGE), anyDouble());
+ }
+
+ @Test
+ public void should_count_elements_for_new_code() {
+ Measure newLines = measureWithVariation(1, 100.0);
+ Measure newConditions = measureWithVariation(1, 1.0);
+ DecoratorContext context = mockNewContext(newLines, null, null, newConditions);
+
+ long count = decorator.countElementsForNewCode(context, 1);
+
+ assertThat(count).isEqualTo(101).isEqualTo(100 + 1);
+ }
+
+ @Test
+ public void should_count_covered_elements_for_new_code() {
+ Measure newLines = measureWithVariation(1, 100.0);
+ Measure newUncoveredConditions = measureWithVariation(1, 10.0);
+ Measure newUncoveredLines = measureWithVariation(1, 5.0);
+ Measure newConditions = measureWithVariation(1, 1.0);
+ DecoratorContext context = mockNewContext(newLines, newUncoveredConditions, newUncoveredLines, newConditions);
+
+ long count = decorator.countCoveredElementsForNewCode(context, 1);
+
+ assertThat(count).isEqualTo(86).isEqualTo(100 + 1 - 10 - 5);
+ }
+
+ private static DecoratorContext mockContext(int lines, int uncoveredLines, int conditions, int uncoveredConditions) {
+ DecoratorContext context = mock(DecoratorContext.class);
+ when(context.getMeasure(CoreMetrics.LINES_TO_COVER)).thenReturn(new Measure(CoreMetrics.LINES_TO_COVER, (double) lines));
+ when(context.getMeasure(CoreMetrics.UNCOVERED_LINES)).thenReturn(new Measure(CoreMetrics.UNCOVERED_LINES, (double) uncoveredLines));
+ when(context.getMeasure(CoreMetrics.CONDITIONS_TO_COVER)).thenReturn(new Measure(CoreMetrics.CONDITIONS_TO_COVER, (double) conditions));
+ when(context.getMeasure(CoreMetrics.UNCOVERED_CONDITIONS)).thenReturn(new Measure(CoreMetrics.UNCOVERED_CONDITIONS, (double) uncoveredConditions));
+ return context;
+ }
+
+ private static DecoratorContext mockNewContext(Measure newLines, Measure newUncoveredConditions, Measure newUncoveredLines, Measure newConditions) {
+ DecoratorContext context = mock(DecoratorContext.class);
+ when(context.getMeasure(CoreMetrics.NEW_LINES_TO_COVER)).thenReturn(newLines);
+ when(context.getMeasure(CoreMetrics.NEW_UNCOVERED_LINES)).thenReturn(newUncoveredLines);
+ when(context.getMeasure(CoreMetrics.NEW_UNCOVERED_CONDITIONS)).thenReturn(newUncoveredConditions);
+ when(context.getMeasure(CoreMetrics.NEW_CONDITIONS_TO_COVER)).thenReturn(newConditions);
+ return context;
+ }
+
+ private static Measure measureWithVariation(int period, double variation) {
+ Measure measure = mock(Measure.class);
+ when(measure.getVariation(period)).thenReturn(variation);
+ return measure;
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/compute/DirectoriesDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/compute/DirectoriesDecoratorTest.java
new file mode 100644
index 00000000000..5e854bc9913
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/compute/DirectoriesDecoratorTest.java
@@ -0,0 +1,90 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import java.util.Arrays;
+import java.util.Collections;
+import org.junit.Test;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.resources.Directory;
+import org.sonar.api.resources.File;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+
+import static org.mockito.Matchers.anyDouble;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class DirectoriesDecoratorTest {
+
+ @Test
+ public void doNotInsertZeroOnFiles() {
+ DirectoriesDecorator decorator = new DirectoriesDecorator();
+ Resource file = File.create("foo.php");
+ DecoratorContext context = mock(DecoratorContext.class);
+
+ decorator.decorate(file, context);
+
+ verify(context, never()).saveMeasure(eq(CoreMetrics.DIRECTORIES), anyDouble());
+ }
+
+ @Test
+ public void directoryCountsForOne() {
+ DirectoriesDecorator decorator = new DirectoriesDecorator();
+ Resource directory = Directory.create("org/foo");
+ DecoratorContext context = mock(DecoratorContext.class);
+ decorator.decorate(directory, context);
+ verify(context).saveMeasure(CoreMetrics.DIRECTORIES, 1.0);
+ }
+
+ @Test
+ public void countProjectDirectories() {
+ DirectoriesDecorator decorator = new DirectoriesDecorator();
+ Resource project = new Project("project");
+ DecoratorContext context = mock(DecoratorContext.class);
+
+ when(context.getChildrenMeasures(CoreMetrics.DIRECTORIES)).thenReturn(Arrays.<Measure>asList(
+ new Measure(CoreMetrics.DIRECTORIES, 1.0),
+ new Measure(CoreMetrics.DIRECTORIES, 1.0),
+ new Measure(CoreMetrics.DIRECTORIES, 1.0)
+ ));
+ decorator.decorate(project, context);
+ verify(context).saveMeasure(CoreMetrics.DIRECTORIES, 3.0);
+ }
+
+ @Test
+ public void noProjectValueWhenOnlyPackages() {
+ DirectoriesDecorator decorator = new DirectoriesDecorator();
+ Resource project = new Project("project");
+ DecoratorContext context = mock(DecoratorContext.class);
+ when(context.getChildrenMeasures(CoreMetrics.DIRECTORIES)).thenReturn(Collections.<Measure>emptyList());
+ when(context.getChildrenMeasures(CoreMetrics.PACKAGES)).thenReturn(Arrays.<Measure>asList(
+ new Measure(CoreMetrics.PACKAGES, 1.0),
+ new Measure(CoreMetrics.PACKAGES, 1.0)
+ ));
+ decorator.decorate(project, context);
+ verify(context, never()).saveMeasure(eq(CoreMetrics.DIRECTORIES), anyDouble());
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/compute/FilesDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/compute/FilesDecoratorTest.java
new file mode 100644
index 00000000000..f750aebcdde
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/compute/FilesDecoratorTest.java
@@ -0,0 +1,111 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import java.util.Arrays;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.resources.Resource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.anyDouble;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class FilesDecoratorTest {
+
+ private FilesDecorator decorator;
+
+ @Mock
+ private DecoratorContext context;
+
+ @Mock
+ private Resource resource;
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ decorator = new FilesDecorator();
+ }
+
+ @Test
+ public void generatesMetrics() {
+ assertThat(decorator.generateDirectoriesMetric()).isEqualTo(CoreMetrics.FILES);
+ }
+
+ @Test
+ public void shouldExecute() {
+ assertThat(decorator.shouldExecuteOnProject(mock(Project.class))).isEqualTo(true);
+ }
+
+ @Test
+ public void shouldNotSaveIfMeasureAlreadyExists() {
+ when(context.getMeasure(CoreMetrics.FILES)).thenReturn(new Measure(CoreMetrics.FILES, 1.0));
+
+ decorator.decorate(resource, context);
+
+ verify(context, never()).saveMeasure(eq(CoreMetrics.FILES), anyDouble());
+ }
+
+ @Test
+ public void shouldSaveOneForFile() {
+ when(resource.getQualifier()).thenReturn(Qualifiers.FILE);
+
+ decorator.decorate(resource, context);
+
+ verify(context, times(1)).saveMeasure(eq(CoreMetrics.FILES), eq(1d));
+ }
+
+ @Test
+ public void shouldSaveOneForClass() {
+ when(resource.getQualifier()).thenReturn(Qualifiers.CLASS);
+
+ decorator.decorate(resource, context);
+
+ verify(context, times(1)).saveMeasure(eq(CoreMetrics.FILES), eq(1d));
+ }
+
+ @Test
+ public void shouldSumChildren() {
+ when(context.getChildrenMeasures(CoreMetrics.FILES)).thenReturn(Arrays.asList(new Measure(CoreMetrics.FILES, 2.0), new Measure(CoreMetrics.FILES, 3.0)));
+
+ decorator.decorate(resource, context);
+
+ verify(context).saveMeasure(eq(CoreMetrics.FILES), eq(5.0));
+ }
+
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/compute/ItBranchCoverageDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/compute/ItBranchCoverageDecoratorTest.java
new file mode 100644
index 00000000000..0e5b70a37e3
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/compute/ItBranchCoverageDecoratorTest.java
@@ -0,0 +1,72 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.resources.Scopes;
+
+import static org.mockito.Matchers.anyDouble;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class ItBranchCoverageDecoratorTest {
+ private final ItBranchCoverageDecorator decorator = new ItBranchCoverageDecorator();
+ private final Project resource = mock(Project.class);
+
+ @Before
+ public void setUp() {
+ when(resource.getScope()).thenReturn(Scopes.PROJECT);
+ when(resource.getQualifier()).thenReturn(Qualifiers.PROJECT);
+ }
+
+ @Test
+ public void shouldSaveBranchCoverage() {
+ DecoratorContext context = mockContext(20, 15);
+
+ decorator.decorate(resource, context);
+
+ verify(context).saveMeasure(CoreMetrics.IT_BRANCH_COVERAGE, 25.0);
+ }
+
+ @Test
+ public void shouldNotSaveBranchCoverageIfMissingConditions() {
+ DecoratorContext context = mock(DecoratorContext.class);
+
+ decorator.decorate(resource, context);
+
+ verify(context, never()).saveMeasure(eq(CoreMetrics.IT_BRANCH_COVERAGE), anyDouble());
+ }
+
+ private static DecoratorContext mockContext(int conditions, int uncoveredConditions) {
+ DecoratorContext context = mock(DecoratorContext.class);
+ when(context.getMeasure(CoreMetrics.IT_CONDITIONS_TO_COVER)).thenReturn(new Measure(CoreMetrics.IT_CONDITIONS_TO_COVER, (double) conditions));
+ when(context.getMeasure(CoreMetrics.IT_UNCOVERED_CONDITIONS)).thenReturn(new Measure(CoreMetrics.IT_UNCOVERED_CONDITIONS, (double) uncoveredConditions));
+ return context;
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/compute/ItCoverageDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/compute/ItCoverageDecoratorTest.java
new file mode 100644
index 00000000000..839577fb1aa
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/compute/ItCoverageDecoratorTest.java
@@ -0,0 +1,142 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import java.util.Collection;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Scopes;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.anyDouble;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class ItCoverageDecoratorTest {
+ private final ItCoverageDecorator decorator = new ItCoverageDecorator();
+ private final Project project = mock(Project.class);
+
+ @Before
+ public void before() {
+ when(project.getScope()).thenReturn(Scopes.PROJECT);
+ }
+
+ @Test
+ public void should_use_metrics() {
+ Collection<Metric> metrics = decorator.usedMetrics();
+
+ assertThat(metrics).containsOnly(CoreMetrics.IT_LINES_TO_COVER, CoreMetrics.IT_UNCOVERED_LINES, CoreMetrics.NEW_IT_LINES_TO_COVER,
+ CoreMetrics.NEW_IT_UNCOVERED_LINES, CoreMetrics.IT_CONDITIONS_TO_COVER, CoreMetrics.IT_UNCOVERED_CONDITIONS,
+ CoreMetrics.NEW_IT_CONDITIONS_TO_COVER, CoreMetrics.NEW_IT_UNCOVERED_CONDITIONS);
+ }
+
+ @Test
+ public void coverage() {
+ DecoratorContext context = mockContext(50, 40, 10, 8);
+
+ decorator.decorate(project, context);
+
+ // (50-40 covered lines + 10-8 covered conditions) / (50 lines + 10 conditions)
+ verify(context).saveMeasure(CoreMetrics.IT_COVERAGE, 20.0);
+ }
+
+ @Test
+ public void coverageCanBe0() {
+ DecoratorContext context = mockContext(50, 50, 5, 5);
+
+ decorator.decorate(project, context);
+
+ verify(context).saveMeasure(CoreMetrics.IT_COVERAGE, 0.0);
+ }
+
+ @Test
+ public void coverageCanBe100() {
+ DecoratorContext context = mockContext(50, 0, 5, 0);
+
+ decorator.decorate(project, context);
+
+ verify(context).saveMeasure(CoreMetrics.IT_COVERAGE, 100.0);
+ }
+
+ @Test
+ public void noCoverageIfNoElements() {
+ DecoratorContext context = mock(DecoratorContext.class);
+
+ decorator.decorate(project, context);
+
+ verify(context, never()).saveMeasure(eq(CoreMetrics.IT_COVERAGE), anyDouble());
+ }
+
+ @Test
+ public void should_count_elements_for_new_code() {
+ Measure newLines = measureWithVariation(1, 100.0);
+ Measure newConditions = measureWithVariation(1, 1.0);
+ DecoratorContext context = mockNewContext(newLines, null, null, newConditions);
+
+ long count = decorator.countElementsForNewCode(context, 1);
+
+ assertThat(count).isEqualTo(101).isEqualTo(100 + 1);
+ }
+
+ @Test
+ public void should_count_covered_elements_for_new_code() {
+ Measure newLines = measureWithVariation(1, 100.0);
+ Measure newUncoveredConditions = measureWithVariation(1, 10.0);
+ Measure newUncoveredLines = measureWithVariation(1, 5.0);
+ Measure newConditions = measureWithVariation(1, 1.0);
+ DecoratorContext context = mockNewContext(newLines, newUncoveredConditions, newUncoveredLines, newConditions);
+
+ long count = decorator.countCoveredElementsForNewCode(context, 1);
+
+ assertThat(count).isEqualTo(86).isEqualTo(100 + 1 - 10 - 5);
+ }
+
+ private static DecoratorContext mockContext(int lines, int uncoveredLines, int conditions, int uncoveredConditions) {
+ DecoratorContext context = mock(DecoratorContext.class);
+ when(context.getMeasure(CoreMetrics.IT_LINES_TO_COVER)).thenReturn(new Measure(CoreMetrics.IT_LINES_TO_COVER, (double) lines));
+ when(context.getMeasure(CoreMetrics.IT_UNCOVERED_LINES)).thenReturn(new Measure(CoreMetrics.IT_UNCOVERED_LINES, (double) uncoveredLines));
+ when(context.getMeasure(CoreMetrics.IT_CONDITIONS_TO_COVER)).thenReturn(new Measure(CoreMetrics.IT_CONDITIONS_TO_COVER, (double) conditions));
+ when(context.getMeasure(CoreMetrics.IT_UNCOVERED_CONDITIONS)).thenReturn(new Measure(CoreMetrics.IT_UNCOVERED_CONDITIONS, (double) uncoveredConditions));
+ return context;
+ }
+
+ private static DecoratorContext mockNewContext(Measure newLines, Measure newUncoveredConditions, Measure newUncoveredLines, Measure newConditions) {
+ DecoratorContext context = mock(DecoratorContext.class);
+ when(context.getMeasure(CoreMetrics.NEW_IT_LINES_TO_COVER)).thenReturn(newLines);
+ when(context.getMeasure(CoreMetrics.NEW_IT_UNCOVERED_LINES)).thenReturn(newUncoveredLines);
+ when(context.getMeasure(CoreMetrics.NEW_IT_UNCOVERED_CONDITIONS)).thenReturn(newUncoveredConditions);
+ when(context.getMeasure(CoreMetrics.NEW_IT_CONDITIONS_TO_COVER)).thenReturn(newConditions);
+ return context;
+ }
+
+ private static Measure measureWithVariation(int period, double variation) {
+ Measure measure = mock(Measure.class);
+ when(measure.getVariation(period)).thenReturn(variation);
+ return measure;
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/compute/ItLineCoverageDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/compute/ItLineCoverageDecoratorTest.java
new file mode 100644
index 00000000000..95e7177b840
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/compute/ItLineCoverageDecoratorTest.java
@@ -0,0 +1,99 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Scopes;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.anyDouble;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class ItLineCoverageDecoratorTest {
+ private final ItLineCoverageDecorator decorator = new ItLineCoverageDecorator();
+ private final Project project = mock(Project.class);
+
+ @Before
+ public void before() {
+ when(project.getScope()).thenReturn(Scopes.PROJECT);
+ }
+
+ @Test
+ public void should_depend_on_coverage_metrics() {
+ List<Metric> metrics = decorator.dependsUponMetrics();
+
+ assertThat(metrics).containsOnly(CoreMetrics.IT_UNCOVERED_LINES, CoreMetrics.IT_LINES_TO_COVER, CoreMetrics.NEW_IT_UNCOVERED_LINES, CoreMetrics.NEW_IT_LINES_TO_COVER);
+ }
+
+ @Test
+ public void lineCoverage() {
+ DecoratorContext context = mockContext(50, 10);
+
+ decorator.decorate(project, context);
+
+ // 50-10 covered lines / 50 lines
+ verify(context).saveMeasure(CoreMetrics.IT_LINE_COVERAGE, 80.0);
+ }
+
+ @Test
+ public void zeroCoveredLines() {
+ DecoratorContext context = mockContext(50, 50);
+
+ decorator.decorate(project, context);
+
+ verify(context).saveMeasure(CoreMetrics.IT_LINE_COVERAGE, 0.0);
+ }
+
+ @Test
+ public void allCoveredLines() {
+ DecoratorContext context = mockContext(50, 00);
+
+ decorator.decorate(project, context);
+
+ verify(context).saveMeasure(CoreMetrics.IT_LINE_COVERAGE, 100.0);
+ }
+
+ @Test
+ public void noLineCoverageIfNoLines() {
+ DecoratorContext context = mock(DecoratorContext.class);
+
+ decorator.decorate(project, context);
+
+ verify(context, never()).saveMeasure(eq(CoreMetrics.IT_LINE_COVERAGE), anyDouble());
+ }
+
+ private static DecoratorContext mockContext(int lines, int uncoveredLines) {
+ DecoratorContext context = mock(DecoratorContext.class);
+ when(context.getMeasure(CoreMetrics.IT_LINES_TO_COVER)).thenReturn(new Measure(CoreMetrics.IT_LINES_TO_COVER, (double) lines));
+ when(context.getMeasure(CoreMetrics.IT_UNCOVERED_LINES)).thenReturn(new Measure(CoreMetrics.IT_UNCOVERED_LINES, (double) uncoveredLines));
+ return context;
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/compute/LineCoverageDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/compute/LineCoverageDecoratorTest.java
new file mode 100644
index 00000000000..2a49f83aaf4
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/compute/LineCoverageDecoratorTest.java
@@ -0,0 +1,98 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Scopes;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.anyDouble;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class LineCoverageDecoratorTest {
+
+ private LineCoverageDecorator decorator;
+ private final Project project = mock(Project.class);
+
+ @Before
+ public void before() {
+ when(project.getScope()).thenReturn(Scopes.PROJECT);
+ decorator = new LineCoverageDecorator();
+ }
+
+ @Test
+ public void should_depend_on_coverage_metrics() {
+ assertThat(decorator.dependsUponMetrics()).containsOnly(CoreMetrics.UNCOVERED_LINES, CoreMetrics.LINES_TO_COVER, CoreMetrics.NEW_UNCOVERED_LINES,
+ CoreMetrics.NEW_LINES_TO_COVER);
+ }
+
+ @Test
+ public void lineCoverage() {
+ DecoratorContext context = mockContext(50, 10);
+
+ decorator.decorate(project, context);
+
+ // 50-10 covered lines / 50 lines
+ verify(context).saveMeasure(CoreMetrics.LINE_COVERAGE, 80.0);
+ }
+
+ @Test
+ public void zeroCoveredLines() {
+ DecoratorContext context = mockContext(50, 50);
+
+ decorator.decorate(project, context);
+
+ verify(context).saveMeasure(CoreMetrics.LINE_COVERAGE, 0.0);
+ }
+
+ @Test
+ public void allCoveredLines() {
+ DecoratorContext context = mockContext(50, 00);
+
+ decorator.decorate(project, context);
+
+ verify(context).saveMeasure(CoreMetrics.LINE_COVERAGE, 100.0);
+ }
+
+ @Test
+ public void noLineCoverageIfNoLines() {
+ DecoratorContext context = mock(DecoratorContext.class);
+
+ decorator.decorate(project, context);
+
+ verify(context, never()).saveMeasure(eq(CoreMetrics.LINE_COVERAGE), anyDouble());
+ }
+
+ private static DecoratorContext mockContext(int lines, int uncoveredLines) {
+ DecoratorContext context = mock(DecoratorContext.class);
+ when(context.getMeasure(CoreMetrics.LINES_TO_COVER)).thenReturn(new Measure(CoreMetrics.LINES_TO_COVER, (double) lines));
+ when(context.getMeasure(CoreMetrics.UNCOVERED_LINES)).thenReturn(new Measure(CoreMetrics.UNCOVERED_LINES, (double) uncoveredLines));
+ return context;
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/compute/ManualMeasureDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/compute/ManualMeasureDecoratorTest.java
new file mode 100644
index 00000000000..bd86438ad05
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/compute/ManualMeasureDecoratorTest.java
@@ -0,0 +1,52 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import org.junit.Test;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.File;
+import org.sonar.api.test.IsMeasure;
+import org.sonar.core.metric.DefaultMetricFinder;
+import org.sonar.jpa.test.AbstractDbUnitTestCase;
+
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+public class ManualMeasureDecoratorTest extends AbstractDbUnitTestCase {
+
+ private Metric reviewNote = new Metric.Builder("review_note", "Note", Metric.ValueType.FLOAT).create().setId(2);
+
+ @Test
+ public void testCopyManualMeasures() throws Exception {
+ setupData("testCopyManualMeasures");
+
+ File javaFile = File.create("Foo.java");
+ javaFile.setId(40);
+
+ ManualMeasureDecorator decorator = new ManualMeasureDecorator(getSession(), new DefaultMetricFinder(getSessionFactory()));
+ DecoratorContext context = mock(DecoratorContext.class);
+ decorator.decorate(javaFile, context);
+
+ verify(context).saveMeasure(argThat(new IsMeasure(reviewNote, 6.0, "six")));
+ }
+
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/compute/NewCoverageAggregatorTest.java b/sonar-batch/src/test/java/org/sonar/batch/compute/NewCoverageAggregatorTest.java
new file mode 100644
index 00000000000..7951e3a7add
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/compute/NewCoverageAggregatorTest.java
@@ -0,0 +1,90 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import java.util.Arrays;
+import java.util.Collections;
+import org.junit.Test;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Matchers;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class NewCoverageAggregatorTest {
+
+ @Test
+ public void shouldNotSaveDataWhenNoMeasures() {
+ NewCoverageAggregator aggregator = new NewCoverageAggregator();
+ DecoratorContext context = mock(DecoratorContext.class);
+ when(context.getChildrenMeasures(CoreMetrics.NEW_LINES_TO_COVER)).thenReturn(Collections.<Measure>emptyList());
+
+ aggregator.aggregate(context, CoreMetrics.NEW_LINES_TO_COVER, 3);
+
+ verify(context, never()).saveMeasure(Matchers.<Measure>anyObject());
+ }
+
+ @Test
+ public void shouldNotsetZeroWhenNoValueOnPeriod() {
+ NewCoverageAggregator aggregator = new NewCoverageAggregator();
+ DecoratorContext context = mock(DecoratorContext.class);
+ when(context.getChildrenMeasures(CoreMetrics.NEW_LINES_TO_COVER)).thenReturn(Arrays.asList(newMeasure(null, 3.0, 2.0), newMeasure(null, 13.0, null)));
+
+ aggregator.aggregate(context, CoreMetrics.NEW_LINES_TO_COVER, 3);
+
+ verify(context).saveMeasure(argThat(new ArgumentMatcher<Measure>() {
+ @Override
+ public boolean matches(Object o) {
+ Measure m = (Measure) o;
+ return m.getVariation1() == null;
+ }
+ }));
+ }
+
+ @Test
+ public void shouldSumValues() {
+ NewCoverageAggregator aggregator = new NewCoverageAggregator();
+ DecoratorContext context = mock(DecoratorContext.class);
+ when(context.getChildrenMeasures(CoreMetrics.NEW_LINES_TO_COVER)).thenReturn(Arrays.asList(newMeasure(null, 3.0, 2.0), newMeasure(null, 13.0, null)));
+
+ aggregator.aggregate(context, CoreMetrics.NEW_LINES_TO_COVER, 3);
+
+ verify(context).saveMeasure(argThat(new ArgumentMatcher<Measure>() {
+ @Override
+ public boolean matches(Object o) {
+ Measure m = (Measure) o;
+ return m.getVariation2() == 16.0 && m.getVariation3() == 2.0;
+ }
+ }));
+ }
+
+ private Measure newMeasure(Double variation1, Double variation2, Double variation3) {
+ return new Measure(CoreMetrics.NEW_LINES_TO_COVER)
+ .setVariation1(variation1)
+ .setVariation2(variation2)
+ .setVariation3(variation3);
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/compute/NewCoverageFileAnalyzerTest.java b/sonar-batch/src/test/java/org/sonar/batch/compute/NewCoverageFileAnalyzerTest.java
new file mode 100644
index 00000000000..6deb673fa50
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/compute/NewCoverageFileAnalyzerTest.java
@@ -0,0 +1,297 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.mockito.ArgumentMatcher;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.File;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.batch.index.BatchComponentCache;
+import org.sonar.batch.protocol.output.BatchReport;
+import org.sonar.batch.protocol.output.BatchReport.Changesets.Changeset;
+import org.sonar.batch.protocol.output.BatchReportWriter;
+import org.sonar.batch.report.ReportPublisher;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class NewCoverageFileAnalyzerTest {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ private DecoratorContext context;
+ private NewCoverageFileAnalyzer decorator;
+ private BatchReportWriter writer;
+
+ @Before
+ public void prepare() throws Exception {
+ context = mock(DecoratorContext.class);
+ Resource f = File.create("src/Foo.java").setEffectiveKey("foo:src/Foo.java");
+ when(context.getResource()).thenReturn(f);
+ BatchComponentCache cache = new BatchComponentCache();
+ cache.add(f, null);
+ List<AbstractNewCoverageFileAnalyzer.PeriodStruct> structs = Arrays.asList(
+ new AbstractNewCoverageFileAnalyzer.PeriodStruct(1, newDate("2009-12-25")),
+ new AbstractNewCoverageFileAnalyzer.PeriodStruct(3, newDate("2011-02-18")));
+ ReportPublisher publishReportJob = mock(ReportPublisher.class);
+ java.io.File reportBaseDir = temp.newFolder();
+ when(publishReportJob.getReportDir()).thenReturn(reportBaseDir);
+ writer = new BatchReportWriter(reportBaseDir);
+ decorator = new NewCoverageFileAnalyzer(structs, publishReportJob, cache);
+
+ }
+
+ @Test
+ public void shouldDoNothingIfNoScmData() {
+ when(context.getMeasure(CoreMetrics.COVERAGE_LINE_HITS_DATA))
+ .thenReturn(new Measure(CoreMetrics.COVERAGE_LINE_HITS_DATA, "1=10"));
+
+ decorator.doDecorate(context);
+ verify(context, never()).saveMeasure(any(Measure.class));
+ }
+
+ @Test
+ public void shouldDoNothingIfNoCoverageData() {
+ writer.writeComponentChangesets(BatchReport.Changesets.newBuilder()
+ .setComponentRef(1)
+ .addChangeset(Changeset.newBuilder()
+ .setDate(DateUtils.parseDateTime("2008-05-18T00:00:00+0000").getTime())
+ .build())
+ .addChangesetIndexByLine(0)
+ .build());
+
+ decorator.doDecorate(context);
+
+ verify(context, never()).saveMeasure(any(Measure.class));
+ }
+
+ @Test
+ public void shouldGetNewLines() {
+ when(context.getMeasure(CoreMetrics.COVERAGE_LINE_HITS_DATA)).thenReturn(
+ new Measure(CoreMetrics.COVERAGE_LINE_HITS_DATA, "10=2;11=3"));
+ writer.writeComponentChangesets(BatchReport.Changesets.newBuilder()
+ .setComponentRef(1)
+ .addChangeset(Changeset.newBuilder()
+ .build())
+ .addChangeset(Changeset.newBuilder()
+ .setDate(DateUtils.parseDateTime("2007-01-15T00:00:00+0000").getTime())
+ .build())
+ .addChangeset(Changeset.newBuilder()
+ .setDate(DateUtils.parseDateTime("2011-01-01T00:00:00+0000").getTime())
+ .build())
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(1)
+ .addChangesetIndexByLine(2)
+ .build());
+
+ decorator.doDecorate(context);
+
+ // line 11 has been updated after date1 (2009-12-25). This line is covered.
+ verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_LINES_TO_COVER, 1, 1.0)));
+ verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_UNCOVERED_LINES, 1, 0.0)));
+
+ // no line have been updated after date3 (2011-02-18)
+ verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_LINES_TO_COVER, 3, null)));
+ verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_UNCOVERED_LINES, 3, null)));
+
+ // no data on other periods
+ verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_LINES_TO_COVER, 2, null)));
+ verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_LINES_TO_COVER, 4, null)));
+ verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_LINES_TO_COVER, 5, null)));
+ verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_UNCOVERED_LINES, 2, null)));
+ verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_UNCOVERED_LINES, 4, null)));
+ verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_UNCOVERED_LINES, 5, null)));
+ }
+
+ @Test
+ public void shouldGetNewConditions() {
+ when(context.getMeasure(CoreMetrics.COVERAGE_LINE_HITS_DATA)).thenReturn(
+ new Measure(CoreMetrics.COVERAGE_LINE_HITS_DATA, "10=2;11=3"));
+ when(context.getMeasure(CoreMetrics.CONDITIONS_BY_LINE)).thenReturn(
+ new Measure(CoreMetrics.CONDITIONS_BY_LINE, "11=4"));
+ when(context.getMeasure(CoreMetrics.COVERED_CONDITIONS_BY_LINE)).thenReturn(
+ new Measure(CoreMetrics.COVERED_CONDITIONS_BY_LINE, "11=1"));
+ writer.writeComponentChangesets(BatchReport.Changesets.newBuilder()
+ .setComponentRef(1)
+ .addChangeset(Changeset.newBuilder()
+ .build())
+ .addChangeset(Changeset.newBuilder()
+ .setDate(DateUtils.parseDateTime("2007-01-15T00:00:00+0000").getTime())
+ .build())
+ .addChangeset(Changeset.newBuilder()
+ .setDate(DateUtils.parseDateTime("2011-01-01T00:00:00+0000").getTime())
+ .build())
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(1)
+ .addChangesetIndexByLine(2)
+ .build());
+
+ decorator.doDecorate(context);
+
+ // line 11 has been updated after date1 (2009-12-25). This line has 1 covered condition amongst 4
+ verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_CONDITIONS_TO_COVER, 1, 4.0)));
+ verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_UNCOVERED_CONDITIONS, 1, 3.0)));
+
+ // no line have been updated after date3 (2011-02-18)
+ verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_CONDITIONS_TO_COVER, 3, null)));
+ verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_UNCOVERED_CONDITIONS, 3, null)));
+
+ // no data on other periods
+ verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_CONDITIONS_TO_COVER, 2, null)));
+ verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_CONDITIONS_TO_COVER, 4, null)));
+ verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_CONDITIONS_TO_COVER, 5, null)));
+ verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_UNCOVERED_CONDITIONS, 2, null)));
+ verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_UNCOVERED_CONDITIONS, 4, null)));
+ verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_UNCOVERED_CONDITIONS, 5, null)));
+ }
+
+ @Test
+ public void shouldNotGetNewConditionsWhenNewLineHasNoConditions() {
+ when(context.getMeasure(CoreMetrics.COVERAGE_LINE_HITS_DATA)).thenReturn(
+ new Measure(CoreMetrics.COVERAGE_LINE_HITS_DATA, "10=2;11=3"));
+ when(context.getMeasure(CoreMetrics.CONDITIONS_BY_LINE)).thenReturn(
+ new Measure(CoreMetrics.CONDITIONS_BY_LINE, "10=1"));
+ when(context.getMeasure(CoreMetrics.COVERED_CONDITIONS_BY_LINE)).thenReturn(
+ new Measure(CoreMetrics.COVERED_CONDITIONS_BY_LINE, "10=1"));
+ writer.writeComponentChangesets(BatchReport.Changesets.newBuilder()
+ .setComponentRef(1)
+ .addChangeset(Changeset.newBuilder()
+ .build())
+ .addChangeset(Changeset.newBuilder()
+ .setDate(DateUtils.parseDateTime("2007-01-15T00:00:00+0000").getTime())
+ .build())
+ .addChangeset(Changeset.newBuilder()
+ .setDate(DateUtils.parseDateTime("2011-01-01T00:00:00+0000").getTime())
+ .build())
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(1)
+ .addChangesetIndexByLine(2)
+ .build());
+
+ decorator.doDecorate(context);
+
+ // line 11 has been updated after date1 (2009-12-25) but it has no conditions
+ verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_CONDITIONS_TO_COVER, 1, 0.0)));
+ verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_UNCOVERED_CONDITIONS, 1, 0.0)));
+ }
+
+ @Test
+ public void shouldLeaveNullValueWhenNothingHasChanged() {
+
+ when(context.getMeasure(CoreMetrics.COVERAGE_LINE_HITS_DATA)).thenReturn(
+ new Measure(CoreMetrics.COVERAGE_LINE_HITS_DATA, "2=1;3=1"));
+ when(context.getMeasure(CoreMetrics.CONDITIONS_BY_LINE)).thenReturn(
+ new Measure(CoreMetrics.CONDITIONS_BY_LINE, "2=1"));
+ when(context.getMeasure(CoreMetrics.COVERED_CONDITIONS_BY_LINE)).thenReturn(
+ new Measure(CoreMetrics.COVERED_CONDITIONS_BY_LINE, "2=1"));
+ writer.writeComponentChangesets(BatchReport.Changesets.newBuilder()
+ .setComponentRef(1)
+ .addChangeset(Changeset.newBuilder()
+ .setDate(DateUtils.parseDateTime("2008-08-02T13:56:37+0200").getTime())
+ .build())
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(0)
+ .addChangesetIndexByLine(0)
+ .build());
+
+ decorator.doDecorate(context);
+
+ verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_LINES_TO_COVER, 1, null)));
+ verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_UNCOVERED_LINES, 1, null)));
+ verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_CONDITIONS_TO_COVER, 1, null)));
+ verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_UNCOVERED_CONDITIONS, 1, null)));
+
+ verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_LINES_TO_COVER, 3, null)));
+ verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_UNCOVERED_LINES, 3, null)));
+ verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_CONDITIONS_TO_COVER, 3, null)));
+ verify(context).saveMeasure(argThat(new VariationMatcher(CoreMetrics.NEW_UNCOVERED_CONDITIONS, 3, null)));
+ }
+
+ static class VariationMatcher extends ArgumentMatcher<Measure> {
+ Metric metric;
+ int index;
+ Double variation;
+
+ VariationMatcher(Metric metric, int index, Double variation) {
+ this.metric = metric;
+ this.index = index;
+ this.variation = variation;
+ }
+
+ @Override
+ public boolean matches(Object o) {
+ Measure m = (Measure) o;
+ if (m.getMetric().equals(metric)) {
+ if ((variation == null && m.getVariation(index) == null) ||
+ (variation != null && variation.equals(m.getVariation(index)))) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ private Date newDate(String s) throws ParseException {
+ return new SimpleDateFormat(DateUtils.DATE_FORMAT).parse(s);
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/compute/OverallBranchCoverageDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/compute/OverallBranchCoverageDecoratorTest.java
new file mode 100644
index 00000000000..9c5e585c969
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/compute/OverallBranchCoverageDecoratorTest.java
@@ -0,0 +1,72 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.resources.Scopes;
+
+import static org.mockito.Matchers.anyDouble;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class OverallBranchCoverageDecoratorTest {
+ private final OverallBranchCoverageDecorator decorator = new OverallBranchCoverageDecorator();
+ private final Project resource = mock(Project.class);
+
+ @Before
+ public void setUp() {
+ when(resource.getScope()).thenReturn(Scopes.PROJECT);
+ when(resource.getQualifier()).thenReturn(Qualifiers.PROJECT);
+ }
+
+ @Test
+ public void shouldSaveBranchCoverage() {
+ DecoratorContext context = mockContext(20, 15);
+
+ decorator.decorate(resource, context);
+
+ verify(context).saveMeasure(CoreMetrics.OVERALL_BRANCH_COVERAGE, 25.0);
+ }
+
+ @Test
+ public void shouldNotSaveBranchCoverageIfMissingConditions() {
+ DecoratorContext context = mock(DecoratorContext.class);
+
+ decorator.decorate(resource, context);
+
+ verify(context, never()).saveMeasure(eq(CoreMetrics.OVERALL_BRANCH_COVERAGE), anyDouble());
+ }
+
+ private static DecoratorContext mockContext(int conditions, int uncoveredConditions) {
+ DecoratorContext context = mock(DecoratorContext.class);
+ when(context.getMeasure(CoreMetrics.OVERALL_CONDITIONS_TO_COVER)).thenReturn(new Measure(CoreMetrics.OVERALL_CONDITIONS_TO_COVER, (double) conditions));
+ when(context.getMeasure(CoreMetrics.OVERALL_UNCOVERED_CONDITIONS)).thenReturn(new Measure(CoreMetrics.OVERALL_UNCOVERED_CONDITIONS, (double) uncoveredConditions));
+ return context;
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/compute/OverallCoverageDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/compute/OverallCoverageDecoratorTest.java
new file mode 100644
index 00000000000..d694227c754
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/compute/OverallCoverageDecoratorTest.java
@@ -0,0 +1,142 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import java.util.Collection;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Scopes;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.anyDouble;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class OverallCoverageDecoratorTest {
+ private final OverallCoverageDecorator decorator = new OverallCoverageDecorator();
+ private final Project project = mock(Project.class);
+
+ @Before
+ public void before() {
+ when(project.getScope()).thenReturn(Scopes.PROJECT);
+ }
+
+ @Test
+ public void should_use_metrics() {
+ Collection<Metric> metrics = decorator.usedMetrics();
+
+ assertThat(metrics).containsOnly(CoreMetrics.OVERALL_LINES_TO_COVER, CoreMetrics.OVERALL_UNCOVERED_LINES, CoreMetrics.NEW_OVERALL_LINES_TO_COVER,
+ CoreMetrics.NEW_OVERALL_UNCOVERED_LINES, CoreMetrics.OVERALL_CONDITIONS_TO_COVER, CoreMetrics.OVERALL_UNCOVERED_CONDITIONS,
+ CoreMetrics.NEW_OVERALL_CONDITIONS_TO_COVER, CoreMetrics.NEW_OVERALL_UNCOVERED_CONDITIONS);
+ }
+
+ @Test
+ public void coverage() {
+ DecoratorContext context = mockContext(50, 40, 10, 8);
+
+ decorator.decorate(project, context);
+
+ // (50-40 covered lines + 10-8 covered conditions) / (50 lines + 10 conditions)
+ verify(context).saveMeasure(CoreMetrics.OVERALL_COVERAGE, 20.0);
+ }
+
+ @Test
+ public void coverageCanBe0() {
+ DecoratorContext context = mockContext(50, 50, 5, 5);
+
+ decorator.decorate(project, context);
+
+ verify(context).saveMeasure(CoreMetrics.OVERALL_COVERAGE, 0.0);
+ }
+
+ @Test
+ public void coverageCanBe100() {
+ DecoratorContext context = mockContext(50, 0, 5, 0);
+
+ decorator.decorate(project, context);
+
+ verify(context).saveMeasure(CoreMetrics.OVERALL_COVERAGE, 100.0);
+ }
+
+ @Test
+ public void noCoverageIfNoElements() {
+ DecoratorContext context = mock(DecoratorContext.class);
+
+ decorator.decorate(project, context);
+
+ verify(context, never()).saveMeasure(eq(CoreMetrics.OVERALL_COVERAGE), anyDouble());
+ }
+
+ @Test
+ public void should_count_elements_for_new_code() {
+ Measure newLines = measureWithVariation(1, 100.0);
+ Measure newConditions = measureWithVariation(1, 1.0);
+ DecoratorContext context = mockNewContext(newLines, null, null, newConditions);
+
+ long count = decorator.countElementsForNewCode(context, 1);
+
+ assertThat(count).isEqualTo(101).isEqualTo(100 + 1);
+ }
+
+ @Test
+ public void should_count_covered_elements_for_new_code() {
+ Measure newLines = measureWithVariation(1, 100.0);
+ Measure newUncoveredConditions = measureWithVariation(1, 10.0);
+ Measure newUncoveredLines = measureWithVariation(1, 5.0);
+ Measure newConditions = measureWithVariation(1, 1.0);
+ DecoratorContext context = mockNewContext(newLines, newUncoveredConditions, newUncoveredLines, newConditions);
+
+ long count = decorator.countCoveredElementsForNewCode(context, 1);
+
+ assertThat(count).isEqualTo(86).isEqualTo(100 + 1 - 10 - 5);
+ }
+
+ private static DecoratorContext mockContext(int lines, int uncoveredLines, int conditions, int uncoveredConditions) {
+ DecoratorContext context = mock(DecoratorContext.class);
+ when(context.getMeasure(CoreMetrics.OVERALL_LINES_TO_COVER)).thenReturn(new Measure(CoreMetrics.OVERALL_LINES_TO_COVER, (double) lines));
+ when(context.getMeasure(CoreMetrics.OVERALL_UNCOVERED_LINES)).thenReturn(new Measure(CoreMetrics.OVERALL_UNCOVERED_LINES, (double) uncoveredLines));
+ when(context.getMeasure(CoreMetrics.OVERALL_CONDITIONS_TO_COVER)).thenReturn(new Measure(CoreMetrics.OVERALL_CONDITIONS_TO_COVER, (double) conditions));
+ when(context.getMeasure(CoreMetrics.OVERALL_UNCOVERED_CONDITIONS)).thenReturn(new Measure(CoreMetrics.OVERALL_UNCOVERED_CONDITIONS, (double) uncoveredConditions));
+ return context;
+ }
+
+ private static DecoratorContext mockNewContext(Measure newLines, Measure newUncoveredConditions, Measure newUncoveredLines, Measure newConditions) {
+ DecoratorContext context = mock(DecoratorContext.class);
+ when(context.getMeasure(CoreMetrics.NEW_OVERALL_LINES_TO_COVER)).thenReturn(newLines);
+ when(context.getMeasure(CoreMetrics.NEW_OVERALL_UNCOVERED_LINES)).thenReturn(newUncoveredLines);
+ when(context.getMeasure(CoreMetrics.NEW_OVERALL_UNCOVERED_CONDITIONS)).thenReturn(newUncoveredConditions);
+ when(context.getMeasure(CoreMetrics.NEW_OVERALL_CONDITIONS_TO_COVER)).thenReturn(newConditions);
+ return context;
+ }
+
+ private static Measure measureWithVariation(int period, double variation) {
+ Measure measure = mock(Measure.class);
+ when(measure.getVariation(period)).thenReturn(variation);
+ return measure;
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/compute/OverallLineCoverageDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/compute/OverallLineCoverageDecoratorTest.java
new file mode 100644
index 00000000000..17d54526a35
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/compute/OverallLineCoverageDecoratorTest.java
@@ -0,0 +1,100 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Scopes;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.anyDouble;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class OverallLineCoverageDecoratorTest {
+ private final OverallLineCoverageDecorator decorator = new OverallLineCoverageDecorator();
+ private final Project project = mock(Project.class);
+
+ @Before
+ public void before() {
+ when(project.getScope()).thenReturn(Scopes.PROJECT);
+ }
+
+ @Test
+ public void should_depend_on_coverage_metrics() {
+ List<Metric> metrics = decorator.dependsUponMetrics();
+
+ assertThat(metrics).containsOnly(CoreMetrics.OVERALL_UNCOVERED_LINES, CoreMetrics.OVERALL_LINES_TO_COVER, CoreMetrics.NEW_OVERALL_UNCOVERED_LINES,
+ CoreMetrics.NEW_OVERALL_LINES_TO_COVER);
+ }
+
+ @Test
+ public void lineCoverage() {
+ DecoratorContext context = mockContext(50, 10);
+
+ decorator.decorate(project, context);
+
+ // 50-10 covered lines / 50 lines
+ verify(context).saveMeasure(CoreMetrics.OVERALL_LINE_COVERAGE, 80.0);
+ }
+
+ @Test
+ public void zeroCoveredLines() {
+ DecoratorContext context = mockContext(50, 50);
+
+ decorator.decorate(project, context);
+
+ verify(context).saveMeasure(CoreMetrics.OVERALL_LINE_COVERAGE, 0.0);
+ }
+
+ @Test
+ public void allCoveredLines() {
+ DecoratorContext context = mockContext(50, 00);
+
+ decorator.decorate(project, context);
+
+ verify(context).saveMeasure(CoreMetrics.OVERALL_LINE_COVERAGE, 100.0);
+ }
+
+ @Test
+ public void noLineCoverageIfNoLines() {
+ DecoratorContext context = mock(DecoratorContext.class);
+
+ decorator.decorate(project, context);
+
+ verify(context, never()).saveMeasure(eq(CoreMetrics.OVERALL_LINE_COVERAGE), anyDouble());
+ }
+
+ private static DecoratorContext mockContext(int lines, int uncoveredLines) {
+ DecoratorContext context = mock(DecoratorContext.class);
+ when(context.getMeasure(CoreMetrics.OVERALL_LINES_TO_COVER)).thenReturn(new Measure(CoreMetrics.OVERALL_LINES_TO_COVER, (double) lines));
+ when(context.getMeasure(CoreMetrics.OVERALL_UNCOVERED_LINES)).thenReturn(new Measure(CoreMetrics.OVERALL_UNCOVERED_LINES, (double) uncoveredLines));
+ return context;
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/compute/TimeMachineConfigurationPersisterTest.java b/sonar-batch/src/test/java/org/sonar/batch/compute/TimeMachineConfigurationPersisterTest.java
new file mode 100644
index 00000000000..2c48e9c6614
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/compute/TimeMachineConfigurationPersisterTest.java
@@ -0,0 +1,59 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import java.util.Arrays;
+import org.junit.Test;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.resources.Project;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.batch.components.PastSnapshot;
+import org.sonar.batch.components.TimeMachineConfiguration;
+import org.sonar.batch.index.BatchComponentCache;
+import org.sonar.jpa.test.AbstractDbUnitTestCase;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TimeMachineConfigurationPersisterTest extends AbstractDbUnitTestCase {
+
+ @Test
+ public void shouldSaveConfigurationInSnapshotsTable() {
+ setupData("shared");
+
+ TimeMachineConfiguration timeMachineConfiguration = mock(TimeMachineConfiguration.class);
+ PastSnapshot vs1 = new PastSnapshot("days", DateUtils.parseDate("2009-01-25"), getSession().getSingleResult(Snapshot.class, "id", 100))
+ .setModeParameter("30").setIndex(1);
+ PastSnapshot vs3 = new PastSnapshot("version", DateUtils.parseDate("2008-12-13"), getSession().getSingleResult(Snapshot.class, "id", 300))
+ .setModeParameter("1.2.3").setIndex(3);
+ when(timeMachineConfiguration.getProjectPastSnapshots()).thenReturn(Arrays.asList(vs1, vs3));
+ Snapshot projectSnapshot = getSession().getSingleResult(Snapshot.class, "id", 1000);
+
+ BatchComponentCache resourceCache = new BatchComponentCache();
+ Project project = new Project("foo");
+ resourceCache.add(project, null).setSnapshot(projectSnapshot);
+
+ TimeMachineConfigurationPersister persister = new TimeMachineConfigurationPersister(timeMachineConfiguration, resourceCache, getSession());
+
+ persister.persistConfiguration(project);
+
+ checkTables("shouldSaveConfigurationInSnapshotsTable", "snapshots");
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/compute/UnitTestDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/compute/UnitTestDecoratorTest.java
new file mode 100644
index 00000000000..eb9bd93a4d1
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/compute/UnitTestDecoratorTest.java
@@ -0,0 +1,87 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import java.util.Arrays;
+import org.hamcrest.Matchers;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.Project;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.doubleThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class UnitTestDecoratorTest {
+
+ private UnitTestDecorator decorator;
+ private DecoratorContext context;
+
+ @Before
+ public void setUp() {
+ decorator = new UnitTestDecorator();
+ context = mock(DecoratorContext.class);
+ }
+
+ @Test
+ public void generatesMetrics() {
+ assertThat(decorator.generatesMetrics()).hasSize(5);
+ }
+
+ @Test
+ public void doNotDecorateStaticAnalysis() {
+ Project project = mock(Project.class);
+ when(project.getAnalysisType()).thenReturn(Project.AnalysisType.STATIC);
+ assertThat(decorator.shouldExecuteOnProject(project)).isFalse();
+
+ when(project.getAnalysisType()).thenReturn(Project.AnalysisType.DYNAMIC);
+ assertThat(decorator.shouldExecuteOnProject(project)).isTrue();
+ }
+
+ @Test
+ public void shouldSumChildren() {
+ Project project = mock(Project.class);
+ mockChildrenMeasures(CoreMetrics.TESTS, 3.0);
+ mockChildrenMeasures(CoreMetrics.TEST_ERRORS, 1.0);
+ mockChildrenMeasures(CoreMetrics.TEST_FAILURES, 1.0);
+ mockChildrenMeasures(CoreMetrics.SKIPPED_TESTS, 1.0);
+ mockChildrenMeasures(CoreMetrics.TEST_EXECUTION_TIME, 1.0);
+
+ decorator.decorate(project, context);
+
+ verify(context).saveMeasure(eq(CoreMetrics.TESTS), eq(6.0));
+ verify(context).saveMeasure(eq(CoreMetrics.TEST_ERRORS), eq(2.0));
+ verify(context).saveMeasure(eq(CoreMetrics.TEST_FAILURES), eq(2.0));
+ verify(context).saveMeasure(eq(CoreMetrics.SKIPPED_TESTS), eq(2.0));
+ verify(context).saveMeasure(eq(CoreMetrics.TEST_EXECUTION_TIME), eq(2.0));
+ verify(context).saveMeasure(eq(CoreMetrics.TEST_SUCCESS_DENSITY), doubleThat(Matchers.closeTo(33.3, 0.1)));
+ }
+
+ private void mockChildrenMeasures(Metric metric, double value) {
+ when(context.getChildrenMeasures(metric)).thenReturn(Arrays.asList(new Measure(metric, value), new Measure(metric, value)));
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/compute/VariationDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/compute/VariationDecoratorTest.java
new file mode 100644
index 00000000000..21f26e09090
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/compute/VariationDecoratorTest.java
@@ -0,0 +1,149 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.compute;
+
+import java.util.Arrays;
+import java.util.Date;
+import org.junit.Test;
+import org.mockito.Matchers;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.MeasuresFilter;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.measures.MetricFinder;
+import org.sonar.api.measures.RuleMeasure;
+import org.sonar.api.resources.Directory;
+import org.sonar.api.resources.File;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleFinder;
+import org.sonar.batch.components.PastMeasuresLoader;
+import org.sonar.batch.components.PastSnapshot;
+import org.sonar.batch.components.TimeMachineConfiguration;
+import org.sonar.jpa.test.AbstractDbUnitTestCase;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class VariationDecoratorTest extends AbstractDbUnitTestCase {
+
+ public static final int NCLOC_ID = 12;
+ public static final Metric NCLOC = new Metric("ncloc").setId(NCLOC_ID);
+
+ public static final int COVERAGE_ID = 16;
+ public static final Metric COVERAGE = new Metric("coverage").setId(COVERAGE_ID);
+
+ public static final int VIOLATIONS_ID = 17;
+ public static final Metric VIOLATIONS = new Metric("violations").setId(VIOLATIONS_ID);
+
+ @Test
+ public void shouldComputeVariations() {
+ TimeMachineConfiguration timeMachineConfiguration = mock(TimeMachineConfiguration.class);
+ VariationDecorator decorator = new VariationDecorator(mock(PastMeasuresLoader.class), mock(MetricFinder.class), timeMachineConfiguration, mock(RuleFinder.class));
+
+ assertThat(decorator.shouldComputeVariation(new Project("foo"))).isTrue();
+ assertThat(decorator.shouldComputeVariation(File.create("foo/bar.c"))).isFalse();
+ }
+
+ @Test
+ public void shouldCompareAndSaveVariation() {
+ Resource dir = Directory.create("org/foo");
+
+ PastMeasuresLoader pastMeasuresLoader = mock(PastMeasuresLoader.class);
+ PastSnapshot pastSnapshot1 = new PastSnapshot("days", new Date()).setIndex(1);
+ PastSnapshot pastSnapshot3 = new PastSnapshot("days", new Date()).setIndex(3);
+
+ // first past analysis
+ when(pastMeasuresLoader.getPastMeasures(dir, pastSnapshot1)).thenReturn(Arrays.asList(
+ new Object[] {NCLOC_ID, null, null, null, 180.0},
+ new Object[] {COVERAGE_ID, null, null, null, 75.0}));
+
+ // second past analysis
+ when(pastMeasuresLoader.getPastMeasures(dir, pastSnapshot3)).thenReturn(Arrays.<Object[]>asList(
+ new Object[] {NCLOC_ID, null, null, null, 240.0}));
+
+ // current analysis
+ DecoratorContext context = mock(DecoratorContext.class);
+ Measure currentNcloc = newMeasure(NCLOC, 200.0);
+ Measure currentCoverage = newMeasure(COVERAGE, 80.0);
+ when(context.getMeasures(Matchers.<MeasuresFilter>anyObject())).thenReturn(Arrays.asList(currentNcloc, currentCoverage));
+
+ VariationDecorator decorator = new VariationDecorator(pastMeasuresLoader, mock(MetricFinder.class), Arrays.asList(pastSnapshot1, pastSnapshot3), mock(RuleFinder.class));
+ decorator.decorate(dir, context);
+
+ // context updated for each variation : 2 times for ncloc and 1 time for coverage
+ verify(context, times(3)).saveMeasure(Matchers.<Measure>anyObject());
+
+ assertThat(currentNcloc.getVariation1()).isEqualTo(20.0);
+ assertThat(currentNcloc.getVariation2()).isNull();
+ assertThat(currentNcloc.getVariation3()).isEqualTo(-40.0);
+
+ assertThat(currentCoverage.getVariation1()).isEqualTo(5.0);
+ assertThat(currentCoverage.getVariation2()).isNull();
+ assertThat(currentCoverage.getVariation3()).isNull();
+ }
+
+ @Test
+ public void shouldComputeVariationOfRuleMeasures() {
+ RuleFinder ruleFinder = mock(RuleFinder.class);
+
+ Rule rule1 = Rule.create("repo", "rule1");
+ rule1.setId(1);
+ Rule rule2 = Rule.create("repo", "rule2");
+ rule2.setId(2);
+
+ when(ruleFinder.findByKey(rule1.ruleKey())).thenReturn(rule1);
+ when(ruleFinder.findByKey(rule2.ruleKey())).thenReturn(rule2);
+
+ Resource dir = Directory.create("org/foo");
+
+ PastMeasuresLoader pastMeasuresLoader = mock(PastMeasuresLoader.class);
+ PastSnapshot pastSnapshot1 = new PastSnapshot("days", new Date()).setIndex(1);
+
+ // first past analysis
+ when(pastMeasuresLoader.getPastMeasures(dir, pastSnapshot1)).thenReturn(Arrays.asList(
+ new Object[] {VIOLATIONS_ID, null, null, null, 180.0},// total
+ new Object[] {VIOLATIONS_ID, null, null, rule1.getId(), 100.0},// rule 1
+ new Object[] {VIOLATIONS_ID, null, null, rule2.getId(), 80.0})); // rule 2
+
+ // current analysis
+ DecoratorContext context = mock(DecoratorContext.class);
+ Measure violations = newMeasure(VIOLATIONS, 200.0);
+ Measure violationsRule1 = RuleMeasure.createForRule(VIOLATIONS, rule1, 130.0);
+ Measure violationsRule2 = RuleMeasure.createForRule(VIOLATIONS, rule2, 70.0);
+ when(context.getMeasures(Matchers.<MeasuresFilter>anyObject())).thenReturn(Arrays.asList(violations, violationsRule1, violationsRule2));
+
+ VariationDecorator decorator = new VariationDecorator(pastMeasuresLoader, mock(MetricFinder.class), Arrays.asList(pastSnapshot1), ruleFinder);
+ decorator.decorate(dir, context);
+
+ // context updated for each variation
+ verify(context, times(3)).saveMeasure(Matchers.<Measure>anyObject());
+
+ assertThat(violations.getVariation1()).isEqualTo(20.0);
+ }
+
+ private Measure newMeasure(Metric metric, double value) {
+ return new Measure(metric, value);
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/measures/MeasuresMediumTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/measures/MeasuresMediumTest.java
index 41bdd9c67df..8d24834d97b 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/measures/MeasuresMediumTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/measures/MeasuresMediumTest.java
@@ -66,7 +66,7 @@ public class MeasuresMediumTest {
.newScanTask(new File(projectDir, "sonar-project.properties"))
.start();
- assertThat(result.allMeasures()).hasSize(61);
+ assertThat(result.allMeasures()).hasSize(90);
}
@Test
@@ -93,7 +93,7 @@ public class MeasuresMediumTest {
.build())
.start();
- assertThat(result.allMeasures()).hasSize(25);
+ assertThat(result.allMeasures()).hasSize(33);
assertThat(result.allMeasures()).contains(new DefaultMeasure<Integer>()
.forMetric(CoreMetrics.LINES)
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/compute/ManualMeasureDecoratorTest/testCopyManualMeasures.xml b/sonar-batch/src/test/resources/org/sonar/batch/compute/ManualMeasureDecoratorTest/testCopyManualMeasures.xml
new file mode 100644
index 00000000000..0307f16e6e3
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/compute/ManualMeasureDecoratorTest/testCopyManualMeasures.xml
@@ -0,0 +1,11 @@
+<dataset>
+ <metrics delete_historical_data="[null]" id="1" NAME="ncloc" VAL_TYPE="INT" DESCRIPTION="[null]" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+ <metrics delete_historical_data="[null]" id="2" NAME="review_note" VAL_TYPE="INT" DESCRIPTION="[null]" domain="[null]" short_name=""
+ enabled="true" worst_value="[null]" optimized_best_value="[null]" best_value="[null]" direction="0" hidden="false"/>
+
+
+ <manual_measures id="1" metric_id="2" resource_id="30" value="3.14" text_value="pi" created_at="[null]" updated_at="[null]" description="this is pi" user_login="me"/>
+ <manual_measures id="2" metric_id="2" resource_id="40" value="6" text_value="six" created_at="[null]" updated_at="[null]" description="this is six" user_login="me"/>
+
+</dataset> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/compute/ProjectLinksSensorTest/shouldDeleteMissingLinks.xml b/sonar-batch/src/test/resources/org/sonar/batch/compute/ProjectLinksSensorTest/shouldDeleteMissingLinks.xml
new file mode 100644
index 00000000000..836bb4630e9
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/compute/ProjectLinksSensorTest/shouldDeleteMissingLinks.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar</artifactId>
+ <packaging>pom</packaging>
+ <version>1.8-SNAPSHOT</version>
+
+</project> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/compute/ProjectLinksSensorTest/shouldSaveLinks.xml b/sonar-batch/src/test/resources/org/sonar/batch/compute/ProjectLinksSensorTest/shouldSaveLinks.xml
new file mode 100644
index 00000000000..a44ea429019
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/compute/ProjectLinksSensorTest/shouldSaveLinks.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar</artifactId>
+ <packaging>pom</packaging>
+ <version>1.8-SNAPSHOT</version>
+ <url>http://sonar.codehaus.org</url>
+
+ <organization>
+ <name>SonarSource SA</name>
+ <url>http://www.sonarsource.com</url>
+ </organization>
+ <inceptionYear>2009</inceptionYear>
+
+ <issueManagement>
+ <system>jira</system>
+ <url>http://jira.codehaus.org/browse/SONAR</url>
+ </issueManagement>
+
+ <mailingLists>
+ <mailingList>
+ <name>Sonar users mailing list</name>
+ <subscribe>http://xircles.codehaus.org/projects/sonar/lists</subscribe>
+ <unsubscribe>http://xircles.codehaus.org/projects/sonar/lists</unsubscribe>
+ <post>user@sonar.codehaus.org</post>
+ <archive>http://www.nabble.com/Sonar-f30151.html</archive>
+ </mailingList>
+ </mailingLists>
+
+ <scm>
+ <connection>scm:svn:http://svn.codehaus.org/sonar/trunk</connection>
+ <developerConnection>scm:svn:https://svn.codehaus.org/sonar/trunk</developerConnection>
+ <url>http://svn.sonar.codehaus.org</url>
+ </scm>
+
+ <ciManagement>
+ <system>bamboo</system>
+ <url>http://bamboo.ci.codehaus.org/browse/SONAR/</url>
+ </ciManagement>
+
+ <licenses>
+ <license>
+ <name>GNU Lesser General Public License (LGPL), v.3</name>
+ <url>http://www.gnu.org/licenses/lgpl.txt</url>
+ <distribution>repo</distribution>
+ </license>
+ </licenses>
+
+</project> \ No newline at end of file
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/compute/TimeMachineConfigurationPersisterTest/shared.xml b/sonar-batch/src/test/resources/org/sonar/batch/compute/TimeMachineConfigurationPersisterTest/shared.xml
new file mode 100644
index 00000000000..70c8178d4e8
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/compute/TimeMachineConfigurationPersisterTest/shared.xml
@@ -0,0 +1,42 @@
+<dataset>
+ <snapshots purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ id="100" project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ scope="PRJ" qualifier="TRK" created_at="1225544280000" build_date="1225544280000" version="[null]" path=""
+ status="P" islast="false" depth="0" />
+
+ <snapshots purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ id="200" project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ scope="PRJ" qualifier="TRK" created_at="1229345880000" build_date="1229345880000" version="[null]" path=""
+ status="P" islast="false" depth="0" />
+
+ <!-- Snapshot of previous version -->
+ <snapshots purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ id="300" project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ scope="PRJ" qualifier="TRK" created_at="1229173080000" build_date="1229173080000" version="1.2.3" path=""
+ status="P" islast="false" depth="0" />
+
+ <snapshots purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ id="1000" project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ scope="PRJ" qualifier="TRK" created_at="1235566680000" build_date="1235566680000" version="[null]" path=""
+ status="P" islast="false" depth="0" />
+</dataset>
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/compute/TimeMachineConfigurationPersisterTest/shouldSaveConfigurationInSnapshotsTable-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/compute/TimeMachineConfigurationPersisterTest/shouldSaveConfigurationInSnapshotsTable-result.xml
new file mode 100644
index 00000000000..34fdf2d4d32
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/compute/TimeMachineConfigurationPersisterTest/shouldSaveConfigurationInSnapshotsTable-result.xml
@@ -0,0 +1,42 @@
+<dataset>
+ <snapshots purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ id="100" project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ scope="PRJ" qualifier="TRK" created_at="1225544280000" build_date="1225544280000" version="[null]" path=""
+ status="P" islast="false" depth="0" />
+
+ <snapshots purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ id="200" project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ scope="PRJ" qualifier="TRK" created_at="1229345880000" build_date="1229345880000" version="[null]" path=""
+ status="P" islast="false" depth="0" />
+
+ <!-- Snapshot of previous version -->
+ <snapshots purge_status="[null]"
+ period1_mode="[null]" period1_param="[null]" period1_date="[null]"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="[null]" period3_param="[null]" period3_date="[null]"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ id="300" project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ scope="PRJ" qualifier="TRK" created_at="1229173080000" build_date="1229173080000" version="1.2.3" path=""
+ status="P" islast="false" depth="0" />
+
+ <snapshots purge_status="[null]"
+ period1_mode="days" period1_param="30" period1_date="1225544280000"
+ period2_mode="[null]" period2_param="[null]" period2_date="[null]"
+ period3_mode="version" period3_param="1.2.3" period3_date="1229173080000"
+ period4_mode="[null]" period4_param="[null]" period4_date="[null]"
+ period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ id="1000" project_id="1" parent_snapshot_id="[null]" root_project_id="1" root_snapshot_id="[null]"
+ scope="PRJ" qualifier="TRK" created_at="1235566680000" build_date="1235566680000" version="[null]" path=""
+ status="P" islast="false" depth="0" />
+</dataset>
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/compute/ViolationPersisterDecoratorTest/shared.xml b/sonar-batch/src/test/resources/org/sonar/batch/compute/ViolationPersisterDecoratorTest/shared.xml
new file mode 100644
index 00000000000..519ce8d5157
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/compute/ViolationPersisterDecoratorTest/shared.xml
@@ -0,0 +1,21 @@
+<dataset>
+
+ <rules tags="[null]" system_tags="[null]" id="30" name="Check Header" plugin_rule_key="com.puppycrawl.tools.checkstyle.checks.header.HeaderCheck"
+ plugin_config_key="Checker/Treewalker/HeaderCheck" plugin_name="checkstyle" description="[null]" priority="4" status="READY"
+ is_template="[false]" template_id="[null]"/>
+
+ <rules tags="[null]" system_tags="[null]" id="31" name="Equals Avoid Null" plugin_rule_key="com.puppycrawl.tools.checkstyle.checks.coding.EqualsAvoidNullCheck"
+ plugin_config_key="Checker/TreeWalker/EqualsAvoidNull" plugin_name="checkstyle" description="[null]" priority="4" status="READY"
+ is_template="[false]" template_id="[null]"/>
+
+ <projects id="200" scope="FIL" qualifier="CLA" kee="project:org.foo.Bar" root_id="[null]"
+ name="Bar" long_name="org.foo.Bar" description="[null]"
+ enabled="true" language="java" copy_resource_id="[null]" person_id="[null]" />
+
+ <snapshots purge_status="[null]" period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]"
+ period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]"
+ id="1000" project_id="200" parent_snapshot_id="[null]" root_project_id="100" root_snapshot_id="[null]"
+ scope="FIL" qualifier="CLA" created_at="1225544280000" build_date="1225544280000" version="[null]" path=""
+ status="U" islast="false" depth="3"/>
+
+</dataset>
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/compute/ViolationPersisterDecoratorTest/shouldCopyPermanentIdFromReferenceViolation-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/compute/ViolationPersisterDecoratorTest/shouldCopyPermanentIdFromReferenceViolation-result.xml
new file mode 100644
index 00000000000..061041849aa
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/compute/ViolationPersisterDecoratorTest/shouldCopyPermanentIdFromReferenceViolation-result.xml
@@ -0,0 +1,21 @@
+<dataset>
+
+ <rules tags="[null]" system_tags="[null]" id="30" name="Check Header" plugin_rule_key="com.puppycrawl.tools.checkstyle.checks.header.HeaderCheck"
+ plugin_config_key="Checker/Treewalker/HeaderCheck" plugin_name="checkstyle" description="[null]" priority="4" status="READY"
+ is_template="[false]" template_id="[null]"/>
+
+ <rules tags="[null]" system_tags="[null]" id="31" name="Equals Avoid Null" plugin_rule_key="com.puppycrawl.tools.checkstyle.checks.coding.EqualsAvoidNullCheck"
+ plugin_config_key="Checker/TreeWalker/EqualsAvoidNull" plugin_name="checkstyle" description="[null]" priority="4" status="READY"
+ is_template="[false]" template_id="[null]"/>
+
+ <projects id="200" scope="FIL" qualifier="CLA" kee="project:org.foo.Bar" root_id="[null]"
+ name="Bar" long_name="org.foo.Bar" description="[null]"
+ enabled="true" language="java" copy_resource_id="[null]" person_id="[null]" />
+
+ <snapshots purge_status="[null]" period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]"
+ period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" id="1000" project_id="200"
+ parent_snapshot_id="[null]" root_project_id="100" root_snapshot_id="[null]"
+ scope="FIL" qualifier="CLA" created_at="1225544280000" build_date="1225544280000" version="[null]" path=""
+ status="U" islast="false" depth="3"/>
+
+</dataset>
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/compute/ViolationPersisterDecoratorTest/shouldSaveViolations-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/compute/ViolationPersisterDecoratorTest/shouldSaveViolations-result.xml
new file mode 100644
index 00000000000..f1bbc0bda6f
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/compute/ViolationPersisterDecoratorTest/shouldSaveViolations-result.xml
@@ -0,0 +1,20 @@
+<dataset>
+ <rules tags="[null]" system_tags="[null]" id="30" name="Check Header" plugin_rule_key="com.puppycrawl.tools.checkstyle.checks.header.HeaderCheck"
+ plugin_config_key="Checker/Treewalker/HeaderCheck" plugin_name="checkstyle" description="[null]" priority="4" status="READY"
+ is_template="[false]" template_id="[null]"/>
+
+ <rules tags="[null]" system_tags="[null]" id="31" name="Equals Avoid Null" plugin_rule_key="com.puppycrawl.tools.checkstyle.checks.coding.EqualsAvoidNullCheck"
+ plugin_config_key="Checker/TreeWalker/EqualsAvoidNull" plugin_name="checkstyle" description="[null]" priority="4" status="READY"
+ is_template="[false]" template_id="[null]"/>
+
+ <projects id="200" scope="FIL" qualifier="CLA" kee="project:org.foo.Bar" root_id="[null]"
+ name="Bar" long_name="org.foo.Bar" description="[null]"
+ enabled="true" language="java" copy_resource_id="[null]" person_id="[null]" />
+
+ <snapshots purge_status="[null]" period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]"
+ period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" id="1000" project_id="200"
+ parent_snapshot_id="[null]" root_project_id="100" root_snapshot_id="[null]"
+ scope="FIL" qualifier="CLA" created_at="1225544280000" build_date="1225544280000" version="[null]" path=""
+ status="U" islast="false" depth="3"/>
+
+</dataset>
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/compute/ViolationTrackingTest/example1-v1.txt b/sonar-batch/src/test/resources/org/sonar/batch/compute/ViolationTrackingTest/example1-v1.txt
new file mode 100644
index 00000000000..1920333ddb6
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/compute/ViolationTrackingTest/example1-v1.txt
@@ -0,0 +1,12 @@
+package example1;
+
+public class Toto {
+
+ public void doSomething() {
+ // doSomething
+ }
+
+ public void doSomethingElse() {
+ // doSomethingElse
+ }
+}
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/compute/ViolationTrackingTest/example1-v2.txt b/sonar-batch/src/test/resources/org/sonar/batch/compute/ViolationTrackingTest/example1-v2.txt
new file mode 100644
index 00000000000..231532452b2
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/compute/ViolationTrackingTest/example1-v2.txt
@@ -0,0 +1,22 @@
+package example1;
+
+public class Toto {
+
+ public Toto(){}
+
+ public void doSomethingNew() {
+ // doSomethingNew
+ }
+
+ public void doSomethingElseNew() {
+ // doSomethingElseNew
+ }
+
+ public void doSomething() {
+ // doSomething
+ }
+
+ public void doSomethingElse() {
+ // doSomethingElse
+ }
+}
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/compute/ViolationTrackingTest/example2-v1.txt b/sonar-batch/src/test/resources/org/sonar/batch/compute/ViolationTrackingTest/example2-v1.txt
new file mode 100644
index 00000000000..a920afe459b
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/compute/ViolationTrackingTest/example2-v1.txt
@@ -0,0 +1,7 @@
+package example2;
+
+public class Toto {
+ void method1() {
+ System.out.println("toto");
+ }
+}
diff --git a/sonar-batch/src/test/resources/org/sonar/batch/compute/ViolationTrackingTest/example2-v2.txt b/sonar-batch/src/test/resources/org/sonar/batch/compute/ViolationTrackingTest/example2-v2.txt
new file mode 100644
index 00000000000..c5c8250cf65
--- /dev/null
+++ b/sonar-batch/src/test/resources/org/sonar/batch/compute/ViolationTrackingTest/example2-v2.txt
@@ -0,0 +1,16 @@
+package example2;
+
+public class Toto {
+
+ void method2() {
+ System.out.println("toto");
+ }
+
+ void method1() {
+ System.out.println("toto");
+ }
+
+ void method3() {
+ System.out.println("toto");
+ }
+}