From cf1c8d76184de334921362e9abd7dd64d56e5f16 Mon Sep 17 00:00:00 2001 From: Michal Duda Date: Thu, 22 Nov 2018 18:09:44 +0100 Subject: [PATCH] SONAR-11507 WS /batch/project new file structure --- .../org/sonar/server/batch/ProjectAction.java | 39 +++++------ .../sonar/server/batch/ProjectDataLoader.java | 29 ++++++-- .../sonar/server/batch/ProjectActionTest.java | 52 ++++++++++++-- .../server/batch/ProjectDataLoaderTest.java | 22 ++++-- .../DefaultProjectRepositoriesLoader.java | 37 ++++++---- .../MultiModuleProjectRepository.java | 45 ++++++++++++ .../repository/ProjectRepositories.java | 35 +++------- .../repository/SingleProjectRepository.java | 49 +++++++++++++ .../scan/filesystem/StatusDetection.java | 9 ++- .../org/sonar/scanner/scm/ScmPublisher.java | 6 +- .../mediumtest/ScannerMediumTester.java | 19 +++--- .../mediumtest/branch/BranchMediumTest.java | 2 +- .../issuesmode/ScanOnlyChangedTest.java | 4 +- .../scanner/mediumtest/scm/ScmMediumTest.java | 8 +-- .../DefaultProjectRepositoriesLoaderTest.java | 7 +- .../MultiModuleProjectRepositoryTest.java | 61 +++++++++++++++++ .../ProjectRepositoriesProviderTest.java | 8 +-- .../SingleProjectRepositoryTest.java | 55 +++++++++++++++ .../scan/filesystem/StatusDetectionTest.java | 26 +++---- .../input/MultiModuleProjectRepository.java | 52 ++++++++++++++ .../protocol/input/ProjectRepositories.java | 30 +------- .../input/SingleProjectRepository.java | 45 ++++++++++++ .../MultiModuleProjectRepositoryTest.java | 68 +++++++++++++++++++ .../input/SingleProjectRepositoryTest.java | 64 +++++++++++++++++ sonar-ws/src/main/protobuf/ws-batch.proto | 1 + 25 files changed, 628 insertions(+), 145 deletions(-) create mode 100644 sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/MultiModuleProjectRepository.java create mode 100644 sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/SingleProjectRepository.java create mode 100644 sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/MultiModuleProjectRepositoryTest.java create mode 100644 sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/SingleProjectRepositoryTest.java create mode 100644 sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/MultiModuleProjectRepository.java create mode 100644 sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/SingleProjectRepository.java create mode 100644 sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/input/MultiModuleProjectRepositoryTest.java create mode 100644 sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/input/SingleProjectRepositoryTest.java diff --git a/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectAction.java b/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectAction.java index 35d8f53b231..5e8cfb22de8 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectAction.java @@ -19,15 +19,18 @@ */ package org.sonar.server.batch; +import com.google.common.collect.Maps; import java.util.Date; -import java.util.HashMap; import java.util.Map; +import java.util.stream.Collectors; import org.sonar.api.server.ws.Change; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; import org.sonar.scanner.protocol.input.FileData; +import org.sonar.scanner.protocol.input.MultiModuleProjectRepository; import org.sonar.scanner.protocol.input.ProjectRepositories; +import org.sonar.scanner.protocol.input.SingleProjectRepository; import org.sonarqube.ws.Batch.WsProjectResponse; import org.sonarqube.ws.Batch.WsProjectResponse.FileData.Builder; @@ -109,33 +112,31 @@ public class ProjectAction implements BatchWsAction { WsProjectResponse.Builder response = WsProjectResponse.newBuilder(); setNullable(data.lastAnalysisDate(), response::setLastAnalysisDate, Date::getTime); response.setTimestamp(data.timestamp()); - response.getMutableFileDataByModuleAndPath() - .putAll(buildFileDataByModuleAndPath(data)); + if (data instanceof SingleProjectRepository) { + response.putAllFileDataByPath(buildFileDataByPath((SingleProjectRepository) data)); + } else { + response.putAllFileDataByModuleAndPath(buildFileDataByModuleAndPath((MultiModuleProjectRepository) data)); + } return response.build(); } - private static Map buildFileDataByModuleAndPath(ProjectRepositories data) { - Map fileDataByModuleAndPathResponse = new HashMap<>(); - for (Map.Entry> moduleAndFileDataByPathEntry : data.fileDataByModuleAndPath().entrySet()) { - fileDataByModuleAndPathResponse.put( - moduleAndFileDataByPathEntry.getKey(), - buildFileDataByPath(moduleAndFileDataByPathEntry.getValue())); - } + private static Map buildFileDataByModuleAndPath(MultiModuleProjectRepository data) { + return data.repositoriesByModule().entrySet() + .stream() + .map(entry -> Maps.immutableEntry(entry.getKey(), buildFileDataByPath(entry.getValue().fileData()))) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } - return fileDataByModuleAndPathResponse; + private static Map buildFileDataByPath(SingleProjectRepository data) { + return data.fileData().entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, e -> toFileDataResponse(e.getValue()))); } private static WsProjectResponse.FileDataByPath buildFileDataByPath(Map fileDataByPath) { WsProjectResponse.FileDataByPath.Builder response = WsProjectResponse.FileDataByPath.newBuilder(); - Map fileDataByPathResponse = response.getMutableFileDataByPath(); - - for (Map.Entry pathFileDataEntry : fileDataByPath.entrySet()) { - fileDataByPathResponse.put( - pathFileDataEntry.getKey(), - toFileDataResponse(pathFileDataEntry.getValue())); - } - + fileDataByPath.forEach((key, value) -> response.putFileDataByPath(key, toFileDataResponse(value))); return response.build(); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectDataLoader.java b/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectDataLoader.java index 7808454455a..53cdb5b81a2 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectDataLoader.java +++ b/server/sonar-server/src/main/java/org/sonar/server/batch/ProjectDataLoader.java @@ -32,7 +32,9 @@ import org.sonar.db.component.ComponentDto; import org.sonar.db.component.FilePathWithHashDto; import org.sonar.db.permission.OrganizationPermission; import org.sonar.scanner.protocol.input.FileData; +import org.sonar.scanner.protocol.input.MultiModuleProjectRepository; import org.sonar.scanner.protocol.input.ProjectRepositories; +import org.sonar.scanner.protocol.input.SingleProjectRepository; import org.sonar.server.component.ComponentFinder; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.user.UserSession; @@ -56,7 +58,6 @@ public class ProjectDataLoader { public ProjectRepositories load(ProjectDataQuery query) { try (DbSession session = dbClient.openSession(false)) { - ProjectRepositories data = new ProjectRepositories(); String projectKey = query.getProjectKey(); String branch = query.getBranch(); String pullRequest = query.getPullRequest(); @@ -72,12 +73,23 @@ public class ProjectDataLoader { List modulesTree = dbClient.componentDao().selectEnabledDescendantModules(session, branchOrMainModule.uuid()); List files = searchFilesWithHashAndRevision(session, branchOrMainModule); - addFileData(data, modulesTree, files); + + // MMF-365 we still have to support multi-module projects because it's not possible to transform from logical to + // physical structure for some multi-module projects + ProjectRepositories data; + if (modulesTree.size() > 1) { + MultiModuleProjectRepository repository = new MultiModuleProjectRepository(); + addFileDataPerModule(repository, modulesTree, files); + data = repository; + } else { + SingleProjectRepository repository = new SingleProjectRepository(); + addFileData(repository, files); + data = repository; + } // FIXME need real value but actually only used to know if there is a previous analysis in local issue tracking mode so any value is // ok data.setLastAnalysisDate(new Date()); - return data; } } @@ -90,7 +102,7 @@ public class ProjectDataLoader { : dbClient.componentDao().selectEnabledDescendantFiles(session, module.uuid()); } - private static void addFileData(ProjectRepositories data, List moduleChildren, List files) { + private static void addFileDataPerModule(MultiModuleProjectRepository data, List moduleChildren, List files) { Map moduleKeysByUuid = newHashMap(); for (ComponentDto module : moduleChildren) { moduleKeysByUuid.put(module.uuid(), module.getKey()); @@ -98,7 +110,14 @@ public class ProjectDataLoader { for (FilePathWithHashDto file : files) { FileData fileData = new FileData(file.getSrcHash(), file.getRevision()); - data.addFileData(moduleKeysByUuid.get(file.getModuleUuid()), file.getPath(), fileData); + data.addFileDataToModule(moduleKeysByUuid.get(file.getModuleUuid()), file.getPath(), fileData); + } + } + + private static void addFileData(SingleProjectRepository data, List files) { + for (FilePathWithHashDto file : files) { + FileData fileData = new FileData(file.getSrcHash(), file.getRevision()); + data.addFileData(file.getPath(), fileData); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/batch/ProjectActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/batch/ProjectActionTest.java index daa57b7d572..16d8c0de139 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/batch/ProjectActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/batch/ProjectActionTest.java @@ -22,7 +22,9 @@ package org.sonar.server.batch; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.sonar.scanner.protocol.input.FileData; +import org.sonar.scanner.protocol.input.MultiModuleProjectRepository; import org.sonar.scanner.protocol.input.ProjectRepositories; +import org.sonar.scanner.protocol.input.SingleProjectRepository; import org.sonar.server.ws.TestResponse; import org.sonar.server.ws.WsActionTester; import org.sonarqube.ws.Batch.WsProjectResponse; @@ -42,7 +44,7 @@ public class ProjectActionTest { public void project_referentials() { String projectKey = "org.codehaus.sonar:sonar"; - ProjectRepositories projectReferentials = mock(ProjectRepositories.class); + ProjectRepositories projectReferentials = mock(SingleProjectRepository.class); ArgumentCaptor queryArgumentCaptor = ArgumentCaptor.forClass(ProjectDataQuery.class); when(projectDataLoader.load(queryArgumentCaptor.capture())).thenReturn(projectReferentials); @@ -53,7 +55,7 @@ public class ProjectActionTest { .setParam("profile", "Default") .setParam("preview", "false") .execute(); - assertJson(response.getInput()).isSimilarTo("{\"fileDataByModuleAndPath\": {}}"); + assertJson(response.getInput()).isSimilarTo("{\"fileDataByPath\": {}}"); assertThat(queryArgumentCaptor.getValue().getProjectKey()).isEqualTo(projectKey); assertThat(queryArgumentCaptor.getValue().getProfileName()).isEqualTo("Default"); @@ -68,13 +70,55 @@ public class ProjectActionTest { public void do_not_fail_when_a_path_is_null() { String projectKey = "org.codehaus.sonar:sonar"; - ProjectRepositories projectRepositories = new ProjectRepositories().addFileData("module-1", null, new FileData(null, null)); + ProjectRepositories projectRepositories = new MultiModuleProjectRepository() + .addFileDataToModule("module-1", null, new FileData(null, null)); when(projectDataLoader.load(any(ProjectDataQuery.class))).thenReturn(projectRepositories); WsProjectResponse wsProjectResponse = ws.newRequest() .setParam("key", projectKey) .setParam("profile", "Default") .executeProtobuf(WsProjectResponse.class); - assertThat(wsProjectResponse.getFileDataByModuleAndPath()).isEmpty(); + assertThat(wsProjectResponse.getFileDataByModuleAndPathMap()).isEmpty(); + } + + @Test + public void use_new_file_structure_for_projects_without_submodules() { + String projectKey = "org.codehaus.sonar:sonar"; + + ProjectRepositories projectRepositories = new SingleProjectRepository() + .addFileData("src/main/java/SomeClass.java", new FileData("789456", "123456789")); + when(projectDataLoader.load(any(ProjectDataQuery.class))).thenReturn(projectRepositories); + + WsProjectResponse wsProjectResponse = ws.newRequest() + .setParam("key", projectKey) + .setParam("profile", "Default") + .executeProtobuf(WsProjectResponse.class); + assertThat(wsProjectResponse.getFileDataByModuleAndPathMap()).isEmpty(); + assertThat(wsProjectResponse.getFileDataByPathCount()).isEqualTo(1); + assertThat(wsProjectResponse.getFileDataByPathMap().get("src/main/java/SomeClass.java")).isNotNull(); + } + + @Test + public void use_old_file_structure_for_projects_with_submodules() { + String projectKey = "org.codehaus.sonar:sonar"; + + ProjectRepositories projectRepositories = new MultiModuleProjectRepository() + .addFileDataToModule("module-1", "src/main/java/SomeClass.java", new FileData("789456", "123456789")); + when(projectDataLoader.load(any(ProjectDataQuery.class))).thenReturn(projectRepositories); + + WsProjectResponse wsProjectResponse = ws.newRequest() + .setParam("key", projectKey) + .setParam("profile", "Default") + .executeProtobuf(WsProjectResponse.class); + + assertThat(wsProjectResponse.getFileDataByPathMap()).isEmpty(); + assertThat(wsProjectResponse.getFileDataByModuleAndPathCount()).isEqualTo(1); + WsProjectResponse.FileDataByPath moduleData = wsProjectResponse.getFileDataByModuleAndPathMap().get("module-1"); + assertThat(moduleData).isNotNull(); + assertThat(moduleData.getFileDataByPathCount()).isEqualTo(1); + WsProjectResponse.FileData fileData = moduleData.getFileDataByPathMap().get("src/main/java/SomeClass.java"); + assertThat(fileData).isNotNull(); + assertThat(fileData.getHash()).isEqualTo("789456"); + assertThat(fileData.getRevision()).isEqualTo("123456789"); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/batch/ProjectDataLoaderTest.java b/server/sonar-server/src/test/java/org/sonar/server/batch/ProjectDataLoaderTest.java index 3b950cd23a4..c7efa7745f0 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/batch/ProjectDataLoaderTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/batch/ProjectDataLoaderTest.java @@ -34,7 +34,9 @@ import org.sonar.db.component.ResourceTypesRule; import org.sonar.db.organization.OrganizationDto; import org.sonar.db.source.FileSourceDto; import org.sonar.scanner.protocol.input.FileData; +import org.sonar.scanner.protocol.input.MultiModuleProjectRepository; import org.sonar.scanner.protocol.input.ProjectRepositories; +import org.sonar.scanner.protocol.input.SingleProjectRepository; import org.sonar.server.component.ComponentFinder; import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.exceptions.ForbiddenException; @@ -44,6 +46,7 @@ import org.sonar.server.tester.UserSessionRule; import static com.google.common.collect.ImmutableList.of; import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.sonar.core.permission.GlobalPermissions.SCAN_EXECUTION; import static org.sonar.db.component.ComponentTesting.newFileDto; @@ -89,8 +92,11 @@ public class ProjectDataLoaderTest { ProjectRepositories ref = underTest.load(ProjectDataQuery.create().setProjectKey(project.getKey())); - assertThat(ref.fileDataByPath(project.getKey())).hasSize(1); - FileData fileData = ref.fileData(project.getKey(), file.path()); + assertTrue(ref instanceof SingleProjectRepository); + SingleProjectRepository singleProjectRepository = ((SingleProjectRepository) ref); + assertThat(singleProjectRepository.fileData()).hasSize(1); + FileData fileData = singleProjectRepository.fileDataByPath(file.path()); + assertThat(fileData).isNotNull(); assertThat(fileData.hash()).isEqualTo("123456"); } @@ -110,8 +116,10 @@ public class ProjectDataLoaderTest { ProjectRepositories ref = underTest.load(ProjectDataQuery.create().setProjectKey(project.getKey())); - assertThat(ref.fileData(project.getKey(), projectFile.path()).hash()).isEqualTo("123456"); - assertThat(ref.fileData(module.getKey(), moduleFile.path()).hash()).isEqualTo("789456"); + assertTrue(ref instanceof MultiModuleProjectRepository); + MultiModuleProjectRepository repository = ((MultiModuleProjectRepository) ref); + assertThat(repository.fileData(project.getKey(), projectFile.path()).hash()).isEqualTo("123456"); + assertThat(repository.fileData(module.getKey(), moduleFile.path()).hash()).isEqualTo("789456"); } @Test @@ -133,8 +141,10 @@ public class ProjectDataLoaderTest { .setProjectKey(project.getKey()) .setBranch("my_branch")); - assertThat(ref.fileData(branch.getKey(), projectFile.path()).hash()).isEqualTo("123456"); - assertThat(ref.fileData(moduleBranch.getKey(), moduleFile.path()).hash()).isEqualTo("789456"); + assertTrue(ref instanceof MultiModuleProjectRepository); + MultiModuleProjectRepository repository = ((MultiModuleProjectRepository) ref); + assertThat(repository.fileData(branch.getKey(), projectFile.path()).hash()).isEqualTo("123456"); + assertThat(repository.fileData(moduleBranch.getKey(), moduleFile.path()).hash()).isEqualTo("789456"); } @Test diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/DefaultProjectRepositoriesLoader.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/DefaultProjectRepositoriesLoader.java index 043264de8b3..271b84bceee 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/DefaultProjectRepositoriesLoader.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/DefaultProjectRepositoriesLoader.java @@ -20,12 +20,11 @@ package org.sonar.scanner.repository; import com.google.common.base.Throwables; -import com.google.common.collect.HashBasedTable; -import com.google.common.collect.Table; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.util.Date; +import java.util.HashMap; import java.util.Map; import javax.annotation.Nullable; import org.apache.commons.io.IOUtils; @@ -34,9 +33,7 @@ import org.slf4j.LoggerFactory; import org.sonar.api.utils.MessageException; import org.sonar.scanner.bootstrap.ScannerWsClient; import org.sonar.scanner.util.ScannerUtils; -import org.sonarqube.ws.Batch; import org.sonarqube.ws.Batch.WsProjectResponse; -import org.sonarqube.ws.Batch.WsProjectResponse.FileDataByPath; import org.sonarqube.ws.client.GetRequest; import org.sonarqube.ws.client.HttpException; import org.sonarqube.ws.client.WsResponse; @@ -62,7 +59,7 @@ public class DefaultProjectRepositoriesLoader implements ProjectRepositoriesLoad } LOG.debug("Project repository not available - continuing without it"); - return new ProjectRepositories(); + return new SingleProjectRepository(); } } @@ -97,22 +94,32 @@ public class DefaultProjectRepositoriesLoader implements ProjectRepositoriesLoad private static ProjectRepositories processStream(InputStream is, String projectKey) { try { WsProjectResponse response = WsProjectResponse.parseFrom(is); - - Table fileDataTable = HashBasedTable.create(); - - Map fileDataByModuleAndPath = response.getFileDataByModuleAndPath(); - for (Map.Entry e1 : fileDataByModuleAndPath.entrySet()) { - for (Map.Entry e2 : e1.getValue().getFileDataByPath().entrySet()) { - FileData fd = new FileData(e2.getValue().getHash(), e2.getValue().getRevision()); - fileDataTable.put(e1.getKey(), e2.getKey(), fd); - } + if (response.getFileDataByModuleAndPathCount() == 0) { + return new SingleProjectRepository(constructFileDataMap(response.getFileDataByPathMap()), new Date(response.getLastAnalysisDate())); + } else { + final Map repositoriesPerModule = new HashMap<>(); + response.getFileDataByModuleAndPathMap().keySet().forEach(moduleKey -> { + WsProjectResponse.FileDataByPath filePaths = response.getFileDataByModuleAndPathMap().get(moduleKey); + repositoriesPerModule.put(moduleKey, new SingleProjectRepository( + constructFileDataMap(filePaths.getFileDataByPathMap()), new Date(response.getLastAnalysisDate()))); + }); + return new MultiModuleProjectRepository(repositoriesPerModule, new Date(response.getLastAnalysisDate())); } - return new ProjectRepositories(fileDataTable, new Date(response.getLastAnalysisDate())); } catch (IOException e) { throw new IllegalStateException("Couldn't load project repository for " + projectKey, e); } finally { IOUtils.closeQuietly(is); } } + + private static Map constructFileDataMap(Map content) { + Map fileDataMap = new HashMap<>(); + content.forEach((key, value) -> { + FileData fd = new FileData(value.getHash(), value.getRevision()); + fileDataMap.put(key, fd); + }); + + return fileDataMap; + } } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/MultiModuleProjectRepository.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/MultiModuleProjectRepository.java new file mode 100644 index 00000000000..6876d638547 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/MultiModuleProjectRepository.java @@ -0,0 +1,45 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.repository; + +import com.google.common.collect.ImmutableMap; +import java.util.Date; +import java.util.Map; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +@Immutable +public class MultiModuleProjectRepository extends ProjectRepositories { + + private Map repositoriesPerModule; + + public MultiModuleProjectRepository(Map repositoriesPerModule, @Nullable Date lastAnalysisDate) { + super(lastAnalysisDate, true); + this.repositoriesPerModule = ImmutableMap.copyOf(repositoriesPerModule); + } + + @CheckForNull + public FileData fileData(String moduleKeyWithBranch, String path) { + SingleProjectRepository repository = repositoriesPerModule.get(moduleKeyWithBranch); + return repository == null ? null : repository.fileData(path); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/ProjectRepositories.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/ProjectRepositories.java index bed6c1c2c34..e4cce89c392 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/ProjectRepositories.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/ProjectRepositories.java @@ -19,48 +19,33 @@ */ package org.sonar.scanner.repository; -import com.google.common.collect.ImmutableTable; -import com.google.common.collect.Table; import java.util.Date; -import java.util.Map; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; +import org.sonar.api.batch.fs.internal.DefaultInputFile; @Immutable -public class ProjectRepositories { - private final ImmutableTable fileDataByModuleAndPath; +public abstract class ProjectRepositories { private final Date lastAnalysisDate; private final boolean exists; - public ProjectRepositories() { - this.exists = false; - this.fileDataByModuleAndPath = new ImmutableTable.Builder().build(); - this.lastAnalysisDate = null; - } - - public ProjectRepositories(Table fileDataByModuleAndPath, - @Nullable Date lastAnalysisDate) { - this.fileDataByModuleAndPath = ImmutableTable.copyOf(fileDataByModuleAndPath); + public ProjectRepositories(@Nullable Date lastAnalysisDate, boolean exists) { this.lastAnalysisDate = lastAnalysisDate; - this.exists = true; + this.exists = exists; } public boolean exists() { return exists; } - public Map fileDataByPath(String moduleKey) { - return fileDataByModuleAndPath.row(moduleKey); - } - - public Table fileDataByModuleAndPath() { - return fileDataByModuleAndPath; - } - @CheckForNull - public FileData fileData(String projectKeyWithBranch, String path) { - return fileDataByModuleAndPath.get(projectKeyWithBranch, path); + public FileData fileData(String moduleKeyWithBranch, DefaultInputFile inputFile) { + if (this instanceof SingleProjectRepository) { + return ((SingleProjectRepository) this).fileData(inputFile.getProjectRelativePath()); + } else { + return ((MultiModuleProjectRepository) this).fileData(moduleKeyWithBranch, inputFile.getModuleRelativePath()); + } } @CheckForNull diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/SingleProjectRepository.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/SingleProjectRepository.java new file mode 100644 index 00000000000..0d9637cf2e3 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/SingleProjectRepository.java @@ -0,0 +1,49 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.repository; + +import com.google.common.collect.ImmutableMap; +import java.util.Date; +import java.util.Map; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +@Immutable +public class SingleProjectRepository extends ProjectRepositories { + + private final ImmutableMap fileDataByPath; + + public SingleProjectRepository() { + super(null, false); + this.fileDataByPath = ImmutableMap.builder().build(); + } + + public SingleProjectRepository(Map fileDataByPath, + @Nullable Date lastAnalysisDate) { + super(lastAnalysisDate, true); + this.fileDataByPath = ImmutableMap.copyOf(fileDataByPath); + } + + @CheckForNull + public FileData fileData(String path) { + return fileDataByPath.get(path); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/StatusDetection.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/StatusDetection.java index 2554bf6b2fe..dedb65043c7 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/StatusDetection.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/StatusDetection.java @@ -34,7 +34,6 @@ import static org.sonar.api.batch.fs.InputFile.Status.SAME; @Immutable public class StatusDetection { - private final ProjectRepositories projectRepositories; private final ScmChangedFiles scmChangedFiles; @@ -43,8 +42,8 @@ public class StatusDetection { this.scmChangedFiles = scmChangedFiles; } - InputFile.Status status(String projectKeyWithBranch, DefaultInputFile inputFile, String hash) { - FileData fileDataPerPath = projectRepositories.fileData(projectKeyWithBranch, inputFile.relativePath()); + InputFile.Status status(String moduleKeyWithBranch, DefaultInputFile inputFile, String hash) { + FileData fileDataPerPath = projectRepositories.fileData(moduleKeyWithBranch, inputFile); if (fileDataPerPath == null) { return checkChanged(ADDED, inputFile); } @@ -64,8 +63,8 @@ public class StatusDetection { * @return null if it was not possible to get the status without calculating metadata */ @CheckForNull - public InputFile.Status getStatusWithoutMetadata(String projectKeyWithBranch, DefaultInputFile inputFile) { - FileData fileDataPerPath = projectRepositories.fileData(projectKeyWithBranch, inputFile.relativePath()); + public InputFile.Status getStatusWithoutMetadata(String moduleKeyWithBranch, DefaultInputFile inputFile) { + FileData fileDataPerPath = projectRepositories.fileData(moduleKeyWithBranch, inputFile); if (fileDataPerPath == null) { return checkChanged(ADDED, inputFile); } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmPublisher.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmPublisher.java index 78184f1dfb1..424f3ee1459 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmPublisher.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scm/ScmPublisher.java @@ -27,8 +27,8 @@ import org.sonar.api.batch.InstantiationStrategy; import org.sonar.api.batch.ScannerSide; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.fs.InputFile.Status; -import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.fs.internal.AbstractProjectOrModule; +import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.scm.ScmProvider; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; @@ -57,7 +57,7 @@ public final class ScmPublisher { private final BranchConfiguration branchConfiguration; public ScmPublisher(AbstractProjectOrModule inputModule, ScmConfiguration configuration, ProjectRepositories projectRepositories, - ModuleInputComponentStore componentStore, DefaultModuleFileSystem fs, ReportPublisher reportPublisher, BranchConfiguration branchConfiguration) { + ModuleInputComponentStore componentStore, DefaultModuleFileSystem fs, ReportPublisher reportPublisher, BranchConfiguration branchConfiguration) { this.inputModule = inputModule; this.configuration = configuration; this.projectRepositories = projectRepositories; @@ -108,7 +108,7 @@ public final class ScmPublisher { addIfNotEmpty(filesToBlame, f); } else if (!branchConfiguration.isShortOrPullRequest()) { // File status is SAME so that mean fileData exists - FileData fileData = projectRepositories.fileData(inputModule.definition().getKeyWithBranch(), inputFile.getModuleRelativePath()); + FileData fileData = projectRepositories.fileData(inputModule.definition().getKeyWithBranch(), inputFile); if (StringUtils.isEmpty(fileData.revision())) { addIfNotEmpty(filesToBlame, f); } else { diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java index eee733bc972..75111308e82 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java @@ -20,6 +20,7 @@ package org.sonar.scanner.mediumtest; import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Maps; import com.google.common.collect.Table; import java.io.File; import java.io.FileInputStream; @@ -62,6 +63,7 @@ import org.sonar.scanner.repository.ProjectRepositories; import org.sonar.scanner.repository.ProjectRepositoriesLoader; import org.sonar.scanner.repository.QualityProfileLoader; import org.sonar.scanner.repository.ServerIssuesLoader; +import org.sonar.scanner.repository.SingleProjectRepository; import org.sonar.scanner.repository.settings.SettingsLoader; import org.sonar.scanner.rule.ActiveRulesLoader; import org.sonar.scanner.rule.LoadedActiveRule; @@ -199,22 +201,22 @@ public class ScannerMediumTester extends ExternalResource { } public ScannerMediumTester addActiveRule(String repositoryKey, String ruleKey, @Nullable String templateRuleKey, String name, @Nullable String severity, - @Nullable String internalKey, @Nullable String languag) { + @Nullable String internalKey, @Nullable String language) { LoadedActiveRule r = new LoadedActiveRule(); r.setInternalKey(internalKey); r.setRuleKey(RuleKey.of(repositoryKey, ruleKey)); r.setName(name); r.setTemplateRuleKey(templateRuleKey); - r.setLanguage(languag); + r.setLanguage(language); r.setSeverity(severity); activeRules.addActiveRule(r); return this; } - public ScannerMediumTester addFileData(String moduleKey, String path, FileData fileData) { - projectRefProvider.addFileData(moduleKey, path, fileData); + public ScannerMediumTester addFileData(String path, FileData fileData) { + projectRefProvider.addFileData(path, fileData); return this; } @@ -368,17 +370,16 @@ public class ScannerMediumTester extends ExternalResource { } private static class FakeProjectRepositoriesLoader implements ProjectRepositoriesLoader { - - private Table fileDataTable = HashBasedTable.create(); + private Map fileDataMap = Maps.newHashMap(); private Date lastAnalysisDate; @Override public ProjectRepositories load(String projectKey, boolean isIssuesMode, @Nullable String branchBase) { - return new ProjectRepositories(fileDataTable, lastAnalysisDate); + return new SingleProjectRepository(fileDataMap, lastAnalysisDate); } - public FakeProjectRepositoriesLoader addFileData(String moduleKey, String path, FileData fileData) { - fileDataTable.put(moduleKey, path, fileData); + public FakeProjectRepositoriesLoader addFileData(String path, FileData fileData) { + fileDataMap.put(path, fileData); return this; } diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/branch/BranchMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/branch/BranchMediumTest.java index 62899cf17f1..7c52194f8de 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/branch/BranchMediumTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/branch/BranchMediumTest.java @@ -66,7 +66,7 @@ public class BranchMediumTest { String md5sum = new FileMetadata() .readMetadata(Files.newInputStream(filepath), StandardCharsets.UTF_8, FILE_PATH) .hash(); - tester.addFileData(PROJECT_KEY, FILE_PATH, new FileData(md5sum, "1.1")); + tester.addFileData(FILE_PATH, new FileData(md5sum, "1.1")); } @Test diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issuesmode/ScanOnlyChangedTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issuesmode/ScanOnlyChangedTest.java index 4f68f3040fa..1c31872a3a8 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issuesmode/ScanOnlyChangedTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issuesmode/ScanOnlyChangedTest.java @@ -47,9 +47,9 @@ import org.sonar.api.CoreProperties; import org.sonar.api.batch.fs.internal.FileMetadata; import org.sonar.api.utils.log.LogTester; import org.sonar.scanner.issue.tracking.TrackedIssue; +import org.sonar.scanner.mediumtest.AnalysisResult; import org.sonar.scanner.mediumtest.ScannerMediumTester; import org.sonar.scanner.mediumtest.ScannerMediumTester.AnalysisBuilder; -import org.sonar.scanner.mediumtest.AnalysisResult; import org.sonar.scanner.protocol.Constants.Severity; import org.sonar.scanner.protocol.input.ScannerInput.ServerIssue; import org.sonar.scanner.repository.FileData; @@ -104,7 +104,7 @@ public class ScanOnlyChangedTest { tester // this will cause the file to have status==SAME - .addFileData(projectKey, filePath, new FileData(md5sum, null)) + .addFileData(filePath, new FileData(md5sum, null)) .setPreviousAnalysisDate(new Date()) // Existing issue that is copied .mockServerIssue(ServerIssue.newBuilder().setKey("xyz") diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/scm/ScmMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/scm/ScmMediumTest.java index 94f6030b3e6..42d7f836bf5 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/scm/ScmMediumTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/scm/ScmMediumTest.java @@ -69,10 +69,10 @@ public class ScmMediumTest { .addRules(new XooRulesDefinition()) // active a rule just to be sure that xoo files are published .addActiveRule("xoo", "xoo:OneIssuePerFile", null, "One Issue Per File", null, null, null) - .addFileData("com.foo.project", CHANGED_CONTENT_SCM_ON_SERVER_XOO, new FileData(DigestUtils.md5Hex(SAMPLE_XOO_CONTENT), null)) - .addFileData("com.foo.project", SAME_CONTENT_NO_SCM_ON_SERVER_XOO, new FileData(DigestUtils.md5Hex(SAMPLE_XOO_CONTENT), null)) - .addFileData("com.foo.project", SAME_CONTENT_SCM_ON_SERVER_XOO, new FileData(DigestUtils.md5Hex(SAMPLE_XOO_CONTENT), "1.1")) - .addFileData("com.foo.project", NO_BLAME_SCM_ON_SERVER_XOO, new FileData(DigestUtils.md5Hex(SAMPLE_XOO_CONTENT), "1.1")); + .addFileData(CHANGED_CONTENT_SCM_ON_SERVER_XOO, new FileData(DigestUtils.md5Hex(SAMPLE_XOO_CONTENT), null)) + .addFileData(SAME_CONTENT_NO_SCM_ON_SERVER_XOO, new FileData(DigestUtils.md5Hex(SAMPLE_XOO_CONTENT), null)) + .addFileData(SAME_CONTENT_SCM_ON_SERVER_XOO, new FileData(DigestUtils.md5Hex(SAMPLE_XOO_CONTENT), "1.1")) + .addFileData(NO_BLAME_SCM_ON_SERVER_XOO, new FileData(DigestUtils.md5Hex(SAMPLE_XOO_CONTENT), "1.1")); @Test public void testScmMeasure() throws IOException, URISyntaxException { diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/DefaultProjectRepositoriesLoaderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/DefaultProjectRepositoriesLoaderTest.java index c4749d87a2b..fff3d8a7830 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/DefaultProjectRepositoriesLoaderTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/DefaultProjectRepositoriesLoaderTest.java @@ -28,6 +28,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.utils.MessageException; import org.sonar.scanner.WsTestUtil; import org.sonar.scanner.bootstrap.ScannerWsClient; @@ -124,9 +125,11 @@ public class DefaultProjectRepositoriesLoaderTest { InputStream is = getTestResource("project.protobuf"); WsTestUtil.mockStream(wsClient, "/batch/project.protobuf?key=org.sonarsource.github%3Asonar-github-plugin&issues_mode=true", is); + DefaultInputFile file = mock(DefaultInputFile.class); + when(file.getModuleRelativePath()).thenReturn("src/test/java/org/sonar/plugins/github/PullRequestIssuePostJobTest.java"); + ProjectRepositories proj = loader.load("org.sonarsource.github:sonar-github-plugin", true, null); - FileData fd = proj.fileData("org.sonarsource.github:sonar-github-plugin", - "src/test/java/org/sonar/plugins/github/PullRequestIssuePostJobTest.java"); + FileData fd = proj.fileData("org.sonarsource.github:sonar-github-plugin", file); assertThat(fd.revision()).isEqualTo("27bf2c54633d05c5df402bbe09471fe43bd9e2e5"); assertThat(fd.hash()).isEqualTo("edb6b3b9ab92d8dc53ba90ab86cd422e"); diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/MultiModuleProjectRepositoryTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/MultiModuleProjectRepositoryTest.java new file mode 100644 index 00000000000..4bc505ecfc2 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/MultiModuleProjectRepositoryTest.java @@ -0,0 +1,61 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.repository; + +import java.util.Date; +import java.util.Map; +import org.assertj.core.util.Maps; +import org.junit.Before; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertNotNull; + +public class MultiModuleProjectRepositoryTest { + + private MultiModuleProjectRepository repository; + + @Before + public void setUp() { + Date lastAnalysisDate = new Date(); + SingleProjectRepository repository1 = new SingleProjectRepository(Maps.newHashMap("/Abc.java", new FileData("123", "456")), lastAnalysisDate); + SingleProjectRepository repository2 = new SingleProjectRepository(Maps.newHashMap("/Def.java", new FileData("567", "321")), lastAnalysisDate); + Map moduleRepositories = Maps.newHashMap("module1", repository1); + moduleRepositories.put("module2", repository2); + + repository = new MultiModuleProjectRepository(moduleRepositories, lastAnalysisDate); + } + + @Test + public void test_file_data_when_module_and_file_exist() { + FileData fileData = repository.fileData("module2", "/Def.java"); + + assertNotNull(fileData); + assertThat(fileData.hash()).isEqualTo("567"); + assertThat(fileData.revision()).isEqualTo("321"); + } + + @Test + public void test_file_data_when_module_does_not_exist() { + FileData fileData = repository.fileData("unknown", "/Def.java"); + + assertThat(fileData).isNull(); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/ProjectRepositoriesProviderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/ProjectRepositoriesProviderTest.java index 515c1b3873e..9af998d5ebd 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/ProjectRepositoriesProviderTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/ProjectRepositoriesProviderTest.java @@ -19,9 +19,9 @@ */ package org.sonar.scanner.repository; -import com.google.common.collect.HashBasedTable; -import com.google.common.collect.Table; +import com.google.common.collect.Maps; import java.util.Date; +import java.util.Map; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; @@ -55,9 +55,9 @@ public class ProjectRepositoriesProviderTest { public void setUp() { MockitoAnnotations.initMocks(this); - Table t2 = HashBasedTable.create(); + Map fileMap = Maps.newHashMap(); - project = new ProjectRepositories(t2, new Date()); + project = new SingleProjectRepository(fileMap, new Date()); provider = new ProjectRepositoriesProvider(); when(props.getKeyWithBranch()).thenReturn("key"); diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/SingleProjectRepositoryTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/SingleProjectRepositoryTest.java new file mode 100644 index 00000000000..8a92b4c746b --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/SingleProjectRepositoryTest.java @@ -0,0 +1,55 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.repository; + +import java.util.Date; +import org.assertj.core.util.Maps; +import org.junit.Before; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertNotNull; + +public class SingleProjectRepositoryTest { + + private SingleProjectRepository repository; + + @Before + public void setUp() { + Date lastAnalysisDate = new Date(); + repository = new SingleProjectRepository(Maps.newHashMap("/Abc.java", new FileData("123", "456")), lastAnalysisDate); + } + + @Test + public void test_file_data_when_file_exists() { + FileData fileData = repository.fileData("/Abc.java"); + + assertNotNull(fileData); + assertThat(fileData.hash()).isEqualTo("123"); + assertThat(fileData.revision()).isEqualTo("456"); + } + + @Test + public void test_file_data_when_file_does_not_exist() { + FileData fileData = repository.fileData("/Def.java"); + + assertThat(fileData).isNull(); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/filesystem/StatusDetectionTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/filesystem/StatusDetectionTest.java index 09dba259df9..c73f303965b 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/filesystem/StatusDetectionTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/filesystem/StatusDetectionTest.java @@ -19,16 +19,16 @@ */ package org.sonar.scanner.scan.filesystem; -import com.google.common.collect.HashBasedTable; -import com.google.common.collect.Table; import java.nio.file.Paths; import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import org.junit.Test; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.fs.internal.TestInputFileBuilder; import org.sonar.scanner.repository.FileData; -import org.sonar.scanner.repository.ProjectRepositories; +import org.sonar.scanner.repository.SingleProjectRepository; import org.sonar.scanner.scm.ScmChangedFiles; import static org.assertj.core.api.Assertions.assertThat; @@ -40,7 +40,7 @@ import static org.mockito.Mockito.when; public class StatusDetectionTest { @Test public void detect_status() { - ProjectRepositories ref = new ProjectRepositories(createTable(), null); + SingleProjectRepository ref = new SingleProjectRepository(createFileDataPerPathMap(), null); ScmChangedFiles changedFiles = new ScmChangedFiles(null); StatusDetection statusDetection = new StatusDetection(ref, changedFiles); @@ -51,7 +51,7 @@ public class StatusDetectionTest { @Test public void detect_status_branches_exclude() { - ProjectRepositories ref = new ProjectRepositories(createTable(), null); + SingleProjectRepository ref = new SingleProjectRepository(createFileDataPerPathMap(), null); ScmChangedFiles changedFiles = new ScmChangedFiles(Collections.emptyList()); StatusDetection statusDetection = new StatusDetection(ref, changedFiles); @@ -65,34 +65,34 @@ public class StatusDetectionTest { @Test public void detect_status_without_metadata() { DefaultInputFile mockedFile = mock(DefaultInputFile.class); - when(mockedFile.relativePath()).thenReturn("module/src/Foo.java"); + when(mockedFile.getProjectRelativePath()).thenReturn("module/src/Foo.java"); when(mockedFile.path()).thenReturn(Paths.get("module", "src", "Foo.java")); - ProjectRepositories ref = new ProjectRepositories(createTable(), null); + SingleProjectRepository ref = new SingleProjectRepository(createFileDataPerPathMap(), null); ScmChangedFiles changedFiles = new ScmChangedFiles(Collections.singletonList(Paths.get("module", "src", "Foo.java"))); StatusDetection statusDetection = new StatusDetection(ref, changedFiles); assertThat(statusDetection.getStatusWithoutMetadata("foo", mockedFile)).isEqualTo(InputFile.Status.ADDED); verify(mockedFile).path(); - verify(mockedFile).relativePath(); + verify(mockedFile).getProjectRelativePath(); verifyNoMoreInteractions(mockedFile); } @Test public void detect_status_branches_confirm() { - ProjectRepositories ref = new ProjectRepositories(createTable(), null); + SingleProjectRepository ref = new SingleProjectRepository(createFileDataPerPathMap(), null); ScmChangedFiles changedFiles = new ScmChangedFiles(Collections.singletonList(Paths.get("module", "src", "Foo.java"))); StatusDetection statusDetection = new StatusDetection(ref, changedFiles); assertThat(statusDetection.status("foo", createFile("src/Foo.java"), "XXXXX")).isEqualTo(InputFile.Status.CHANGED); } - private static Table createTable() { - Table t = HashBasedTable.create(); + private static Map createFileDataPerPathMap() { + Map t = new HashMap<>(); - t.put("foo", "src/Foo.java", new FileData("ABCDE", "12345789")); - t.put("foo", "src/Bar.java", new FileData("FGHIJ", "123456789")); + t.put("src/Foo.java", new FileData("ABCDE", "12345789")); + t.put("src/Bar.java", new FileData("FGHIJ", "123456789")); return t; } diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/MultiModuleProjectRepository.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/MultiModuleProjectRepository.java new file mode 100644 index 00000000000..7fbb9bb948f --- /dev/null +++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/MultiModuleProjectRepository.java @@ -0,0 +1,52 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.protocol.input; + +import com.google.common.collect.Maps; +import java.util.Map; +import java.util.Optional; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; + +public class MultiModuleProjectRepository extends ProjectRepositories { + private Map repositoryPerModule = Maps.newHashMap(); + + public ProjectRepositories addFileDataToModule(String moduleKey, @Nullable String path, FileData fileData) { + if (path == null || (fileData.hash() == null && fileData.revision() == null)) { + return this; + } + + SingleProjectRepository repository = repositoryPerModule.computeIfAbsent(moduleKey, k -> new SingleProjectRepository()); + repository.addFileData(path, fileData); + return this; + } + + public Map repositoriesByModule() { + return repositoryPerModule; + } + + @CheckForNull + public FileData fileData(String moduleKeyWithBranch, @Nullable String path) { + Optional moduleRepository = Optional.ofNullable(repositoryPerModule.get(moduleKeyWithBranch)); + return moduleRepository + .map(singleProjectRepository -> singleProjectRepository.fileDataByPath(path)) + .orElse(null); + } +} diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/ProjectRepositories.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/ProjectRepositories.java index 9a557aa64cc..3737d98ab77 100644 --- a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/ProjectRepositories.java +++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/ProjectRepositories.java @@ -19,10 +19,7 @@ */ package org.sonar.scanner.protocol.input; -import java.util.Collections; import java.util.Date; -import java.util.HashMap; -import java.util.Map; import javax.annotation.CheckForNull; import javax.annotation.Nullable; @@ -30,34 +27,11 @@ import javax.annotation.Nullable; * Container for all project data going from server to batch. * This is not an API since server and batch always share the same version. */ -public class ProjectRepositories { +public abstract class ProjectRepositories { private long timestamp; - private Map> fileDataByModuleAndPath = new HashMap<>(); - private Date lastAnalysisDate; - - public Map> fileDataByModuleAndPath() { - return fileDataByModuleAndPath; - } - - public Map fileDataByPath(String moduleKey) { - return fileDataByModuleAndPath.containsKey(moduleKey) ? fileDataByModuleAndPath.get(moduleKey) : Collections.emptyMap(); - } - - public ProjectRepositories addFileData(String moduleKey, @Nullable String path, FileData fileData) { - if (path == null || (fileData.hash() == null && fileData.revision() == null)) { - return this; - } - Map existingFileDataByPath = fileDataByModuleAndPath.computeIfAbsent(moduleKey, k -> new HashMap<>()); - existingFileDataByPath.put(path, fileData); - return this; - } - - @CheckForNull - public FileData fileData(String projectKey, String path) { - return fileDataByPath(projectKey).get(path); - } + private Date lastAnalysisDate; public long timestamp() { return timestamp; diff --git a/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/SingleProjectRepository.java b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/SingleProjectRepository.java new file mode 100644 index 00000000000..37af28c03c4 --- /dev/null +++ b/sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/input/SingleProjectRepository.java @@ -0,0 +1,45 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.protocol.input; + +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nullable; + +public class SingleProjectRepository extends ProjectRepositories { + private Map fileDataByPath = new HashMap<>(); + + public ProjectRepositories addFileData(@Nullable String path, FileData fileData) { + if (path == null || (fileData.hash() == null && fileData.revision() == null)) { + return this; + } + + fileDataByPath.put(path, fileData); + return this; + } + + public Map fileData() { + return fileDataByPath; + } + + public FileData fileDataByPath(@Nullable String path) { + return fileDataByPath.get(path); + } +} diff --git a/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/input/MultiModuleProjectRepositoryTest.java b/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/input/MultiModuleProjectRepositoryTest.java new file mode 100644 index 00000000000..61aea5c2ec7 --- /dev/null +++ b/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/input/MultiModuleProjectRepositoryTest.java @@ -0,0 +1,68 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.protocol.input; + +import org.junit.Before; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MultiModuleProjectRepositoryTest { + + private MultiModuleProjectRepository repository; + + @Before + public void setUp() { + repository = new MultiModuleProjectRepository(); + } + + @Test + public void add_file_data_to_nodule() { + FileData fileData1 = new FileData("123", "456"); + FileData fileData2 = new FileData("153", "6432"); + FileData fileData3 = new FileData("987", "6343"); + + repository.addFileDataToModule("Module1", "/Abc.java", fileData1); + repository.addFileDataToModule("Module1", "/Xyz.java", fileData2); + repository.addFileDataToModule("Module2", "/Def.java", fileData3); + + assertThat(repository.repositoriesByModule()).hasSize(2); + assertThat(repository.fileData("Module1", "/Xyz.java")).isEqualTo(fileData2); + assertThat(repository.fileData("Module2", "/Def.java")).isEqualTo(fileData3); + } + + @Test + public void add_file_does_not_add_the_file_without_path() { + FileData fileData = new FileData("123", "456"); + + repository.addFileDataToModule("module1", null, fileData); + + assertThat(repository.repositoriesByModule()).hasSize(0); + } + + @Test + public void add_file_does_not_add_the_file_without_revision_and_hash() { + FileData fileData = new FileData(null, null); + + repository.addFileDataToModule("module2", "/Abc.java", fileData); + + assertThat(repository.repositoriesByModule()).hasSize(0); + } +} diff --git a/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/input/SingleProjectRepositoryTest.java b/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/input/SingleProjectRepositoryTest.java new file mode 100644 index 00000000000..c16c0fc8799 --- /dev/null +++ b/sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/input/SingleProjectRepositoryTest.java @@ -0,0 +1,64 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.protocol.input; + +import com.google.common.collect.Maps; +import org.junit.Before; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SingleProjectRepositoryTest { + private SingleProjectRepository repository; + + @Before + public void setUp() { + repository = new SingleProjectRepository(); + } + + @Test + public void add_file_data() { + FileData fileData = new FileData("123", "456"); + + repository.addFileData("/Abc.java", fileData); + + assertThat(repository.fileData()).hasSize(1); + assertThat(repository.fileData()).contains(Maps.immutableEntry("/Abc.java", fileData)); + assertThat(repository.fileDataByPath("/Abc.java")).isEqualTo(fileData); + } + + @Test + public void add_file_data_doesnt_add_the_file_without_path() { + FileData fileData = new FileData("123", "456"); + + repository.addFileData(null, fileData); + + assertThat(repository.fileData()).hasSize(0); + } + + @Test + public void add_file_data_doesnt_add_the_file_without_revision_and_hash() { + FileData fileData = new FileData(null, null); + + repository.addFileData("/Abc.java", fileData); + + assertThat(repository.fileData()).hasSize(0); + } +} diff --git a/sonar-ws/src/main/protobuf/ws-batch.proto b/sonar-ws/src/main/protobuf/ws-batch.proto index d600acadc32..54b2a5dfba4 100644 --- a/sonar-ws/src/main/protobuf/ws-batch.proto +++ b/sonar-ws/src/main/protobuf/ws-batch.proto @@ -30,6 +30,7 @@ message WsProjectResponse { optional int64 timestamp = 1; map fileDataByModuleAndPath = 3; optional int64 lastAnalysisDate = 4; + map fileDataByPath = 5; message FileDataByPath { map FileDataByPath = 1; -- 2.39.5