]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-926 fix loading of quality profile alerts on multi-modules projects
authorSimon Brandhof <simon.brandhof@gmail.com>
Wed, 26 Feb 2014 14:25:11 +0000 (15:25 +0100)
committerSimon Brandhof <simon.brandhof@gmail.com>
Wed, 26 Feb 2014 14:38:39 +0000 (15:38 +0100)
26 files changed:
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/AlertUtils.java [deleted file]
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/CheckAlertThresholds.java [deleted file]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/AlertUtilsTest.java [deleted file]
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/CheckAlertThresholdsTest.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/fs/package-info.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/phases/PhaseExecutor.java
sonar-batch/src/main/java/org/sonar/batch/phases/ProfileLogger.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/qualitygate/AlertUtils.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/qualitygate/ProjectAlerts.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/qualitygate/QualityGateLoader.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/qualitygate/QualityGateVerifier.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/qualitygate/package-info.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/rule/ProjectAlerts.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/rule/QProfileVerifier.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/scan/LanguageVerifier.java
sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java
sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java
sonar-batch/src/test/java/org/sonar/batch/phases/ProfileLoggerTest.java [deleted file]
sonar-batch/src/test/java/org/sonar/batch/qualitygate/AlertUtilsTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/qualitygate/ProjectAlertsTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/qualitygate/QualityGateLoaderTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/qualitygate/QualityGateVerifierTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/rule/QProfileVerifierTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/scan/LanguageVerifierTest.java
sonar-plugin-api/src/main/java/org/sonar/api/profiles/Alert.java

index 0d2bd21ac5efcc08c6c48af545c20f420f0493db..0031a119e2ec0f8cccbdfbc7b8e01c6502d69ef6 100644 (file)
  */
 package org.sonar.plugins.core;
 
-import org.sonar.batch.issue.ignore.IssueExclusionsConfiguration;
-
 import com.google.common.collect.ImmutableList;
-import org.sonar.api.CoreProperties;
-import org.sonar.api.Properties;
-import org.sonar.api.Property;
-import org.sonar.api.PropertyType;
-import org.sonar.api.SonarPlugin;
+import org.sonar.api.*;
 import org.sonar.api.checks.NoSonarFilter;
 import org.sonar.api.config.PropertyDefinition;
 import org.sonar.api.resources.Qualifiers;
 import org.sonar.api.utils.WorkDurationFactory;
 import org.sonar.batch.components.PastSnapshotFinder;
 import org.sonar.batch.debt.IssueChangelogDebtCalculator;
+import org.sonar.batch.issue.ignore.IssueExclusionsConfiguration;
 import org.sonar.core.timemachine.Periods;
 import org.sonar.plugins.core.batch.IndexProjectPostJob;
 import org.sonar.plugins.core.charts.DistributionAreaChart;
 import org.sonar.plugins.core.charts.DistributionBarChart;
 import org.sonar.plugins.core.charts.XradarChart;
 import org.sonar.plugins.core.colorizers.JavaColorizerFormat;
-import org.sonar.plugins.core.dashboards.GlobalDefaultDashboard;
-import org.sonar.plugins.core.dashboards.ProjectDefaultDashboard;
-import org.sonar.plugins.core.dashboards.ProjectHotspotDashboard;
-import org.sonar.plugins.core.dashboards.ProjectIssuesDashboard;
-import org.sonar.plugins.core.dashboards.ProjectTimeMachineDashboard;
-import org.sonar.plugins.core.issue.CountFalsePositivesDecorator;
-import org.sonar.plugins.core.issue.CountUnresolvedIssuesDecorator;
-import org.sonar.plugins.core.issue.InitialOpenIssuesSensor;
-import org.sonar.plugins.core.issue.InitialOpenIssuesStack;
-import org.sonar.plugins.core.issue.IssueHandlers;
-import org.sonar.plugins.core.issue.IssueTracking;
-import org.sonar.plugins.core.issue.IssueTrackingDecorator;
-import org.sonar.plugins.core.issue.IssuesDensityDecorator;
-import org.sonar.plugins.core.issue.WeightedIssuesDecorator;
-import org.sonar.plugins.core.issue.notification.ChangesOnMyIssueNotificationDispatcher;
-import org.sonar.plugins.core.issue.notification.IssueChangesEmailTemplate;
-import org.sonar.plugins.core.issue.notification.NewFalsePositiveNotificationDispatcher;
-import org.sonar.plugins.core.issue.notification.NewIssuesEmailTemplate;
-import org.sonar.plugins.core.issue.notification.NewIssuesNotificationDispatcher;
-import org.sonar.plugins.core.issue.notification.SendIssueNotificationsPostJob;
+import org.sonar.plugins.core.dashboards.*;
+import org.sonar.plugins.core.issue.*;
+import org.sonar.plugins.core.issue.notification.*;
 import org.sonar.plugins.core.measurefilters.MyFavouritesFilter;
 import org.sonar.plugins.core.measurefilters.ProjectFilter;
 import org.sonar.plugins.core.notifications.alerts.NewAlerts;
 import org.sonar.plugins.core.security.ApplyProjectRolesDecorator;
-import org.sonar.plugins.core.sensors.BranchCoverageDecorator;
-import org.sonar.plugins.core.sensors.CheckAlertThresholds;
-import org.sonar.plugins.core.sensors.CommentDensityDecorator;
-import org.sonar.plugins.core.sensors.CoverageDecorator;
-import org.sonar.plugins.core.sensors.CoverageMeasurementFilter;
-import org.sonar.plugins.core.sensors.DirectoriesDecorator;
-import org.sonar.plugins.core.sensors.FileHashSensor;
-import org.sonar.plugins.core.sensors.FilesDecorator;
-import org.sonar.plugins.core.sensors.GenerateAlertEvents;
-import org.sonar.plugins.core.sensors.ItBranchCoverageDecorator;
-import org.sonar.plugins.core.sensors.ItCoverageDecorator;
-import org.sonar.plugins.core.sensors.ItLineCoverageDecorator;
-import org.sonar.plugins.core.sensors.LineCoverageDecorator;
-import org.sonar.plugins.core.sensors.ManualMeasureDecorator;
-import org.sonar.plugins.core.sensors.OverallBranchCoverageDecorator;
-import org.sonar.plugins.core.sensors.OverallCoverageDecorator;
-import org.sonar.plugins.core.sensors.OverallLineCoverageDecorator;
-import org.sonar.plugins.core.sensors.ProfileEventsSensor;
-import org.sonar.plugins.core.sensors.ProjectLinksSensor;
-import org.sonar.plugins.core.sensors.UnitTestDecorator;
-import org.sonar.plugins.core.sensors.VersionEventsSensor;
+import org.sonar.plugins.core.sensors.*;
 import org.sonar.plugins.core.technicaldebt.NewTechnicalDebtDecorator;
 import org.sonar.plugins.core.technicaldebt.TechnicalDebtDecorator;
-import org.sonar.plugins.core.timemachine.NewCoverageAggregator;
-import org.sonar.plugins.core.timemachine.NewCoverageFileAnalyzer;
-import org.sonar.plugins.core.timemachine.NewItCoverageFileAnalyzer;
-import org.sonar.plugins.core.timemachine.NewOverallCoverageFileAnalyzer;
-import org.sonar.plugins.core.timemachine.TendencyDecorator;
-import org.sonar.plugins.core.timemachine.TimeMachineConfigurationPersister;
-import org.sonar.plugins.core.timemachine.VariationDecorator;
+import org.sonar.plugins.core.timemachine.*;
 import org.sonar.plugins.core.web.TestsViewer;
-import org.sonar.plugins.core.widgets.AlertsWidget;
-import org.sonar.plugins.core.widgets.BubbleChartWidget;
-import org.sonar.plugins.core.widgets.ComplexityWidget;
-import org.sonar.plugins.core.widgets.CoverageWidget;
-import org.sonar.plugins.core.widgets.CustomMeasuresWidget;
-import org.sonar.plugins.core.widgets.DescriptionWidget;
-import org.sonar.plugins.core.widgets.DocumentationCommentsWidget;
-import org.sonar.plugins.core.widgets.DuplicationsWidget;
-import org.sonar.plugins.core.widgets.EventsWidget;
-import org.sonar.plugins.core.widgets.HotspotMetricWidget;
-import org.sonar.plugins.core.widgets.HotspotMostViolatedResourcesWidget;
-import org.sonar.plugins.core.widgets.HotspotMostViolatedRulesWidget;
-import org.sonar.plugins.core.widgets.ItCoverageWidget;
-import org.sonar.plugins.core.widgets.SizeWidget;
-import org.sonar.plugins.core.widgets.TechnicalDebtPyramidWidget;
-import org.sonar.plugins.core.widgets.TimeMachineWidget;
-import org.sonar.plugins.core.widgets.TimelineWidget;
-import org.sonar.plugins.core.widgets.TreemapWidget;
-import org.sonar.plugins.core.widgets.WelcomeWidget;
-import org.sonar.plugins.core.widgets.issues.ActionPlansWidget;
-import org.sonar.plugins.core.widgets.issues.FalsePositiveIssuesWidget;
-import org.sonar.plugins.core.widgets.issues.IssueFilterWidget;
-import org.sonar.plugins.core.widgets.issues.IssuesWidget;
-import org.sonar.plugins.core.widgets.issues.MyUnresolvedIssuesWidget;
-import org.sonar.plugins.core.widgets.issues.UnresolvedIssuesPerAssigneeWidget;
-import org.sonar.plugins.core.widgets.issues.UnresolvedIssuesStatusesWidget;
-import org.sonar.plugins.core.widgets.measures.MeasureFilterAsBubbleChartWidget;
-import org.sonar.plugins.core.widgets.measures.MeasureFilterAsHistogramWidget;
-import org.sonar.plugins.core.widgets.measures.MeasureFilterAsPieChartWidget;
-import org.sonar.plugins.core.widgets.measures.MeasureFilterListWidget;
-import org.sonar.plugins.core.widgets.measures.MeasureFilterTreemapWidget;
+import org.sonar.plugins.core.widgets.*;
+import org.sonar.plugins.core.widgets.issues.*;
+import org.sonar.plugins.core.widgets.measures.*;
 
 import java.util.Arrays;
 import java.util.List;
@@ -378,7 +302,6 @@ public final class CorePlugin extends SonarPlugin {
       ProjectLinksSensor.class,
       UnitTestDecorator.class,
       VersionEventsSensor.class,
-      CheckAlertThresholds.class,
       GenerateAlertEvents.class,
       LineCoverageDecorator.class,
       CoverageDecorator.class,
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/AlertUtils.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/AlertUtils.java
deleted file mode 100644 (file)
index 61e6967..0000000
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 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.plugins.core.sensors;
-
-import org.apache.commons.lang.NotImplementedException;
-import org.apache.commons.lang.StringUtils;
-import org.sonar.api.measures.Measure;
-import org.sonar.api.measures.Metric;
-import org.sonar.api.profiles.Alert;
-
-public final class AlertUtils {
-
-  private AlertUtils() {
-  }
-
-  /**
-   * Get the matching alert level for the given measure
-   */
-  public static Metric.Level getLevel(Alert alert, Measure measure) {
-    if (evaluateAlert(alert, measure, Metric.Level.ERROR)) {
-      return Metric.Level.ERROR;
-    }
-    if (evaluateAlert(alert, measure, Metric.Level.WARN)) {
-      return Metric.Level.WARN;
-    }
-    return Metric.Level.OK;
-  }
-
-  private static boolean evaluateAlert(Alert alert, Measure measure, Metric.Level alertLevel) {
-    String valueToEval = getValueToEval(alert, alertLevel);
-    if (StringUtils.isEmpty(valueToEval)) {
-      return false;
-    }
-
-    Comparable criteriaValue = getValueForComparison(alert.getMetric(), valueToEval);
-    Comparable measureValue = getMeasureValue(alert, measure);
-    if (measureValue != null) {
-      return doesReachThresholds(measureValue, criteriaValue, alert);
-    }
-    return false;
-  }
-
-  private static boolean doesReachThresholds(Comparable measureValue, Comparable criteriaValue, Alert alert) {
-    int comparison = measureValue.compareTo(criteriaValue);
-    return !(isNotEquals(comparison, alert)
-        || isGreater(comparison, alert)
-        || isSmaller(comparison, alert)
-        || isEquals(comparison, alert));
-  }
-
-  private static boolean isNotEquals(int comparison, Alert alert) {
-    return alert.isNotEqualsOperator() && comparison == 0;
-  }
-
-  private static boolean isGreater(int comparison, Alert alert) {
-    return alert.isGreaterOperator() && comparison != 1;
-  }
-
-  private static boolean isSmaller(int comparison, Alert alert) {
-    return alert.isSmallerOperator() && comparison != -1;
-  }
-
-  private static boolean isEquals(int comparison, Alert alert) {
-    return alert.isEqualsOperator() && comparison != 0;
-  }
-
-  private static String getValueToEval(Alert alert, Metric.Level alertLevel) {
-    if (alertLevel.equals(Metric.Level.ERROR)) {
-      return alert.getValueError();
-    } else if (alertLevel.equals(Metric.Level.WARN)) {
-      return alert.getValueWarning();
-    } else {
-      throw new IllegalStateException(alertLevel.toString());
-    }
-  }
-
-  private static Comparable getValueForComparison(Metric metric, String value) {
-    if (isADouble(metric)) {
-      return Double.parseDouble(value);
-    }
-    if (isAInteger(metric)) {
-      return parseInteger(value);
-    }
-    if (isAString(metric)) {
-      return value;
-    }
-    if (isABoolean(metric)) {
-      return Integer.parseInt(value);
-    }
-    throw new NotImplementedException(metric.getType().toString());
-  }
-
-  private static Comparable<Integer> parseInteger(String value) {
-    return value.contains(".") ? Integer.parseInt(value.substring(0, value.indexOf('.'))) : Integer.parseInt(value);
-  }
-
-  private static Comparable getMeasureValue(Alert alert, Measure measure) {
-    Metric metric = alert.getMetric();
-    if (isADouble(metric)) {
-      return getValue(alert, measure);
-    }
-    if (isAInteger(metric)) {
-      return parseInteger(alert, measure);
-    }
-    if (alert.getPeriod() == null) {
-      return getMeasureValueForStringOrBoolean(metric, measure);
-    }
-    throw new NotImplementedException(metric.getType().toString());
-  }
-
-  private static Comparable getMeasureValueForStringOrBoolean(Metric metric, Measure measure) {
-    if (isAString(metric)) {
-      return measure.getData();
-    }
-    if (isABoolean(metric)) {
-      return measure.getValue().intValue();
-    }
-    throw new NotImplementedException(metric.getType().toString());
-  }
-
-  private static Comparable<Integer> parseInteger(Alert alert, Measure measure) {
-    Double value = getValue(alert, measure);
-    return value != null ? value.intValue() : null;
-  }
-
-  private static boolean isADouble(Metric metric) {
-    return metric.getType() == Metric.ValueType.FLOAT ||
-        metric.getType() == Metric.ValueType.PERCENT ||
-        metric.getType() == Metric.ValueType.RATING;
-  }
-
-  private static boolean isAInteger(Metric metric) {
-    return metric.getType() == Metric.ValueType.INT ||
-        metric.getType() == Metric.ValueType.MILLISEC;
-  }
-
-  private static boolean isAString(Metric metric) {
-    return metric.getType() == Metric.ValueType.STRING ||
-        metric.getType() == Metric.ValueType.LEVEL;
-  }
-
-  private static boolean isABoolean(Metric metric) {
-    return metric.getType() == Metric.ValueType.BOOL;
-  }
-
-  private static Double getValue(Alert alert, Measure measure) {
-    if (alert.getPeriod() == null) {
-      return measure.getValue();
-    } else if (alert.getPeriod() == 1) {
-      return measure.getVariation1();
-    } else if (alert.getPeriod() == 2) {
-      return measure.getVariation2();
-    } else if (alert.getPeriod() == 3) {
-      return measure.getVariation3();
-    } else {
-      throw new IllegalStateException("Following index period is not allowed : " + Double.toString(alert.getPeriod()));
-    }
-  }
-}
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/CheckAlertThresholds.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/CheckAlertThresholds.java
deleted file mode 100644 (file)
index 1580d14..0000000
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 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.plugins.core.sensors;
-
-import com.google.common.collect.Lists;
-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.database.model.Snapshot;
-import org.sonar.api.i18n.I18n;
-import org.sonar.api.measures.CoreMetrics;
-import org.sonar.api.measures.Measure;
-import org.sonar.api.measures.Metric;
-import org.sonar.api.profiles.Alert;
-import org.sonar.api.resources.Project;
-import org.sonar.api.resources.Resource;
-import org.sonar.api.resources.ResourceUtils;
-import org.sonar.batch.rule.ProjectAlerts;
-import org.sonar.core.timemachine.Periods;
-
-import java.util.List;
-import java.util.Locale;
-
-public class CheckAlertThresholds implements Decorator {
-
-  private static final String VARIATION_METRIC_PREFIX = "new_";
-  private static final String VARIATION = "variation";
-
-  private final Snapshot snapshot;
-  private final Periods periods;
-  private final I18n i18n;
-  private ProjectAlerts projectAlerts;
-
-  public CheckAlertThresholds(Snapshot snapshot, ProjectAlerts projectAlerts, Periods periods, I18n i18n) {
-    this.snapshot = snapshot;
-    this.projectAlerts = projectAlerts;
-    this.periods = periods;
-    this.i18n = i18n;
-  }
-
-  @DependedUpon
-  public Metric generatesAlertStatus() {
-    return CoreMetrics.ALERT_STATUS;
-  }
-
-  @DependsUpon
-  public String dependsOnVariations() {
-    return DecoratorBarriers.END_OF_TIME_MACHINE;
-  }
-
-  @DependsUpon
-  public List<Metric> dependsUponMetrics() {
-    List<Metric> metrics = Lists.newLinkedList();
-    for (Alert alert : projectAlerts.all()) {
-      metrics.add(alert.getMetric());
-    }
-    return metrics;
-  }
-
-  public boolean shouldExecuteOnProject(Project project) {
-    return !projectAlerts.all().isEmpty()
-      && ResourceUtils.isRootProject(project);
-  }
-
-  public void decorate(final Resource resource, final DecoratorContext context) {
-    if (shouldDecorateResource(resource)) {
-      decorateResource(context);
-    }
-  }
-
-  private void decorateResource(DecoratorContext context) {
-    Metric.Level globalLevel = Metric.Level.OK;
-    List<String> labels = Lists.newArrayList();
-
-    for (final Alert alert : projectAlerts.all()) {
-      Measure measure = context.getMeasure(alert.getMetric());
-      if (measure != null) {
-        Metric.Level level = AlertUtils.getLevel(alert, measure);
-
-        measure.setAlertStatus(level);
-        String text = getText(alert, level);
-        if (!StringUtils.isBlank(text)) {
-          measure.setAlertText(text);
-          labels.add(text);
-        }
-
-        context.saveMeasure(measure);
-
-        if (Metric.Level.WARN == level && globalLevel != Metric.Level.ERROR) {
-          globalLevel = Metric.Level.WARN;
-
-        } else if (Metric.Level.ERROR == level) {
-          globalLevel = Metric.Level.ERROR;
-        }
-      }
-    }
-
-    Measure globalMeasure = new Measure(CoreMetrics.ALERT_STATUS, globalLevel);
-    globalMeasure.setAlertStatus(globalLevel);
-    globalMeasure.setAlertText(StringUtils.join(labels, ", "));
-    context.saveMeasure(globalMeasure);
-  }
-
-  private boolean shouldDecorateResource(final Resource resource) {
-    return ResourceUtils.isRootProject(resource);
-  }
-
-  private String getText(Alert alert, Metric.Level level) {
-    if (level == Metric.Level.OK) {
-      return null;
-    }
-    return getAlertLabel(alert, level);
-  }
-
-  private String getAlertLabel(Alert alert, Metric.Level level) {
-    Integer alertPeriod = alert.getPeriod();
-    String metric = i18n.message(getLocale(), "metric." + alert.getMetric().getKey() + ".name", alert.getMetric().getName());
-
-    StringBuilder stringBuilder = new StringBuilder();
-    stringBuilder.append(metric);
-
-    if (alertPeriod != null && !alert.getMetric().getKey().startsWith(VARIATION_METRIC_PREFIX)) {
-      String variation = i18n.message(getLocale(), VARIATION, VARIATION).toLowerCase();
-      stringBuilder.append(" ").append(variation);
-    }
-
-    stringBuilder
-      .append(" ").append(alert.getOperator()).append(" ")
-      .append(level.equals(Metric.Level.ERROR) ? alert.getValueError() : alert.getValueWarning());
-
-    if (alertPeriod != null) {
-      stringBuilder.append(" ").append(periods.label(snapshot, alertPeriod));
-    }
-
-    return stringBuilder.toString();
-  }
-
-  @Override
-  public String toString() {
-    return getClass().getSimpleName();
-  }
-
-  private Locale getLocale() {
-    return Locale.ENGLISH;
-  }
-
-}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/AlertUtilsTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/AlertUtilsTest.java
deleted file mode 100644 (file)
index 4856c53..0000000
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 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.plugins.core.sensors;
-
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.api.measures.Measure;
-import org.sonar.api.measures.Metric;
-import org.sonar.api.profiles.Alert;
-
-public class AlertUtilsTest {
-  private Metric metric;
-  private Measure measure;
-  private Metric metric2;
-  private Measure measure2;
-  private Alert alert;
-
-  @Before
-  public void setup() {
-    metric = new Metric.Builder("test-metric", "name", Metric.ValueType.FLOAT).create();
-    measure = new Measure();
-    measure.setMetric(metric);
-
-    metric2 = new Metric.Builder("test-metric2", "name2", Metric.ValueType.FLOAT).create();
-    measure2 = new Measure();
-    measure2.setMetric(metric2);
-
-    alert = new Alert();
-  }
-
-  @Test
-  public void testInputNumbers() {
-    metric.setType(Metric.ValueType.FLOAT);
-    measure.setValue(10.2d);
-    alert.setOperator(Alert.OPERATOR_SMALLER);
-    alert.setMetric(metric);
-
-    try {
-      metric.setType(Metric.ValueType.FLOAT);
-      alert.setValueError("20");
-      AlertUtils.getLevel(alert, measure);
-    } catch (NumberFormatException ex) {
-      Assert.fail();
-    }
-
-    try {
-      metric.setType(Metric.ValueType.INT);
-      alert.setValueError("20.1");
-      AlertUtils.getLevel(alert, measure);
-    } catch (NumberFormatException ex) {
-      Assert.fail();
-    }
-
-    try {
-      metric.setType(Metric.ValueType.PERCENT);
-      alert.setValueError("20.1");
-      AlertUtils.getLevel(alert, measure);
-    } catch (NumberFormatException ex) {
-      Assert.fail();
-    }
-  }
-
-  @Test
-  public void testEquals() {
-
-    metric.setType(Metric.ValueType.FLOAT);
-    measure.setValue(10.2d);
-    alert.setOperator(Alert.OPERATOR_EQUALS);
-    alert.setMetric(metric);
-
-    alert.setValueError("10.2");
-    Assert.assertEquals(Metric.Level.ERROR, AlertUtils.getLevel(alert, measure));
-
-    alert.setValueError("10.1");
-    Assert.assertEquals(Metric.Level.OK, AlertUtils.getLevel(alert, measure));
-
-    metric.setType(Metric.ValueType.STRING);
-    measure.setData("TEST");
-    measure.setValue(null);
-
-    alert.setValueError("TEST");
-    Assert.assertEquals(Metric.Level.ERROR, AlertUtils.getLevel(alert, measure));
-
-    alert.setValueError("TEST2");
-    Assert.assertEquals(Metric.Level.OK, AlertUtils.getLevel(alert, measure));
-
-  }
-
-  @Test
-  public void testNotEquals() {
-
-    metric.setType(Metric.ValueType.FLOAT);
-    measure.setValue(10.2d);
-    alert.setOperator(Alert.OPERATOR_NOT_EQUALS);
-    alert.setMetric(metric);
-
-    alert.setValueError("10.2");
-    Assert.assertEquals(Metric.Level.OK, AlertUtils.getLevel(alert, measure));
-
-    alert.setValueError("10.1");
-    Assert.assertEquals(Metric.Level.ERROR, AlertUtils.getLevel(alert, measure));
-
-    metric.setType(Metric.ValueType.STRING);
-    measure.setData("TEST");
-    measure.setValue(null);
-
-    alert.setValueError("TEST");
-    Assert.assertEquals(Metric.Level.OK, AlertUtils.getLevel(alert, measure));
-
-    alert.setValueError("TEST2");
-    Assert.assertEquals(Metric.Level.ERROR, AlertUtils.getLevel(alert, measure));
-
-  }
-
-  @Test
-  public void testGreater() {
-    metric.setType(Metric.ValueType.FLOAT);
-    measure.setValue(10.2d);
-    alert.setOperator(Alert.OPERATOR_GREATER);
-    alert.setMetric(metric);
-
-    alert.setValueError("10.1");
-    Assert.assertEquals(Metric.Level.ERROR, AlertUtils.getLevel(alert, measure));
-
-    alert.setValueError("10.3");
-    Assert.assertEquals(Metric.Level.OK, AlertUtils.getLevel(alert, measure));
-  }
-
-  @Test
-  public void testSmaller() {
-    metric.setType(Metric.ValueType.FLOAT);
-    measure.setValue(10.2d);
-    alert.setOperator(Alert.OPERATOR_SMALLER);
-    alert.setMetric(metric);
-
-    alert.setValueError("10.1");
-    Assert.assertEquals(Metric.Level.OK, AlertUtils.getLevel(alert, measure));
-
-    alert.setValueError("10.3");
-    Assert.assertEquals(Metric.Level.ERROR, AlertUtils.getLevel(alert, measure));
-  }
-
-  @Test
-  public void testPercent() {
-    metric.setType(Metric.ValueType.PERCENT);
-    measure.setValue(10.2d);
-    alert.setOperator(Alert.OPERATOR_EQUALS);
-    alert.setMetric(metric);
-
-    alert.setValueError("10.2");
-    Assert.assertEquals(Metric.Level.ERROR, AlertUtils.getLevel(alert, measure));
-  }
-
-  @Test
-  public void testFloat() {
-    metric.setType(Metric.ValueType.FLOAT);
-    measure.setValue(10.2d);
-    alert.setOperator(Alert.OPERATOR_EQUALS);
-    alert.setMetric(metric);
-
-    alert.setValueError("10.2");
-    Assert.assertEquals(Metric.Level.ERROR, AlertUtils.getLevel(alert, measure));
-  }
-
-  @Test
-  public void testInteger() {
-    metric.setType(Metric.ValueType.INT);
-    measure.setValue(10.2d);
-    alert.setOperator(Alert.OPERATOR_EQUALS);
-    alert.setMetric(metric);
-
-    alert.setValueError("10");
-    Assert.assertEquals(Metric.Level.ERROR, AlertUtils.getLevel(alert, measure));
-
-    alert.setValueError("10.2");
-    Assert.assertEquals(Metric.Level.ERROR, AlertUtils.getLevel(alert, measure));
-  }
-
-  @Test
-  public void testLevel() {
-    metric.setType(Metric.ValueType.LEVEL);
-    measure.setData(Metric.Level.ERROR.toString());
-    alert.setOperator(Alert.OPERATOR_EQUALS);
-    alert.setMetric(metric);
-
-    alert.setValueError(Metric.Level.ERROR.toString());
-    Assert.assertEquals(Metric.Level.ERROR, AlertUtils.getLevel(alert, measure));
-
-    alert.setValueError(Metric.Level.OK.toString());
-    Assert.assertEquals(Metric.Level.OK, AlertUtils.getLevel(alert, measure));
-
-    alert.setOperator(Alert.OPERATOR_NOT_EQUALS);
-    Assert.assertEquals(Metric.Level.ERROR, AlertUtils.getLevel(alert, measure));
-  }
-
-  @Test
-  public void testBooleans() {
-    metric.setType(Metric.ValueType.BOOL);
-    measure.setValue(0d);
-    alert.setOperator(Alert.OPERATOR_EQUALS);
-    alert.setMetric(metric);
-
-    alert.setValueError("1");
-    Assert.assertEquals(Metric.Level.OK, AlertUtils.getLevel(alert, measure));
-
-    alert.setValueError("0");
-    Assert.assertEquals(Metric.Level.ERROR, AlertUtils.getLevel(alert, measure));
-
-    alert.setOperator(Alert.OPERATOR_NOT_EQUALS);
-    alert.setValueError("1");
-    Assert.assertEquals(Metric.Level.ERROR, AlertUtils.getLevel(alert, measure));
-
-    alert.setValueError("0");
-    Assert.assertEquals(Metric.Level.OK, AlertUtils.getLevel(alert, measure));
-  }
-
-  @Test
-  public void testErrorAndWarningLevel() {
-
-    metric.setType(Metric.ValueType.FLOAT);
-    measure.setValue(10.2d);
-    alert.setOperator(Alert.OPERATOR_EQUALS);
-    alert.setMetric(metric);
-
-    alert.setValueError("10.2");
-    Assert.assertEquals(Metric.Level.ERROR, AlertUtils.getLevel(alert, measure));
-
-    alert.setValueError("10.1");
-    Assert.assertEquals(Metric.Level.OK, AlertUtils.getLevel(alert, measure));
-
-    alert.setValueError("10.3");
-    alert.setValueWarning("10.2");
-    Assert.assertEquals(Metric.Level.WARN, AlertUtils.getLevel(alert, measure));
-  }
-}
diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/CheckAlertThresholdsTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/CheckAlertThresholdsTest.java
deleted file mode 100644 (file)
index 6154147..0000000
+++ /dev/null
@@ -1,374 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 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.plugins.core.sensors;
-
-import org.apache.commons.lang.NotImplementedException;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentMatcher;
-import org.sonar.api.batch.DecoratorBarriers;
-import org.sonar.api.batch.DecoratorContext;
-import org.sonar.api.database.model.Snapshot;
-import org.sonar.api.i18n.I18n;
-import org.sonar.api.measures.CoreMetrics;
-import org.sonar.api.measures.Measure;
-import org.sonar.api.measures.Metric;
-import org.sonar.api.profiles.Alert;
-import org.sonar.api.resources.Project;
-import org.sonar.api.resources.Qualifiers;
-import org.sonar.api.resources.Resource;
-import org.sonar.api.test.IsMeasure;
-import org.sonar.batch.rule.ProjectAlerts;
-import org.sonar.core.timemachine.Periods;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Locale;
-
-import static com.google.common.collect.Lists.newArrayList;
-import static org.fest.assertions.Assertions.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.argThat;
-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 CheckAlertThresholdsTest {
-
-  private CheckAlertThresholds decorator;
-  private DecoratorContext context;
-  private ProjectAlerts projectAlerts;
-
-  private Measure measureClasses;
-  private Measure measureCoverage;
-  private Measure measureComplexity;
-
-  private Resource project;
-  private Snapshot snapshot;
-  private Periods periods;
-  private I18n i18n;
-
-  @Before
-  public void setup() {
-    context = mock(DecoratorContext.class);
-    periods = mock(Periods.class);
-    i18n = mock(I18n.class);
-    when(i18n.message(any(Locale.class), eq("variation"), eq("variation"))).thenReturn("variation");
-
-    measureClasses = new Measure(CoreMetrics.CLASSES, 20d);
-    measureCoverage = new Measure(CoreMetrics.COVERAGE, 35d);
-    measureComplexity = new Measure(CoreMetrics.COMPLEXITY, 50d);
-
-    when(context.getMeasure(CoreMetrics.CLASSES)).thenReturn(measureClasses);
-    when(context.getMeasure(CoreMetrics.COVERAGE)).thenReturn(measureCoverage);
-    when(context.getMeasure(CoreMetrics.COMPLEXITY)).thenReturn(measureComplexity);
-
-    snapshot = mock(Snapshot.class);
-    projectAlerts = mock(ProjectAlerts.class);
-    decorator = new CheckAlertThresholds(snapshot, projectAlerts, periods, i18n);
-    project = mock(Resource.class);
-    when(project.getQualifier()).thenReturn(Qualifiers.PROJECT);
-  }
-
-  @Test
-  public void should_generates_alert_status() {
-    assertThat(decorator.generatesAlertStatus()).isEqualTo(CoreMetrics.ALERT_STATUS);
-  }
-
-  @Test
-  public void should_depends_on_variations() {
-    assertThat(decorator.dependsOnVariations()).isEqualTo(DecoratorBarriers.END_OF_TIME_MACHINE);
-  }
-
-  @Test
-  public void should_depends_upon_metrics() {
-    when(projectAlerts.all()).thenReturn(newArrayList(new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "20")));
-    assertThat(decorator.dependsUponMetrics()).containsOnly(CoreMetrics.CLASSES);
-  }
-
-  @Test
-  public void shouldNotCreateAlertsWhenNoThresholds() {
-    when(projectAlerts.all()).thenReturn(new ArrayList<Alert>());
-    assertThat(decorator.shouldExecuteOnProject(new Project("key"))).isFalse();
-  }
-
-  @Test
-  public void shouldBeOkWhenNoAlert() {
-    when(projectAlerts.all()).thenReturn(Arrays.asList(
-      new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "20"),
-      new Alert(null, CoreMetrics.COVERAGE, Alert.OPERATOR_GREATER, null, "35.0")));
-
-    decorator.decorate(project, context);
-
-    verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.ALERT_STATUS, Metric.Level.OK.toString())));
-    verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.OK)));
-    verify(context).saveMeasure(argThat(hasLevel(measureCoverage, Metric.Level.OK)));
-  }
-
-  @Test
-  public void checkRootProjectsOnly() {
-    when(project.getQualifier()).thenReturn(Resource.QUALIFIER_FILE);
-    when(projectAlerts.all()).thenReturn(Arrays.asList(
-      new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "20"),
-      new Alert(null, CoreMetrics.COVERAGE, Alert.OPERATOR_GREATER, null, "35.0")));
-
-    decorator.decorate(project, context);
-
-    verify(context, never()).saveMeasure(any(Measure.class));
-  }
-
-  @Test
-  public void shouldGenerateWarnings() {
-    when(projectAlerts.all()).thenReturn(Arrays.asList(
-      new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "100"),
-      new Alert(null, CoreMetrics.COVERAGE, Alert.OPERATOR_SMALLER, null, "95.0"))); // generates warning because coverage 35% < 95%
-
-    decorator.decorate(project, context);
-
-    verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.WARN, null)));
-
-    verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.OK)));
-    verify(context).saveMeasure(argThat(hasLevel(measureCoverage, Metric.Level.WARN)));
-
-  }
-
-  @Test
-  public void globalStatusShouldBeErrorIfWarningsAndErrors() {
-    when(projectAlerts.all()).thenReturn(Arrays.asList(
-      new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_SMALLER, null, "100"), // generates warning because classes 20 < 100
-      new Alert(null, CoreMetrics.COVERAGE, Alert.OPERATOR_SMALLER, "50.0", "80.0"))); // generates error because coverage 35% < 50%
-
-    decorator.decorate(project, context);
-
-    verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.ERROR, null)));
-
-    verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.WARN)));
-    verify(context).saveMeasure(argThat(hasLevel(measureCoverage, Metric.Level.ERROR)));
-  }
-
-  @Test
-  public void globalLabelShouldAggregateAllLabels() {
-    when(i18n.message(any(Locale.class), eq("metric.classes.name"), anyString())).thenReturn("Classes");
-    when(i18n.message(any(Locale.class), eq("metric.coverage.name"), anyString())).thenReturn("Coverages");
-    when(projectAlerts.all()).thenReturn(Arrays.asList(
-      new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_SMALLER, null, "10000"), // there are 20 classes, error threshold is higher =>
-                                                                                   // alert
-      new Alert(null, CoreMetrics.COVERAGE, Alert.OPERATOR_SMALLER, "50.0", "80.0"))); // coverage is 35%, warning threshold is higher =>
-                                                                                       // alert
-
-    decorator.decorate(project, context);
-
-    verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.ERROR, "Classes < 10000, Coverages < 50.0")));
-  }
-
-  @Test
-  public void alertLabelUsesL10nMetricName() {
-    Metric metric = new Metric.Builder("rating", "Rating", Metric.ValueType.INT).create();
-
-    // metric name is declared in l10n bundle
-    when(i18n.message(any(Locale.class), eq("metric.rating.name"), anyString())).thenReturn("THE RATING");
-
-    when(context.getMeasure(metric)).thenReturn(new Measure(metric, 4d));
-    when(projectAlerts.all()).thenReturn(Arrays.<Alert>asList(new Alert(null, metric, Alert.OPERATOR_SMALLER, "10", null)));
-    decorator.decorate(project, context);
-
-    verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.ERROR, "THE RATING < 10")));
-  }
-
-  @Test
-  public void alertLabelUsesMetricNameIfMissingL10nBundle() {
-    // the third argument is Metric#getName()
-    when(i18n.message(any(Locale.class), eq("metric.classes.name"), eq("Classes"))).thenReturn("Classes");
-    when(projectAlerts.all()).thenReturn(Arrays.<Alert>asList(
-      // there are 20 classes, error threshold is higher => alert
-      new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_SMALLER, "10000", null)
-      ));
-
-    decorator.decorate(project, context);
-
-    verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.ERROR, "Classes < 10000")));
-  }
-
-  @Test
-  public void shouldBeOkIfPeriodVariationIsEnough() {
-    measureClasses.setVariation1(0d);
-    measureCoverage.setVariation2(50d);
-    measureComplexity.setVariation3(2d);
-
-    when(projectAlerts.all()).thenReturn(Arrays.asList(
-      new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "10", 1), // ok because no variation
-      new Alert(null, CoreMetrics.COVERAGE, Alert.OPERATOR_SMALLER, null, "40.0", 2), // ok because coverage increases of 50%, which is more
-                                                                                      // than 40%
-      new Alert(null, CoreMetrics.COMPLEXITY, Alert.OPERATOR_GREATER, null, "5", 3) // ok because complexity increases of 2, which is less
-                                                                                    // than 5
-      ));
-
-    decorator.decorate(project, context);
-
-    verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.OK, null)));
-
-    verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.OK)));
-    verify(context).saveMeasure(argThat(hasLevel(measureCoverage, Metric.Level.OK)));
-    verify(context).saveMeasure(argThat(hasLevel(measureComplexity, Metric.Level.OK)));
-  }
-
-  @Test
-  public void shouldGenerateWarningIfPeriodVariationIsNotEnough() {
-    measureClasses.setVariation1(40d);
-    measureCoverage.setVariation2(5d);
-    measureComplexity.setVariation3(70d);
-
-    when(projectAlerts.all()).thenReturn(Arrays.asList(
-      new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "30", 1), // generates warning because classes increases of 40,
-                                                                                   // which is greater than 30
-      new Alert(null, CoreMetrics.COVERAGE, Alert.OPERATOR_SMALLER, null, "10.0", 2), // generates warning because coverage increases of 5%,
-                                                                                      // which is smaller than 10%
-      new Alert(null, CoreMetrics.COMPLEXITY, Alert.OPERATOR_GREATER, null, "60", 3) // generates warning because complexity increases of
-                                                                                     // 70, which is smaller than 60
-      ));
-
-    decorator.decorate(project, context);
-
-    verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.WARN, null)));
-
-    verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.WARN)));
-    verify(context).saveMeasure(argThat(hasLevel(measureCoverage, Metric.Level.WARN)));
-    verify(context).saveMeasure(argThat(hasLevel(measureComplexity, Metric.Level.WARN)));
-  }
-
-  @Test
-  public void shouldBeOkIfVariationIsNull() {
-    measureClasses.setVariation1(null);
-
-    when(projectAlerts.all()).thenReturn(Arrays.asList(
-      new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "10", 1)
-      ));
-
-    decorator.decorate(project, context);
-
-    verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.OK, null)));
-    verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.OK)));
-  }
-
-  @Test
-  public void shouldVariationPeriodValueCouldBeUsedForRatingMetric() {
-    Metric ratingMetric = new Metric.Builder("key_rating_metric", "Rating metric", Metric.ValueType.RATING).create();
-    Measure measureRatingMetric = new Measure(ratingMetric, 150d);
-    measureRatingMetric.setVariation1(50d);
-    when(context.getMeasure(ratingMetric)).thenReturn(measureRatingMetric);
-
-    when(projectAlerts.all()).thenReturn(Arrays.asList(
-      new Alert(null, ratingMetric, Alert.OPERATOR_GREATER, null, "100", 1)
-      ));
-
-    decorator.decorate(project, context);
-
-    verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.OK, null)));
-    verify(context).saveMeasure(argThat(hasLevel(measureRatingMetric, Metric.Level.OK)));
-  }
-
-  @Test(expected = IllegalStateException.class)
-  public void shouldAllowOnlyVariationPeriodOneGlobalPeriods() {
-    measureClasses.setVariation4(40d);
-
-    when(projectAlerts.all()).thenReturn(Arrays.asList(
-      new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "30", 4)
-      ));
-
-    decorator.decorate(project, context);
-  }
-
-  @Test(expected = NotImplementedException.class)
-  public void shouldNotAllowPeriodVariationAlertOnStringMetric() {
-    Measure measure = new Measure(CoreMetrics.SCM_AUTHORS_BY_LINE, 100d);
-    measure.setVariation1(50d);
-    when(context.getMeasure(CoreMetrics.SCM_AUTHORS_BY_LINE)).thenReturn(measure);
-
-    when(projectAlerts.all()).thenReturn(Arrays.asList(
-      new Alert(null, CoreMetrics.SCM_AUTHORS_BY_LINE, Alert.OPERATOR_GREATER, null, "30", 1)
-      ));
-
-    decorator.decorate(project, context);
-  }
-
-  @Test
-  public void shouldLabelAlertContainsPeriod() {
-    measureClasses.setVariation1(40d);
-
-    when(i18n.message(any(Locale.class), eq("metric.classes.name"), anyString())).thenReturn("Classes");
-    when(periods.label(snapshot, 1)).thenReturn("since someday");
-
-    when(projectAlerts.all()).thenReturn(Arrays.asList(
-      new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "30", 1) // generates warning because classes increases of 40,
-                                                                                  // which is greater than 30
-      ));
-
-    decorator.decorate(project, context);
-
-    verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.WARN, "Classes variation > 30 since someday")));
-  }
-
-  @Test
-  public void shouldLabelAlertForNewMetricDoNotContainsVariationWord() {
-    Metric newMetric = new Metric.Builder("new_metric_key", "New Metric", Metric.ValueType.INT).create();
-    Measure measure = new Measure(newMetric, 15d);
-    measure.setVariation1(50d);
-    when(context.getMeasure(newMetric)).thenReturn(measure);
-    measureClasses.setVariation1(40d);
-
-    when(i18n.message(any(Locale.class), eq("metric.new_metric_key.name"), anyString())).thenReturn("New Measure");
-    when(periods.label(snapshot, 1)).thenReturn("since someday");
-
-    when(projectAlerts.all()).thenReturn(Arrays.asList(
-      new Alert(null, newMetric, Alert.OPERATOR_GREATER, null, "30", 1) // generates warning because classes increases of 40, which is
-                                                                        // greater than 30
-      ));
-
-    decorator.decorate(project, context);
-
-    verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.WARN, "New Measure > 30 since someday")));
-  }
-
-  private ArgumentMatcher<Measure> matchesMetric(final Metric metric, final Metric.Level alertStatus, final String alertText) {
-    return new ArgumentMatcher<Measure>() {
-      @Override
-      public boolean matches(Object arg) {
-        boolean result = ((Measure) arg).getMetric().equals(metric) && ((Measure) arg).getAlertStatus() == alertStatus;
-        if (result && alertText != null) {
-          result = alertText.equals(((Measure) arg).getAlertText());
-        }
-        return result;
-      }
-    };
-  }
-
-  private ArgumentMatcher<Measure> hasLevel(final Measure measure, final Metric.Level alertStatus) {
-    return new ArgumentMatcher<Measure>() {
-      @Override
-      public boolean matches(Object arg) {
-        return arg == measure && ((Measure) arg).getAlertStatus().equals(alertStatus);
-      }
-    };
-  }
-
-}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/fs/package-info.java b/sonar-batch/src/main/java/org/sonar/batch/fs/package-info.java
deleted file mode 100644 (file)
index 9b04956..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 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.
- */
-
-/**
- * This package is a part of bootstrap process, so we should take care about backward compatibility.
- */
-@ParametersAreNonnullByDefault
-package org.sonar.batch.fs;
-
-import javax.annotation.ParametersAreNonnullByDefault;
index 2043d7ee452685ad02369f6756dcf38fac77a672..8893efb8d0dcc027d15ec9f6a37bb24590a0f0f6 100644 (file)
@@ -30,6 +30,7 @@ import org.sonar.batch.index.DefaultIndex;
 import org.sonar.batch.index.PersistenceManager;
 import org.sonar.batch.index.ScanPersister;
 import org.sonar.batch.issue.ignore.scanner.IssueExclusionsLoader;
+import org.sonar.batch.rule.QProfileVerifier;
 import org.sonar.batch.scan.filesystem.DefaultModuleFileSystem;
 import org.sonar.batch.scan.filesystem.FileSystemLogger;
 import org.sonar.batch.scan.maven.MavenPhaseExecutor;
@@ -65,7 +66,7 @@ public final class PhaseExecutor {
   private final FileSystemLogger fsLogger;
   private final JsonReport jsonReport;
   private final DefaultModuleFileSystem fs;
-  private final ProfileLogger profileLogger;
+  private final QProfileVerifier profileVerifier;
   private final IssueExclusionsLoader issueExclusionsLoader;
 
   public PhaseExecutor(Phases phases, DecoratorsExecutor decoratorsExecutor, MavenPhaseExecutor mavenPhaseExecutor,
@@ -73,7 +74,7 @@ public final class PhaseExecutor {
     PostJobsExecutor postJobsExecutor, SensorsExecutor sensorsExecutor,
     PersistenceManager persistenceManager, SensorContext sensorContext, DefaultIndex index,
     EventBus eventBus, UpdateStatusJob updateStatusJob, ProjectInitializer pi,
-    ScanPersister[] persisters, FileSystemLogger fsLogger, JsonReport jsonReport, DefaultModuleFileSystem fs, ProfileLogger profileLogger,
+    ScanPersister[] persisters, FileSystemLogger fsLogger, JsonReport jsonReport, DefaultModuleFileSystem fs, QProfileVerifier profileVerifier,
     IssueExclusionsLoader issueExclusionsLoader) {
     this.phases = phases;
     this.decoratorsExecutor = decoratorsExecutor;
@@ -92,7 +93,7 @@ public final class PhaseExecutor {
     this.fsLogger = fsLogger;
     this.jsonReport = jsonReport;
     this.fs = fs;
-    this.profileLogger = profileLogger;
+    this.profileVerifier = profileVerifier;
     this.issueExclusionsLoader = issueExclusionsLoader;
   }
 
@@ -101,9 +102,9 @@ public final class PhaseExecutor {
     PostJobsExecutor postJobsExecutor, SensorsExecutor sensorsExecutor,
     PersistenceManager persistenceManager, SensorContext sensorContext, DefaultIndex index,
     EventBus eventBus, ProjectInitializer pi, ScanPersister[] persisters, FileSystemLogger fsLogger, JsonReport jsonReport,
-    DefaultModuleFileSystem fs, ProfileLogger profileLogger, IssueExclusionsLoader issueExclusionsLoader) {
+    DefaultModuleFileSystem fs, QProfileVerifier profileVerifier, IssueExclusionsLoader issueExclusionsLoader) {
     this(phases, decoratorsExecutor, mavenPhaseExecutor, mavenPluginsConfigurator, initializersExecutor, postJobsExecutor,
-      sensorsExecutor, persistenceManager, sensorContext, index, eventBus, null, pi, persisters, fsLogger, jsonReport, fs, profileLogger, issueExclusionsLoader);
+      sensorsExecutor, persistenceManager, sensorContext, index, eventBus, null, pi, persisters, fsLogger, jsonReport, fs, profileVerifier, issueExclusionsLoader);
   }
 
   /**
@@ -125,7 +126,7 @@ public final class PhaseExecutor {
       fs.index();
 
       // Log detected languages and their profiles after FS is indexed and languages detected
-      profileLogger.execute();
+      profileVerifier.execute();
 
       // Initialize issue exclusions
       issueExclusionsLoader.execute();
diff --git a/sonar-batch/src/main/java/org/sonar/batch/phases/ProfileLogger.java b/sonar-batch/src/main/java/org/sonar/batch/phases/ProfileLogger.java
deleted file mode 100644 (file)
index 0aa232d..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 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.phases;
-
-import com.google.common.annotations.VisibleForTesting;
-import org.apache.commons.lang.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.api.BatchComponent;
-import org.sonar.api.batch.fs.FileSystem;
-import org.sonar.api.config.Settings;
-import org.sonar.api.profiles.Alert;
-import org.sonar.api.profiles.RulesProfile;
-import org.sonar.api.utils.MessageException;
-import org.sonar.batch.rule.ModuleQProfiles;
-import org.sonar.batch.rule.ModuleQProfiles.QProfile;
-import org.sonar.batch.rule.ProjectAlerts;
-import org.sonar.batch.rule.RulesProfileWrapper;
-
-public class ProfileLogger implements BatchComponent {
-
-  private static final Logger LOG = LoggerFactory.getLogger(ProfileLogger.class);
-
-  private final Settings settings;
-  private final FileSystem fs;
-  private final ModuleQProfiles profiles;
-  private final ProjectAlerts projectAlerts;
-
-  private final RulesProfile rulesProfile;
-
-  public ProfileLogger(Settings settings, FileSystem fs, ModuleQProfiles profiles, ProjectAlerts projectAlerts, RulesProfile rulesProfile) {
-    this.settings = settings;
-    this.fs = fs;
-    this.profiles = profiles;
-    this.projectAlerts = projectAlerts;
-    this.rulesProfile = rulesProfile;
-  }
-
-  public void execute() {
-    execute(LOG);
-  }
-
-  @VisibleForTesting
-  void execute(Logger logger) {
-    String defaultName = settings.getString(ModuleQProfiles.SONAR_PROFILE_PROP);
-    boolean defaultNameUsed = StringUtils.isBlank(defaultName);
-    for (String lang : fs.languages()) {
-      QProfile profile = profiles.findByLanguage(lang);
-      if (profile == null) {
-        logger.warn("No Quality profile found for language " + lang);
-      } else {
-        logger.info("Quality profile for {}: {}", lang, profile.name());
-        if (StringUtils.isNotBlank(defaultName) && defaultName.equals(profile.name())) {
-          defaultNameUsed = true;
-        }
-      }
-    }
-    if (!defaultNameUsed && !fs.languages().isEmpty()) {
-      throw MessageException.of("sonar.profile was set to '" + defaultName + "' but didn't match any profile for any language. Please check your configuration.");
-    }
-
-    addModuleAlertsToProjectAlerts();
-  }
-
-  private void addModuleAlertsToProjectAlerts() {
-    RulesProfileWrapper profileWrapper = (RulesProfileWrapper) rulesProfile;
-    for (String lang : fs.languages()) {
-      RulesProfile profile = profileWrapper.getProfileByLanguage(lang);
-      for (Alert alert : profile.getAlerts()) {
-        projectAlerts.add(alert);
-      }
-    }
-  }
-
-}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/qualitygate/AlertUtils.java b/sonar-batch/src/main/java/org/sonar/batch/qualitygate/AlertUtils.java
new file mode 100644 (file)
index 0000000..0f4a874
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.qualitygate;
+
+import org.apache.commons.lang.NotImplementedException;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.profiles.Alert;
+
+class AlertUtils {
+
+  private AlertUtils() {
+    // only static stuff
+  }
+
+  /**
+   * Get the matching alert level for the given measure
+   */
+  static Metric.Level getLevel(Alert alert, Measure measure) {
+    if (evaluateAlert(alert, measure, Metric.Level.ERROR)) {
+      return Metric.Level.ERROR;
+    }
+    if (evaluateAlert(alert, measure, Metric.Level.WARN)) {
+      return Metric.Level.WARN;
+    }
+    return Metric.Level.OK;
+  }
+
+  private static boolean evaluateAlert(Alert alert, Measure measure, Metric.Level alertLevel) {
+    String valueToEval = getValueToEval(alert, alertLevel);
+    if (StringUtils.isEmpty(valueToEval)) {
+      return false;
+    }
+
+    Comparable criteriaValue = getValueForComparison(alert.getMetric(), valueToEval);
+    Comparable measureValue = getMeasureValue(alert, measure);
+    if (measureValue != null) {
+      return doesReachThresholds(measureValue, criteriaValue, alert);
+    }
+    return false;
+  }
+
+  private static boolean doesReachThresholds(Comparable measureValue, Comparable criteriaValue, Alert alert) {
+    int comparison = measureValue.compareTo(criteriaValue);
+    return !(isNotEquals(comparison, alert)
+        || isGreater(comparison, alert)
+        || isSmaller(comparison, alert)
+        || isEquals(comparison, alert));
+  }
+
+  private static boolean isNotEquals(int comparison, Alert alert) {
+    return alert.isNotEqualsOperator() && comparison == 0;
+  }
+
+  private static boolean isGreater(int comparison, Alert alert) {
+    return alert.isGreaterOperator() && comparison != 1;
+  }
+
+  private static boolean isSmaller(int comparison, Alert alert) {
+    return alert.isSmallerOperator() && comparison != -1;
+  }
+
+  private static boolean isEquals(int comparison, Alert alert) {
+    return alert.isEqualsOperator() && comparison != 0;
+  }
+
+  private static String getValueToEval(Alert alert, Metric.Level alertLevel) {
+    if (alertLevel.equals(Metric.Level.ERROR)) {
+      return alert.getValueError();
+    } else if (alertLevel.equals(Metric.Level.WARN)) {
+      return alert.getValueWarning();
+    } else {
+      throw new IllegalStateException(alertLevel.toString());
+    }
+  }
+
+  private static Comparable getValueForComparison(Metric metric, String value) {
+    if (isADouble(metric)) {
+      return Double.parseDouble(value);
+    }
+    if (isAInteger(metric)) {
+      return parseInteger(value);
+    }
+    if (isAString(metric)) {
+      return value;
+    }
+    if (isABoolean(metric)) {
+      return Integer.parseInt(value);
+    }
+    throw new NotImplementedException(metric.getType().toString());
+  }
+
+  private static Comparable<Integer> parseInteger(String value) {
+    return value.contains(".") ? Integer.parseInt(value.substring(0, value.indexOf('.'))) : Integer.parseInt(value);
+  }
+
+  private static Comparable getMeasureValue(Alert alert, Measure measure) {
+    Metric metric = alert.getMetric();
+    if (isADouble(metric)) {
+      return getValue(alert, measure);
+    }
+    if (isAInteger(metric)) {
+      return parseInteger(alert, measure);
+    }
+    if (alert.getPeriod() == null) {
+      return getMeasureValueForStringOrBoolean(metric, measure);
+    }
+    throw new NotImplementedException(metric.getType().toString());
+  }
+
+  private static Comparable getMeasureValueForStringOrBoolean(Metric metric, Measure measure) {
+    if (isAString(metric)) {
+      return measure.getData();
+    }
+    if (isABoolean(metric)) {
+      return measure.getValue().intValue();
+    }
+    throw new NotImplementedException(metric.getType().toString());
+  }
+
+  private static Comparable<Integer> parseInteger(Alert alert, Measure measure) {
+    Double value = getValue(alert, measure);
+    return value != null ? value.intValue() : null;
+  }
+
+  private static boolean isADouble(Metric metric) {
+    return metric.getType() == Metric.ValueType.FLOAT ||
+        metric.getType() == Metric.ValueType.PERCENT ||
+        metric.getType() == Metric.ValueType.RATING;
+  }
+
+  private static boolean isAInteger(Metric metric) {
+    return metric.getType() == Metric.ValueType.INT ||
+        metric.getType() == Metric.ValueType.MILLISEC;
+  }
+
+  private static boolean isAString(Metric metric) {
+    return metric.getType() == Metric.ValueType.STRING ||
+        metric.getType() == Metric.ValueType.LEVEL;
+  }
+
+  private static boolean isABoolean(Metric metric) {
+    return metric.getType() == Metric.ValueType.BOOL;
+  }
+
+  private static Double getValue(Alert alert, Measure measure) {
+    if (alert.getPeriod() == null) {
+      return measure.getValue();
+    } else if (alert.getPeriod() == 1) {
+      return measure.getVariation1();
+    } else if (alert.getPeriod() == 2) {
+      return measure.getVariation2();
+    } else if (alert.getPeriod() == 3) {
+      return measure.getVariation3();
+    } else {
+      throw new IllegalStateException("Following index period is not allowed : " + Double.toString(alert.getPeriod()));
+    }
+  }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/qualitygate/ProjectAlerts.java b/sonar-batch/src/main/java/org/sonar/batch/qualitygate/ProjectAlerts.java
new file mode 100644 (file)
index 0000000..9210327
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.qualitygate;
+
+import com.google.common.collect.Sets;
+import org.sonar.api.BatchComponent;
+import org.sonar.api.profiles.Alert;
+
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * Lists the alerts enabled on the current project.
+ */
+public class ProjectAlerts implements BatchComponent {
+
+  private final Set<Alert> alerts = Sets.newHashSet();
+
+  void addAll(Collection<Alert> alerts) {
+    this.alerts.addAll(alerts);
+  }
+
+  Set<Alert> all() {
+    return alerts;
+  }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/qualitygate/QualityGateLoader.java b/sonar-batch/src/main/java/org/sonar/batch/qualitygate/QualityGateLoader.java
new file mode 100644 (file)
index 0000000..f43e939
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.qualitygate;
+
+import org.sonar.api.batch.Sensor;
+import org.sonar.api.batch.SensorContext;
+import org.sonar.api.batch.fs.FileSystem;
+import org.sonar.api.profiles.RulesProfile;
+import org.sonar.api.resources.Project;
+import org.sonar.batch.rule.RulesProfileWrapper;
+
+/**
+ * Executed on every module to feed {@link org.sonar.batch.qualitygate.ProjectAlerts}
+ */
+public class QualityGateLoader implements Sensor {
+
+  private final FileSystem fs;
+  private final RulesProfileWrapper qProfile;
+  private final ProjectAlerts projectAlerts;
+
+  public QualityGateLoader(FileSystem fs, RulesProfileWrapper qProfile, ProjectAlerts projectAlerts) {
+    this.fs = fs;
+    this.qProfile = qProfile;
+    this.projectAlerts = projectAlerts;
+  }
+
+  @Override
+  public boolean shouldExecuteOnProject(Project project) {
+    return true;
+  }
+
+  @Override
+  public void analyse(Project module, SensorContext context) {
+    for (String lang : fs.languages()) {
+      RulesProfile profile = qProfile.getProfileByLanguage(lang);
+      if (profile != null) {
+        projectAlerts.addAll(profile.getAlerts());
+      }
+    }
+  }
+
+  @Override
+  public String toString() {
+    return "Quality gate loader";
+  }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/qualitygate/QualityGateVerifier.java b/sonar-batch/src/main/java/org/sonar/batch/qualitygate/QualityGateVerifier.java
new file mode 100644 (file)
index 0000000..59ce484
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.qualitygate;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.batch.*;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.i18n.I18n;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.profiles.Alert;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.resources.ResourceUtils;
+import org.sonar.core.timemachine.Periods;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+public class QualityGateVerifier implements Decorator {
+
+  private static final String VARIATION_METRIC_PREFIX = "new_";
+  private static final String VARIATION = "variation";
+
+  private final Snapshot snapshot;
+  private final Periods periods;
+  private final I18n i18n;
+  private ProjectAlerts projectAlerts;
+
+  public QualityGateVerifier(Snapshot snapshot, ProjectAlerts projectAlerts, Periods periods, I18n i18n) {
+    this.snapshot = snapshot;
+    this.projectAlerts = projectAlerts;
+    this.periods = periods;
+    this.i18n = i18n;
+  }
+
+  @DependedUpon
+  public Metric generatesAlertStatus() {
+    return CoreMetrics.ALERT_STATUS;
+  }
+
+  @DependsUpon
+  public String dependsOnVariations() {
+    return DecoratorBarriers.END_OF_TIME_MACHINE;
+  }
+
+  @DependsUpon
+  public Collection<Metric> dependsUponMetrics() {
+    Set<Metric> metrics = Sets.newHashSet();
+    for (Alert alert : projectAlerts.all()) {
+      metrics.add(alert.getMetric());
+    }
+    return metrics;
+  }
+
+  public boolean shouldExecuteOnProject(Project project) {
+    return true;
+  }
+
+  public void decorate(final Resource resource, final DecoratorContext context) {
+    if (ResourceUtils.isRootProject(resource) && !projectAlerts.all().isEmpty()) {
+      checkProjectAlerts(context);
+    }
+  }
+
+  private void checkProjectAlerts(DecoratorContext context) {
+    Metric.Level globalLevel = Metric.Level.OK;
+    List<String> labels = Lists.newArrayList();
+
+    for (Alert alert : projectAlerts.all()) {
+      Measure measure = context.getMeasure(alert.getMetric());
+      if (measure != null) {
+        Metric.Level level = AlertUtils.getLevel(alert, measure);
+
+        measure.setAlertStatus(level);
+        String text = getText(alert, level);
+        if (!StringUtils.isBlank(text)) {
+          measure.setAlertText(text);
+          labels.add(text);
+        }
+
+        context.saveMeasure(measure);
+
+        if (Metric.Level.WARN == level && globalLevel != Metric.Level.ERROR) {
+          globalLevel = Metric.Level.WARN;
+
+        } else if (Metric.Level.ERROR == level) {
+          globalLevel = Metric.Level.ERROR;
+        }
+      }
+    }
+
+    Measure globalMeasure = new Measure(CoreMetrics.ALERT_STATUS, globalLevel);
+    globalMeasure.setAlertStatus(globalLevel);
+    globalMeasure.setAlertText(StringUtils.join(labels, ", "));
+    context.saveMeasure(globalMeasure);
+  }
+
+  private String getText(Alert alert, Metric.Level level) {
+    if (level == Metric.Level.OK) {
+      return null;
+    }
+    return getAlertLabel(alert, level);
+  }
+
+  private String getAlertLabel(Alert alert, Metric.Level level) {
+    Integer alertPeriod = alert.getPeriod();
+    String metric = i18n.message(Locale.ENGLISH, "metric." + alert.getMetric().getKey() + ".name", alert.getMetric().getName());
+
+    StringBuilder stringBuilder = new StringBuilder();
+    stringBuilder.append(metric);
+
+    if (alertPeriod != null && !alert.getMetric().getKey().startsWith(VARIATION_METRIC_PREFIX)) {
+      String variation = i18n.message(Locale.ENGLISH, VARIATION, VARIATION).toLowerCase();
+      stringBuilder.append(" ").append(variation);
+    }
+
+    stringBuilder
+      .append(" ").append(alert.getOperator()).append(" ")
+      .append(level.equals(Metric.Level.ERROR) ? alert.getValueError() : alert.getValueWarning());
+
+    if (alertPeriod != null) {
+      stringBuilder.append(" ").append(periods.label(snapshot, alertPeriod));
+    }
+
+    return stringBuilder.toString();
+  }
+
+  @Override
+  public String toString() {
+    return getClass().getSimpleName();
+  }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/qualitygate/package-info.java b/sonar-batch/src/main/java/org/sonar/batch/qualitygate/package-info.java
new file mode 100644 (file)
index 0000000..8ccd3b1
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.qualitygate;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/sonar-batch/src/main/java/org/sonar/batch/rule/ProjectAlerts.java b/sonar-batch/src/main/java/org/sonar/batch/rule/ProjectAlerts.java
deleted file mode 100644 (file)
index c1d7a11..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 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.rule;
-
-import com.google.common.collect.Lists;
-import org.sonar.api.BatchComponent;
-import org.sonar.api.profiles.Alert;
-
-import java.util.List;
-
-/**
- * Lists the alerts enabled on the current project.
- */
-public class ProjectAlerts implements BatchComponent {
-
-  private final List<Alert> alerts = Lists.newArrayList();
-
-  public ProjectAlerts() {
-  }
-
-  public void add(Alert alert) {
-    alerts.add(alert);
-  }
-
-  public List<Alert> all() {
-    return alerts;
-  }
-
-}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/rule/QProfileVerifier.java b/sonar-batch/src/main/java/org/sonar/batch/rule/QProfileVerifier.java
new file mode 100644 (file)
index 0000000..226affd
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.rule;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.BatchComponent;
+import org.sonar.api.batch.fs.FileSystem;
+import org.sonar.api.config.Settings;
+import org.sonar.api.utils.MessageException;
+import org.sonar.batch.rule.ModuleQProfiles.QProfile;
+
+public class QProfileVerifier implements BatchComponent {
+
+  private static final Logger LOG = LoggerFactory.getLogger(QProfileVerifier.class);
+
+  private final Settings settings;
+  private final FileSystem fs;
+  private final ModuleQProfiles profiles;
+
+  public QProfileVerifier(Settings settings, FileSystem fs, ModuleQProfiles profiles) {
+    this.settings = settings;
+    this.fs = fs;
+    this.profiles = profiles;
+  }
+
+  public void execute() {
+    execute(LOG);
+  }
+
+  @VisibleForTesting
+  void execute(Logger logger) {
+    String defaultName = settings.getString(ModuleQProfiles.SONAR_PROFILE_PROP);
+    boolean defaultNameUsed = StringUtils.isBlank(defaultName);
+    for (String lang : fs.languages()) {
+      QProfile profile = profiles.findByLanguage(lang);
+      if (profile == null) {
+        logger.warn("No Quality profile found for language " + lang);
+      } else {
+        logger.info("Quality profile for {}: {}", lang, profile.name());
+        if (StringUtils.isNotBlank(defaultName) && defaultName.equals(profile.name())) {
+          defaultNameUsed = true;
+        }
+      }
+    }
+    if (!defaultNameUsed && !fs.languages().isEmpty()) {
+      throw MessageException.of("sonar.profile was set to '" + defaultName + "' but didn't match any profile for any language. Please check your configuration.");
+    }
+  }
+}
index b99fbd5ba7d64598f51d61a4c069aeb976217938..02115c858f59d63bee4cdb004f52fbdf765ad78c 100644 (file)
@@ -23,6 +23,8 @@ import org.picocontainer.Startable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.sonar.api.CoreProperties;
+import org.sonar.api.batch.fs.FileSystem;
+import org.sonar.api.batch.fs.internal.DefaultFileSystem;
 import org.sonar.api.config.Settings;
 import org.sonar.api.resources.Language;
 import org.sonar.api.resources.Languages;
@@ -37,10 +39,12 @@ public class LanguageVerifier implements Startable {
 
   private final Settings settings;
   private final Languages languages;
+  private final DefaultFileSystem fs;
 
-  public LanguageVerifier(Settings settings, Languages languages) {
+  public LanguageVerifier(Settings settings, Languages languages, DefaultFileSystem fs) {
     this.settings = settings;
     this.languages = languages;
+    this.fs = fs;
   }
 
 
@@ -53,6 +57,9 @@ public class LanguageVerifier implements Startable {
       if (language == null) {
         throw MessageException.of("You must install a plugin that supports the language '" + languageKey + "'");
       }
+
+      // force the registration of the language, even if there are no related source files
+      fs.addLanguages(languageKey);
     }
   }
 
index 4fca5d3596fef05b9f79144d59db737ff8238044..d8103d52f29578d0b4417205d38880a9f34e40d6 100644 (file)
@@ -54,7 +54,9 @@ import org.sonar.batch.issue.IssueFilters;
 import org.sonar.batch.issue.ModuleIssues;
 import org.sonar.batch.phases.PhaseExecutor;
 import org.sonar.batch.phases.PhasesTimeProfiler;
-import org.sonar.batch.phases.ProfileLogger;
+import org.sonar.batch.rule.QProfileVerifier;
+import org.sonar.batch.qualitygate.QualityGateLoader;
+import org.sonar.batch.qualitygate.QualityGateVerifier;
 import org.sonar.batch.rule.ActiveRulesProvider;
 import org.sonar.batch.rule.ModuleQProfiles;
 import org.sonar.batch.rule.QProfileSensor;
@@ -117,7 +119,7 @@ public class ModuleScanContainer extends ComponentContainer {
       DefaultModuleFileSystem.class,
       ModuleFileSystemInitializer.class,
       ProjectFileSystemAdapter.class,
-      ProfileLogger.class,
+      QProfileVerifier.class,
 
       // the Snapshot component will be removed when asynchronous measures are improved (required for AsynchronousMeasureSensor)
       getComponentByType(ResourcePersister.class).getSnapshot(module),
@@ -131,6 +133,10 @@ public class ModuleScanContainer extends ComponentContainer {
       MeasurementFilters.class,
       ResourceFilters.class,
 
+      // quality gates
+      QualityGateLoader.class,
+      QualityGateVerifier.class,
+
       // rules
       ModuleQProfiles.class,
       new ActiveRulesProvider(),
index 500b42bf39954d9310cc60a228f500a2a817cd68..c86e1fc25c6232fb40f97677512848b92412576f 100644 (file)
@@ -68,7 +68,7 @@ import org.sonar.batch.issue.IssuePersister;
 import org.sonar.batch.issue.ScanIssueStorage;
 import org.sonar.batch.phases.GraphPersister;
 import org.sonar.batch.profiling.PhasesSumUpTimeProfiler;
-import org.sonar.batch.rule.ProjectAlerts;
+import org.sonar.batch.qualitygate.ProjectAlerts;
 import org.sonar.batch.scan.filesystem.InputFileCache;
 import org.sonar.batch.scan.maven.FakeMavenPluginExecutor;
 import org.sonar.batch.scan.maven.MavenPluginExecutor;
@@ -189,6 +189,7 @@ public class ProjectScanContainer extends ComponentContainer {
 
       ProjectSettingsReady.class,
 
+      // quality gates
       ProjectAlerts.class);
   }
 
diff --git a/sonar-batch/src/test/java/org/sonar/batch/phases/ProfileLoggerTest.java b/sonar-batch/src/test/java/org/sonar/batch/phases/ProfileLoggerTest.java
deleted file mode 100644 (file)
index e3bcc49..0000000
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 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.phases;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.slf4j.Logger;
-import org.sonar.api.batch.fs.internal.DefaultFileSystem;
-import org.sonar.api.config.Settings;
-import org.sonar.api.profiles.Alert;
-import org.sonar.api.profiles.RulesProfile;
-import org.sonar.api.utils.MessageException;
-import org.sonar.batch.rule.ModuleQProfiles;
-import org.sonar.batch.rule.ModuleQProfiles.QProfile;
-import org.sonar.batch.rule.ProjectAlerts;
-import org.sonar.batch.rule.RulesProfileWrapper;
-
-import java.util.Arrays;
-import java.util.Collections;
-
-import static org.fest.assertions.Assertions.assertThat;
-import static org.mockito.Mockito.*;
-
-public class ProfileLoggerTest {
-
-  @Rule
-  public ExpectedException thrown = ExpectedException.none();
-
-  DefaultFileSystem fs = new DefaultFileSystem();
-  ModuleQProfiles profiles;
-  Settings settings = new Settings();
-  ProjectAlerts projectAlerts = new ProjectAlerts();
-  RulesProfileWrapper rulesProfile = mock(RulesProfileWrapper.class);
-  RulesProfile javaRulesProfile;
-  RulesProfile cobolRulesProfile;
-
-  @Before
-  public void before() {
-    profiles = mock(ModuleQProfiles.class);
-    QProfile javaProfile = mock(QProfile.class);
-    when(javaProfile.name()).thenReturn("My Java profile");
-    javaRulesProfile = mock(RulesProfile.class);
-    when(rulesProfile.getProfileByLanguage("java")).thenReturn(javaRulesProfile);
-    when(profiles.findByLanguage("java")).thenReturn(javaProfile);
-    QProfile cobolProfile = mock(QProfile.class);
-    when(cobolProfile.name()).thenReturn("My Cobol profile");
-    cobolRulesProfile = mock(RulesProfile.class);
-    when(rulesProfile.getProfileByLanguage("cobol")).thenReturn(cobolRulesProfile);
-    when(profiles.findByLanguage("cobol")).thenReturn(cobolProfile);
-  }
-
-  @Test
-  public void should_log_all_used_profiles() {
-    fs.addLanguages("java", "cobol");
-    ProfileLogger profileLogger = new ProfileLogger(settings, fs, profiles, projectAlerts, rulesProfile);
-    Logger logger = mock(Logger.class);
-    profileLogger.execute(logger);
-
-    verify(logger).info("Quality profile for {}: {}", "java", "My Java profile");
-    verify(logger).info("Quality profile for {}: {}", "cobol", "My Cobol profile");
-  }
-
-  @Test
-  public void should_fail_if_default_profile_not_used() {
-    fs.addLanguages("java", "cobol");
-    settings.setProperty("sonar.profile", "Unknown");
-
-    ProfileLogger profileLogger = new ProfileLogger(settings, fs, profiles, projectAlerts, rulesProfile);
-
-    thrown.expect(MessageException.class);
-    thrown.expectMessage("sonar.profile was set to 'Unknown' but didn't match any profile for any language. Please check your configuration.");
-
-    profileLogger.execute();
-  }
-
-  @Test
-  public void should_not_fail_if_no_language_on_project() {
-    settings.setProperty("sonar.profile", "Unknown");
-
-    ProfileLogger profileLogger = new ProfileLogger(settings, fs, profiles, projectAlerts, rulesProfile);
-
-    profileLogger.execute();
-
-  }
-
-  @Test
-  public void should_not_fail_if_default_profile_used_at_least_once() {
-    fs.addLanguages("java", "cobol");
-    settings.setProperty("sonar.profile", "My Java profile");
-
-    ProfileLogger profileLogger = new ProfileLogger(settings, fs, profiles, projectAlerts, rulesProfile);
-
-    profileLogger.execute();
-  }
-
-  @Test
-  public void should_collect_alerts() {
-    fs.addLanguages("java", "cobol");
-    Alert javaAlert1 = new Alert();
-    Alert javaAlert2 = new Alert();
-    Alert cobolAlert1 = new Alert();
-    Alert cobolAlert2 = new Alert();
-    when(javaRulesProfile.getAlerts()).thenReturn(Arrays.asList(javaAlert1, javaAlert2));
-    when(cobolRulesProfile.getAlerts()).thenReturn(Arrays.asList(cobolAlert1, cobolAlert2));
-
-    ProfileLogger profileLogger = new ProfileLogger(settings, fs, profiles, projectAlerts, rulesProfile);
-
-    profileLogger.execute();
-
-    assertThat(projectAlerts.all()).containsExactly(cobolAlert1, cobolAlert2, javaAlert1, javaAlert2);
-  }
-}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/qualitygate/AlertUtilsTest.java b/sonar-batch/src/test/java/org/sonar/batch/qualitygate/AlertUtilsTest.java
new file mode 100644 (file)
index 0000000..d785564
--- /dev/null
@@ -0,0 +1,253 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.qualitygate;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.profiles.Alert;
+
+public class AlertUtilsTest {
+  private Metric metric;
+  private Measure measure;
+  private Metric metric2;
+  private Measure measure2;
+  private Alert alert;
+
+  @Before
+  public void setup() {
+    metric = new Metric.Builder("test-metric", "name", Metric.ValueType.FLOAT).create();
+    measure = new Measure();
+    measure.setMetric(metric);
+
+    metric2 = new Metric.Builder("test-metric2", "name2", Metric.ValueType.FLOAT).create();
+    measure2 = new Measure();
+    measure2.setMetric(metric2);
+
+    alert = new Alert();
+  }
+
+  @Test
+  public void testInputNumbers() {
+    metric.setType(Metric.ValueType.FLOAT);
+    measure.setValue(10.2d);
+    alert.setOperator(Alert.OPERATOR_SMALLER);
+    alert.setMetric(metric);
+
+    try {
+      metric.setType(Metric.ValueType.FLOAT);
+      alert.setValueError("20");
+      AlertUtils.getLevel(alert, measure);
+    } catch (NumberFormatException ex) {
+      Assert.fail();
+    }
+
+    try {
+      metric.setType(Metric.ValueType.INT);
+      alert.setValueError("20.1");
+      AlertUtils.getLevel(alert, measure);
+    } catch (NumberFormatException ex) {
+      Assert.fail();
+    }
+
+    try {
+      metric.setType(Metric.ValueType.PERCENT);
+      alert.setValueError("20.1");
+      AlertUtils.getLevel(alert, measure);
+    } catch (NumberFormatException ex) {
+      Assert.fail();
+    }
+  }
+
+  @Test
+  public void testEquals() {
+
+    metric.setType(Metric.ValueType.FLOAT);
+    measure.setValue(10.2d);
+    alert.setOperator(Alert.OPERATOR_EQUALS);
+    alert.setMetric(metric);
+
+    alert.setValueError("10.2");
+    Assert.assertEquals(Metric.Level.ERROR, AlertUtils.getLevel(alert, measure));
+
+    alert.setValueError("10.1");
+    Assert.assertEquals(Metric.Level.OK, AlertUtils.getLevel(alert, measure));
+
+    metric.setType(Metric.ValueType.STRING);
+    measure.setData("TEST");
+    measure.setValue(null);
+
+    alert.setValueError("TEST");
+    Assert.assertEquals(Metric.Level.ERROR, AlertUtils.getLevel(alert, measure));
+
+    alert.setValueError("TEST2");
+    Assert.assertEquals(Metric.Level.OK, AlertUtils.getLevel(alert, measure));
+
+  }
+
+  @Test
+  public void testNotEquals() {
+
+    metric.setType(Metric.ValueType.FLOAT);
+    measure.setValue(10.2d);
+    alert.setOperator(Alert.OPERATOR_NOT_EQUALS);
+    alert.setMetric(metric);
+
+    alert.setValueError("10.2");
+    Assert.assertEquals(Metric.Level.OK, AlertUtils.getLevel(alert, measure));
+
+    alert.setValueError("10.1");
+    Assert.assertEquals(Metric.Level.ERROR, AlertUtils.getLevel(alert, measure));
+
+    metric.setType(Metric.ValueType.STRING);
+    measure.setData("TEST");
+    measure.setValue(null);
+
+    alert.setValueError("TEST");
+    Assert.assertEquals(Metric.Level.OK, AlertUtils.getLevel(alert, measure));
+
+    alert.setValueError("TEST2");
+    Assert.assertEquals(Metric.Level.ERROR, AlertUtils.getLevel(alert, measure));
+
+  }
+
+  @Test
+  public void testGreater() {
+    metric.setType(Metric.ValueType.FLOAT);
+    measure.setValue(10.2d);
+    alert.setOperator(Alert.OPERATOR_GREATER);
+    alert.setMetric(metric);
+
+    alert.setValueError("10.1");
+    Assert.assertEquals(Metric.Level.ERROR, AlertUtils.getLevel(alert, measure));
+
+    alert.setValueError("10.3");
+    Assert.assertEquals(Metric.Level.OK, AlertUtils.getLevel(alert, measure));
+  }
+
+  @Test
+  public void testSmaller() {
+    metric.setType(Metric.ValueType.FLOAT);
+    measure.setValue(10.2d);
+    alert.setOperator(Alert.OPERATOR_SMALLER);
+    alert.setMetric(metric);
+
+    alert.setValueError("10.1");
+    Assert.assertEquals(Metric.Level.OK, AlertUtils.getLevel(alert, measure));
+
+    alert.setValueError("10.3");
+    Assert.assertEquals(Metric.Level.ERROR, AlertUtils.getLevel(alert, measure));
+  }
+
+  @Test
+  public void testPercent() {
+    metric.setType(Metric.ValueType.PERCENT);
+    measure.setValue(10.2d);
+    alert.setOperator(Alert.OPERATOR_EQUALS);
+    alert.setMetric(metric);
+
+    alert.setValueError("10.2");
+    Assert.assertEquals(Metric.Level.ERROR, AlertUtils.getLevel(alert, measure));
+  }
+
+  @Test
+  public void testFloat() {
+    metric.setType(Metric.ValueType.FLOAT);
+    measure.setValue(10.2d);
+    alert.setOperator(Alert.OPERATOR_EQUALS);
+    alert.setMetric(metric);
+
+    alert.setValueError("10.2");
+    Assert.assertEquals(Metric.Level.ERROR, AlertUtils.getLevel(alert, measure));
+  }
+
+  @Test
+  public void testInteger() {
+    metric.setType(Metric.ValueType.INT);
+    measure.setValue(10.2d);
+    alert.setOperator(Alert.OPERATOR_EQUALS);
+    alert.setMetric(metric);
+
+    alert.setValueError("10");
+    Assert.assertEquals(Metric.Level.ERROR, AlertUtils.getLevel(alert, measure));
+
+    alert.setValueError("10.2");
+    Assert.assertEquals(Metric.Level.ERROR, AlertUtils.getLevel(alert, measure));
+  }
+
+  @Test
+  public void testLevel() {
+    metric.setType(Metric.ValueType.LEVEL);
+    measure.setData(Metric.Level.ERROR.toString());
+    alert.setOperator(Alert.OPERATOR_EQUALS);
+    alert.setMetric(metric);
+
+    alert.setValueError(Metric.Level.ERROR.toString());
+    Assert.assertEquals(Metric.Level.ERROR, AlertUtils.getLevel(alert, measure));
+
+    alert.setValueError(Metric.Level.OK.toString());
+    Assert.assertEquals(Metric.Level.OK, AlertUtils.getLevel(alert, measure));
+
+    alert.setOperator(Alert.OPERATOR_NOT_EQUALS);
+    Assert.assertEquals(Metric.Level.ERROR, AlertUtils.getLevel(alert, measure));
+  }
+
+  @Test
+  public void testBooleans() {
+    metric.setType(Metric.ValueType.BOOL);
+    measure.setValue(0d);
+    alert.setOperator(Alert.OPERATOR_EQUALS);
+    alert.setMetric(metric);
+
+    alert.setValueError("1");
+    Assert.assertEquals(Metric.Level.OK, AlertUtils.getLevel(alert, measure));
+
+    alert.setValueError("0");
+    Assert.assertEquals(Metric.Level.ERROR, AlertUtils.getLevel(alert, measure));
+
+    alert.setOperator(Alert.OPERATOR_NOT_EQUALS);
+    alert.setValueError("1");
+    Assert.assertEquals(Metric.Level.ERROR, AlertUtils.getLevel(alert, measure));
+
+    alert.setValueError("0");
+    Assert.assertEquals(Metric.Level.OK, AlertUtils.getLevel(alert, measure));
+  }
+
+  @Test
+  public void testErrorAndWarningLevel() {
+    metric.setType(Metric.ValueType.FLOAT);
+    measure.setValue(10.2d);
+    alert.setOperator(Alert.OPERATOR_EQUALS);
+    alert.setMetric(metric);
+
+    alert.setValueError("10.2");
+    Assert.assertEquals(Metric.Level.ERROR, AlertUtils.getLevel(alert, measure));
+
+    alert.setValueError("10.1");
+    Assert.assertEquals(Metric.Level.OK, AlertUtils.getLevel(alert, measure));
+
+    alert.setValueError("10.3");
+    alert.setValueWarning("10.2");
+    Assert.assertEquals(Metric.Level.WARN, AlertUtils.getLevel(alert, measure));
+  }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/qualitygate/ProjectAlertsTest.java b/sonar-batch/src/test/java/org/sonar/batch/qualitygate/ProjectAlertsTest.java
new file mode 100644 (file)
index 0000000..86df906
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.qualitygate;
+
+import com.google.common.collect.Lists;
+import org.junit.Test;
+import org.sonar.api.profiles.Alert;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class ProjectAlertsTest {
+  @Test
+  public void test() throws Exception {
+    ProjectAlerts alerts = new ProjectAlerts();
+    assertThat(alerts.all()).isEmpty();
+
+    alerts.addAll(Lists.newArrayList(new Alert()));
+    assertThat(alerts.all()).hasSize(1);
+  }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/qualitygate/QualityGateLoaderTest.java b/sonar-batch/src/test/java/org/sonar/batch/qualitygate/QualityGateLoaderTest.java
new file mode 100644 (file)
index 0000000..5989492
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.qualitygate;
+
+import com.google.common.collect.Lists;
+import org.junit.Test;
+import org.sonar.api.batch.fs.internal.DefaultFileSystem;
+import org.sonar.api.profiles.Alert;
+import org.sonar.api.profiles.RulesProfile;
+import org.sonar.api.resources.Project;
+import org.sonar.batch.rule.RulesProfileWrapper;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class QualityGateLoaderTest {
+
+  DefaultFileSystem fs = new DefaultFileSystem();
+  RulesProfileWrapper qProfile = mock(RulesProfileWrapper.class);
+  ProjectAlerts alerts = new ProjectAlerts();
+  QualityGateLoader loader = new QualityGateLoader(fs, qProfile, alerts);
+
+  @Test
+  public void should_always_be_executed() throws Exception {
+    assertThat(loader.shouldExecuteOnProject(new Project("struts"))).isTrue();
+  }
+
+  @Test
+  public void test_toString() throws Exception {
+    assertThat(loader.toString()).isEqualTo("Quality gate loader");
+  }
+
+  @Test
+  public void register_project_alerts() throws Exception {
+    fs.addLanguages("java", "php");
+
+    RulesProfile javaProfile = new RulesProfile();
+    javaProfile.setAlerts(Lists.newArrayList(new Alert()));
+    when(qProfile.getProfileByLanguage("java")).thenReturn(javaProfile);
+
+    loader.analyse(null, null);
+
+    assertThat(alerts.all()).hasSize(1);
+  }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/qualitygate/QualityGateVerifierTest.java b/sonar-batch/src/test/java/org/sonar/batch/qualitygate/QualityGateVerifierTest.java
new file mode 100644 (file)
index 0000000..b0b92fa
--- /dev/null
@@ -0,0 +1,369 @@
+
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.qualitygate;
+
+import com.google.common.collect.Lists;
+import org.apache.commons.lang.NotImplementedException;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentMatcher;
+import org.sonar.api.batch.DecoratorBarriers;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.i18n.I18n;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.profiles.Alert;
+import org.sonar.api.resources.File;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.test.IsMeasure;
+import org.sonar.core.timemachine.Periods;
+
+import java.util.Locale;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.*;
+
+public class QualityGateVerifierTest {
+
+  QualityGateVerifier verifier;
+  DecoratorContext context;
+  ProjectAlerts projectAlerts;
+
+  Measure measureClasses;
+  Measure measureCoverage;
+  Measure measureComplexity;
+  Resource project;
+  Snapshot snapshot;
+  Periods periods;
+  I18n i18n;
+
+  @Before
+  public void before() {
+    context = mock(DecoratorContext.class);
+    periods = mock(Periods.class);
+    i18n = mock(I18n.class);
+    when(i18n.message(any(Locale.class), eq("variation"), eq("variation"))).thenReturn("variation");
+
+    measureClasses = new Measure(CoreMetrics.CLASSES, 20d);
+    measureCoverage = new Measure(CoreMetrics.COVERAGE, 35d);
+    measureComplexity = new Measure(CoreMetrics.COMPLEXITY, 50d);
+
+    when(context.getMeasure(CoreMetrics.CLASSES)).thenReturn(measureClasses);
+    when(context.getMeasure(CoreMetrics.COVERAGE)).thenReturn(measureCoverage);
+    when(context.getMeasure(CoreMetrics.COMPLEXITY)).thenReturn(measureComplexity);
+
+    snapshot = mock(Snapshot.class);
+    projectAlerts = new ProjectAlerts();
+    verifier = new QualityGateVerifier(snapshot, projectAlerts, periods, i18n);
+    project = new Project("foo");
+  }
+
+  @Test
+  public void should_always_be_executed() {
+    assertThat(verifier.shouldExecuteOnProject(new Project("foo"))).isTrue();
+  }
+
+  @Test
+  public void test_toString() {
+    assertThat(verifier.toString()).isEqualTo("QualityGateVerifier");
+  }
+
+  @Test
+  public void generates_alert_status() {
+    assertThat(verifier.generatesAlertStatus()).isEqualTo(CoreMetrics.ALERT_STATUS);
+  }
+
+  @Test
+  public void depends_on_variations() {
+    assertThat(verifier.dependsOnVariations()).isEqualTo(DecoratorBarriers.END_OF_TIME_MACHINE);
+  }
+
+  @Test
+  public void depends_upon_metrics() {
+    projectAlerts.addAll(Lists.newArrayList(new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "20")));
+    assertThat(verifier.dependsUponMetrics()).containsOnly(CoreMetrics.CLASSES);
+  }
+
+  @Test
+  public void ok_when_no_alerts() {
+    projectAlerts.addAll(Lists.newArrayList(
+      new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "20"),
+      new Alert(null, CoreMetrics.COVERAGE, Alert.OPERATOR_GREATER, null, "35.0")));
+
+    verifier.decorate(project, context);
+
+    verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.ALERT_STATUS, Metric.Level.OK.toString())));
+    verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.OK)));
+    verify(context).saveMeasure(argThat(hasLevel(measureCoverage, Metric.Level.OK)));
+  }
+
+  @Test
+  public void check_root_modules_only() {
+    projectAlerts.addAll(Lists.newArrayList(
+      new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "20"),
+      new Alert(null, CoreMetrics.COVERAGE, Alert.OPERATOR_GREATER, null, "35.0")));
+
+    verifier.decorate(File.create("src/Foo.php"), context);
+
+    verify(context, never()).saveMeasure(any(Measure.class));
+  }
+
+  @Test
+  public void generate_warnings() {
+    projectAlerts.addAll(Lists.newArrayList(
+      new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "100"),
+      new Alert(null, CoreMetrics.COVERAGE, Alert.OPERATOR_SMALLER, null, "95.0"))); // generates warning because coverage 35% < 95%
+
+    verifier.decorate(project, context);
+
+    verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.WARN, null)));
+
+    verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.OK)));
+    verify(context).saveMeasure(argThat(hasLevel(measureCoverage, Metric.Level.WARN)));
+
+  }
+
+  @Test
+  public void globalStatusShouldBeErrorIfWarningsAndErrors() {
+    projectAlerts.addAll(Lists.newArrayList(
+      new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_SMALLER, null, "100"), // generates warning because classes 20 < 100
+      new Alert(null, CoreMetrics.COVERAGE, Alert.OPERATOR_SMALLER, "50.0", "80.0"))); // generates error because coverage 35% < 50%
+
+    verifier.decorate(project, context);
+
+    verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.ERROR, null)));
+
+    verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.WARN)));
+    verify(context).saveMeasure(argThat(hasLevel(measureCoverage, Metric.Level.ERROR)));
+  }
+
+  @Test
+  public void globalLabelShouldAggregateAllLabels() {
+    when(i18n.message(any(Locale.class), eq("metric.classes.name"), anyString())).thenReturn("Classes");
+    when(i18n.message(any(Locale.class), eq("metric.coverage.name"), anyString())).thenReturn("Coverages");
+    projectAlerts.addAll(Lists.newArrayList(
+      new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_SMALLER, null, "10000"), // there are 20 classes, error threshold is higher =>
+      // alert
+      new Alert(null, CoreMetrics.COVERAGE, Alert.OPERATOR_SMALLER, "50.0", "80.0"))); // coverage is 35%, warning threshold is higher =>
+    // alert
+
+    verifier.decorate(project, context);
+
+    verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.ERROR, "Classes < 10000, Coverages < 50.0")));
+  }
+
+  @Test
+  public void alertLabelUsesL10nMetricName() {
+    Metric metric = new Metric.Builder("rating", "Rating", Metric.ValueType.INT).create();
+
+    // metric name is declared in l10n bundle
+    when(i18n.message(any(Locale.class), eq("metric.rating.name"), anyString())).thenReturn("THE RATING");
+
+    when(context.getMeasure(metric)).thenReturn(new Measure(metric, 4d));
+    projectAlerts.addAll(Lists.newArrayList(new Alert(null, metric, Alert.OPERATOR_SMALLER, "10", null)));
+    verifier.decorate(project, context);
+
+    verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.ERROR, "THE RATING < 10")));
+  }
+
+  @Test
+  public void alertLabelUsesMetricNameIfMissingL10nBundle() {
+    // the third argument is Metric#getName()
+    when(i18n.message(any(Locale.class), eq("metric.classes.name"), eq("Classes"))).thenReturn("Classes");
+    projectAlerts.addAll(Lists.newArrayList(
+      // there are 20 classes, error threshold is higher => alert
+      new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_SMALLER, "10000", null)
+    ));
+
+    verifier.decorate(project, context);
+
+    verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.ERROR, "Classes < 10000")));
+  }
+
+  @Test
+  public void shouldBeOkIfPeriodVariationIsEnough() {
+    measureClasses.setVariation1(0d);
+    measureCoverage.setVariation2(50d);
+    measureComplexity.setVariation3(2d);
+
+    projectAlerts.addAll(Lists.newArrayList(
+      new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "10", 1), // ok because no variation
+      new Alert(null, CoreMetrics.COVERAGE, Alert.OPERATOR_SMALLER, null, "40.0", 2), // ok because coverage increases of 50%, which is more
+      // than 40%
+      new Alert(null, CoreMetrics.COMPLEXITY, Alert.OPERATOR_GREATER, null, "5", 3) // ok because complexity increases of 2, which is less
+      // than 5
+    ));
+
+    verifier.decorate(project, context);
+
+    verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.OK, null)));
+
+    verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.OK)));
+    verify(context).saveMeasure(argThat(hasLevel(measureCoverage, Metric.Level.OK)));
+    verify(context).saveMeasure(argThat(hasLevel(measureComplexity, Metric.Level.OK)));
+  }
+
+  @Test
+  public void shouldGenerateWarningIfPeriodVariationIsNotEnough() {
+    measureClasses.setVariation1(40d);
+    measureCoverage.setVariation2(5d);
+    measureComplexity.setVariation3(70d);
+
+    projectAlerts.addAll(Lists.newArrayList(
+      new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "30", 1), // generates warning because classes increases of 40,
+      // which is greater than 30
+      new Alert(null, CoreMetrics.COVERAGE, Alert.OPERATOR_SMALLER, null, "10.0", 2), // generates warning because coverage increases of 5%,
+      // which is smaller than 10%
+      new Alert(null, CoreMetrics.COMPLEXITY, Alert.OPERATOR_GREATER, null, "60", 3) // generates warning because complexity increases of
+      // 70, which is smaller than 60
+    ));
+
+    verifier.decorate(project, context);
+
+    verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.WARN, null)));
+
+    verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.WARN)));
+    verify(context).saveMeasure(argThat(hasLevel(measureCoverage, Metric.Level.WARN)));
+    verify(context).saveMeasure(argThat(hasLevel(measureComplexity, Metric.Level.WARN)));
+  }
+
+  @Test
+  public void shouldBeOkIfVariationIsNull() {
+    measureClasses.setVariation1(null);
+
+    projectAlerts.addAll(Lists.newArrayList(
+      new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "10", 1)));
+
+    verifier.decorate(project, context);
+
+    verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.OK, null)));
+    verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.OK)));
+  }
+
+  @Test
+  public void shouldVariationPeriodValueCouldBeUsedForRatingMetric() {
+    Metric ratingMetric = new Metric.Builder("key_rating_metric", "Rating metric", Metric.ValueType.RATING).create();
+    Measure measureRatingMetric = new Measure(ratingMetric, 150d);
+    measureRatingMetric.setVariation1(50d);
+    when(context.getMeasure(ratingMetric)).thenReturn(measureRatingMetric);
+
+    projectAlerts.addAll(Lists.newArrayList(
+      new Alert(null, ratingMetric, Alert.OPERATOR_GREATER, null, "100", 1)
+    ));
+
+    verifier.decorate(project, context);
+
+    verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.OK, null)));
+    verify(context).saveMeasure(argThat(hasLevel(measureRatingMetric, Metric.Level.OK)));
+  }
+
+  @Test(expected = IllegalStateException.class)
+  public void shouldAllowOnlyVariationPeriodOneGlobalPeriods() {
+    measureClasses.setVariation4(40d);
+
+    projectAlerts.addAll(Lists.newArrayList(
+      new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "30", 4)
+    ));
+
+    verifier.decorate(project, context);
+  }
+
+  @Test(expected = NotImplementedException.class)
+  public void shouldNotAllowPeriodVariationAlertOnStringMetric() {
+    Measure measure = new Measure(CoreMetrics.SCM_AUTHORS_BY_LINE, 100d);
+    measure.setVariation1(50d);
+    when(context.getMeasure(CoreMetrics.SCM_AUTHORS_BY_LINE)).thenReturn(measure);
+
+    projectAlerts.addAll(Lists.newArrayList(
+      new Alert(null, CoreMetrics.SCM_AUTHORS_BY_LINE, Alert.OPERATOR_GREATER, null, "30", 1)
+    ));
+
+    verifier.decorate(project, context);
+  }
+
+  @Test
+  public void shouldLabelAlertContainsPeriod() {
+    measureClasses.setVariation1(40d);
+
+    when(i18n.message(any(Locale.class), eq("metric.classes.name"), anyString())).thenReturn("Classes");
+    when(periods.label(snapshot, 1)).thenReturn("since someday");
+
+    projectAlerts.addAll(Lists.newArrayList(
+      new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "30", 1) // generates warning because classes increases of 40,
+      // which is greater than 30
+    ));
+
+    verifier.decorate(project, context);
+
+    verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.WARN, "Classes variation > 30 since someday")));
+  }
+
+  @Test
+  public void shouldLabelAlertForNewMetricDoNotContainsVariationWord() {
+    Metric newMetric = new Metric.Builder("new_metric_key", "New Metric", Metric.ValueType.INT).create();
+    Measure measure = new Measure(newMetric, 15d);
+    measure.setVariation1(50d);
+    when(context.getMeasure(newMetric)).thenReturn(measure);
+    measureClasses.setVariation1(40d);
+
+    when(i18n.message(any(Locale.class), eq("metric.new_metric_key.name"), anyString())).thenReturn("New Measure");
+    when(periods.label(snapshot, 1)).thenReturn("since someday");
+
+    projectAlerts.addAll(Lists.newArrayList(
+      new Alert(null, newMetric, Alert.OPERATOR_GREATER, null, "30", 1) // generates warning because classes increases of 40, which is
+      // greater than 30
+    ));
+
+    verifier.decorate(project, context);
+
+    verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.WARN, "New Measure > 30 since someday")));
+  }
+
+  private ArgumentMatcher<Measure> matchesMetric(final Metric metric, final Metric.Level alertStatus, final String alertText) {
+    return new ArgumentMatcher<Measure>() {
+      @Override
+      public boolean matches(Object arg) {
+        boolean result = ((Measure) arg).getMetric().equals(metric) && ((Measure) arg).getAlertStatus() == alertStatus;
+        if (result && alertText != null) {
+          result = alertText.equals(((Measure) arg).getAlertText());
+        }
+        return result;
+      }
+    };
+  }
+
+  private ArgumentMatcher<Measure> hasLevel(final Measure measure, final Metric.Level alertStatus) {
+    return new ArgumentMatcher<Measure>() {
+      @Override
+      public boolean matches(Object arg) {
+        return arg == measure && ((Measure) arg).getAlertStatus().equals(alertStatus);
+      }
+    };
+  }
+
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/rule/QProfileVerifierTest.java b/sonar-batch/src/test/java/org/sonar/batch/rule/QProfileVerifierTest.java
new file mode 100644 (file)
index 0000000..bd82f70
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.rule;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.slf4j.Logger;
+import org.sonar.api.batch.fs.internal.DefaultFileSystem;
+import org.sonar.api.config.Settings;
+import org.sonar.api.profiles.RulesProfile;
+import org.sonar.api.utils.MessageException;
+import org.sonar.batch.rule.ModuleQProfiles.QProfile;
+
+import static org.mockito.Mockito.*;
+
+public class QProfileVerifierTest {
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  DefaultFileSystem fs = new DefaultFileSystem();
+  ModuleQProfiles profiles;
+  Settings settings = new Settings();
+  RulesProfile javaRulesProfile;
+  RulesProfile cobolRulesProfile;
+
+  @Before
+  public void before() {
+    profiles = mock(ModuleQProfiles.class);
+    QProfile javaProfile = mock(QProfile.class);
+    when(javaProfile.name()).thenReturn("My Java profile");
+    javaRulesProfile = mock(RulesProfile.class);
+    when(profiles.findByLanguage("java")).thenReturn(javaProfile);
+    QProfile cobolProfile = mock(QProfile.class);
+    when(cobolProfile.name()).thenReturn("My Cobol profile");
+    cobolRulesProfile = mock(RulesProfile.class);
+    when(profiles.findByLanguage("cobol")).thenReturn(cobolProfile);
+  }
+
+  @Test
+  public void should_log_all_used_profiles() {
+    fs.addLanguages("java", "cobol");
+    QProfileVerifier profileLogger = new QProfileVerifier(settings, fs, profiles);
+    Logger logger = mock(Logger.class);
+    profileLogger.execute(logger);
+
+    verify(logger).info("Quality profile for {}: {}", "java", "My Java profile");
+    verify(logger).info("Quality profile for {}: {}", "cobol", "My Cobol profile");
+  }
+
+  @Test
+  public void should_fail_if_default_profile_not_used() {
+    fs.addLanguages("java", "cobol");
+    settings.setProperty("sonar.profile", "Unknown");
+
+    QProfileVerifier profileLogger = new QProfileVerifier(settings, fs, profiles);
+
+    thrown.expect(MessageException.class);
+    thrown.expectMessage("sonar.profile was set to 'Unknown' but didn't match any profile for any language. Please check your configuration.");
+
+    profileLogger.execute();
+  }
+
+  @Test
+  public void should_not_fail_if_no_language_on_project() {
+    settings.setProperty("sonar.profile", "Unknown");
+
+    QProfileVerifier profileLogger = new QProfileVerifier(settings, fs, profiles);
+
+    profileLogger.execute();
+
+  }
+
+  @Test
+  public void should_not_fail_if_default_profile_used_at_least_once() {
+    fs.addLanguages("java", "cobol");
+    settings.setProperty("sonar.profile", "My Java profile");
+
+    QProfileVerifier profileLogger = new QProfileVerifier(settings, fs, profiles);
+
+    profileLogger.execute();
+  }
+}
index 0ef69de6f4d948b17cb4616be3518c1093d4d589..62559d651b26c000dde2a97e516c8b0f0af6a8ef 100644 (file)
@@ -22,25 +22,30 @@ package org.sonar.batch.scan;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
+import org.sonar.api.batch.fs.internal.DefaultFileSystem;
 import org.sonar.api.config.Settings;
 import org.sonar.api.resources.Java;
 import org.sonar.api.resources.Languages;
 import org.sonar.api.utils.MessageException;
 
+import static org.fest.assertions.Assertions.assertThat;
+
 public class LanguageVerifierTest {
 
   Settings settings = new Settings();
   Languages languages = new Languages(Java.INSTANCE);
+  DefaultFileSystem fs = new DefaultFileSystem();
 
   @Rule
   public ExpectedException thrown = ExpectedException.none();
 
   @Test
   public void language_is_not_set() throws Exception {
-    LanguageVerifier verifier = new LanguageVerifier(settings, languages);
+    LanguageVerifier verifier = new LanguageVerifier(settings, languages, fs);
     verifier.start();
 
-    // no failure
+    // no failure and no language is forced
+    assertThat(fs.languages()).isEmpty();
 
     verifier.stop();
   }
@@ -49,10 +54,11 @@ public class LanguageVerifierTest {
   public void language_is_valid() throws Exception {
     settings.setProperty("sonar.language", "java");
 
-    LanguageVerifier verifier = new LanguageVerifier(settings, languages);
+    LanguageVerifier verifier = new LanguageVerifier(settings, languages, fs);
     verifier.start();
 
-    // no failure
+    // no failure and language is hardly registered
+    assertThat(fs.languages()).contains("java");
 
     verifier.stop();
   }
@@ -63,7 +69,7 @@ public class LanguageVerifierTest {
     thrown.expectMessage("You must install a plugin that supports the language 'php'");
 
     settings.setProperty("sonar.language", "php");
-    LanguageVerifier verifier = new LanguageVerifier(settings, languages);
+    LanguageVerifier verifier = new LanguageVerifier(settings, languages, fs);
     verifier.start();
   }
 }
index 8b377186b4e46dbe73eb206bb072d706908b463a..d5feb04f4d88613a2b1fef341cedfff6f96ee417 100644 (file)
@@ -247,4 +247,29 @@ public class Alert extends BaseIdentifiable implements Cloneable {
     return new Alert(getRulesProfile(), getMetric(), getOperator(), getValueError(), getValueWarning(), getPeriod());
   }
 
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    Alert alert = (Alert) o;
+    if (metric != null ? !metric.equals(alert.metric) : alert.metric != null) {
+      return false;
+    }
+    if (period != null ? !period.equals(alert.period) : alert.period != null) {
+      return false;
+    }
+    return !(rulesProfile != null ? !rulesProfile.equals(alert.rulesProfile) : alert.rulesProfile != null);
+  }
+
+  @Override
+  public int hashCode() {
+    int result = rulesProfile != null ? rulesProfile.hashCode() : 0;
+    result = 31 * result + (metric != null ? metric.hashCode() : 0);
+    result = 31 * result + (period != null ? period.hashCode() : 0);
+    return result;
+  }
 }