From 1c53c91568bc9af10ca78ae5aff7a1e79f255074 Mon Sep 17 00:00:00 2001 From: Michal Duda Date: Fri, 11 Sep 2020 15:48:56 +0200 Subject: [PATCH] SONAR-13867 Raise analysis warning in CE when scanner report has unanalysed C/C++ files --- .../PerformNotAnalyzedFilesCheckStep.java | 111 ++++++++++++ .../step/ReportComputationSteps.java | 1 + .../PerformNotAnalyzedFilesCheckStepTest.java | 160 ++++++++++++++++++ .../step/PersistAnalysisWarningsStepTest.java | 4 +- 4 files changed, 274 insertions(+), 2 deletions(-) create mode 100644 server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PerformNotAnalyzedFilesCheckStep.java create mode 100644 server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PerformNotAnalyzedFilesCheckStepTest.java diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PerformNotAnalyzedFilesCheckStep.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PerformNotAnalyzedFilesCheckStep.java new file mode 100644 index 00000000000..217aeafe712 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PerformNotAnalyzedFilesCheckStep.java @@ -0,0 +1,111 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.step; + +import java.util.Iterator; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.stream.Collectors; +import org.sonar.api.utils.System2; +import org.sonar.ce.task.log.CeTaskMessages; +import org.sonar.ce.task.projectanalysis.batch.BatchReportReader; +import org.sonar.ce.task.step.ComputationStep; +import org.sonar.core.platform.EditionProvider; +import org.sonar.core.platform.PlatformEditionProvider; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static java.lang.String.format; + +/** + * Check if there are files that could be analyzed with a higher SQ edition. + */ +public class PerformNotAnalyzedFilesCheckStep implements ComputationStep { + static final String DESCRIPTION = "Check upgrade possibility for not analyzed code files."; + + private static final String LANGUAGE_UPGRADE_MESSAGE = "%s file(s) detected during the last analysis. %s code cannot be analyzed with SonarQube " + + "community edition. Please consider upgrading to " + + "the Developer Edition to analyze this language."; + + private final BatchReportReader reportReader; + private final CeTaskMessages ceTaskMessages; + private final PlatformEditionProvider editionProvider; + private final System2 system; + + public PerformNotAnalyzedFilesCheckStep(BatchReportReader reportReader, CeTaskMessages ceTaskMessages, PlatformEditionProvider editionProvider, + System2 system) { + this.reportReader = reportReader; + this.ceTaskMessages = ceTaskMessages; + this.editionProvider = editionProvider; + this.system = system; + } + + @Override + public void execute(Context context) { + editionProvider.get().ifPresent(edition -> { + if (!edition.equals(EditionProvider.Edition.COMMUNITY)) { + return; + } + + Map filesPerLanguage = reportReader.readMetadata().getNotAnalyzedFilesByLanguageMap() + .entrySet() + .stream() + .filter(entry -> entry.getValue() > 0) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + if (filesPerLanguage.isEmpty()) { + return; + } + + ceTaskMessages.add(constructMessage(filesPerLanguage)); + }); + } + + private CeTaskMessages.Message constructMessage(Map filesPerLanguage) { + checkNotNull(filesPerLanguage); + checkArgument(filesPerLanguage.size() > 0); + + SortedMap sortedLanguageMap = new TreeMap<>(filesPerLanguage); + Iterator> iterator = sortedLanguageMap.entrySet().iterator(); + Map.Entry firstLanguage = iterator.next(); + StringBuilder languageLabel = new StringBuilder(firstLanguage.getKey()); + StringBuilder fileCountLabel = new StringBuilder(format("%s %s", firstLanguage.getValue(), firstLanguage.getKey())); + while (iterator.hasNext()) { + Map.Entry nextLanguage = iterator.next(); + if (iterator.hasNext()) { + languageLabel.append(", "); + fileCountLabel.append(", "); + } else { + languageLabel.append(" and "); + fileCountLabel.append(" and "); + } + languageLabel.append(nextLanguage.getKey()); + fileCountLabel.append(format("%s %s", nextLanguage.getValue(), nextLanguage.getKey())); + } + + return new CeTaskMessages.Message(format(LANGUAGE_UPGRADE_MESSAGE, fileCountLabel, languageLabel), system.now()); + } + + @Override + public String getDescription() { + return DESCRIPTION; + } +} diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/ReportComputationSteps.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/ReportComputationSteps.java index 9eda77e3c9a..cd708269303 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/ReportComputationSteps.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/ReportComputationSteps.java @@ -38,6 +38,7 @@ public class ReportComputationSteps extends AbstractComputationSteps { private static final List> STEPS = Arrays.asList( ExtractReportStep.class, PersistScannerContextStep.class, + PerformNotAnalyzedFilesCheckStep.class, PersistAnalysisWarningsStep.class, GenerateAnalysisUuid.class, diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PerformNotAnalyzedFilesCheckStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PerformNotAnalyzedFilesCheckStepTest.java new file mode 100644 index 00000000000..130a7a519ed --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PerformNotAnalyzedFilesCheckStepTest.java @@ -0,0 +1,160 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.step; + +import com.google.common.collect.ImmutableList; +import java.util.List; +import java.util.Optional; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.sonar.api.utils.System2; +import org.sonar.ce.task.log.CeTaskMessages; +import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule; +import org.sonar.ce.task.step.TestComputationStepContext; +import org.sonar.core.platform.EditionProvider; +import org.sonar.core.platform.PlatformEditionProvider; +import org.sonar.scanner.protocol.output.ScannerReport; + +import static com.google.common.collect.ImmutableList.of; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class PerformNotAnalyzedFilesCheckStepTest { + + @Rule + public BatchReportReaderRule reportReader = new BatchReportReaderRule(); + + private final PlatformEditionProvider editionProvider = mock(PlatformEditionProvider.class); + private final CeTaskMessages ceTaskMessages = mock(CeTaskMessages.class); + private final PerformNotAnalyzedFilesCheckStep underTest = new PerformNotAnalyzedFilesCheckStep(reportReader, ceTaskMessages, editionProvider, System2.INSTANCE); + + @Test + public void getDescription() { + assertThat(underTest.getDescription()).isEqualTo(PerformNotAnalyzedFilesCheckStep.DESCRIPTION); + } + + @Test + public void execute_adds_warning_in_SQ_community_edition_if_there_are_c_or_cpp_files() { + when(editionProvider.get()).thenReturn(Optional.of(EditionProvider.Edition.COMMUNITY)); + ScannerReport.AnalysisWarning warning1 = ScannerReport.AnalysisWarning.newBuilder().setText("warning 1").build(); + ScannerReport.AnalysisWarning warning2 = ScannerReport.AnalysisWarning.newBuilder().setText("warning 2").build(); + ImmutableList warnings = of(warning1, warning2); + reportReader.setAnalysisWarnings(warnings); + reportReader.setMetadata(ScannerReport.Metadata.newBuilder() + .putNotAnalyzedFilesByLanguage("C++", 20) + .putNotAnalyzedFilesByLanguage("C", 10) + .putNotAnalyzedFilesByLanguage("SomeLang", 1000) + .build()); + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(CeTaskMessages.Message.class); + + underTest.execute(new TestComputationStepContext()); + + verify(ceTaskMessages, times(1)).add(argumentCaptor.capture()); + List messages = argumentCaptor.getAllValues(); + assertThat(messages).extracting(CeTaskMessages.Message::getText).containsExactly( + "10 C, 20 C++ and 1000 SomeLang file(s) detected during the last analysis. C, C++ and SomeLang code cannot be analyzed with SonarQube community " + + "edition. Please consider upgrading to the Developer " + + "Edition to analyze this language."); + } + + @Test + public void execute_adds_warning_in_SQ_community_edition_if_there_are_c_files() { + when(editionProvider.get()).thenReturn(Optional.of(EditionProvider.Edition.COMMUNITY)); + reportReader.setMetadata(ScannerReport.Metadata.newBuilder() + .putNotAnalyzedFilesByLanguage("C", 10) + .build()); + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(CeTaskMessages.Message.class); + + underTest.execute(new TestComputationStepContext()); + + verify(ceTaskMessages, times(1)).add(argumentCaptor.capture()); + List messages = argumentCaptor.getAllValues(); + assertThat(messages).extracting(CeTaskMessages.Message::getText).containsExactly( + "10 C file(s) detected during the last analysis. C code cannot be analyzed with SonarQube community " + + "edition. Please consider upgrading to the Developer " + + "Edition to analyze this language."); + } + + @Test + public void execute_adds_warning_in_SQ_community_edition_if_there_are_cpp_files() { + when(editionProvider.get()).thenReturn(Optional.of(EditionProvider.Edition.COMMUNITY)); + reportReader.setMetadata(ScannerReport.Metadata.newBuilder() + .putNotAnalyzedFilesByLanguage("C++", 9) + .build()); + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(CeTaskMessages.Message.class); + + underTest.execute(new TestComputationStepContext()); + + verify(ceTaskMessages, times(1)).add(argumentCaptor.capture()); + List messages = argumentCaptor.getAllValues(); + assertThat(messages).extracting(CeTaskMessages.Message::getText).containsExactly( + "9 C++ file(s) detected during the last analysis. C++ code cannot be analyzed with SonarQube community " + + "edition. Please consider upgrading to the Developer " + + "Edition to analyze this language."); + } + + @Test + public void execute_does_not_add_a_warning_in_SQ_community_edition_if_cpp_files_in_report_is_zero() { + when(editionProvider.get()).thenReturn(Optional.of(EditionProvider.Edition.COMMUNITY)); + ScannerReport.AnalysisWarning warning1 = ScannerReport.AnalysisWarning.newBuilder().setText("warning 1").build(); + ScannerReport.AnalysisWarning warning2 = ScannerReport.AnalysisWarning.newBuilder().setText("warning 2").build(); + ImmutableList warnings = of(warning1, warning2); + reportReader.setAnalysisWarnings(warnings); + reportReader.setMetadata(ScannerReport.Metadata.newBuilder().putNotAnalyzedFilesByLanguage("C++", 0).build()); + + underTest.execute(new TestComputationStepContext()); + + verify(ceTaskMessages, never()).add(any()); + } + + @Test + public void execute_does_not_add_a_warning_in_SQ_community_edition_if_no_c_or_cpp_files_2() { + when(editionProvider.get()).thenReturn(Optional.of(EditionProvider.Edition.COMMUNITY)); + ScannerReport.AnalysisWarning warning1 = ScannerReport.AnalysisWarning.newBuilder().setText("warning 1").build(); + ScannerReport.AnalysisWarning warning2 = ScannerReport.AnalysisWarning.newBuilder().setText("warning 2").build(); + ImmutableList warnings = of(warning1, warning2); + reportReader.setAnalysisWarnings(warnings); + reportReader.setMetadata(ScannerReport.Metadata.newBuilder().build()); + + underTest.execute(new TestComputationStepContext()); + + verify(ceTaskMessages, never()).add(any()); + } + + @Test + public void execute_does_not_add_a_warning_in_SQ_non_community_edition() { + when(editionProvider.get()).thenReturn(Optional.of(EditionProvider.Edition.ENTERPRISE)); + ScannerReport.AnalysisWarning warning1 = ScannerReport.AnalysisWarning.newBuilder().setText("warning 1").build(); + ScannerReport.AnalysisWarning warning2 = ScannerReport.AnalysisWarning.newBuilder().setText("warning 2").build(); + ImmutableList warnings = of(warning1, warning2); + reportReader.setAnalysisWarnings(warnings); + reportReader.setMetadata(ScannerReport.Metadata.newBuilder().putNotAnalyzedFilesByLanguage("C++", 20).build()); + + underTest.execute(new TestComputationStepContext()); + + verify(ceTaskMessages, never()).add(any()); + } +} diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistAnalysisWarningsStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistAnalysisWarningsStepTest.java index b49c37fe7d1..1185859da3f 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistAnalysisWarningsStepTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistAnalysisWarningsStepTest.java @@ -34,7 +34,7 @@ import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.verifyNoInteractions; public class PersistAnalysisWarningsStepTest { @@ -70,6 +70,6 @@ public class PersistAnalysisWarningsStepTest { underTest.execute(new TestComputationStepContext()); - verifyZeroInteractions(ceTaskMessages); + verifyNoInteractions(ceTaskMessages); } } -- 2.39.5