import org.sonar.server.computation.task.projectanalysis.issue.IssueVisitors;
import org.sonar.server.computation.task.projectanalysis.issue.IssuesRepositoryVisitor;
import org.sonar.server.computation.task.projectanalysis.issue.LoadComponentUuidsHavingOpenIssuesVisitor;
+import org.sonar.server.computation.task.projectanalysis.issue.MergeBranchTrackerExecution;
import org.sonar.server.computation.task.projectanalysis.issue.MovedIssueVisitor;
import org.sonar.server.computation.task.projectanalysis.issue.NewEffortAggregator;
import org.sonar.server.computation.task.projectanalysis.issue.NewEffortCalculator;
Tracker.class,
TrackerExecution.class,
ShortBranchTrackerExecution.class,
+ MergeBranchTrackerExecution.class,
ComponentIssuesLoader.class,
BaseIssuesLoader.class,
IssueTrackingDelegator.class,
import java.util.Map;
import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.tracking.Tracking;
import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
import org.sonar.server.computation.task.projectanalysis.component.Component;
import org.sonar.server.computation.task.projectanalysis.component.Component.Status;
issueVisitors.beforeComponent(component);
if (isIncremental(component)) {
+ // no tracking needed, simply re-use existing issues
List<DefaultIssue> issues = issuesLoader.loadForComponentUuid(component.getUuid());
- fillIncrementalOpenIssues(component, issues, cacheAppender);
+ reuseOpenIssues(component, issues, cacheAppender);
} else {
- Tracking<DefaultIssue, DefaultIssue> tracking = issueTracking.track(component);
- fillNewOpenIssues(component, tracking.getUnmatchedRaws(), cacheAppender);
- fillExistingOpenIssues(component, tracking.getMatchedRaws(), cacheAppender);
- closeUnmatchedBaseIssues(component, tracking.getUnmatchedBases(), cacheAppender);
+ TrackingResult tracking = issueTracking.track(component);
+ fillNewOpenIssues(component, tracking.newIssues(), cacheAppender);
+ fillExistingOpenIssues(component, tracking.issuesToMerge(), cacheAppender);
+ closeIssues(component, tracking.issuesToClose(), cacheAppender);
+ copyIssues(component, tracking.issuesToCopy(), cacheAppender);
}
issueVisitors.afterComponent(component);
} catch (Exception e) {
}
}
- private void fillIncrementalOpenIssues(Component component, Collection<DefaultIssue> issues, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
+ private void copyIssues(Component component, Map<DefaultIssue, DefaultIssue> matched, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
+ for (Map.Entry<DefaultIssue, DefaultIssue> entry : matched.entrySet()) {
+ DefaultIssue raw = entry.getKey();
+ DefaultIssue base = entry.getValue();
+ issueLifecycle.copyExistingOpenIssue(raw, base);
+ process(component, raw, cacheAppender);
+ }
+ }
+
+ private void reuseOpenIssues(Component component, Collection<DefaultIssue> issues, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
for (DefaultIssue issue : issues) {
process(component, issue, cacheAppender);
}
}
}
- private void closeUnmatchedBaseIssues(Component component, Iterable<DefaultIssue> issues, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
+ private void closeIssues(Component component, Iterable<DefaultIssue> issues, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
for (DefaultIssue issue : issues) {
// TODO should replace flag "beingClosed" by express call to transition "automaticClose"
issue.setBeingClosed(true);
issue.setEffort(debtCalculator.calculate(issue));
}
+ public void copyExistingOpenIssue(DefaultIssue raw, DefaultIssue base) {
+ raw.setKey(Uuids.create());
+ raw.setNew(false);
+ raw.setCopied(true);
+ raw.setType(base.type());
+ raw.setCreationDate(base.creationDate());
+ raw.setUpdateDate(base.updateDate());
+ raw.setCloseDate(base.closeDate());
+ raw.setResolution(base.resolution());
+ raw.setStatus(base.status());
+ raw.setAssignee(base.assignee());
+ raw.setAuthorLogin(base.authorLogin());
+ raw.setTags(base.tags());
+ raw.setAttributes(base.attributes());
+ raw.setEffort(debtCalculator.calculate(raw));
+ raw.setOnDisabledRule(base.isOnDisabledRule());
+ if (base.manualSeverity()) {
+ raw.setManualSeverity(true);
+ raw.setSeverity(base.severity());
+ }
+ raw.setSelectedAt(base.selectedAt());
+ }
+
public void mergeExistingOpenIssue(DefaultIssue raw, DefaultIssue base) {
raw.setNew(false);
raw.setKey(base.key());
*/
package org.sonar.server.computation.task.projectanalysis.issue;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.emptyMap;
+
+import java.util.Optional;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.tracking.Tracking;
+import org.sonar.db.component.BranchType;
import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
+import org.sonar.server.computation.task.projectanalysis.analysis.Branch;
import org.sonar.server.computation.task.projectanalysis.component.Component;
public class IssueTrackingDelegator {
private final ShortBranchTrackerExecution shortBranchTracker;
private final TrackerExecution tracker;
private final AnalysisMetadataHolder analysisMetadataHolder;
+ private final MergeBranchTrackerExecution mergeBranchTracker;
- public IssueTrackingDelegator(ShortBranchTrackerExecution shortBranchTracker, TrackerExecution tracker, AnalysisMetadataHolder analysisMetadataHolder) {
+ public IssueTrackingDelegator(ShortBranchTrackerExecution shortBranchTracker, MergeBranchTrackerExecution longBranchTracker,
+ TrackerExecution tracker, AnalysisMetadataHolder analysisMetadataHolder) {
this.shortBranchTracker = shortBranchTracker;
+ this.mergeBranchTracker = longBranchTracker;
this.tracker = tracker;
this.analysisMetadataHolder = analysisMetadataHolder;
}
- public Tracking<DefaultIssue, DefaultIssue> track(Component component) {
+ public TrackingResult track(Component component) {
if (analysisMetadataHolder.isShortLivingBranch()) {
- return shortBranchTracker.track(component);
+ return standardResult(shortBranchTracker.track(component));
+ } else if (isFirstAnalysisSecondaryLongLivingBranch()) {
+ Tracking<DefaultIssue, DefaultIssue> tracking = mergeBranchTracker.track(component);
+ return new TrackingResult(tracking.getMatchedRaws(), emptyMap(), emptyList(), tracking.getUnmatchedRaws());
} else {
- return tracker.track(component);
+ return standardResult(tracker.track(component));
+ }
+ }
+
+ private static TrackingResult standardResult(Tracking<DefaultIssue, DefaultIssue> tracking) {
+ return new TrackingResult(emptyMap(), tracking.getMatchedRaws(), tracking.getUnmatchedBases(), tracking.getUnmatchedRaws());
+ }
+
+ /**
+ * Special case where we want to do the issue tracking with the merge branch, and copy matched issue to the current branch.
+ */
+ private boolean isFirstAnalysisSecondaryLongLivingBranch() {
+ if (analysisMetadataHolder.isFirstAnalysis()) {
+ Optional<Branch> branch = analysisMetadataHolder.getBranch();
+ if (branch.isPresent()) {
+ return !branch.get().isMain() && branch.get().getType() == BranchType.LONG;
+ }
}
+ return false;
}
}
--- /dev/null
+/*
+ * 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.server.computation.task.projectanalysis.issue;
+
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.tracking.Tracker;
+import org.sonar.core.issue.tracking.Tracking;
+import org.sonar.server.computation.task.projectanalysis.component.Component;
+
+public class MergeBranchTrackerExecution {
+ private final TrackerRawInputFactory rawInputFactory;
+ private final TrackerMergeBranchInputFactory mergeInputFactory;
+ private final Tracker<DefaultIssue, DefaultIssue> tracker;
+
+ public MergeBranchTrackerExecution(TrackerRawInputFactory rawInputFactory, TrackerMergeBranchInputFactory mergeInputFactory,
+ Tracker<DefaultIssue, DefaultIssue> tracker) {
+ this.rawInputFactory = rawInputFactory;
+ this.mergeInputFactory = mergeInputFactory;
+ this.tracker = tracker;
+ }
+
+ public Tracking<DefaultIssue, DefaultIssue> track(Component component) {
+ return tracker.track(rawInputFactory.create(component), mergeInputFactory.create(component));
+ }
+}
import org.sonar.server.computation.task.projectanalysis.component.Component;
public class ShortBranchTrackerExecution {
- private TrackerBaseInputFactory baseInputFactory;
- private TrackerRawInputFactory rawInputFactory;
- private TrackerMergeBranchInputFactory mergeInputFactory;
- private Tracker<DefaultIssue, DefaultIssue> tracker;
+ private final TrackerBaseInputFactory baseInputFactory;
+ private final TrackerRawInputFactory rawInputFactory;
+ private final TrackerMergeBranchInputFactory mergeInputFactory;
+ private final Tracker<DefaultIssue, DefaultIssue> tracker;
public ShortBranchTrackerExecution(TrackerBaseInputFactory baseInputFactory, TrackerRawInputFactory rawInputFactory, TrackerMergeBranchInputFactory mergeInputFactory,
Tracker<DefaultIssue, DefaultIssue> tracker) {
--- /dev/null
+/*
+ * 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.server.computation.task.projectanalysis.issue;
+
+import java.util.Map;
+
+import org.sonar.core.issue.DefaultIssue;
+
+public class TrackingResult {
+ private final Map<DefaultIssue, DefaultIssue> issuesToCopy;
+ private final Map<DefaultIssue, DefaultIssue> issuesToMerge;
+ private final Iterable<DefaultIssue> issuesToClose;
+ private final Iterable<DefaultIssue> newIssues;
+
+ public TrackingResult(Map<DefaultIssue, DefaultIssue> issuesToCopy, Map<DefaultIssue, DefaultIssue> issuesToMerge,
+ Iterable<DefaultIssue> issuesToClose, Iterable<DefaultIssue> newIssues) {
+ this.issuesToCopy = issuesToCopy;
+ this.issuesToMerge = issuesToMerge;
+ this.issuesToClose = issuesToClose;
+ this.newIssues = newIssues;
+ }
+
+ public Map<DefaultIssue, DefaultIssue> issuesToCopy() {
+ return issuesToCopy;
+ }
+
+ public Map<DefaultIssue, DefaultIssue> issuesToMerge() {
+ return issuesToMerge;
+ }
+
+ public Iterable<DefaultIssue> issuesToClose() {
+ return issuesToClose;
+ }
+
+ public Iterable<DefaultIssue> newIssues() {
+ return newIssues;
+ }
+}
}
private boolean persistIssueIfRequired(IssueMapper mapper, DefaultIssue issue) {
- if (issue.isNew()) {
+ if (issue.isNew() || issue.isCopied()) {
persistNewIssue(mapper, issue);
return true;
}
+
if (issue.isChanged()) {
persistChangedIssue(mapper, issue);
return true;
IssueTrackingDelegator trackingDelegator;
TrackerExecution tracker;
ShortBranchTrackerExecution shortBranchTracker;
+ MergeBranchTrackerExecution mergeBranchTracker;
IssueCache issueCache;
TypeAwareVisitor underTest;
TrackerMergeBranchInputFactory mergeInputFactory = new TrackerMergeBranchInputFactory(issuesLoader, analysisMetadataHolder, dbTester.getDbClient());
tracker = new TrackerExecution(baseInputFactory, rawInputFactory, new Tracker<>());
shortBranchTracker = new ShortBranchTrackerExecution(baseInputFactory, rawInputFactory, mergeInputFactory, new Tracker<>());
+ mergeBranchTracker = new MergeBranchTrackerExecution(rawInputFactory, mergeInputFactory, new Tracker<>());
- trackingDelegator = new IssueTrackingDelegator(shortBranchTracker, tracker, analysisMetadataHolder);
+ trackingDelegator = new IssueTrackingDelegator(shortBranchTracker, mergeBranchTracker, tracker, analysisMetadataHolder);
treeRootHolder.setRoot(PROJECT);
issueCache = new IssueCache(temp.newFile(), System2.INSTANCE);
when(analysisMetadataHolder.isIncrementalAnalysis()).thenReturn(false);
assertThat(newArrayList(issueCache.traverse())).hasSize(1);
}
- @Test
- public void process_short_branch_issues() {
- //TODO
- }
-
@Test
public void process_new_issue() throws Exception {
ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
underTest.visitAny(FILE);
}
- private void addMergeIssue(RuleKey ruleKey) {
-
- }
-
private void addBaseIssue(RuleKey ruleKey) {
ComponentDto project = ComponentTesting.newPrivateProjectDto(dbTester.organizations().insert(), PROJECT_UUID).setDbKey(PROJECT_KEY);
ComponentDto file = ComponentTesting.newFileDto(project, null, FILE_UUID).setDbKey(FILE_KEY);
package org.sonar.server.computation.task.projectanalysis.issue;
import org.junit.Test;
+import org.junit.Rule;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.log.LogTester;
import org.sonar.api.utils.log.LoggerLevel;
static final int FILE_REF = 1;
static final Component FILE = builder(Component.Type.FILE, FILE_REF).setKey("FILE_KEY").setUuid("FILE_UUID").build();
- @org.junit.Rule
+ @Rule
public LogTester logTester = new LogTester();
- @org.junit.Rule
+ @Rule
public ScmInfoRepositoryRule scmInfoRepository = new ScmInfoRepositoryRule();
- @org.junit.Rule
+ @Rule
public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule().setAnalysisDate(123456789L);
ScmAccountToUser scmAccountToUser = mock(ScmAccountToUser.class);
"moduleUuid=<null>,moduleUuidPath=<null>,projectUuid=<null>,projectKey=<null>,ruleKey=<null>,language=<null>,severity=<null>," +
"manualSeverity=false,message=<null>,line=2,gap=<null>,effort=<null>,status=<null>,resolution=<null>," +
"assignee=<null>,checksum=<null>,attributes=<null>,authorLogin=<null>,comments=<null>,tags=<null>," +
- "locations=<null>,creationDate=<null>,updateDate=<null>,closeDate=<null>,currentChange=<null>,changes=<null>,isNew=true," +
+ "locations=<null>,creationDate=<null>,updateDate=<null>,closeDate=<null>,currentChange=<null>,changes=<null>,isNew=true,isCopied=false," +
"beingClosed=false,onDisabledRule=false,isChanged=false,sendNotifications=false,selectedAt=<null>]");
}
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
import static org.sonar.api.issue.Issue.STATUS_CLOSED;
assertThat(issue.updateDate()).isNotNull();
assertThat(issue.status()).isEqualTo(STATUS_OPEN);
assertThat(issue.debt()).isEqualTo(DEFAULT_DURATION);
+ assertThat(issue.isNew()).isTrue();
+ assertThat(issue.isCopied()).isFalse();
+ }
+
+ @Test
+ public void copiedIssue() throws Exception {
+ DefaultIssue raw = new DefaultIssue()
+ .setNew(true)
+ .setKey("RAW_KEY")
+ .setCreationDate(parseDate("2015-10-01"))
+ .setUpdateDate(parseDate("2015-10-02"))
+ .setCloseDate(parseDate("2015-10-03"));
+
+ DbIssues.Locations issueLocations = DbIssues.Locations.newBuilder()
+ .setTextRange(DbCommons.TextRange.newBuilder()
+ .setStartLine(10)
+ .setEndLine(12)
+ .build())
+ .build();
+ DefaultIssue base = new DefaultIssue()
+ .setKey("BASE_KEY")
+ .setCreationDate(parseDate("2015-01-01"))
+ .setUpdateDate(parseDate("2015-01-02"))
+ .setCloseDate(parseDate("2015-01-03"))
+ .setResolution(RESOLUTION_FIXED)
+ .setStatus(STATUS_CLOSED)
+ .setSeverity(BLOCKER)
+ .setAssignee("base assignee")
+ .setAuthorLogin("base author")
+ .setTags(newArrayList("base tag"))
+ .setOnDisabledRule(true)
+ .setSelectedAt(1000L)
+ .setLine(10)
+ .setMessage("message")
+ .setGap(15d)
+ .setEffort(Duration.create(15L))
+ .setManualSeverity(false)
+ .setLocations(issueLocations);
+
+ when(debtCalculator.calculate(raw)).thenReturn(DEFAULT_DURATION);
+
+ underTest.copyExistingOpenIssue(raw, base);
+
+ assertThat(raw.isNew()).isFalse();
+ assertThat(raw.isCopied()).isTrue();
+ assertThat(raw.key()).isNotNull();
+ assertThat(raw.key()).isNotEqualTo(base.key());
+ assertThat(raw.creationDate()).isEqualTo(base.creationDate());
+ assertThat(raw.updateDate()).isEqualTo(base.updateDate());
+ assertThat(raw.closeDate()).isEqualTo(base.closeDate());
+ assertThat(raw.resolution()).isEqualTo(RESOLUTION_FIXED);
+ assertThat(raw.status()).isEqualTo(STATUS_CLOSED);
+ assertThat(raw.assignee()).isEqualTo("base assignee");
+ assertThat(raw.authorLogin()).isEqualTo("base author");
+ assertThat(raw.tags()).containsOnly("base tag");
+ assertThat(raw.debt()).isEqualTo(DEFAULT_DURATION);
+ assertThat(raw.isOnDisabledRule()).isTrue();
+ assertThat(raw.selectedAt()).isEqualTo(1000L);
+
+ verifyZeroInteractions(updater);
}
@Test
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
+import java.util.Optional;
+
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.tracking.Tracking;
import org.sonar.db.component.BranchType;
import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
import org.sonar.server.computation.task.projectanalysis.analysis.Branch;
@Mock
private ShortBranchTrackerExecution shortBranchTracker;
@Mock
+ private MergeBranchTrackerExecution mergeBranchTracker;
+ @Mock
private TrackerExecution tracker;
@Mock
private AnalysisMetadataHolder analysisMetadataHolder;
@Mock
private Component component;
+ @Mock
+ private Tracking<DefaultIssue, DefaultIssue> trackingResult;
private IssueTrackingDelegator underTest;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- underTest = new IssueTrackingDelegator(shortBranchTracker, tracker, analysisMetadataHolder);
+ underTest = new IssueTrackingDelegator(shortBranchTracker, mergeBranchTracker, tracker, analysisMetadataHolder);
+ when(tracker.track(component)).thenReturn(trackingResult);
+ when(mergeBranchTracker.track(component)).thenReturn(trackingResult);
+ when(shortBranchTracker.track(component)).thenReturn(trackingResult);
}
@Test
public void delegate_regular_tracker() {
when(analysisMetadataHolder.isShortLivingBranch()).thenReturn(false);
+ when(analysisMetadataHolder.getBranch()).thenReturn(Optional.empty());
underTest.track(component);
verify(tracker).track(component);
verifyZeroInteractions(shortBranchTracker);
+ verifyZeroInteractions(mergeBranchTracker);
+ }
+
+ @Test
+ public void delegate_merge_tracker() {
+ Branch branch = mock(Branch.class);
+ when(branch.getType()).thenReturn(BranchType.LONG);
+ when(branch.isMain()).thenReturn(false);
+ when(analysisMetadataHolder.getBranch()).thenReturn(Optional.of(branch));
+ when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(true);
+
+ underTest.track(component);
+
+ verify(mergeBranchTracker).track(component);
+ verifyZeroInteractions(tracker);
+ verifyZeroInteractions(shortBranchTracker);
+
}
@Test
public void delegate_short_branch_tracker() {
Branch branch = mock(Branch.class);
when(branch.getType()).thenReturn(BranchType.SHORT);
+ when(analysisMetadataHolder.getBranch()).thenReturn(Optional.empty());
when(analysisMetadataHolder.isShortLivingBranch()).thenReturn(true);
underTest.track(component);
verify(shortBranchTracker).track(component);
verifyZeroInteractions(tracker);
-
+ verifyZeroInteractions(mergeBranchTracker);
}
}
--- /dev/null
+/*
+ * 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.server.computation.task.projectanalysis.issue;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.tracking.Input;
+import org.sonar.core.issue.tracking.Tracker;
+import org.sonar.core.issue.tracking.Tracking;
+import org.sonar.server.computation.task.projectanalysis.component.Component;
+
+public class MergeBranchTrackerExecutionTest {
+ @Mock
+ private TrackerRawInputFactory rawInputFactory;
+ @Mock
+ private TrackerMergeBranchInputFactory mergeInputFactory;
+ @Mock
+ private Tracker<DefaultIssue, DefaultIssue> tracker;
+ @Mock
+ private Component component;
+
+ private MergeBranchTrackerExecution underTest;
+
+ @Before
+ public void before() {
+ MockitoAnnotations.initMocks(this);
+ underTest = new MergeBranchTrackerExecution(rawInputFactory, mergeInputFactory, tracker);
+ }
+
+ @Test
+ public void testTracking() {
+ Input<DefaultIssue> rawInput = mock(Input.class);
+ Input<DefaultIssue> mergeInput = mock(Input.class);
+ Tracking<DefaultIssue, DefaultIssue> result = mock(Tracking.class);
+ when(rawInputFactory.create(component)).thenReturn(rawInput);
+ when(mergeInputFactory.create(component)).thenReturn(mergeInput);
+ when(tracker.track(rawInput, mergeInput)).thenReturn(result);
+
+ assertThat(underTest.track(component)).isEqualTo(result);
+ }
+}
session.close();
}
+ @Test
+ public void insert_copied_issue() {
+ RuleDefinitionDto rule = RuleTesting.newRule(RuleKey.of("xoo", "S01"));
+ dbTester.rules().insert(rule);
+ OrganizationDto organizationDto = dbTester.organizations().insert();
+ ComponentDto project = ComponentTesting.newPrivateProjectDto(organizationDto);
+ dbClient.componentDao().insert(session, project);
+ ComponentDto file = ComponentTesting.newFileDto(project, null);
+ dbClient.componentDao().insert(session, file);
+ session.commit();
+
+ issueCache.newAppender().append(new DefaultIssue()
+ .setKey("ISSUE")
+ .setType(RuleType.CODE_SMELL)
+ .setRuleKey(rule.getKey())
+ .setComponentUuid(file.uuid())
+ .setProjectUuid(project.uuid())
+ .setSeverity(Severity.BLOCKER)
+ .setStatus(Issue.STATUS_OPEN)
+ .setNew(false)
+ .setCopied(true)
+ .setType(RuleType.BUG)).close();
+
+ step.execute();
+
+ IssueDto result = dbClient.issueDao().selectOrFailByKey(session, "ISSUE");
+ assertThat(result.getKey()).isEqualTo("ISSUE");
+ assertThat(result.getRuleKey()).isEqualTo(rule.getKey());
+ assertThat(result.getComponentUuid()).isEqualTo(file.uuid());
+ assertThat(result.getProjectUuid()).isEqualTo(project.uuid());
+ assertThat(result.getSeverity()).isEqualTo(Severity.BLOCKER);
+ assertThat(result.getStatus()).isEqualTo(Issue.STATUS_OPEN);
+ assertThat(result.getType()).isEqualTo(RuleType.BUG.getDbConstant());
+ }
+
@Test
public void insert_new_issue() {
RuleDefinitionDto rule = RuleTesting.newRule(RuleKey.of("xoo", "S01"));
// all changes
private List<FieldDiffs> changes = null;
- // true if the the issue did not exist in the previous scan.
+ // true if the issue did not exist in the previous scan.
private boolean isNew = true;
- // True if the the issue did exist in the previous scan but not in the current one. That means
+ // true if the issue is being copied to a different branch
+ private boolean isCopied = false;
+
+ // True if the issue did exist in the previous scan but not in the current one. That means
// that this issue should be closed.
private boolean beingClosed = false;
return isNew;
}
+ @Override
+ public boolean isCopied() {
+ return isCopied;
+ }
+
+ public DefaultIssue setCopied(boolean b) {
+ isCopied = b;
+ return this;
+ }
+
public DefaultIssue setNew(boolean b) {
isNew = b;
return this;
issue.setStatus(Issue.STATUS_OPEN);
issue.setCloseDate(null);
issue.setNew(true);
+ issue.setCopied(false);
issue.setBeingClosed(false);
issue.setOnDisabledRule(false);
return issue;
.setNew(true)
.setBeingClosed(true)
.setOnDisabledRule(true)
+ .setCopied(true)
.setChanged(true)
.setSendNotifications(true)
.setCreationDate(new SimpleDateFormat("yyyy-MM-dd").parse("2013-08-19"))
assertThat(issue.authorLogin()).isEqualTo("steph");
assertThat(issue.checksum()).isEqualTo("c7b5db46591806455cf082bb348631e8");
assertThat(issue.isNew()).isTrue();
+ assertThat(issue.isCopied()).isTrue();
assertThat(issue.isBeingClosed()).isTrue();
assertThat(issue.isOnDisabledRule()).isTrue();
assertThat(issue.isChanged()).isTrue();
*/
boolean isNew();
+ /**
+ * During a scan returns true if the issue is copied from another branch.
+ * @since 6.6
+ */
+ boolean isCopied();
+
/**
* @deprecated since 5.5, replaced by {@link #effort()}
*/
throw unsupported();
}
+ @Override
+ public boolean isCopied() {
+ throw unsupported();
+ }
+
@Deprecated
@Override
public Duration debt() {
return false;
}
+ @Override
+ public boolean isCopied() {
+ return false;
+ }
+
@Override
public Duration debt() {
return null;
return issue.isNew();
}
+ @Override
+ public boolean isCopied() {
+ return false;
+ }
+
@Override
public Map<String, String> attributes() {
return new HashMap<>();