diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ComponentTreeBuilder.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ComponentTreeBuilder.java index 68080a5fa07e..91479aee6773 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ComponentTreeBuilder.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ComponentTreeBuilder.java @@ -31,6 +31,7 @@ import javax.annotation.Nullable; import org.sonar.api.internal.apachecommons.lang.StringUtils; import org.sonar.ce.task.projectanalysis.analysis.Branch; +import org.sonar.ce.task.projectanalysis.issue.IssueRelocationToRoot; import org.sonar.core.util.stream.MoreCollectors; import org.sonar.db.component.SnapshotDto; import org.sonar.scanner.protocol.output.ScannerReport; @@ -67,6 +68,7 @@ public class ComponentTreeBuilder { private final Branch branch; @Nullable private final SnapshotDto baseAnalysis; + private final IssueRelocationToRoot issueRelocationToRoot; private ScannerReport.Component rootComponent; private String scmBasePath; @@ -77,7 +79,7 @@ public ComponentTreeBuilder( Function uuidSupplier, Function scannerComponentSupplier, Project project, - Branch branch, @Nullable SnapshotDto baseAnalysis) { + Branch branch, @Nullable SnapshotDto baseAnalysis, IssueRelocationToRoot issueRelocationToRoot) { this.keyGenerator = keyGenerator; this.publicKeyGenerator = publicKeyGenerator; @@ -86,17 +88,18 @@ public ComponentTreeBuilder( this.project = project; this.branch = branch; this.baseAnalysis = baseAnalysis; + this.issueRelocationToRoot = issueRelocationToRoot; } public Component buildProject(ScannerReport.Component project, String scmBasePath) { this.rootComponent = project; this.scmBasePath = trimToNull(scmBasePath); - Node root = buildFolderHierarchy(project); - return buildNode(root, ""); + Node root = createProjectHierarchy(project); + return buildComponent(root, ""); } - private Node buildFolderHierarchy(ScannerReport.Component rootComponent) { + private Node createProjectHierarchy(ScannerReport.Component rootComponent) { Preconditions.checkArgument(rootComponent.getType() == ScannerReport.Component.ComponentType.PROJECT, "Expected root component of type 'PROJECT'"); LinkedList queue = new LinkedList<>(); @@ -112,16 +115,11 @@ private Node buildFolderHierarchy(ScannerReport.Component rootComponent) { ScannerReport.Component component = queue.removeFirst(); switch (component.getType()) { case FILE: - addFileOrDirectory(root, component); + addFile(root, component); break; case MODULE: - - component.getChildRefList().stream() - .map(scannerComponentSupplier) - .forEach(queue::addLast); - break; case DIRECTORY: - addFileOrDirectory(root, component); + issueRelocationToRoot.relocate(rootComponent, component); component.getChildRefList().stream() .map(scannerComponentSupplier) .forEach(queue::addLast); @@ -133,9 +131,8 @@ private Node buildFolderHierarchy(ScannerReport.Component rootComponent) { return root; } - private static void addFileOrDirectory(Node root, ScannerReport.Component file) { - Preconditions.checkArgument(file.getType() != ScannerReport.Component.ComponentType.FILE || !StringUtils.isEmpty(file.getProjectRelativePath()), - "Files should have a relative path: " + file); + private static void addFile(Node root, ScannerReport.Component file) { + Preconditions.checkArgument(!StringUtils.isEmpty(file.getProjectRelativePath()), "Files should have a project relative path: " + file); String[] split = StringUtils.split(file.getProjectRelativePath(), '/'); Node currentNode = root.children().computeIfAbsent("", k -> new Node()); @@ -145,7 +142,7 @@ private static void addFileOrDirectory(Node root, ScannerReport.Component file) currentNode.reportComponent = file; } - private Component buildNode(Node node, String currentPath) { + private Component buildComponent(Node node, String currentPath) { List childComponents = buildChildren(node, currentPath); ScannerReport.Component component = node.reportComponent(); @@ -157,7 +154,7 @@ private Component buildNode(Node node, String currentPath) { } } - return buildDirectory(currentPath, component, childComponents); + return buildDirectory(currentPath, childComponents); } private List buildChildren(Node node, String currentPath) { @@ -165,14 +162,15 @@ private List buildChildren(Node node, String currentPath) { for (Map.Entry e : node.children().entrySet()) { String path = buildPath(currentPath, e.getKey()); - Node n = e.getValue(); + Node childNode = e.getValue(); - while (n.children().size() == 1 && n.children().values().iterator().next().children().size() > 0) { - Map.Entry childEntry = n.children().entrySet().iterator().next(); + // collapse folders that only contain one folder + while (childNode.children().size() == 1 && childNode.children().values().iterator().next().children().size() > 0) { + Map.Entry childEntry = childNode.children().entrySet().iterator().next(); path = buildPath(path, childEntry.getKey()); - n = childEntry.getValue(); + childNode = childEntry.getValue(); } - children.add(buildNode(n, path)); + children.add(buildComponent(childNode, path)); } return children; } @@ -216,18 +214,17 @@ private ComponentImpl buildFile(ScannerReport.Component component) { .build(); } - private ComponentImpl buildDirectory(String path, @Nullable ScannerReport.Component scannerComponent, List children) { + private ComponentImpl buildDirectory(String path, List children) { String nonEmptyPath = path.isEmpty() ? "/" : path; String key = keyGenerator.generateKey(rootComponent, nonEmptyPath); String publicKey = publicKeyGenerator.generateKey(rootComponent, nonEmptyPath); - Integer ref = scannerComponent != null ? scannerComponent.getRef() : null; return ComponentImpl.builder(Component.Type.DIRECTORY) .setUuid(uuidSupplier.apply(key)) .setDbKey(key) .setKey(publicKey) .setName(publicKey) .setStatus(convertStatus(FileStatus.UNAVAILABLE)) - .setReportAttributes(createAttributesBuilder(ref, nonEmptyPath, scmBasePath, path).build()) + .setReportAttributes(createAttributesBuilder(null, nonEmptyPath, scmBasePath, path).build()) .addChildren(children) .build(); } @@ -359,7 +356,7 @@ private static ReportAttributes.Builder createAttributesBuilder(@Nullable Intege @CheckForNull private static String computeScmPath(@Nullable String scmBasePath, String scmRelativePath) { if (scmRelativePath.isEmpty()) { - return null; + return scmBasePath; } if (scmBasePath == null) { return scmRelativePath; diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactory.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactory.java index 58e5ac7041e6..b87e740492a8 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactory.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactory.java @@ -22,20 +22,69 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.resources.Qualifiers; +import org.sonar.core.component.ComponentKeys; import org.sonar.core.util.Uuids; import org.sonar.db.DbClient; import org.sonar.db.DbSession; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.component.ComponentWithModuleUuidDto; import org.sonar.db.component.KeyWithUuidDto; public class ComponentUuidFactory { - private final Map uuidsByKey = new HashMap<>(); public ComponentUuidFactory(DbClient dbClient, DbSession dbSession, String rootKey) { - List keys = dbClient.componentDao().selectUuidsByKeyFromProjectKey(dbSession, rootKey); - for (KeyWithUuidDto dto : keys) { - uuidsByKey.put(dto.key(), dto.uuid()); + Map modulePathsByUuid = loadModulePathsByUuid(dbClient, dbSession, rootKey); + + if (modulePathsByUuid.isEmpty()) { + // only contains root project + List keys = dbClient.componentDao().selectUuidsByKeyFromProjectKey(dbSession, rootKey); + keys.forEach(dto -> uuidsByKey.put(dto.key(), dto.uuid())); + } else { + List dtos = loadComponentsWithModuleUuid(dbClient, dbSession, rootKey); + for (ComponentWithModuleUuidDto dto : dtos) { + String pathFromRootProject = modulePathsByUuid.get(dto.moduleUuid()); + String componentPath = StringUtils.isEmpty(pathFromRootProject) ? dto.path() : (pathFromRootProject + "/" + dto.path()); + uuidsByKey.put(ComponentKeys.createEffectiveKey(rootKey, componentPath), dto.uuid()); + } + } + } + + private static List loadComponentsWithModuleUuid(DbClient dbClient, DbSession dbSession, String rootKey) { + return dbClient.componentDao().selectComponentsWithModuleUuidFromProjectKey(dbSession, rootKey); + } + + private static Map loadModulePathsByUuid(DbClient dbClient, DbSession dbSession, String rootKey) { + List moduleDtos = dbClient.componentDao() + .selectEnabledModulesFromProjectKey(dbSession, rootKey, false).stream() + .filter(c -> Qualifiers.MODULE.equals(c.qualifier())) + .collect(Collectors.toList()); + + Map dtoByUuid = moduleDtos.stream() + .collect(Collectors.toMap(ComponentDto::uuid, dto -> dto)); + + Map modulePathByUuid = new HashMap<>(); + + for (ComponentDto dto : moduleDtos) { + String modulePath = null; + ComponentDto currentDto = dto; + while (currentDto != null && currentDto.moduleUuid() != null) { + String path = currentDto.path(); + if (modulePath == null) { + modulePath = path; + } else { + modulePath = path + "/" + modulePath; + } + currentDto = dtoByUuid.get(currentDto.moduleUuid()); + } + + modulePathByUuid.put(dto.uuid(), modulePath); } + + return modulePathByUuid; } /** diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java index 051b90d4c27d..7f661f1cde32 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java @@ -64,6 +64,7 @@ import org.sonar.ce.task.projectanalysis.issue.IssueCounter; import org.sonar.ce.task.projectanalysis.issue.IssueCreationDateCalculator; import org.sonar.ce.task.projectanalysis.issue.IssueLifecycle; +import org.sonar.ce.task.projectanalysis.issue.IssueRelocationToRoot; import org.sonar.ce.task.projectanalysis.issue.IssueTrackingDelegator; import org.sonar.ce.task.projectanalysis.issue.IssueVisitors; import org.sonar.ce.task.projectanalysis.issue.IssuesRepositoryVisitor; @@ -232,6 +233,7 @@ private static List componentClasses() { ComponentsWithUnprocessedIssues.class, ComponentIssuesRepositoryImpl.class, IssueFilter.class, + IssueRelocationToRoot.class, // common rules CommonRuleEngineImpl.class, diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/duplication/DuplicationMeasures.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/duplication/DuplicationMeasures.java index 861c1afe725c..f29477995f37 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/duplication/DuplicationMeasures.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/duplication/DuplicationMeasures.java @@ -156,10 +156,7 @@ private void initializeForProjectView(CounterInitializationContext context) { private static int getMeasure(CounterInitializationContext context, String metricKey) { Optional files = context.getMeasure(metricKey); - if (files.isPresent()) { - return files.get().getIntValue(); - } - return 0; + return files.map(Measure::getIntValue).orElse(0); } } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueRelocationToRoot.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueRelocationToRoot.java new file mode 100644 index 000000000000..af6f87a75e19 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueRelocationToRoot.java @@ -0,0 +1,75 @@ +/* + * 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.ce.task.projectanalysis.issue; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import org.sonar.ce.task.projectanalysis.batch.BatchReportReader; +import org.sonar.core.util.CloseableIterator; +import org.sonar.scanner.protocol.output.ScannerReport; + +public class IssueRelocationToRoot { + private final BatchReportReader reader; + private final List movedIssues = new ArrayList<>(); + + public IssueRelocationToRoot(BatchReportReader reader) { + this.reader = reader; + } + + public List getMovedIssues() { + return Collections.unmodifiableList(movedIssues); + } + + public void relocate(ScannerReport.Component root, ScannerReport.Component component) { + CloseableIterator issueIt = reader.readComponentIssues(component.getRef()); + while (issueIt.hasNext()) { + ScannerReport.Issue issue = issueIt.next(); + movedIssues.add(moveIssueToRoot(root, component, issue)); + } + } + + private static ScannerReport.Issue moveIssueToRoot(ScannerReport.Component root, ScannerReport.Component component, ScannerReport.Issue issue) { + return ScannerReport.Issue.newBuilder(issue) + .clearFlow() + .clearTextRange() + .addAllFlow(issue.getFlowList().stream() + .map(flow -> moveFlow(root, component, flow)) + .collect(Collectors.toList())) + .build(); + } + + private static ScannerReport.IssueLocation moveLocation(ScannerReport.Component root, ScannerReport.Component component, ScannerReport.IssueLocation location) { + String msg = "[" + component.getProjectRelativePath() + "] " + location.getMsg(); + return ScannerReport.IssueLocation.newBuilder() + .setComponentRef(root.getRef()) + .setMsg(msg) + .build(); + } + + private static ScannerReport.Flow moveFlow(ScannerReport.Component root, ScannerReport.Component component, ScannerReport.Flow flow) { + return ScannerReport.Flow.newBuilder() + .addAllLocation(flow.getLocationList().stream() + .map(location -> moveLocation(root, component, location)) + .collect(Collectors.toList())) + .build(); + } +} diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactory.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactory.java index 2cd65c16a0a5..eb44e5e43d8e 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactory.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactory.java @@ -61,10 +61,11 @@ public class TrackerRawInputFactory { private final SourceLinesHashRepository sourceLinesHash; private final RuleRepository ruleRepository; private final ActiveRulesHolder activeRulesHolder; + private final IssueRelocationToRoot issueRelocationToRoot; public TrackerRawInputFactory(TreeRootHolder treeRootHolder, BatchReportReader reportReader, SourceLinesHashRepository sourceLinesHash, CommonRuleEngine commonRuleEngine, IssueFilter issueFilter, RuleRepository ruleRepository, - ActiveRulesHolder activeRulesHolder) { + ActiveRulesHolder activeRulesHolder, IssueRelocationToRoot issueRelocationToRoot) { this.treeRootHolder = treeRootHolder; this.reportReader = reportReader; this.sourceLinesHash = sourceLinesHash; @@ -72,6 +73,7 @@ public TrackerRawInputFactory(TreeRootHolder treeRootHolder, BatchReportReader r this.issueFilter = issueFilter; this.ruleRepository = ruleRepository; this.activeRulesHolder = activeRulesHolder; + this.issueRelocationToRoot = issueRelocationToRoot; } public Input create(Component component) { @@ -144,6 +146,13 @@ protected List loadIssues() { } } + if (component.getType() == Component.Type.PROJECT) { + // TODO apply filters? + issueRelocationToRoot.getMovedIssues().stream() + .map(i -> toIssue(getLineHashSequence(), i)) + .forEach(result::add); + } + return result; } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/BuildComponentTreeStep.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/BuildComponentTreeStep.java index f4b73f412d36..342e5cdbc814 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/BuildComponentTreeStep.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/BuildComponentTreeStep.java @@ -31,6 +31,7 @@ import org.sonar.ce.task.projectanalysis.component.ComponentUuidFactory; import org.sonar.ce.task.projectanalysis.component.DefaultBranchImpl; import org.sonar.ce.task.projectanalysis.component.MutableTreeRootHolder; +import org.sonar.ce.task.projectanalysis.issue.IssueRelocationToRoot; import org.sonar.ce.task.step.ComputationStep; import org.sonar.db.DbClient; import org.sonar.db.DbSession; @@ -47,13 +48,15 @@ public class BuildComponentTreeStep implements ComputationStep { private final BatchReportReader reportReader; private final MutableTreeRootHolder treeRootHolder; private final MutableAnalysisMetadataHolder analysisMetadataHolder; + private final IssueRelocationToRoot issueRelocationToRoot; public BuildComponentTreeStep(DbClient dbClient, BatchReportReader reportReader, - MutableTreeRootHolder treeRootHolder, MutableAnalysisMetadataHolder analysisMetadataHolder) { + MutableTreeRootHolder treeRootHolder, MutableAnalysisMetadataHolder analysisMetadataHolder, IssueRelocationToRoot issueRelocationToRoot) { this.dbClient = dbClient; this.reportReader = reportReader; this.treeRootHolder = treeRootHolder; this.analysisMetadataHolder = analysisMetadataHolder; + this.issueRelocationToRoot = issueRelocationToRoot; } @Override @@ -82,7 +85,7 @@ public void execute(ComputationStep.Context context) { reportReader::readComponent, analysisMetadataHolder.getProject(), analysisMetadataHolder.getBranch(), - baseAnalysis); + baseAnalysis, issueRelocationToRoot); String relativePathFromScmRoot = reportReader.readMetadata().getRelativePathFromScmRoot(); Component reportTreeRoot = builder.buildProject(reportProject, relativePathFromScmRoot); diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/ComponentTreeBuilderTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/ComponentTreeBuilderTest.java index f5fe6f8ab543..9f4920dac21c 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/ComponentTreeBuilderTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/ComponentTreeBuilderTest.java @@ -43,6 +43,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static org.sonar.ce.task.projectanalysis.component.ComponentVisitor.Order.PRE_ORDER; import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto; @@ -64,6 +66,7 @@ public class ComponentTreeBuilderTest { private static final EnumSet REPORT_TYPES = EnumSet.of(PROJECT, MODULE, DIRECTORY, FILE); private static final String NO_SCM_BASE_PATH = ""; + private IssueRelocationToRoot issueRelocationToRoot = mock(IssueRelocationToRoot.class); @Rule public ExpectedException expectedException = ExpectedException.none(); @@ -570,6 +573,39 @@ public void uuids_are_provided_by_supplier() { assertThat(file.getUuid()).isEqualTo("generated_c1:src/js/Foo.js_uuid"); } + @Test + public void issues_are_relocated_from_directories_and_modules_to_root() { + ScannerReport.Component project = newBuilder() + .setType(PROJECT) + .setKey("c1") + .setRef(1) + .addChildRef(2) + .build(); + ScannerReport.Component.Builder module = newBuilder() + .setRef(2) + .setType(MODULE) + .setKey("c2") + .addChildRef(3); + scannerComponentProvider.add(module); + ScannerReport.Component.Builder directory = newBuilder() + .setRef(3) + .setType(DIRECTORY) + .setProjectRelativePath("src/js") + .addChildRef(4); + scannerComponentProvider.add(directory); + ScannerReport.Component.Builder file = newBuilder() + .setRef(4) + .setType(FILE) + .setProjectRelativePath("src/js/Foo.js") + .setLines(1); + scannerComponentProvider.add(file); + + call(project); + verify(issueRelocationToRoot).relocate(project, module.build()); + verify(issueRelocationToRoot).relocate(project, directory.build()); + verifyNoMoreInteractions(issueRelocationToRoot); + } + @Test public void descriptions_of_module_directory_and_file_are_null_if_absent_from_report() { ScannerReport.Component project = newBuilder() @@ -826,19 +862,7 @@ private ComponentTreeBuilder newUnderTest(@Nullable SnapshotDto baseAnalysis, bo Branch branch = mock(Branch.class); when(branch.isMain()).thenReturn(mainBranch); return new ComponentTreeBuilder(KEY_GENERATOR, PUBLIC_KEY_GENERATOR, UUID_SUPPLIER, scannerComponentProvider, projectInDb, branch, baseAnalysis, - mock(IssueRelocationToRoot.class)); - } - - private static Map indexComponentByRef(Component root) { - Map componentsByRef = new HashMap<>(); - new DepthTraversalTypeAwareCrawler( - new TypeAwareVisitorAdapter(CrawlerDepthLimit.FILE, PRE_ORDER) { - @Override - public void visitAny(Component any) { - componentsByRef.put(any.getReportAttributes().getRef(), any); - } - }).visit(root); - return componentsByRef; + issueRelocationToRoot); } private static Map indexComponentByKey(Component root) { diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactoryTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactoryTest.java index 9b9f4c56196c..90ad37a15cbc 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactoryTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactoryTest.java @@ -36,11 +36,94 @@ public class ComponentUuidFactoryTest { @Test public void load_uuids_from_existing_components_in_db() { ComponentDto project = db.components().insertPrivateProject(); - ComponentDto module = db.components().insertComponent(ComponentTesting.newModuleDto(project)); + ComponentDto module = db.components().insertComponent(ComponentTesting + .newModuleDto(project).setPath("module1")); ComponentUuidFactory underTest = new ComponentUuidFactory(db.getDbClient(), db.getSession(), project.getDbKey()); + + assertThat(underTest.getOrCreateForKey(project.getDbKey())).isEqualTo(project.uuid()); + assertThat(underTest.getOrCreateForKey(module.getDbKey())).isNotEqualTo(module.uuid()); + } + + @Test + public void migrate_project_with_modules() { + ComponentDto project = db.components().insertPrivateProject(dto -> dto.setDbKey("project")); + ComponentDto module1 = db.components().insertComponent(ComponentTesting.newModuleDto(project) + .setDbKey("project:module1") + .setPath("module1_path")); + ComponentDto module2 = db.components().insertComponent(ComponentTesting.newModuleDto(module1) + .setDbKey("project:module1:module2") + .setPath("module2_path")); + ComponentDto file1 = db.components().insertComponent(ComponentTesting.newFileDto(project) + .setDbKey("project:file1") + .setPath("file1_path")); + ComponentDto file2 = db.components().insertComponent(ComponentTesting.newFileDto(module2) + .setDbKey("project:module1:module2:file2") + .setPath("file2_path")); + + assertThat(file2.moduleUuidPath()).isEqualTo("." + project.uuid() + "." + module1.uuid() + "." + module2.uuid() + "."); + + ComponentUuidFactory underTest = new ComponentUuidFactory(db.getDbClient(), db.getSession(), project.getDbKey()); + + // migrated files + assertThat(underTest.getOrCreateForKey("project:file1_path")).isEqualTo(file1.uuid()); + assertThat(underTest.getOrCreateForKey("project:module1_path/module2_path/file2_path")).isEqualTo(file2.uuid()); + + // project remains the same assertThat(underTest.getOrCreateForKey(project.getDbKey())).isEqualTo(project.uuid()); - assertThat(underTest.getOrCreateForKey(module.getDbKey())).isEqualTo(module.uuid()); + + // old keys with modules don't exist + assertThat(underTest.getOrCreateForKey(module1.getDbKey())).isNotEqualTo(module1.uuid()); + assertThat(underTest.getOrCreateForKey(module2.getDbKey())).isNotEqualTo(module2.uuid()); + assertThat(underTest.getOrCreateForKey(file1.getDbKey())).isNotEqualTo(file1.uuid()); + assertThat(underTest.getOrCreateForKey(file2.getDbKey())).isNotEqualTo(file2.uuid()); + } + + @Test + public void migrate_project_with_disabled_modules() { + ComponentDto project = db.components().insertPrivateProject(dto -> dto.setDbKey("project")); + ComponentDto module1 = db.components().insertComponent(ComponentTesting.newModuleDto(project) + .setDbKey("project:module1") + .setEnabled(false) + .setPath("module1_path")); + ComponentDto file1 = db.components().insertComponent(ComponentTesting.newFileDto(module1) + .setDbKey("project:file1") + .setEnabled(false) + .setPath("file1_path")); + + ComponentUuidFactory underTest = new ComponentUuidFactory(db.getDbClient(), db.getSession(), project.getDbKey()); + + // migrated files + assertThat(underTest.getOrCreateForKey("project:module1_path/file1_path")).isEqualTo(file1.uuid()); + } + + @Test + public void migrate_project_having_modules_without_paths() { + ComponentDto project = db.components().insertPrivateProject(dto -> dto.setDbKey("project")); + ComponentDto module = db.components().insertComponent(ComponentTesting.newModuleDto(project) + .setDbKey("project:module") + .setPath(null)); + ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(module) + .setDbKey("project:module:file") + .setPath("file_path")); + + assertThat(file.moduleUuidPath()).isEqualTo("." + project.uuid() + "." + module.uuid() + "."); + + ComponentUuidFactory underTest = new ComponentUuidFactory(db.getDbClient(), db.getSession(), project.getDbKey()); + + // file will have this key since the module has a null path + assertThat(underTest.getOrCreateForKey("project:file_path")).isEqualTo(file.uuid()); + + // migrated module + // TODO!! + //assertThat(underTest.getOrCreateForKey("project:module")).isEqualTo(module.uuid()); + + // project remains the same + //assertThat(underTest.getOrCreateForKey(project.getDbKey())).isEqualTo(project.uuid()); + + // old keys with modules don't exist + assertThat(underTest.getOrCreateForKey(module.getDbKey())).isNotEqualTo(module.uuid()); + assertThat(underTest.getOrCreateForKey(file.getDbKey())).isNotEqualTo(file.uuid()); } @Test diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueRelocationToRootTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueRelocationToRootTest.java new file mode 100644 index 000000000000..b344e5804f6d --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueRelocationToRootTest.java @@ -0,0 +1,95 @@ +/* + * 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.ce.task.projectanalysis.issue; + +import java.util.Collections; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule; +import org.sonar.scanner.protocol.output.ScannerReport; + +import static org.assertj.core.api.Assertions.assertThat; + +public class IssueRelocationToRootTest { + @Rule + public BatchReportReaderRule reader = new BatchReportReaderRule(); + private IssueRelocationToRoot underTest = new IssueRelocationToRoot(reader); + + private ScannerReport.Component root; + private ScannerReport.Component module; + private ScannerReport.Component directory; + + @Before + public void createComponents() { + root = ScannerReport.Component.newBuilder() + .setType(ScannerReport.Component.ComponentType.PROJECT) + .setRef(1) + .setStatus(ScannerReport.Component.FileStatus.UNAVAILABLE) + .setKey("PROJECT") + .addChildRef(2) + .build(); + module = ScannerReport.Component.newBuilder() + .setType(ScannerReport.Component.ComponentType.MODULE) + .setRef(2) + .setStatus(ScannerReport.Component.FileStatus.UNAVAILABLE) + .setKey("PROJECT:MODULE") + .addChildRef(3) + .build(); + directory = ScannerReport.Component.newBuilder() + .setType(ScannerReport.Component.ComponentType.DIRECTORY) + .setRef(3) + .setStatus(ScannerReport.Component.FileStatus.UNAVAILABLE) + .setKey("PROJECT:MODULE:DIRECTORY") + .addChildRef(4) + .build(); + + reader.putComponent(root); + reader.putComponent(module); + reader.putComponent(directory); + } + + private void createIssues() { + reader.putIssues(2, Collections.singletonList(ScannerReport.Issue.newBuilder().setRuleKey("module_issue").build())); + reader.putIssues(3, Collections.singletonList(ScannerReport.Issue.newBuilder().setRuleKey("directory_issue").build())); + } + + @Test + public void move_module_and_directory_issues() { + createComponents(); + createIssues(); + + underTest.relocate(root, module); + underTest.relocate(root, directory); + + assertThat(underTest.getMovedIssues()).hasSize(2); + assertThat(underTest.getMovedIssues()).extracting(ScannerReport.Issue::getRuleKey).containsOnly("module_issue", "directory_issue"); + } + + @Test + public void do_nothing_if_no_issues() { + createComponents(); + underTest.relocate(root, module); + underTest.relocate(root, directory); + + assertThat(underTest.getMovedIssues()).hasSize(0); + } + +} diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/BuildComponentTreeStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/BuildComponentTreeStepTest.java index 46afb3798517..ba95929db6a8 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/BuildComponentTreeStepTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/BuildComponentTreeStepTest.java @@ -165,25 +165,49 @@ public void compute_keys_and_uuids() { @Test public void return_existing_uuids() { + setAnalysisMetadataHolder(); + OrganizationDto organizationDto = dbTester.organizations().insert(); + ComponentDto project = insertComponent(newPrivateProjectDto(organizationDto, "ABCD").setDbKey(REPORT_PROJECT_KEY)); + ComponentDto directory = newDirectory(project, "CDEF", REPORT_DIR_PATH_1); + insertComponent(directory.setDbKey(REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_1)); + insertComponent(newFileDto(project, directory, "DEFG") + .setDbKey(REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1) + .setPath(REPORT_FILE_PATH_1)); + + // new structure, without modules + reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, DIR_REF_1)); + reportReader.putComponent(componentWithPath(DIR_REF_1, DIRECTORY, REPORT_DIR_PATH_1, FILE_1_REF)); + reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1)); + + underTest.execute(new TestComputationStepContext()); + + verifyComponentByRef(ROOT_REF, REPORT_PROJECT_KEY, "ABCD"); + verifyComponentByKey(REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_1, REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_1, "CDEF"); + verifyComponentByRef(FILE_1_REF, REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1, "DEFG"); + } + + @Test + public void return_existing_uuids_with_modules() { setAnalysisMetadataHolder(); OrganizationDto organizationDto = dbTester.organizations().insert(); ComponentDto project = insertComponent(newPrivateProjectDto(organizationDto, "ABCD").setDbKey(REPORT_PROJECT_KEY)); ComponentDto module = insertComponent(newModuleDto("BCDE", project).setDbKey(REPORT_MODULE_KEY)); ComponentDto directory = newDirectory(module, "CDEF", REPORT_DIR_PATH_1); insertComponent(directory.setDbKey(REPORT_MODULE_KEY + ":" + REPORT_DIR_PATH_1)); - insertComponent(newFileDto(module, directory, "DEFG").setDbKey(REPORT_MODULE_KEY + ":" + REPORT_FILE_PATH_1)); + insertComponent(newFileDto(module, directory, "DEFG") + .setDbKey(REPORT_MODULE_KEY + ":" + REPORT_FILE_PATH_1) + .setPath(REPORT_FILE_PATH_1)); - reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, MODULE_REF)); - reportReader.putComponent(component(MODULE_REF, MODULE, REPORT_MODULE_KEY, DIR_REF_1)); - reportReader.putComponent(componentWithPath(DIR_REF_1, DIRECTORY, REPORT_DIR_PATH_1, FILE_1_REF)); - reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1)); + // new structure, without modules + reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, DIR_REF_1)); + reportReader.putComponent(componentWithPath(DIR_REF_1, DIRECTORY, "module/" + REPORT_DIR_PATH_1, FILE_1_REF)); + reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, "module/" + REPORT_FILE_PATH_1)); underTest.execute(new TestComputationStepContext()); verifyComponentByRef(ROOT_REF, REPORT_PROJECT_KEY, "ABCD"); - // TODO migrate modules - // verifyComponentByRef(DIR_REF_1, REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_1, "CDEF"); - // verifyComponentByRef(FILE_1_REF, REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1, "DEFG"); + verifyComponentByKey(REPORT_PROJECT_KEY + ":module/" + REPORT_DIR_PATH_1, REPORT_PROJECT_KEY + ":module/" + REPORT_DIR_PATH_1, "CDEF"); + verifyComponentByRef(FILE_1_REF, REPORT_PROJECT_KEY + ":module/" + REPORT_FILE_PATH_1, "DEFG"); } @Test diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/ValidateProjectStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/ValidateProjectStepTest.java index 6e472812af71..5f26eadf1e25 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/ValidateProjectStepTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/ValidateProjectStepTest.java @@ -28,7 +28,6 @@ import org.sonar.api.utils.System2; import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule; import org.sonar.ce.task.projectanalysis.analysis.Branch; -import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule; import org.sonar.ce.task.projectanalysis.component.Component; import org.sonar.ce.task.projectanalysis.component.DefaultBranchImpl; import org.sonar.ce.task.projectanalysis.component.ReportComponent; @@ -39,8 +38,6 @@ import org.sonar.db.component.ComponentDto; import org.sonar.db.component.ComponentTesting; import org.sonar.db.component.SnapshotTesting; -import org.sonar.scanner.protocol.output.ScannerReport; -import org.sonar.scanner.protocol.output.ScannerReport.Component.ComponentType; public class ValidateProjectStepTest { @@ -54,9 +51,6 @@ public class ValidateProjectStepTest { @Rule public ExpectedException thrown = ExpectedException.none(); - @Rule - public BatchReportReaderRule reportReader = new BatchReportReaderRule(); - @Rule public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule(); @@ -67,17 +61,10 @@ public class ValidateProjectStepTest { DbClient dbClient = dbTester.getDbClient(); - ValidateProjectStep underTest = new ValidateProjectStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder); + ValidateProjectStep underTest = new ValidateProjectStep(dbClient, treeRootHolder, analysisMetadataHolder); @Test public void not_fail_if_analysis_date_is_after_last_analysis() { - reportReader.putComponent(ScannerReport.Component.newBuilder() - .setRef(1) - .setType(ComponentType.PROJECT) - .setKey(PROJECT_KEY) - .addChildRef(2) - .build()); - ComponentDto project = ComponentTesting.newPrivateProjectDto(dbTester.organizations().insert(), "ABCD").setDbKey(PROJECT_KEY); dbClient.componentDao().insert(dbTester.getSession(), project); dbClient.snapshotDao().insert(dbTester.getSession(), SnapshotTesting.newAnalysis(project).setCreatedAt(1420088400000L)); // 2015-01-01 @@ -92,13 +79,6 @@ public void not_fail_if_analysis_date_is_after_last_analysis() { public void fail_if_analysis_date_is_before_last_analysis() { analysisMetadataHolder.setAnalysisDate(DateUtils.parseDate("2015-01-01")); - reportReader.putComponent(ScannerReport.Component.newBuilder() - .setRef(1) - .setType(ComponentType.PROJECT) - .setKey(PROJECT_KEY) - .addChildRef(2) - .build()); - ComponentDto project = ComponentTesting.newPrivateProjectDto(dbTester.organizations().insert(), "ABCD").setDbKey(PROJECT_KEY); dbClient.componentDao().insert(dbTester.getSession(), project); dbClient.snapshotDao().insert(dbTester.getSession(), SnapshotTesting.newAnalysis(project).setCreatedAt(1433131200000L)); // 2015-06-01 diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java index 18c7509ffeb1..5ce01ea804a4 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java @@ -48,6 +48,7 @@ import org.sonar.db.component.AnalysisPropertiesMapper; import org.sonar.db.component.BranchMapper; import org.sonar.db.component.ComponentDto; +import org.sonar.db.component.ComponentWithModuleUuidDto; import org.sonar.db.component.ComponentDtoWithSnapshotId; import org.sonar.db.component.ComponentKeyUpdaterMapper; import org.sonar.db.component.ComponentMapper; @@ -167,6 +168,7 @@ public void start() { confBuilder.loadAlias("ActiveRuleParam", ActiveRuleParamDto.class); confBuilder.loadAlias("CeTaskCharacteristic", CeTaskCharacteristicDto.class); confBuilder.loadAlias("Component", ComponentDto.class); + confBuilder.loadAlias("ComponentWithModuleUuid", ComponentWithModuleUuidDto.class); confBuilder.loadAlias("ComponentWithSnapshot", ComponentDtoWithSnapshotId.class); confBuilder.loadAlias("CustomMeasure", CustomMeasureDto.class); confBuilder.loadAlias("DuplicationUnit", DuplicationUnitDto.class); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java index f6f79f3de773..c0449b593e66 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java @@ -183,8 +183,12 @@ public List selectUuidsByKeyFromProjectKey(DbSession session, St return mapper(session).selectUuidsByKeyFromProjectKey(projectKey); } + public List selectEnabledModulesFromProjectKey(DbSession session, String projectKey, boolean excludeDisabled) { + return mapper(session).selectComponentsFromProjectKeyAndScope(projectKey, Scopes.PROJECT, excludeDisabled); + } + public List selectEnabledModulesFromProjectKey(DbSession session, String projectKey) { - return mapper(session).selectComponentsFromProjectKeyAndScope(projectKey, Scopes.PROJECT, true); + return selectEnabledModulesFromProjectKey(session, projectKey, true); } public List selectByKeys(DbSession session, Collection keys) { @@ -347,6 +351,10 @@ public Set selectComponentsByQualifiers(DbSession dbSession, Set(mapper(dbSession).selectComponentsByQualifiers(qualifiers)); } + public List selectComponentsWithModuleUuidFromProjectKey(DbSession dbSession, String projectKey) { + return mapper(dbSession).selectComponentsWithModuleUuidFromProjectKey(projectKey); + } + public List selectProjectsByNameQuery(DbSession dbSession, @Nullable String nameQuery, boolean includeModules) { String nameQueryForSql = nameQuery == null ? null : buildLikeValue(nameQuery, BEFORE_AND_AFTER).toUpperCase(Locale.ENGLISH); return mapper(dbSession).selectProjectsByNameQuery(nameQueryForSql, includeModules); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java index 687ee46addeb..f096156ed3c4 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java @@ -164,4 +164,6 @@ List selectComponentsFromProjectKeyAndScope(@Param("projectKey") S List selectComponentKeysHavingIssuesToMerge(@Param("mergeBranchUuid") String mergeBranchUuid); List selectPrivateProjectsWithNcloc(@Param("organizationUuid") String organizationUuid); + + List selectComponentsWithModuleUuidFromProjectKey(String projectKey); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentWithModuleUuidDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentWithModuleUuidDto.java new file mode 100644 index 000000000000..3c2045f4873f --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentWithModuleUuidDto.java @@ -0,0 +1,47 @@ +/* + * 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.db.component; + +import javax.annotation.concurrent.Immutable; + +@Immutable +public class ComponentWithModuleUuidDto { + private final String uuid; + private final String moduleUuid; + private final String path; + + public ComponentWithModuleUuidDto(String uuid, String moduleUuid, String path) { + this.uuid = uuid; + this.moduleUuid = moduleUuid; + this.path = path; + } + + public String path() { + return path; + } + + public String moduleUuid() { + return moduleUuid; + } + + public String uuid() { + return uuid; + } +} diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml index 1f79ca8d1a30..77fd01b84ba2 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml @@ -509,6 +509,15 @@ + +