/* * 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.ast.visitors; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.io.Files; import org.apache.commons.lang.StringUtils; import org.assertj.core.api.Fail; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.measures.FileLinesContext; import org.sonar.java.JavaConfiguration; import org.sonar.java.JavaSquid; import org.sonar.java.SonarComponents; import org.sonar.plugins.java.api.tree.SyntaxTrivia; import org.sonar.plugins.java.api.tree.Tree; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class FileLinesVisitorTest { private JavaConfiguration conf; private File baseDir; @Before public void setUp() throws Exception { conf = new JavaConfiguration(StandardCharsets.UTF_8); baseDir = new File("src/test/files/metrics"); } private void checkLines(String filename, FileLinesContext context) { checkLines(filename, context, false); } private void checkLines(String filename, FileLinesContext context, boolean sqGreaterThan62) { SonarComponents sonarComponents = mock(SonarComponents.class); when(sonarComponents.fileLength(Mockito.any(File.class))).thenAnswer(invocation -> { File arg = (File) invocation.getArguments()[0]; return Files.readLines(arg, StandardCharsets.UTF_8).size(); }); when(sonarComponents.isSQGreaterThan62()).thenReturn(sqGreaterThan62); when(sonarComponents.fileLinesContextFor(Mockito.any(File.class))).thenReturn(context); JavaSquid squid = new JavaSquid(conf, null, null, null, null, new FileLinesVisitor(sonarComponents)); squid.scan(Lists.newArrayList(new File(baseDir, filename)), Collections.emptyList()); } private int countTrivia(String filename) { TriviaVisitor triviaVisitor = new TriviaVisitor(); JavaSquid squid = new JavaSquid(conf, null, null, null, null, triviaVisitor); squid.scan(Lists.newArrayList(new File(baseDir, filename)), Collections.emptyList()); return triviaVisitor.numberTrivia; } @Test public void lines_of_code_data() { FileLinesContext context = mock(FileLinesContext.class); checkLines("LinesOfCode.java", context); verify(context).setIntValue(CoreMetrics.NCLOC_DATA_KEY, 1, 0); verify(context).setIntValue(CoreMetrics.NCLOC_DATA_KEY, 2, 0); verify(context).setIntValue(CoreMetrics.NCLOC_DATA_KEY, 3, 0); verify(context).setIntValue(CoreMetrics.NCLOC_DATA_KEY, 4, 0); verify(context).setIntValue(CoreMetrics.NCLOC_DATA_KEY, 5, 1); verify(context).setIntValue(CoreMetrics.NCLOC_DATA_KEY, 6, 1); verify(context).save(); } @Test public void comment_lines_data() { FileLinesContext context = mock(FileLinesContext.class); checkLines("Comments.java", context); verify(context).setIntValue(CoreMetrics.COMMENT_LINES_DATA_KEY, 1, 1); verify(context).setIntValue(CoreMetrics.COMMENT_LINES_DATA_KEY, 2, 1); verify(context).setIntValue(CoreMetrics.COMMENT_LINES_DATA_KEY, 3, 1); verify(context).setIntValue(CoreMetrics.COMMENT_LINES_DATA_KEY, 4, 0); verify(context).setIntValue(CoreMetrics.COMMENT_LINES_DATA_KEY, 5, 1); verify(context).setIntValue(CoreMetrics.COMMENT_LINES_DATA_KEY, 6, 1); verify(context).setIntValue(CoreMetrics.COMMENT_LINES_DATA_KEY, 7, 1); verify(context).setIntValue(CoreMetrics.COMMENT_LINES_DATA_KEY, 8, 0); verify(context).setIntValue(CoreMetrics.COMMENT_LINES_DATA_KEY, 9, 0); verify(context).setIntValue(CoreMetrics.COMMENT_LINES_DATA_KEY, 10, 1); verify(context).setIntValue(CoreMetrics.COMMENT_LINES_DATA_KEY, 11, 1); verify(context).setIntValue(CoreMetrics.COMMENT_LINES_DATA_KEY, 12, 1); verify(context).setIntValue(CoreMetrics.COMMENT_LINES_DATA_KEY, 13, 0); verify(context).setIntValue(CoreMetrics.COMMENT_LINES_DATA_KEY, 14, 1); verify(context).setIntValue(CoreMetrics.COMMENT_LINES_DATA_KEY, 15, 1); verify(context).setIntValue(CoreMetrics.COMMENT_LINES_DATA_KEY, 16, 0); verify(context).setIntValue(CoreMetrics.COMMENT_LINES_DATA_KEY, 17, 0); verify(context).setIntValue(CoreMetrics.COMMENT_LINES_DATA_KEY, 18, 0); verify(context).save(); } @Test public void comments_full() { String filename = "CommentsFull.java"; CommentsVerifier context = new CommentsVerifier(); checkLines(filename, context); List<Integer> reportedCommentLines = context.commentedLine; CommentsCounter counter = CommentsCounter.getComments(baseDir, filename); assertThatContainsAllLines(counter, reportedCommentLines); int expectedNumberComments = counter.numberComments - counter.numberFixme; assertThat(context.numberComments).isEqualTo(expectedNumberComments); assertThat(countTrivia(filename)).isEqualTo( expectedNumberComments // FIXME variable declarations sharing types are using the same type when iterating over the tokens. see line 109 + 1); } @Test public void executable_lines_should_be_counted_withSQGreaterThan62() throws Exception { FileLinesContext context = mock(FileLinesContext.class); checkLines("ExecutableLines.java", context, true); int[] expected = new int[] {0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0}; assertThat(expected).hasSize(56); for (int i = 0; i < expected.length; i++) { int line = i + 1; verify(context).setIntValue(CoreMetrics.EXECUTABLE_LINES_DATA_KEY, line, expected[i]); } verify(context).save(); } @Test public void executable_lines_should_NOT_be_counted_withSQLessThan62() throws Exception { FileLinesContext context = mock(FileLinesContext.class); checkLines("ExecutableLines.java", context); verify(context, times(0)).setIntValue(eq(CoreMetrics.EXECUTABLE_LINES_DATA_KEY), anyInt(), anyInt()); verify(context).save(); } private static void assertThatContainsAllLines(CommentsCounter counter, List<Integer> reportedCommentLines) { for (Integer line : reportedCommentLines) { if (counter.commentedLines.contains(line)) { counter.commentedLines.remove(line); } else { Fail.fail("should not have extra lines"); } } assertThat(counter.commentedLines).containsExactly(counter.casesNotCoveredLines.toArray(new Integer[0])); } private static class CommentsCounter { private int numberComments = 0; private int numberFixme = 0; private List<Integer> commentedLines = Lists.newLinkedList(); private List<Integer> casesNotCoveredLines = Lists.newLinkedList(); private CommentsCounter() { } private static CommentsCounter getComments(File baseDir, String filename) { CommentsCounter counter = new CommentsCounter(); try { int lineNumber = 1; for (String line : Files.readLines(new File(baseDir, filename), StandardCharsets.UTF_8)) { int commentCount = StringUtils.countMatches(line, "comment"); for (int i = 0; i < commentCount; i++) { counter.commentedLines.add(lineNumber); } counter.numberComments += commentCount; int fixmeCount = StringUtils.countMatches(line, "FIXME"); for (int i = 0; i < fixmeCount; i++) { counter.casesNotCoveredLines.add(lineNumber); } counter.numberFixme += fixmeCount; lineNumber++; } } catch (IOException e) { Fail.fail(e.getMessage()); } return counter; } } private static class CommentsVerifier implements FileLinesContext { private List<Integer> commentedLine = Lists.newLinkedList(); private int numberComments = 0; @Override public void setStringValue(String metricKey, int line, String value) { } @Override public void setIntValue(String metricKey, int line, int value) { if (CoreMetrics.COMMENT_LINES_DATA_KEY.equals(metricKey)) { for (int i = 0; i < value; i++) { commentedLine.add(line); } numberComments += value; } } @Override public void save() { } @Override public String getStringValue(String metricKey, int line) { return null; } @Override public Integer getIntValue(String metricKey, int line) { return null; } } private static class TriviaVisitor extends SubscriptionVisitor { int numberTrivia = 0; @Override public List<Tree.Kind> nodesToVisit() { return ImmutableList.of(Tree.Kind.TRIVIA); } @Override public void visitTrivia(SyntaxTrivia syntaxTrivia) { numberTrivia++; } } }