]> source.dussan.org Git - sonarqube.git/blob
46e43b20a410a31f35880772cf88033e1278206d
[sonarqube.git] /
1 /*
2  * SonarQube, open source software quality management tool.
3  * Copyright (C) 2008-2013 SonarSource
4  * mailto:contact AT sonarsource DOT com
5  *
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.
10  *
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.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20 package org.sonar.plugins.core.sensors;
21
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;
41
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.Locale;
45
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.*;
51
52 public class CheckAlertThresholdsTest {
53
54   private CheckAlertThresholds decorator;
55   private DecoratorContext context;
56   private RulesProfile profile;
57
58   private Measure measureClasses;
59   private Measure measureCoverage;
60   private Measure measureComplexity;
61
62   private Resource project;
63   private Snapshot snapshot;
64   private Periods periods;
65   private I18n i18n;
66
67   @Before
68   public void setup() {
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");
73
74     measureClasses = new Measure(CoreMetrics.CLASSES, 20d);
75     measureCoverage = new Measure(CoreMetrics.COVERAGE, 35d);
76     measureComplexity = new Measure(CoreMetrics.COMPLEXITY, 50d);
77
78     when(context.getMeasure(CoreMetrics.CLASSES)).thenReturn(measureClasses);
79     when(context.getMeasure(CoreMetrics.COVERAGE)).thenReturn(measureCoverage);
80     when(context.getMeasure(CoreMetrics.COMPLEXITY)).thenReturn(measureComplexity);
81
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);
87   }
88
89   @Test
90   public void should_generates_alert_status(){
91     assertThat(decorator.generatesAlertStatus()).isEqualTo(CoreMetrics.ALERT_STATUS);
92   }
93
94   @Test
95   public void should_depends_on_variations(){
96     assertThat(decorator.dependsOnVariations()).isEqualTo(DecoratorBarriers.END_OF_TIME_MACHINE);
97   }
98
99   @Test
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);
103   }
104
105   @Test
106   public void shouldNotCreateAlertsWhenNoThresholds() {
107     when(profile.getAlerts()).thenReturn(new ArrayList<Alert>());
108     assertThat(decorator.shouldExecuteOnProject(new Project("key"))).isFalse();
109   }
110
111   @Test
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")));
116
117     decorator.decorate(project, context);
118
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)));
122   }
123
124   @Test
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")));
130
131     decorator.decorate(project, context);
132
133     verify(context, never()).saveMeasure(any(Measure.class));
134   }
135
136   @Test
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%
141
142     decorator.decorate(project, context);
143
144     verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.WARN, null)));
145
146     verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.OK)));
147     verify(context).saveMeasure(argThat(hasLevel(measureCoverage, Metric.Level.WARN)));
148
149   }
150
151   @Test
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%
156
157     decorator.decorate(project, context);
158
159     verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.ERROR, null)));
160
161     verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.WARN)));
162     verify(context).saveMeasure(argThat(hasLevel(measureCoverage, Metric.Level.ERROR)));
163   }
164
165   @Test
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
172
173     decorator.decorate(project, context);
174
175     verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.ERROR, "Classes < 10000, Coverages < 50.0")));
176   }
177
178   @Test
179   public void alertLabelUsesL10nMetricName() {
180     Metric metric = new Metric.Builder("rating", "Rating", Metric.ValueType.INT).create();
181
182     // metric name is declared in l10n bundle
183     when(i18n.message(any(Locale.class), eq("metric.rating.name"), anyString())).thenReturn("THE RATING");
184
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);
188
189     verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.ERROR, "THE RATING < 10")));
190   }
191
192   @Test
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)
199     ));
200
201     decorator.decorate(project, context);
202
203     verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.ERROR, "Classes < 10000")));
204   }
205
206   @Test
207   public void shouldBeOkIfPeriodVariationIsEnough() {
208     measureClasses.setVariation1(0d);
209     measureCoverage.setVariation2(50d);
210     measureComplexity.setVariation3(2d);
211
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
216     ));
217
218     decorator.decorate(project, context);
219
220     verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.OK, null)));
221
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)));
225   }
226
227   @Test
228   public void shouldGenerateWarningIfPeriodVariationIsNotEnough() {
229     measureClasses.setVariation1(40d);
230     measureCoverage.setVariation2(5d);
231     measureComplexity.setVariation3(70d);
232
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
237     ));
238
239     decorator.decorate(project, context);
240
241     verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.WARN, null)));
242
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)));
246   }
247
248   @Test
249   public void shouldBeOkIfVariationIsNull() {
250     measureClasses.setVariation1(null);
251
252     when(profile.getAlerts()).thenReturn(Arrays.asList(
253         new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "10", 1)
254     ));
255
256     decorator.decorate(project, context);
257
258     verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.OK, null)));
259     verify(context).saveMeasure(argThat(hasLevel(measureClasses, Metric.Level.OK)));
260   }
261
262   @Test
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);
268
269     when(profile.getAlerts()).thenReturn(Arrays.asList(
270         new Alert(null, ratingMetric, Alert.OPERATOR_GREATER, null, "100", 1)
271     ));
272
273     decorator.decorate(project, context);
274
275     verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.OK, null)));
276     verify(context).saveMeasure(argThat(hasLevel(measureRatingMetric, Metric.Level.OK)));
277   }
278
279   @Test(expected = IllegalStateException.class)
280   public void shouldAllowOnlyVariationPeriodOneGlobalPeriods() {
281     measureClasses.setVariation4(40d);
282
283     when(profile.getAlerts()).thenReturn(Arrays.asList(
284         new Alert(null, CoreMetrics.CLASSES, Alert.OPERATOR_GREATER, null, "30", 4)
285     ));
286
287     decorator.decorate(project, context);
288   }
289
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);
295
296     when(profile.getAlerts()).thenReturn(Arrays.asList(
297         new Alert(null, CoreMetrics.SCM_AUTHORS_BY_LINE, Alert.OPERATOR_GREATER, null, "30", 1)
298     ));
299
300     decorator.decorate(project, context);
301   }
302
303   @Test
304   public void shouldLabelAlertContainsPeriod() {
305     measureClasses.setVariation1(40d);
306
307     when(i18n.message(any(Locale.class), eq("metric.classes.name"), anyString())).thenReturn("Classes");
308     when(periods.label(snapshot, 1)).thenReturn("since someday");
309
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
312     ));
313
314     decorator.decorate(project, context);
315
316     verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.WARN, "Classes variation > 30 since someday")));
317   }
318
319   @Test
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);
326
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");
329
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
332     ));
333
334     decorator.decorate(project, context);
335
336     verify(context).saveMeasure(argThat(matchesMetric(CoreMetrics.ALERT_STATUS, Metric.Level.WARN, "New Measure > 30 since someday")));
337   }
338
339   private ArgumentMatcher<Measure> matchesMetric(final Metric metric, final Metric.Level alertStatus, final String alertText) {
340     return new ArgumentMatcher<Measure>() {
341       @Override
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());
346         }
347         return result;
348       }
349     };
350   }
351
352   private ArgumentMatcher<Measure> hasLevel(final Measure measure, final Metric.Level alertStatus) {
353     return new ArgumentMatcher<Measure>() {
354       @Override
355       public boolean matches(Object arg) {
356         return arg == measure && ((Measure) arg).getAlertStatus().equals(alertStatus);
357       }
358     };
359   }
360
361 }