org.sonar.batch.issue.tracking.IssueTrackingTest.java Source code

Java tutorial

Introduction

Here is the source code for org.sonar.batch.issue.tracking.IssueTrackingTest.java

Source

/*
 * SonarQube, open source software quality management tool.
 * Copyright (C) 2008-2014 SonarSource
 * mailto:contact AT sonarsource DOT com
 *
 * SonarQube 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.
 *
 * SonarQube 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.batch.issue.tracking;

import com.google.common.collect.Lists;
import com.google.common.io.Resources;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.issue.Issue;
import org.sonar.api.issue.internal.DefaultIssue;
import org.sonar.api.resources.Project;
import org.sonar.api.resources.Resource;
import org.sonar.api.rule.RuleKey;
import org.sonar.core.issue.db.IssueDto;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;

import static com.google.common.collect.Lists.newArrayList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;

public class IssueTrackingTest {

    @Rule
    public TemporaryFolder temp = new TemporaryFolder();

    @Rule
    public ExpectedException thrown = ExpectedException.none();

    IssueTracking tracking;
    Resource project;
    SourceHashHolder sourceHashHolder;
    ServerLineHashesLoader lastSnapshots;
    long violationId = 0;

    @Before
    public void before() {
        lastSnapshots = mock(ServerLineHashesLoader.class);

        project = mock(Project.class);
        tracking = new IssueTracking();
    }

    @Test
    public void key_should_be_the_prioritary_field_to_check() {
        ServerIssueFromDb referenceIssue1 = newReferenceIssue("message", 10, "squid", "AvoidCycle", "checksum1");
        referenceIssue1.getDto().setKee("100");
        ServerIssueFromDb referenceIssue2 = newReferenceIssue("message", 10, "squid", "AvoidCycle", "checksum2");
        referenceIssue2.getDto().setKee("200");

        // exactly the fields of referenceIssue1 but not the same key
        DefaultIssue newIssue = newDefaultIssue("message", 10, RuleKey.of("squid", "AvoidCycle"), "checksum1")
                .setKey("200");

        IssueTrackingResult result = new IssueTrackingResult();
        tracking.mapIssues(newArrayList(newIssue),
                Lists.<ServerIssue>newArrayList(referenceIssue1, referenceIssue2), null, result);
        // same key
        assertThat(result.matching(newIssue)).isSameAs(referenceIssue2);
    }

    @Test
    public void checksum_should_have_greater_priority_than_line() {
        ServerIssue referenceIssue1 = newReferenceIssue("message", 1, "squid", "AvoidCycle", "checksum1");
        ServerIssue referenceIssue2 = newReferenceIssue("message", 3, "squid", "AvoidCycle", "checksum2");

        DefaultIssue newIssue1 = newDefaultIssue("message", 3, RuleKey.of("squid", "AvoidCycle"), "checksum1");
        DefaultIssue newIssue2 = newDefaultIssue("message", 5, RuleKey.of("squid", "AvoidCycle"), "checksum2");

        IssueTrackingResult result = new IssueTrackingResult();
        tracking.mapIssues(newArrayList(newIssue1, newIssue2), newArrayList(referenceIssue1, referenceIssue2), null,
                result);
        assertThat(result.matching(newIssue1)).isSameAs(referenceIssue1);
        assertThat(result.matching(newIssue2)).isSameAs(referenceIssue2);
    }

    /**
     * SONAR-2928
     */
    @Test
    public void same_rule_and_null_line_and_checksum_but_different_messages() {
        DefaultIssue newIssue = newDefaultIssue("new message", null, RuleKey.of("squid", "AvoidCycle"),
                "checksum1");
        ServerIssue referenceIssue = newReferenceIssue("old message", null, "squid", "AvoidCycle", "checksum1");

        IssueTrackingResult result = new IssueTrackingResult();
        tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), null, result);
        assertThat(result.matching(newIssue)).isSameAs(referenceIssue);
    }

    @Test
    public void same_rule_and_line_and_checksum_but_different_messages() {
        DefaultIssue newIssue = newDefaultIssue("new message", 1, RuleKey.of("squid", "AvoidCycle"), "checksum1");
        ServerIssue referenceIssue = newReferenceIssue("old message", 1, "squid", "AvoidCycle", "checksum1");

        IssueTrackingResult result = new IssueTrackingResult();
        tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), null, result);
        assertThat(result.matching(newIssue)).isSameAs(referenceIssue);
    }

    @Test
    public void same_rule_and_line_message() {
        DefaultIssue newIssue = newDefaultIssue("message", 1, RuleKey.of("squid", "AvoidCycle"), "checksum1");
        ServerIssue referenceIssue = newReferenceIssue("message", 1, "squid", "AvoidCycle", "checksum2");

        IssueTrackingResult result = new IssueTrackingResult();
        tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), null, result);
        assertThat(result.matching(newIssue)).isSameAs(referenceIssue);
    }

    @Test
    public void should_ignore_reference_measure_without_checksum() {
        DefaultIssue newIssue = newDefaultIssue("message", 1, RuleKey.of("squid", "AvoidCycle"), null);
        ServerIssue referenceIssue = newReferenceIssue("message", 1, "squid", "NullDeref", null);

        IssueTrackingResult result = new IssueTrackingResult();
        tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), null, result);
        assertThat(result.matching(newIssue)).isNull();
    }

    @Test
    public void same_rule_and_message_and_checksum_but_different_line() {
        DefaultIssue newIssue = newDefaultIssue("message", 1, RuleKey.of("squid", "AvoidCycle"), "checksum1");
        ServerIssue referenceIssue = newReferenceIssue("message", 2, "squid", "AvoidCycle", "checksum1");

        IssueTrackingResult result = new IssueTrackingResult();
        tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), null, result);
        assertThat(result.matching(newIssue)).isSameAs(referenceIssue);
    }

    /**
     * SONAR-2812
     */
    @Test
    public void same_checksum_and_rule_but_different_line_and_different_message() {
        DefaultIssue newIssue = newDefaultIssue("new message", 1, RuleKey.of("squid", "AvoidCycle"), "checksum1");
        ServerIssue referenceIssue = newReferenceIssue("old message", 2, "squid", "AvoidCycle", "checksum1");

        IssueTrackingResult result = new IssueTrackingResult();
        tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), null, result);
        assertThat(result.matching(newIssue)).isSameAs(referenceIssue);
    }

    @Test
    public void should_create_new_issue_when_same_rule_same_message_but_different_line_and_checksum() {
        DefaultIssue newIssue = newDefaultIssue("message", 1, RuleKey.of("squid", "AvoidCycle"), "checksum1");
        ServerIssue referenceIssue = newReferenceIssue("message", 2, "squid", "AvoidCycle", "checksum2");

        IssueTrackingResult result = new IssueTrackingResult();
        tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), null, result);
        assertThat(result.matching(newIssue)).isNull();
    }

    @Test
    public void should_not_track_issue_if_different_rule() {
        DefaultIssue newIssue = newDefaultIssue("message", 1, RuleKey.of("squid", "AvoidCycle"), "checksum1");
        ServerIssue referenceIssue = newReferenceIssue("message", 1, "squid", "NullDeref", "checksum1");

        IssueTrackingResult result = new IssueTrackingResult();
        tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), null, result);
        assertThat(result.matching(newIssue)).isNull();
    }

    @Test
    public void should_compare_issues_with_database_format() {
        // issue messages are trimmed and can be abbreviated when persisted in database.
        // Comparing issue messages must use the same format.
        DefaultIssue newIssue = newDefaultIssue("      message    ", 1, RuleKey.of("squid", "AvoidCycle"),
                "checksum1");
        ServerIssue referenceIssue = newReferenceIssue("message", 1, "squid", "AvoidCycle", "checksum2");

        IssueTrackingResult result = new IssueTrackingResult();
        tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), null, result);
        assertThat(result.matching(newIssue)).isSameAs(referenceIssue);
    }

    @Test
    public void past_issue_not_associated_with_line_should_not_cause_npe() throws Exception {
        initLastHashes("example2-v1", "example2-v2");

        DefaultIssue newIssue = newDefaultIssue("Indentation", 9, RuleKey.of("squid", "AvoidCycle"), "foo");
        ServerIssue referenceIssue = newReferenceIssue("2 branches need to be covered", null, "squid", "AvoidCycle",
                null);

        IssueTrackingResult result = tracking.track(sourceHashHolder, newArrayList(referenceIssue),
                newArrayList(newIssue));

        assertThat(result.matched()).isEmpty();
    }

    @Test
    public void new_issue_not_associated_with_line_should_not_cause_npe() throws Exception {
        initLastHashes("example2-v1", "example2-v2");

        DefaultIssue newIssue = newDefaultIssue("1 branch need to be covered", null,
                RuleKey.of("squid", "AvoidCycle"), "foo");
        ServerIssue referenceIssue = newReferenceIssue("Indentationd", 7, "squid", "AvoidCycle", null);

        IssueTrackingResult result = tracking.track(sourceHashHolder, newArrayList(referenceIssue),
                newArrayList(newIssue));

        assertThat(result.matched()).isEmpty();
    }

    /**
     * SONAR-2928
     */
    @Test
    public void issue_not_associated_with_line() throws Exception {
        initLastHashes("example2-v1", "example2-v2");

        DefaultIssue newIssue = newDefaultIssue("1 branch need to be covered", null,
                RuleKey.of("squid", "AvoidCycle"), null);
        ServerIssue referenceIssue = newReferenceIssue("2 branches need to be covered", null, "squid", "AvoidCycle",
                null);

        IssueTrackingResult result = tracking.track(sourceHashHolder, newArrayList(referenceIssue),
                newArrayList(newIssue));

        assertThat(result.matching(newIssue)).isEqualTo(referenceIssue);
    }

    @Test
    public void check_valid_line() throws Exception {
        initLastHashes("example2-v1", "example2-v2");

        DefaultIssue newIssue = newDefaultIssue("1 branch need to be covered", 200,
                RuleKey.of("squid", "AvoidCycle"), null);

        thrown.expectMessage(
                "Invalid line number for issue DefaultIssue[key=<null>,componentUuid=<null>,componentKey=<null>,moduleUuid=<null>,moduleUuidPath=<null>,projectUuid=<null>,projectKey=<null>,ruleKey=squid:AvoidCycle,language=<null>,severity=<null>,manualSeverity=false,message=1 branch need to be covered,line=200,effortToFix=<null>,debt=<null>,status=OPEN,resolution=<null>,reporter=<null>,assignee=<null>,checksum=<null>,attributes=<null>,authorLogin=<null>,actionPlanKey=<null>,comments=<null>,tags=<null>,creationDate=<null>,updateDate=<null>,closeDate=<null>,currentChange=<null>,changes=<null>,isNew=true,endOfLife=false,onDisabledRule=false,isChanged=false,sendNotifications=false,selectedAt=<null>]. File has only 17 line(s)");

        tracking.track(sourceHashHolder, Collections.<ServerIssue>emptyList(), newArrayList(newIssue));
    }

    /**
     * SONAR-3072
     */
    @Test
    public void should_track_issues_based_on_blocks_recognition_on_example1() throws Exception {
        initLastHashes("example1-v1", "example1-v2");

        ServerIssue referenceIssue1 = newReferenceIssue("Indentation", 7, "squid", "AvoidCycle", null);
        ServerIssue referenceIssue2 = newReferenceIssue("Indentation", 11, "squid", "AvoidCycle", null);

        DefaultIssue newIssue1 = newDefaultIssue("Indentation", 9, RuleKey.of("squid", "AvoidCycle"), null);
        DefaultIssue newIssue2 = newDefaultIssue("Indentation", 13, RuleKey.of("squid", "AvoidCycle"), null);
        DefaultIssue newIssue3 = newDefaultIssue("Indentation", 17, RuleKey.of("squid", "AvoidCycle"), null);
        DefaultIssue newIssue4 = newDefaultIssue("Indentation", 21, RuleKey.of("squid", "AvoidCycle"), null);

        IssueTrackingResult result = tracking.track(sourceHashHolder,
                Arrays.asList(referenceIssue1, referenceIssue2),
                Arrays.asList(newIssue1, newIssue2, newIssue3, newIssue4));

        assertThat(result.matching(newIssue1)).isNull();
        assertThat(result.matching(newIssue2)).isNull();
        assertThat(result.matching(newIssue3)).isSameAs(referenceIssue1);
        assertThat(result.matching(newIssue4)).isSameAs(referenceIssue2);
    }

    /**
     * SONAR-3072
     */
    @Test
    public void should_track_issues_based_on_blocks_recognition_on_example2() throws Exception {
        initLastHashes("example2-v1", "example2-v2");

        ServerIssue referenceIssue1 = newReferenceIssue("SystemPrintln", 5, "squid", "AvoidCycle", null);

        DefaultIssue newIssue1 = newDefaultIssue("SystemPrintln", 6, RuleKey.of("squid", "AvoidCycle"), null);
        DefaultIssue newIssue2 = newDefaultIssue("SystemPrintln", 10, RuleKey.of("squid", "AvoidCycle"), null);
        DefaultIssue newIssue3 = newDefaultIssue("SystemPrintln", 14, RuleKey.of("squid", "AvoidCycle"), null);

        IssueTrackingResult result = new IssueTrackingResult();
        tracking.mapIssues(Arrays.asList(newIssue1, newIssue2, newIssue3), Arrays.asList(referenceIssue1),
                sourceHashHolder, result);

        assertThat(result.matching(newIssue1)).isNull();
        assertThat(result.matching(newIssue2)).isSameAs(referenceIssue1);
        assertThat(result.matching(newIssue3)).isNull();
    }

    @Test
    public void should_track_issues_based_on_blocks_recognition_on_example3() throws Exception {
        initLastHashes("example3-v1", "example3-v2");

        ServerIssue referenceIssue1 = newReferenceIssue("Avoid unused local variables such as 'j'.", 6, "squid",
                "AvoidCycle", "63c11570fc0a76434156be5f8138fa03");
        ServerIssue referenceIssue2 = newReferenceIssue("Avoid unused private methods such as 'myMethod()'.", 13,
                "squid", "NullDeref", "ef23288705d1ef1e512448ace287586e");
        ServerIssue referenceIssue3 = newReferenceIssue(
                "Method 'avoidUtilityClass' is not designed for extension - needs to be abstract, final or empty.",
                9, "pmd", "UnusedLocalVariable", "ed5cdd046fda82727d6fedd1d8e3a310");

        // New issue
        DefaultIssue newIssue1 = newDefaultIssue("Avoid unused local variables such as 'msg'.", 18,
                RuleKey.of("squid", "AvoidCycle"), "a24254126be2bf1a9b9a8db43f633733");
        // Same as referenceIssue2
        DefaultIssue newIssue2 = newDefaultIssue("Avoid unused private methods such as 'myMethod()'.", 13,
                RuleKey.of("squid", "NullDeref"), "ef23288705d1ef1e512448ace287586e");
        // Same as referenceIssue3
        DefaultIssue newIssue3 = newDefaultIssue(
                "Method 'avoidUtilityClass' is not designed for extension - needs to be abstract, final or empty.",
                9, RuleKey.of("pmd", "UnusedLocalVariable"), "ed5cdd046fda82727d6fedd1d8e3a310");
        // New issue
        DefaultIssue newIssue4 = newDefaultIssue(
                "Method 'newViolation' is not designed for extension - needs to be abstract, final or empty.", 17,
                RuleKey.of("pmd", "UnusedLocalVariable"), "7d58ac9040c27e4ca2f11a0269e251e2");
        // Same as referenceIssue1
        DefaultIssue newIssue5 = newDefaultIssue("Avoid unused local variables such as 'j'.", 6,
                RuleKey.of("squid", "AvoidCycle"), "4432a2675ec3e1620daefe38386b51ef");

        IssueTrackingResult result = new IssueTrackingResult();
        tracking.mapIssues(Arrays.asList(newIssue1, newIssue2, newIssue3, newIssue4, newIssue5),
                Arrays.asList(referenceIssue1, referenceIssue2, referenceIssue3), sourceHashHolder, result);

        assertThat(result.matching(newIssue1)).isNull();
        assertThat(result.matching(newIssue2)).isSameAs(referenceIssue2);
        assertThat(result.matching(newIssue3)).isSameAs(referenceIssue3);
        assertThat(result.matching(newIssue4)).isNull();
        assertThat(result.matching(newIssue5)).isSameAs(referenceIssue1);
    }

    @Test
    public void dont_load_checksum_if_no_new_issue() {
        sourceHashHolder = mock(SourceHashHolder.class);

        ServerIssue referenceIssue = newReferenceIssue("2 branches need to be covered", null, "squid", "AvoidCycle",
                null);

        tracking.track(sourceHashHolder, newArrayList(referenceIssue), Collections.<DefaultIssue>emptyList());

        verifyZeroInteractions(lastSnapshots, sourceHashHolder);
    }

    private static String load(String name) throws IOException {
        return Resources.toString(IssueTrackingTest.class.getResource("IssueTrackingTest/" + name + ".txt"),
                StandardCharsets.UTF_8);
    }

    private DefaultIssue newDefaultIssue(String message, Integer line, RuleKey ruleKey, String checksum) {
        return new DefaultIssue().setMessage(message).setLine(line).setRuleKey(ruleKey).setChecksum(checksum)
                .setStatus(Issue.STATUS_OPEN);
    }

    private ServerIssueFromDb newReferenceIssue(String message, Integer lineId, String ruleRepo, String ruleKey,
            String lineChecksum) {
        IssueDto referenceIssue = new IssueDto();
        Long id = violationId++;
        referenceIssue.setId(id);
        referenceIssue.setKee(Long.toString(id));
        referenceIssue.setLine(lineId);
        referenceIssue.setMessage(message);
        referenceIssue.setRuleKey(ruleRepo, ruleKey);
        referenceIssue.setChecksum(lineChecksum);
        referenceIssue.setResolution(null);
        referenceIssue.setStatus(Issue.STATUS_OPEN);
        return new ServerIssueFromDb(referenceIssue);
    }

    private void initLastHashes(String reference, String newSource) throws IOException {
        DefaultInputFile inputFile = mock(DefaultInputFile.class);
        File f = temp.newFile();
        when(inputFile.path()).thenReturn(f.toPath());
        when(inputFile.file()).thenReturn(f);
        when(inputFile.charset()).thenReturn(StandardCharsets.UTF_8);
        String data = load(newSource);
        when(inputFile.lines()).thenReturn(StringUtils.countMatches(data, "\n") + 1);
        FileUtils.write(f, data, StandardCharsets.UTF_8);
        when(inputFile.key()).thenReturn("foo:Action.java");
        when(lastSnapshots.getLineHashes("foo:Action.java")).thenReturn(computeHexHashes(load(reference)));
        sourceHashHolder = new SourceHashHolder(inputFile, lastSnapshots);
    }

    private String[] computeHexHashes(String source) {
        String[] lines = source.split("\n");
        String[] hashes = new String[lines.length];
        for (int i = 0; i < lines.length; i++) {
            hashes[i] = DigestUtils.md5Hex(lines[i].replaceAll("[\t ]", ""));
        }
        return hashes;
    }
}