Java tutorial
/* * SonarQube * Copyright (C) 2009-2017 SonarSource SA * mailto:info AT sonarsource DOT com * * This program 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. * * This program 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 it.qualityGate; import com.google.gson.Gson; import com.sonar.orchestrator.Orchestrator; import com.sonar.orchestrator.build.BuildResult; import com.sonar.orchestrator.build.SonarScanner; import it.Category1Suite; import java.io.File; import java.io.IOException; import java.io.StringReader; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Properties; import java.util.stream.Collectors; import java.util.stream.StreamSupport; import org.apache.commons.io.Charsets; import org.apache.commons.io.FileUtils; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; import org.sonar.wsclient.qualitygate.NewCondition; import org.sonar.wsclient.qualitygate.QualityGate; import org.sonar.wsclient.qualitygate.QualityGateClient; import org.sonarqube.ws.MediaTypes; import org.sonarqube.ws.WsCe; import org.sonarqube.ws.WsMeasures.Measure; import org.sonarqube.ws.WsQualityGates.ProjectStatusWsResponse; import org.sonarqube.ws.client.GetRequest; import org.sonarqube.ws.client.PostRequest; import org.sonarqube.ws.client.WsClient; import org.sonarqube.ws.client.WsResponse; import org.sonarqube.ws.client.qualitygate.ProjectStatusWsRequest; import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.groups.Tuple.tuple; import static util.ItUtils.concat; import static util.ItUtils.extractCeTaskId; import static util.ItUtils.getMeasure; import static util.ItUtils.newAdminWsClient; import static util.ItUtils.newProjectKey; import static util.ItUtils.projectDir; public class QualityGateTest { private static final String TASK_STATUS_SUCCESS = "SUCCESS"; private static final String QG_STATUS_NO_QG = "null"; private static final String QG_STATUS_OK = "OK"; private static final String QG_STATUS_ERROR = "ERROR"; private static final String QG_STATUS_WARN = "WARN"; private static long DEFAULT_QUALITY_GATE; @ClassRule public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; private static WsClient wsClient; @BeforeClass public static void startOrchestrator() { wsClient = newAdminWsClient(orchestrator); DEFAULT_QUALITY_GATE = qgClient().list().defaultGate().id(); } @AfterClass public static void restoreDefaultQualitGate() throws Exception { qgClient().setDefault(DEFAULT_QUALITY_GATE); } @Test public void do_not_compute_status_if_no_gate() throws IOException { String projectKey = newProjectKey(); BuildResult buildResult = executeAnalysis(projectKey); verifyQGStatusInPostTask(buildResult, projectKey, TASK_STATUS_SUCCESS, QG_STATUS_NO_QG); assertThat(getGateStatusMeasure(projectKey)).isNull(); } @Test public void status_ok_if_empty_gate() throws IOException { QualityGate empty = qgClient().create("Empty"); qgClient().setDefault(empty.id()); try { String projectKey = newProjectKey(); BuildResult buildResult = executeAnalysis(projectKey); verifyQGStatusInPostTask(buildResult, projectKey, TASK_STATUS_SUCCESS, QG_STATUS_OK); assertThat(getGateStatusMeasure(projectKey).getValue()).isEqualTo("OK"); } finally { qgClient().unsetDefault(); qgClient().destroy(empty.id()); } } @Test public void test_status_ok() throws IOException { QualityGate simple = qgClient().create("SimpleWithHighThreshold"); qgClient().setDefault(simple.id()); qgClient().createCondition( NewCondition.create(simple.id()).metricKey("ncloc").operator("GT").warningThreshold("40")); try { String projectKey = newProjectKey(); BuildResult buildResult = executeAnalysis(projectKey); verifyQGStatusInPostTask(buildResult, projectKey, TASK_STATUS_SUCCESS, QG_STATUS_OK); assertThat(getGateStatusMeasure(projectKey).getValue()).isEqualTo("OK"); } finally { qgClient().unsetDefault(); qgClient().destroy(simple.id()); } } @Test public void test_status_warning() throws IOException { QualityGate simple = qgClient().create("SimpleWithLowThreshold"); qgClient().setDefault(simple.id()); qgClient().createCondition( NewCondition.create(simple.id()).metricKey("ncloc").operator("GT").warningThreshold("10")); try { String projectKey = newProjectKey(); BuildResult buildResult = executeAnalysis(projectKey); verifyQGStatusInPostTask(buildResult, projectKey, TASK_STATUS_SUCCESS, QG_STATUS_WARN); assertThat(getGateStatusMeasure(projectKey).getValue()).isEqualTo("WARN"); } finally { qgClient().unsetDefault(); qgClient().destroy(simple.id()); } } @Test public void test_status_error() throws IOException { QualityGate simple = qgClient().create("SimpleWithLowThreshold"); qgClient().setDefault(simple.id()); qgClient().createCondition( NewCondition.create(simple.id()).metricKey("ncloc").operator("GT").errorThreshold("10")); try { String projectKey = newProjectKey(); BuildResult buildResult = executeAnalysis(projectKey); verifyQGStatusInPostTask(buildResult, projectKey, TASK_STATUS_SUCCESS, QG_STATUS_ERROR); assertThat(getGateStatusMeasure(projectKey).getValue()).isEqualTo("ERROR"); } finally { qgClient().unsetDefault(); qgClient().destroy(simple.id()); } } @Test public void use_server_settings_instead_of_default_gate() throws IOException { QualityGate alert = qgClient().create("AlertWithLowThreshold"); qgClient().createCondition( NewCondition.create(alert.id()).metricKey("ncloc").operator("GT").warningThreshold("10")); QualityGate error = qgClient().create("ErrorWithLowThreshold"); qgClient().createCondition( NewCondition.create(error.id()).metricKey("ncloc").operator("GT").errorThreshold("10")); qgClient().setDefault(alert.id()); String projectKey = newProjectKey(); orchestrator.getServer().provisionProject(projectKey, projectKey); associateQualityGateToProject(error.id(), projectKey); try { BuildResult buildResult = executeAnalysis(projectKey); verifyQGStatusInPostTask(buildResult, projectKey, TASK_STATUS_SUCCESS, QG_STATUS_ERROR); assertThat(getGateStatusMeasure(projectKey).getValue()).isEqualTo("ERROR"); } finally { qgClient().unsetDefault(); qgClient().destroy(alert.id()); qgClient().destroy(error.id()); } } @Test public void conditions_on_multiple_metric_types() throws IOException { QualityGate allTypes = qgClient().create("AllMetricTypes"); qgClient().createCondition( NewCondition.create(allTypes.id()).metricKey("ncloc").operator("GT").warningThreshold("10")); qgClient().createCondition(NewCondition.create(allTypes.id()).metricKey("duplicated_lines_density") .operator("GT").warningThreshold("20")); qgClient().setDefault(allTypes.id()); try { String projectKey = newProjectKey(); BuildResult buildResult = executeAnalysis(projectKey, "sonar.cpd.xoo.minimumLines", "2", "sonar.cpd.xoo.minimumTokens", "5"); verifyQGStatusInPostTask(buildResult, projectKey, TASK_STATUS_SUCCESS, QG_STATUS_WARN); Measure alertStatus = getGateStatusMeasure(projectKey); assertThat(alertStatus.getValue()).isEqualTo("WARN"); String qualityGateDetailJson = getMeasure(orchestrator, projectKey, "quality_gate_details").getValue(); assertThat(QualityGateDetails.parse(qualityGateDetailJson).getConditions()) .extracting(QualityGateDetails.Conditions::getMetric, QualityGateDetails.Conditions::getOp, QualityGateDetails.Conditions::getWarning) .contains(tuple("ncloc", "GT", "10"), tuple("duplicated_lines_density", "GT", "20")); } finally { qgClient().unsetDefault(); qgClient().destroy(allTypes.id()); } } @Test public void ad_hoc_build_break_strategy() throws IOException { QualityGate simple = qgClient().create("SimpleWithLowThresholdForBuildBreakStrategy"); qgClient().setDefault(simple.id()); qgClient().createCondition( NewCondition.create(simple.id()).metricKey("ncloc").operator("GT").errorThreshold("7")); try { String projectKey = newProjectKey(); BuildResult buildResult = executeAnalysis(projectKey); verifyQGStatusInPostTask(buildResult, projectKey, TASK_STATUS_SUCCESS, QG_STATUS_ERROR); String taskId = getTaskIdInLocalReport(projectDir("qualitygate/xoo-sample")); String analysisId = getAnalysisId(taskId); ProjectStatusWsResponse projectStatusWsResponse = wsClient.qualityGates() .projectStatus(new ProjectStatusWsRequest().setAnalysisId(analysisId)); ProjectStatusWsResponse.ProjectStatus projectStatus = projectStatusWsResponse.getProjectStatus(); assertThat(projectStatus.getStatus()).isEqualTo(ProjectStatusWsResponse.Status.ERROR); assertThat(projectStatus.getConditionsCount()).isEqualTo(1); ProjectStatusWsResponse.Condition condition = projectStatus.getConditionsList().get(0); assertThat(condition.getMetricKey()).isEqualTo("ncloc"); assertThat(condition.getErrorThreshold()).isEqualTo("7"); } finally { qgClient().unsetDefault(); qgClient().destroy(simple.id()); } } @Test public void does_not_fail_when_condition_is_on_removed_metric() throws IOException { String customMetricKey = randomAlphabetic(10); createCustomIntMetric(orchestrator, customMetricKey); QualityGate simple = qgClient().create("OnCustomMetric"); qgClient().setDefault(simple.id()); qgClient().createCondition( NewCondition.create(simple.id()).metricKey(customMetricKey).operator("GT").warningThreshold("40")); try { deleteCustomMetric(orchestrator, customMetricKey); String projectKey = newProjectKey(); BuildResult buildResult = executeAnalysis(projectKey); verifyQGStatusInPostTask(buildResult, projectKey, TASK_STATUS_SUCCESS, QG_STATUS_OK); assertThat(getGateStatusMeasure(projectKey).getValue()).isEqualTo("OK"); } finally { deleteCustomMetric(orchestrator, customMetricKey); qgClient().unsetDefault(); qgClient().destroy(simple.id()); } } private BuildResult executeAnalysis(String projectKey, String... keyValueProperties) { return orchestrator.executeBuild(SonarScanner.create(projectDir("qualitygate/xoo-sample"), concat(keyValueProperties, "sonar.projectKey", projectKey))); } private void verifyQGStatusInPostTask(BuildResult buildResult, String projectKey, String taskStatus, String qgStatus) throws IOException { List<String> logsLines = FileUtils.readLines(orchestrator.getServer().getCeLogs(), Charsets.UTF_8); List<String> postTaskLogLines = extractPosttaskPluginLogs(extractCeTaskId(buildResult), logsLines); assertThat(postTaskLogLines).hasSize(1); assertThat(postTaskLogLines.iterator().next()).contains("CeTask[" + taskStatus + "]") .contains("Project[" + projectKey + "]").contains("QualityGate[" + qgStatus + "]"); } private String getAnalysisId(String taskId) throws IOException { WsResponse activity = wsClient.wsConnector() .call(new GetRequest("api/ce/task").setParam("id", taskId).setMediaType(MediaTypes.PROTOBUF)); WsCe.TaskResponse activityWsResponse = WsCe.TaskResponse.parseFrom(activity.contentStream()); return activityWsResponse.getTask().getAnalysisId(); } private String getTaskIdInLocalReport(File projectDirectory) throws IOException { File metadata = new File(projectDirectory, ".sonar/report-task.txt"); assertThat(metadata).exists().isFile(); // verify properties Properties props = new Properties(); props.load(new StringReader(FileUtils.readFileToString(metadata, StandardCharsets.UTF_8))); assertThat(props.getProperty("ceTaskId")).isNotEmpty(); return props.getProperty("ceTaskId"); } private Measure getGateStatusMeasure(String projectKey) { return getMeasure(orchestrator, projectKey, "alert_status"); } private static QualityGateClient qgClient() { return orchestrator.getServer().adminWsClient().qualityGateClient(); } private static void associateQualityGateToProject(long qGateId, String projectKey) { newAdminWsClient(orchestrator).wsConnector().call(new PostRequest("api/qualitygates/select") .setParam("gateId", qGateId).setParam("projectKey", projectKey)).failIfNotSuccessful(); } private static List<String> extractPosttaskPluginLogs(String taskUuid, Iterable<String> ceLogs) { return StreamSupport.stream(ceLogs.spliterator(), false) .filter(s -> s.contains("POSTASKPLUGIN: finished()")).filter(s -> s.contains(taskUuid)) .collect(Collectors.toList()); } private static void createCustomIntMetric(Orchestrator orchestrator, String metricKey) { newAdminWsClient(orchestrator).wsConnector().call(new PostRequest("api/metrics/create") .setParam("key", metricKey).setParam("name", metricKey).setParam("type", "INT")) .failIfNotSuccessful(); } private static void deleteCustomMetric(Orchestrator orchestrator, String metricKey) { newAdminWsClient(orchestrator).wsConnector() .call(new PostRequest("api/metrics/delete").setParam("keys", metricKey)).failIfNotSuccessful(); } static class QualityGateDetails { private String level; private List<Conditions> conditions = new ArrayList<>(); String getLevel() { return level; } List<Conditions> getConditions() { return conditions; } public static QualityGateDetails parse(String json) { Gson gson = new Gson(); return gson.fromJson(json, QualityGateDetails.class); } public static class Conditions { private final String metric; private final String op; private final String warning; private final String actual; private final String level; private Conditions(String metric, String op, String values, String actual, String level) { this.metric = metric; this.op = op; this.warning = values; this.actual = actual; this.level = level; } String getMetric() { return metric; } String getOp() { return op; } String getWarning() { return warning; } String getActual() { return actual; } String getLevel() { return level; } } } }