diff options
10 files changed, 136 insertions, 40 deletions
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/ReportComputationSteps.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/ReportComputationSteps.java index 1d6137f5dbd..b7f17b328b8 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/ReportComputationSteps.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/ReportComputationSteps.java @@ -47,8 +47,6 @@ public class ReportComputationSteps extends AbstractComputationSteps { ExecuteStatelessInitExtensionsStep.class, BuildComponentTreeStep.class, ValidateProjectStep.class, - - PersistScannerAnalysisCacheStep.class, LoadQualityProfilesStep.class, // load project related stuffs @@ -89,6 +87,7 @@ public class ReportComputationSteps extends AbstractComputationSteps { HandleUnanalyzedLanguagesStep.class, // Persist data + PersistScannerAnalysisCacheStep.class, PersistComponentsStep.class, PersistAnalysisStep.class, PersistAnalysisPropertiesStep.class, diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/ws/GetAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/ws/GetAction.java index ba95c5e6393..8cc9792e51d 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/ws/GetAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/scannercache/ws/GetAction.java @@ -28,8 +28,8 @@ import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; import org.sonar.api.web.UserRole; -import org.sonar.db.DbInputStream; import org.sonar.db.DbClient; +import org.sonar.db.DbInputStream; import org.sonar.db.DbSession; import org.sonar.db.component.ComponentDto; import org.sonar.server.component.ComponentFinder; @@ -41,10 +41,12 @@ import static org.sonar.db.permission.GlobalPermission.SCAN; import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException; import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001; import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; +import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001; public class GetAction implements AnalysisCacheWsAction { private static final String PROJECT = "project"; private static final String BRANCH = "branch"; + private static final String PR = "pullRequest"; private final DbClient dbClient; private final UserSession userSession; @@ -76,20 +78,26 @@ public class GetAction implements AnalysisCacheWsAction { .setDescription("Branch key. If not provided, main branch will be used.") .setExampleValue(KEY_BRANCH_EXAMPLE_001) .setRequired(false); + + action.createParam(PR) + .setDescription("Pull request id. Not available in the community edition.") + .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001) + .setRequired(false); } @Override public void handle(Request request, Response response) throws Exception { String projectKey = request.mandatoryParam(PROJECT); String branchKey = request.param(BRANCH); + String prKey = request.param(PR); try (DbSession dbSession = dbClient.openSession(false)) { - ComponentDto component = componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, projectKey, branchKey, null); + ComponentDto component = componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, projectKey, branchKey, prKey); checkPermission(component); try (DbInputStream dbInputStream = cache.get(component.uuid())) { if (dbInputStream == null) { - throw new NotFoundException("No cache for given branch"); + throw new NotFoundException("No cache for given branch or pull request"); } boolean compressed = requestedCompressedData(request); diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/scannercache/ws/GetActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/scannercache/ws/GetActionTest.java index 312df15ca6b..1aec02807f4 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/scannercache/ws/GetActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/scannercache/ws/GetActionTest.java @@ -32,6 +32,7 @@ import org.sonar.api.utils.System2; import org.sonar.api.web.UserRole; import org.sonar.db.DbTester; import org.sonar.db.component.BranchDto; +import org.sonar.db.component.BranchType; import org.sonar.db.project.ProjectDto; import org.sonar.db.scannercache.ScannerAnalysisCacheDao; import org.sonar.server.component.ComponentFinder; @@ -115,6 +116,24 @@ public class GetActionTest { } @Test + public void get_data_for_pr() throws IOException { + ProjectDto project1 = dbTester.components().insertPrivateProjectDto(); + BranchDto branch = dbTester.components().insertProjectBranch(project1, b -> b.setBranchType(BranchType.PULL_REQUEST)); + + dao.insert(dbTester.getSession(), project1.getUuid(), stringToCompressedInputStream("test data1")); + dao.insert(dbTester.getSession(), branch.getUuid(), stringToCompressedInputStream("test data2")); + + userSession.logIn().addProjectPermission(SCAN, project1); + TestResponse response = wsTester.newRequest() + .setParam("project", project1.getKey()) + .setParam("pullRequest", branch.getKey()) + .setHeader("Accept-Encoding", "gzip") + .execute(); + + assertThat(compressedInputStreamToString(response.getInputStream())).isEqualTo("test data2"); + } + + @Test public void return_not_found_if_project_not_found() { TestRequest request = wsTester .newRequest() @@ -123,6 +142,19 @@ public class GetActionTest { } @Test + public void return_not_found_if_branch_mixed_with_pr() { + ProjectDto project1 = dbTester.components().insertPrivateProjectDto(); + BranchDto branch = dbTester.components().insertProjectBranch(project1); + + userSession.logIn().addProjectPermission(SCAN, project1); + TestRequest request = wsTester.newRequest() + .setParam("project", project1.getKey()) + .setParam("pullRequest", branch.getKey()); + + assertThatThrownBy(request::execute).isInstanceOf(NotFoundException.class); + } + + @Test public void return_not_found_if_cache_not_found() { ProjectDto project1 = dbTester.components().insertPrivateProjectDto(); diff --git a/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java b/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java index 9cbe24f4eeb..dc5acf17136 100644 --- a/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java +++ b/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java @@ -423,12 +423,12 @@ public class SensorContextTester implements SensorContext { } @Override - public ReadCache previousAnalysisCache() { + public ReadCache previousCache() { return readCache; } - public void setPreviousAnalysisCache(ReadCache cache) { + public void setPreviousCache(ReadCache cache) { this.readCache = cache; } diff --git a/sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/sensor/internal/SensorContextTesterTest.java b/sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/sensor/internal/SensorContextTesterTest.java index 12e42d9b3ff..99f28cb066b 100644 --- a/sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/sensor/internal/SensorContextTesterTest.java +++ b/sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/sensor/internal/SensorContextTesterTest.java @@ -87,18 +87,18 @@ public class SensorContextTesterTest { @Test public void testPluginCache() { assertThat(tester.nextCache()).isNull(); - assertThat(tester.previousAnalysisCache()).isNull(); + assertThat(tester.previousCache()).isNull(); assertThat(tester.isCacheEnabled()).isFalse(); ReadCache readCache = mock(ReadCache.class); WriteCache writeCache = mock(WriteCache.class); - tester.setPreviousAnalysisCache(readCache); + tester.setPreviousCache(readCache); tester.setNextCache(writeCache); tester.setCacheEnabled(true); assertThat(tester.nextCache()).isEqualTo(writeCache); - assertThat(tester.previousAnalysisCache()).isEqualTo(readCache); + assertThat(tester.previousCache()).isEqualTo(readCache); assertThat(tester.isCacheEnabled()).isTrue(); } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java index da9578d0c98..9f1a282543d 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java @@ -239,13 +239,13 @@ public interface SensorContext { * @since 9.4 */ @Beta - ReadCache previousAnalysisCache(); + ReadCache previousCache(); /** * Returns true if caching is enabled. * This API is experimental and can be changed or dropped at any time. * @see #nextCache() - * @see #previousAnalysisCache() + * @see #previousCache() * @since 9.4 */ @Beta diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/cache/AnalysisCacheLoader.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/cache/AnalysisCacheLoader.java index f582e122079..035d3cf0010 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/cache/AnalysisCacheLoader.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/cache/AnalysisCacheLoader.java @@ -21,10 +21,10 @@ package org.sonar.scanner.cache; import java.io.IOException; import java.io.InputStream; -import java.net.HttpURLConnection; import java.util.Optional; import java.util.zip.InflaterInputStream; import org.sonar.api.scanner.fs.InputProject; +import org.sonar.api.utils.MessageException; import org.sonar.core.util.Protobuf; import org.sonar.scanner.bootstrap.DefaultScannerWsClient; import org.sonar.scanner.protocol.internal.ScannerInternal; @@ -32,6 +32,7 @@ import org.sonar.scanner.protocol.internal.ScannerInternal.AnalysisCacheMsg; import org.sonar.scanner.scan.branch.BranchConfiguration; import org.sonar.scanner.scan.branch.BranchType; import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.HttpException; import org.sonarqube.ws.client.WsResponse; /** @@ -56,24 +57,26 @@ public class AnalysisCacheLoader { String url = URL + "?project=" + project.key(); if (branchConfiguration.branchType() == BranchType.BRANCH && branchConfiguration.branchName() != null) { url = url + "&branch=" + branchConfiguration.branchName(); + } else if (branchConfiguration.isPullRequest()) { + url = url + "&pullRequest=" + branchConfiguration.pullRequestKey(); } GetRequest request = new GetRequest(url).setHeader(ACCEPT_ENCODING, "gzip"); - try (WsResponse response = wsClient.call(request)) { - if (response.code() == HttpURLConnection.HTTP_NOT_FOUND) { - return Optional.empty(); + try (WsResponse response = wsClient.call(request); InputStream is = response.contentStream()) { + Optional<String> contentEncoding = response.header(CONTENT_ENCODING); + if (contentEncoding.isPresent() && contentEncoding.get().equals("gzip")) { + return Optional.of(decompress(is)); + } else { + return Optional.of(Protobuf.read(is, AnalysisCacheMsg.parser())); } - try (InputStream is = response.contentStream()) { - Optional<String> contentEncoding = response.header(CONTENT_ENCODING); - if (contentEncoding.isPresent() && contentEncoding.get().equals("gzip")) { - return Optional.of(decompress(is)); - } else { - return Optional.of(Protobuf.read(is, AnalysisCacheMsg.parser())); - } - } catch (IOException e) { - throw new IllegalStateException("Failed to download cache", e); + } catch (HttpException e) { + if (e.code() == 404) { + return Optional.empty(); } + throw MessageException.of("Failed to download analysis cache: " + DefaultScannerWsClient.createErrorMessage(e)); + } catch (Exception e) { + throw new IllegalStateException("Failed to download analysis cache", e); } } @@ -81,7 +84,7 @@ public class AnalysisCacheLoader { try (InflaterInputStream iis = new InflaterInputStream(is)) { return Protobuf.read(iis, ScannerInternal.AnalysisCacheMsg.parser()); } catch (IOException e) { - throw new IllegalStateException("Failed to decompress plugin cache", e); + throw new IllegalStateException("Failed to decompress analysis cache", e); } } } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorContext.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorContext.java index c3cd0ac0521..a4cd85157d6 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorContext.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorContext.java @@ -199,7 +199,7 @@ public class ProjectSensorContext implements SensorContext { } @Override - public ReadCache previousAnalysisCache() { + public ReadCache previousCache() { return readCache; } diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/cache/AnalysisCacheLoaderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/cache/AnalysisCacheLoaderTest.java index 8ccdcafdb2d..1c1a04c7e08 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/cache/AnalysisCacheLoaderTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/cache/AnalysisCacheLoaderTest.java @@ -29,20 +29,29 @@ import java.util.Optional; import java.util.zip.DeflaterInputStream; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; import org.sonar.api.scanner.fs.InputProject; +import org.sonar.api.utils.MessageException; import org.sonar.scanner.bootstrap.DefaultScannerWsClient; -import org.sonar.scanner.protocol.internal.ScannerInternal; import org.sonar.scanner.protocol.internal.ScannerInternal.AnalysisCacheMsg; import org.sonar.scanner.scan.branch.BranchConfiguration; +import org.sonar.scanner.scan.branch.BranchType; +import org.sonarqube.ws.client.HttpException; +import org.sonarqube.ws.client.WsRequest; import org.sonarqube.ws.client.WsResponse; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.sonar.scanner.cache.AnalysisCacheLoader.CONTENT_ENCODING; public class AnalysisCacheLoaderTest { + private final static AnalysisCacheMsg MSG = AnalysisCacheMsg.newBuilder() + .putMap("key", ByteString.copyFrom("value", StandardCharsets.UTF_8)) + .build(); private final WsResponse response = mock(WsResponse.class); private final DefaultScannerWsClient wsClient = mock(DefaultScannerWsClient.class); private final InputProject project = mock(InputProject.class); @@ -51,35 +60,75 @@ public class AnalysisCacheLoaderTest { @Before public void before() { + when(project.key()).thenReturn("myproject"); when(wsClient.call(any())).thenReturn(response); } @Test public void loads_content() throws IOException { - ScannerInternal.AnalysisCacheMsg expected = ScannerInternal.AnalysisCacheMsg.newBuilder() - .putMap("key", ByteString.copyFrom("value", StandardCharsets.UTF_8)) - .build(); - setResponse(expected); + setResponse(MSG); AnalysisCacheMsg msg = loader.load().get(); - assertThat(msg).isEqualTo(expected); + assertThat(msg).isEqualTo(MSG); + assertRequestPath("api/analysis_cache/get?project=myproject"); + } + + @Test + public void loads_content_for_branch() throws IOException { + when(branchConfiguration.branchType()).thenReturn(BranchType.BRANCH); + when(branchConfiguration.branchName()).thenReturn("name"); + + setResponse(MSG); + AnalysisCacheMsg msg = loader.load().get(); + + assertThat(msg).isEqualTo(MSG); + assertRequestPath("api/analysis_cache/get?project=myproject&branch=name"); + } + + @Test + public void loads_content_for_pr() throws IOException { + when(branchConfiguration.isPullRequest()).thenReturn(true); + when(branchConfiguration.pullRequestKey()).thenReturn("key"); + setResponse(MSG); + AnalysisCacheMsg msg = loader.load().get(); + assertThat(msg).isEqualTo(MSG); + assertRequestPath("api/analysis_cache/get?project=myproject&pullRequest=key"); } @Test public void loads_compressed_content() throws IOException { - AnalysisCacheMsg expected = AnalysisCacheMsg.newBuilder() - .putMap("key", ByteString.copyFrom("value", StandardCharsets.UTF_8)) - .build(); - setCompressedResponse(expected); + setCompressedResponse(MSG); AnalysisCacheMsg msg = loader.load().get(); - assertThat(msg).isEqualTo(expected); + assertThat(msg).isEqualTo(MSG); } @Test public void returns_empty_if_404() { - when(response.code()).thenReturn(404); + when(wsClient.call(any())).thenThrow(new HttpException("url", 404, "content")); assertThat(loader.load()).isEmpty(); } + @Test + public void throw_error_if_http_exception_not_404() { + when(wsClient.call(any())).thenThrow(new HttpException("url", 401, "content")); + assertThatThrownBy(loader::load) + .isInstanceOf(MessageException.class) + .hasMessage("Failed to download analysis cache: HTTP code 401: content"); + } + + @Test + public void throw_error_if_cant_decompress_content() { + setInvalidCompressedResponse(); + assertThatThrownBy(loader::load) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Failed to download analysis cache"); + } + + private void assertRequestPath(String expectedPath) { + ArgumentCaptor<WsRequest> requestCaptor = ArgumentCaptor.forClass(WsRequest.class); + verify(wsClient).call(requestCaptor.capture()); + assertThat(requestCaptor.getValue().getPath()).isEqualTo(expectedPath); + } + private void setResponse(AnalysisCacheMsg msg) throws IOException { when(response.contentStream()).thenReturn(createInputStream(msg)); } @@ -89,6 +138,11 @@ public class AnalysisCacheLoaderTest { when(response.header(CONTENT_ENCODING)).thenReturn(Optional.of("gzip")); } + private void setInvalidCompressedResponse() { + when(response.contentStream()).thenReturn(new ByteArrayInputStream(new byte[] {1, 2, 3})); + when(response.header(CONTENT_ENCODING)).thenReturn(Optional.of("gzip")); + } + private InputStream createInputStream(AnalysisCacheMsg analysisCacheMsg) throws IOException { ByteArrayOutputStream serialized = new ByteArrayOutputStream(analysisCacheMsg.getSerializedSize()); analysisCacheMsg.writeTo(serialized); diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/ModuleSensorContextTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/ModuleSensorContextTest.java index 1063283716e..d40c7344bee 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/ModuleSensorContextTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/ModuleSensorContextTest.java @@ -89,7 +89,7 @@ public class ModuleSensorContextTest { assertThat(adaptor.canSkipUnchangedFiles()).isFalse(); assertThat(adaptor.nextCache()).isEqualTo(writeCache); - assertThat(adaptor.previousAnalysisCache()).isEqualTo(readCache); + assertThat(adaptor.previousCache()).isEqualTo(readCache); assertThat(adaptor.newIssue()).isNotNull(); assertThat(adaptor.newExternalIssue()).isNotNull(); |