/* * SonarQube * Copyright (C) 2009-2025 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.mediumtest.coverage; import com.google.common.collect.ImmutableMap; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import org.apache.commons.io.FileUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.io.TempDir; import org.slf4j.event.Level; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.testfixtures.log.LogTesterJUnit5; import org.sonar.scanner.mediumtest.AnalysisResult; import org.sonar.scanner.mediumtest.ScannerMediumTester; import org.sonar.scanner.protocol.output.ScannerReport; import org.sonar.xoo.XooPlugin; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.tuple; class CoverageMediumIT { @RegisterExtension private final LogTesterJUnit5 logTester = new LogTesterJUnit5(); @TempDir private File temp; @RegisterExtension private final ScannerMediumTester tester = new ScannerMediumTester() .registerPlugin("xoo", new XooPlugin()) .addDefaultQProfile("xoo", "Sonar Way"); @BeforeEach void prepare() throws IOException { logTester.setLevel(Level.DEBUG); } @Test void singleReport() throws IOException { File baseDir = temp; File srcDir = new File(baseDir, "src"); srcDir.mkdir(); File xooFile = new File(srcDir, "sample.xoo"); File xooUtCoverageFile = new File(srcDir, "sample.xoo.coverage"); FileUtils.write(xooFile, "function foo() {\n if (a && b) {\nalert('hello');\n}\n}", StandardCharsets.UTF_8); FileUtils.write(xooUtCoverageFile, "2:2:2:1\n3:1", StandardCharsets.UTF_8); AnalysisResult result = tester.newAnalysis() .properties(ImmutableMap.builder() .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) .put("sonar.projectKey", "com.foo.project") .put("sonar.sources", "src") .build()) .execute(); InputFile file = result.inputFile("src/sample.xoo"); assertThat(result.coverageFor(file, 2).getHits()).isTrue(); assertThat(result.coverageFor(file, 2).getConditions()).isEqualTo(2); assertThat(result.coverageFor(file, 2).getCoveredConditions()).isOne(); } @Test void twoReports() throws IOException { File baseDir = temp; File srcDir = new File(baseDir, "src"); srcDir.mkdir(); File xooFile = new File(srcDir, "sample.xoo"); FileUtils.write(xooFile, "function foo() {\n if (a && b) {\nalert('hello');\n}\n}", StandardCharsets.UTF_8); File xooUtCoverageFile = new File(srcDir, "sample.xoo.coverage"); FileUtils.write(xooUtCoverageFile, "2:2:2:2\n4:0", StandardCharsets.UTF_8); File xooItCoverageFile = new File(srcDir, "sample.xoo.itcoverage"); FileUtils.write(xooItCoverageFile, "2:0:2:1\n3:1\n5:0", StandardCharsets.UTF_8); AnalysisResult result = tester.newAnalysis() .properties(ImmutableMap.builder() .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) .put("sonar.projectKey", "com.foo.project") .put("sonar.sources", "src") .build()) .execute(); InputFile file = result.inputFile("src/sample.xoo"); assertThat(result.coverageFor(file, 2).getHits()).isTrue(); assertThat(result.coverageFor(file, 2).getConditions()).isEqualTo(2); assertThat(result.coverageFor(file, 2).getCoveredConditions()).isEqualTo(2); assertThat(result.coverageFor(file, 3).getHits()).isTrue(); } @Test void exclusionsForSimpleProject() throws IOException { File baseDir = temp; File srcDir = new File(baseDir, "src"); srcDir.mkdir(); File xooFile = new File(srcDir, "sample.xoo"); File xooUtCoverageFile = new File(srcDir, "sample.xoo.coverage"); FileUtils.write(xooFile, "function foo() {\n if (a && b) {\nalert('hello');\n}\n}", StandardCharsets.UTF_8); FileUtils.write(xooUtCoverageFile, "2:2:2:1\n3:1", StandardCharsets.UTF_8); AnalysisResult result = tester.newAnalysis() .properties(ImmutableMap.builder() .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) .put("sonar.projectKey", "com.foo.project") .put("sonar.sources", "src") .put("sonar.coverage.exclusions", "**/sample.xoo") .build()) .execute(); InputFile file = result.inputFile("src/sample.xoo"); assertThat(result.coverageFor(file, 2)).isNull(); } @Test void warn_user_for_outdated_inherited_scanner_side_exclusions_for_multi_module_project() throws IOException { File baseDir = temp; File baseDirModuleA = new File(baseDir, "moduleA"); File baseDirModuleB = new File(baseDir, "moduleB"); File srcDirA = new File(baseDirModuleA, "src"); srcDirA.mkdirs(); File srcDirB = new File(baseDirModuleB, "src"); srcDirB.mkdirs(); File xooFileA = new File(srcDirA, "sampleA.xoo"); File xooUtCoverageFileA = new File(srcDirA, "sampleA.xoo.coverage"); FileUtils.write(xooFileA, "function foo() {\n if (a && b) {\nalert('hello');\n}\n}", StandardCharsets.UTF_8); FileUtils.write(xooUtCoverageFileA, "2:2:2:1\n3:1", StandardCharsets.UTF_8); File xooFileB = new File(srcDirB, "sampleB.xoo"); File xooUtCoverageFileB = new File(srcDirB, "sampleB.xoo.coverage"); FileUtils.write(xooFileB, "function foo() {\n if (a && b) {\nalert('hello');\n}\n}", StandardCharsets.UTF_8); FileUtils.write(xooUtCoverageFileB, "2:2:2:1\n3:1", StandardCharsets.UTF_8); AnalysisResult result = tester.newAnalysis() .properties(ImmutableMap.builder() .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) .put("sonar.projectKey", "com.foo.project") .put("sonar.sources", "src") .put("sonar.modules", "moduleA,moduleB") .put("sonar.coverage.exclusions", "src/sampleA.xoo") .build()) .execute(); InputFile fileA = result.inputFile("moduleA/src/sampleA.xoo"); assertThat(result.coverageFor(fileA, 2)).isNull(); InputFile fileB = result.inputFile("moduleB/src/sampleB.xoo"); assertThat(result.coverageFor(fileB, 2)).isNotNull(); assertThat(logTester.logs(Level.WARN)).contains("Specifying module-relative paths at project level in the property 'sonar.coverage.exclusions' is deprecated. " + "To continue matching files like 'moduleA/src/sampleA.xoo', update this property so that patterns refer to project-relative paths."); } @Test void module_level_exclusions_override_parent_for_multi_module_project() throws IOException { File baseDir = temp; File baseDirModuleA = new File(baseDir, "moduleA"); File baseDirModuleB = new File(baseDir, "moduleB"); File srcDirA = new File(baseDirModuleA, "src"); srcDirA.mkdirs(); File srcDirB = new File(baseDirModuleB, "src"); srcDirB.mkdirs(); File xooFileA = new File(srcDirA, "sampleA.xoo"); File xooUtCoverageFileA = new File(srcDirA, "sampleA.xoo.coverage"); FileUtils.write(xooFileA, "function foo() {\n if (a && b) {\nalert('hello');\n}\n}", StandardCharsets.UTF_8); FileUtils.write(xooUtCoverageFileA, "2:2:2:1\n3:1", StandardCharsets.UTF_8); File xooFileB = new File(srcDirB, "sampleB.xoo"); File xooUtCoverageFileB = new File(srcDirB, "sampleB.xoo.coverage"); FileUtils.write(xooFileB, "function foo() {\n if (a && b) {\nalert('hello');\n}\n}", StandardCharsets.UTF_8); FileUtils.write(xooUtCoverageFileB, "2:2:2:1\n3:1", StandardCharsets.UTF_8); AnalysisResult result = tester.newAnalysis() .properties(ImmutableMap.builder() .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) .put("sonar.projectKey", "com.foo.project") .put("sonar.sources", "src") .put("sonar.modules", "moduleA,moduleB") .put("sonar.coverage.exclusions", "**/*.xoo") .put("moduleA.sonar.coverage.exclusions", "**/*.nothing") .build()) .execute(); InputFile fileA = result.inputFile("moduleA/src/sampleA.xoo"); assertThat(result.coverageFor(fileA, 2)).isNotNull(); InputFile fileB = result.inputFile("moduleB/src/sampleB.xoo"); assertThat(result.coverageFor(fileB, 2)).isNull(); } @Test void warn_user_for_outdated_server_side_exclusions_for_multi_module_project() throws IOException { File baseDir = temp; File baseDirModuleA = new File(baseDir, "moduleA"); File baseDirModuleB = new File(baseDir, "moduleB"); File srcDirA = new File(baseDirModuleA, "src"); srcDirA.mkdirs(); File srcDirB = new File(baseDirModuleB, "src"); srcDirB.mkdirs(); File xooFileA = new File(srcDirA, "sample.xoo"); File xooUtCoverageFileA = new File(srcDirA, "sample.xoo.coverage"); FileUtils.write(xooFileA, "function foo() {\n if (a && b) {\nalert('hello');\n}\n}", StandardCharsets.UTF_8); FileUtils.write(xooUtCoverageFileA, "2:2:2:1\n3:1", StandardCharsets.UTF_8); File xooFileB = new File(srcDirB, "sample.xoo"); File xooUtCoverageFileB = new File(srcDirB, "sample.xoo.coverage"); FileUtils.write(xooFileB, "function foo() {\n if (a && b) {\nalert('hello');\n}\n}", StandardCharsets.UTF_8); FileUtils.write(xooUtCoverageFileB, "2:2:2:1\n3:1", StandardCharsets.UTF_8); tester.addProjectServerSettings("sonar.coverage.exclusions", "src/sample.xoo"); AnalysisResult result = tester.newAnalysis() .properties(ImmutableMap.builder() .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) .put("sonar.projectKey", "com.foo.project") .put("sonar.sources", "src") .put("sonar.modules", "moduleA,moduleB") .build()) .execute(); InputFile fileA = result.inputFile("moduleA/src/sample.xoo"); assertThat(result.coverageFor(fileA, 2)).isNull(); InputFile fileB = result.inputFile("moduleB/src/sample.xoo"); assertThat(result.coverageFor(fileB, 2)).isNull(); assertThat(logTester.logs(Level.WARN)).contains("Specifying module-relative paths at project level in the property 'sonar.coverage.exclusions' is deprecated. " + "To continue matching files like 'moduleA/src/sample.xoo', update this property so that patterns refer to project-relative paths."); } @Test void fallbackOnExecutableLines() throws IOException { File baseDir = temp; File srcDir = new File(baseDir, "src"); srcDir.mkdir(); File xooFile = new File(srcDir, "sample.xoo"); File measuresFile = new File(srcDir, "sample.xoo.measures"); FileUtils.write(xooFile, "function foo() {\n if (a && b) {\nalert('hello');\n}\n}", StandardCharsets.UTF_8); FileUtils.write(measuresFile, "executable_lines_data:2=1;3=1;4=0", StandardCharsets.UTF_8); AnalysisResult result = tester.newAnalysis() .properties(ImmutableMap.builder() .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) .put("sonar.projectKey", "com.foo.project") .put("sonar.sources", "src") .build()) .execute(); InputFile file = result.inputFile("src/sample.xoo"); assertThat(result.coverageFor(file, 1)).isNull(); assertThat(result.coverageFor(file, 2).getHits()).isFalse(); assertThat(result.coverageFor(file, 2).getConditions()).isZero(); assertThat(result.coverageFor(file, 2).getCoveredConditions()).isZero(); assertThat(result.coverageFor(file, 3).getHits()).isFalse(); assertThat(result.coverageFor(file, 4)).isNull(); assertThat(result.allMeasures().get(file.key())) .extracting(ScannerReport.Measure::getMetricKey, m -> m.getStringValue().getValue()) .contains(tuple("executable_lines_data", "2=1;3=1;4=0")); } // SONAR-11641 @Test void dontFallbackOnExecutableLinesIfNoCoverageSaved() throws IOException { File baseDir = temp; File srcDir = new File(baseDir, "src"); srcDir.mkdir(); File xooFile = new File(srcDir, "sample.xoo"); File measuresFile = new File(srcDir, "sample.xoo.measures"); File coverageFile = new File(srcDir, "sample.xoo.coverage"); FileUtils.write(xooFile, "function foo() {\n if (a && b) {\nalert('hello');\n}\n}", StandardCharsets.UTF_8); FileUtils.write(measuresFile, "# The code analyzer disagree with the coverage tool and consider some lines to be executable\nexecutable_lines_data:2=1;3=1;4=0", StandardCharsets.UTF_8); FileUtils.write(coverageFile, "# No lines to cover in this file according to the coverage tool", StandardCharsets.UTF_8); AnalysisResult result = tester.newAnalysis() .properties(ImmutableMap.builder() .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) .put("sonar.projectKey", "com.foo.project") .put("sonar.sources", "src") .build()) .execute(); InputFile file = result.inputFile("src/sample.xoo"); assertThat(result.coverageFor(file, 1)).isNull(); assertThat(result.coverageFor(file, 2)).isNull(); assertThat(result.coverageFor(file, 3)).isNull(); assertThat(result.coverageFor(file, 4)).isNull(); } // SONAR-9557 @Test void exclusionsAndForceToZeroOnModules() throws IOException { File baseDir = temp; File srcDir = new File(baseDir, "module1/src"); srcDir.mkdir(); File xooFile1 = new File(srcDir, "sample1.xoo"); File measuresFile1 = new File(srcDir, "sample1.xoo.measures"); FileUtils.write(xooFile1, "function foo() {\n if (a && b) {\nalert('hello');\n}\n}", StandardCharsets.UTF_8); FileUtils.write(measuresFile1, "executable_lines_data:2=1;3=1;4=0", StandardCharsets.UTF_8); File xooFile2 = new File(srcDir, "sample2.xoo"); File measuresFile2 = new File(srcDir, "sample2.xoo.measures"); FileUtils.write(xooFile2, "function foo() {\n if (a && b) {\nalert('hello');\n}\n}", StandardCharsets.UTF_8); FileUtils.write(measuresFile2, "executable_lines_data:2=1;3=1;4=0", StandardCharsets.UTF_8); AnalysisResult result = tester.newAnalysis() .properties(ImmutableMap.builder() .put("sonar.projectBaseDir", baseDir.getAbsolutePath()) .put("sonar.projectKey", "com.foo.project") .put("sonar.modules", "module1") .put("sonar.sources", "src") .put("sonar.coverage.exclusions", "**/sample2.xoo") .build()) .execute(); InputFile file1 = result.inputFile("module1/src/sample1.xoo"); assertThat(result.coverageFor(file1, 1)).isNull(); assertThat(result.coverageFor(file1, 2).getHits()).isFalse(); assertThat(result.coverageFor(file1, 2).getConditions()).isZero(); assertThat(result.coverageFor(file1, 2).getCoveredConditions()).isZero(); assertThat(result.coverageFor(file1, 3).getHits()).isFalse(); assertThat(result.coverageFor(file1, 4)).isNull(); InputFile file2 = result.inputFile("module1/src/sample2.xoo"); assertThat(result.coverageFor(file2, 1)).isNull(); assertThat(result.coverageFor(file2, 2)).isNull(); assertThat(result.coverageFor(file2, 3)).isNull(); assertThat(result.coverageFor(file2, 4)).isNull(); } }