2 * SonarQube, open source software quality management tool.
3 * Copyright (C) 2008-2013 SonarSource
4 * mailto:contact AT sonarsource DOT com
6 * SonarQube is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * SonarQube is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 package org.sonar.plugins.core.sensors;
22 import org.apache.commons.lang.NotImplementedException;
23 import org.junit.Before;
24 import org.junit.Test;
25 import org.mockito.ArgumentMatcher;
26 import org.mockito.Mockito;
27 import org.sonar.api.batch.DecoratorBarriers;
28 import org.sonar.api.batch.DecoratorContext;
29 import org.sonar.api.database.model.Snapshot;
30 import org.sonar.api.i18n.I18n;
31 import org.sonar.api.measures.CoreMetrics;
32 import org.sonar.api.measures.Measure;
33 import org.sonar.api.measures.Metric;
34 import org.sonar.api.profiles.Alert;
35 import org.sonar.api.profiles.RulesProfile;
36 import org.sonar.api.resources.Project;
37 import org.sonar.api.resources.Qualifiers;
38 import org.sonar.api.resources.Resource;
39 import org.sonar.api.test.IsMeasure;
40 import org.sonar.core.timemachine.Periods;
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.Locale;
46 import static com.google.common.collect.Lists.newArrayList;
47 import static org.fest.assertions.Assertions.assertThat;
48 import static org.mockito.Matchers.anyString;
49 import static org.mockito.Matchers.argThat;
50 import static org.mockito.Mockito.*;
52 public class CheckAlertThresholdsTest {
54 private CheckAlertThresholds decorator;
55 private DecoratorContext context;
56 private RulesProfile profile;
58 private Measure measureClasses;
59 private Measure measureCoverage;
60 private Measure measureComplexity;
62 private Resource project;
63 private Snapshot snapshot;
64 private Periods periods;
69 context = mock(DecoratorContext.class);
70 periods = mock(Periods.class);
71 i18n = mock(I18n.class);
72 when(i18n.message(any(Locale.class), eq("variation"), eq("variation"))).thenReturn("variation");
74 measureClasses = new Measure(CoreMetrics.CLASSES, 20d);
75 measureCoverage = new Measure(CoreMetrics.COVERAGE, 35d);
76 measureComplexity = new Measure(CoreMetrics.COMPLEXITY, 50d);
78 when(context.getMeasure(CoreMetrics.CLASSES)).thenReturn(measureClasses);
79 when(context.getMeasure(CoreMetrics.COVERAGE)).thenReturn(measureCoverage);
80 when(context.getMeasure(CoreMetrics.COMPLEXITY)).thenReturn(measureComplexity);
82 snapshot = mock(Snapshot.class);
83 profile = mock(RulesProfile.class);
84 decorator = new CheckAlertThresholds(snapshot, profile, periods, i18n);
85 project = mock(Resource.class);
86 when(project.getQualifier()).thenReturn(Qualifiers.PROJECT);
90 public void should_generates_alert_status(){
91 assertThat(decorator.generatesAlertStatus()).isEqualTo(CoreMetrics.ALERT_STATUS);
95 public void should_depends_on_variations(){
96 assertThat(decorator.dependsOnVariations()).isEqualTo(DecoratorBarriers.END_OF_TIME_MACHINE);
100 public void should_depends_upon_metrics(){
101 when(profile.getAlerts()).thenReturn(newArrayList(new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "20")));
102 assertThat(decorator.dependsUponMetrics()).containsOnly(CoreMetrics.CLASSES);
106 public void shouldNotCreateAlertsWhenNoThresholds() {
107 when(profile.getAlerts()).thenReturn(new ArrayList<Alert>());
108 assertThat(decorator.shouldExecuteOnProject(new Project("key"))).isFalse();
112 public void shouldBeOkWhenNoAlert() {
113 when(profile.getAlerts()).thenReturn(Arrays.asList(
114 new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "20"),
115 new Alert(null, CoreMetrics.COVERAGE, Alert.OPERATOR_GREATER, null, "35.0")));
117 decorator.decorate(project, context);
119 verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.ALERT_STATUS, Metric.Level.OK.toString())));
120 verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.OK)));
121 verify(context).saveMeasure(argThat(hasLevel(measureCoverage, Metric.Level.OK)));
125 public void checkRootProjectsOnly() {
126 when(project.getQualifier()).thenReturn(Resource.QUALIFIER_FILE);
127 when(profile.getAlerts()).thenReturn(Arrays.asList(
128 new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "20"),
129 new Alert(null, CoreMetrics.COVERAGE, Alert.OPERATOR_GREATER, null, "35.0")));
131 decorator.decorate(project, context);
133 verify(context, never()).saveMeasure(any(Measure.class));
137 public void shouldGenerateWarnings() {
138 when(profile.getAlerts()).thenReturn(Arrays.asList(
139 new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "100"),
140 new Alert(null, CoreMetrics.COVERAGE, Alert.OPERATOR_SMALLER, null, "95.0"))); // generates warning because coverage 35% < 95%
142 decorator.decorate(project, context);
144 verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.WARN, null)));
146 verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.OK)));
147 verify(context).saveMeasure(argThat(hasLevel(measureCoverage, Metric.Level.WARN)));
152 public void globalStatusShouldBeErrorIfWarningsAndErrors() {
153 when(profile.getAlerts()).thenReturn(Arrays.asList(
154 new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_SMALLER, null, "100"), // generates warning because classes 20 < 100
155 new Alert(null, CoreMetrics.COVERAGE, Alert.OPERATOR_SMALLER, "50.0", "80.0"))); // generates error because coverage 35% < 50%
157 decorator.decorate(project, context);
159 verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.ERROR, null)));
161 verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.WARN)));
162 verify(context).saveMeasure(argThat(hasLevel(measureCoverage, Metric.Level.ERROR)));
166 public void globalLabelShouldAggregateAllLabels() {
167 when(i18n.message(any(Locale.class), eq("metric.classes.name"), anyString())).thenReturn("Classes");
168 when(i18n.message(any(Locale.class), eq("metric.coverage.name"), anyString())).thenReturn("Coverages");
169 when(profile.getAlerts()).thenReturn(Arrays.asList(
170 new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_SMALLER, null, "10000"), // there are 20 classes, error threshold is higher => alert
171 new Alert(null, CoreMetrics.COVERAGE, Alert.OPERATOR_SMALLER, "50.0", "80.0"))); // coverage is 35%, warning threshold is higher => alert
173 decorator.decorate(project, context);
175 verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.ERROR, "Classes < 10000, Coverages < 50.0")));
179 public void alertLabelUsesL10nMetricName() {
180 Metric metric = new Metric.Builder("rating", "Rating", Metric.ValueType.INT).create();
182 // metric name is declared in l10n bundle
183 when(i18n.message(any(Locale.class), eq("metric.rating.name"), anyString())).thenReturn("THE RATING");
185 when(context.getMeasure(metric)).thenReturn(new Measure(metric, 4d));
186 when(profile.getAlerts()).thenReturn(Arrays.<Alert>asList(new Alert(null, metric, Alert.OPERATOR_SMALLER, "10", null)));
187 decorator.decorate(project, context);
189 verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.ERROR, "THE RATING < 10")));
193 public void alertLabelUsesMetricNameIfMissingL10nBundle() {
194 // the third argument is Metric#getName()
195 when(i18n.message(any(Locale.class), eq("metric.classes.name"), eq("Classes"))).thenReturn("Classes");
196 when(profile.getAlerts()).thenReturn(Arrays.<Alert>asList(
197 // there are 20 classes, error threshold is higher => alert
198 new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_SMALLER, "10000", null)
201 decorator.decorate(project, context);
203 verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.ERROR, "Classes < 10000")));
207 public void shouldBeOkIfPeriodVariationIsEnough() {
208 measureClasses.setVariation1(0d);
209 measureCoverage.setVariation2(50d);
210 measureComplexity.setVariation3(2d);
212 when(profile.getAlerts()).thenReturn(Arrays.asList(
213 new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "10", 1), // ok because no variation
214 new Alert(null, CoreMetrics.COVERAGE, Alert.OPERATOR_SMALLER, null, "40.0", 2), // ok because coverage increases of 50%, which is more than 40%
215 new Alert(null, CoreMetrics.COMPLEXITY, Alert.OPERATOR_GREATER, null, "5", 3) // ok because complexity increases of 2, which is less than 5
218 decorator.decorate(project, context);
220 verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.OK, null)));
222 verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.OK)));
223 verify(context).saveMeasure(argThat(hasLevel(measureCoverage, Metric.Level.OK)));
224 verify(context).saveMeasure(argThat(hasLevel(measureComplexity, Metric.Level.OK)));
228 public void shouldGenerateWarningIfPeriodVariationIsNotEnough() {
229 measureClasses.setVariation1(40d);
230 measureCoverage.setVariation2(5d);
231 measureComplexity.setVariation3(70d);
233 when(profile.getAlerts()).thenReturn(Arrays.asList(
234 new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "30", 1), // generates warning because classes increases of 40, which is greater than 30
235 new Alert(null, CoreMetrics.COVERAGE, Alert.OPERATOR_SMALLER, null, "10.0", 2), // generates warning because coverage increases of 5%, which is smaller than 10%
236 new Alert(null, CoreMetrics.COMPLEXITY, Alert.OPERATOR_GREATER, null, "60", 3) // generates warning because complexity increases of 70, which is smaller than 60
239 decorator.decorate(project, context);
241 verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.WARN, null)));
243 verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.WARN)));
244 verify(context).saveMeasure(argThat(hasLevel(measureCoverage, Metric.Level.WARN)));
245 verify(context).saveMeasure(argThat(hasLevel(measureComplexity, Metric.Level.WARN)));
249 public void shouldBeOkIfVariationIsNull() {
250 measureClasses.setVariation1(null);
252 when(profile.getAlerts()).thenReturn(Arrays.asList(
253 new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "10", 1)
256 decorator.decorate(project, context);
258 verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.OK, null)));
259 verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.OK)));
263 public void shouldVariationPeriodValueCouldBeUsedForRatingMetric() {
264 Metric ratingMetric = new Metric.Builder("key_rating_metric", "Rating metric", Metric.ValueType.RATING).create();
265 Measure measureRatingMetric = new Measure(ratingMetric, 150d);
266 measureRatingMetric.setVariation1(50d);
267 when(context.getMeasure(ratingMetric)).thenReturn(measureRatingMetric);
269 when(profile.getAlerts()).thenReturn(Arrays.asList(
270 new Alert(null, ratingMetric, Alert.OPERATOR_GREATER, null, "100", 1)
273 decorator.decorate(project, context);
275 verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.OK, null)));
276 verify(context).saveMeasure(argThat(hasLevel(measureRatingMetric, Metric.Level.OK)));
279 @Test(expected = IllegalStateException.class)
280 public void shouldAllowOnlyVariationPeriodOneGlobalPeriods() {
281 measureClasses.setVariation4(40d);
283 when(profile.getAlerts()).thenReturn(Arrays.asList(
284 new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "30", 4)
287 decorator.decorate(project, context);
290 @Test(expected = NotImplementedException.class)
291 public void shouldNotAllowPeriodVariationAlertOnStringMetric() {
292 Measure measure = new Measure(CoreMetrics.SCM_AUTHORS_BY_LINE, 100d);
293 measure.setVariation1(50d);
294 when(context.getMeasure(CoreMetrics.SCM_AUTHORS_BY_LINE)).thenReturn(measure);
296 when(profile.getAlerts()).thenReturn(Arrays.asList(
297 new Alert(null, CoreMetrics.SCM_AUTHORS_BY_LINE, Alert.OPERATOR_GREATER, null, "30", 1)
300 decorator.decorate(project, context);
304 public void shouldLabelAlertContainsPeriod() {
305 measureClasses.setVariation1(40d);
307 when(i18n.message(any(Locale.class), eq("metric.classes.name"), anyString())).thenReturn("Classes");
308 when(periods.label(snapshot, 1)).thenReturn("since someday");
310 when(profile.getAlerts()).thenReturn(Arrays.asList(
311 new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "30", 1) // generates warning because classes increases of 40, which is greater than 30
314 decorator.decorate(project, context);
316 verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.WARN, "Classes variation > 30 since someday")));
320 public void shouldLabelAlertForNewMetricDoNotContainsVariationWord() {
321 Metric newMetric = new Metric.Builder("new_metric_key", "New Metric", Metric.ValueType.INT).create();
322 Measure measure = new Measure(newMetric, 15d);
323 measure.setVariation1(50d);
324 when(context.getMeasure(newMetric)).thenReturn(measure);
325 measureClasses.setVariation1(40d);
327 when(i18n.message(any(Locale.class), eq("metric.new_metric_key.name"), anyString())).thenReturn("New Measure");
328 when(periods.label(snapshot, 1)).thenReturn("since someday");
330 when(profile.getAlerts()).thenReturn(Arrays.asList(
331 new Alert(null, newMetric, Alert.OPERATOR_GREATER, null, "30", 1) // generates warning because classes increases of 40, which is greater than 30
334 decorator.decorate(project, context);
336 verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.WARN, "New Measure > 30 since someday")));
339 private ArgumentMatcher<Measure> matchesMetric(final Metric metric, final Metric.Level alertStatus, final String alertText) {
340 return new ArgumentMatcher<Measure>() {
342 public boolean matches(Object arg) {
343 boolean result = ((Measure) arg).getMetric().equals(metric) && ((Measure) arg).getAlertStatus() == alertStatus;
344 if (result && alertText != null) {
345 result = alertText.equals(((Measure) arg).getAlertText());
352 private ArgumentMatcher<Measure> hasLevel(final Measure measure, final Metric.Level alertStatus) {
353 return new ArgumentMatcher<Measure>() {
355 public boolean matches(Object arg) {
356 return arg == measure && ((Measure) arg).getAlertStatus().equals(alertStatus);