From 10e04a8aafbabd665f1f668395b97fd43555c757 Mon Sep 17 00:00:00 2001 From: Jacek Date: Thu, 31 Oct 2019 13:11:32 +0100 Subject: [PATCH] SONAR-12629 unit and integration tests + minor fixes --- .../scanner/qualitygate/QualityGateCheck.java | 18 +- .../report/CeTaskReportDataHolder.java | 6 +- .../scanner/scan/ProjectScanContainer.java | 3 + .../qualitygate/QualityGateCheckTest.java | 358 ++++++++++++++++++ 4 files changed, 372 insertions(+), 13 deletions(-) create mode 100644 sonar-scanner-engine/src/test/java/org/sonar/scanner/qualitygate/QualityGateCheckTest.java diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/qualitygate/QualityGateCheck.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/qualitygate/QualityGateCheck.java index 871ba195b74..9b320f86f8a 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/qualitygate/QualityGateCheck.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/qualitygate/QualityGateCheck.java @@ -74,15 +74,15 @@ public class QualityGateCheck implements Startable { } public void await() { - if (!analysisMode.isMediumTest()) { - throw new IllegalStateException("Quality Gate check not available in medium test mode"); - } - if (!enabled) { LOG.debug("Quality Gate check disabled - skipping"); return; } + if (analysisMode.isMediumTest()) { + throw new IllegalStateException("Quality Gate check not available in medium test mode"); + } + String taskId = reportMetadataHolder.getCeTaskId(); Ce.Task task = waitForCeTaskToFinish(taskId); @@ -95,8 +95,6 @@ public class QualityGateCheck implements Startable { if (Status.OK.equals(qualityGateStatus)) { LOG.info("Quality Gate - OK"); - } else if (Status.NONE.equals(qualityGateStatus)) { - LOG.info("No Quality Gate is associated with the analysis - skipping"); } else { throw MessageException.of("Quality Gate - FAILED"); } @@ -124,17 +122,17 @@ public class QualityGateCheck implements Startable { throw MessageException.of(String.format("Failed to get CE Task status - %s", DefaultScannerWsClient.createErrorMessage(e))); } catch (InterruptedException e) { Thread.currentThread().interrupt(); - throw MessageException.of("Quality Gate Check has been interrupted", e); + throw MessageException.of("Quality Gate check has been interrupted", e); } } - throw MessageException.of("Quality Gate Check timeout exceeded"); + throw MessageException.of("Quality Gate check timeout exceeded"); } private static Ce.Task parseCeTaskResponse(WsResponse response) { try (InputStream protobuf = response.contentStream()) { return Ce.TaskResponse.parser().parseFrom(protobuf).getTask(); } catch (Exception e) { - throw new RuntimeException(e); + throw new IllegalStateException("Failed to parse response from " + response.requestUrl(), e); } } @@ -155,7 +153,7 @@ public class QualityGateCheck implements Startable { try (InputStream protobuf = response.contentStream()) { return Qualitygates.ProjectStatusResponse.parser().parseFrom(protobuf).getProjectStatus(); } catch (Exception e) { - throw new RuntimeException(e); + throw new IllegalStateException("Failed to parse response from " + response.requestUrl(), e); } } } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/CeTaskReportDataHolder.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/CeTaskReportDataHolder.java index a64bf79a855..2d657c41057 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/CeTaskReportDataHolder.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/CeTaskReportDataHolder.java @@ -23,9 +23,9 @@ import static java.util.Objects.requireNonNull; public class CeTaskReportDataHolder { private boolean initialized = false; - private String ceTaskId; - private String ceTaskUrl; - private String dashboardUrl; + private String ceTaskId = null; + private String ceTaskUrl = null; + private String dashboardUrl= null; public void init(String ceTaskId, String ceTaskUrl, String dashboardUrl) { requireNonNull(ceTaskId, "CE task id must not be null"); diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java index 45b63ec6cc1..6092d065016 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java @@ -81,6 +81,7 @@ import org.sonar.scanner.qualitygate.QualityGateCheck; import org.sonar.scanner.report.ActiveRulesPublisher; import org.sonar.scanner.report.AnalysisContextReportPublisher; import org.sonar.scanner.report.AnalysisWarningsPublisher; +import org.sonar.scanner.report.CeTaskReportDataHolder; import org.sonar.scanner.report.ChangedLinesPublisher; import org.sonar.scanner.report.ComponentsPublisher; import org.sonar.scanner.report.ContextPropertiesPublisher; @@ -251,6 +252,8 @@ public class ProjectScanContainer extends ComponentContainer { ChangedLinesPublisher.class, AnalysisResultReporter.class, + CeTaskReportDataHolder.class, + //QualityGate check QualityGateCheck.class, diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/qualitygate/QualityGateCheckTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/qualitygate/QualityGateCheckTest.java new file mode 100644 index 00000000000..49165a44ea3 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/qualitygate/QualityGateCheckTest.java @@ -0,0 +1,358 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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 org.sonar.scanner.qualitygate; + +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.ArgumentMatcher; +import org.mockito.Mockito; +import org.sonar.api.utils.MessageException; +import org.sonar.api.utils.log.LogTester; +import org.sonar.scanner.bootstrap.DefaultScannerWsClient; +import org.sonar.scanner.bootstrap.GlobalAnalysisMode; +import org.sonar.scanner.report.CeTaskReportDataHolder; +import org.sonar.scanner.scan.ScanProperties; +import org.sonarqube.ws.Ce; +import org.sonarqube.ws.Ce.TaskStatus; +import org.sonarqube.ws.Qualitygates; +import org.sonarqube.ws.Qualitygates.ProjectStatusResponse.Status; +import org.sonarqube.ws.client.HttpException; +import org.sonarqube.ws.client.MockWsResponse; +import org.sonarqube.ws.client.WsRequest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(DataProviderRunner.class) +public class QualityGateCheckTest { + private DefaultScannerWsClient wsClient = mock(DefaultScannerWsClient.class, Mockito.RETURNS_DEEP_STUBS); + private GlobalAnalysisMode analysisMode = mock(GlobalAnalysisMode.class); + private CeTaskReportDataHolder reportMetadataHolder = mock(CeTaskReportDataHolder.class); + private ScanProperties properties = mock(ScanProperties.class); + + @Rule + public LogTester logTester = new LogTester(); + + @Rule + public ExpectedException exception = ExpectedException.none(); + + QualityGateCheck underTest = new QualityGateCheck(wsClient, analysisMode, reportMetadataHolder, properties); + + @Test + public void should_pass_if_quality_gate_ok() { + when(properties.shouldWaitForQualityGate()).thenReturn(true); + when(properties.qualityGateWaitTimeout()).thenReturn(5); + + when(reportMetadataHolder.getCeTaskId()).thenReturn("task-1234"); + + MockWsResponse ceTaskWsResponse = getCeTaskWsResponse(TaskStatus.SUCCESS); + doReturn(ceTaskWsResponse).when(wsClient).call(newGetCeTaskRequest()); + + MockWsResponse qualityGateResponse = getQualityGateWsResponse(Status.OK); + doReturn(qualityGateResponse).when(wsClient).call(newGetQualityGateRequest()); + + underTest.start(); + + underTest.await(); + + underTest.stop(); + + assertThat(logTester.logs()) + .containsOnly("Quality Gate - OK"); + } + + @Test + public void should_wait_and_then_pass_if_quality_gate_ok() { + when(properties.shouldWaitForQualityGate()).thenReturn(true); + when(properties.qualityGateWaitTimeout()).thenReturn(10); + + when(reportMetadataHolder.getCeTaskId()).thenReturn("task-1234"); + + MockWsResponse pendingTask = getCeTaskWsResponse(TaskStatus.PENDING); + MockWsResponse successTask = getCeTaskWsResponse(TaskStatus.SUCCESS); + doReturn(pendingTask, successTask).when(wsClient).call(newGetCeTaskRequest()); + + MockWsResponse qualityGateResponse = getQualityGateWsResponse(Status.OK); + doReturn(qualityGateResponse).when(wsClient).call(newGetQualityGateRequest()); + + underTest.start(); + + underTest.await(); + + assertThat(logTester.logs()) + .contains("Quality Gate - OK"); + } + + @Test + public void should_fail_if_quality_gate_none() { + when(properties.shouldWaitForQualityGate()).thenReturn(true); + when(properties.qualityGateWaitTimeout()).thenReturn(5); + + when(reportMetadataHolder.getCeTaskId()).thenReturn("task-1234"); + + MockWsResponse ceTaskWsResponse = getCeTaskWsResponse(TaskStatus.SUCCESS); + doReturn(ceTaskWsResponse).when(wsClient).call(newGetCeTaskRequest()); + + MockWsResponse qualityGateResponse = getQualityGateWsResponse(Status.ERROR); + doReturn(qualityGateResponse).when(wsClient).call(newGetQualityGateRequest()); + + underTest.start(); + + exception.expect(MessageException.class); + exception.expectMessage("Quality Gate - FAILED"); + underTest.await(); + } + + @Test + public void should_fail_if_quality_gate_error() { + when(properties.shouldWaitForQualityGate()).thenReturn(true); + when(properties.qualityGateWaitTimeout()).thenReturn(5); + + when(reportMetadataHolder.getCeTaskId()).thenReturn("task-1234"); + + MockWsResponse ceTaskWsResponse = getCeTaskWsResponse(TaskStatus.SUCCESS); + doReturn(ceTaskWsResponse).when(wsClient).call(newGetCeTaskRequest()); + + MockWsResponse qualityGateResponse = getQualityGateWsResponse(Status.ERROR); + doReturn(qualityGateResponse).when(wsClient).call(newGetQualityGateRequest()); + + underTest.start(); + + exception.expect(MessageException.class); + exception.expectMessage("Quality Gate - FAILED"); + underTest.await(); + } + + @Test + public void should_wait_and_then_fail_if_quality_gate_error() { + when(properties.shouldWaitForQualityGate()).thenReturn(true); + when(properties.qualityGateWaitTimeout()).thenReturn(10); + + when(reportMetadataHolder.getCeTaskId()).thenReturn("task-1234"); + + MockWsResponse pendingTask = getCeTaskWsResponse(TaskStatus.PENDING); + MockWsResponse successTask = getCeTaskWsResponse(TaskStatus.SUCCESS); + doReturn(pendingTask, successTask).when(wsClient).call(newGetCeTaskRequest()); + + MockWsResponse qualityGateResponse = getQualityGateWsResponse(Status.ERROR); + doReturn(qualityGateResponse).when(wsClient).call(newGetQualityGateRequest()); + + underTest.start(); + + exception.expect(MessageException.class); + exception.expectMessage("Quality Gate - FAILED"); + + underTest.await(); + } + + @Test + public void should_fail_if_quality_gate_timeout_exceeded() { + when(properties.shouldWaitForQualityGate()).thenReturn(true); + when(properties.qualityGateWaitTimeout()).thenReturn(1); + + when(reportMetadataHolder.getCeTaskId()).thenReturn("task-1234"); + + MockWsResponse ceTaskWsResponse = getCeTaskWsResponse(TaskStatus.PENDING); + doReturn(ceTaskWsResponse).when(wsClient).call(newGetCeTaskRequest()); + + underTest.start(); + + exception.expect(MessageException.class); + exception.expectMessage("Quality Gate check timeout exceeded"); + underTest.await(); + } + + @Test + public void should_fail_if_cant_call_ws_for_quality_gate() { + when(properties.shouldWaitForQualityGate()).thenReturn(true); + when(properties.qualityGateWaitTimeout()).thenReturn(5); + + when(reportMetadataHolder.getCeTaskId()).thenReturn("task-1234"); + + MockWsResponse ceTaskWsResponse = getCeTaskWsResponse(TaskStatus.SUCCESS); + doReturn(ceTaskWsResponse).when(wsClient).call(newGetCeTaskRequest()); + + doThrow(new HttpException("quality-gate-url", 400, "content")).when(wsClient).call(newGetQualityGateRequest()); + + underTest.start(); + + exception.expect(MessageException.class); + exception.expectMessage("Failed to get Quality Gate status - HTTP code 400: content"); + underTest.await(); + } + + @Test + public void should_fail_if_invalid_response_from_quality_gate_ws() { + when(properties.shouldWaitForQualityGate()).thenReturn(true); + when(properties.qualityGateWaitTimeout()).thenReturn(5); + + when(reportMetadataHolder.getCeTaskId()).thenReturn("task-1234"); + + MockWsResponse ceTaskWsResponse = getCeTaskWsResponse(TaskStatus.SUCCESS); + doReturn(ceTaskWsResponse).when(wsClient).call(newGetCeTaskRequest()); + + MockWsResponse qualityGateResponse = new MockWsResponse(); + qualityGateResponse.setRequestUrl("quality-gate-url"); + qualityGateResponse.setContent("blabla"); + doReturn(qualityGateResponse).when(wsClient).call(newGetQualityGateRequest()); + + underTest.start(); + + exception.expect(IllegalStateException.class); + exception.expectMessage("Failed to parse response from quality-gate-url"); + underTest.await(); + } + + @Test + public void should_fail_if_cant_call_ws_for_task() { + when(properties.shouldWaitForQualityGate()).thenReturn(true); + when(properties.qualityGateWaitTimeout()).thenReturn(5); + + when(reportMetadataHolder.getCeTaskId()).thenReturn("task-1234"); + + when(wsClient.call(newGetCeTaskRequest())).thenThrow(new HttpException("task-url", 400, "content")); + + underTest.start(); + + exception.expect(MessageException.class); + exception.expectMessage("Failed to get CE Task status - HTTP code 400: content"); + underTest.await(); + } + + @Test + public void should_fail_if_invalid_response_from_ws_task() { + when(properties.shouldWaitForQualityGate()).thenReturn(true); + when(properties.qualityGateWaitTimeout()).thenReturn(5); + + when(reportMetadataHolder.getCeTaskId()).thenReturn("task-1234"); + + MockWsResponse getCeTaskRequest = new MockWsResponse(); + getCeTaskRequest.setRequestUrl("ce-task-url"); + getCeTaskRequest.setContent("blabla"); + + when(wsClient.call(newGetCeTaskRequest())).thenReturn(getCeTaskRequest); + + underTest.start(); + + exception.expect(IllegalStateException.class); + exception.expectMessage("Failed to parse response from ce-task-url"); + underTest.await(); + } + + @Test + @UseDataProvider("ceTaskNotSucceededStatuses") + public void should_fail_if_task_not_succeeded(TaskStatus taskStatus) { + when(properties.shouldWaitForQualityGate()).thenReturn(true); + when(properties.qualityGateWaitTimeout()).thenReturn(5); + + when(reportMetadataHolder.getCeTaskId()).thenReturn("task-1234"); + + MockWsResponse ceTaskWsResponse = getCeTaskWsResponse(taskStatus); + when(wsClient.call(newGetCeTaskRequest())).thenReturn(ceTaskWsResponse); + + underTest.start(); + + exception.expect(MessageException.class); + exception.expectMessage("CE Task finished abnormally with status: " + taskStatus.name()); + underTest.await(); + } + + private WsRequest newGetCeTaskRequest() { + return argThat(new WsRequestPathMatcher("api/ce/task")); + } + + private MockWsResponse getCeTaskWsResponse(TaskStatus status) { + MockWsResponse submitMockResponse = new MockWsResponse(); + submitMockResponse.setContent(Ce.TaskResponse.newBuilder() + .setTask(Ce.Task.newBuilder().setStatus(status)) + .build() + .toByteArray()); + return submitMockResponse; + } + + @Test + public void should_skip_wait_if_disabled() { + when(properties.shouldWaitForQualityGate()).thenReturn(false); + + underTest.start(); + + underTest.await(); + + assertThat(logTester.logs()) + .contains("Quality Gate check disabled - skipping"); + } + + @Test + public void should_fail_if_enabled_with_medium_test() { + when(properties.shouldWaitForQualityGate()).thenReturn(true); + when(analysisMode.isMediumTest()).thenReturn(true); + + underTest.start(); + + exception.expect(IllegalStateException.class); + + underTest.await(); + } + + private WsRequest newGetQualityGateRequest() { + return argThat(new WsRequestPathMatcher("api/qualitygates/project_status")); + } + + private MockWsResponse getQualityGateWsResponse(Status status) { + MockWsResponse qualityGateWsResponse = new MockWsResponse(); + qualityGateWsResponse.setContent(Qualitygates.ProjectStatusResponse.newBuilder() + .setProjectStatus(Qualitygates.ProjectStatusResponse.ProjectStatus.newBuilder() + .setStatus(status) + .build()) + .build() + .toByteArray()); + return qualityGateWsResponse; + } + + @DataProvider + public static Object[][] ceTaskNotSucceededStatuses() { + return new Object[][] { + {TaskStatus.CANCELED}, + {TaskStatus.FAILED}, + }; + } + + private static class WsRequestPathMatcher implements ArgumentMatcher { + String path; + + WsRequestPathMatcher(String path) { + this.path = path; + } + + @Override + public boolean matches(WsRequest right) { + return path.equals(right.getPath()); + } + } +} -- 2.39.5