/* * SonarQube Java * Copyright (C) 2010-2016 SonarSource SA * mailto:contact 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.plugins.jacoco; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.io.Files; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.mockito.Mockito; import org.sonar.api.SonarQubeSide; import org.sonar.api.batch.fs.internal.DefaultFileSystem; import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.sensor.internal.SensorContextTester; import org.sonar.api.component.ResourcePerspectives; import org.sonar.api.config.MapSettings; import org.sonar.api.config.Settings; import org.sonar.api.internal.SonarRuntimeImpl; import org.sonar.api.scan.filesystem.PathResolver; import org.sonar.api.test.MutableTestCase; import org.sonar.api.test.MutableTestPlan; import org.sonar.api.test.MutableTestable; import org.sonar.api.utils.Version; import org.sonar.api.utils.log.LogTester; import org.sonar.api.utils.log.LoggerLevel; import org.sonar.java.JavaClasspath; import org.sonar.plugins.java.api.JavaResourceLocator; import org.sonar.test.TestUtils; import java.io.File; import java.io.IOException; import java.util.List; import java.util.Map; import static com.google.common.collect.Lists.newArrayList; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.sonar.plugins.jacoco.JacocoConfiguration.IT_REPORT_PATH_PROPERTY; import static org.sonar.plugins.jacoco.JacocoConfiguration.REPORT_PATHS_PROPERTY; import static org.sonar.plugins.jacoco.JacocoConfiguration.REPORT_PATH_PROPERTY; import static org.sonar.plugins.jacoco.JacocoConfiguration.SQ_6_2; public class JaCoCoSensorTest { private static final Version SQ_5_6 = Version.create(5, 6); private File jacocoExecutionData; private File outputDir; private JacocoConfiguration configuration; private ResourcePerspectives perspectives; private SensorContextTester context; private PathResolver pathResolver; private JaCoCoSensor sensor; private JavaResourceLocator javaResourceLocator = mock(JavaResourceLocator.class); private JavaClasspath javaClasspath; @Rule public TemporaryFolder temp = new TemporaryFolder(); @Rule public LogTester logTester = new LogTester(); @Before public void setUp() throws Exception { outputDir = TestUtils.getResource("/org/sonar/plugins/jacoco/JaCoCoSensorTest/"); jacocoExecutionData = new File(outputDir, "jacoco.exec"); Files.copy(TestUtils.getResource("Hello.class.toCopy"), new File(jacocoExecutionData.getParentFile(), "Hello.class")); context = SensorContextTester.create(outputDir); context.setRuntime(SonarRuntimeImpl.forSonarQube(SQ_5_6, SonarQubeSide.SCANNER)); context.fileSystem().setWorkDir(temp.newFolder()); pathResolver = mock(PathResolver.class); Settings settings = new MapSettings(); settings.setProperty(REPORT_PATH_PROPERTY, JacocoConfiguration.REPORT_PATH_DEFAULT_VALUE); context.settings().setProperty(REPORT_PATH_PROPERTY, JacocoConfiguration.REPORT_PATH_DEFAULT_VALUE); configuration = new JacocoConfiguration(settings); perspectives = mock(ResourcePerspectives.class); javaClasspath = mock(JavaClasspath.class); sensor = new JaCoCoSensor(configuration, perspectives, context.fileSystem(), pathResolver, javaResourceLocator, javaClasspath); } @Test public void testSensorDefinition() { assertThat(sensor.toString()).isEqualTo("JaCoCoSensor"); } @Test public void logIfInvalidReportPath() { context.setRuntime(SonarRuntimeImpl.forSonarQube(SQ_6_2, SonarQubeSide.SCANNER)); context.settings().setProperty(REPORT_PATH_PROPERTY, "unknown.exec"); context.settings().setProperty(IT_REPORT_PATH_PROPERTY, "unknownit.exec"); context.settings().setProperty(REPORT_PATHS_PROPERTY, "unknown1.exec,unknown2.exec"); sensor.execute(context); assertThat(logTester.logs(LoggerLevel.INFO)).contains( "JaCoCo UT report not found: 'unknown.exec'", "JaCoCo IT report not found: 'unknownit.exec'", "JaCoCo report not found: 'unknown1.exec'", "JaCoCo report not found: 'unknown2.exec'"); } @Test public void test_read_execution_data_before_6_2() throws Exception { context.setRuntime(SonarRuntimeImpl.forSonarQube(SQ_5_6, SonarQubeSide.SCANNER)); context.settings().setProperty(REPORT_PATH_PROPERTY, "jacoco.exec"); runAnalysis(); } @Test public void test_read_execution_data_after_6_2_using_deprecated_prop() throws Exception { context.setRuntime(SonarRuntimeImpl.forSonarQube(SQ_6_2, SonarQubeSide.SCANNER)); context.settings().setProperty(REPORT_PATH_PROPERTY, "jacoco.exec"); runAnalysis(); assertThat(logTester.logs(LoggerLevel.WARN)).contains("Property 'sonar.jacoco.reportPath' is deprecated. Please use 'sonar.jacoco.reportPaths' instead."); } @Test public void test_read_execution_data_after_6_2_using_deprecated_it_prop() throws Exception { context.setRuntime(SonarRuntimeImpl.forSonarQube(SQ_6_2, SonarQubeSide.SCANNER)); context.settings().setProperty(IT_REPORT_PATH_PROPERTY, "jacoco.exec"); runAnalysis(); assertThat(logTester.logs(LoggerLevel.WARN)).contains("Property 'sonar.jacoco.itReportPath' is deprecated. Please use 'sonar.jacoco.reportPaths' instead."); } @Test public void test_read_execution_data_after_6_2_using_correct_prop() throws Exception { context.setRuntime(SonarRuntimeImpl.forSonarQube(SQ_6_2, SonarQubeSide.SCANNER)); context.settings().setProperty(REPORT_PATHS_PROPERTY, "jacoco.exec"); runAnalysis(); } @Test public void test_read_execution_data_after_6_2_should_merge_reports() throws Exception { context.setRuntime(SonarRuntimeImpl.forSonarQube(SQ_6_2, SonarQubeSide.SCANNER)); String path1 = TestUtils.getResource("org/sonar/plugins/jacoco/JaCoCo_incompatible_merge/jacoco-0.7.5.exec").getPath(); String path2 = TestUtils.getResource("org/sonar/plugins/jacoco/JaCoCo_incompatible_merge/jacoco-it-0.7.5.exec").getPath(); context.settings().setProperty(REPORT_PATHS_PROPERTY, path1+","+path2); when(javaClasspath.getBinaryDirs()).thenReturn(ImmutableList.of(outputDir)); sensor.execute(context); assertThat(logTester.logs(LoggerLevel.INFO)).contains("Analysing "+path1); assertThat(logTester.logs(LoggerLevel.INFO)).contains("Analysing "+path2); assertThat(logTester.logs(LoggerLevel.INFO)).contains("Analysing "+new File(context.fileSystem().workDir(), "jacoco-merged.exec").getAbsolutePath()); } @Test public void should_execute_if_report_exists() { JacocoConfiguration configuration = mock(JacocoConfiguration.class); JaCoCoSensor sensor = new JaCoCoSensor(configuration, perspectives, context.fileSystem(), pathResolver, javaResourceLocator, javaClasspath); context.settings().setProperty(REPORT_PATH_PROPERTY, "ut.exec"); File outputDir = TestUtils.getResource(JaCoCoOverallSensorTest.class, "."); when(pathResolver.relativeFile(any(File.class), eq("ut.exec"))).thenReturn(new File(outputDir, "ut.exec")); when(configuration.shouldExecuteOnProject(true)).thenReturn(true); when(configuration.shouldExecuteOnProject(false)).thenReturn(false); sensor.execute(context); assertThat(logTester.logs(LoggerLevel.INFO)).contains("No JaCoCo analysis of project coverage can be done since there is no class files."); when(pathResolver.relativeFile(any(File.class), eq("ut.exec"))).thenReturn(new File(outputDir, "ut.not.found.exec")); sensor.execute(context); List<String> logs = logTester.logs(LoggerLevel.INFO); assertThat(logs).hasSize(2); assertThat(logs.get(1)).startsWith("JaCoCoSensor: JaCoCo report not found :"); } private void runAnalysis() throws IOException { DefaultInputFile resource = new DefaultInputFile("", "org/sonar/plugins/jacoco/tests/Hello"); resource.setLines(19); when(javaResourceLocator.findResourceByClassName("org/sonar/plugins/jacoco/tests/Hello")).thenReturn(resource); when(javaClasspath.getBinaryDirs()).thenReturn(ImmutableList.of(outputDir)); when(pathResolver.relativeFile(any(File.class), any(String.class))).thenReturn(jacocoExecutionData); sensor.execute(context); int[] oneHitlines = new int[] {6, 7, 8, 11}; int[] zeroHitlines = new int[] {15, 16, 18}; for (int zeroHitline : zeroHitlines) { assertThat(context.lineHits(resource.key(), zeroHitline)).isEqualTo(0); } for (int oneHitline : oneHitlines) { assertThat(context.lineHits(resource.key(), oneHitline)).isEqualTo(1); } assertThat(context.conditions(resource.key(), 15)).isEqualTo(2); assertThat(context.coveredConditions(resource.key(), 15)).isEqualTo(0); } @Test public void test_read_execution_data_for_lines_covered_by_tests() throws IOException { outputDir = TestUtils.getResource("/org/sonar/plugins/jacoco/JaCoCoSensorTest2/"); jacocoExecutionData = new File(outputDir, "jacoco.exec"); Files.copy(TestUtils.getResource("/org/sonar/plugins/jacoco/JaCoCoSensorTest2/org/example/App.class.toCopy"), new File(jacocoExecutionData.getParentFile(), "/org/example/App.class")); DefaultInputFile resource = new DefaultInputFile("", ""); resource.setLines(10); when(javaResourceLocator.findResourceByClassName(anyString())).thenReturn(resource); when(javaClasspath.getBinaryDirs()).thenReturn(ImmutableList.of(outputDir)); when(pathResolver.relativeFile(any(File.class), any(String.class))).thenReturn(jacocoExecutionData); MutableTestable testAbleFile = mock(MutableTestable.class); when(perspectives.as(eq(MutableTestable.class), eq(resource))).thenReturn(testAbleFile); MutableTestCase testCase = mock(MutableTestCase.class); when(testCase.name()).thenReturn("test"); MutableTestPlan testPlan = mock(MutableTestPlan.class); when(testPlan.testCasesByName("test")).thenReturn(newArrayList(testCase)); when(perspectives.as(eq(MutableTestPlan.class), eq(resource))).thenReturn(testPlan); SensorContextTester spy = Mockito.spy(context); sensor.execute(spy); verify(spy, times(1)).newCoverage(); verify(testCase).setCoverageBlock(testAbleFile, newArrayList(3, 6)); } @Test public void test_read_execution_data_for_lines_covered_by_tests_v0_7_5() throws IOException { testExecutionDataForLinesCoveredByTest("/org/sonar/plugins/jacoco/JaCoCov0_7_5_coverage_per_test/", newArrayList(3, 4, 5, 8, 12)); } @Test public void test_read_execution_data_for_lines_covered_by_tests_v0_7_4() throws IOException { testExecutionDataForLinesCoveredByTest("/org/sonar/plugins/jacoco/JaCoCov0_7_4_coverage_per_test/", newArrayList(3, 4, 5, 8, 12)); } @Test public void test_read_execution_data_for_lines_covered_by_tests_v0_7_4_incompatible() throws IOException { testExecutionDataForLinesCoveredByTest("/org/sonar/plugins/jacoco/JaCoCov0_7_4_incompatible_coverage_per_test/", newArrayList(3, 4, 5, 8, 12)); } @Test public void test_read_execution_data_for_lines_covered_by_tests_v0_7_5_incompatible() throws IOException { testExecutionDataForLinesCoveredByTest("/org/sonar/plugins/jacoco/JaCoCov0_7_5_incompatible_coverage_per_test/", newArrayList(3, 4, 5, 8, 9, 10, 13, 16)); } private void testExecutionDataForLinesCoveredByTest(String path, List<Integer> linesExpected) { outputDir = TestUtils.getResource(path); jacocoExecutionData = new File(outputDir, "jacoco.exec"); DefaultInputFile resource = new DefaultInputFile("", ""); resource.setLines(25); when(javaResourceLocator.findResourceByClassName(anyString())).thenReturn(resource); when(javaClasspath.getBinaryDirs()).thenReturn(ImmutableList.of(outputDir)); when(pathResolver.relativeFile(any(File.class), any(String.class))).thenReturn(jacocoExecutionData); MutableTestable testAbleFile = mock(MutableTestable.class); when(perspectives.as(eq(MutableTestable.class), eq(resource))).thenReturn(testAbleFile); MutableTestCase testCase = mock(MutableTestCase.class); when(testCase.name()).thenReturn("test"); MutableTestPlan testPlan = mock(MutableTestPlan.class); when(testPlan.testCasesByName("testBoth")).thenReturn(newArrayList(testCase)); when(testPlan.testCasesByName("testFoo")).thenReturn(newArrayList(testCase)); when(perspectives.as(eq(MutableTestPlan.class), eq(resource))).thenReturn(testPlan); sensor.execute(context); verify(testCase).setCoverageBlock(testAbleFile, linesExpected); } @Test public void force_coverage_to_zero_when_no_report_lts() { context.setRuntime(SonarRuntimeImpl.forSonarQube(SQ_5_6, SonarQubeSide.SCANNER)); Map<String, String> props = ImmutableMap.of(JacocoConfiguration.REPORT_MISSING_FORCE_ZERO, "true", REPORT_PATH_PROPERTY, "foo"); DefaultFileSystem fileSystem = new DefaultFileSystem((File)null); fileSystem.add(new DefaultInputFile("","foo").setLanguage("java")); JacocoConfiguration configuration = new JacocoConfiguration(new MapSettings().addProperties(props)); JaCoCoSensor sensor_force_coverage = new JaCoCoSensor(configuration, perspectives, fileSystem, pathResolver, javaResourceLocator, javaClasspath); outputDir = TestUtils.getResource("/org/sonar/plugins/jacoco/JaCoCoSensorTest/"); DefaultInputFile resource = new DefaultInputFile("", ""); resource.setLines(25); when(javaResourceLocator.findResourceByClassName(anyString())).thenReturn(resource); when(javaClasspath.getBinaryDirs()).thenReturn(ImmutableList.of(outputDir)); when(pathResolver.relativeFile(any(File.class), any(String.class))).thenReturn(new File("foo")); sensor_force_coverage.execute(context); int[] zeroHitlines = new int[] {6, 7, 8, 11, 15, 16, 18}; for (int zeroHitline : zeroHitlines) { assertThat(context.lineHits(":", zeroHitline)).isEqualTo(0); } } @Test public void force_coverage_to_zero_is_deprecated_on_6_2() { context.setRuntime(SonarRuntimeImpl.forSonarQube(SQ_6_2, SonarQubeSide.SCANNER)); Map<String, String> props = ImmutableMap.of(JacocoConfiguration.REPORT_MISSING_FORCE_ZERO, "true", REPORT_PATHS_PROPERTY, "foo"); DefaultFileSystem fileSystem = new DefaultFileSystem((File)null); fileSystem.add(new DefaultInputFile("","foo").setLanguage("java")); JacocoConfiguration configuration = new JacocoConfiguration(new MapSettings().addProperties(props)); JaCoCoSensor sensor_force_coverage = new JaCoCoSensor(configuration, perspectives, fileSystem, pathResolver, javaResourceLocator, javaClasspath); context.settings().addProperties(props); outputDir = TestUtils.getResource("/org/sonar/plugins/jacoco/JaCoCoSensorTest/"); DefaultInputFile resource = new DefaultInputFile("", ""); resource.setLines(25); when(javaResourceLocator.findResourceByClassName(anyString())).thenReturn(resource); when(javaClasspath.getBinaryDirs()).thenReturn(ImmutableList.of(outputDir)); when(pathResolver.relativeFile(any(File.class), any(String.class))).thenReturn(new File("foo")); sensor_force_coverage.execute(context); assertThat(logTester.logs(LoggerLevel.WARN)).contains("Property 'sonar.jacoco.reportMissing.force.zero' is deprecated and its value will be ignored."); } @Test public void do_not_save_measure_on_resource_which_doesnt_exist_in_the_context() { when(javaClasspath.getBinaryDirs()).thenReturn(ImmutableList.of(outputDir)); when(pathResolver.relativeFile(any(File.class), anyString())).thenReturn(jacocoExecutionData); SensorContextTester context = spy(SensorContextTester.create(new File(""))); sensor.execute(context); verify(context, never()).newCoverage(); } @Test public void should_do_nothing_if_output_dir_does_not_exists() { when(javaClasspath.getBinaryDirs()).thenReturn(ImmutableList.of(new File("nowhere"))); when(pathResolver.relativeFile(any(File.class), anyString())).thenReturn(jacocoExecutionData); SensorContextTester context = SensorContextTester.create(new File("")); context.setRuntime(SonarRuntimeImpl.forSonarQube(SQ_5_6, SonarQubeSide.SCANNER)); sensor.execute(context); assertThat(logTester.logs(LoggerLevel.INFO)).contains("No JaCoCo analysis of project coverage can be done since there is no class files."); } }