3 * Copyright (C) 2009-2023 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program 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 * This program 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.server.qualitygate;
22 import org.junit.After;
23 import org.junit.Before;
24 import org.junit.Rule;
25 import org.junit.Test;
26 import org.slf4j.event.Level;
27 import org.sonar.api.config.internal.MapSettings;
28 import org.sonar.api.measures.CoreMetrics;
29 import org.sonar.api.measures.Metric;
30 import org.sonar.api.testfixtures.log.LogTester;
31 import org.sonar.api.utils.System2;
32 import org.sonar.api.utils.log.LoggerLevel;
33 import org.sonar.db.DbTester;
34 import org.sonar.db.component.ProjectData;
35 import org.sonar.db.metric.MetricDto;
36 import org.sonar.server.es.EsTester;
37 import org.sonar.server.measure.index.ProjectMeasuresIndex;
38 import org.sonar.server.measure.index.ProjectMeasuresIndexer;
39 import org.sonar.server.permission.index.PermissionIndexerTester;
40 import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
41 import org.sonar.server.util.GlobalLockManager;
42 import org.sonar.server.util.GlobalLockManagerImpl;
44 import static org.assertj.core.api.Assertions.assertThat;
45 import static org.mockito.ArgumentMatchers.any;
46 import static org.mockito.ArgumentMatchers.anyInt;
47 import static org.mockito.Mockito.mock;
48 import static org.mockito.Mockito.when;
49 import static org.sonar.api.measures.Metric.Level.WARN;
50 import static org.sonar.db.measure.MeasureTesting.newLiveMeasure;
51 import static org.sonar.server.qualitygate.ProjectsInWarningDaemon.PROJECTS_IN_WARNING_INTERNAL_PROPERTY;
53 public class ProjectsInWarningDaemonTest {
56 public DbTester db = DbTester.create();
58 public EsTester es = EsTester.create();
60 public LogTester logger = new LogTester().setLevel(LoggerLevel.DEBUG);
62 private final PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es, new ProjectMeasuresIndexer(db.getDbClient(), es.client()));
63 private final ProjectMeasuresIndexer projectMeasuresIndexer = new ProjectMeasuresIndexer(db.getDbClient(), es.client());
64 private final ProjectMeasuresIndex projectMeasuresIndex = new ProjectMeasuresIndex(es.client(), new WebAuthorizationTypeSupport(null), System2.INSTANCE);
66 private final MapSettings settings = new MapSettings();
67 private final GlobalLockManager lockManager = mock(GlobalLockManagerImpl.class);
68 private final ProjectsInWarning projectsInWarning = new ProjectsInWarning();
70 private final ProjectsInWarningDaemon underTest = new ProjectsInWarningDaemon(db.getDbClient(), projectMeasuresIndex, settings.asConfig(), lockManager, projectsInWarning);
74 settings.setProperty("sonar.projectsInWarning.frequencyInMilliseconds", "100");
78 public void tearDown() {
83 public void store_projects_in_warning() throws InterruptedException {
84 allowLockToBeAcquired();
85 MetricDto qualityGateStatus = insertQualityGateStatusMetric();
86 insertProjectInWarning(qualityGateStatus);
87 insertProjectInWarning(qualityGateStatus);
88 // Setting does not exist
89 assertThat(db.getDbClient().internalPropertiesDao().selectByKey(db.getSession(), PROJECTS_IN_WARNING_INTERNAL_PROPERTY)).isEmpty();
91 underTest.notifyStart();
93 assertProjectsInWarningValue(2L);
94 assertThat(logger.logs(Level.INFO)).contains("Counting number of projects in warning is enabled.");
98 public void update_projects_in_warning_when_new_project_in_warning() throws InterruptedException {
99 allowLockToBeAcquired();
100 MetricDto qualityGateStatus = insertQualityGateStatusMetric();
101 insertProjectInWarning(qualityGateStatus);
102 insertProjectInWarning(qualityGateStatus);
103 // Setting does not exist
104 assertThat(db.getDbClient().internalPropertiesDao().selectByKey(db.getSession(), PROJECTS_IN_WARNING_INTERNAL_PROPERTY)).isEmpty();
106 underTest.notifyStart();
107 // Add a project in warning after the start in order to let the thread do his job
108 insertProjectInWarning(qualityGateStatus);
110 assertProjectsInWarningValue(3L);
111 assertThat(logger.logs(Level.INFO)).contains("Counting number of projects in warning is enabled.");
115 public void stop_thread_when_number_of_projects_in_warning_reach_zero() throws InterruptedException {
116 allowLockToBeAcquired();
117 MetricDto qualityGateStatus = insertQualityGateStatusMetric();
118 ProjectData project = insertProjectInWarning(qualityGateStatus);
120 underTest.notifyStart();
121 assertProjectsInWarningValue(1L);
122 // Set quality gate status of the project to OK => No more projects in warning
123 db.getDbClient().liveMeasureDao().insertOrUpdate(db.getSession(),
124 newLiveMeasure(project.getMainBranchComponent(), qualityGateStatus).setData(Metric.Level.OK.name()).setValue(null));
126 projectMeasuresIndexer.indexOnAnalysis(project.mainBranchUuid());
128 assertProjectsInWarningValue(0L);
129 assertThat(logger.logs(Level.INFO))
131 "Counting number of projects in warning is enabled.",
132 "Counting number of projects in warning will be disabled as there are no more projects in warning.");
136 public void update_internal_properties_when_already_exits_and_projects_in_warnings_more_than_zero() throws InterruptedException {
137 allowLockToBeAcquired();
138 MetricDto qualityGateStatus = insertQualityGateStatusMetric();
139 insertProjectInWarning(qualityGateStatus);
140 insertProjectInWarning(qualityGateStatus);
141 // Setting contains 10, it should be updated with new value
142 db.getDbClient().internalPropertiesDao().save(db.getSession(), PROJECTS_IN_WARNING_INTERNAL_PROPERTY, "10");
145 underTest.notifyStart();
147 assertProjectsInWarningValue(2L);
148 assertThat(logger.logs(Level.INFO)).contains("Counting number of projects in warning is enabled.");
152 public void store_zero_projects_in_warning_when_no_projects() throws InterruptedException {
153 allowLockToBeAcquired();
154 assertThat(db.getDbClient().internalPropertiesDao().selectByKey(db.getSession(), PROJECTS_IN_WARNING_INTERNAL_PROPERTY)).isEmpty();
156 underTest.notifyStart();
158 assertProjectsInWarningValue(0L);
159 assertThat(logger.logs(Level.INFO)).contains("Counting number of projects in warning is enabled.");
163 public void do_not_compute_projects_in_warning_when_internal_property_is_zero() throws InterruptedException {
164 allowLockToBeAcquired();
165 MetricDto qualityGateStatus = insertQualityGateStatusMetric();
166 insertProjectInWarning(qualityGateStatus);
167 // Setting contains 0, even if there are projects in warning it will stay 0 (as it's not possible to have new projects in warning)
168 db.getDbClient().internalPropertiesDao().save(db.getSession(), PROJECTS_IN_WARNING_INTERNAL_PROPERTY, "0");
171 underTest.notifyStart();
173 assertProjectsInWarningValue(0L);
174 assertThat(logger.logs(Level.INFO)).contains("Counting number of projects in warning is not started as there are no projects in this situation.");
178 public void do_not_store_projects_in_warning_in_db_when_cannot_acquire_lock() throws InterruptedException {
179 when(lockManager.tryLock(any(), anyInt())).thenReturn(false);
180 MetricDto qualityGateStatus = insertQualityGateStatusMetric();
181 insertProjectInWarning(qualityGateStatus);
183 underTest.notifyStart();
185 waitForValueToBeComputed(1L);
186 assertThat(projectsInWarning.count()).isOne();
187 assertThat(countNumberOfProjectsInWarning()).isZero();
190 private void waitForValueToBeComputed(long expectedValue) throws InterruptedException {
191 for (int i = 0; i < 1000; i++) {
192 if (projectsInWarning.isInitialized() && projectsInWarning.count() == expectedValue) {
199 private void assertProjectsInWarningValue(long expectedValue) throws InterruptedException {
200 waitForValueToBeComputed(expectedValue);
201 assertThat(projectsInWarning.count()).isEqualTo(expectedValue);
202 assertThat(countNumberOfProjectsInWarning()).isEqualTo(expectedValue);
205 private long countNumberOfProjectsInWarning() {
206 return db.getDbClient().internalPropertiesDao().selectByKey(db.getSession(), PROJECTS_IN_WARNING_INTERNAL_PROPERTY)
211 private ProjectData insertProjectInWarning(MetricDto qualityGateStatus) {
212 ProjectData project = db.components().insertPrivateProject();
213 db.measures().insertLiveMeasure(project, qualityGateStatus, lm -> lm.setData(WARN.name()).setValue(null));
214 authorizationIndexerTester.allowOnlyAnyone(project.getProjectDto());
215 projectMeasuresIndexer.indexOnAnalysis(project.mainBranchUuid());
219 private MetricDto insertQualityGateStatusMetric() {
220 return db.measures().insertMetric(m -> m.setKey(CoreMetrics.ALERT_STATUS_KEY).setValueType(Metric.ValueType.LEVEL.name()));
223 private void allowLockToBeAcquired() {
224 when(lockManager.tryLock(any(), anyInt())).thenReturn(true);