/* * SonarQube Java * Copyright (C) 2012-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.java; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.sonar.sslr.api.RecognitionException; import com.sonar.sslr.impl.LexerException; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.sonar.api.SonarQubeSide; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.fs.internal.DefaultFileSystem; import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.rule.CheckFactory; import org.sonar.api.batch.rule.Checks; import org.sonar.api.batch.sensor.SensorContext; import org.sonar.api.batch.sensor.highlighting.NewHighlighting; import org.sonar.api.batch.sensor.internal.SensorContextTester; import org.sonar.api.batch.sensor.symbol.NewSymbolTable; import org.sonar.api.internal.SonarRuntimeImpl; import org.sonar.api.issue.Issuable; import org.sonar.api.issue.Issue; import org.sonar.api.measures.FileLinesContext; import org.sonar.api.measures.FileLinesContextFactory; import org.sonar.api.rule.RuleKey; import org.sonar.api.utils.Version; import org.sonar.plugins.java.api.CheckRegistrar; import org.sonar.plugins.java.api.JavaCheck; import org.sonar.squidbridge.api.CodeVisitor; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; 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; @RunWith(MockitoJUnitRunner.class) public class SonarComponentsTest { private static final String REPOSITORY_NAME = "custom"; @Mock private FileLinesContextFactory fileLinesContextFactory; @Mock private CheckFactory checkFactory; @Mock private Checks<JavaCheck> checks; @Mock private SensorContext context; @Before public void setUp() { // configure mocks that need verification when(this.checkFactory.<JavaCheck>create(anyString())).thenReturn(this.checks); when(this.checks.addAnnotatedChecks(any(Iterable.class))).thenReturn(this.checks); } public void postTestExecutionChecks() { // each time a SonarComponent is instantiated the following methods must be called twice // once for custom checks, once for custom java checks verify(this.checkFactory, times(2)).create(REPOSITORY_NAME); verify(this.checks, times(2)).addAnnotatedChecks(any(Iterable.class)); verify(this.checks, times(2)).all(); } @Test public void test_sonar_components() { SensorContextTester sensorContextTester = spy(SensorContextTester.create(new File(""))); DefaultFileSystem fs = sensorContextTester.fileSystem(); JavaTestClasspath javaTestClasspath = mock(JavaTestClasspath.class); ImmutableList<File> javaTestClasspathList = ImmutableList.of(); when(javaTestClasspath.getElements()).thenReturn(javaTestClasspathList); File file = new File("foo.java"); fs.add(new DefaultInputFile("", "foo.java")); FileLinesContext fileLinesContext = mock(FileLinesContext.class); when(fileLinesContextFactory.createFor(any(InputFile.class))).thenReturn(fileLinesContext); SonarComponents sonarComponents = new SonarComponents(fileLinesContextFactory, fs, null, javaTestClasspath, checkFactory); sonarComponents.setSensorContext(sensorContextTester); CodeVisitor[] visitors = sonarComponents.checkClasses(); assertThat(visitors).hasSize(0); Collection<JavaCheck> testChecks = sonarComponents.testCheckClasses(); assertThat(testChecks).hasSize(0); assertThat(sonarComponents.getFileSystem()).isEqualTo(fs); assertThat(sonarComponents.getJavaClasspath()).isEmpty(); assertThat(sonarComponents.getJavaTestClasspath()).isEqualTo(javaTestClasspathList); NewHighlighting newHighlighting = sonarComponents.highlightableFor(file); assertThat(newHighlighting).isNotNull(); verify(sensorContextTester, times(1)).newHighlighting(); NewSymbolTable newSymbolTable = sonarComponents.symbolizableFor(file); assertThat(newSymbolTable ).isNotNull(); verify(sensorContextTester, times(1)).newSymbolTable(); assertThat(sonarComponents.fileLinesContextFor(file)).isEqualTo(fileLinesContext); JavaClasspath javaClasspath = mock(JavaClasspath.class); List<File> list = mock(List.class); when(javaClasspath.getElements()).thenReturn(list); sonarComponents = new SonarComponents(fileLinesContextFactory, fs, javaClasspath, javaTestClasspath, checkFactory); assertThat(sonarComponents.getJavaClasspath()).isEqualTo(list); } @Test public void creation_of_custom_checks() { JavaCheck expectedCheck = new CustomCheck(); CheckRegistrar expectedRegistrar = getRegistrar(expectedCheck); when(this.checks.all()).thenReturn(Lists.newArrayList(expectedCheck)).thenReturn(new ArrayList<JavaCheck>()); SonarComponents sonarComponents = new SonarComponents(this.fileLinesContextFactory, null, null, null, this.checkFactory, new CheckRegistrar[] { expectedRegistrar }); sonarComponents.setSensorContext(context); CodeVisitor[] visitors = sonarComponents.checkClasses(); assertThat(visitors).hasSize(1); assertThat(visitors[0]).isEqualTo(expectedCheck); Collection<JavaCheck> testChecks = sonarComponents.testCheckClasses(); assertThat(testChecks).hasSize(0); postTestExecutionChecks(); } @Test public void creation_of_custom_test_checks() { JavaCheck expectedCheck = new CustomTestCheck(); CheckRegistrar expectedRegistrar = getRegistrar(expectedCheck); when(checks.all()).thenReturn(new ArrayList<JavaCheck>()).thenReturn(Lists.newArrayList(expectedCheck)); SonarComponents sonarComponents = new SonarComponents(fileLinesContextFactory, null, null, null, checkFactory, new CheckRegistrar[] { expectedRegistrar }); sonarComponents.setSensorContext(context); CodeVisitor[] visitors = sonarComponents.checkClasses(); assertThat(visitors).hasSize(0); Collection<JavaCheck> testChecks = sonarComponents.testCheckClasses(); assertThat(testChecks).hasSize(1); assertThat(testChecks.iterator().next()).isEqualTo(expectedCheck); postTestExecutionChecks(); } @Test public void creation_of_both_types_test_checks() { JavaCheck expectedCheck = new CustomCheck(); JavaCheck expectedTestCheck = new CustomTestCheck(); CheckRegistrar expectedRegistrar = registrarContext -> registrarContext.registerClassesForRepository( REPOSITORY_NAME, Lists.<Class<? extends JavaCheck>>newArrayList(CustomCheck.class), Lists.<Class<? extends JavaCheck>>newArrayList(CustomTestCheck.class)); when(this.checks.all()).thenReturn(Lists.newArrayList(expectedCheck)).thenReturn(Lists.newArrayList(expectedTestCheck)); SonarComponents sonarComponents = new SonarComponents(fileLinesContextFactory, null, null, null, checkFactory, new CheckRegistrar[] { expectedRegistrar }); sonarComponents.setSensorContext(context); CodeVisitor[] visitors = sonarComponents.checkClasses(); assertThat(visitors).hasSize(1); assertThat(visitors[0]).isEqualTo(expectedCheck); Collection<JavaCheck> testChecks = sonarComponents.testCheckClasses(); assertThat(testChecks).hasSize(1); assertThat(testChecks.iterator().next()).isEqualTo(expectedTestCheck); assertThat(sonarComponents.checks()).hasSize(2); postTestExecutionChecks(); } @Test public void no_issue_when_check_not_found() throws Exception { JavaCheck expectedCheck = new CustomCheck(); CheckRegistrar expectedRegistrar = getRegistrar(expectedCheck); Issuable issuable = mock(Issuable.class); when(this.checks.all()).thenReturn(Lists.newArrayList(expectedCheck)).thenReturn(new ArrayList<>()); when(this.checks.ruleKey(any(JavaCheck.class))).thenReturn(null); SonarComponents sonarComponents = new SonarComponents(fileLinesContextFactory, null, null, null, checkFactory, new CheckRegistrar[] { expectedRegistrar }); sonarComponents.setSensorContext(context); sonarComponents.addIssue(new File(""), expectedCheck, 0, "message", null); verify(issuable, never()).addIssue(any(Issue.class)); } @Test public void no_issue_if_file_not_found() throws Exception { JavaCheck expectedCheck = new CustomCheck(); CheckRegistrar expectedRegistrar = getRegistrar(expectedCheck); DefaultFileSystem fileSystem = new DefaultFileSystem(new File("")); File file = new File("file.java"); when(this.checks.all()).thenReturn(Lists.newArrayList(expectedCheck)).thenReturn(new ArrayList<>()); when(this.checks.ruleKey(any(JavaCheck.class))).thenReturn(mock(RuleKey.class)); SonarComponents sonarComponents = new SonarComponents(fileLinesContextFactory, fileSystem, null, null, checkFactory, new CheckRegistrar[] { expectedRegistrar }); sonarComponents.setSensorContext(context); sonarComponents.addIssue(file, expectedCheck, 0, "message", null); } @Test public void add_issue_or_parse_error() throws Exception { JavaCheck expectedCheck = new CustomCheck(); CheckRegistrar expectedRegistrar = getRegistrar(expectedCheck); SensorContextTester context = SensorContextTester.create(new File("")); DefaultFileSystem fileSystem = context.fileSystem(); File file = new File("file.java"); DefaultInputFile inputFile = new DefaultInputFile("", "file.java"); inputFile.setLines(45); int[] linesOffset = new int[45]; linesOffset[35] = 12; linesOffset[42] = 1; inputFile.setOriginalLineOffsets(linesOffset); inputFile.setLastValidOffset(420); fileSystem.add(inputFile); when(this.checks.all()).thenReturn(Lists.newArrayList(expectedCheck)).thenReturn(new ArrayList<>()); when(this.checks.ruleKey(any(JavaCheck.class))).thenReturn(mock(RuleKey.class)); SonarComponents sonarComponents = new SonarComponents(fileLinesContextFactory, fileSystem, null, null, checkFactory, new CheckRegistrar[] { expectedRegistrar }); sonarComponents.setSensorContext(context); sonarComponents.addIssue(file, expectedCheck, -5, "message on wrong line", null); sonarComponents.addIssue(file, expectedCheck, 42, "message on line", 1); sonarComponents.addIssue(new File("."), expectedCheck, 42, "message on line", 1); sonarComponents.addIssue(new File("unknown_file"), expectedCheck, 42, "message on line", 1); sonarComponents.reportIssue(new AnalyzerMessage(expectedCheck, file, 35, "other message", 0)); assertThat(context.allIssues()).hasSize(3); Version version60 = Version.create(6, 0); Version version56 = Version.create(5, 6); RecognitionException parseError = new RecognitionException(new LexerException("parse error")); context.setRuntime(SonarRuntimeImpl.forSonarLint(version60)); assertThat(sonarComponents.reportAnalysisError(parseError, file)).isTrue(); context.setRuntime(SonarRuntimeImpl.forSonarLint(version56)); assertThat(sonarComponents.reportAnalysisError(parseError, file)).isFalse(); context.setRuntime(SonarRuntimeImpl.forSonarQube(version60, SonarQubeSide.SCANNER)); assertThat(sonarComponents.reportAnalysisError(parseError, file)).isFalse(); context.setRuntime(SonarRuntimeImpl.forSonarQube(version56, SonarQubeSide.SCANNER)); assertThat(sonarComponents.reportAnalysisError(parseError, file)).isFalse(); } @Test public void fail_on_empty_location() { JavaCheck expectedCheck = new CustomCheck(); CheckRegistrar expectedRegistrar = getRegistrar(expectedCheck); RuleKey ruleKey = RuleKey.of("MyRepo", "CustomCheck"); File file = new File("file.java"); DefaultInputFile inputFile = new DefaultInputFile("", file.getPath()); inputFile.initMetadata("class A {\n" + " void foo() {\n" + " System.out.println();\n" + " }\n" + "}\n"); SensorContextTester context = SensorContextTester.create(new File("")); DefaultFileSystem fileSystem = context.fileSystem(); SonarComponents sonarComponents = new SonarComponents(fileLinesContextFactory, fileSystem, null, null, checkFactory, new CheckRegistrar[] {expectedRegistrar}); sonarComponents.setSensorContext(context); AnalyzerMessage.TextSpan emptyTextSpan = new AnalyzerMessage.TextSpan(3, 10, 3, 10); AnalyzerMessage analyzerMessageEmptyLocation = new AnalyzerMessage(expectedCheck, file, emptyTextSpan, "message", 0); assertThatThrownBy(() -> sonarComponents.reportIssue(analyzerMessageEmptyLocation, ruleKey, inputFile, 0.0)) .isInstanceOf(IllegalStateException.class).hasMessageContaining("Issue location should not be empty"); assertThat(context.allIssues()).isEmpty(); AnalyzerMessage.TextSpan nonEmptyTextSpan = new AnalyzerMessage.TextSpan(3, 10, 3, 15); AnalyzerMessage analyzerMessageValidLocation = new AnalyzerMessage(expectedCheck, file, nonEmptyTextSpan, "message", 0); sonarComponents.reportIssue(analyzerMessageValidLocation, ruleKey, inputFile, 0.0); assertThat(context.allIssues()).isNotEmpty(); } @Test public void verify_sq_version() { SonarComponents sonarComponents = new SonarComponents(null, null, null, null, null, null); SensorContextTester context = SensorContextTester.create(new File("")); sonarComponents.setSensorContext(context); context.setRuntime(SonarRuntimeImpl.forSonarLint(Version.create(5, 6))); assertThat(sonarComponents.isSQGreaterThan62()).isFalse(); context.setRuntime(SonarRuntimeImpl.forSonarLint(Version.create(6, 2))); assertThat(sonarComponents.isSQGreaterThan62()).isTrue(); } private static CheckRegistrar getRegistrar(final JavaCheck expectedCheck) { return registrarContext -> registrarContext.registerClassesForRepository(REPOSITORY_NAME, Lists.<Class<? extends JavaCheck>>newArrayList(expectedCheck.getClass()), null); } private static class CustomCheck implements JavaCheck { } private static class CustomTestCheck implements JavaCheck { } }