From: Simon Brandhof Date: Wed, 26 Feb 2014 14:25:11 +0000 (+0100) Subject: SONAR-926 fix loading of quality profile alerts on multi-modules projects X-Git-Tag: 4.3~674 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=d9b3a077d3eb970e85c04bed1b11246b135eb5a9;p=sonarqube.git SONAR-926 fix loading of quality profile alerts on multi-modules projects --- diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java index 0d2bd21ac5e..0031a119e2e 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java @@ -19,112 +19,36 @@ */ 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 index 61e6967ff3e..00000000000 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/AlertUtils.java +++ /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 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 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 index 1580d14f1ed..00000000000 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/sensors/CheckAlertThresholds.java +++ /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 dependsUponMetrics() { - List 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 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 index 4856c53fdb9..00000000000 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/AlertUtilsTest.java +++ /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 index 6154147a1ee..00000000000 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/sensors/CheckAlertThresholdsTest.java +++ /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()); - 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.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.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 matchesMetric(final Metric metric, final Metric.Level alertStatus, final String alertText) { - return new ArgumentMatcher() { - @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 hasLevel(final Measure measure, final Metric.Level alertStatus) { - return new ArgumentMatcher() { - @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 index 9b04956ebae..00000000000 --- a/sonar-batch/src/main/java/org/sonar/batch/fs/package-info.java +++ /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; diff --git a/sonar-batch/src/main/java/org/sonar/batch/phases/PhaseExecutor.java b/sonar-batch/src/main/java/org/sonar/batch/phases/PhaseExecutor.java index 2043d7ee452..8893efb8d0d 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/phases/PhaseExecutor.java +++ b/sonar-batch/src/main/java/org/sonar/batch/phases/PhaseExecutor.java @@ -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 index 0aa232db26f..00000000000 --- a/sonar-batch/src/main/java/org/sonar/batch/phases/ProfileLogger.java +++ /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 index 00000000000..0f4a87476f8 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/qualitygate/AlertUtils.java @@ -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 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 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 index 00000000000..9210327407d --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/qualitygate/ProjectAlerts.java @@ -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 alerts = Sets.newHashSet(); + + void addAll(Collection alerts) { + this.alerts.addAll(alerts); + } + + Set 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 index 00000000000..f43e9390253 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/qualitygate/QualityGateLoader.java @@ -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 index 00000000000..59ce484ec57 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/qualitygate/QualityGateVerifier.java @@ -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 dependsUponMetrics() { + Set 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 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 index 00000000000..8ccd3b1a33f --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/qualitygate/package-info.java @@ -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 index c1d7a116a19..00000000000 --- a/sonar-batch/src/main/java/org/sonar/batch/rule/ProjectAlerts.java +++ /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 alerts = Lists.newArrayList(); - - public ProjectAlerts() { - } - - public void add(Alert alert) { - alerts.add(alert); - } - - public List 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 index 00000000000..226affd3e3b --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/rule/QProfileVerifier.java @@ -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."); + } + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/LanguageVerifier.java b/sonar-batch/src/main/java/org/sonar/batch/scan/LanguageVerifier.java index b99fbd5ba7d..02115c858f5 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/LanguageVerifier.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/LanguageVerifier.java @@ -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); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java index 4fca5d3596f..d8103d52f29 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java @@ -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(), diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java index 500b42bf399..c86e1fc25c6 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java @@ -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 index e3bcc49929e..00000000000 --- a/sonar-batch/src/test/java/org/sonar/batch/phases/ProfileLoggerTest.java +++ /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 index 00000000000..d785564815a --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/qualitygate/AlertUtilsTest.java @@ -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 index 00000000000..86df906286d --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/qualitygate/ProjectAlertsTest.java @@ -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 index 00000000000..5989492d8b2 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/qualitygate/QualityGateLoaderTest.java @@ -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 index 00000000000..b0b92fab2ca --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/qualitygate/QualityGateVerifierTest.java @@ -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 matchesMetric(final Metric metric, final Metric.Level alertStatus, final String alertText) { + return new ArgumentMatcher() { + @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 hasLevel(final Measure measure, final Metric.Level alertStatus) { + return new ArgumentMatcher() { + @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 index 00000000000..bd82f70eea8 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/rule/QProfileVerifierTest.java @@ -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(); + } +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/LanguageVerifierTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/LanguageVerifierTest.java index 0ef69de6f4d..62559d651b2 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/scan/LanguageVerifierTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/scan/LanguageVerifierTest.java @@ -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(); } } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/profiles/Alert.java b/sonar-plugin-api/src/main/java/org/sonar/api/profiles/Alert.java index 8b377186b4e..d5feb04f4d8 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/profiles/Alert.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/profiles/Alert.java @@ -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; + } }