/* * SonarQube * Copyright (C) 2009-2017 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.core.issue.tracking; import java.util.ArrayList; import java.util.Collection; import java.util.List; import javax.annotation.Nullable; import org.apache.commons.codec.digest.DigestUtils; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.sonar.api.rule.RuleKey; import static java.util.Arrays.asList; import static org.apache.commons.lang.StringUtils.trim; import static org.assertj.core.api.Assertions.assertThat; public class TrackerTest { public static final RuleKey RULE_SYSTEM_PRINT = RuleKey.of("java", "SystemPrint"); public static final RuleKey RULE_UNUSED_LOCAL_VARIABLE = RuleKey.of("java", "UnusedLocalVariable"); public static final RuleKey RULE_UNUSED_PRIVATE_METHOD = RuleKey.of("java", "UnusedPrivateMethod"); public static final RuleKey RULE_NOT_DESIGNED_FOR_EXTENSION = RuleKey.of("java", "NotDesignedForExtension"); public static final RuleKey RULE_USE_DIAMOND = RuleKey.of("java", "UseDiamond"); @Rule public ExpectedException thrown = ExpectedException.none(); Tracker<Issue, Issue> tracker = new Tracker<>(); /** * Of course rule must match */ @Test public void similar_issues_except_rule_do_not_match() { FakeInput baseInput = new FakeInput("H1"); baseInput.createIssueOnLine(1, RULE_SYSTEM_PRINT, "msg"); FakeInput rawInput = new FakeInput("H1"); Issue raw = rawInput.createIssueOnLine(1, RULE_UNUSED_LOCAL_VARIABLE, "msg"); Tracking<Issue, Issue> tracking = tracker.track(rawInput, baseInput); assertThat(tracking.baseFor(raw)).isNull(); } @Test public void line_hash_has_greater_priority_than_line() { FakeInput baseInput = new FakeInput("H1", "H2", "H3"); Issue base1 = baseInput.createIssueOnLine(1, RULE_SYSTEM_PRINT, "msg"); Issue base2 = baseInput.createIssueOnLine(3, RULE_SYSTEM_PRINT, "msg"); FakeInput rawInput = new FakeInput("a", "b", "H1", "H2", "H3"); Issue raw1 = rawInput.createIssueOnLine(3, RULE_SYSTEM_PRINT, "msg"); Issue raw2 = rawInput.createIssueOnLine(5, RULE_SYSTEM_PRINT, "msg"); Tracking<Issue, Issue> tracking = tracker.track(rawInput, baseInput); assertThat(tracking.baseFor(raw1)).isSameAs(base1); assertThat(tracking.baseFor(raw2)).isSameAs(base2); } /** * SONAR-2928 */ @Test public void no_lines_and_different_messages_match() { FakeInput baseInput = new FakeInput("H1", "H2", "H3"); Issue base = baseInput.createIssue(RULE_SYSTEM_PRINT, "msg1"); FakeInput rawInput = new FakeInput("H10", "H11", "H12"); Issue raw = rawInput.createIssue(RULE_SYSTEM_PRINT, "msg2"); Tracking<Issue, Issue> tracking = tracker.track(rawInput, baseInput); assertThat(tracking.baseFor(raw)).isSameAs(base); } @Test public void similar_issues_except_message_match() { FakeInput baseInput = new FakeInput("H1"); Issue base = baseInput.createIssueOnLine(1, RULE_SYSTEM_PRINT, "msg1"); FakeInput rawInput = new FakeInput("H1"); Issue raw = rawInput.createIssueOnLine(1, RULE_SYSTEM_PRINT, "msg2"); Tracking<Issue, Issue> tracking = tracker.track(rawInput, baseInput); assertThat(tracking.baseFor(raw)).isSameAs(base); } @Test public void similar_issues_if_trimmed_messages_match() { FakeInput baseInput = new FakeInput("H1"); Issue base = baseInput.createIssueOnLine(1, RULE_SYSTEM_PRINT, " message "); FakeInput rawInput = new FakeInput("H2"); Issue raw = rawInput.createIssueOnLine(1, RULE_SYSTEM_PRINT, "message"); Tracking<Issue, Issue> tracking = tracker.track(rawInput, baseInput); assertThat(tracking.baseFor(raw)).isSameAs(base); } /** * Source code of this line was changed, but line and message still match */ @Test public void similar_issues_except_line_hash_match() { FakeInput baseInput = new FakeInput("H1"); Issue base = baseInput.createIssueOnLine(1, RULE_SYSTEM_PRINT, "msg"); FakeInput rawInput = new FakeInput("H2"); Issue raw = rawInput.createIssueOnLine(1, RULE_SYSTEM_PRINT, "msg"); Tracking<Issue, Issue> tracking = tracker.track(rawInput, baseInput); assertThat(tracking.baseFor(raw)).isSameAs(base); } @Test public void similar_issues_except_line_match() { FakeInput baseInput = new FakeInput("H1", "H2"); Issue base = baseInput.createIssueOnLine(1, RULE_SYSTEM_PRINT, "msg"); FakeInput rawInput = new FakeInput("H2", "H1"); Issue raw = rawInput.createIssueOnLine(2, RULE_SYSTEM_PRINT, "msg"); Tracking<Issue, Issue> tracking = tracker.track(rawInput, baseInput); assertThat(tracking.baseFor(raw)).isSameAs(base); } /** * SONAR-2812 */ @Test public void only_same_line_hash_match_match() { FakeInput baseInput = new FakeInput("H1", "H2"); Issue base = baseInput.createIssueOnLine(1, RULE_SYSTEM_PRINT, "msg"); FakeInput rawInput = new FakeInput("H3", "H4", "H1"); Issue raw = rawInput.createIssueOnLine(3, RULE_SYSTEM_PRINT, "other message"); Tracking<Issue, Issue> tracking = tracker.track(rawInput, baseInput); assertThat(tracking.baseFor(raw)).isSameAs(base); } @Test public void do_not_fail_if_base_issue_without_line() { FakeInput baseInput = new FakeInput("H1", "H2"); Issue base = baseInput.createIssueOnLine(1, RULE_SYSTEM_PRINT, "msg1"); FakeInput rawInput = new FakeInput("H3", "H4", "H5"); Issue raw = rawInput.createIssue(RULE_UNUSED_LOCAL_VARIABLE, "msg2"); Tracking<Issue, Issue> tracking = tracker.track(rawInput, baseInput); assertThat(tracking.baseFor(raw)).isNull(); assertThat(tracking.getUnmatchedBases()).containsOnly(base); } @Test public void do_not_fail_if_raw_issue_without_line() { FakeInput baseInput = new FakeInput("H1", "H2"); Issue base = baseInput.createIssue(RULE_SYSTEM_PRINT, "msg1"); FakeInput rawInput = new FakeInput("H3", "H4", "H5"); Issue raw = rawInput.createIssueOnLine(1, RULE_UNUSED_LOCAL_VARIABLE, "msg2"); Tracking<Issue, Issue> tracking = tracker.track(rawInput, baseInput); assertThat(tracking.baseFor(raw)).isNull(); assertThat(tracking.getUnmatchedBases()).containsOnly(base); } @Test public void do_not_fail_if_raw_line_does_not_exist() { FakeInput baseInput = new FakeInput(); FakeInput rawInput = new FakeInput("H1").addIssue(new Issue(200, "H200", RULE_SYSTEM_PRINT, "msg")); Tracking<Issue, Issue> tracking = tracker.track(rawInput, baseInput); assertThat(tracking.getUnmatchedRaws()).hasSize(1); } /** * SONAR-3072 */ @Test public void recognize_blocks_1() { FakeInput baseInput = FakeInput.createForSourceLines( "package example1;", "", "public class Toto {", "", " public void doSomething() {", " // doSomething", " }", "", " public void doSomethingElse() {", " // doSomethingElse", " }", "}"); Issue base1 = baseInput.createIssueOnLine(7, RULE_SYSTEM_PRINT, "Indentation"); Issue base2 = baseInput.createIssueOnLine(11, RULE_SYSTEM_PRINT, "Indentation"); FakeInput rawInput = FakeInput.createForSourceLines( "package example1;", "", "public class Toto {", "", " public Toto(){}", "", " public void doSomethingNew() {", " // doSomethingNew", " }", "", " public void doSomethingElseNew() {", " // doSomethingElseNew", " }", "", " public void doSomething() {", " // doSomething", " }", "", " public void doSomethingElse() {", " // doSomethingElse", " }", "}"); Issue raw1 = rawInput.createIssueOnLine(9, RULE_SYSTEM_PRINT, "Indentation"); Issue raw2 = rawInput.createIssueOnLine(13, RULE_SYSTEM_PRINT, "Indentation"); Issue raw3 = rawInput.createIssueOnLine(17, RULE_SYSTEM_PRINT, "Indentation"); Issue raw4 = rawInput.createIssueOnLine(21, RULE_SYSTEM_PRINT, "Indentation"); Tracking<Issue, Issue> tracking = tracker.track(rawInput, baseInput); assertThat(tracking.baseFor(raw1)).isNull(); assertThat(tracking.baseFor(raw2)).isNull(); assertThat(tracking.baseFor(raw3)).isSameAs(base1); assertThat(tracking.baseFor(raw4)).isSameAs(base2); assertThat(tracking.getUnmatchedBases()).isEmpty(); } /** * SONAR-3072 */ @Test public void recognize_blocks_2() { FakeInput baseInput = FakeInput.createForSourceLines( "package example2;", "", "public class Toto {", " void method1() {", " System.out.println(\"toto\");", " }", "}"); Issue base1 = baseInput.createIssueOnLine(5, RULE_SYSTEM_PRINT, "SystemPrintln"); FakeInput rawInput = FakeInput.createForSourceLines( "package example2;", "", "public class Toto {", "", " void method2() {", " System.out.println(\"toto\");", " }", "", " void method1() {", " System.out.println(\"toto\");", " }", "", " void method3() {", " System.out.println(\"toto\");", " }", "}"); Issue raw1 = rawInput.createIssueOnLine(6, RULE_SYSTEM_PRINT, "SystemPrintln"); Issue raw2 = rawInput.createIssueOnLine(10, RULE_SYSTEM_PRINT, "SystemPrintln"); Issue raw3 = rawInput.createIssueOnLine(14, RULE_SYSTEM_PRINT, "SystemPrintln"); Tracking<Issue, Issue> tracking = tracker.track(rawInput, baseInput); assertThat(tracking.baseFor(raw1)).isNull(); assertThat(tracking.baseFor(raw2)).isSameAs(base1); assertThat(tracking.baseFor(raw3)).isNull(); } @Test public void recognize_blocks_3() { FakeInput baseInput = FakeInput.createForSourceLines( "package sample;", "", "public class Sample {", "\t", "\tpublic Sample(int i) {", "\t\tint j = i+1;", // UnusedLocalVariable "\t}", "", "\tpublic boolean avoidUtilityClass() {", // NotDesignedForExtension "\t\treturn true;", "\t}", "", "\tprivate String myMethod() {", // UnusedPrivateMethod "\t\treturn \"hello\";", "\t}", "}"); Issue base1 = baseInput.createIssueOnLine(6, RULE_UNUSED_LOCAL_VARIABLE, "Avoid unused local variables such as 'j'."); Issue base2 = baseInput.createIssueOnLine(13, RULE_UNUSED_PRIVATE_METHOD, "Avoid unused private methods such as 'myMethod()'."); Issue base3 = baseInput.createIssueOnLine(9, RULE_NOT_DESIGNED_FOR_EXTENSION, "Method 'avoidUtilityClass' is not designed for extension - needs to be abstract, final or empty."); FakeInput rawInput = FakeInput.createForSourceLines( "package sample;", "", "public class Sample {", "", "\tpublic Sample(int i) {", "\t\tint j = i+1;", // UnusedLocalVariable is still there "\t}", "\t", "\tpublic boolean avoidUtilityClass() {", // NotDesignedForExtension is still there "\t\treturn true;", "\t}", "\t", "\tprivate String myMethod() {", // issue UnusedPrivateMethod is fixed because it's called at line 18 "\t\treturn \"hello\";", "\t}", "", " public void newIssue() {", " String msg = myMethod();", // new issue UnusedLocalVariable " }", "}"); Issue newRaw = rawInput.createIssueOnLine(18, RULE_UNUSED_LOCAL_VARIABLE, "Avoid unused local variables such as 'msg'."); Issue rawSameAsBase1 = rawInput.createIssueOnLine(6, RULE_UNUSED_LOCAL_VARIABLE, "Avoid unused local variables such as 'j'."); Issue rawSameAsBase3 = rawInput.createIssueOnLine(9, RULE_NOT_DESIGNED_FOR_EXTENSION, "Method 'avoidUtilityClass' is not designed for extension - needs to be abstract, final or empty."); Tracking<Issue, Issue> tracking = tracker.track(rawInput, baseInput); assertThat(tracking.baseFor(newRaw)).isNull(); assertThat(tracking.baseFor(rawSameAsBase1)).isSameAs(base1); assertThat(tracking.baseFor(rawSameAsBase3)).isSameAs(base3); assertThat(tracking.getUnmatchedBases()).containsOnly(base2); } /** * https://jira.sonarsource.com/browse/SONAR-7595 */ @Test public void match_only_one_issue_when_multiple_blocks_match_the_same_block() { FakeInput baseInput = FakeInput.createForSourceLines( "public class Toto {", " private final Deque<Set<DataItem>> one = new ArrayDeque<Set<DataItem>>();", " private final Deque<Set<DataItem>> two = new ArrayDeque<Set<DataItem>>();", " private final Deque<Integer> three = new ArrayDeque<Integer>();", " private final Deque<Set<Set<DataItem>>> four = new ArrayDeque<Set<DataItem>>();"); Issue base1 = baseInput.createIssueOnLine(2, RULE_USE_DIAMOND, "Use diamond"); baseInput.createIssueOnLine(3, RULE_USE_DIAMOND, "Use diamond"); baseInput.createIssueOnLine(4, RULE_USE_DIAMOND, "Use diamond"); baseInput.createIssueOnLine(5, RULE_USE_DIAMOND, "Use diamond"); FakeInput rawInput = FakeInput.createForSourceLines( "public class Toto {", " // move all lines", " private final Deque<Set<DataItem>> one = new ArrayDeque<Set<DataItem>>();", " private final Deque<Set<DataItem>> two = new ArrayDeque<>();", " private final Deque<Integer> three = new ArrayDeque<>();", " private final Deque<Set<Set<DataItem>>> four = new ArrayDeque<>();"); Issue raw1 = rawInput.createIssueOnLine(3, RULE_USE_DIAMOND, "Use diamond"); Tracking<Issue, Issue> tracking = tracker.track(rawInput, baseInput); assertThat(tracking.getUnmatchedBases()).hasSize(3); assertThat(tracking.baseFor(raw1)).isEqualTo(base1); } private static class Issue implements Trackable { private final RuleKey ruleKey; private final Integer line; private final String message, lineHash; Issue(@Nullable Integer line, String lineHash, RuleKey ruleKey, String message) { this.line = line; this.lineHash = lineHash; this.ruleKey = ruleKey; this.message = trim(message); } @Override public Integer getLine() { return line; } @Override public String getMessage() { return message; } @Override public String getLineHash() { return lineHash; } @Override public RuleKey getRuleKey() { return ruleKey; } } private static class FakeInput implements Input<Issue> { private final List<Issue> issues = new ArrayList<>(); private final List<String> lineHashes; FakeInput(String... lineHashes) { this.lineHashes = asList(lineHashes); } static FakeInput createForSourceLines(String... lines) { String[] hashes = new String[lines.length]; for (int i = 0; i < lines.length; i++) { hashes[i] = DigestUtils.md5Hex(lines[i].replaceAll("[\t ]", "")); } return new FakeInput(hashes); } Issue createIssueOnLine(int line, RuleKey ruleKey, String message) { Issue issue = new Issue(line, lineHashes.get(line - 1), ruleKey, message); issues.add(issue); return issue; } /** * No line (line 0) */ Issue createIssue(RuleKey ruleKey, String message) { Issue issue = new Issue(null, "", ruleKey, message); issues.add(issue); return issue; } FakeInput addIssue(Issue issue) { this.issues.add(issue); return this; } @Override public LineHashSequence getLineHashSequence() { return new LineHashSequence(lineHashes); } @Override public BlockHashSequence getBlockHashSequence() { return new BlockHashSequence(getLineHashSequence(), 2); } @Override public Collection<Issue> getIssues() { return issues; } } }