<groupId>org.codehaus.sonar</groupId>
<artifactId>sonar-squid</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar-ws-client</artifactId>
+ </dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
import org.sonar.api.utils.HttpDownloader;
import org.sonar.api.utils.SonarException;
import org.sonar.batch.bootstrapper.EnvironmentInformation;
+import org.sonar.wsclient.SonarClient;
import javax.annotation.Nullable;
private BootstrapSettings settings;
private HttpDownloader.BaseHttpDownloader downloader;
+ private SonarClient wsClient;
public ServerClient(BootstrapSettings settings, EnvironmentInformation env) {
this.settings = settings;
this.downloader = new HttpDownloader.BaseHttpDownloader(settings.properties(), env.toString());
+ this.wsClient = SonarClient.create(getURL());
}
public String getURL() {
}
}
+ public SonarClient wsClient() {
+ return wsClient;
+ }
+
private InputSupplier<InputStream> doRequest(String pathStartingWithSlash, @Nullable Integer timeoutMillis) {
Preconditions.checkArgument(pathStartingWithSlash.startsWith("/"), "Path must start with slash /");
String path = StringEscapeUtils.escapeHtml(pathStartingWithSlash);
--- /dev/null
+/*
+ * 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.sonar.api.BatchComponent;
+import org.sonar.wsclient.qualitygate.QualityGateCondition;
+
+import javax.annotation.Nullable;
+
+import java.util.Collection;
+
+public class QualityGate implements BatchComponent {
+
+ private final String name;
+
+ private final Collection<QualityGateCondition> conditions;
+
+ QualityGate(@Nullable String name) {
+ this.name = name;
+ this.conditions = Lists.newArrayList();
+ }
+
+ static QualityGate disabled() {
+ return new QualityGate(null);
+ }
+
+ public String name() {
+ return name;
+ }
+
+ public Collection<QualityGateCondition> conditions() {
+ return conditions;
+ }
+
+ public boolean isEnabled() {
+ return name != null;
+ }
+}
--- /dev/null
+/*
+ * 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.annotations.VisibleForTesting;
+import org.picocontainer.injectors.ProviderAdapter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.config.Settings;
+import org.sonar.api.utils.MessageException;
+import org.sonar.batch.bootstrap.ServerClient;
+import org.sonar.wsclient.base.HttpException;
+import org.sonar.wsclient.qualitygate.QualityGateClient;
+import org.sonar.wsclient.qualitygate.QualityGateDetails;
+
+import java.net.HttpURLConnection;
+
+public class QualityGateProvider extends ProviderAdapter {
+
+ private static final Logger LOG = LoggerFactory.getLogger(QualityGateProvider.class);
+
+ private static final String PROPERTY_QUALITY_GATE = "sonar.qualitygate";
+
+ public QualityGate provide(Settings settings, ServerClient client) {
+ return init(settings, client, LOG);
+ }
+
+ @VisibleForTesting
+ QualityGate init(Settings settings, ServerClient client, Logger logger) {
+ QualityGate result = QualityGate.disabled();
+ String qualityGateSetting = settings.getString(PROPERTY_QUALITY_GATE);
+ if (qualityGateSetting == null) {
+ logger.info("No quality gate is configured.");
+ } else {
+ result = load(qualityGateSetting, client.wsClient().qualityGateClient());
+ }
+ logger.info("Loaded quality gate '{}'", result.name());
+ return result;
+ }
+
+ private QualityGate load(String qualityGateSetting, QualityGateClient qualityGateClient) {
+ QualityGateDetails definitionFromServer = null;
+ try {
+ definitionFromServer = fetch(qualityGateSetting, qualityGateClient);
+ } catch (HttpException serverError) {
+ if (serverError.status() == HttpURLConnection.HTTP_NOT_FOUND) {
+ throw MessageException.of("No quality gate found with configured value '" + qualityGateSetting + "'. Please check your configuration.");
+ } else {
+ throw serverError;
+ }
+ }
+
+ QualityGate configuredGate = new QualityGate(definitionFromServer.name());
+
+ return configuredGate;
+ }
+
+ private QualityGateDetails fetch(String qualityGateSetting, QualityGateClient qualityGateClient) {
+ QualityGateDetails definitionFromServer = null;
+ try {
+ long qGateId = Long.valueOf(qualityGateSetting);
+ definitionFromServer = qualityGateClient.show(qGateId);
+ } catch(NumberFormatException configIsNameInsteadOfId) {
+ definitionFromServer = qualityGateClient.show(qualityGateSetting);
+ }
+ return definitionFromServer;
+ }
+}
--- /dev/null
+/*
+ * 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.*;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.resources.ResourceUtils;
+
+public class QualityGateVerifier implements Decorator {
+
+ private QualityGate qualityGate;
+
+ public QualityGateVerifier(QualityGate qualityGate) {
+ this.qualityGate = qualityGate;
+ }
+
+ @DependedUpon
+ public Metric generatesQualityGateStatus() {
+ return CoreMetrics.QUALITY_GATE_STATUS;
+ }
+
+ @DependsUpon
+ public String dependsOnVariations() {
+ return DecoratorBarriers.END_OF_TIME_MACHINE;
+ }
+
+ @Override
+ public boolean shouldExecuteOnProject(Project project) {
+ return qualityGate.isEnabled();
+ }
+
+ @Override
+ public void decorate(Resource resource, DecoratorContext context) {
+ if (ResourceUtils.isRootProject(resource)) {
+ checkProjectConditions(context);
+ }
+ }
+
+ private void checkProjectConditions(DecoratorContext context) {
+ Metric.Level globalLevel = Metric.Level.OK;
+ Measure globalMeasure = new Measure(CoreMetrics.QUALITY_GATE_STATUS, globalLevel);
+ globalMeasure.setAlertStatus(globalLevel);
+ globalMeasure.setAlertText("");
+ context.saveMeasure(globalMeasure);
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+}
*/
package org.sonar.batch.scan;
-import org.sonar.batch.issue.ignore.EnforceIssuesFilter;
-import org.sonar.batch.issue.ignore.IgnoreIssuesFilter;
-import org.sonar.batch.issue.ignore.pattern.IssueExclusionPatternInitializer;
-import org.sonar.batch.issue.ignore.pattern.IssueInclusionPatternInitializer;
-import org.sonar.batch.issue.ignore.scanner.IssueExclusionsLoader;
-import org.sonar.batch.issue.ignore.scanner.IssueExclusionsRegexpScanner;
-
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.BatchExtension;
import org.sonar.api.platform.ComponentContainer;
import org.sonar.api.resources.Project;
import org.sonar.api.scan.filesystem.FileExclusions;
-import org.sonar.batch.DefaultProjectClasspath;
-import org.sonar.batch.DefaultSensorContext;
-import org.sonar.batch.DefaultTimeMachine;
-import org.sonar.batch.ProjectTree;
-import org.sonar.batch.ResourceFilters;
-import org.sonar.batch.ViolationFilters;
+import org.sonar.batch.*;
import org.sonar.batch.bootstrap.BatchExtensionDictionnary;
import org.sonar.batch.bootstrap.ExtensionInstaller;
import org.sonar.batch.bootstrap.ExtensionMatcher;
import org.sonar.batch.issue.IssuableFactory;
import org.sonar.batch.issue.IssueFilters;
import org.sonar.batch.issue.ModuleIssues;
+import org.sonar.batch.issue.ignore.EnforceIssuesFilter;
+import org.sonar.batch.issue.ignore.IgnoreIssuesFilter;
+import org.sonar.batch.issue.ignore.pattern.IssueExclusionPatternInitializer;
+import org.sonar.batch.issue.ignore.pattern.IssueInclusionPatternInitializer;
+import org.sonar.batch.issue.ignore.scanner.IssueExclusionsLoader;
+import org.sonar.batch.issue.ignore.scanner.IssueExclusionsRegexpScanner;
import org.sonar.batch.phases.PhaseExecutor;
import org.sonar.batch.phases.PhasesTimeProfiler;
-import org.sonar.batch.rule.QProfileVerifier;
import org.sonar.batch.qualitygate.LegacyQualityGateLoader;
import org.sonar.batch.qualitygate.LegacyQualityGateVerifier;
-import org.sonar.batch.rule.ActiveRulesProvider;
-import org.sonar.batch.rule.ModuleQProfiles;
-import org.sonar.batch.rule.QProfileSensor;
-import org.sonar.batch.rule.RulesProfileProvider;
+import org.sonar.batch.qualitygate.QualityGateProvider;
+import org.sonar.batch.qualitygate.QualityGateVerifier;
+import org.sonar.batch.rule.*;
import org.sonar.batch.scan.filesystem.*;
-import org.sonar.batch.scan.filesystem.FileIndexer;
import org.sonar.batch.scan.report.JsonReport;
import org.sonar.core.component.ScanPerspectives;
import org.sonar.core.measure.MeasurementFilters;
ResourceFilters.class,
// quality gates
+ new QualityGateProvider(),
+ QualityGateVerifier.class,
LegacyQualityGateLoader.class,
LegacyQualityGateVerifier.class,
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import org.sonar.batch.bootstrapper.EnvironmentInformation;
+import org.sonar.wsclient.SonarClient;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
newServerClient().request("/foo");
}
+ @Test
+ public void should_give_access_to_ws_client() throws Exception {
+ server = new MockHttpServer();
+ server.start();
+
+ SonarClient wsClient = newServerClient().wsClient();
+ assertThat(wsClient).isNotNull();
+ }
+
private ServerClient newServerClient() {
when(settings.property(eq("sonar.host.url"), anyString())).thenReturn("http://localhost:" + server.getPort());
return new ServerClient(settings, new EnvironmentInformation("Junit", "4"));
--- /dev/null
+/*
+ * 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.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.slf4j.Logger;
+import org.sonar.api.config.Settings;
+import org.sonar.api.utils.MessageException;
+import org.sonar.batch.bootstrap.ServerClient;
+import org.sonar.wsclient.SonarClient;
+import org.sonar.wsclient.base.HttpException;
+import org.sonar.wsclient.qualitygate.QualityGateClient;
+import org.sonar.wsclient.qualitygate.QualityGateDetails;
+
+import java.net.HttpURLConnection;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class QualityGateProviderTest {
+
+ @Mock
+ private Settings settings;
+
+ @Mock
+ private ServerClient client;
+
+ @Mock
+ private QualityGateClient qualityGateClient;
+
+ @Mock
+ private Logger logger;
+
+ @Before
+ public void initMocks() {
+ SonarClient wsClient = mock(SonarClient.class);
+ when(client.wsClient()).thenReturn(wsClient);
+ when(wsClient.qualityGateClient()).thenReturn(qualityGateClient);
+ }
+
+ @Test
+ public void should_load_empty_quality_gate_from_default_settings() {
+ assertThat(new QualityGateProvider().provide(settings, client).conditions()).isEmpty();
+ assertThat(new QualityGateProvider().init(settings, client, logger).isEnabled()).isFalse();
+ verify(logger).info("No quality gate is configured.");
+ }
+
+ @Test
+ public void should_load_quality_gate_using_name() {
+ String qGateName = "Sonar way";
+ when(settings.getString("sonar.qualitygate")).thenReturn(qGateName);
+ QualityGateDetails qGate = mock(QualityGateDetails.class);
+ when(qualityGateClient.show(qGateName)).thenReturn(qGate);
+ when(qGate.name()).thenReturn(qGateName);
+ QualityGate actualGate = new QualityGateProvider().init(settings, client, logger);
+ assertThat(actualGate.name()).isEqualTo(qGateName);
+ assertThat(actualGate.isEnabled()).isTrue();
+ verify(logger).info("Loaded quality gate '{}'", qGateName);
+ }
+
+ @Test
+ public void should_load_quality_gate_using_id() {
+ long qGateId = 12345L;
+ String qGateName = "Sonar way";
+ when(settings.getString("sonar.qualitygate")).thenReturn(Long.toString(qGateId));
+ QualityGateDetails qGate = mock(QualityGateDetails.class);
+ when(qualityGateClient.show(qGateId)).thenReturn(qGate);
+ when(qGate.name()).thenReturn(qGateName);
+ assertThat(new QualityGateProvider().init(settings, client, logger).name()).isEqualTo(qGateName);
+ verify(logger).info("Loaded quality gate '{}'", qGateName);
+ }
+
+ @Test(expected = MessageException.class)
+ public void should_stop_analysis_if_gate_not_found() {
+ String qGateName = "Sonar way";
+ when(settings.getString("sonar.qualitygate")).thenReturn(qGateName);
+ when(qualityGateClient.show(qGateName)).thenThrow(new HttpException("http://server/api/qualitygates/show?name=Sonar%20way", HttpURLConnection.HTTP_NOT_FOUND));
+ new QualityGateProvider().provide(settings, client);
+ }
+
+ @Test(expected = HttpException.class)
+ public void should_stop_analysis_if_server_error() {
+ String qGateName = "Sonar way";
+ when(settings.getString("sonar.qualitygate")).thenReturn(qGateName);
+ when(qualityGateClient.show(qGateName)).thenThrow(new HttpException("http://server/api/qualitygates/show?name=Sonar%20way", HttpURLConnection.HTTP_NOT_ACCEPTABLE));
+ new QualityGateProvider().provide(settings, client);
+ }
+
+}
--- /dev/null
+/*
+ * 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.Before;
+import org.junit.Test;
+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.resources.Project;
+import org.sonar.api.resources.Resource;
+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.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class QualityGateVerifierTest {
+
+ QualityGateVerifier verifier;
+ DecoratorContext context;
+ QualityGate qualityGate;
+
+ 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);
+ qualityGate = mock(QualityGate.class);
+ when(qualityGate.isEnabled()).thenReturn(true);
+ verifier = new QualityGateVerifier(qualityGate);
+ project = new Project("foo");
+ }
+
+ @Test
+ public void should_be_executed_if_quality_gate_is_enabled() throws Exception {
+ assertThat(verifier.shouldExecuteOnProject((Project) project)).isTrue();
+ when(qualityGate.isEnabled()).thenReturn(false);
+ assertThat(verifier.shouldExecuteOnProject((Project) project)).isFalse();
+ }
+
+ @Test
+ public void test_toString() {
+ assertThat(verifier.toString()).isEqualTo("QualityGateVerifier");
+ }
+
+ @Test
+ public void generates_quality_gates_status() {
+ assertThat(verifier.generatesQualityGateStatus()).isEqualTo(CoreMetrics.QUALITY_GATE_STATUS);
+ }
+
+ @Test
+ public void depends_on_variations() {
+ assertThat(verifier.dependsOnVariations()).isEqualTo(DecoratorBarriers.END_OF_TIME_MACHINE);
+ }
+
+}