import org.sonar.plugins.core.dashboards.ProjectTimeMachineDashboard;
import org.sonar.plugins.core.issue.CountFalsePositivesDecorator;
import org.sonar.plugins.core.issue.CountUnresolvedIssuesDecorator;
-import org.sonar.plugins.core.issue.InitialOpenIssuesSensor;
-import org.sonar.plugins.core.issue.InitialOpenIssuesStack;
-import org.sonar.plugins.core.issue.IssueHandlers;
-import org.sonar.plugins.core.issue.IssueTracking;
-import org.sonar.plugins.core.issue.IssueTrackingDecorator;
import org.sonar.plugins.core.measurefilters.MyFavouritesFilter;
import org.sonar.plugins.core.measurefilters.ProjectFilter;
import org.sonar.plugins.core.notifications.alerts.NewAlerts;
DistributionAreaChart.class,
// issues
- IssueTrackingDecorator.class,
- IssueTracking.class,
- IssueHandlers.class,
CountUnresolvedIssuesDecorator.class,
CountFalsePositivesDecorator.class,
- InitialOpenIssuesSensor.class,
- InitialOpenIssuesStack.class,
HotspotMostViolatedRulesWidget.class,
MyUnresolvedIssuesWidget.class,
FalsePositiveIssuesWidget.class,
+++ /dev/null
-/*
- * 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.plugins.core.issue;
-
-import org.apache.commons.lang.time.DateUtils;
-import org.apache.ibatis.session.ResultContext;
-import org.apache.ibatis.session.ResultHandler;
-import org.sonar.api.batch.Sensor;
-import org.sonar.api.batch.SensorContext;
-import org.sonar.api.resources.Project;
-import org.sonar.core.issue.db.IssueChangeDao;
-import org.sonar.core.issue.db.IssueChangeDto;
-import org.sonar.core.issue.db.IssueDao;
-import org.sonar.core.issue.db.IssueDto;
-
-import java.util.Calendar;
-import java.util.Date;
-
-/**
- * Load all the issues referenced during the previous scan.
- */
-public class InitialOpenIssuesSensor implements Sensor {
-
- private final InitialOpenIssuesStack initialOpenIssuesStack;
- private final IssueDao issueDao;
- private final IssueChangeDao issueChangeDao;
-
- public InitialOpenIssuesSensor(InitialOpenIssuesStack initialOpenIssuesStack, IssueDao issueDao, IssueChangeDao issueChangeDao) {
- this.initialOpenIssuesStack = initialOpenIssuesStack;
- this.issueDao = issueDao;
- this.issueChangeDao = issueChangeDao;
- }
-
- @Override
- public boolean shouldExecuteOnProject(Project project) {
- return true;
- }
-
- @Override
- public void analyse(Project project, SensorContext context) {
- // Adding one second is a hack for resolving conflicts with concurrent user
- // changes during issue persistence
- final Date now = DateUtils.addSeconds(DateUtils.truncate(new Date(), Calendar.MILLISECOND), 1);
-
- issueDao.selectNonClosedIssuesByModule(project.getId(), new ResultHandler() {
- @Override
- public void handleResult(ResultContext rc) {
- IssueDto dto = (IssueDto) rc.getResultObject();
- dto.setSelectedAt(now.getTime());
- initialOpenIssuesStack.addIssue(dto);
- }
- });
-
- issueChangeDao.selectChangelogOnNonClosedIssuesByModuleAndType(project.getId(), new ResultHandler() {
- @Override
- public void handleResult(ResultContext rc) {
- IssueChangeDto dto = (IssueChangeDto) rc.getResultObject();
- initialOpenIssuesStack.addChangelog(dto);
- }
- });
- }
-
- @Override
- public String toString() {
- return getClass().getSimpleName();
- }
-}
+++ /dev/null
-/*
- * 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.plugins.core.issue;
-
-import java.util.Collections;
-import org.sonar.api.BatchExtension;
-import org.sonar.api.batch.InstantiationStrategy;
-import org.sonar.batch.index.Cache;
-import org.sonar.batch.index.Caches;
-import org.sonar.core.issue.db.IssueChangeDto;
-import org.sonar.core.issue.db.IssueDto;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static com.google.common.collect.Lists.newArrayList;
-
-@InstantiationStrategy(InstantiationStrategy.PER_BATCH)
-public class InitialOpenIssuesStack implements BatchExtension {
-
- private final Cache<IssueDto> issuesCache;
- private final Cache<ArrayList<IssueChangeDto>> issuesChangelogCache;
-
- public InitialOpenIssuesStack(Caches caches) {
- issuesCache = caches.createCache("last-open-issues");
- issuesChangelogCache = caches.createCache("issues-changelog");
- }
-
- public InitialOpenIssuesStack addIssue(IssueDto issueDto) {
- issuesCache.put(issueDto.getComponentKey(), issueDto.getKee(), issueDto);
- return this;
- }
-
- public List<IssueDto> selectAndRemoveIssues(String componentKey) {
- Iterable<IssueDto> issues = issuesCache.values(componentKey);
- List<IssueDto> result = newArrayList();
- for (IssueDto issue : issues) {
- result.add(issue);
- }
- issuesCache.clear(componentKey);
- return result;
- }
-
- public Iterable<IssueDto> selectAllIssues() {
- return issuesCache.values();
- }
-
- public InitialOpenIssuesStack addChangelog(IssueChangeDto issueChangeDto) {
- List<IssueChangeDto> changeDtos = issuesChangelogCache.get(issueChangeDto.getIssueKey());
- if (changeDtos == null) {
- changeDtos = newArrayList();
- }
- changeDtos.add(issueChangeDto);
- issuesChangelogCache.put(issueChangeDto.getIssueKey(), newArrayList(changeDtos));
- return this;
- }
-
- public List<IssueChangeDto> selectChangelog(String issueKey) {
- List<IssueChangeDto> changeDtos = issuesChangelogCache.get(issueKey);
- return changeDtos != null ? changeDtos : Collections.<IssueChangeDto>emptyList();
- }
-
- public void clear() {
- issuesCache.clear();
- issuesChangelogCache.clear();
- }
-}
+++ /dev/null
-/*
- * 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.plugins.core.issue;
-
-import org.sonar.api.BatchExtension;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.issue.IssueHandler;
-import org.sonar.api.issue.internal.DefaultIssue;
-import org.sonar.api.issue.internal.IssueChangeContext;
-import org.sonar.api.user.User;
-import org.sonar.core.issue.IssueUpdater;
-import org.sonar.core.user.DefaultUser;
-
-import javax.annotation.Nullable;
-
-public class IssueHandlers implements BatchExtension {
- private final IssueHandler[] handlers;
- private final DefaultContext context;
-
- public IssueHandlers(IssueUpdater updater, IssueHandler[] handlers) {
- this.handlers = handlers;
- this.context = new DefaultContext(updater);
- }
-
- public IssueHandlers(IssueUpdater updater) {
- this(updater, new IssueHandler[0]);
- }
-
- public void execute(DefaultIssue issue, IssueChangeContext changeContext) {
- context.reset(issue, changeContext);
- for (IssueHandler handler : handlers) {
- handler.onIssue(context);
- }
- }
-
- static class DefaultContext implements IssueHandler.Context {
- private final IssueUpdater updater;
- private DefaultIssue issue;
- private IssueChangeContext changeContext;
-
- private DefaultContext(IssueUpdater updater) {
- this.updater = updater;
- }
-
- private void reset(DefaultIssue i, IssueChangeContext changeContext) {
- this.issue = i;
- this.changeContext = changeContext;
- }
-
- @Override
- public Issue issue() {
- return issue;
- }
-
- @Override
- public boolean isNew() {
- return issue.isNew();
- }
-
- @Override
- public boolean isEndOfLife() {
- return issue.isEndOfLife();
- }
-
- @Override
- public IssueHandler.Context setLine(@Nullable Integer line) {
- updater.setLine(issue, line);
- return this;
- }
-
- @Override
- public IssueHandler.Context setMessage(@Nullable String s) {
- updater.setMessage(issue, s, changeContext);
- return this;
- }
-
- @Override
- public IssueHandler.Context setSeverity(String severity) {
- updater.setSeverity(issue, severity, changeContext);
- return this;
- }
-
- @Override
- public IssueHandler.Context setAuthorLogin(@Nullable String login) {
- updater.setAuthorLogin(issue, login, changeContext);
- return this;
- }
-
- @Override
- public IssueHandler.Context setEffortToFix(@Nullable Double d) {
- updater.setEffortToFix(issue, d, changeContext);
- return this;
- }
-
- @Override
- public IssueHandler.Context setAttribute(String key, @Nullable String value) {
- throw new UnsupportedOperationException("TODO");
- }
-
- @Override
- public IssueHandler.Context assign(@Nullable String assignee) {
- User user = null;
- if(assignee != null) {
- user = new DefaultUser().setLogin(assignee).setName(assignee);
- }
- updater.assign(issue, user, changeContext);
- return this;
- }
-
- @Override
- public IssueHandler.Context assign(@Nullable User user) {
- updater.assign(issue, user, changeContext);
- return this;
- }
-
- @Override
- public IssueHandler.Context addComment(String text) {
- updater.addComment(issue, text, changeContext);
- return this;
- }
- }
-
-}
+++ /dev/null
-/*
- * 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.plugins.core.issue;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Objects;
-import com.google.common.collect.LinkedHashMultimap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Multimap;
-import org.sonar.api.BatchExtension;
-import org.sonar.api.issue.internal.DefaultIssue;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.core.issue.db.IssueDto;
-import org.sonar.plugins.core.issue.tracking.FileHashes;
-import org.sonar.plugins.core.issue.tracking.IssueTrackingBlocksRecognizer;
-import org.sonar.plugins.core.issue.tracking.RollingFileHashes;
-
-import javax.annotation.Nullable;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-
-public class IssueTracking implements BatchExtension {
-
- /**
- * @param sourceHashHolder Null when working on resource that is not a file (directory/project)
- */
- public IssueTrackingResult track(@Nullable SourceHashHolder sourceHashHolder, Collection<IssueDto> dbIssues, Collection<DefaultIssue> newIssues) {
- IssueTrackingResult result = new IssueTrackingResult();
-
- if (sourceHashHolder != null) {
- setChecksumOnNewIssues(newIssues, sourceHashHolder);
- }
-
- // Map new issues with old ones
- mapIssues(newIssues, dbIssues, sourceHashHolder, result);
- return result;
- }
-
- private void setChecksumOnNewIssues(Collection<DefaultIssue> issues, SourceHashHolder sourceHashHolder) {
- if (issues.isEmpty()) {
- return;
- }
- for (DefaultIssue issue : issues) {
- Integer line = issue.line();
- if (line != null) {
- issue.setChecksum(sourceHashHolder.getHashedSource().getHash(line));
- }
- }
- }
-
- @VisibleForTesting
- void mapIssues(Collection<DefaultIssue> newIssues, @Nullable Collection<IssueDto> lastIssues, @Nullable SourceHashHolder sourceHashHolder, IssueTrackingResult result) {
- boolean hasLastScan = false;
-
- if (lastIssues != null) {
- hasLastScan = true;
- mapLastIssues(newIssues, lastIssues, result);
- }
-
- // If each new issue matches an old one we can stop the matching mechanism
- if (result.matched().size() != newIssues.size()) {
- if (sourceHashHolder != null && hasLastScan) {
- FileHashes hashedReference = sourceHashHolder.getHashedReference();
- if (hashedReference != null) {
- mapNewissues(hashedReference, sourceHashHolder.getHashedSource(), newIssues, result);
- }
- }
- mapIssuesOnSameRule(newIssues, result);
- }
- }
-
- private void mapLastIssues(Collection<DefaultIssue> newIssues, Collection<IssueDto> lastIssues, IssueTrackingResult result) {
- for (IssueDto lastIssue : lastIssues) {
- result.addUnmatched(lastIssue);
- }
-
- // Match the key of the issue. (For manual issues)
- for (DefaultIssue newIssue : newIssues) {
- mapIssue(newIssue, result.unmatchedByKeyForRule(newIssue.ruleKey()).get(newIssue.key()), result);
- }
-
- // Try first to match issues on same rule with same line and with same checksum (but not necessarily with same message)
- for (DefaultIssue newIssue : newIssues) {
- if (isNotAlreadyMapped(newIssue, result)) {
- mapIssue(
- newIssue,
- findLastIssueWithSameLineAndChecksum(newIssue, result),
- result);
- }
- }
- }
-
- private void mapNewissues(FileHashes hashedReference, FileHashes hashedSource, Collection<DefaultIssue> newIssues, IssueTrackingResult result) {
-
- IssueTrackingBlocksRecognizer rec = new IssueTrackingBlocksRecognizer(hashedReference, hashedSource);
-
- RollingFileHashes a = RollingFileHashes.create(hashedReference, 5);
- RollingFileHashes b = RollingFileHashes.create(hashedSource, 5);
-
- Multimap<Integer, DefaultIssue> newIssuesByLines = newIssuesByLines(newIssues, rec, result);
- Multimap<Integer, IssueDto> lastIssuesByLines = lastIssuesByLines(result.unmatched(), rec);
-
- Map<Integer, HashOccurrence> map = Maps.newHashMap();
-
- for (Integer line : lastIssuesByLines.keySet()) {
- int hash = a.getHash(line);
- HashOccurrence hashOccurrence = map.get(hash);
- if (hashOccurrence == null) {
- // first occurrence in A
- hashOccurrence = new HashOccurrence();
- hashOccurrence.lineA = line;
- hashOccurrence.countA = 1;
- map.put(hash, hashOccurrence);
- } else {
- hashOccurrence.countA++;
- }
- }
-
- for (Integer line : newIssuesByLines.keySet()) {
- int hash = b.getHash(line);
- HashOccurrence hashOccurrence = map.get(hash);
- if (hashOccurrence != null) {
- hashOccurrence.lineB = line;
- hashOccurrence.countB++;
- }
- }
-
- for (HashOccurrence hashOccurrence : map.values()) {
- if (hashOccurrence.countA == 1 && hashOccurrence.countB == 1) {
- // Guaranteed that lineA has been moved to lineB, so we can map all issues on lineA to all issues on lineB
- map(newIssuesByLines.get(hashOccurrence.lineB), lastIssuesByLines.get(hashOccurrence.lineA), result);
- lastIssuesByLines.removeAll(hashOccurrence.lineA);
- newIssuesByLines.removeAll(hashOccurrence.lineB);
- }
- }
-
- // Check if remaining number of lines exceeds threshold
- if (lastIssuesByLines.keySet().size() * newIssuesByLines.keySet().size() < 250000) {
- List<LinePair> possibleLinePairs = Lists.newArrayList();
- for (Integer oldLine : lastIssuesByLines.keySet()) {
- for (Integer newLine : newIssuesByLines.keySet()) {
- int weight = rec.computeLengthOfMaximalBlock(oldLine, newLine);
- possibleLinePairs.add(new LinePair(oldLine, newLine, weight));
- }
- }
- Collections.sort(possibleLinePairs, LINE_PAIR_COMPARATOR);
- for (LinePair linePair : possibleLinePairs) {
- // High probability that lineA has been moved to lineB, so we can map all Issues on lineA to all Issues on lineB
- map(newIssuesByLines.get(linePair.lineB), lastIssuesByLines.get(linePair.lineA), result);
- }
- }
- }
-
- private void mapIssuesOnSameRule(Collection<DefaultIssue> newIssues, IssueTrackingResult result) {
- // Try then to match issues on same rule with same message and with same checksum
- for (DefaultIssue newIssue : newIssues) {
- if (isNotAlreadyMapped(newIssue, result)) {
- mapIssue(
- newIssue,
- findLastIssueWithSameChecksumAndMessage(newIssue, result.unmatchedByKeyForRule(newIssue.ruleKey()).values()),
- result);
- }
- }
-
- // Try then to match issues on same rule with same line and with same message
- for (DefaultIssue newIssue : newIssues) {
- if (isNotAlreadyMapped(newIssue, result)) {
- mapIssue(
- newIssue,
- findLastIssueWithSameLineAndMessage(newIssue, result.unmatchedByKeyForRule(newIssue.ruleKey()).values()),
- result);
- }
- }
-
- // Last check: match issue if same rule and same checksum but different line and different message
- // See SONAR-2812
- for (DefaultIssue newIssue : newIssues) {
- if (isNotAlreadyMapped(newIssue, result)) {
- mapIssue(
- newIssue,
- findLastIssueWithSameChecksum(newIssue, result.unmatchedByKeyForRule(newIssue.ruleKey()).values()),
- result);
- }
- }
- }
-
- private void map(Collection<DefaultIssue> newIssues, Collection<IssueDto> lastIssues, IssueTrackingResult result) {
- for (DefaultIssue newIssue : newIssues) {
- if (isNotAlreadyMapped(newIssue, result)) {
- for (IssueDto pastIssue : lastIssues) {
- if (isNotAlreadyMapped(pastIssue, result) && Objects.equal(newIssue.ruleKey(), RuleKey.of(pastIssue.getRuleRepo(), pastIssue.getRule()))) {
- mapIssue(newIssue, pastIssue, result);
- break;
- }
- }
- }
- }
- }
-
- private Multimap<Integer, DefaultIssue> newIssuesByLines(Collection<DefaultIssue> newIssues, IssueTrackingBlocksRecognizer rec, IssueTrackingResult result) {
- Multimap<Integer, DefaultIssue> newIssuesByLines = LinkedHashMultimap.create();
- for (DefaultIssue newIssue : newIssues) {
- if (isNotAlreadyMapped(newIssue, result) && rec.isValidLineInSource(newIssue.line())) {
- newIssuesByLines.put(newIssue.line(), newIssue);
- }
- }
- return newIssuesByLines;
- }
-
- private Multimap<Integer, IssueDto> lastIssuesByLines(Collection<IssueDto> lastIssues, IssueTrackingBlocksRecognizer rec) {
- Multimap<Integer, IssueDto> lastIssuesByLines = LinkedHashMultimap.create();
- for (IssueDto pastIssue : lastIssues) {
- if (rec.isValidLineInReference(pastIssue.getLine())) {
- lastIssuesByLines.put(pastIssue.getLine(), pastIssue);
- }
- }
- return lastIssuesByLines;
- }
-
- private IssueDto findLastIssueWithSameChecksum(DefaultIssue newIssue, Collection<IssueDto> lastIssues) {
- for (IssueDto pastIssue : lastIssues) {
- if (isSameChecksum(newIssue, pastIssue)) {
- return pastIssue;
- }
- }
- return null;
- }
-
- private IssueDto findLastIssueWithSameLineAndMessage(DefaultIssue newIssue, Collection<IssueDto> lastIssues) {
- for (IssueDto pastIssue : lastIssues) {
- if (isSameLine(newIssue, pastIssue) && isSameMessage(newIssue, pastIssue)) {
- return pastIssue;
- }
- }
- return null;
- }
-
- private IssueDto findLastIssueWithSameChecksumAndMessage(DefaultIssue newIssue, Collection<IssueDto> lastIssues) {
- for (IssueDto pastIssue : lastIssues) {
- if (isSameChecksum(newIssue, pastIssue) && isSameMessage(newIssue, pastIssue)) {
- return pastIssue;
- }
- }
- return null;
- }
-
- private IssueDto findLastIssueWithSameLineAndChecksum(DefaultIssue newIssue, IssueTrackingResult result) {
- Collection<IssueDto> sameRuleAndSameLineAndSameChecksum = result.unmatchedForRuleAndForLineAndForChecksum(newIssue.ruleKey(), newIssue.line(), newIssue.checksum());
- if (!sameRuleAndSameLineAndSameChecksum.isEmpty()) {
- return sameRuleAndSameLineAndSameChecksum.iterator().next();
- }
- return null;
- }
-
- private boolean isNotAlreadyMapped(IssueDto pastIssue, IssueTrackingResult result) {
- return result.unmatched().contains(pastIssue);
- }
-
- private boolean isNotAlreadyMapped(DefaultIssue newIssue, IssueTrackingResult result) {
- return !result.isMatched(newIssue);
- }
-
- private boolean isSameChecksum(DefaultIssue newIssue, IssueDto pastIssue) {
- return Objects.equal(pastIssue.getChecksum(), newIssue.checksum());
- }
-
- private boolean isSameLine(DefaultIssue newIssue, IssueDto pastIssue) {
- return Objects.equal(pastIssue.getLine(), newIssue.line());
- }
-
- private boolean isSameMessage(DefaultIssue newIssue, IssueDto pastIssue) {
- return Objects.equal(newIssue.message(), pastIssue.getMessage());
- }
-
- private void mapIssue(DefaultIssue issue, @Nullable IssueDto ref, IssueTrackingResult result) {
- if (ref != null) {
- result.setMatch(issue, ref);
- }
- }
-
- @Override
- public String toString() {
- return getClass().getSimpleName();
- }
-
- private static class LinePair {
- int lineA;
- int lineB;
- int weight;
-
- public LinePair(int lineA, int lineB, int weight) {
- this.lineA = lineA;
- this.lineB = lineB;
- this.weight = weight;
- }
- }
-
- private static class HashOccurrence {
- int lineA;
- int lineB;
- int countA;
- int countB;
- }
-
- private static final Comparator<LinePair> LINE_PAIR_COMPARATOR = new Comparator<LinePair>() {
- @Override
- public int compare(LinePair o1, LinePair o2) {
- int weightDiff = o2.weight - o1.weight;
- if (weightDiff != 0) {
- return weightDiff;
- } else {
- return Math.abs(o1.lineA - o1.lineB) - Math.abs(o2.lineA - o2.lineB);
- }
- }
- };
-
-}
+++ /dev/null
-/*
- * 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.plugins.core.issue;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Strings;
-import com.google.common.collect.Lists;
-import org.apache.commons.lang.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.api.batch.Decorator;
-import org.sonar.api.batch.DecoratorBarriers;
-import org.sonar.api.batch.DecoratorContext;
-import org.sonar.api.batch.DependedUpon;
-import org.sonar.api.batch.DependsUpon;
-import org.sonar.api.batch.fs.InputFile;
-import org.sonar.api.batch.fs.internal.DefaultInputFile;
-import org.sonar.api.component.ResourcePerspectives;
-import org.sonar.api.issue.Issuable;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.issue.internal.DefaultIssue;
-import org.sonar.api.issue.internal.IssueChangeContext;
-import org.sonar.api.profiles.RulesProfile;
-import org.sonar.api.resources.File;
-import org.sonar.api.resources.Project;
-import org.sonar.api.resources.Resource;
-import org.sonar.api.resources.ResourceUtils;
-import org.sonar.api.rules.ActiveRule;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleFinder;
-import org.sonar.api.utils.Duration;
-import org.sonar.api.utils.KeyValueFormat;
-import org.sonar.batch.issue.IssueCache;
-import org.sonar.batch.scan.LastLineHashes;
-import org.sonar.batch.scan.filesystem.InputPathCache;
-import org.sonar.core.issue.IssueUpdater;
-import org.sonar.core.issue.db.IssueChangeDto;
-import org.sonar.core.issue.db.IssueDto;
-import org.sonar.core.issue.workflow.IssueWorkflow;
-
-import java.util.Collection;
-
-@DependsUpon(DecoratorBarriers.ISSUES_ADDED)
-@DependedUpon(DecoratorBarriers.ISSUES_TRACKED)
-public class IssueTrackingDecorator implements Decorator {
-
- private static final Logger LOG = LoggerFactory.getLogger(IssueTrackingDecorator.class);
-
- private final IssueCache issueCache;
- private final InitialOpenIssuesStack initialOpenIssues;
- private final IssueTracking tracking;
- private final LastLineHashes lastLineHashes;
- private final IssueHandlers handlers;
- private final IssueWorkflow workflow;
- private final IssueUpdater updater;
- private final IssueChangeContext changeContext;
- private final ResourcePerspectives perspectives;
- private final RulesProfile rulesProfile;
- private final RuleFinder ruleFinder;
- private final InputPathCache inputPathCache;
- private final Project project;
-
- public IssueTrackingDecorator(IssueCache issueCache, InitialOpenIssuesStack initialOpenIssues, IssueTracking tracking,
- LastLineHashes lastLineHashes,
- IssueHandlers handlers, IssueWorkflow workflow,
- IssueUpdater updater,
- Project project,
- ResourcePerspectives perspectives,
- RulesProfile rulesProfile,
- RuleFinder ruleFinder, InputPathCache inputPathCache) {
- this.issueCache = issueCache;
- this.initialOpenIssues = initialOpenIssues;
- this.tracking = tracking;
- this.lastLineHashes = lastLineHashes;
- this.handlers = handlers;
- this.workflow = workflow;
- this.updater = updater;
- this.project = project;
- this.inputPathCache = inputPathCache;
- this.changeContext = IssueChangeContext.createScan(project.getAnalysisDate());
- this.perspectives = perspectives;
- this.rulesProfile = rulesProfile;
- this.ruleFinder = ruleFinder;
- }
-
- @Override
- public boolean shouldExecuteOnProject(Project project) {
- return true;
- }
-
- @Override
- public void decorate(Resource resource, DecoratorContext context) {
- Issuable issuable = perspectives.as(Issuable.class, resource);
- if (issuable != null) {
- doDecorate(resource);
- }
- }
-
- @VisibleForTesting
- void doDecorate(Resource resource) {
- Collection<DefaultIssue> issues = Lists.newArrayList();
- for (Issue issue : issueCache.byComponent(resource.getEffectiveKey())) {
- issues.add((DefaultIssue) issue);
- }
- issueCache.clear(resource.getEffectiveKey());
- // issues = all the issues created by rule engines during this module scan and not excluded by filters
-
- // all the issues that are not closed in db before starting this module scan, including manual issues
- Collection<IssueDto> dbOpenIssues = initialOpenIssues.selectAndRemoveIssues(resource.getEffectiveKey());
-
- SourceHashHolder sourceHashHolder = null;
- if (ResourceUtils.isFile(resource)) {
- File sonarFile = (File) resource;
- InputFile file = inputPathCache.getFile(project.getEffectiveKey(), sonarFile.getPath());
- if (file == null) {
- throw new IllegalStateException("Resource " + resource + " was not found in InputPath cache");
- }
- sourceHashHolder = new SourceHashHolder((DefaultInputFile) file, lastLineHashes);
- }
-
- IssueTrackingResult trackingResult = tracking.track(sourceHashHolder, dbOpenIssues, issues);
-
- // unmatched = issues that have been resolved + issues on disabled/removed rules + manual issues
- addUnmatched(trackingResult.unmatched(), sourceHashHolder, issues);
-
- mergeMatched(trackingResult);
-
- if (ResourceUtils.isProject(resource)) {
- // issues that relate to deleted components
- addIssuesOnDeletedComponents(issues);
- }
-
- for (DefaultIssue issue : issues) {
- workflow.doAutomaticTransition(issue, changeContext);
- handlers.execute(issue, changeContext);
- issueCache.put(issue);
- }
- }
-
- @VisibleForTesting
- protected void mergeMatched(IssueTrackingResult result) {
- for (DefaultIssue issue : result.matched()) {
- IssueDto ref = result.matching(issue);
-
- // invariant fields
- issue.setKey(ref.getKee());
- issue.setCreationDate(ref.getIssueCreationDate());
- issue.setUpdateDate(ref.getIssueUpdateDate());
- issue.setCloseDate(ref.getIssueCloseDate());
-
- // non-persisted fields
- issue.setNew(false);
- issue.setEndOfLife(false);
- issue.setOnDisabledRule(false);
- issue.setSelectedAt(ref.getSelectedAt());
-
- // fields to update with old values
- issue.setActionPlanKey(ref.getActionPlanKey());
- issue.setResolution(ref.getResolution());
- issue.setStatus(ref.getStatus());
- issue.setAssignee(ref.getAssignee());
- issue.setAuthorLogin(ref.getAuthorLogin());
- issue.setTags(ref.getTags());
-
- if (ref.getIssueAttributes() != null) {
- issue.setAttributes(KeyValueFormat.parse(ref.getIssueAttributes()));
- }
-
- // populate existing changelog
- Collection<IssueChangeDto> issueChangeDtos = initialOpenIssues.selectChangelog(issue.key());
- for (IssueChangeDto issueChangeDto : issueChangeDtos) {
- issue.addChange(issueChangeDto.toFieldDiffs());
- }
-
- // fields to update with current values
- if (ref.isManualSeverity()) {
- issue.setManualSeverity(true);
- issue.setSeverity(ref.getSeverity());
- } else {
- updater.setPastSeverity(issue, ref.getSeverity(), changeContext);
- }
- updater.setPastLine(issue, ref.getLine());
- updater.setPastMessage(issue, ref.getMessage(), changeContext);
- updater.setPastEffortToFix(issue, ref.getEffortToFix(), changeContext);
- Long debtInMinutes = ref.getDebt();
- Duration previousTechnicalDebt = debtInMinutes != null ? Duration.create(debtInMinutes) : null;
- updater.setPastTechnicalDebt(issue, previousTechnicalDebt, changeContext);
- updater.setPastProject(issue, ref.getProjectKey(), changeContext);
- }
- }
-
- private void addUnmatched(Collection<IssueDto> unmatchedIssues, SourceHashHolder sourceHashHolder, Collection<DefaultIssue> issues) {
- for (IssueDto unmatchedDto : unmatchedIssues) {
- DefaultIssue unmatched = unmatchedDto.toDefaultIssue();
- if (StringUtils.isNotBlank(unmatchedDto.getReporter()) && !Issue.STATUS_CLOSED.equals(unmatchedDto.getStatus())) {
- relocateManualIssue(unmatched, unmatchedDto, sourceHashHolder);
- }
- updateUnmatchedIssue(unmatched, false /* manual issues can be kept open */);
- issues.add(unmatched);
- }
- }
-
- private void addIssuesOnDeletedComponents(Collection<DefaultIssue> issues) {
- for (IssueDto deadDto : initialOpenIssues.selectAllIssues()) {
- DefaultIssue dead = deadDto.toDefaultIssue();
- updateUnmatchedIssue(dead, true);
- issues.add(dead);
- }
- initialOpenIssues.clear();
- }
-
- private void updateUnmatchedIssue(DefaultIssue issue, boolean forceEndOfLife) {
- issue.setNew(false);
-
- boolean manualIssue = !Strings.isNullOrEmpty(issue.reporter());
- Rule rule = ruleFinder.findByKey(issue.ruleKey());
- if (manualIssue) {
- // Manual rules are not declared in Quality profiles, so no need to check ActiveRule
- boolean isRemovedRule = rule == null || Rule.STATUS_REMOVED.equals(rule.getStatus());
- issue.setEndOfLife(forceEndOfLife || isRemovedRule);
- issue.setOnDisabledRule(isRemovedRule);
- } else {
- ActiveRule activeRule = rulesProfile.getActiveRule(issue.ruleKey().repository(), issue.ruleKey().rule());
- issue.setEndOfLife(true);
- issue.setOnDisabledRule(activeRule == null || rule == null || Rule.STATUS_REMOVED.equals(rule.getStatus()));
- }
- }
-
- private void relocateManualIssue(DefaultIssue newIssue, IssueDto oldIssue, SourceHashHolder sourceHashHolder) {
- LOG.debug("Trying to relocate manual issue {}", oldIssue.getKee());
-
- Integer previousLine = oldIssue.getLine();
- if (previousLine == null) {
- LOG.debug("Cannot relocate issue at resource level");
- return;
- }
-
- Collection<Integer> newLinesWithSameHash = sourceHashHolder.getNewLinesMatching(previousLine);
- LOG.debug("Found the following lines with same hash: {}", newLinesWithSameHash);
- if (newLinesWithSameHash.isEmpty()) {
- if (previousLine > sourceHashHolder.getHashedSource().length()) {
- LOG.debug("Old issue line {} is out of new source, closing and removing line number", previousLine);
- newIssue.setLine(null);
- updater.setStatus(newIssue, Issue.STATUS_CLOSED, changeContext);
- updater.setResolution(newIssue, Issue.RESOLUTION_REMOVED, changeContext);
- updater.setPastLine(newIssue, previousLine);
- updater.setPastMessage(newIssue, oldIssue.getMessage(), changeContext);
- updater.setPastEffortToFix(newIssue, oldIssue.getEffortToFix(), changeContext);
- }
- } else if (newLinesWithSameHash.size() == 1) {
- Integer newLine = newLinesWithSameHash.iterator().next();
- LOG.debug("Relocating issue to line {}", newLine);
-
- newIssue.setLine(newLine);
- updater.setPastLine(newIssue, previousLine);
- updater.setPastMessage(newIssue, oldIssue.getMessage(), changeContext);
- updater.setPastEffortToFix(newIssue, oldIssue.getEffortToFix(), changeContext);
- }
- }
-}
+++ /dev/null
-/*
- * 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.plugins.core.issue;
-
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Multimap;
-import org.apache.commons.lang.StringUtils;
-import org.sonar.api.issue.internal.DefaultIssue;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.core.issue.db.IssueDto;
-
-import javax.annotation.Nullable;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-class IssueTrackingResult {
- private final Map<String, IssueDto> unmatchedByKey = new HashMap<String, IssueDto>();
- private final Map<RuleKey, Map<String, IssueDto>> unmatchedByRuleAndKey = new HashMap<RuleKey, Map<String, IssueDto>>();
- private final Map<RuleKey, Map<Integer, Multimap<String, IssueDto>>> unmatchedByRuleAndLineAndChecksum =
- new HashMap<RuleKey, Map<Integer, Multimap<String, IssueDto>>>();
- private final Map<DefaultIssue, IssueDto> matched = Maps.newIdentityHashMap();
-
- Collection<IssueDto> unmatched() {
- return unmatchedByKey.values();
- }
-
- Map<String, IssueDto> unmatchedByKeyForRule(RuleKey ruleKey) {
- return unmatchedByRuleAndKey.containsKey(ruleKey) ? unmatchedByRuleAndKey.get(ruleKey) : Collections.<String, IssueDto>emptyMap();
- }
-
- Collection<IssueDto> unmatchedForRuleAndForLineAndForChecksum(RuleKey ruleKey, @Nullable Integer line, @Nullable String checksum) {
- if (!unmatchedByRuleAndLineAndChecksum.containsKey(ruleKey)) {
- return Collections.emptyList();
- }
- Map<Integer, Multimap<String, IssueDto>> unmatchedForRule = unmatchedByRuleAndLineAndChecksum.get(ruleKey);
- Integer lineNotNull = line != null ? line : 0;
- if (!unmatchedForRule.containsKey(lineNotNull)) {
- return Collections.emptyList();
- }
- Multimap<String, IssueDto> unmatchedForRuleAndLine = unmatchedForRule.get(lineNotNull);
- String checksumNotNull = StringUtils.defaultString(checksum, "");
- if (!unmatchedForRuleAndLine.containsKey(checksumNotNull)) {
- return Collections.emptyList();
- }
- return unmatchedForRuleAndLine.get(checksumNotNull);
- }
-
- Collection<DefaultIssue> matched() {
- return matched.keySet();
- }
-
- boolean isMatched(DefaultIssue issue) {
- return matched.containsKey(issue);
- }
-
- IssueDto matching(DefaultIssue issue) {
- return matched.get(issue);
- }
-
- void addUnmatched(IssueDto i) {
- unmatchedByKey.put(i.getKee(), i);
- RuleKey ruleKey = RuleKey.of(i.getRuleRepo(), i.getRule());
- if (!unmatchedByRuleAndKey.containsKey(ruleKey)) {
- unmatchedByRuleAndKey.put(ruleKey, new HashMap<String, IssueDto>());
- unmatchedByRuleAndLineAndChecksum.put(ruleKey, new HashMap<Integer, Multimap<String, IssueDto>>());
- }
- unmatchedByRuleAndKey.get(ruleKey).put(i.getKee(), i);
- Map<Integer, Multimap<String, IssueDto>> unmatchedForRule = unmatchedByRuleAndLineAndChecksum.get(ruleKey);
- Integer lineNotNull = lineNotNull(i);
- if (!unmatchedForRule.containsKey(lineNotNull)) {
- unmatchedForRule.put(lineNotNull, HashMultimap.<String, IssueDto>create());
- }
- Multimap<String, IssueDto> unmatchedForRuleAndLine = unmatchedForRule.get(lineNotNull);
- String checksumNotNull = StringUtils.defaultString(i.getChecksum(), "");
- unmatchedForRuleAndLine.put(checksumNotNull, i);
- }
-
- private Integer lineNotNull(IssueDto i) {
- Integer line = i.getLine();
- return line != null ? line : 0;
- }
-
- void setMatch(DefaultIssue issue, IssueDto matching) {
- matched.put(issue, matching);
- RuleKey ruleKey = RuleKey.of(matching.getRuleRepo(), matching.getRule());
- unmatchedByRuleAndKey.get(ruleKey).remove(matching.getKee());
- unmatchedByKey.remove(matching.getKee());
- Integer lineNotNull = lineNotNull(matching);
- String checksumNotNull = StringUtils.defaultString(matching.getChecksum(), "");
- unmatchedByRuleAndLineAndChecksum.get(ruleKey).get(lineNotNull).get(checksumNotNull).remove(matching);
- }
-}
+++ /dev/null
-/*
- * 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.plugins.core.issue;
-
-import com.google.common.collect.ImmutableSet;
-import org.sonar.api.batch.fs.InputFile.Status;
-import org.sonar.api.batch.fs.internal.DefaultInputFile;
-import org.sonar.batch.scan.LastLineHashes;
-import org.sonar.plugins.core.issue.tracking.FileHashes;
-
-import javax.annotation.CheckForNull;
-
-import java.util.Collection;
-
-public class SourceHashHolder {
-
- private final LastLineHashes lastSnapshots;
-
- private FileHashes hashedReference;
- private FileHashes hashedSource;
- private DefaultInputFile inputFile;
-
- public SourceHashHolder(DefaultInputFile inputFile, LastLineHashes lastSnapshots) {
- this.inputFile = inputFile;
- this.lastSnapshots = lastSnapshots;
- }
-
- private void initHashes() {
- if (hashedSource == null) {
- hashedSource = FileHashes.create(inputFile.lineHashes());
- Status status = inputFile.status();
- if (status == Status.ADDED) {
- hashedReference = null;
- } else if (status == Status.SAME) {
- hashedReference = hashedSource;
- } else {
- String[] lineHashes = lastSnapshots.getLineHashes(inputFile.key());
- hashedReference = lineHashes != null ? FileHashes.create(lineHashes) : null;
- }
- }
- }
-
- @CheckForNull
- public FileHashes getHashedReference() {
- initHashes();
- return hashedReference;
- }
-
- public FileHashes getHashedSource() {
- initHashes();
- return hashedSource;
- }
-
- public Collection<Integer> getNewLinesMatching(Integer originLine) {
- FileHashes reference = getHashedReference();
- if (reference == null) {
- return ImmutableSet.of();
- } else {
- return getHashedSource().getLinesForHash(reference.getHash(originLine));
- }
- }
-}
+++ /dev/null
-/*
- * 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.plugins.core.issue.tracking;
-
-import com.google.common.collect.LinkedHashMultimap;
-import com.google.common.collect.Multimap;
-import org.apache.commons.codec.binary.Hex;
-import org.apache.commons.lang.ObjectUtils;
-
-import java.util.Collection;
-
-/**
- * Wraps a {@link Sequence} to assign hash codes to elements.
- */
-public final class FileHashes {
-
- private final String[] hashes;
- private final Multimap<String, Integer> linesByHash;
-
- private FileHashes(String[] hashes, Multimap<String, Integer> linesByHash) {
- this.hashes = hashes;
- this.linesByHash = linesByHash;
- }
-
- public static FileHashes create(String[] hashes) {
- int size = hashes.length;
- Multimap<String, Integer> linesByHash = LinkedHashMultimap.create();
- for (int i = 0; i < size; i++) {
- // indices in array are shifted one line before
- linesByHash.put(hashes[i], i + 1);
- }
- return new FileHashes(hashes, linesByHash);
- }
-
- public static FileHashes create(byte[][] hashes) {
- int size = hashes.length;
- Multimap<String, Integer> linesByHash = LinkedHashMultimap.create();
- String[] hexHashes = new String[size];
- for (int i = 0; i < size; i++) {
- String hash = hashes[i] != null ? Hex.encodeHexString(hashes[i]) : "";
- hexHashes[i] = hash;
- // indices in array are shifted one line before
- linesByHash.put(hash, i + 1);
- }
- return new FileHashes(hexHashes, linesByHash);
- }
-
- public int length() {
- return hashes.length;
- }
-
- public Collection<Integer> getLinesForHash(String hash) {
- return linesByHash.get(hash);
- }
-
- public String getHash(int line) {
- // indices in array are shifted one line before
- return (String) ObjectUtils.defaultIfNull(hashes[line - 1], "");
- }
-}
+++ /dev/null
-/*
- * 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.plugins.core.issue.tracking;
-
-import javax.annotation.Nullable;
-
-public class IssueTrackingBlocksRecognizer {
-
- private final FileHashes a;
- private final FileHashes b;
-
- public IssueTrackingBlocksRecognizer(FileHashes a, FileHashes b) {
- this.a = a;
- this.b = b;
- }
-
- public boolean isValidLineInReference(@Nullable Integer line) {
- return (line != null) && (0 <= line - 1) && (line - 1 < a.length());
- }
-
- public boolean isValidLineInSource(@Nullable Integer line) {
- return (line != null) && (0 <= line - 1) && (line - 1 < b.length());
- }
-
- /**
- * @param startA number of line from first version of text (numbering starts from 1)
- * @param startB number of line from second version of text (numbering starts from 1)
- */
- public int computeLengthOfMaximalBlock(int startA, int startB) {
- if (!a.getHash(startA).equals(b.getHash(startB))) {
- return 0;
- }
- int length = 0;
- int ai = startA;
- int bi = startB;
- while (ai <= a.length() && bi <= b.length() && a.getHash(ai).equals(b.getHash(bi))) {
- ai++;
- bi++;
- length++;
- }
- ai = startA;
- bi = startB;
- while (ai > 0 && bi > 0 && a.getHash(ai).equals(b.getHash(bi))) {
- ai--;
- bi--;
- length++;
- }
- // Note that position (startA, startB) was counted twice
- return length - 1;
- }
-
-}
+++ /dev/null
-/*
- * 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.plugins.core.issue.tracking;
-
-/**
- * Compute hashes of block around each line
- */
-public class RollingFileHashes {
-
- final int[] rollingHashes;
-
- public static RollingFileHashes create(FileHashes hashes, int halfBlockSize) {
- int size = hashes.length();
- int[] rollingHashes = new int[size];
-
- RollingHashCalculator hashCalulator = new RollingHashCalculator(halfBlockSize * 2 + 1);
- for (int i = 1; i <= Math.min(size, halfBlockSize + 1); i++) {
- hashCalulator.add(hashes.getHash(i).hashCode());
- }
- for (int i = 1; i <= size; i++) {
- rollingHashes[i - 1] = hashCalulator.getHash();
- if (i - halfBlockSize > 0) {
- hashCalulator.remove(hashes.getHash(i - halfBlockSize).hashCode());
- }
- if (i + 1 + halfBlockSize <= size) {
- hashCalulator.add(hashes.getHash(i + 1 + halfBlockSize).hashCode());
- } else {
- hashCalulator.add(0);
- }
- }
-
- return new RollingFileHashes(rollingHashes);
- }
-
- public int getHash(int line) {
- return rollingHashes[line - 1];
- }
-
- private RollingFileHashes(int[] hashes) {
- this.rollingHashes = hashes;
- }
-
- private static class RollingHashCalculator {
-
- private static final int PRIME_BASE = 31;
-
- private final int power;
- private int hash;
-
- public RollingHashCalculator(int size) {
- int pow = 1;
- for (int i = 0; i < size - 1; i++) {
- pow = pow * PRIME_BASE;
- }
- this.power = pow;
- }
-
- public void add(int value) {
- hash = hash * PRIME_BASE + value;
- }
-
- public void remove(int value) {
- hash = hash - power * value;
- }
-
- public int getHash() {
- return hash;
- }
-
- }
-
-}
+++ /dev/null
-/*
- * 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.
- */
-
-@javax.annotation.ParametersAreNonnullByDefault
-package org.sonar.plugins.core.issue.tracking;
-
+++ /dev/null
-/*
- * 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.plugins.core.issue;
-
-import org.apache.ibatis.session.ResultHandler;
-import org.junit.Test;
-import org.sonar.api.resources.Project;
-import org.sonar.core.issue.db.IssueChangeDao;
-import org.sonar.core.issue.db.IssueDao;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-public class InitialOpenIssuesSensorTest {
-
- InitialOpenIssuesStack stack = mock(InitialOpenIssuesStack.class);
- IssueDao issueDao = mock(IssueDao.class);
- IssueChangeDao issueChangeDao = mock(IssueChangeDao.class);
-
- InitialOpenIssuesSensor sensor = new InitialOpenIssuesSensor(stack, issueDao, issueChangeDao);
-
- @Test
- public void should_select_module_open_issues() {
- Project project = new Project("key");
- project.setId(1);
- sensor.analyse(project, null);
-
- verify(issueDao).selectNonClosedIssuesByModule(eq(1), any(ResultHandler.class));
- }
-
- @Test
- public void should_select_module_open_issues_changelog() {
- Project project = new Project("key");
- project.setId(1);
- sensor.analyse(project, null);
-
- verify(issueChangeDao).selectChangelogOnNonClosedIssuesByModuleAndType(eq(1), any(ResultHandler.class));
- }
-
- @Test
- public void test_toString() throws Exception {
- assertThat(sensor.toString()).isEqualTo("InitialOpenIssuesSensor");
-
- }
-}
+++ /dev/null
-/*
- * 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.plugins.core.issue;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.api.CoreProperties;
-import org.sonar.batch.bootstrap.BootstrapProperties;
-import org.sonar.batch.bootstrap.TempFolderProvider;
-import org.sonar.batch.index.Caches;
-import org.sonar.core.issue.db.IssueChangeDto;
-import org.sonar.core.issue.db.IssueDto;
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class InitialOpenIssuesStackTest {
-
- @ClassRule
- public static TemporaryFolder temp = new TemporaryFolder();
-
- public static Caches createCacheOnTemp(TemporaryFolder temp) {
- BootstrapProperties bootstrapSettings = new BootstrapProperties(Collections.<String, String>emptyMap());
- try {
- bootstrapSettings.properties().put(CoreProperties.WORKING_DIRECTORY, temp.newFolder().getAbsolutePath());
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- return new Caches(new TempFolderProvider().provide(bootstrapSettings));
- }
-
- InitialOpenIssuesStack stack;
- Caches caches;
-
- @Before
- public void before() throws Exception {
- caches = createCacheOnTemp(temp);
- caches.start();
- stack = new InitialOpenIssuesStack(caches);
- }
-
- @After
- public void after() {
- caches.stop();
- }
-
- @Test
- public void get_and_remove_issues() {
- IssueDto issueDto = new IssueDto().setComponentKey("org.struts.Action").setKee("ISSUE-1");
- stack.addIssue(issueDto);
-
- List<IssueDto> issueDtos = stack.selectAndRemoveIssues("org.struts.Action");
- assertThat(issueDtos).hasSize(1);
- assertThat(issueDtos.get(0).getKee()).isEqualTo("ISSUE-1");
-
- assertThat(stack.selectAllIssues()).isEmpty();
- }
-
- @Test
- public void get_and_remove_with_many_issues_on_same_resource() {
- stack.addIssue(new IssueDto().setComponentKey("org.struts.Action").setKee("ISSUE-1"));
- stack.addIssue(new IssueDto().setComponentKey("org.struts.Action").setKee("ISSUE-2"));
-
- List<IssueDto> issueDtos = stack.selectAndRemoveIssues("org.struts.Action");
- assertThat(issueDtos).hasSize(2);
-
- assertThat(stack.selectAllIssues()).isEmpty();
- }
-
- @Test
- public void get_and_remove_do_nothing_if_resource_not_found() {
- stack.addIssue(new IssueDto().setComponentKey("org.struts.Action").setKee("ISSUE-1"));
-
- List<IssueDto> issueDtos = stack.selectAndRemoveIssues("Other");
- assertThat(issueDtos).hasSize(0);
-
- assertThat(stack.selectAllIssues()).hasSize(1);
- }
-
- @Test
- public void select_changelog() {
- stack.addChangelog(new IssueChangeDto().setKey("CHANGE-1").setIssueKey("ISSUE-1"));
- stack.addChangelog(new IssueChangeDto().setKey("CHANGE-2").setIssueKey("ISSUE-1"));
-
- List<IssueChangeDto> issueChangeDtos = stack.selectChangelog("ISSUE-1");
- assertThat(issueChangeDtos).hasSize(2);
- assertThat(issueChangeDtos.get(0).getKey()).isEqualTo("CHANGE-1");
- assertThat(issueChangeDtos.get(1).getKey()).isEqualTo("CHANGE-2");
- }
-
- @Test
- public void return_empty_changelog() {
- assertThat(stack.selectChangelog("ISSUE-1")).isEmpty();
- }
-
- @Test
- public void clear_issues() {
- stack.addIssue(new IssueDto().setComponentKey("org.struts.Action").setKee("ISSUE-1"));
-
- assertThat(stack.selectAllIssues()).hasSize(1);
-
- // issues are not removed
- assertThat(stack.selectAllIssues()).hasSize(1);
-
- stack.clear();
- assertThat(stack.selectAllIssues()).isEmpty();
- }
-
- @Test
- public void clear_issues_changelog() {
- stack.addChangelog(new IssueChangeDto().setKey("CHANGE-1").setIssueKey("ISSUE-1"));
-
- assertThat(stack.selectChangelog("ISSUE-1")).hasSize(1);
-
- stack.clear();
- assertThat(stack.selectChangelog("ISSUE-1")).isEmpty();
- }
-}
+++ /dev/null
-/*
- * 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.plugins.core.issue;
-
-import org.junit.Test;
-import org.mockito.ArgumentMatcher;
-import org.sonar.api.issue.IssueHandler;
-import org.sonar.api.issue.internal.DefaultIssue;
-import org.sonar.api.issue.internal.IssueChangeContext;
-import org.sonar.core.issue.IssueUpdater;
-
-import java.util.Date;
-
-import static org.mockito.Matchers.argThat;
-import static org.mockito.Mockito.*;
-
-public class IssueHandlersTest {
- @Test
- public void should_execute_handlers() throws Exception {
- IssueHandler h1 = mock(IssueHandler.class);
- IssueHandler h2 = mock(IssueHandler.class);
- IssueUpdater updater = mock(IssueUpdater.class);
-
- IssueHandlers handlers = new IssueHandlers(updater, new IssueHandler[]{h1, h2});
- final DefaultIssue issue = new DefaultIssue();
- handlers.execute(issue, IssueChangeContext.createScan(new Date()));
-
- verify(h1).onIssue(argThat(new ArgumentMatcher<IssueHandler.Context>() {
- @Override
- public boolean matches(Object o) {
- return ((IssueHandler.Context) o).issue() == issue;
- }
- }));
- }
-
- @Test
- public void test_no_handlers() {
- IssueUpdater updater = mock(IssueUpdater.class);
- IssueHandlers handlers = new IssueHandlers(updater);
- handlers.execute(new DefaultIssue(), IssueChangeContext.createScan(new Date()));
- verifyZeroInteractions(updater);
- }
-}
+++ /dev/null
-/*
- * 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.plugins.core.issue;
-
-import org.apache.commons.codec.digest.DigestUtils;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatcher;
-import org.sonar.api.batch.DecoratorContext;
-import org.sonar.api.batch.fs.internal.DefaultInputFile;
-import org.sonar.api.component.ResourcePerspectives;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.issue.internal.DefaultIssue;
-import org.sonar.api.issue.internal.IssueChangeContext;
-import org.sonar.api.profiles.RulesProfile;
-import org.sonar.api.resources.File;
-import org.sonar.api.resources.Project;
-import org.sonar.api.resources.Resource;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleFinder;
-import org.sonar.api.utils.Duration;
-import org.sonar.api.utils.System2;
-import org.sonar.batch.issue.IssueCache;
-import org.sonar.batch.scan.LastLineHashes;
-import org.sonar.batch.scan.filesystem.InputPathCache;
-import org.sonar.core.issue.IssueUpdater;
-import org.sonar.core.issue.db.IssueChangeDto;
-import org.sonar.core.issue.db.IssueDto;
-import org.sonar.core.issue.workflow.IssueWorkflow;
-import org.sonar.java.api.JavaClass;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-import static com.google.common.collect.Lists.newArrayList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyCollection;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.argThat;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isA;
-import static org.mockito.Mockito.*;
-
-public class IssueTrackingDecoratorTest {
-
- IssueTrackingDecorator decorator;
- IssueCache issueCache = mock(IssueCache.class, RETURNS_MOCKS);
- InitialOpenIssuesStack initialOpenIssues = mock(InitialOpenIssuesStack.class);
- IssueTracking tracking = mock(IssueTracking.class, RETURNS_MOCKS);
- LastLineHashes lastSnapshots = mock(LastLineHashes.class);
- IssueHandlers handlers = mock(IssueHandlers.class);
- IssueWorkflow workflow = mock(IssueWorkflow.class);
- IssueUpdater updater = mock(IssueUpdater.class);
- ResourcePerspectives perspectives = mock(ResourcePerspectives.class);
- RulesProfile profile = mock(RulesProfile.class);
- RuleFinder ruleFinder = mock(RuleFinder.class);
- InputPathCache inputPathCache = mock(InputPathCache.class);
-
- @Before
- public void init() {
- decorator = new IssueTrackingDecorator(
- issueCache,
- initialOpenIssues,
- tracking,
- lastSnapshots,
- handlers,
- workflow,
- updater,
- new Project("foo"),
- perspectives,
- profile,
- ruleFinder,
- inputPathCache);
- }
-
- @Test
- public void should_execute_on_project() {
- Project project = mock(Project.class);
- assertThat(decorator.shouldExecuteOnProject(project)).isTrue();
- }
-
- @Test
- public void should_not_be_executed_on_classes_not_methods() throws Exception {
- DecoratorContext context = mock(DecoratorContext.class);
- decorator.decorate(JavaClass.create("org.foo.Bar"), context);
- verifyZeroInteractions(context, issueCache, tracking, handlers, workflow);
- }
-
- @Test
- public void should_process_open_issues() throws Exception {
- Resource file = File.create("Action.java").setEffectiveKey("struts:Action.java").setId(123);
- final DefaultIssue issue = new DefaultIssue();
-
- // INPUT : one issue, no open issues during previous scan, no filtering
- when(issueCache.byComponent("struts:Action.java")).thenReturn(Arrays.asList(issue));
- List<IssueDto> dbIssues = Collections.emptyList();
- when(initialOpenIssues.selectAndRemoveIssues("struts:Action.java")).thenReturn(dbIssues);
- when(inputPathCache.getFile("foo", "Action.java")).thenReturn(mock(DefaultInputFile.class));
- decorator.doDecorate(file);
-
- // Apply filters, track, apply transitions, notify extensions then update cache
- verify(tracking).track(isA(SourceHashHolder.class), eq(dbIssues), argThat(new ArgumentMatcher<Collection<DefaultIssue>>() {
- @Override
- public boolean matches(Object o) {
- List<DefaultIssue> issues = (List<DefaultIssue>) o;
- return issues.size() == 1 && issues.get(0) == issue;
- }
- }));
- verify(workflow).doAutomaticTransition(eq(issue), any(IssueChangeContext.class));
- verify(handlers).execute(eq(issue), any(IssueChangeContext.class));
- verify(issueCache).put(issue);
- }
-
- @Test
- public void should_register_unmatched_issues_as_end_of_life() throws Exception {
- // "Unmatched" issues existed in previous scan but not in current one -> they have to be closed
- Resource file = File.create("Action.java").setEffectiveKey("struts:Action.java").setId(123);
-
- // INPUT : one issue existing during previous scan
- IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey("squid", "AvoidCycle");
-
- IssueTrackingResult trackingResult = new IssueTrackingResult();
- trackingResult.addUnmatched(unmatchedIssue);
-
- when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
- when(inputPathCache.getFile("foo", "Action.java")).thenReturn(mock(DefaultInputFile.class));
-
- decorator.doDecorate(file);
-
- verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
- verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
-
- ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
- verify(issueCache).put(argument.capture());
-
- DefaultIssue issue = argument.getValue();
- assertThat(issue.key()).isEqualTo("ABCDE");
- assertThat(issue.isNew()).isFalse();
- assertThat(issue.isEndOfLife()).isTrue();
- }
-
- @Test
- public void manual_issues_should_be_moved_if_matching_line_found() throws Exception {
- // INPUT : one issue existing during previous scan
- IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(6).setStatus("OPEN").setRuleKey("manual", "Performance");
- when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance"));
-
- IssueTrackingResult trackingResult = new IssueTrackingResult();
- trackingResult.addUnmatched(unmatchedIssue);
-
- String originalSource = "public interface Action {\n"
- + " void method1();\n"
- + " void method2();\n"
- + " void method3();\n"
- + " void method4();\n"
- + " void method5();\n" // Original issue here
- + "}";
- String newSource = "public interface Action {\n"
- + " void method5();\n" // New issue here
- + " void method1();\n"
- + " void method2();\n"
- + " void method3();\n"
- + " void method4();\n"
- + "}";
- Resource file = mockHashes(originalSource, newSource);
-
- when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
-
- decorator.doDecorate(file);
-
- verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
- verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
-
- ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
- verify(issueCache).put(argument.capture());
-
- DefaultIssue issue = argument.getValue();
- assertThat(issue.line()).isEqualTo(2);
- assertThat(issue.key()).isEqualTo("ABCDE");
- assertThat(issue.isNew()).isFalse();
- assertThat(issue.isEndOfLife()).isFalse();
- assertThat(issue.isOnDisabledRule()).isFalse();
- }
-
- private Resource mockHashes(String originalSource, String newSource) {
- DefaultInputFile inputFile = mock(DefaultInputFile.class);
- byte[][] hashes = computeHashes(newSource);
- when(inputFile.lineHashes()).thenReturn(hashes);
- when(inputFile.key()).thenReturn("foo:Action.java");
- when(inputPathCache.getFile("foo", "Action.java")).thenReturn(inputFile);
- when(lastSnapshots.getLineHashes("foo:Action.java")).thenReturn(computeHexHashes(originalSource));
- Resource file = File.create("Action.java");
- return file;
- }
-
- @Test
- public void manual_issues_should_be_untouched_if_already_closed() throws Exception {
-
- // INPUT : one issue existing during previous scan
- IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(1).setStatus("CLOSED").setRuleKey("manual", "Performance");
- when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance"));
-
- IssueTrackingResult trackingResult = new IssueTrackingResult();
- trackingResult.addUnmatched(unmatchedIssue);
-
- String originalSource = "public interface Action {}";
- Resource file = mockHashes(originalSource, originalSource);
-
- when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
-
- decorator.doDecorate(file);
-
- verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
- verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
-
- ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
- verify(issueCache).put(argument.capture());
-
- DefaultIssue issue = argument.getValue();
- assertThat(issue.line()).isEqualTo(1);
- assertThat(issue.key()).isEqualTo("ABCDE");
- assertThat(issue.isNew()).isFalse();
- assertThat(issue.isEndOfLife()).isFalse();
- assertThat(issue.isOnDisabledRule()).isFalse();
- assertThat(issue.status()).isEqualTo("CLOSED");
- }
-
- @Test
- public void manual_issues_should_be_untouched_if_line_is_null() throws Exception {
-
- // INPUT : one issue existing during previous scan
- IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(null).setStatus("OPEN").setRuleKey("manual", "Performance");
- when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance"));
-
- IssueTrackingResult trackingResult = new IssueTrackingResult();
- trackingResult.addUnmatched(unmatchedIssue);
-
- String originalSource = "public interface Action {}";
- Resource file = mockHashes(originalSource, originalSource);
-
- when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
-
- decorator.doDecorate(file);
-
- verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
- verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
-
- ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
- verify(issueCache).put(argument.capture());
-
- DefaultIssue issue = argument.getValue();
- assertThat(issue.line()).isEqualTo(null);
- assertThat(issue.key()).isEqualTo("ABCDE");
- assertThat(issue.isNew()).isFalse();
- assertThat(issue.isEndOfLife()).isFalse();
- assertThat(issue.isOnDisabledRule()).isFalse();
- assertThat(issue.status()).isEqualTo("OPEN");
- }
-
- @Test
- public void manual_issues_should_be_kept_if_matching_line_not_found() throws Exception {
- // "Unmatched" issues existed in previous scan but not in current one -> they have to be closed
-
- // INPUT : one issue existing during previous scan
- final int issueOnLine = 6;
- IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(issueOnLine).setStatus("OPEN").setRuleKey("manual", "Performance");
- when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance"));
-
- IssueTrackingResult trackingResult = new IssueTrackingResult();
- trackingResult.addUnmatched(unmatchedIssue);
-
- String originalSource = "public interface Action {\n"
- + " void method1();\n"
- + " void method2();\n"
- + " void method3();\n"
- + " void method4();\n"
- + " void method5();\n" // Original issue here
- + "}";
- String newSource = "public interface Action {\n"
- + " void method1();\n"
- + " void method2();\n"
- + " void method3();\n"
- + " void method4();\n"
- + " void method6();\n" // Poof, no method5 anymore
- + "}";
-
- Resource file = mockHashes(originalSource, newSource);
-
- when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
-
- decorator.doDecorate(file);
-
- verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
- verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
-
- ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
- verify(issueCache).put(argument.capture());
-
- DefaultIssue issue = argument.getValue();
- assertThat(issue.line()).isEqualTo(issueOnLine);
- assertThat(issue.key()).isEqualTo("ABCDE");
- assertThat(issue.isNew()).isFalse();
- assertThat(issue.isEndOfLife()).isFalse();
- assertThat(issue.isOnDisabledRule()).isFalse();
- }
-
- @Test
- public void manual_issues_should_be_kept_if_multiple_matching_lines_found() throws Exception {
- // "Unmatched" issues existed in previous scan but not in current one -> they have to be closed
-
- // INPUT : one issue existing during previous scan
- final int issueOnLine = 3;
- IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(issueOnLine).setStatus("OPEN").setRuleKey("manual", "Performance");
- when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance"));
-
- IssueTrackingResult trackingResult = new IssueTrackingResult();
- trackingResult.addUnmatched(unmatchedIssue);
-
- String originalSource = "public class Action {\n"
- + " void method1() {\n"
- + " notify();\n" // initial issue
- + " }\n"
- + "}";
- String newSource = "public class Action {\n"
- + " \n"
- + " void method1() {\n" // new issue will appear here
- + " notify();\n"
- + " }\n"
- + " void method2() {\n"
- + " notify();\n"
- + " }\n"
- + "}";
- Resource file = mockHashes(originalSource, newSource);
-
- when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
-
- decorator.doDecorate(file);
-
- verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
- verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
-
- ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
- verify(issueCache).put(argument.capture());
-
- DefaultIssue issue = argument.getValue();
- assertThat(issue.line()).isEqualTo(issueOnLine);
- assertThat(issue.key()).isEqualTo("ABCDE");
- assertThat(issue.isNew()).isFalse();
- assertThat(issue.isEndOfLife()).isFalse();
- assertThat(issue.isOnDisabledRule()).isFalse();
- }
-
- @Test
- public void manual_issues_should_be_closed_if_manual_rule_is_removed() throws Exception {
- // "Unmatched" issues existed in previous scan but not in current one -> they have to be closed
-
- // INPUT : one issue existing during previous scan
- IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(1).setStatus("OPEN").setRuleKey("manual", "Performance");
- when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance").setStatus(Rule.STATUS_REMOVED));
-
- IssueTrackingResult trackingResult = new IssueTrackingResult();
- trackingResult.addUnmatched(unmatchedIssue);
-
- String source = "public interface Action {}";
- Resource file = mockHashes(source, source);
-
- when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
-
- decorator.doDecorate(file);
-
- verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
- verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
-
- ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
- verify(issueCache).put(argument.capture());
-
- DefaultIssue issue = argument.getValue();
- assertThat(issue.key()).isEqualTo("ABCDE");
- assertThat(issue.isNew()).isFalse();
- assertThat(issue.isEndOfLife()).isTrue();
- assertThat(issue.isOnDisabledRule()).isTrue();
- }
-
- @Test
- public void manual_issues_should_be_closed_if_manual_rule_is_not_found() throws Exception {
- // "Unmatched" issues existed in previous scan but not in current one -> they have to be closed
-
- // INPUT : one issue existing during previous scan
- IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(1).setStatus("OPEN").setRuleKey("manual", "Performance");
- when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(null);
-
- IssueTrackingResult trackingResult = new IssueTrackingResult();
- trackingResult.addUnmatched(unmatchedIssue);
-
- String source = "public interface Action {}";
- Resource file = mockHashes(source, source);
-
- when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
-
- decorator.doDecorate(file);
-
- verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
- verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
-
- ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
- verify(issueCache).put(argument.capture());
-
- DefaultIssue issue = argument.getValue();
- assertThat(issue.key()).isEqualTo("ABCDE");
- assertThat(issue.isNew()).isFalse();
- assertThat(issue.isEndOfLife()).isTrue();
- assertThat(issue.isOnDisabledRule()).isTrue();
- }
-
- @Test
- public void manual_issues_should_be_closed_if_new_source_is_shorter() throws Exception {
- // "Unmatched" issues existed in previous scan but not in current one -> they have to be closed
-
- // INPUT : one issue existing during previous scan
- IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(6).setStatus("OPEN").setRuleKey("manual", "Performance");
- when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(null);
-
- IssueTrackingResult trackingResult = new IssueTrackingResult();
- trackingResult.addUnmatched(unmatchedIssue);
-
- String originalSource = "public interface Action {\n"
- + " void method1();\n"
- + " void method2();\n"
- + " void method3();\n"
- + " void method4();\n"
- + " void method5();\n"
- + "}";
- String newSource = "public interface Action {\n"
- + " void method1();\n"
- + " void method2();\n"
- + "}";
- Resource file = mockHashes(originalSource, newSource);
-
- when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
-
- decorator.doDecorate(file);
-
- verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
- verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
-
- ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
- verify(issueCache).put(argument.capture());
-
- DefaultIssue issue = argument.getValue();
- verify(updater).setResolution(eq(issue), eq(Issue.RESOLUTION_REMOVED), any(IssueChangeContext.class));
- verify(updater).setStatus(eq(issue), eq(Issue.STATUS_CLOSED), any(IssueChangeContext.class));
-
- assertThat(issue.key()).isEqualTo("ABCDE");
- assertThat(issue.isNew()).isFalse();
- assertThat(issue.isEndOfLife()).isTrue();
- assertThat(issue.isOnDisabledRule()).isTrue();
- }
-
- @Test
- public void should_register_issues_on_deleted_components() throws Exception {
- Project project = new Project("struts");
- DefaultIssue openIssue = new DefaultIssue();
- when(issueCache.byComponent("struts")).thenReturn(Arrays.asList(openIssue));
- IssueDto deadIssue = new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey("squid", "AvoidCycle");
- when(initialOpenIssues.selectAllIssues()).thenReturn(Arrays.asList(deadIssue));
-
- decorator.doDecorate(project);
-
- // the dead issue must be closed -> apply automatic transition, notify handlers and add to cache
- verify(workflow, times(2)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
- verify(handlers, times(2)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
- verify(issueCache, times(2)).put(any(DefaultIssue.class));
-
- verify(issueCache).put(argThat(new ArgumentMatcher<DefaultIssue>() {
- @Override
- public boolean matches(Object o) {
- DefaultIssue dead = (DefaultIssue) o;
- return "ABCDE".equals(dead.key()) && !dead.isNew() && dead.isEndOfLife();
- }
- }));
- }
-
- @Test
- public void merge_matched_issue() throws Exception {
- IssueDto previousIssue = new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey("squid", "AvoidCycle")
- .setLine(10).setSeverity("MAJOR").setMessage("Message").setEffortToFix(1.5).setDebt(1L).setProjectKey("sample");
- DefaultIssue issue = new DefaultIssue();
-
- IssueTrackingResult trackingResult = mock(IssueTrackingResult.class);
- when(trackingResult.matched()).thenReturn(newArrayList(issue));
- when(trackingResult.matching(eq(issue))).thenReturn(previousIssue);
- decorator.mergeMatched(trackingResult);
-
- verify(updater).setPastSeverity(eq(issue), eq("MAJOR"), any(IssueChangeContext.class));
- verify(updater).setPastLine(eq(issue), eq(10));
- verify(updater).setPastMessage(eq(issue), eq("Message"), any(IssueChangeContext.class));
- verify(updater).setPastEffortToFix(eq(issue), eq(1.5), any(IssueChangeContext.class));
- verify(updater).setPastTechnicalDebt(eq(issue), eq(Duration.create(1L)), any(IssueChangeContext.class));
- verify(updater).setPastProject(eq(issue), eq("sample"), any(IssueChangeContext.class));
- }
-
- @Test
- public void merge_matched_issue_on_manual_severity() throws Exception {
- IssueDto previousIssue = new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey("squid", "AvoidCycle")
- .setLine(10).setManualSeverity(true).setSeverity("MAJOR").setMessage("Message").setEffortToFix(1.5).setDebt(1L);
- DefaultIssue issue = new DefaultIssue();
-
- IssueTrackingResult trackingResult = mock(IssueTrackingResult.class);
- when(trackingResult.matched()).thenReturn(newArrayList(issue));
- when(trackingResult.matching(eq(issue))).thenReturn(previousIssue);
- decorator.mergeMatched(trackingResult);
-
- assertThat(issue.manualSeverity()).isTrue();
- assertThat(issue.severity()).isEqualTo("MAJOR");
- verify(updater, never()).setPastSeverity(eq(issue), anyString(), any(IssueChangeContext.class));
- }
-
- @Test
- public void merge_issue_changelog_with_previous_changelog() throws Exception {
- when(initialOpenIssues.selectChangelog("ABCDE")).thenReturn(newArrayList(new IssueChangeDto().setIssueKey("ABCD").setCreatedAt(System2.INSTANCE.now())));
-
- IssueDto previousIssue = new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey("squid", "AvoidCycle")
- .setLine(10).setMessage("Message").setEffortToFix(1.5).setDebt(1L).setCreatedAt(System2.INSTANCE.now());
- DefaultIssue issue = new DefaultIssue();
-
- IssueTrackingResult trackingResult = mock(IssueTrackingResult.class);
- when(trackingResult.matched()).thenReturn(newArrayList(issue));
- when(trackingResult.matching(eq(issue))).thenReturn(previousIssue);
- decorator.mergeMatched(trackingResult);
-
- assertThat(issue.changes()).hasSize(1);
- }
-
- private byte[][] computeHashes(String source) {
- String[] lines = source.split("\n");
- byte[][] hashes = new byte[lines.length][];
- for (int i = 0; i < lines.length; i++) {
- hashes[i] = DigestUtils.md5(lines[i].replaceAll("[\t ]", ""));
- }
- return hashes;
- }
-
- 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;
- }
-
-}
+++ /dev/null
-/*
- * 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.plugins.core.issue;
-
-import com.google.common.base.Charsets;
-import com.google.common.io.Resources;
-import org.apache.commons.codec.digest.DigestUtils;
-import org.junit.Before;
-import org.junit.Test;
-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.batch.scan.LastLineHashes;
-import org.sonar.core.issue.db.IssueDto;
-
-import java.io.IOException;
-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 {
-
- IssueTracking tracking;
- Resource project;
- SourceHashHolder sourceHashHolder;
- LastLineHashes lastSnapshots;
- long violationId = 0;
-
- @Before
- public void before() {
- lastSnapshots = mock(LastLineHashes.class);
-
- project = mock(Project.class);
- tracking = new IssueTracking();
- }
-
- @Test
- public void key_should_be_the_prioritary_field_to_check() {
- IssueDto referenceIssue1 = newReferenceIssue("message", 10, "squid", "AvoidCycle", "checksum1").setKee("100");
- IssueDto referenceIssue2 = newReferenceIssue("message", 10, "squid", "AvoidCycle", "checksum2").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), newArrayList(referenceIssue1, referenceIssue2), null, result);
- // same key
- assertThat(result.matching(newIssue)).isSameAs(referenceIssue2);
- }
-
- @Test
- public void checksum_should_have_greater_priority_than_line() {
- IssueDto referenceIssue1 = newReferenceIssue("message", 1, "squid", "AvoidCycle", "checksum1");
- IssueDto 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");
- IssueDto 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");
- IssueDto 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");
- IssueDto 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);
- IssueDto 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");
- IssueDto 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");
- IssueDto 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");
- IssueDto 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");
- IssueDto 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");
- IssueDto 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");
- IssueDto 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");
- IssueDto 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);
- IssueDto 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);
- }
-
- /**
- * SONAR-3072
- */
- @Test
- public void should_track_issues_based_on_blocks_recognition_on_example1() throws Exception {
- initLastHashes("example1-v1", "example1-v2");
-
- IssueDto referenceIssue1 = newReferenceIssue("Indentation", 7, "squid", "AvoidCycle", null);
- IssueDto 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");
-
- IssueDto 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");
-
- IssueDto referenceIssue1 = newReferenceIssue("Avoid unused local variables such as 'j'.", 6, "squid", "AvoidCycle", "63c11570fc0a76434156be5f8138fa03");
- IssueDto referenceIssue2 = newReferenceIssue("Avoid unused private methods such as 'myMethod()'.", 13, "squid", "NullDeref", "ef23288705d1ef1e512448ace287586e");
- IssueDto 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() throws Exception {
- sourceHashHolder = mock(SourceHashHolder.class);
-
- IssueDto 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"), Charsets.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 IssueDto 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 referenceIssue;
- }
-
- private void initLastHashes(String reference, String newSource) throws IOException {
- DefaultInputFile inputFile = mock(DefaultInputFile.class);
- byte[][] hashes = computeHashes(load(newSource));
- when(inputFile.lineHashes()).thenReturn(hashes);
- when(inputFile.key()).thenReturn("foo:Action.java");
- when(lastSnapshots.getLineHashes("foo:Action.java")).thenReturn(computeHexHashes(load(reference)));
- sourceHashHolder = new SourceHashHolder(inputFile, lastSnapshots);
- }
-
- private byte[][] computeHashes(String source) {
- String[] lines = source.split("\n");
- byte[][] hashes = new byte[lines.length][];
- for (int i = 0; i < lines.length; i++) {
- hashes[i] = DigestUtils.md5(lines[i].replaceAll("[\t ]", ""));
- }
- return hashes;
- }
-
- 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;
- }
-}
+++ /dev/null
-/*
- * 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.plugins.core.issue;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mockito;
-import org.sonar.api.batch.fs.InputFile;
-import org.sonar.api.batch.fs.internal.DefaultInputFile;
-import org.sonar.batch.scan.LastLineHashes;
-
-import static org.apache.commons.codec.digest.DigestUtils.md5;
-import static org.apache.commons.codec.digest.DigestUtils.md5Hex;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-public class SourceHashHolderTest {
-
- SourceHashHolder sourceHashHolder;
-
- LastLineHashes lastSnapshots;
- DefaultInputFile file;
-
- @Before
- public void setUp() {
- lastSnapshots = mock(LastLineHashes.class);
- file = mock(DefaultInputFile.class);
-
- sourceHashHolder = new SourceHashHolder(file, lastSnapshots);
- }
-
- @Test
- public void should_lazy_load_line_hashes() {
- final String source = "source";
- when(file.lineHashes()).thenReturn(new byte[][] {md5(source), null});
-
- assertThat(sourceHashHolder.getHashedSource().getHash(1)).isEqualTo(md5Hex(source));
- assertThat(sourceHashHolder.getHashedSource().getHash(2)).isEqualTo("");
- verify(file).lineHashes();
- verify(file).key();
- verify(file).status();
-
- assertThat(sourceHashHolder.getHashedSource().getHash(1)).isEqualTo(md5Hex(source));
- Mockito.verifyNoMoreInteractions(file);
- }
-
- @Test
- public void should_lazy_load_reference_hashes_when_status_changed() {
- final String source = "source";
- String key = "foo:src/Foo.java";
- when(file.lineHashes()).thenReturn(new byte[][] {md5(source)});
- when(file.key()).thenReturn(key);
- when(file.status()).thenReturn(InputFile.Status.CHANGED);
- when(lastSnapshots.getLineHashes(key)).thenReturn(new String[] {md5Hex(source)});
-
- assertThat(sourceHashHolder.getHashedReference().getHash(1)).isEqualTo(md5Hex(source));
- verify(lastSnapshots).getLineHashes(key);
-
- assertThat(sourceHashHolder.getHashedReference().getHash(1)).isEqualTo(md5Hex(source));
- Mockito.verifyNoMoreInteractions(lastSnapshots);
- }
-
- @Test
- public void should_not_load_reference_hashes_when_status_same() {
- final String source = "source";
- String key = "foo:src/Foo.java";
- when(file.lineHashes()).thenReturn(new byte[][] {md5(source)});
- when(file.key()).thenReturn(key);
- when(file.status()).thenReturn(InputFile.Status.SAME);
-
- assertThat(sourceHashHolder.getHashedReference().getHash(1)).isEqualTo(md5Hex(source));
- assertThat(sourceHashHolder.getHashedReference().getHash(1)).isEqualTo(md5Hex(source));
- Mockito.verifyNoMoreInteractions(lastSnapshots);
- }
-
- @Test
- public void no_reference_hashes_when_status_added() {
- final String source = "source";
- String key = "foo:src/Foo.java";
- when(file.lineHashes()).thenReturn(new byte[][] {md5(source)});
- when(file.key()).thenReturn(key);
- when(file.status()).thenReturn(InputFile.Status.ADDED);
-
- assertThat(sourceHashHolder.getHashedReference()).isNull();
- Mockito.verifyNoMoreInteractions(lastSnapshots);
- }
-
-}
+++ /dev/null
-/*
- * 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.plugins.core.issue.tracking;
-
-import org.junit.Test;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class IssueTrackingBlocksRecognizerTest {
-
- @Test
- public void test() {
- assertThat(compute(t("abcde"), t("abcde"), 4, 4)).isEqualTo(5);
- assertThat(compute(t("abcde"), t("abcd"), 4, 4)).isEqualTo(4);
- assertThat(compute(t("bcde"), t("abcde"), 4, 4)).isEqualTo(0);
- assertThat(compute(t("bcde"), t("abcde"), 3, 4)).isEqualTo(4);
- }
-
- private static int compute(FileHashes a, FileHashes b, int ai, int bi) {
- IssueTrackingBlocksRecognizer rec = new IssueTrackingBlocksRecognizer(a, b);
- return rec.computeLengthOfMaximalBlock(ai, bi);
- }
-
- private static FileHashes t(String text) {
- String[] array = new String[text.length()];
- for (int i = 0; i < text.length(); i++) {
- array[i] = "" + text.charAt(i);
- }
- return FileHashes.create(array);
- }
-
-}
+++ /dev/null
-/*
- * 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.plugins.core.issue.tracking;
-
-import org.junit.Test;
-
-import static org.apache.commons.codec.digest.DigestUtils.md5Hex;
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class RollingFileHashesTest {
-
- @Test
- public void test_equals() {
- RollingFileHashes a = RollingFileHashes.create(FileHashes.create(new String[] {md5Hex("line0"), md5Hex("line1"), md5Hex("line2")}), 1);
- RollingFileHashes b = RollingFileHashes.create(FileHashes.create(new String[] {md5Hex("line0"), md5Hex("line1"), md5Hex("line2"), md5Hex("line3")}), 1);
-
- assertThat(a.getHash(1) == b.getHash(1)).isTrue();
- assertThat(a.getHash(2) == b.getHash(2)).isTrue();
- assertThat(a.getHash(3) == b.getHash(3)).isFalse();
-
- RollingFileHashes c = RollingFileHashes.create(FileHashes.create(new String[] {md5Hex("line-1"), md5Hex("line0"), md5Hex("line1"), md5Hex("line2"), md5Hex("line3")}), 1);
- assertThat(a.getHash(1) == c.getHash(2)).isFalse();
- assertThat(a.getHash(2) == c.getHash(3)).isTrue();
- }
-
-}
+++ /dev/null
-package example1;
-
-public class Toto {
-
- public void doSomething() {
- // doSomething
- }
-
- public void doSomethingElse() {
- // doSomethingElse
- }
-}
+++ /dev/null
-package example1;
-
-public class Toto {
-
- public Toto(){}
-
- public void doSomethingNew() {
- // doSomethingNew
- }
-
- public void doSomethingElseNew() {
- // doSomethingElseNew
- }
-
- public void doSomething() {
- // doSomething
- }
-
- public void doSomethingElse() {
- // doSomethingElse
- }
-}
+++ /dev/null
-package example2;
-
-public class Toto {
- void method1() {
- System.out.println("toto");
- }
-}
+++ /dev/null
-package example2;
-
-public class Toto {
-
- void method2() {
- System.out.println("toto");
- }
-
- void method1() {
- System.out.println("toto");
- }
-
- void method3() {
- System.out.println("toto");
- }
-}
+++ /dev/null
-package sample;
-
-public class Sample {
-
- public Sample(int i) {
- int j = i+1; // violation: unused local variable
- }
-
- public boolean avoidUtilityClass() {
- return true;
- }
-
- private String myMethod() { // violation : unused private method
- return "hello";
- }
-}
+++ /dev/null
-package sample;
-
-public class Sample {
-
- public Sample(int i) {
- int j = i+1; // still the same violation: unused local variable
- }
-
- public boolean avoidUtilityClass() {
- return true;
- }
-
- private String myMethod() { // violation "unused private method" is fixed because it's called in newViolation
- return "hello";
- }
-
- public void newViolation() {
- String msg = myMethod(); // new violation : msg is an unused variable
- }
-}
*/
package org.sonar.xoo.rule;
+import org.sonar.api.batch.fs.FilePredicates;
+import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.InputFile.Type;
import org.sonar.api.batch.rule.ActiveRule;
import org.sonar.api.batch.sensor.Sensor;
import org.sonar.api.batch.sensor.SensorContext;
@Override
public void execute(SensorContext context) {
- for (InputFile file : context.fileSystem().inputFiles(context.fileSystem().predicates().hasLanguages(Xoo.KEY))) {
+ FileSystem fs = context.fileSystem();
+ FilePredicates p = fs.predicates();
+ for (InputFile file : fs.inputFiles(p.and(p.hasLanguages(Xoo.KEY), p.hasType(Type.MAIN)))) {
createIssues(file, context);
}
}
*/
package org.sonar.xoo.rule;
+import org.sonar.api.batch.fs.FilePredicates;
+import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.InputFile.Type;
import org.sonar.api.batch.sensor.Sensor;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.batch.sensor.SensorDescriptor;
@Override
public void execute(SensorContext context) {
- for (InputFile file : context.fileSystem().inputFiles(context.fileSystem().predicates().hasLanguages(Xoo.KEY))) {
+ FileSystem fs = context.fileSystem();
+ FilePredicates p = fs.predicates();
+ for (InputFile file : fs.inputFiles(p.and(p.hasLanguages(Xoo.KEY), p.hasType(Type.MAIN)))) {
createIssues(file, context);
}
}
import org.sonar.api.server.ws.RequestHandler;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
-import org.sonar.batch.protocol.input.ProjectReferentials;
+import org.sonar.batch.protocol.input.ProjectRepository;
import org.sonar.server.plugins.MimeTypes;
public class ProjectRepositoryAction implements RequestHandler {
@Override
public void handle(Request request, Response response) throws Exception {
- ProjectReferentials ref = projectReferentialsLoader.load(ProjectRepositoryQuery.create()
+ ProjectRepository ref = projectReferentialsLoader.load(ProjectRepositoryQuery.create()
.setModuleKey(request.mandatoryParam(PARAM_KEY))
.setProfileName(request.param(PARAM_PROFILE))
.setPreview(request.mandatoryParamAsBoolean(PARAM_PREVIEW)));
import org.sonar.api.resources.Languages;
import org.sonar.api.rule.RuleKey;
import org.sonar.batch.protocol.input.FileData;
-import org.sonar.batch.protocol.input.ProjectReferentials;
+import org.sonar.batch.protocol.input.ProjectRepository;
import org.sonar.core.UtcDateUtils;
import org.sonar.core.component.ComponentDto;
import org.sonar.core.component.FilePathWithHashDto;
this.languages = languages;
}
- public ProjectReferentials load(ProjectRepositoryQuery query) {
+ public ProjectRepository load(ProjectRepositoryQuery query) {
boolean hasScanPerm = UserSession.get().hasGlobalPermission(GlobalPermissions.SCAN_EXECUTION);
checkPermission(query.isPreview());
DbSession session = dbClient.openSession(false);
try {
- ProjectReferentials ref = new ProjectReferentials();
+ ProjectRepository ref = new ProjectRepository();
String projectKey = query.getModuleKey();
ComponentDto module = dbClient.componentDao().getNullableByKey(session, query.getModuleKey());
// Current project/module can be null when analysing a new project
}
}
- private void addSettingsToChildrenModules(ProjectReferentials ref, String moduleKey, Map<String, String> parentProperties, TreeModuleSettings treeModuleSettings,
+ private void addSettingsToChildrenModules(ProjectRepository ref, String moduleKey, Map<String, String> parentProperties, TreeModuleSettings treeModuleSettings,
boolean hasScanPerm, DbSession session) {
Map<String, String> currentParentProperties = newHashMap();
currentParentProperties.putAll(parentProperties);
}
}
- private void addSettings(ProjectReferentials ref, String module, Map<String, String> properties) {
+ private void addSettings(ProjectRepository ref, String module, Map<String, String> properties) {
if (!properties.isEmpty()) {
ref.addSettings(module, properties);
}
return !key.contains(".secured") || hasScanPerm;
}
- private void addProfiles(ProjectReferentials ref, @Nullable String projectKey, @Nullable String profileName, DbSession session) {
+ private void addProfiles(ProjectRepository ref, @Nullable String projectKey, @Nullable String profileName, DbSession session) {
for (Language language : languages.all()) {
String languageKey = language.getKey();
QualityProfileDto qualityProfileDto = getProfile(languageKey, projectKey, profileName, session);
}
}
- private void addActiveRules(ProjectReferentials ref) {
+ private void addActiveRules(ProjectRepository ref) {
for (org.sonar.batch.protocol.input.QProfile qProfile : ref.qProfiles()) {
for (ActiveRule activeRule : qProfileLoader.findActiveRulesByProfile(qProfile.key())) {
Rule rule = ruleService.getNonNullByKey(activeRule.key().ruleKey());
}
}
- private void addManualRules(ProjectReferentials ref) {
+ private void addManualRules(ProjectRepository ref) {
Result<Rule> ruleSearchResult = ruleService.search(new RuleQuery().setRepositories(newArrayList(RuleKey.MANUAL_REPOSITORY_KEY)), new QueryContext().setScroll(true)
.setFieldsToReturn(newArrayList(RuleNormalizer.RuleField.KEY.field(), RuleNormalizer.RuleField.NAME.field())));
Iterator<Rule> rules = ruleSearchResult.scroll();
}
}
- private void addFileData(DbSession session, ProjectReferentials ref, List<ComponentDto> moduleChildren, String moduleKey) {
+ private void addFileData(DbSession session, ProjectRepository ref, List<ComponentDto> moduleChildren, String moduleKey) {
Map<String, String> moduleKeysByUuid = newHashMap();
for (ComponentDto module : moduleChildren) {
moduleKeysByUuid.put(module.uuid(), module.key());
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
-import org.sonar.batch.protocol.input.ProjectReferentials;
+import org.sonar.batch.protocol.input.ProjectRepository;
import org.sonar.server.ws.WsTester;
import static org.assertj.core.api.Assertions.assertThat;
public void project_referentials() throws Exception {
String projectKey = "org.codehaus.sonar:sonar";
- ProjectReferentials projectReferentials = mock(ProjectReferentials.class);
+ ProjectRepository projectReferentials = mock(ProjectRepository.class);
when(projectReferentials.toJson()).thenReturn("{\"settingsByModule\": {}}");
ArgumentCaptor<ProjectRepositoryQuery> queryArgumentCaptor = ArgumentCaptor.forClass(ProjectRepositoryQuery.class);
import org.sonar.api.utils.DateUtils;
import org.sonar.batch.protocol.input.ActiveRule;
import org.sonar.batch.protocol.input.FileData;
-import org.sonar.batch.protocol.input.ProjectReferentials;
+import org.sonar.batch.protocol.input.ProjectRepository;
import org.sonar.batch.protocol.input.QProfile;
import org.sonar.core.component.ComponentDto;
import org.sonar.core.permission.GlobalPermissions;
dbSession);
dbSession.commit();
- ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
+ ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
Map<String, String> projectSettings = ref.settings(project.key());
assertThat(projectSettings).isEqualTo(ImmutableMap.of(
"sonar.jira.project.key", "SONAR",
dbSession);
dbSession.commit();
- ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()).setPreview(true));
+ ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()).setPreview(true));
Map<String, String> projectSettings = ref.settings(project.key());
assertThat(projectSettings).isEqualTo(ImmutableMap.of(
"sonar.jira.project.key", "SONAR"
dbSession.commit();
- ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
+ ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
assertThat(ref.settings(project.key())).isEqualTo(ImmutableMap.of(
"sonar.jira.project.key", "SONAR",
"sonar.jira.login.secured", "john"
dbSession.commit();
- ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
+ ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
assertThat(ref.settings(project.key())).isEqualTo(ImmutableMap.of(
"sonar.jira.project.key", "SONAR",
"sonar.jira.login.secured", "john"
dbSession.commit();
- ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
+ ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
assertThat(ref.settings(project.key())).isEqualTo(ImmutableMap.of(
"sonar.jira.project.key", "SONAR",
"sonar.jira.login.secured", "john"
dbSession.commit();
- ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
+ ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
assertThat(ref.settings(project.key())).isEqualTo(ImmutableMap.of(
"sonar.jira.project.key", "SONAR",
"sonar.jira.login.secured", "john"
));
}
+ @Test
+ public void return_provisioned_project_settings() throws Exception {
+ MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION);
+
+ // No snapshot attached on the project -> provisioned project
+ ComponentDto project = ComponentTesting.newProjectDto();
+ tester.get(DbClient.class).componentDao().insert(dbSession, project);
+ addDefaultProfile();
+
+ // Project properties
+ tester.get(DbClient.class).propertiesDao().setProperty(new PropertyDto().setKey("sonar.jira.project.key").setValue("SONAR").setResourceId(project.getId()), dbSession);
+ tester.get(DbClient.class).propertiesDao().setProperty(new PropertyDto().setKey("sonar.jira.login.secured").setValue("john").setResourceId(project.getId()), dbSession);
+
+ dbSession.commit();
+
+ ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
+ assertThat(ref.settings(project.key())).isEqualTo(ImmutableMap.of(
+ "sonar.jira.project.key", "SONAR",
+ "sonar.jira.login.secured", "john"
+ ));
+ }
+
@Test
public void return_sub_module_settings() throws Exception {
MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION);
dbSession.commit();
- ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(subModule.key()));
+ ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(subModule.key()));
assertThat(ref.settings(project.key())).isEmpty();
assertThat(ref.settings(module.key())).isEmpty();
assertThat(ref.settings(subModule.key())).isEqualTo(ImmutableMap.of(
dbSession.commit();
- ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(subModule.key()));
+ ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(subModule.key()));
assertThat(ref.settings(project.key())).isEmpty();
assertThat(ref.settings(module.key())).isEmpty();
assertThat(ref.settings(subModule.key())).isEqualTo(ImmutableMap.of(
dbSession.commit();
- ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(subModule.key()));
+ ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(subModule.key()));
assertThat(ref.settings(project.key())).isEmpty();
assertThat(ref.settings(module.key())).isEmpty();
assertThat(ref.settings(subModule.key())).isEqualTo(ImmutableMap.of(
dbSession.commit();
- ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(subModule.key()));
+ ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(subModule.key()));
assertThat(ref.settings(project.key())).isEmpty();
assertThat(ref.settings(module.key())).isEmpty();
assertThat(ref.settings(subModule.key())).isEqualTo(ImmutableMap.of(
dbSession.commit();
- ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
+ ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
List<QProfile> profiles = newArrayList(ref.qProfiles());
assertThat(profiles).hasSize(1);
assertThat(profiles.get(0).key()).isEqualTo("abcd");
dbSession.commit();
- ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
+ ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
List<QProfile> profiles = newArrayList(ref.qProfiles());
assertThat(profiles).hasSize(1);
assertThat(profiles.get(0).key()).isEqualTo("abcd");
dbSession.commit();
- ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()).setProfileName("SonarQube way"));
+ ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()).setProfileName("SonarQube way"));
List<QProfile> profiles = newArrayList(ref.qProfiles());
assertThat(profiles).hasSize(1);
assertThat(profiles.get(0).key()).isEqualTo("abcd");
dbSession.commit();
- ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey("project"));
+ ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey("project"));
List<QProfile> profiles = newArrayList(ref.qProfiles());
assertThat(profiles).hasSize(1);
assertThat(profiles.get(0).key()).isEqualTo("abcd");
dbSession.commit();
- ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
+ ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
List<QProfile> profiles = newArrayList(ref.qProfiles());
assertThat(profiles).hasSize(1);
assertThat(profiles.get(0).key()).isEqualTo("abcd");
dbSession.commit();
- ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
+ ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
List<ActiveRule> activeRules = newArrayList(ref.activeRules());
assertThat(activeRules).hasSize(1);
assertThat(activeRules.get(0).repositoryKey()).isEqualTo("squid");
dbSession.commit();
- ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
+ ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
List<ActiveRule> activeRules = newArrayList(ref.activeRules());
assertThat(activeRules).extracting("repositoryKey").containsOnly(RuleKey.MANUAL_REPOSITORY_KEY);
assertThat(activeRules).extracting("ruleKey").containsOnly("manualRuleKey");
dbSession.commit();
- ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
+ ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
assertThat(ref.fileDataByPath(project.key())).hasSize(1);
FileData fileData = ref.fileData(project.key(), file.path());
assertThat(fileData.hash()).isEqualTo("123456");
dbSession.commit();
- ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
+ ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(project.key()));
assertThat(ref.fileData(project.key(), projectFile.path()).hash()).isEqualTo("123456");
assertThat(ref.fileData(module.key(), moduleFile.path()).hash()).isEqualTo("789456");
}
dbSession.commit();
- ProjectReferentials ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(module.key()));
+ ProjectRepository ref = loader.load(ProjectRepositoryQuery.create().setModuleKey(module.key()));
assertThat(ref.fileData(module.key(), moduleFile.path()).hash()).isEqualTo("789456");
assertThat(ref.fileData(project.key(), projectFile.path())).isNull();
}
+++ /dev/null
-/*
- * 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.protocol.input;
-
-import org.sonar.batch.protocol.GsonHelper;
-
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-
-import java.util.*;
-
-/**
- * Container for all project data going from server to batch.
- * This is not an API since server and batch always share the same version.
- */
-public class ProjectReferentials {
-
- private long timestamp;
- private Map<String, QProfile> qprofilesByLanguage = new HashMap<String, QProfile>();
- private Collection<ActiveRule> activeRules = new ArrayList<ActiveRule>();
- private Map<String, Map<String, String>> settingsByModule = new HashMap<String, Map<String, String>>();
- private Map<String, Map<String, FileData>> fileDataByModuleAndPath = new HashMap<String, Map<String, FileData>>();
- private Date lastAnalysisDate;
-
- public Map<String, String> settings(String moduleKey) {
- return settingsByModule.containsKey(moduleKey) ? settingsByModule.get(moduleKey) : Collections.<String, String>emptyMap();
- }
-
- public ProjectReferentials addSettings(String moduleKey, Map<String, String> settings) {
- Map<String, String> existingSettings = settingsByModule.get(moduleKey);
- if (existingSettings == null) {
- existingSettings = new HashMap<>();
- settingsByModule.put(moduleKey, existingSettings);
- }
- existingSettings.putAll(settings);
- return this;
- }
-
- public Collection<QProfile> qProfiles() {
- return qprofilesByLanguage.values();
- }
-
- public ProjectReferentials addQProfile(QProfile qProfile) {
- qprofilesByLanguage.put(qProfile.language(), qProfile);
- return this;
- }
-
- public Collection<ActiveRule> activeRules() {
- return activeRules;
- }
-
- public ProjectReferentials addActiveRule(ActiveRule activeRule) {
- activeRules.add(activeRule);
- return this;
- }
-
- public Map<String, FileData> fileDataByPath(String moduleKey) {
- return fileDataByModuleAndPath.containsKey(moduleKey) ? fileDataByModuleAndPath.get(moduleKey) : Collections.<String, FileData>emptyMap();
- }
-
- public ProjectReferentials addFileData(String moduleKey, String path, FileData fileData) {
- Map<String, FileData> existingFileDataByPath = fileDataByModuleAndPath.get(moduleKey);
- if (existingFileDataByPath == null) {
- existingFileDataByPath = new HashMap<>();
- fileDataByModuleAndPath.put(moduleKey, existingFileDataByPath);
- }
- existingFileDataByPath.put(path, fileData);
- return this;
- }
-
- @CheckForNull
- public FileData fileData(String projectKey, String path) {
- return fileDataByPath(projectKey).get(path);
- }
-
- public long timestamp() {
- return timestamp;
- }
-
- public void setTimestamp(long timestamp) {
- this.timestamp = timestamp;
- }
-
- @CheckForNull
- public Date lastAnalysisDate() {
- return lastAnalysisDate;
- }
-
- public void setLastAnalysisDate(@Nullable Date lastAnalysisDate) {
- this.lastAnalysisDate = lastAnalysisDate;
- }
-
- public String toJson() {
- return GsonHelper.create().toJson(this);
- }
-
- public static ProjectReferentials fromJson(String json) {
- return GsonHelper.create().fromJson(json, ProjectReferentials.class);
- }
-
-}
--- /dev/null
+/*
+ * 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.protocol.input;
+
+import org.sonar.batch.protocol.GsonHelper;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+import java.util.*;
+
+/**
+ * Container for all project data going from server to batch.
+ * This is not an API since server and batch always share the same version.
+ */
+public class ProjectRepository {
+
+ private long timestamp;
+ private Map<String, QProfile> qprofilesByLanguage = new HashMap<String, QProfile>();
+ private Collection<ActiveRule> activeRules = new ArrayList<ActiveRule>();
+ private Map<String, Map<String, String>> settingsByModule = new HashMap<String, Map<String, String>>();
+ private Map<String, Map<String, FileData>> fileDataByModuleAndPath = new HashMap<String, Map<String, FileData>>();
+ private Date lastAnalysisDate;
+
+ public Map<String, String> settings(String moduleKey) {
+ return settingsByModule.containsKey(moduleKey) ? settingsByModule.get(moduleKey) : Collections.<String, String>emptyMap();
+ }
+
+ public ProjectRepository addSettings(String moduleKey, Map<String, String> settings) {
+ Map<String, String> existingSettings = settingsByModule.get(moduleKey);
+ if (existingSettings == null) {
+ existingSettings = new HashMap<>();
+ settingsByModule.put(moduleKey, existingSettings);
+ }
+ existingSettings.putAll(settings);
+ return this;
+ }
+
+ public Collection<QProfile> qProfiles() {
+ return qprofilesByLanguage.values();
+ }
+
+ public ProjectRepository addQProfile(QProfile qProfile) {
+ qprofilesByLanguage.put(qProfile.language(), qProfile);
+ return this;
+ }
+
+ public Collection<ActiveRule> activeRules() {
+ return activeRules;
+ }
+
+ public ProjectRepository addActiveRule(ActiveRule activeRule) {
+ activeRules.add(activeRule);
+ return this;
+ }
+
+ public Map<String, FileData> fileDataByPath(String moduleKey) {
+ return fileDataByModuleAndPath.containsKey(moduleKey) ? fileDataByModuleAndPath.get(moduleKey) : Collections.<String, FileData>emptyMap();
+ }
+
+ public ProjectRepository addFileData(String moduleKey, String path, FileData fileData) {
+ Map<String, FileData> existingFileDataByPath = fileDataByModuleAndPath.get(moduleKey);
+ if (existingFileDataByPath == null) {
+ existingFileDataByPath = new HashMap<>();
+ fileDataByModuleAndPath.put(moduleKey, existingFileDataByPath);
+ }
+ existingFileDataByPath.put(path, fileData);
+ return this;
+ }
+
+ @CheckForNull
+ public FileData fileData(String projectKey, String path) {
+ return fileDataByPath(projectKey).get(path);
+ }
+
+ public long timestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(long timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ @CheckForNull
+ public Date lastAnalysisDate() {
+ return lastAnalysisDate;
+ }
+
+ public void setLastAnalysisDate(@Nullable Date lastAnalysisDate) {
+ this.lastAnalysisDate = lastAnalysisDate;
+ }
+
+ public String toJson() {
+ return GsonHelper.create().toJson(this);
+ }
+
+ public static ProjectRepository fromJson(String json) {
+ return GsonHelper.create().fromJson(json, ProjectRepository.class);
+ }
+
+}
*/
package org.sonar.batch.protocol.input.issues;
-import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
-public class PreviousIssue {
+import java.io.Serializable;
+
+public class PreviousIssue implements Serializable {
private String key;
private String componentKey;
private String ruleRepo;
private Integer line;
private String message;
+ // For manual issues and when user has overriden severity
private String overriddenSeverity;
private String resolution;
private String status;
return key;
}
- public PreviousIssue setComponentKey(@Nullable String key) {
+ public PreviousIssue setComponentKey(String key) {
this.componentKey = key;
return this;
}
return this;
}
- @CheckForNull
public String overriddenSeverity() {
return overriddenSeverity;
}
}
}
- public Iterable<PreviousIssue> getIssues(final Reader reader) {
+ public static Iterable<PreviousIssue> getIssues(final Reader reader) {
return new Iterable<PreviousIssue>() {
@Override
};
}
- private final class PreviousIssueIterator implements Iterator<PreviousIssue> {
+ private final static class PreviousIssueIterator implements Iterator<PreviousIssue> {
private JsonReader jsonreader;
+ private final Gson gson = GsonHelper.create();
public PreviousIssueIterator(Reader reader) {
try {
+++ /dev/null
-/*
- * 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.protocol.input;
-
-import org.json.JSONException;
-import org.junit.Test;
-import org.skyscreamer.jsonassert.JSONAssert;
-
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.HashMap;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class ProjectReferentialsTest {
-
- @Test
- public void testToJson() throws Exception {
- ProjectReferentials ref = new ProjectReferentials();
- assertThat(ref.settings("foo")).isEmpty();
-
- ref.addQProfile(new QProfile("squid-java", "Java", "java", new SimpleDateFormat("dd/MM/yyyy").parse("14/03/1984")));
- HashMap<String, String> settings = new HashMap<String, String>();
- settings.put("prop1", "value1");
- ref.addSettings("foo", settings);
- settings = new HashMap<String, String>();
- settings.put("prop2", "value2");
- ref.addSettings("foo", settings);
- ref.settings("foo").put("prop", "value");
- ActiveRule activeRule = new ActiveRule("repo", "rule", "Rule", "MAJOR", "rule", "java");
- activeRule.addParam("param1", "value1");
- ref.addActiveRule(activeRule);
- ref.setLastAnalysisDate(new SimpleDateFormat("dd/MM/yyyy").parse("31/10/2014"));
- ref.setTimestamp(10);
- ref.addFileData("foo", "src/main/java/Foo.java", new FileData("xyz", true, "1=12345,2=3456", "1=345,2=345", "1=henryju,2=gaudin"));
- ref.addFileData("foo", "src/main/java/Foo2.java", new FileData("xyz", false, "1=12345,2=3456", "1=345,2=345", "1=henryju,2=gaudin"));
-
- JSONAssert
- .assertEquals(
- "{timestamp:10,"
- + "qprofilesByLanguage:{java:{key:\"squid-java\",name:Java,language:java,rulesUpdatedAt:\"1984-03-14T00:00:00+0100\"}},"
- + "activeRules:[{repositoryKey:repo,ruleKey:rule,name:Rule,severity:MAJOR,internalKey:rule,language:java,params:{param1:value1}}],"
- + "settingsByModule:{foo:{prop1:value1,prop2:value2,prop:value}},"
- + "fileDataByModuleAndPath:{foo:{\"src/main/java/Foo.java\":{hash:xyz,needBlame:true,scmLastCommitDatetimesByLine:\"1\u003d12345,2\u003d3456\",scmRevisionsByLine:\"1\u003d345,2\u003d345\",scmAuthorsByLine:\"1\u003dhenryju,2\u003dgaudin\"},"
- + "\"src/main/java/Foo2.java\":{hash:xyz,needBlame:false,scmLastCommitDatetimesByLine:\"1\u003d12345,2\u003d3456\",scmRevisionsByLine:\"1\u003d345,2\u003d345\",scmAuthorsByLine:\"1\u003dhenryju,2\u003dgaudin\"}}},"
- + "lastAnalysisDate:\"2014-10-31T00:00:00+0100\"}",
- ref.toJson(), true);
- }
-
- @Test
- public void testFromJson() throws JSONException, ParseException {
- ProjectReferentials ref = ProjectReferentials
- .fromJson("{timestamp:1,"
- + "qprofilesByLanguage:{java:{key:\"squid-java\",name:Java,language:java,rulesUpdatedAt:\"1984-03-14T00:00:00+0100\"}},"
- + "activeRules:[{repositoryKey:repo,ruleKey:rule,name:Rule,severity:MAJOR,internalKey:rule1,language:java,params:{param1:value1}}],"
- + "settingsByModule:{foo:{prop:value}},"
- + "fileDataByModuleAndPath:{foo:{\"src/main/java/Foo.java\":{hash:xyz,needBlame:true,scmLastCommitDatetimesByLine:\"1\u003d12345,2\u003d3456\",scmRevisionsByLine:\"1\u003d345,2\u003d345\",scmAuthorsByLine:\"1\u003dhenryju,2\u003dgaudin\"}}},"
- + "lastAnalysisDate:\"2014-10-31T00:00:00+0100\"}");
-
- assertThat(ref.timestamp()).isEqualTo(1);
-
- ActiveRule activeRule = ref.activeRules().iterator().next();
- assertThat(activeRule.ruleKey()).isEqualTo("rule");
- assertThat(activeRule.repositoryKey()).isEqualTo("repo");
- assertThat(activeRule.name()).isEqualTo("Rule");
- assertThat(activeRule.severity()).isEqualTo("MAJOR");
- assertThat(activeRule.internalKey()).isEqualTo("rule1");
- assertThat(activeRule.language()).isEqualTo("java");
- assertThat(activeRule.params()).containsEntry("param1", "value1");
- assertThat(activeRule.param("param1")).isEqualTo("value1");
- QProfile qProfile = ref.qProfiles().iterator().next();
- assertThat(qProfile.key()).isEqualTo("squid-java");
- assertThat(qProfile.name()).isEqualTo("Java");
- assertThat(qProfile.rulesUpdatedAt()).isEqualTo(new SimpleDateFormat("dd/MM/yyyy").parse("14/03/1984"));
- assertThat(ref.settings("foo")).containsEntry("prop", "value");
-
- assertThat(ref.fileData("foo2", "src/main/java/Foo3.java")).isNull();
-
- assertThat(ref.fileData("foo", "src/main/java/Foo.java").hash()).isEqualTo("xyz");
- assertThat(ref.fileData("foo", "src/main/java/Foo.java").needBlame()).isTrue();
- assertThat(ref.fileData("foo", "src/main/java/Foo.java").scmAuthorsByLine()).isEqualTo("1=henryju,2=gaudin");
- assertThat(ref.fileData("foo", "src/main/java/Foo.java").scmLastCommitDatetimesByLine()).isEqualTo("1=12345,2=3456");
- assertThat(ref.fileData("foo", "src/main/java/Foo.java").scmRevisionsByLine()).isEqualTo("1=345,2=345");
-
- assertThat(ref.lastAnalysisDate()).isEqualTo(new SimpleDateFormat("dd/MM/yyyy").parse("31/10/2014"));
- }
-}
--- /dev/null
+/*
+ * 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.protocol.input;
+
+import org.json.JSONException;
+import org.junit.Test;
+import org.skyscreamer.jsonassert.JSONAssert;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.HashMap;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ProjectRepositoryTest {
+
+ @Test
+ public void testToJson() throws Exception {
+ ProjectRepository ref = new ProjectRepository();
+ assertThat(ref.settings("foo")).isEmpty();
+
+ ref.addQProfile(new QProfile("squid-java", "Java", "java", new SimpleDateFormat("dd/MM/yyyy").parse("14/03/1984")));
+ HashMap<String, String> settings = new HashMap<String, String>();
+ settings.put("prop1", "value1");
+ ref.addSettings("foo", settings);
+ settings = new HashMap<String, String>();
+ settings.put("prop2", "value2");
+ ref.addSettings("foo", settings);
+ ref.settings("foo").put("prop", "value");
+ ActiveRule activeRule = new ActiveRule("repo", "rule", "Rule", "MAJOR", "rule", "java");
+ activeRule.addParam("param1", "value1");
+ ref.addActiveRule(activeRule);
+ ref.setLastAnalysisDate(new SimpleDateFormat("dd/MM/yyyy").parse("31/10/2014"));
+ ref.setTimestamp(10);
+ ref.addFileData("foo", "src/main/java/Foo.java", new FileData("xyz", true, "1=12345,2=3456", "1=345,2=345", "1=henryju,2=gaudin"));
+ ref.addFileData("foo", "src/main/java/Foo2.java", new FileData("xyz", false, "1=12345,2=3456", "1=345,2=345", "1=henryju,2=gaudin"));
+
+ JSONAssert
+ .assertEquals(
+ "{timestamp:10,"
+ + "qprofilesByLanguage:{java:{key:\"squid-java\",name:Java,language:java,rulesUpdatedAt:\"1984-03-14T00:00:00+0100\"}},"
+ + "activeRules:[{repositoryKey:repo,ruleKey:rule,name:Rule,severity:MAJOR,internalKey:rule,language:java,params:{param1:value1}}],"
+ + "settingsByModule:{foo:{prop1:value1,prop2:value2,prop:value}},"
+ + "fileDataByModuleAndPath:{foo:{\"src/main/java/Foo.java\":{hash:xyz,needBlame:true,scmLastCommitDatetimesByLine:\"1\u003d12345,2\u003d3456\",scmRevisionsByLine:\"1\u003d345,2\u003d345\",scmAuthorsByLine:\"1\u003dhenryju,2\u003dgaudin\"},"
+ + "\"src/main/java/Foo2.java\":{hash:xyz,needBlame:false,scmLastCommitDatetimesByLine:\"1\u003d12345,2\u003d3456\",scmRevisionsByLine:\"1\u003d345,2\u003d345\",scmAuthorsByLine:\"1\u003dhenryju,2\u003dgaudin\"}}},"
+ + "lastAnalysisDate:\"2014-10-31T00:00:00+0100\"}",
+ ref.toJson(), true);
+ }
+
+ @Test
+ public void testFromJson() throws JSONException, ParseException {
+ ProjectRepository ref = ProjectRepository
+ .fromJson("{timestamp:1,"
+ + "qprofilesByLanguage:{java:{key:\"squid-java\",name:Java,language:java,rulesUpdatedAt:\"1984-03-14T00:00:00+0100\"}},"
+ + "activeRules:[{repositoryKey:repo,ruleKey:rule,name:Rule,severity:MAJOR,internalKey:rule1,language:java,params:{param1:value1}}],"
+ + "settingsByModule:{foo:{prop:value}},"
+ + "fileDataByModuleAndPath:{foo:{\"src/main/java/Foo.java\":{hash:xyz,needBlame:true,scmLastCommitDatetimesByLine:\"1\u003d12345,2\u003d3456\",scmRevisionsByLine:\"1\u003d345,2\u003d345\",scmAuthorsByLine:\"1\u003dhenryju,2\u003dgaudin\"}}},"
+ + "lastAnalysisDate:\"2014-10-31T00:00:00+0100\"}");
+
+ assertThat(ref.timestamp()).isEqualTo(1);
+
+ ActiveRule activeRule = ref.activeRules().iterator().next();
+ assertThat(activeRule.ruleKey()).isEqualTo("rule");
+ assertThat(activeRule.repositoryKey()).isEqualTo("repo");
+ assertThat(activeRule.name()).isEqualTo("Rule");
+ assertThat(activeRule.severity()).isEqualTo("MAJOR");
+ assertThat(activeRule.internalKey()).isEqualTo("rule1");
+ assertThat(activeRule.language()).isEqualTo("java");
+ assertThat(activeRule.params()).containsEntry("param1", "value1");
+ assertThat(activeRule.param("param1")).isEqualTo("value1");
+ QProfile qProfile = ref.qProfiles().iterator().next();
+ assertThat(qProfile.key()).isEqualTo("squid-java");
+ assertThat(qProfile.name()).isEqualTo("Java");
+ assertThat(qProfile.rulesUpdatedAt()).isEqualTo(new SimpleDateFormat("dd/MM/yyyy").parse("14/03/1984"));
+ assertThat(ref.settings("foo")).containsEntry("prop", "value");
+
+ assertThat(ref.fileData("foo2", "src/main/java/Foo3.java")).isNull();
+
+ assertThat(ref.fileData("foo", "src/main/java/Foo.java").hash()).isEqualTo("xyz");
+ assertThat(ref.fileData("foo", "src/main/java/Foo.java").needBlame()).isTrue();
+ assertThat(ref.fileData("foo", "src/main/java/Foo.java").scmAuthorsByLine()).isEqualTo("1=henryju,2=gaudin");
+ assertThat(ref.fileData("foo", "src/main/java/Foo.java").scmLastCommitDatetimesByLine()).isEqualTo("1=12345,2=3456");
+ assertThat(ref.fileData("foo", "src/main/java/Foo.java").scmRevisionsByLine()).isEqualTo("1=345,2=345");
+
+ assertThat(ref.lastAnalysisDate()).isEqualTo(new SimpleDateFormat("dd/MM/yyyy").parse("31/10/2014"));
+ }
+}
import org.sonar.batch.design.MavenDependenciesSensor;
import org.sonar.batch.design.ProjectDsmDecorator;
import org.sonar.batch.design.SubProjectDsmDecorator;
+import org.sonar.batch.issue.tracking.IssueTracking;
import org.sonar.batch.maven.DefaultMavenPluginExecutor;
import org.sonar.batch.maven.MavenProjectBootstrapper;
import org.sonar.batch.maven.MavenProjectBuilder;
LinesSensor.class,
+ // Issues tracking
+ IssueTracking.class,
+
// Reports
ConsoleReport.class,
JSONReport.class,
import org.sonar.batch.components.PastSnapshotFinderByPreviousAnalysis;
import org.sonar.batch.components.PastSnapshotFinderByPreviousVersion;
import org.sonar.batch.components.PastSnapshotFinderByVersion;
-import org.sonar.batch.referential.DefaultGlobalReferentialsLoader;
-import org.sonar.batch.referential.DefaultProjectReferentialsLoader;
-import org.sonar.batch.referential.GlobalReferentialsLoader;
-import org.sonar.batch.referential.GlobalReferentialsProvider;
-import org.sonar.batch.referential.ProjectReferentialsLoader;
+import org.sonar.batch.repository.DefaultGlobalReferentialsLoader;
+import org.sonar.batch.repository.DefaultPreviousIssuesLoader;
+import org.sonar.batch.repository.DefaultProjectReferentialsLoader;
+import org.sonar.batch.repository.GlobalReferentialsLoader;
+import org.sonar.batch.repository.GlobalReferentialsProvider;
+import org.sonar.batch.repository.PreviousIssuesLoader;
+import org.sonar.batch.repository.ProjectRepositoriesLoader;
import org.sonar.batch.user.UserRepository;
import org.sonar.core.cluster.NullQueue;
import org.sonar.core.config.Logback;
if (getComponentByType(GlobalReferentialsLoader.class) == null) {
add(DefaultGlobalReferentialsLoader.class);
}
- if (getComponentByType(ProjectReferentialsLoader.class) == null) {
+ if (getComponentByType(ProjectRepositoriesLoader.class) == null) {
add(DefaultProjectReferentialsLoader.class);
}
+ if (getComponentByType(PreviousIssuesLoader.class) == null) {
+ add(DefaultPreviousIssuesLoader.class);
+ }
}
private void addDatabaseComponents() {
}
}
- private InputSupplier<InputStream> doRequest(String pathStartingWithSlash, String requestMethod, @Nullable Integer timeoutMillis) {
+ public InputSupplier<InputStream> doRequest(String pathStartingWithSlash, String requestMethod, @Nullable Integer timeoutMillis) {
Preconditions.checkArgument(pathStartingWithSlash.startsWith("/"), "Path must start with slash /");
String path = StringEscapeUtils.escapeHtml(pathStartingWithSlash);
// dedicated cache for libraries
private final Map<Library, BatchResource> libraries = Maps.newLinkedHashMap();
+ private BatchResource root;
+
@CheckForNull
public BatchResource get(String componentKey) {
return resources.get(componentKey);
String componentKey = resource.getEffectiveKey();
Preconditions.checkState(!Strings.isNullOrEmpty(componentKey), "Missing resource effective key");
BatchResource parent = parentResource != null ? get(parentResource.getEffectiveKey()) : null;
- BatchResource batchResource = new BatchResource((long) resources.size() + 1, resource, parent);
+ BatchResource batchResource = new BatchResource(resources.size() + 1, resource, parent);
if (!(resource instanceof Library)) {
// Libraries can have the same effective key than a project so we can't cache by effectiveKey
resources.put(componentKey, batchResource);
+ if (parent == null) {
+ root = batchResource;
+ }
} else {
libraries.put((Library) resource, batchResource);
}
public Collection<BatchResource> allLibraries() {
return libraries.values();
}
+
+ public BatchResource getRoot() {
+ return root;
+ }
}
--- /dev/null
+/*
+ * 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.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.lang.ObjectUtils;
+
+import java.util.Collection;
+
+/**
+ * Wraps a {@link Sequence} to assign hash codes to elements.
+ */
+public final class FileHashes {
+
+ private final String[] hashes;
+ private final Multimap<String, Integer> linesByHash;
+
+ private FileHashes(String[] hashes, Multimap<String, Integer> linesByHash) {
+ this.hashes = hashes;
+ this.linesByHash = linesByHash;
+ }
+
+ public static FileHashes create(String[] hashes) {
+ int size = hashes.length;
+ Multimap<String, Integer> linesByHash = LinkedHashMultimap.create();
+ for (int i = 0; i < size; i++) {
+ // indices in array are shifted one line before
+ linesByHash.put(hashes[i], i + 1);
+ }
+ return new FileHashes(hashes, linesByHash);
+ }
+
+ public static FileHashes create(byte[][] hashes) {
+ int size = hashes.length;
+ Multimap<String, Integer> linesByHash = LinkedHashMultimap.create();
+ String[] hexHashes = new String[size];
+ for (int i = 0; i < size; i++) {
+ String hash = hashes[i] != null ? Hex.encodeHexString(hashes[i]) : "";
+ hexHashes[i] = hash;
+ // indices in array are shifted one line before
+ linesByHash.put(hash, i + 1);
+ }
+ return new FileHashes(hexHashes, linesByHash);
+ }
+
+ public int length() {
+ return hashes.length;
+ }
+
+ public Collection<Integer> getLinesForHash(String hash) {
+ return linesByHash.get(hash);
+ }
+
+ public String getHash(int line) {
+ // indices in array are shifted one line before
+ return (String) ObjectUtils.defaultIfNull(hashes[line - 1], "");
+ }
+}
--- /dev/null
+/*
+ * 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 org.apache.commons.lang.time.DateUtils;
+import org.apache.ibatis.session.ResultContext;
+import org.apache.ibatis.session.ResultHandler;
+import org.sonar.api.batch.Sensor;
+import org.sonar.api.batch.SensorContext;
+import org.sonar.api.resources.Project;
+import org.sonar.core.DryRunIncompatible;
+import org.sonar.core.issue.db.IssueChangeDao;
+import org.sonar.core.issue.db.IssueChangeDto;
+import org.sonar.core.issue.db.IssueDao;
+import org.sonar.core.issue.db.IssueDto;
+
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * Load all the issues referenced during the previous scan.
+ */
+@DryRunIncompatible
+public class InitialOpenIssuesSensor implements Sensor {
+
+ private final InitialOpenIssuesStack initialOpenIssuesStack;
+ private final IssueDao issueDao;
+ private final IssueChangeDao issueChangeDao;
+
+ public InitialOpenIssuesSensor(InitialOpenIssuesStack initialOpenIssuesStack, IssueDao issueDao, IssueChangeDao issueChangeDao) {
+ this.initialOpenIssuesStack = initialOpenIssuesStack;
+ this.issueDao = issueDao;
+ this.issueChangeDao = issueChangeDao;
+ }
+
+ @Override
+ public boolean shouldExecuteOnProject(Project project) {
+ return true;
+ }
+
+ @Override
+ public void analyse(Project project, SensorContext context) {
+ // Adding one second is a hack for resolving conflicts with concurrent user
+ // changes during issue persistence
+ final Date now = DateUtils.addSeconds(DateUtils.truncate(new Date(), Calendar.MILLISECOND), 1);
+
+ issueDao.selectNonClosedIssuesByModule(project.getId(), new ResultHandler() {
+ @Override
+ public void handleResult(ResultContext rc) {
+ IssueDto dto = (IssueDto) rc.getResultObject();
+ dto.setSelectedAt(now.getTime());
+ initialOpenIssuesStack.addIssue(dto);
+ }
+ });
+
+ issueChangeDao.selectChangelogOnNonClosedIssuesByModuleAndType(project.getId(), new ResultHandler() {
+ @Override
+ public void handleResult(ResultContext rc) {
+ IssueChangeDto dto = (IssueChangeDto) rc.getResultObject();
+ initialOpenIssuesStack.addChangelog(dto);
+ }
+ });
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+}
--- /dev/null
+/*
+ * 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 org.sonar.api.BatchExtension;
+import org.sonar.api.batch.InstantiationStrategy;
+import org.sonar.batch.index.Cache;
+import org.sonar.batch.index.Caches;
+import org.sonar.core.issue.db.IssueChangeDto;
+import org.sonar.core.issue.db.IssueDto;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+@InstantiationStrategy(InstantiationStrategy.PER_BATCH)
+public class InitialOpenIssuesStack implements BatchExtension {
+
+ private final Cache<IssueDto> issuesCache;
+ private final Cache<ArrayList<IssueChangeDto>> issuesChangelogCache;
+
+ public InitialOpenIssuesStack(Caches caches) {
+ issuesCache = caches.createCache("last-open-issues");
+ issuesChangelogCache = caches.createCache("issues-changelog");
+ }
+
+ public InitialOpenIssuesStack addIssue(IssueDto issueDto) {
+ issuesCache.put(issueDto.getComponentKey(), issueDto.getKee(), issueDto);
+ return this;
+ }
+
+ public List<PreviousIssue> selectAndRemoveIssues(String componentKey) {
+ Iterable<IssueDto> issues = issuesCache.values(componentKey);
+ List<PreviousIssue> result = newArrayList();
+ for (IssueDto issue : issues) {
+ result.add(new PreviousIssueFromDb(issue));
+ }
+ issuesCache.clear(componentKey);
+ return result;
+ }
+
+ public Iterable<IssueDto> selectAllIssues() {
+ return issuesCache.values();
+ }
+
+ public InitialOpenIssuesStack addChangelog(IssueChangeDto issueChangeDto) {
+ List<IssueChangeDto> changeDtos = issuesChangelogCache.get(issueChangeDto.getIssueKey());
+ if (changeDtos == null) {
+ changeDtos = newArrayList();
+ }
+ changeDtos.add(issueChangeDto);
+ issuesChangelogCache.put(issueChangeDto.getIssueKey(), newArrayList(changeDtos));
+ return this;
+ }
+
+ public List<IssueChangeDto> selectChangelog(String issueKey) {
+ List<IssueChangeDto> changeDtos = issuesChangelogCache.get(issueKey);
+ return changeDtos != null ? changeDtos : Collections.<IssueChangeDto>emptyList();
+ }
+
+ public void clear() {
+ issuesCache.clear();
+ issuesChangelogCache.clear();
+ }
+}
--- /dev/null
+/*
+ * 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 org.sonar.api.BatchExtension;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.IssueHandler;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.issue.internal.IssueChangeContext;
+import org.sonar.api.user.User;
+import org.sonar.core.issue.IssueUpdater;
+import org.sonar.core.user.DefaultUser;
+
+import javax.annotation.Nullable;
+
+public class IssueHandlers implements BatchExtension {
+ private final IssueHandler[] handlers;
+ private final DefaultContext context;
+
+ public IssueHandlers(IssueUpdater updater, IssueHandler[] handlers) {
+ this.handlers = handlers;
+ this.context = new DefaultContext(updater);
+ }
+
+ public IssueHandlers(IssueUpdater updater) {
+ this(updater, new IssueHandler[0]);
+ }
+
+ public void execute(DefaultIssue issue, IssueChangeContext changeContext) {
+ context.reset(issue, changeContext);
+ for (IssueHandler handler : handlers) {
+ handler.onIssue(context);
+ }
+ }
+
+ static class DefaultContext implements IssueHandler.Context {
+ private final IssueUpdater updater;
+ private DefaultIssue issue;
+ private IssueChangeContext changeContext;
+
+ private DefaultContext(IssueUpdater updater) {
+ this.updater = updater;
+ }
+
+ private void reset(DefaultIssue i, IssueChangeContext changeContext) {
+ this.issue = i;
+ this.changeContext = changeContext;
+ }
+
+ @Override
+ public Issue issue() {
+ return issue;
+ }
+
+ @Override
+ public boolean isNew() {
+ return issue.isNew();
+ }
+
+ @Override
+ public boolean isEndOfLife() {
+ return issue.isEndOfLife();
+ }
+
+ @Override
+ public IssueHandler.Context setLine(@Nullable Integer line) {
+ updater.setLine(issue, line);
+ return this;
+ }
+
+ @Override
+ public IssueHandler.Context setMessage(@Nullable String s) {
+ updater.setMessage(issue, s, changeContext);
+ return this;
+ }
+
+ @Override
+ public IssueHandler.Context setSeverity(String severity) {
+ updater.setSeverity(issue, severity, changeContext);
+ return this;
+ }
+
+ @Override
+ public IssueHandler.Context setAuthorLogin(@Nullable String login) {
+ updater.setAuthorLogin(issue, login, changeContext);
+ return this;
+ }
+
+ @Override
+ public IssueHandler.Context setEffortToFix(@Nullable Double d) {
+ updater.setEffortToFix(issue, d, changeContext);
+ return this;
+ }
+
+ @Override
+ public IssueHandler.Context setAttribute(String key, @Nullable String value) {
+ throw new UnsupportedOperationException("TODO");
+ }
+
+ @Override
+ public IssueHandler.Context assign(@Nullable String assignee) {
+ User user = null;
+ if(assignee != null) {
+ user = new DefaultUser().setLogin(assignee).setName(assignee);
+ }
+ updater.assign(issue, user, changeContext);
+ return this;
+ }
+
+ @Override
+ public IssueHandler.Context assign(@Nullable User user) {
+ updater.assign(issue, user, changeContext);
+ return this;
+ }
+
+ @Override
+ public IssueHandler.Context addComment(String text) {
+ updater.addComment(issue, text, changeContext);
+ return this;
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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.annotations.VisibleForTesting;
+import com.google.common.base.Objects;
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import org.sonar.api.BatchComponent;
+import org.sonar.api.batch.InstantiationStrategy;
+import org.sonar.api.issue.internal.DefaultIssue;
+
+import javax.annotation.Nullable;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+@InstantiationStrategy(InstantiationStrategy.PER_BATCH)
+public class IssueTracking implements BatchComponent {
+
+ /**
+ * @param sourceHashHolder Null when working on resource that is not a file (directory/project)
+ */
+ public IssueTrackingResult track(@Nullable SourceHashHolder sourceHashHolder, Collection<PreviousIssue> previousIssues, Collection<DefaultIssue> newIssues) {
+ IssueTrackingResult result = new IssueTrackingResult();
+
+ if (sourceHashHolder != null) {
+ setChecksumOnNewIssues(newIssues, sourceHashHolder);
+ }
+
+ // Map new issues with old ones
+ mapIssues(newIssues, previousIssues, sourceHashHolder, result);
+ return result;
+ }
+
+ private void setChecksumOnNewIssues(Collection<DefaultIssue> issues, SourceHashHolder sourceHashHolder) {
+ if (issues.isEmpty()) {
+ return;
+ }
+ for (DefaultIssue issue : issues) {
+ Integer line = issue.line();
+ if (line != null) {
+ issue.setChecksum(sourceHashHolder.getHashedSource().getHash(line));
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void mapIssues(Collection<DefaultIssue> newIssues, @Nullable Collection<PreviousIssue> previousIssues, @Nullable SourceHashHolder sourceHashHolder, IssueTrackingResult result) {
+ boolean hasLastScan = false;
+
+ if (previousIssues != null) {
+ hasLastScan = true;
+ mapLastIssues(newIssues, previousIssues, result);
+ }
+
+ // If each new issue matches an old one we can stop the matching mechanism
+ if (result.matched().size() != newIssues.size()) {
+ if (sourceHashHolder != null && hasLastScan) {
+ FileHashes hashedReference = sourceHashHolder.getHashedReference();
+ if (hashedReference != null) {
+ mapNewissues(hashedReference, sourceHashHolder.getHashedSource(), newIssues, result);
+ }
+ }
+ mapIssuesOnSameRule(newIssues, result);
+ }
+ }
+
+ private void mapLastIssues(Collection<DefaultIssue> newIssues, Collection<PreviousIssue> previousIssues, IssueTrackingResult result) {
+ for (PreviousIssue lastIssue : previousIssues) {
+ result.addUnmatched(lastIssue);
+ }
+
+ // Match the key of the issue. (For manual issues)
+ for (DefaultIssue newIssue : newIssues) {
+ mapIssue(newIssue, result.unmatchedByKeyForRule(newIssue.ruleKey()).get(newIssue.key()), result);
+ }
+
+ // Try first to match issues on same rule with same line and with same checksum (but not necessarily with same message)
+ for (DefaultIssue newIssue : newIssues) {
+ if (isNotAlreadyMapped(newIssue, result)) {
+ mapIssue(
+ newIssue,
+ findLastIssueWithSameLineAndChecksum(newIssue, result),
+ result);
+ }
+ }
+ }
+
+ private void mapNewissues(FileHashes hashedReference, FileHashes hashedSource, Collection<DefaultIssue> newIssues, IssueTrackingResult result) {
+
+ IssueTrackingBlocksRecognizer rec = new IssueTrackingBlocksRecognizer(hashedReference, hashedSource);
+
+ RollingFileHashes a = RollingFileHashes.create(hashedReference, 5);
+ RollingFileHashes b = RollingFileHashes.create(hashedSource, 5);
+
+ Multimap<Integer, DefaultIssue> newIssuesByLines = newIssuesByLines(newIssues, rec, result);
+ Multimap<Integer, PreviousIssue> lastIssuesByLines = lastIssuesByLines(result.unmatched(), rec);
+
+ Map<Integer, HashOccurrence> map = Maps.newHashMap();
+
+ for (Integer line : lastIssuesByLines.keySet()) {
+ int hash = a.getHash(line);
+ HashOccurrence hashOccurrence = map.get(hash);
+ if (hashOccurrence == null) {
+ // first occurrence in A
+ hashOccurrence = new HashOccurrence();
+ hashOccurrence.lineA = line;
+ hashOccurrence.countA = 1;
+ map.put(hash, hashOccurrence);
+ } else {
+ hashOccurrence.countA++;
+ }
+ }
+
+ for (Integer line : newIssuesByLines.keySet()) {
+ int hash = b.getHash(line);
+ HashOccurrence hashOccurrence = map.get(hash);
+ if (hashOccurrence != null) {
+ hashOccurrence.lineB = line;
+ hashOccurrence.countB++;
+ }
+ }
+
+ for (HashOccurrence hashOccurrence : map.values()) {
+ if (hashOccurrence.countA == 1 && hashOccurrence.countB == 1) {
+ // Guaranteed that lineA has been moved to lineB, so we can map all issues on lineA to all issues on lineB
+ map(newIssuesByLines.get(hashOccurrence.lineB), lastIssuesByLines.get(hashOccurrence.lineA), result);
+ lastIssuesByLines.removeAll(hashOccurrence.lineA);
+ newIssuesByLines.removeAll(hashOccurrence.lineB);
+ }
+ }
+
+ // Check if remaining number of lines exceeds threshold
+ if (lastIssuesByLines.keySet().size() * newIssuesByLines.keySet().size() < 250000) {
+ List<LinePair> possibleLinePairs = Lists.newArrayList();
+ for (Integer oldLine : lastIssuesByLines.keySet()) {
+ for (Integer newLine : newIssuesByLines.keySet()) {
+ int weight = rec.computeLengthOfMaximalBlock(oldLine, newLine);
+ possibleLinePairs.add(new LinePair(oldLine, newLine, weight));
+ }
+ }
+ Collections.sort(possibleLinePairs, LINE_PAIR_COMPARATOR);
+ for (LinePair linePair : possibleLinePairs) {
+ // High probability that lineA has been moved to lineB, so we can map all Issues on lineA to all Issues on lineB
+ map(newIssuesByLines.get(linePair.lineB), lastIssuesByLines.get(linePair.lineA), result);
+ }
+ }
+ }
+
+ private void mapIssuesOnSameRule(Collection<DefaultIssue> newIssues, IssueTrackingResult result) {
+ // Try then to match issues on same rule with same message and with same checksum
+ for (DefaultIssue newIssue : newIssues) {
+ if (isNotAlreadyMapped(newIssue, result)) {
+ mapIssue(
+ newIssue,
+ findLastIssueWithSameChecksumAndMessage(newIssue, result.unmatchedByKeyForRule(newIssue.ruleKey()).values()),
+ result);
+ }
+ }
+
+ // Try then to match issues on same rule with same line and with same message
+ for (DefaultIssue newIssue : newIssues) {
+ if (isNotAlreadyMapped(newIssue, result)) {
+ mapIssue(
+ newIssue,
+ findLastIssueWithSameLineAndMessage(newIssue, result.unmatchedByKeyForRule(newIssue.ruleKey()).values()),
+ result);
+ }
+ }
+
+ // Last check: match issue if same rule and same checksum but different line and different message
+ // See SONAR-2812
+ for (DefaultIssue newIssue : newIssues) {
+ if (isNotAlreadyMapped(newIssue, result)) {
+ mapIssue(
+ newIssue,
+ findLastIssueWithSameChecksum(newIssue, result.unmatchedByKeyForRule(newIssue.ruleKey()).values()),
+ result);
+ }
+ }
+ }
+
+ private void map(Collection<DefaultIssue> newIssues, Collection<PreviousIssue> previousIssues, IssueTrackingResult result) {
+ for (DefaultIssue newIssue : newIssues) {
+ if (isNotAlreadyMapped(newIssue, result)) {
+ for (PreviousIssue previousIssue : previousIssues) {
+ if (isNotAlreadyMapped(previousIssue, result) && Objects.equal(newIssue.ruleKey(), previousIssue.ruleKey())) {
+ mapIssue(newIssue, previousIssue, result);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ private Multimap<Integer, DefaultIssue> newIssuesByLines(Collection<DefaultIssue> newIssues, IssueTrackingBlocksRecognizer rec, IssueTrackingResult result) {
+ Multimap<Integer, DefaultIssue> newIssuesByLines = LinkedHashMultimap.create();
+ for (DefaultIssue newIssue : newIssues) {
+ if (isNotAlreadyMapped(newIssue, result) && rec.isValidLineInSource(newIssue.line())) {
+ newIssuesByLines.put(newIssue.line(), newIssue);
+ }
+ }
+ return newIssuesByLines;
+ }
+
+ private Multimap<Integer, PreviousIssue> lastIssuesByLines(Collection<PreviousIssue> previousIssues, IssueTrackingBlocksRecognizer rec) {
+ Multimap<Integer, PreviousIssue> previousIssuesByLines = LinkedHashMultimap.create();
+ for (PreviousIssue previousIssue : previousIssues) {
+ if (rec.isValidLineInReference(previousIssue.line())) {
+ previousIssuesByLines.put(previousIssue.line(), previousIssue);
+ }
+ }
+ return previousIssuesByLines;
+ }
+
+ private PreviousIssue findLastIssueWithSameChecksum(DefaultIssue newIssue, Collection<PreviousIssue> previousIssues) {
+ for (PreviousIssue previousIssue : previousIssues) {
+ if (isSameChecksum(newIssue, previousIssue)) {
+ return previousIssue;
+ }
+ }
+ return null;
+ }
+
+ private PreviousIssue findLastIssueWithSameLineAndMessage(DefaultIssue newIssue, Collection<PreviousIssue> previousIssues) {
+ for (PreviousIssue previousIssue : previousIssues) {
+ if (isSameLine(newIssue, previousIssue) && isSameMessage(newIssue, previousIssue)) {
+ return previousIssue;
+ }
+ }
+ return null;
+ }
+
+ private PreviousIssue findLastIssueWithSameChecksumAndMessage(DefaultIssue newIssue, Collection<PreviousIssue> previousIssues) {
+ for (PreviousIssue previousIssue : previousIssues) {
+ if (isSameChecksum(newIssue, previousIssue) && isSameMessage(newIssue, previousIssue)) {
+ return previousIssue;
+ }
+ }
+ return null;
+ }
+
+ private PreviousIssue findLastIssueWithSameLineAndChecksum(DefaultIssue newIssue, IssueTrackingResult result) {
+ Collection<PreviousIssue> sameRuleAndSameLineAndSameChecksum = result.unmatchedForRuleAndForLineAndForChecksum(newIssue.ruleKey(), newIssue.line(), newIssue.checksum());
+ if (!sameRuleAndSameLineAndSameChecksum.isEmpty()) {
+ return sameRuleAndSameLineAndSameChecksum.iterator().next();
+ }
+ return null;
+ }
+
+ private boolean isNotAlreadyMapped(PreviousIssue previousIssue, IssueTrackingResult result) {
+ return result.unmatched().contains(previousIssue);
+ }
+
+ private boolean isNotAlreadyMapped(DefaultIssue newIssue, IssueTrackingResult result) {
+ return !result.isMatched(newIssue);
+ }
+
+ private boolean isSameChecksum(DefaultIssue newIssue, PreviousIssue previousIssue) {
+ return Objects.equal(previousIssue.checksum(), newIssue.checksum());
+ }
+
+ private boolean isSameLine(DefaultIssue newIssue, PreviousIssue previousIssue) {
+ return Objects.equal(previousIssue.line(), newIssue.line());
+ }
+
+ private boolean isSameMessage(DefaultIssue newIssue, PreviousIssue previousIssue) {
+ return Objects.equal(newIssue.message(), previousIssue.message());
+ }
+
+ private void mapIssue(DefaultIssue issue, @Nullable PreviousIssue ref, IssueTrackingResult result) {
+ if (ref != null) {
+ result.setMatch(issue, ref);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+
+ private static class LinePair {
+ int lineA;
+ int lineB;
+ int weight;
+
+ public LinePair(int lineA, int lineB, int weight) {
+ this.lineA = lineA;
+ this.lineB = lineB;
+ this.weight = weight;
+ }
+ }
+
+ private static class HashOccurrence {
+ int lineA;
+ int lineB;
+ int countA;
+ int countB;
+ }
+
+ private static final Comparator<LinePair> LINE_PAIR_COMPARATOR = new Comparator<LinePair>() {
+ @Override
+ public int compare(LinePair o1, LinePair o2) {
+ int weightDiff = o2.weight - o1.weight;
+ if (weightDiff != 0) {
+ return weightDiff;
+ } else {
+ return Math.abs(o1.lineA - o1.lineB) - Math.abs(o2.lineA - o2.lineB);
+ }
+ }
+ };
+
+}
--- /dev/null
+/*
+ * 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 javax.annotation.Nullable;
+
+public class IssueTrackingBlocksRecognizer {
+
+ private final FileHashes a;
+ private final FileHashes b;
+
+ public IssueTrackingBlocksRecognizer(FileHashes a, FileHashes b) {
+ this.a = a;
+ this.b = b;
+ }
+
+ public boolean isValidLineInReference(@Nullable Integer line) {
+ return (line != null) && (0 <= line - 1) && (line - 1 < a.length());
+ }
+
+ public boolean isValidLineInSource(@Nullable Integer line) {
+ return (line != null) && (0 <= line - 1) && (line - 1 < b.length());
+ }
+
+ /**
+ * @param startA number of line from first version of text (numbering starts from 1)
+ * @param startB number of line from second version of text (numbering starts from 1)
+ */
+ public int computeLengthOfMaximalBlock(int startA, int startB) {
+ if (!a.getHash(startA).equals(b.getHash(startB))) {
+ return 0;
+ }
+ int length = 0;
+ int ai = startA;
+ int bi = startB;
+ while (ai <= a.length() && bi <= b.length() && a.getHash(ai).equals(b.getHash(bi))) {
+ ai++;
+ bi++;
+ length++;
+ }
+ ai = startA;
+ bi = startB;
+ while (ai > 0 && bi > 0 && a.getHash(ai).equals(b.getHash(bi))) {
+ ai--;
+ bi--;
+ length++;
+ }
+ // Note that position (startA, startB) was counted twice
+ return length - 1;
+ }
+
+}
--- /dev/null
+/*
+ * 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.annotations.VisibleForTesting;
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.batch.Decorator;
+import org.sonar.api.batch.DecoratorBarriers;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependedUpon;
+import org.sonar.api.batch.DependsUpon;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.component.ResourcePerspectives;
+import org.sonar.api.issue.Issuable;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.issue.internal.IssueChangeContext;
+import org.sonar.api.profiles.RulesProfile;
+import org.sonar.api.resources.File;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.resources.ResourceUtils;
+import org.sonar.api.rules.ActiveRule;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleFinder;
+import org.sonar.api.utils.Duration;
+import org.sonar.api.utils.KeyValueFormat;
+import org.sonar.batch.issue.IssueCache;
+import org.sonar.batch.scan.LastLineHashes;
+import org.sonar.batch.scan.filesystem.InputPathCache;
+import org.sonar.core.DryRunIncompatible;
+import org.sonar.core.issue.IssueUpdater;
+import org.sonar.core.issue.db.IssueChangeDto;
+import org.sonar.core.issue.db.IssueDto;
+import org.sonar.core.issue.workflow.IssueWorkflow;
+
+import java.util.Collection;
+
+@DependsUpon(DecoratorBarriers.ISSUES_ADDED)
+@DependedUpon(DecoratorBarriers.ISSUES_TRACKED)
+@DryRunIncompatible
+public class IssueTrackingDecorator implements Decorator {
+
+ private static final Logger LOG = LoggerFactory.getLogger(IssueTrackingDecorator.class);
+
+ private final IssueCache issueCache;
+ private final InitialOpenIssuesStack initialOpenIssues;
+ private final IssueTracking tracking;
+ private final LastLineHashes lastLineHashes;
+ private final IssueHandlers handlers;
+ private final IssueWorkflow workflow;
+ private final IssueUpdater updater;
+ private final IssueChangeContext changeContext;
+ private final ResourcePerspectives perspectives;
+ private final RulesProfile rulesProfile;
+ private final RuleFinder ruleFinder;
+ private final InputPathCache inputPathCache;
+ private final Project project;
+
+ public IssueTrackingDecorator(IssueCache issueCache, InitialOpenIssuesStack initialOpenIssues, IssueTracking tracking,
+ LastLineHashes lastLineHashes,
+ IssueHandlers handlers, IssueWorkflow workflow,
+ IssueUpdater updater,
+ Project project,
+ ResourcePerspectives perspectives,
+ RulesProfile rulesProfile,
+ RuleFinder ruleFinder, InputPathCache inputPathCache) {
+ this.issueCache = issueCache;
+ this.initialOpenIssues = initialOpenIssues;
+ this.tracking = tracking;
+ this.lastLineHashes = lastLineHashes;
+ this.handlers = handlers;
+ this.workflow = workflow;
+ this.updater = updater;
+ this.project = project;
+ this.inputPathCache = inputPathCache;
+ this.changeContext = IssueChangeContext.createScan(project.getAnalysisDate());
+ this.perspectives = perspectives;
+ this.rulesProfile = rulesProfile;
+ this.ruleFinder = ruleFinder;
+ }
+
+ @Override
+ public boolean shouldExecuteOnProject(Project project) {
+ return true;
+ }
+
+ @Override
+ public void decorate(Resource resource, DecoratorContext context) {
+ Issuable issuable = perspectives.as(Issuable.class, resource);
+ if (issuable != null) {
+ doDecorate(resource);
+ }
+ }
+
+ @VisibleForTesting
+ void doDecorate(Resource resource) {
+ Collection<DefaultIssue> issues = Lists.newArrayList();
+ for (Issue issue : issueCache.byComponent(resource.getEffectiveKey())) {
+ issues.add((DefaultIssue) issue);
+ }
+ issueCache.clear(resource.getEffectiveKey());
+ // issues = all the issues created by rule engines during this module scan and not excluded by filters
+
+ // all the issues that are not closed in db before starting this module scan, including manual issues
+ Collection<PreviousIssue> dbOpenIssues = initialOpenIssues.selectAndRemoveIssues(resource.getEffectiveKey());
+
+ SourceHashHolder sourceHashHolder = null;
+ if (ResourceUtils.isFile(resource)) {
+ File sonarFile = (File) resource;
+ InputFile file = inputPathCache.getFile(project.getEffectiveKey(), sonarFile.getPath());
+ if (file == null) {
+ throw new IllegalStateException("Resource " + resource + " was not found in InputPath cache");
+ }
+ sourceHashHolder = new SourceHashHolder((DefaultInputFile) file, lastLineHashes);
+ }
+
+ IssueTrackingResult trackingResult = tracking.track(sourceHashHolder, dbOpenIssues, issues);
+
+ // unmatched = issues that have been resolved + issues on disabled/removed rules + manual issues
+ addUnmatched(trackingResult.unmatched(), sourceHashHolder, issues);
+
+ mergeMatched(trackingResult);
+
+ if (ResourceUtils.isProject(resource)) {
+ // issues that relate to deleted components
+ addIssuesOnDeletedComponents(issues);
+ }
+
+ for (DefaultIssue issue : issues) {
+ workflow.doAutomaticTransition(issue, changeContext);
+ handlers.execute(issue, changeContext);
+ issueCache.put(issue);
+ }
+ }
+
+ @VisibleForTesting
+ protected void mergeMatched(IssueTrackingResult result) {
+ for (DefaultIssue issue : result.matched()) {
+ IssueDto ref = ((PreviousIssueFromDb) result.matching(issue)).getDto();
+
+ // invariant fields
+ issue.setKey(ref.getKee());
+ issue.setCreationDate(ref.getIssueCreationDate());
+ issue.setUpdateDate(ref.getIssueUpdateDate());
+ issue.setCloseDate(ref.getIssueCloseDate());
+
+ // non-persisted fields
+ issue.setNew(false);
+ issue.setEndOfLife(false);
+ issue.setOnDisabledRule(false);
+ issue.setSelectedAt(ref.getSelectedAt());
+
+ // fields to update with old values
+ issue.setActionPlanKey(ref.getActionPlanKey());
+ issue.setResolution(ref.getResolution());
+ issue.setStatus(ref.getStatus());
+ issue.setAssignee(ref.getAssignee());
+ issue.setAuthorLogin(ref.getAuthorLogin());
+ issue.setTags(ref.getTags());
+
+ if (ref.getIssueAttributes() != null) {
+ issue.setAttributes(KeyValueFormat.parse(ref.getIssueAttributes()));
+ }
+
+ // populate existing changelog
+ Collection<IssueChangeDto> issueChangeDtos = initialOpenIssues.selectChangelog(issue.key());
+ for (IssueChangeDto issueChangeDto : issueChangeDtos) {
+ issue.addChange(issueChangeDto.toFieldDiffs());
+ }
+
+ // fields to update with current values
+ if (ref.isManualSeverity()) {
+ issue.setManualSeverity(true);
+ issue.setSeverity(ref.getSeverity());
+ } else {
+ updater.setPastSeverity(issue, ref.getSeverity(), changeContext);
+ }
+ updater.setPastLine(issue, ref.getLine());
+ updater.setPastMessage(issue, ref.getMessage(), changeContext);
+ updater.setPastEffortToFix(issue, ref.getEffortToFix(), changeContext);
+ Long debtInMinutes = ref.getDebt();
+ Duration previousTechnicalDebt = debtInMinutes != null ? Duration.create(debtInMinutes) : null;
+ updater.setPastTechnicalDebt(issue, previousTechnicalDebt, changeContext);
+ updater.setPastProject(issue, ref.getProjectKey(), changeContext);
+ }
+ }
+
+ private void addUnmatched(Collection<PreviousIssue> unmatchedIssues, SourceHashHolder sourceHashHolder, Collection<DefaultIssue> issues) {
+ for (PreviousIssue unmatchedIssue : unmatchedIssues) {
+ IssueDto unmatchedDto = ((PreviousIssueFromDb) unmatchedIssue).getDto();
+ DefaultIssue unmatched = unmatchedDto.toDefaultIssue();
+ if (StringUtils.isNotBlank(unmatchedDto.getReporter()) && !Issue.STATUS_CLOSED.equals(unmatchedDto.getStatus())) {
+ relocateManualIssue(unmatched, unmatchedDto, sourceHashHolder);
+ }
+ updateUnmatchedIssue(unmatched, false /* manual issues can be kept open */);
+ issues.add(unmatched);
+ }
+ }
+
+ private void addIssuesOnDeletedComponents(Collection<DefaultIssue> issues) {
+ for (IssueDto deadDto : initialOpenIssues.selectAllIssues()) {
+ DefaultIssue dead = deadDto.toDefaultIssue();
+ updateUnmatchedIssue(dead, true);
+ issues.add(dead);
+ }
+ initialOpenIssues.clear();
+ }
+
+ private void updateUnmatchedIssue(DefaultIssue issue, boolean forceEndOfLife) {
+ issue.setNew(false);
+
+ boolean manualIssue = !Strings.isNullOrEmpty(issue.reporter());
+ Rule rule = ruleFinder.findByKey(issue.ruleKey());
+ if (manualIssue) {
+ // Manual rules are not declared in Quality profiles, so no need to check ActiveRule
+ boolean isRemovedRule = rule == null || Rule.STATUS_REMOVED.equals(rule.getStatus());
+ issue.setEndOfLife(forceEndOfLife || isRemovedRule);
+ issue.setOnDisabledRule(isRemovedRule);
+ } else {
+ ActiveRule activeRule = rulesProfile.getActiveRule(issue.ruleKey().repository(), issue.ruleKey().rule());
+ issue.setEndOfLife(true);
+ issue.setOnDisabledRule(activeRule == null || rule == null || Rule.STATUS_REMOVED.equals(rule.getStatus()));
+ }
+ }
+
+ private void relocateManualIssue(DefaultIssue newIssue, IssueDto oldIssue, SourceHashHolder sourceHashHolder) {
+ LOG.debug("Trying to relocate manual issue {}", oldIssue.getKee());
+
+ Integer previousLine = oldIssue.getLine();
+ if (previousLine == null) {
+ LOG.debug("Cannot relocate issue at resource level");
+ return;
+ }
+
+ Collection<Integer> newLinesWithSameHash = sourceHashHolder.getNewLinesMatching(previousLine);
+ LOG.debug("Found the following lines with same hash: {}", newLinesWithSameHash);
+ if (newLinesWithSameHash.isEmpty()) {
+ if (previousLine > sourceHashHolder.getHashedSource().length()) {
+ LOG.debug("Old issue line {} is out of new source, closing and removing line number", previousLine);
+ newIssue.setLine(null);
+ updater.setStatus(newIssue, Issue.STATUS_CLOSED, changeContext);
+ updater.setResolution(newIssue, Issue.RESOLUTION_REMOVED, changeContext);
+ updater.setPastLine(newIssue, previousLine);
+ updater.setPastMessage(newIssue, oldIssue.getMessage(), changeContext);
+ updater.setPastEffortToFix(newIssue, oldIssue.getEffortToFix(), changeContext);
+ }
+ } else if (newLinesWithSameHash.size() == 1) {
+ Integer newLine = newLinesWithSameHash.iterator().next();
+ LOG.debug("Relocating issue to line {}", newLine);
+
+ newIssue.setLine(newLine);
+ updater.setPastLine(newIssue, previousLine);
+ updater.setPastMessage(newIssue, oldIssue.getMessage(), changeContext);
+ updater.setPastEffortToFix(newIssue, oldIssue.getEffortToFix(), changeContext);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.HashMultimap;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.rule.RuleKey;
+
+import javax.annotation.Nullable;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+class IssueTrackingResult {
+ private final Map<String, PreviousIssue> unmatchedByKey = new HashMap<>();
+ private final Map<RuleKey, Map<String, PreviousIssue>> unmatchedByRuleAndKey = new HashMap<>();
+ private final Map<RuleKey, Map<Integer, Multimap<String, PreviousIssue>>> unmatchedByRuleAndLineAndChecksum = new HashMap<>();
+ private final Map<DefaultIssue, PreviousIssue> matched = Maps.newIdentityHashMap();
+
+ Collection<PreviousIssue> unmatched() {
+ return unmatchedByKey.values();
+ }
+
+ Map<String, PreviousIssue> unmatchedByKeyForRule(RuleKey ruleKey) {
+ return unmatchedByRuleAndKey.containsKey(ruleKey) ? unmatchedByRuleAndKey.get(ruleKey) : Collections.<String, PreviousIssue>emptyMap();
+ }
+
+ Collection<PreviousIssue> unmatchedForRuleAndForLineAndForChecksum(RuleKey ruleKey, @Nullable Integer line, @Nullable String checksum) {
+ if (!unmatchedByRuleAndLineAndChecksum.containsKey(ruleKey)) {
+ return Collections.emptyList();
+ }
+ Map<Integer, Multimap<String, PreviousIssue>> unmatchedForRule = unmatchedByRuleAndLineAndChecksum.get(ruleKey);
+ Integer lineNotNull = line != null ? line : 0;
+ if (!unmatchedForRule.containsKey(lineNotNull)) {
+ return Collections.emptyList();
+ }
+ Multimap<String, PreviousIssue> unmatchedForRuleAndLine = unmatchedForRule.get(lineNotNull);
+ String checksumNotNull = StringUtils.defaultString(checksum, "");
+ if (!unmatchedForRuleAndLine.containsKey(checksumNotNull)) {
+ return Collections.emptyList();
+ }
+ return unmatchedForRuleAndLine.get(checksumNotNull);
+ }
+
+ Collection<DefaultIssue> matched() {
+ return matched.keySet();
+ }
+
+ boolean isMatched(DefaultIssue issue) {
+ return matched.containsKey(issue);
+ }
+
+ PreviousIssue matching(DefaultIssue issue) {
+ return matched.get(issue);
+ }
+
+ void addUnmatched(PreviousIssue i) {
+ unmatchedByKey.put(i.key(), i);
+ RuleKey ruleKey = i.ruleKey();
+ if (!unmatchedByRuleAndKey.containsKey(ruleKey)) {
+ unmatchedByRuleAndKey.put(ruleKey, new HashMap<String, PreviousIssue>());
+ unmatchedByRuleAndLineAndChecksum.put(ruleKey, new HashMap<Integer, Multimap<String, PreviousIssue>>());
+ }
+ unmatchedByRuleAndKey.get(ruleKey).put(i.key(), i);
+ Map<Integer, Multimap<String, PreviousIssue>> unmatchedForRule = unmatchedByRuleAndLineAndChecksum.get(ruleKey);
+ Integer lineNotNull = lineNotNull(i);
+ if (!unmatchedForRule.containsKey(lineNotNull)) {
+ unmatchedForRule.put(lineNotNull, HashMultimap.<String, PreviousIssue>create());
+ }
+ Multimap<String, PreviousIssue> unmatchedForRuleAndLine = unmatchedForRule.get(lineNotNull);
+ String checksumNotNull = StringUtils.defaultString(i.checksum(), "");
+ unmatchedForRuleAndLine.put(checksumNotNull, i);
+ }
+
+ private Integer lineNotNull(PreviousIssue i) {
+ Integer line = i.line();
+ return line != null ? line : 0;
+ }
+
+ void setMatch(DefaultIssue issue, PreviousIssue matching) {
+ matched.put(issue, matching);
+ RuleKey ruleKey = matching.ruleKey();
+ unmatchedByRuleAndKey.get(ruleKey).remove(matching.key());
+ unmatchedByKey.remove(matching.key());
+ Integer lineNotNull = lineNotNull(matching);
+ String checksumNotNull = StringUtils.defaultString(matching.checksum(), "");
+ unmatchedByRuleAndLineAndChecksum.get(ruleKey).get(lineNotNull).get(checksumNotNull).remove(matching);
+ }
+}
--- /dev/null
+/*
+ * 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.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
+import org.sonar.api.BatchComponent;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.rule.ActiveRule;
+import org.sonar.api.batch.rule.ActiveRules;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.issue.internal.IssueChangeContext;
+import org.sonar.api.resources.File;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.ResourceUtils;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.batch.index.BatchResource;
+import org.sonar.batch.index.ResourceCache;
+import org.sonar.batch.issue.IssueCache;
+import org.sonar.batch.scan.LastLineHashes;
+import org.sonar.batch.scan.filesystem.InputPathCache;
+import org.sonar.core.issue.IssueUpdater;
+import org.sonar.core.issue.workflow.IssueWorkflow;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+public class LocalIssueTracking implements BatchComponent {
+
+ private final IssueCache issueCache;
+ private final IssueTracking tracking;
+ private final LastLineHashes lastLineHashes;
+ private final IssueWorkflow workflow;
+ private final IssueUpdater updater;
+ private final IssueChangeContext changeContext;
+ private final ActiveRules activeRules;
+ private final InputPathCache inputPathCache;
+ private final ResourceCache resourceCache;
+ private final PreviousIssueRepository previousIssueCache;
+
+ public LocalIssueTracking(ResourceCache resourceCache, IssueCache issueCache, IssueTracking tracking,
+ LastLineHashes lastLineHashes, IssueWorkflow workflow, IssueUpdater updater,
+ ActiveRules activeRules, InputPathCache inputPathCache, PreviousIssueRepository previousIssueCache) {
+ this.resourceCache = resourceCache;
+ this.issueCache = issueCache;
+ this.tracking = tracking;
+ this.lastLineHashes = lastLineHashes;
+ this.workflow = workflow;
+ this.updater = updater;
+ this.inputPathCache = inputPathCache;
+ this.previousIssueCache = previousIssueCache;
+ this.changeContext = IssueChangeContext.createScan(((Project) resourceCache.getRoot().resource()).getAnalysisDate());
+ this.activeRules = activeRules;
+ }
+
+ public void execute() {
+ previousIssueCache.load();
+
+ for (BatchResource component : resourceCache.all()) {
+ trackIssues(component);
+ }
+ }
+
+ public void trackIssues(BatchResource component) {
+
+ Collection<DefaultIssue> issues = Lists.newArrayList();
+ for (Issue issue : issueCache.byComponent(component.resource().getEffectiveKey())) {
+ issues.add((DefaultIssue) issue);
+ }
+ issueCache.clear(component.resource().getEffectiveKey());
+ // issues = all the issues created by rule engines during this module scan and not excluded by filters
+
+ // all the issues that are not closed in db before starting this module scan, including manual issues
+ Collection<PreviousIssue> previousIssues = new ArrayList<>();
+ for (org.sonar.batch.protocol.input.issues.PreviousIssue previousIssue : previousIssueCache.byComponent(component)) {
+ previousIssues.add(new PreviousIssueFromWs(previousIssue));
+ }
+
+ SourceHashHolder sourceHashHolder = null;
+ if (ResourceUtils.isFile(component.resource())) {
+ File sonarFile = (File) component.resource();
+ InputFile file = inputPathCache.getFile(component.parent().parent().resource().getEffectiveKey(), sonarFile.getPath());
+ if (file == null) {
+ throw new IllegalStateException("Resource " + component.resource() + " was not found in InputPath cache");
+ }
+ sourceHashHolder = new SourceHashHolder((DefaultInputFile) file, lastLineHashes);
+ }
+
+ IssueTrackingResult trackingResult = tracking.track(sourceHashHolder, previousIssues, issues);
+
+ // unmatched = issues that have been resolved + issues on disabled/removed rules + manual issues
+ addUnmatched(trackingResult.unmatched(), sourceHashHolder, issues);
+
+ mergeMatched(trackingResult);
+
+ if (ResourceUtils.isRootProject(component.resource())) {
+ // issues that relate to deleted components
+ addIssuesOnDeletedComponents(issues);
+ }
+
+ for (DefaultIssue issue : issues) {
+ workflow.doAutomaticTransition(issue, changeContext);
+ issueCache.put(issue);
+ }
+ }
+
+ @VisibleForTesting
+ protected void mergeMatched(IssueTrackingResult result) {
+ for (DefaultIssue issue : result.matched()) {
+ org.sonar.batch.protocol.input.issues.PreviousIssue ref = ((PreviousIssueFromWs) result.matching(issue)).getDto();
+
+ // invariant fields
+ issue.setKey(ref.key());
+
+ // non-persisted fields
+ issue.setNew(false);
+ issue.setEndOfLife(false);
+ issue.setOnDisabledRule(false);
+
+ // fields to update with old values
+ issue.setResolution(ref.resolution());
+ issue.setStatus(ref.status());
+ issue.setAssignee(ref.assigneeLogin());
+
+ String overriddenSeverity = ref.overriddenSeverity();
+ if (overriddenSeverity != null) {
+ // Severity overriden by user
+ issue.setSeverity(overriddenSeverity);
+ }
+ }
+ }
+
+ private void addUnmatched(Collection<PreviousIssue> unmatchedIssues, SourceHashHolder sourceHashHolder, Collection<DefaultIssue> issues) {
+ for (PreviousIssue unmatchedIssue : unmatchedIssues) {
+ org.sonar.batch.protocol.input.issues.PreviousIssue unmatchedPreviousIssue = ((PreviousIssueFromWs) unmatchedIssue).getDto();
+ ActiveRule activeRule = activeRules.find(unmatchedIssue.ruleKey());
+ DefaultIssue unmatched = toUnmatchedIssue(unmatchedPreviousIssue);
+ if (activeRule != null && !Issue.STATUS_CLOSED.equals(unmatchedPreviousIssue.status())) {
+ relocateManualIssue(unmatched, unmatchedIssue, sourceHashHolder);
+ }
+ updateUnmatchedIssue(unmatched, false /* manual issues can be kept open */);
+ issues.add(unmatched);
+ }
+ }
+
+ private void addIssuesOnDeletedComponents(Collection<DefaultIssue> issues) {
+ for (org.sonar.batch.protocol.input.issues.PreviousIssue previous : previousIssueCache.issuesOnMissingComponents()) {
+ DefaultIssue dead = toUnmatchedIssue(previous);
+ updateUnmatchedIssue(dead, true);
+ issues.add(dead);
+ }
+ }
+
+ private DefaultIssue toUnmatchedIssue(org.sonar.batch.protocol.input.issues.PreviousIssue previous) {
+ DefaultIssue issue = new DefaultIssue();
+ issue.setKey(previous.key());
+ issue.setStatus(previous.status());
+ issue.setResolution(previous.resolution());
+ issue.setMessage(previous.message());
+ issue.setLine(previous.line());
+ String overriddenSeverity = previous.overriddenSeverity();
+ if (overriddenSeverity != null) {
+ issue.setSeverity(overriddenSeverity);
+ } else {
+ ActiveRule activeRule = activeRules.find(RuleKey.of(previous.ruleRepo(), previous.ruleKey()));
+ if (activeRule != null) {
+ // FIXME if rule was removed we can't guess what was the severity of the issue
+ issue.setSeverity(activeRule.severity());
+ }
+ }
+ issue.setAssignee(previous.assigneeLogin());
+ issue.setComponentKey(previous.componentKey());
+ issue.setManualSeverity(overriddenSeverity != null);
+ issue.setRuleKey(RuleKey.of(previous.ruleRepo(), previous.ruleKey()));
+ issue.setNew(false);
+ return issue;
+ }
+
+ private void updateUnmatchedIssue(DefaultIssue issue, boolean forceEndOfLife) {
+ ActiveRule activeRule = activeRules.find(issue.ruleKey());
+ boolean isRemovedRule = activeRule == null;
+ issue.setEndOfLife(forceEndOfLife || isRemovedRule);
+ issue.setOnDisabledRule(isRemovedRule);
+ }
+
+ private void relocateManualIssue(DefaultIssue newIssue, PreviousIssue oldIssue, SourceHashHolder sourceHashHolder) {
+ Integer previousLine = oldIssue.line();
+ if (previousLine == null) {
+ return;
+ }
+
+ Collection<Integer> newLinesWithSameHash = sourceHashHolder.getNewLinesMatching(previousLine);
+ if (newLinesWithSameHash.isEmpty()) {
+ if (previousLine > sourceHashHolder.getHashedSource().length()) {
+ newIssue.setLine(null);
+ updater.setStatus(newIssue, Issue.STATUS_CLOSED, changeContext);
+ updater.setResolution(newIssue, Issue.RESOLUTION_REMOVED, changeContext);
+ updater.setPastLine(newIssue, previousLine);
+ updater.setPastMessage(newIssue, oldIssue.message(), changeContext);
+ }
+ } else if (newLinesWithSameHash.size() == 1) {
+ Integer newLine = newLinesWithSameHash.iterator().next();
+ newIssue.setLine(newLine);
+ updater.setPastLine(newIssue, previousLine);
+ updater.setPastMessage(newIssue, oldIssue.message(), changeContext);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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 org.sonar.api.rule.RuleKey;
+
+import javax.annotation.CheckForNull;
+
+public interface PreviousIssue {
+
+ String key();
+
+ RuleKey ruleKey();
+
+ /**
+ * Null for issue with no line
+ */
+ @CheckForNull
+ String checksum();
+
+ /**
+ * Global issues have no line
+ */
+ @CheckForNull
+ Integer line();
+
+ @CheckForNull
+ String message();
+
+}
--- /dev/null
+/*
+ * 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 org.sonar.api.rule.RuleKey;
+import org.sonar.core.issue.db.IssueDto;
+
+public class PreviousIssueFromDb implements PreviousIssue {
+
+ private IssueDto dto;
+
+ public PreviousIssueFromDb(IssueDto dto) {
+ this.dto = dto;
+ }
+
+ public IssueDto getDto() {
+ return dto;
+ }
+
+ @Override
+ public String key() {
+ return dto.getKee();
+ }
+
+ @Override
+ public RuleKey ruleKey() {
+ return dto.getRuleKey();
+ }
+
+ @Override
+ public String checksum() {
+ return dto.getChecksum();
+ }
+
+ @Override
+ public Integer line() {
+ return dto.getLine();
+ }
+
+ @Override
+ public String message() {
+ return dto.getMessage();
+ }
+
+}
--- /dev/null
+/*
+ * 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 org.sonar.api.rule.RuleKey;
+
+public class PreviousIssueFromWs implements PreviousIssue {
+
+ private org.sonar.batch.protocol.input.issues.PreviousIssue dto;
+
+ public PreviousIssueFromWs(org.sonar.batch.protocol.input.issues.PreviousIssue dto) {
+ this.dto = dto;
+ }
+
+ public org.sonar.batch.protocol.input.issues.PreviousIssue getDto() {
+ return dto;
+ }
+
+ @Override
+ public String key() {
+ return dto.key();
+ }
+
+ @Override
+ public RuleKey ruleKey() {
+ return RuleKey.of(dto.ruleRepo(), dto.ruleKey());
+ }
+
+ @Override
+ public String checksum() {
+ return dto.checksum();
+ }
+
+ @Override
+ public Integer line() {
+ return dto.line();
+ }
+
+ @Override
+ public String message() {
+ return dto.message();
+ }
+
+}
--- /dev/null
+/*
+ * 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.base.Function;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.BatchComponent;
+import org.sonar.api.batch.InstantiationStrategy;
+import org.sonar.api.batch.bootstrap.ProjectReactor;
+import org.sonar.api.utils.TimeProfiler;
+import org.sonar.batch.index.BatchResource;
+import org.sonar.batch.index.Cache;
+import org.sonar.batch.index.Caches;
+import org.sonar.batch.index.ResourceCache;
+import org.sonar.batch.protocol.input.issues.PreviousIssue;
+import org.sonar.batch.repository.PreviousIssuesLoader;
+
+@InstantiationStrategy(InstantiationStrategy.PER_BATCH)
+public class PreviousIssueRepository implements BatchComponent {
+
+ private static final Logger LOG = LoggerFactory.getLogger(PreviousIssueRepository.class);
+
+ private final Caches caches;
+ private Cache<PreviousIssue> issuesCache;
+ private final PreviousIssuesLoader previousIssuesLoader;
+ private final ProjectReactor reactor;
+ private final ResourceCache resourceCache;
+
+ public PreviousIssueRepository(Caches caches, PreviousIssuesLoader previousIssuesLoader, ProjectReactor reactor, ResourceCache resourceCache) {
+ this.caches = caches;
+ this.previousIssuesLoader = previousIssuesLoader;
+ this.reactor = reactor;
+ this.resourceCache = resourceCache;
+ }
+
+ public void load() {
+ TimeProfiler profiler = new TimeProfiler(LOG).start("Load previous issues");
+ try {
+ this.issuesCache = caches.createCache("previousIssues");
+ previousIssuesLoader.load(reactor, new Function<PreviousIssue, Void>() {
+
+ @Override
+ public Void apply(PreviousIssue issue) {
+ String componentKey = issue.componentKey();
+ BatchResource r = resourceCache.get(componentKey);
+ if (r == null) {
+ // Deleted resource
+ issuesCache.put(0, issue.key(), issue);
+ }
+ issuesCache.put(r.batchId(), issue.key(), issue);
+ return null;
+ }
+ });
+ } finally {
+ profiler.stop();
+ }
+ }
+
+ public Iterable<PreviousIssue> byComponent(BatchResource component) {
+ return issuesCache.values(component.batchId());
+ }
+
+ public Iterable<PreviousIssue> issuesOnMissingComponents() {
+ return issuesCache.values(0);
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+/**
+ * Compute hashes of block around each line
+ */
+public class RollingFileHashes {
+
+ final int[] rollingHashes;
+
+ public static RollingFileHashes create(FileHashes hashes, int halfBlockSize) {
+ int size = hashes.length();
+ int[] rollingHashes = new int[size];
+
+ RollingHashCalculator hashCalulator = new RollingHashCalculator(halfBlockSize * 2 + 1);
+ for (int i = 1; i <= Math.min(size, halfBlockSize + 1); i++) {
+ hashCalulator.add(hashes.getHash(i).hashCode());
+ }
+ for (int i = 1; i <= size; i++) {
+ rollingHashes[i - 1] = hashCalulator.getHash();
+ if (i - halfBlockSize > 0) {
+ hashCalulator.remove(hashes.getHash(i - halfBlockSize).hashCode());
+ }
+ if (i + 1 + halfBlockSize <= size) {
+ hashCalulator.add(hashes.getHash(i + 1 + halfBlockSize).hashCode());
+ } else {
+ hashCalulator.add(0);
+ }
+ }
+
+ return new RollingFileHashes(rollingHashes);
+ }
+
+ public int getHash(int line) {
+ return rollingHashes[line - 1];
+ }
+
+ private RollingFileHashes(int[] hashes) {
+ this.rollingHashes = hashes;
+ }
+
+ private static class RollingHashCalculator {
+
+ private static final int PRIME_BASE = 31;
+
+ private final int power;
+ private int hash;
+
+ public RollingHashCalculator(int size) {
+ int pow = 1;
+ for (int i = 0; i < size - 1; i++) {
+ pow = pow * PRIME_BASE;
+ }
+ this.power = pow;
+ }
+
+ public void add(int value) {
+ hash = hash * PRIME_BASE + value;
+ }
+
+ public void remove(int value) {
+ hash = hash - power * value;
+ }
+
+ public int getHash() {
+ return hash;
+ }
+
+ }
+
+}
--- /dev/null
+/*
+ * 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.ImmutableSet;
+import org.sonar.api.batch.fs.InputFile.Status;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.batch.scan.LastLineHashes;
+
+import javax.annotation.CheckForNull;
+
+import java.util.Collection;
+
+public class SourceHashHolder {
+
+ private final LastLineHashes lastSnapshots;
+
+ private FileHashes hashedReference;
+ private FileHashes hashedSource;
+ private DefaultInputFile inputFile;
+
+ public SourceHashHolder(DefaultInputFile inputFile, LastLineHashes lastSnapshots) {
+ this.inputFile = inputFile;
+ this.lastSnapshots = lastSnapshots;
+ }
+
+ private void initHashes() {
+ if (hashedSource == null) {
+ hashedSource = FileHashes.create(inputFile.lineHashes());
+ Status status = inputFile.status();
+ if (status == Status.ADDED) {
+ hashedReference = null;
+ } else if (status == Status.SAME) {
+ hashedReference = hashedSource;
+ } else {
+ String[] lineHashes = lastSnapshots.getLineHashes(inputFile.key());
+ hashedReference = lineHashes != null ? FileHashes.create(lineHashes) : null;
+ }
+ }
+ }
+
+ @CheckForNull
+ public FileHashes getHashedReference() {
+ initHashes();
+ return hashedReference;
+ }
+
+ public FileHashes getHashedSource() {
+ initHashes();
+ return hashedSource;
+ }
+
+ public Collection<Integer> getNewLinesMatching(Integer originLine) {
+ FileHashes reference = getHashedReference();
+ if (reference == null) {
+ return ImmutableSet.of();
+ } else {
+ return getHashedSource().getLinesForHash(reference.getHash(originLine));
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.batch.issue.tracking;
+
+import javax.annotation.ParametersAreNonnullByDefault;
*/
package org.sonar.batch.mediumtest;
+import com.google.common.base.Function;
import org.apache.commons.io.Charsets;
import org.sonar.api.SonarPlugin;
import org.sonar.api.batch.bootstrap.ProjectReactor;
import org.sonar.batch.bootstrapper.EnvironmentInformation;
import org.sonar.batch.protocol.input.ActiveRule;
import org.sonar.batch.protocol.input.GlobalReferentials;
-import org.sonar.batch.protocol.input.ProjectReferentials;
-import org.sonar.batch.referential.GlobalReferentialsLoader;
-import org.sonar.batch.referential.ProjectReferentialsLoader;
+import org.sonar.batch.protocol.input.ProjectRepository;
+import org.sonar.batch.protocol.input.issues.PreviousIssue;
+import org.sonar.batch.repository.GlobalReferentialsLoader;
+import org.sonar.batch.repository.PreviousIssuesLoader;
+import org.sonar.batch.repository.ProjectRepositoriesLoader;
import org.sonar.core.plugins.DefaultPluginMetadata;
import org.sonar.core.plugins.RemotePlugin;
private final FakeGlobalReferentialsLoader globalRefProvider = new FakeGlobalReferentialsLoader();
private final FakeProjectReferentialsLoader projectRefProvider = new FakeProjectReferentialsLoader();
private final FakePluginsReferential pluginsReferential = new FakePluginsReferential();
+ private final FakePreviousIssuesLoader previousIssues = new FakePreviousIssuesLoader();
private final Map<String, String> bootstrapProperties = new HashMap<String, String>();
public BatchMediumTester build() {
return this;
}
+ public BatchMediumTesterBuilder addPreviousIssue(PreviousIssue issue) {
+ previousIssues.getPreviousIssues().add(issue);
+ return this;
+ }
+
}
public void start() {
builder.pluginsReferential,
builder.globalRefProvider,
builder.projectRefProvider,
+ builder.previousIssues,
new DefaultDebtModel())
.setBootstrapProperties(builder.bootstrapProperties)
.build();
}
}
- private static class FakeProjectReferentialsLoader implements ProjectReferentialsLoader {
+ private static class FakeProjectReferentialsLoader implements ProjectRepositoriesLoader {
- private ProjectReferentials ref = new ProjectReferentials();
+ private ProjectRepository ref = new ProjectRepository();
@Override
- public ProjectReferentials load(ProjectReactor reactor, TaskProperties taskProperties) {
+ public ProjectRepository load(ProjectReactor reactor, TaskProperties taskProperties) {
return ref;
}
}
+ private static class FakePreviousIssuesLoader implements PreviousIssuesLoader {
+
+ List<PreviousIssue> previousIssues = new ArrayList<>();
+
+ public List<PreviousIssue> getPreviousIssues() {
+ return previousIssues;
+ }
+
+ @Override
+ public void load(ProjectReactor reactor, Function<PreviousIssue, Void> consumer) {
+ for (PreviousIssue previousIssue : previousIssues) {
+ consumer.apply(previousIssue);
+ }
+
+ }
+
+ }
+
}
import org.sonar.batch.events.EventBus;
import org.sonar.batch.index.DefaultIndex;
import org.sonar.batch.issue.ignore.scanner.IssueExclusionsLoader;
+import org.sonar.batch.issue.tracking.LocalIssueTracking;
import org.sonar.batch.rule.QProfileVerifier;
import org.sonar.batch.scan.filesystem.DefaultModuleFileSystem;
import org.sonar.batch.scan.filesystem.FileSystemLogger;
private final QProfileVerifier profileVerifier;
private final IssueExclusionsLoader issueExclusionsLoader;
private final IssuesReports issuesReport;
+ private final LocalIssueTracking localIssueTracking;
public PreviewPhaseExecutor(Phases phases,
MavenPluginsConfigurator mavenPluginsConfigurator, InitializersExecutor initializersExecutor,
SensorsExecutor sensorsExecutor,
SensorContext sensorContext, DefaultIndex index,
EventBus eventBus, ProjectInitializer pi, FileSystemLogger fsLogger, IssuesReports jsonReport, DefaultModuleFileSystem fs, QProfileVerifier profileVerifier,
- IssueExclusionsLoader issueExclusionsLoader) {
+ IssueExclusionsLoader issueExclusionsLoader, LocalIssueTracking localIssueTracking) {
this.phases = phases;
this.mavenPluginsConfigurator = mavenPluginsConfigurator;
this.initializersExecutor = initializersExecutor;
this.fs = fs;
this.profileVerifier = profileVerifier;
this.issueExclusionsLoader = issueExclusionsLoader;
+ this.localIssueTracking = localIssueTracking;
}
/**
}
if (module.isRoot()) {
+
+ localIssueTracking();
+
issuesReport();
}
eventBus.fireEvent(new ProjectAnalysisEvent(module, false));
}
+ private void localIssueTracking() {
+ String stepName = "Local Issue Tracking";
+ eventBus.fireEvent(new BatchStepEvent(stepName, true));
+ localIssueTracking.execute();
+ eventBus.fireEvent(new BatchStepEvent(stepName, false));
+ }
+
private void issuesReport() {
String stepName = "Issues Reports";
eventBus.fireEvent(new BatchStepEvent(stepName, true));
+++ /dev/null
-/*
- * 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.referential;
-
-import org.sonar.batch.bootstrap.ServerClient;
-import org.sonar.batch.protocol.input.GlobalReferentials;
-
-public class DefaultGlobalReferentialsLoader implements GlobalReferentialsLoader {
-
- private static final String BATCH_GLOBAL_URL = "/batch/global";
-
- private final ServerClient serverClient;
-
- public DefaultGlobalReferentialsLoader(ServerClient serverClient) {
- this.serverClient = serverClient;
- }
-
- @Override
- public GlobalReferentials load() {
- return GlobalReferentials.fromJson(serverClient.request(BATCH_GLOBAL_URL));
- }
-
-}
+++ /dev/null
-/*
- * 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.referential;
-
-import com.google.common.collect.Maps;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.api.batch.bootstrap.ProjectDefinition;
-import org.sonar.api.batch.bootstrap.ProjectReactor;
-import org.sonar.api.database.DatabaseSession;
-import org.sonar.api.database.model.MeasureModel;
-import org.sonar.api.database.model.ResourceModel;
-import org.sonar.api.database.model.Snapshot;
-import org.sonar.api.measures.CoreMetrics;
-import org.sonar.api.measures.Metric;
-import org.sonar.api.resources.Qualifiers;
-import org.sonar.batch.bootstrap.AnalysisMode;
-import org.sonar.batch.bootstrap.ServerClient;
-import org.sonar.batch.bootstrap.TaskProperties;
-import org.sonar.batch.protocol.input.FileData;
-import org.sonar.batch.protocol.input.ProjectReferentials;
-import org.sonar.batch.rule.ModuleQProfiles;
-
-import javax.annotation.CheckForNull;
-import javax.persistence.NoResultException;
-import javax.persistence.Query;
-
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
-public class DefaultProjectReferentialsLoader implements ProjectReferentialsLoader {
-
- private static final Logger LOG = LoggerFactory.getLogger(DefaultProjectReferentialsLoader.class);
-
- private static final String BATCH_PROJECT_URL = "/batch/project";
-
- private final ServerClient serverClient;
- private final AnalysisMode analysisMode;
- private final DatabaseSession session;
-
- public DefaultProjectReferentialsLoader(DatabaseSession session, ServerClient serverClient, AnalysisMode analysisMode) {
- this.session = session;
- this.serverClient = serverClient;
- this.analysisMode = analysisMode;
- }
-
- public DefaultProjectReferentialsLoader(ServerClient serverClient, AnalysisMode analysisMode) {
- this.session = null;
- this.serverClient = serverClient;
- this.analysisMode = analysisMode;
- }
-
- @Override
- public ProjectReferentials load(ProjectReactor reactor, TaskProperties taskProperties) {
- String projectKey = reactor.getRoot().getKeyWithBranch();
- String url = BATCH_PROJECT_URL + "?key=" + ServerClient.encodeForUrl(projectKey);
- if (taskProperties.properties().containsKey(ModuleQProfiles.SONAR_PROFILE_PROP)) {
- LOG.warn("Ability to set quality profile from command line using '" + ModuleQProfiles.SONAR_PROFILE_PROP
- + "' is deprecated and will be dropped in a future SonarQube version. Please configure quality profile used by your project on SonarQube server.");
- url += "&profile=" + ServerClient.encodeForUrl(taskProperties.properties().get(ModuleQProfiles.SONAR_PROFILE_PROP));
- }
- url += "&preview=" + analysisMode.isPreview();
- ProjectReferentials ref = ProjectReferentials.fromJson(serverClient.request(url));
-
- if (session != null) {
- for (ProjectDefinition module : reactor.getProjects()) {
-
- for (Entry<String, FileData> fileDataByPaths : ref.fileDataByPath(module.getKeyWithBranch()).entrySet()) {
- String path = fileDataByPaths.getKey();
- FileData fileData = fileDataByPaths.getValue();
- String lastCommits = null;
- String revisions = null;
- String authors = null;
- List<Object[]> measuresByKey = query(projectKey + ":" + path, CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE_KEY, CoreMetrics.SCM_REVISIONS_BY_LINE_KEY,
- CoreMetrics.SCM_AUTHORS_BY_LINE_KEY);
- for (Object[] measureByKey : measuresByKey) {
- if (measureByKey[0].equals(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE_KEY)) {
- lastCommits = ((MeasureModel) measureByKey[1]).getData(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE);
- } else if (measureByKey[0].equals(CoreMetrics.SCM_REVISIONS_BY_LINE_KEY)) {
- revisions = ((MeasureModel) measureByKey[1]).getData(CoreMetrics.SCM_REVISIONS_BY_LINE);
- } else if (measureByKey[0].equals(CoreMetrics.SCM_AUTHORS_BY_LINE_KEY)) {
- authors = ((MeasureModel) measureByKey[1]).getData(CoreMetrics.SCM_AUTHORS_BY_LINE);
- }
- }
- ref.addFileData(module.getKeyWithBranch(), path, new FileData(fileData.hash(), authors == null, lastCommits, revisions, authors));
- }
- }
- ref.setLastAnalysisDate(lastSnapshotCreationDate(projectKey));
- }
- return ref;
- }
-
- public List<Object[]> query(String resourceKey, String... metricKeys) {
- StringBuilder sb = new StringBuilder();
- Map<String, Object> params = Maps.newHashMap();
-
- sb.append("SELECT met.key, m");
- sb.append(" FROM ")
- .append(MeasureModel.class.getSimpleName())
- .append(" m, ")
- .append(Metric.class.getSimpleName())
- .append(" met, ")
- .append(ResourceModel.class.getSimpleName())
- .append(" r, ")
- .append(Snapshot.class.getSimpleName())
- .append(" s WHERE met.id=m.metricId AND m.snapshotId=s.id AND s.resourceId=r.id AND r.key=:kee AND s.status=:status AND s.qualifier<>:lib");
- params.put("kee", resourceKey);
- params.put("status", Snapshot.STATUS_PROCESSED);
- params.put("lib", Qualifiers.LIBRARY);
-
- sb.append(" AND m.characteristicId IS NULL");
- sb.append(" AND m.personId IS NULL");
- sb.append(" AND m.ruleId IS NULL AND m.rulePriority IS NULL");
- if (metricKeys.length > 0) {
- sb.append(" AND met.key IN (:metricKeys) ");
- params.put("metricKeys", Arrays.asList(metricKeys));
- }
- sb.append(" AND s.last=true ");
- sb.append(" ORDER BY s.createdAt ");
-
- Query jpaQuery = session.createQuery(sb.toString());
-
- for (Map.Entry<String, Object> entry : params.entrySet()) {
- jpaQuery.setParameter(entry.getKey(), entry.getValue());
- }
- return jpaQuery.getResultList();
- }
-
- @CheckForNull
- Date lastSnapshotCreationDate(String resourceKey) {
- StringBuilder sb = new StringBuilder();
- Map<String, Object> params = Maps.newHashMap();
-
- sb.append("SELECT s.buildDate");
- sb.append(" FROM ")
- .append(ResourceModel.class.getSimpleName())
- .append(" r, ")
- .append(Snapshot.class.getSimpleName())
- .append(" s WHERE s.resourceId=r.id AND r.key=:kee AND s.status=:status AND s.qualifier<>:lib");
- params.put("kee", resourceKey);
- params.put("status", Snapshot.STATUS_PROCESSED);
- params.put("lib", Qualifiers.LIBRARY);
-
- sb.append(" AND s.last=true ");
-
- Query jpaQuery = session.createQuery(sb.toString());
-
- for (Map.Entry<String, Object> entry : params.entrySet()) {
- jpaQuery.setParameter(entry.getKey(), entry.getValue());
- }
- try {
- return (Date) jpaQuery.getSingleResult();
- } catch (NoResultException e) {
- return null;
- }
- }
-}
+++ /dev/null
-/*
- * 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.referential;
-
-import org.sonar.batch.protocol.input.GlobalReferentials;
-
-public interface GlobalReferentialsLoader {
-
- GlobalReferentials load();
-
-}
+++ /dev/null
-/*
- * 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.referential;
-
-import org.picocontainer.injectors.ProviderAdapter;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.api.utils.TimeProfiler;
-import org.sonar.batch.protocol.input.GlobalReferentials;
-
-public class GlobalReferentialsProvider extends ProviderAdapter {
-
- private static final Logger LOG = LoggerFactory.getLogger(GlobalReferentialsProvider.class);
-
- private GlobalReferentials globalReferentials;
-
- public GlobalReferentials provide(GlobalReferentialsLoader loader) {
- if (globalReferentials == null) {
- TimeProfiler profiler = new TimeProfiler(LOG).start("Load global referentials");
- try {
- globalReferentials = loader.load();
- } finally {
- profiler.stop();
- }
- }
- return globalReferentials;
- }
-}
+++ /dev/null
-/*
- * 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.referential;
-
-import org.sonar.api.batch.bootstrap.ProjectReactor;
-import org.sonar.batch.bootstrap.TaskProperties;
-import org.sonar.batch.protocol.input.ProjectReferentials;
-
-public interface ProjectReferentialsLoader {
-
- ProjectReferentials load(ProjectReactor reactor, TaskProperties taskProperties);
-
-}
+++ /dev/null
-/*
- * 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.referential;
-
-import org.picocontainer.injectors.ProviderAdapter;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.api.batch.bootstrap.ProjectReactor;
-import org.sonar.api.utils.TimeProfiler;
-import org.sonar.batch.bootstrap.TaskProperties;
-import org.sonar.batch.protocol.input.ProjectReferentials;
-
-public class ProjectReferentialsProvider extends ProviderAdapter {
-
- private static final Logger LOG = LoggerFactory.getLogger(ProjectReferentialsProvider.class);
-
- private ProjectReferentials projectReferentials;
-
- public ProjectReferentials provide(ProjectReferentialsLoader loader, ProjectReactor reactor, TaskProperties taskProps) {
- if (projectReferentials == null) {
- TimeProfiler profiler = new TimeProfiler(LOG).start("Load project referentials");
- try {
- projectReferentials = loader.load(reactor, taskProps);
- } finally {
- profiler.stop();
- }
- }
- return projectReferentials;
- }
-}
+++ /dev/null
-/*
- * 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.
- */
-@ParametersAreNonnullByDefault
-package org.sonar.batch.referential;
-
-import javax.annotation.ParametersAreNonnullByDefault;
--- /dev/null
+/*
+ * 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.repository;
+
+import org.sonar.batch.bootstrap.ServerClient;
+import org.sonar.batch.protocol.input.GlobalReferentials;
+
+public class DefaultGlobalReferentialsLoader implements GlobalReferentialsLoader {
+
+ private static final String BATCH_GLOBAL_URL = "/batch/global";
+
+ private final ServerClient serverClient;
+
+ public DefaultGlobalReferentialsLoader(ServerClient serverClient) {
+ this.serverClient = serverClient;
+ }
+
+ @Override
+ public GlobalReferentials load() {
+ return GlobalReferentials.fromJson(serverClient.request(BATCH_GLOBAL_URL));
+ }
+
+}
--- /dev/null
+/*
+ * 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.repository;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Function;
+import com.google.common.io.InputSupplier;
+import org.sonar.api.batch.bootstrap.ProjectReactor;
+import org.sonar.batch.bootstrap.ServerClient;
+import org.sonar.batch.protocol.input.issues.PreviousIssue;
+import org.sonar.batch.protocol.input.issues.PreviousIssueHelper;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+
+public class DefaultPreviousIssuesLoader implements PreviousIssuesLoader {
+
+ private final ServerClient serverClient;
+
+ public DefaultPreviousIssuesLoader(ServerClient serverClient) {
+ this.serverClient = serverClient;
+ }
+
+ @Override
+ public void load(ProjectReactor reactor, Function<PreviousIssue, Void> consumer) {
+ InputSupplier<InputStream> request = serverClient.doRequest("/batch/issues?key=" + ServerClient.encodeForUrl(reactor.getRoot().getKeyWithBranch()), "GET", null);
+ try (InputStream is = request.getInput(); Reader reader = new InputStreamReader(is, Charsets.UTF_8)) {
+ for (PreviousIssue issue : PreviousIssueHelper.getIssues(reader)) {
+ consumer.apply(issue);
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException("Unable to get previous issues", e);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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.repository;
+
+import com.google.common.collect.Maps;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.batch.bootstrap.ProjectDefinition;
+import org.sonar.api.batch.bootstrap.ProjectReactor;
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.api.database.model.MeasureModel;
+import org.sonar.api.database.model.ResourceModel;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.batch.bootstrap.AnalysisMode;
+import org.sonar.batch.bootstrap.ServerClient;
+import org.sonar.batch.bootstrap.TaskProperties;
+import org.sonar.batch.protocol.input.FileData;
+import org.sonar.batch.protocol.input.ProjectRepository;
+import org.sonar.batch.rule.ModuleQProfiles;
+
+import javax.annotation.CheckForNull;
+import javax.persistence.NoResultException;
+import javax.persistence.Query;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+public class DefaultProjectReferentialsLoader implements ProjectRepositoriesLoader {
+
+ private static final Logger LOG = LoggerFactory.getLogger(DefaultProjectReferentialsLoader.class);
+
+ private static final String BATCH_PROJECT_URL = "/batch/project";
+
+ private final ServerClient serverClient;
+ private final AnalysisMode analysisMode;
+ private final DatabaseSession session;
+
+ public DefaultProjectReferentialsLoader(DatabaseSession session, ServerClient serverClient, AnalysisMode analysisMode) {
+ this.session = session;
+ this.serverClient = serverClient;
+ this.analysisMode = analysisMode;
+ }
+
+ public DefaultProjectReferentialsLoader(ServerClient serverClient, AnalysisMode analysisMode) {
+ this.session = null;
+ this.serverClient = serverClient;
+ this.analysisMode = analysisMode;
+ }
+
+ @Override
+ public ProjectRepository load(ProjectReactor reactor, TaskProperties taskProperties) {
+ String projectKey = reactor.getRoot().getKeyWithBranch();
+ String url = BATCH_PROJECT_URL + "?key=" + ServerClient.encodeForUrl(projectKey);
+ if (taskProperties.properties().containsKey(ModuleQProfiles.SONAR_PROFILE_PROP)) {
+ LOG.warn("Ability to set quality profile from command line using '" + ModuleQProfiles.SONAR_PROFILE_PROP
+ + "' is deprecated and will be dropped in a future SonarQube version. Please configure quality profile used by your project on SonarQube server.");
+ url += "&profile=" + ServerClient.encodeForUrl(taskProperties.properties().get(ModuleQProfiles.SONAR_PROFILE_PROP));
+ }
+ url += "&preview=" + analysisMode.isPreview();
+ ProjectRepository ref = ProjectRepository.fromJson(serverClient.request(url));
+
+ if (session != null) {
+ for (ProjectDefinition module : reactor.getProjects()) {
+
+ for (Entry<String, FileData> fileDataByPaths : ref.fileDataByPath(module.getKeyWithBranch()).entrySet()) {
+ String path = fileDataByPaths.getKey();
+ FileData fileData = fileDataByPaths.getValue();
+ String lastCommits = null;
+ String revisions = null;
+ String authors = null;
+ List<Object[]> measuresByKey = query(projectKey + ":" + path, CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE_KEY, CoreMetrics.SCM_REVISIONS_BY_LINE_KEY,
+ CoreMetrics.SCM_AUTHORS_BY_LINE_KEY);
+ for (Object[] measureByKey : measuresByKey) {
+ if (measureByKey[0].equals(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE_KEY)) {
+ lastCommits = ((MeasureModel) measureByKey[1]).getData(CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE);
+ } else if (measureByKey[0].equals(CoreMetrics.SCM_REVISIONS_BY_LINE_KEY)) {
+ revisions = ((MeasureModel) measureByKey[1]).getData(CoreMetrics.SCM_REVISIONS_BY_LINE);
+ } else if (measureByKey[0].equals(CoreMetrics.SCM_AUTHORS_BY_LINE_KEY)) {
+ authors = ((MeasureModel) measureByKey[1]).getData(CoreMetrics.SCM_AUTHORS_BY_LINE);
+ }
+ }
+ ref.addFileData(module.getKeyWithBranch(), path, new FileData(fileData.hash(), authors == null, lastCommits, revisions, authors));
+ }
+ }
+ ref.setLastAnalysisDate(lastSnapshotCreationDate(projectKey));
+ }
+ return ref;
+ }
+
+ public List<Object[]> query(String resourceKey, String... metricKeys) {
+ StringBuilder sb = new StringBuilder();
+ Map<String, Object> params = Maps.newHashMap();
+
+ sb.append("SELECT met.key, m");
+ sb.append(" FROM ")
+ .append(MeasureModel.class.getSimpleName())
+ .append(" m, ")
+ .append(Metric.class.getSimpleName())
+ .append(" met, ")
+ .append(ResourceModel.class.getSimpleName())
+ .append(" r, ")
+ .append(Snapshot.class.getSimpleName())
+ .append(" s WHERE met.id=m.metricId AND m.snapshotId=s.id AND s.resourceId=r.id AND r.key=:kee AND s.status=:status AND s.qualifier<>:lib");
+ params.put("kee", resourceKey);
+ params.put("status", Snapshot.STATUS_PROCESSED);
+ params.put("lib", Qualifiers.LIBRARY);
+
+ sb.append(" AND m.characteristicId IS NULL");
+ sb.append(" AND m.personId IS NULL");
+ sb.append(" AND m.ruleId IS NULL AND m.rulePriority IS NULL");
+ if (metricKeys.length > 0) {
+ sb.append(" AND met.key IN (:metricKeys) ");
+ params.put("metricKeys", Arrays.asList(metricKeys));
+ }
+ sb.append(" AND s.last=true ");
+ sb.append(" ORDER BY s.createdAt ");
+
+ Query jpaQuery = session.createQuery(sb.toString());
+
+ for (Map.Entry<String, Object> entry : params.entrySet()) {
+ jpaQuery.setParameter(entry.getKey(), entry.getValue());
+ }
+ return jpaQuery.getResultList();
+ }
+
+ @CheckForNull
+ Date lastSnapshotCreationDate(String resourceKey) {
+ StringBuilder sb = new StringBuilder();
+ Map<String, Object> params = Maps.newHashMap();
+
+ sb.append("SELECT s.buildDate");
+ sb.append(" FROM ")
+ .append(ResourceModel.class.getSimpleName())
+ .append(" r, ")
+ .append(Snapshot.class.getSimpleName())
+ .append(" s WHERE s.resourceId=r.id AND r.key=:kee AND s.status=:status AND s.qualifier<>:lib");
+ params.put("kee", resourceKey);
+ params.put("status", Snapshot.STATUS_PROCESSED);
+ params.put("lib", Qualifiers.LIBRARY);
+
+ sb.append(" AND s.last=true ");
+
+ Query jpaQuery = session.createQuery(sb.toString());
+
+ for (Map.Entry<String, Object> entry : params.entrySet()) {
+ jpaQuery.setParameter(entry.getKey(), entry.getValue());
+ }
+ try {
+ return (Date) jpaQuery.getSingleResult();
+ } catch (NoResultException e) {
+ return null;
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.repository;
+
+import org.sonar.batch.protocol.input.GlobalReferentials;
+
+public interface GlobalReferentialsLoader {
+
+ GlobalReferentials load();
+
+}
--- /dev/null
+/*
+ * 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.repository;
+
+import org.picocontainer.injectors.ProviderAdapter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.utils.TimeProfiler;
+import org.sonar.batch.protocol.input.GlobalReferentials;
+
+public class GlobalReferentialsProvider extends ProviderAdapter {
+
+ private static final Logger LOG = LoggerFactory.getLogger(GlobalReferentialsProvider.class);
+
+ private GlobalReferentials globalReferentials;
+
+ public GlobalReferentials provide(GlobalReferentialsLoader loader) {
+ if (globalReferentials == null) {
+ TimeProfiler profiler = new TimeProfiler(LOG).start("Load global referentials");
+ try {
+ globalReferentials = loader.load();
+ } finally {
+ profiler.stop();
+ }
+ }
+ return globalReferentials;
+ }
+}
--- /dev/null
+/*
+ * 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.repository;
+
+import com.google.common.base.Function;
+import org.sonar.api.batch.bootstrap.ProjectReactor;
+import org.sonar.batch.protocol.input.issues.PreviousIssue;
+
+public interface PreviousIssuesLoader {
+
+ void load(ProjectReactor reactor, Function<PreviousIssue, Void> consumer);
+
+}
--- /dev/null
+/*
+ * 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.repository;
+
+import org.sonar.api.batch.bootstrap.ProjectReactor;
+import org.sonar.batch.bootstrap.TaskProperties;
+import org.sonar.batch.protocol.input.ProjectRepository;
+
+public interface ProjectRepositoriesLoader {
+
+ ProjectRepository load(ProjectReactor reactor, TaskProperties taskProperties);
+
+}
--- /dev/null
+/*
+ * 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.repository;
+
+import org.picocontainer.injectors.ProviderAdapter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.batch.bootstrap.ProjectReactor;
+import org.sonar.api.utils.TimeProfiler;
+import org.sonar.batch.bootstrap.TaskProperties;
+import org.sonar.batch.protocol.input.ProjectRepository;
+
+public class ProjectRepositoriesProvider extends ProviderAdapter {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ProjectRepositoriesProvider.class);
+
+ private ProjectRepository projectReferentials;
+
+ public ProjectRepository provide(ProjectRepositoriesLoader loader, ProjectReactor reactor, TaskProperties taskProps) {
+ if (projectReferentials == null) {
+ TimeProfiler profiler = new TimeProfiler(LOG).start("Load project referentials");
+ try {
+ projectReferentials = loader.load(reactor, taskProps);
+ } finally {
+ profiler.stop();
+ }
+ }
+ return projectReferentials;
+ }
+}
--- /dev/null
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.batch.repository;
+
+import javax.annotation.ParametersAreNonnullByDefault;
import org.sonar.api.batch.rule.internal.NewActiveRule;
import org.sonar.api.rule.RuleKey;
import org.sonar.batch.protocol.input.ActiveRule;
-import org.sonar.batch.protocol.input.ProjectReferentials;
+import org.sonar.batch.protocol.input.ProjectRepository;
import java.util.Map.Entry;
/**
* Loads the rules that are activated on the Quality profiles
- * used by the current module and build {@link org.sonar.api.batch.rule.ActiveRules}.
+ * used by the current project and build {@link org.sonar.api.batch.rule.ActiveRules}.
*/
public class ActiveRulesProvider extends ProviderAdapter {
private ActiveRules singleton = null;
- public ActiveRules provide(ProjectReferentials ref) {
+ public ActiveRules provide(ProjectRepository ref) {
if (singleton == null) {
singleton = load(ref);
}
return singleton;
}
- private ActiveRules load(ProjectReferentials ref) {
+ private ActiveRules load(ProjectRepository ref) {
ActiveRulesBuilder builder = new ActiveRulesBuilder();
for (ActiveRule activeRule : ref.activeRules()) {
NewActiveRule newActiveRule = builder.create(RuleKey.of(activeRule.repositoryKey(), activeRule.ruleKey()));
import com.google.common.collect.ImmutableMap;
import org.sonar.api.BatchComponent;
-import org.sonar.batch.protocol.input.ProjectReferentials;
+import org.sonar.batch.protocol.input.ProjectRepository;
import javax.annotation.CheckForNull;
public static final String SONAR_PROFILE_PROP = "sonar.profile";
private final Map<String, QProfile> byLanguage;
- public ModuleQProfiles(ProjectReferentials ref) {
+ public ModuleQProfiles(ProjectRepository ref) {
ImmutableMap.Builder<String, QProfile> builder = ImmutableMap.builder();
for (org.sonar.batch.protocol.input.QProfile qProfile : ref.qProfiles()) {
*/
package org.sonar.batch.scan;
-import org.sonar.batch.sensor.AnalyzerOptimizer;
-
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.BatchComponent;
import org.sonar.batch.issue.ignore.pattern.IssueInclusionPatternInitializer;
import org.sonar.batch.issue.ignore.scanner.IssueExclusionsLoader;
import org.sonar.batch.issue.ignore.scanner.IssueExclusionsRegexpScanner;
+import org.sonar.batch.issue.tracking.InitialOpenIssuesSensor;
+import org.sonar.batch.issue.tracking.IssueHandlers;
+import org.sonar.batch.issue.tracking.IssueTrackingDecorator;
import org.sonar.batch.language.LanguageDistributionDecorator;
import org.sonar.batch.phases.DecoratorsExecutor;
import org.sonar.batch.phases.DefaultPhaseExecutor;
import org.sonar.batch.report.ComponentsPublisher;
import org.sonar.batch.report.IssuesPublisher;
import org.sonar.batch.report.PublishReportJob;
-import org.sonar.batch.rule.ActiveRulesProvider;
import org.sonar.batch.rule.ModuleQProfiles;
import org.sonar.batch.rule.QProfileDecorator;
import org.sonar.batch.rule.QProfileEventsDecorator;
import org.sonar.batch.scan.filesystem.StatusDetectionFactory;
import org.sonar.batch.scan.maven.MavenPluginsConfigurator;
import org.sonar.batch.scan.report.IssuesReports;
+import org.sonar.batch.sensor.AnalyzerOptimizer;
import org.sonar.batch.sensor.DefaultSensorContext;
import org.sonar.batch.sensor.DefaultSensorStorage;
import org.sonar.batch.sensor.coverage.CoverageExclusions;
// rules
ModuleQProfiles.class,
- new ActiveRulesProvider(),
new RulesProfileProvider(),
QProfileSensor.class,
QProfileDecorator.class,
SqaleRatingDecorator.class,
SqaleRatingSettings.class,
+ // Issue tracking
+ IssueTrackingDecorator.class,
+ IssueHandlers.class,
+ InitialOpenIssuesSensor.class,
+
QProfileEventsDecorator.class,
TimeMachineConfiguration.class);
import org.sonar.api.utils.MessageException;
import org.sonar.batch.bootstrap.AnalysisMode;
import org.sonar.batch.bootstrap.GlobalSettings;
-import org.sonar.batch.protocol.input.ProjectReferentials;
+import org.sonar.batch.protocol.input.ProjectRepository;
import java.util.List;
*/
public class ModuleSettings extends Settings {
- private final ProjectReferentials projectReferentials;
+ private final ProjectRepository projectReferentials;
private AnalysisMode analysisMode;
- public ModuleSettings(GlobalSettings batchSettings, ProjectDefinition project, ProjectReferentials projectReferentials,
+ public ModuleSettings(GlobalSettings batchSettings, ProjectDefinition project, ProjectRepository projectReferentials,
AnalysisMode analysisMode) {
super(batchSettings.getDefinitions());
this.projectReferentials = projectReferentials;
import org.sonar.batch.index.SourcePersister;
import org.sonar.batch.issue.DefaultProjectIssues;
import org.sonar.batch.issue.IssueCache;
+import org.sonar.batch.issue.tracking.InitialOpenIssuesStack;
+import org.sonar.batch.issue.tracking.LocalIssueTracking;
+import org.sonar.batch.issue.tracking.PreviousIssueRepository;
import org.sonar.batch.languages.DefaultLanguagesReferential;
import org.sonar.batch.mediumtest.ScanTaskObservers;
import org.sonar.batch.phases.GraphPersister;
import org.sonar.batch.profiling.PhasesSumUpTimeProfiler;
-import org.sonar.batch.referential.ProjectReferentialsProvider;
+import org.sonar.batch.repository.ProjectRepositoriesProvider;
+import org.sonar.batch.rule.ActiveRulesProvider;
import org.sonar.batch.rule.RulesProvider;
import org.sonar.batch.scan.filesystem.InputPathCache;
import org.sonar.batch.scan.maven.FakeMavenPluginExecutor;
private void addBatchComponents() {
add(
- new ProjectReferentialsProvider(),
+ new ProjectRepositoriesProvider(),
DefaultResourceCreationLock.class,
CodeColorizers.class,
DefaultNotificationManager.class,
InputPathCache.class,
PathResolver.class,
+ // rules
+ new ActiveRulesProvider(),
+
// issues
IssueUpdater.class,
FunctionExecutor.class,
IssueCache.class,
DefaultProjectIssues.class,
IssueChangelogDebtCalculator.class,
+ LocalIssueTracking.class,
+ PreviousIssueRepository.class,
// tests
TestPlanPerspectiveLoader.class,
// technical debt
DefaultTechnicalDebtModel.class,
+ // Issue tracking
+ InitialOpenIssuesStack.class,
+
ProjectLock.class);
}
import org.sonar.api.utils.MessageException;
import org.sonar.batch.bootstrap.AnalysisMode;
import org.sonar.batch.bootstrap.GlobalSettings;
-import org.sonar.batch.protocol.input.ProjectReferentials;
+import org.sonar.batch.protocol.input.ProjectRepository;
public class ProjectSettings extends Settings {
private static final Logger LOG = LoggerFactory.getLogger(ProjectSettings.class);
private final GlobalSettings globalSettings;
- private final ProjectReferentials projectReferentials;
+ private final ProjectRepository projectReferentials;
private final AnalysisMode mode;
public ProjectSettings(ProjectReactor reactor, GlobalSettings globalSettings, PropertyDefinitions propertyDefinitions,
- ProjectReferentials projectReferentials, AnalysisMode mode) {
+ ProjectRepository projectReferentials, AnalysisMode mode) {
super(propertyDefinitions);
this.mode = mode;
getEncryption().setPathToSecretKey(globalSettings.getString(CoreProperties.ENCRYPTION_SECRET_KEY_PATH));
import org.apache.commons.lang.StringUtils;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.batch.protocol.input.FileData;
-import org.sonar.batch.protocol.input.ProjectReferentials;
+import org.sonar.batch.protocol.input.ProjectRepository;
class StatusDetection {
- private final ProjectReferentials projectReferentials;
+ private final ProjectRepository projectReferentials;
- StatusDetection(ProjectReferentials projectReferentials) {
+ StatusDetection(ProjectRepository projectReferentials) {
this.projectReferentials = projectReferentials;
}
package org.sonar.batch.scan.filesystem;
import org.sonar.api.BatchComponent;
-import org.sonar.batch.protocol.input.ProjectReferentials;
+import org.sonar.batch.protocol.input.ProjectRepository;
public class StatusDetectionFactory implements BatchComponent {
- private final ProjectReferentials projectReferentials;
+ private final ProjectRepository projectReferentials;
- public StatusDetectionFactory(ProjectReferentials projectReferentials) {
+ public StatusDetectionFactory(ProjectRepository projectReferentials) {
this.projectReferentials = projectReferentials;
}
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.utils.TimeProfiler;
import org.sonar.batch.protocol.input.FileData;
-import org.sonar.batch.protocol.input.ProjectReferentials;
+import org.sonar.batch.protocol.input.ProjectRepository;
import org.sonar.core.DryRunIncompatible;
import java.util.LinkedList;
private final ProjectDefinition projectDefinition;
private final ScmConfiguration configuration;
private final FileSystem fs;
- private final ProjectReferentials projectReferentials;
+ private final ProjectRepository projectReferentials;
public ScmSensor(ProjectDefinition projectDefinition, ScmConfiguration configuration,
- ProjectReferentials projectReferentials, FileSystem fs) {
+ ProjectRepository projectReferentials, FileSystem fs) {
this.projectDefinition = projectDefinition;
this.configuration = configuration;
this.projectReferentials = projectReferentials;
--- /dev/null
+/*
+ * 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 org.apache.ibatis.session.ResultHandler;
+import org.junit.Test;
+import org.sonar.api.resources.Project;
+import org.sonar.core.issue.db.IssueChangeDao;
+import org.sonar.core.issue.db.IssueDao;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+public class InitialOpenIssuesSensorTest {
+
+ InitialOpenIssuesStack stack = mock(InitialOpenIssuesStack.class);
+ IssueDao issueDao = mock(IssueDao.class);
+ IssueChangeDao issueChangeDao = mock(IssueChangeDao.class);
+
+ InitialOpenIssuesSensor sensor = new InitialOpenIssuesSensor(stack, issueDao, issueChangeDao);
+
+ @Test
+ public void should_select_module_open_issues() {
+ Project project = new Project("key");
+ project.setId(1);
+ sensor.analyse(project, null);
+
+ verify(issueDao).selectNonClosedIssuesByModule(eq(1), any(ResultHandler.class));
+ }
+
+ @Test
+ public void should_select_module_open_issues_changelog() {
+ Project project = new Project("key");
+ project.setId(1);
+ sensor.analyse(project, null);
+
+ verify(issueChangeDao).selectChangelogOnNonClosedIssuesByModuleAndType(eq(1), any(ResultHandler.class));
+ }
+
+ @Test
+ public void test_toString() throws Exception {
+ assertThat(sensor.toString()).isEqualTo("InitialOpenIssuesSensor");
+
+ }
+}
--- /dev/null
+/*
+ * 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 org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.CoreProperties;
+import org.sonar.batch.bootstrap.BootstrapProperties;
+import org.sonar.batch.bootstrap.TempFolderProvider;
+import org.sonar.batch.index.Caches;
+import org.sonar.core.issue.db.IssueChangeDto;
+import org.sonar.core.issue.db.IssueDto;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class InitialOpenIssuesStackTest {
+
+ @ClassRule
+ public static TemporaryFolder temp = new TemporaryFolder();
+
+ public static Caches createCacheOnTemp(TemporaryFolder temp) {
+ BootstrapProperties bootstrapSettings = new BootstrapProperties(Collections.<String, String>emptyMap());
+ try {
+ bootstrapSettings.properties().put(CoreProperties.WORKING_DIRECTORY, temp.newFolder().getAbsolutePath());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return new Caches(new TempFolderProvider().provide(bootstrapSettings));
+ }
+
+ InitialOpenIssuesStack stack;
+ Caches caches;
+
+ @Before
+ public void before() throws Exception {
+ caches = createCacheOnTemp(temp);
+ caches.start();
+ stack = new InitialOpenIssuesStack(caches);
+ }
+
+ @After
+ public void after() {
+ caches.stop();
+ }
+
+ @Test
+ public void get_and_remove_issues() {
+ IssueDto issueDto = new IssueDto().setComponentKey("org.struts.Action").setKee("ISSUE-1");
+ stack.addIssue(issueDto);
+
+ List<PreviousIssue> issueDtos = stack.selectAndRemoveIssues("org.struts.Action");
+ assertThat(issueDtos).hasSize(1);
+ assertThat(issueDtos.get(0).key()).isEqualTo("ISSUE-1");
+
+ assertThat(stack.selectAllIssues()).isEmpty();
+ }
+
+ @Test
+ public void get_and_remove_with_many_issues_on_same_resource() {
+ stack.addIssue(new IssueDto().setComponentKey("org.struts.Action").setKee("ISSUE-1"));
+ stack.addIssue(new IssueDto().setComponentKey("org.struts.Action").setKee("ISSUE-2"));
+
+ List<PreviousIssue> issueDtos = stack.selectAndRemoveIssues("org.struts.Action");
+ assertThat(issueDtos).hasSize(2);
+
+ assertThat(stack.selectAllIssues()).isEmpty();
+ }
+
+ @Test
+ public void get_and_remove_do_nothing_if_resource_not_found() {
+ stack.addIssue(new IssueDto().setComponentKey("org.struts.Action").setKee("ISSUE-1"));
+
+ List<PreviousIssue> issueDtos = stack.selectAndRemoveIssues("Other");
+ assertThat(issueDtos).hasSize(0);
+
+ assertThat(stack.selectAllIssues()).hasSize(1);
+ }
+
+ @Test
+ public void select_changelog() {
+ stack.addChangelog(new IssueChangeDto().setKey("CHANGE-1").setIssueKey("ISSUE-1"));
+ stack.addChangelog(new IssueChangeDto().setKey("CHANGE-2").setIssueKey("ISSUE-1"));
+
+ List<IssueChangeDto> issueChangeDtos = stack.selectChangelog("ISSUE-1");
+ assertThat(issueChangeDtos).hasSize(2);
+ assertThat(issueChangeDtos.get(0).getKey()).isEqualTo("CHANGE-1");
+ assertThat(issueChangeDtos.get(1).getKey()).isEqualTo("CHANGE-2");
+ }
+
+ @Test
+ public void return_empty_changelog() {
+ assertThat(stack.selectChangelog("ISSUE-1")).isEmpty();
+ }
+
+ @Test
+ public void clear_issues() {
+ stack.addIssue(new IssueDto().setComponentKey("org.struts.Action").setKee("ISSUE-1"));
+
+ assertThat(stack.selectAllIssues()).hasSize(1);
+
+ // issues are not removed
+ assertThat(stack.selectAllIssues()).hasSize(1);
+
+ stack.clear();
+ assertThat(stack.selectAllIssues()).isEmpty();
+ }
+
+ @Test
+ public void clear_issues_changelog() {
+ stack.addChangelog(new IssueChangeDto().setKey("CHANGE-1").setIssueKey("ISSUE-1"));
+
+ assertThat(stack.selectChangelog("ISSUE-1")).hasSize(1);
+
+ stack.clear();
+ assertThat(stack.selectChangelog("ISSUE-1")).isEmpty();
+ }
+}
--- /dev/null
+/*
+ * 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 org.sonar.batch.issue.tracking.IssueHandlers;
+
+import org.junit.Test;
+import org.mockito.ArgumentMatcher;
+import org.sonar.api.issue.IssueHandler;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.issue.internal.IssueChangeContext;
+import org.sonar.core.issue.IssueUpdater;
+
+import java.util.Date;
+
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.*;
+
+public class IssueHandlersTest {
+ @Test
+ public void should_execute_handlers() throws Exception {
+ IssueHandler h1 = mock(IssueHandler.class);
+ IssueHandler h2 = mock(IssueHandler.class);
+ IssueUpdater updater = mock(IssueUpdater.class);
+
+ IssueHandlers handlers = new IssueHandlers(updater, new IssueHandler[]{h1, h2});
+ final DefaultIssue issue = new DefaultIssue();
+ handlers.execute(issue, IssueChangeContext.createScan(new Date()));
+
+ verify(h1).onIssue(argThat(new ArgumentMatcher<IssueHandler.Context>() {
+ @Override
+ public boolean matches(Object o) {
+ return ((IssueHandler.Context) o).issue() == issue;
+ }
+ }));
+ }
+
+ @Test
+ public void test_no_handlers() {
+ IssueUpdater updater = mock(IssueUpdater.class);
+ IssueHandlers handlers = new IssueHandlers(updater);
+ handlers.execute(new DefaultIssue(), IssueChangeContext.createScan(new Date()));
+ verifyZeroInteractions(updater);
+ }
+}
--- /dev/null
+/*
+ * 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 org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class IssueTrackingBlocksRecognizerTest {
+
+ @Test
+ public void test() {
+ assertThat(compute(t("abcde"), t("abcde"), 4, 4)).isEqualTo(5);
+ assertThat(compute(t("abcde"), t("abcd"), 4, 4)).isEqualTo(4);
+ assertThat(compute(t("bcde"), t("abcde"), 4, 4)).isEqualTo(0);
+ assertThat(compute(t("bcde"), t("abcde"), 3, 4)).isEqualTo(4);
+ }
+
+ private static int compute(FileHashes a, FileHashes b, int ai, int bi) {
+ IssueTrackingBlocksRecognizer rec = new IssueTrackingBlocksRecognizer(a, b);
+ return rec.computeLengthOfMaximalBlock(ai, bi);
+ }
+
+ private static FileHashes t(String text) {
+ String[] array = new String[text.length()];
+ for (int i = 0; i < text.length(); i++) {
+ array[i] = "" + text.charAt(i);
+ }
+ return FileHashes.create(array);
+ }
+
+}
--- /dev/null
+/*
+ * 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 org.apache.commons.codec.digest.DigestUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.component.ResourcePerspectives;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.issue.internal.IssueChangeContext;
+import org.sonar.api.profiles.RulesProfile;
+import org.sonar.api.resources.File;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleFinder;
+import org.sonar.api.utils.Duration;
+import org.sonar.api.utils.System2;
+import org.sonar.batch.issue.IssueCache;
+import org.sonar.batch.scan.LastLineHashes;
+import org.sonar.batch.scan.filesystem.InputPathCache;
+import org.sonar.core.issue.IssueUpdater;
+import org.sonar.core.issue.db.IssueChangeDto;
+import org.sonar.core.issue.db.IssueDto;
+import org.sonar.core.issue.workflow.IssueWorkflow;
+import org.sonar.java.api.JavaClass;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyCollection;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.RETURNS_MOCKS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+public class IssueTrackingDecoratorTest {
+
+ IssueTrackingDecorator decorator;
+ IssueCache issueCache = mock(IssueCache.class, RETURNS_MOCKS);
+ InitialOpenIssuesStack initialOpenIssues = mock(InitialOpenIssuesStack.class);
+ IssueTracking tracking = mock(IssueTracking.class, RETURNS_MOCKS);
+ LastLineHashes lastSnapshots = mock(LastLineHashes.class);
+ IssueHandlers handlers = mock(IssueHandlers.class);
+ IssueWorkflow workflow = mock(IssueWorkflow.class);
+ IssueUpdater updater = mock(IssueUpdater.class);
+ ResourcePerspectives perspectives = mock(ResourcePerspectives.class);
+ RulesProfile profile = mock(RulesProfile.class);
+ RuleFinder ruleFinder = mock(RuleFinder.class);
+ InputPathCache inputPathCache = mock(InputPathCache.class);
+
+ @Before
+ public void init() {
+ decorator = new IssueTrackingDecorator(
+ issueCache,
+ initialOpenIssues,
+ tracking,
+ lastSnapshots,
+ handlers,
+ workflow,
+ updater,
+ new Project("foo"),
+ perspectives,
+ profile,
+ ruleFinder,
+ inputPathCache);
+ }
+
+ @Test
+ public void should_execute_on_project() {
+ Project project = mock(Project.class);
+ assertThat(decorator.shouldExecuteOnProject(project)).isTrue();
+ }
+
+ @Test
+ public void should_not_be_executed_on_classes_not_methods() throws Exception {
+ DecoratorContext context = mock(DecoratorContext.class);
+ decorator.decorate(JavaClass.create("org.foo.Bar"), context);
+ verifyZeroInteractions(context, issueCache, tracking, handlers, workflow);
+ }
+
+ @Test
+ public void should_process_open_issues() throws Exception {
+ Resource file = File.create("Action.java").setEffectiveKey("struts:Action.java").setId(123);
+ final DefaultIssue issue = new DefaultIssue();
+
+ // INPUT : one issue, no open issues during previous scan, no filtering
+ when(issueCache.byComponent("struts:Action.java")).thenReturn(Arrays.asList(issue));
+ List<PreviousIssue> dbIssues = Collections.emptyList();
+ when(initialOpenIssues.selectAndRemoveIssues("struts:Action.java")).thenReturn(dbIssues);
+ when(inputPathCache.getFile("foo", "Action.java")).thenReturn(mock(DefaultInputFile.class));
+ decorator.doDecorate(file);
+
+ // Apply filters, track, apply transitions, notify extensions then update cache
+ verify(tracking).track(isA(SourceHashHolder.class), eq(dbIssues), argThat(new ArgumentMatcher<Collection<DefaultIssue>>() {
+ @Override
+ public boolean matches(Object o) {
+ List<DefaultIssue> issues = (List<DefaultIssue>) o;
+ return issues.size() == 1 && issues.get(0) == issue;
+ }
+ }));
+ verify(workflow).doAutomaticTransition(eq(issue), any(IssueChangeContext.class));
+ verify(handlers).execute(eq(issue), any(IssueChangeContext.class));
+ verify(issueCache).put(issue);
+ }
+
+ @Test
+ public void should_register_unmatched_issues_as_end_of_life() throws Exception {
+ // "Unmatched" issues existed in previous scan but not in current one -> they have to be closed
+ Resource file = File.create("Action.java").setEffectiveKey("struts:Action.java").setId(123);
+
+ // INPUT : one issue existing during previous scan
+ PreviousIssue unmatchedIssue = new PreviousIssueFromDb(new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey("squid", "AvoidCycle"));
+
+ IssueTrackingResult trackingResult = new IssueTrackingResult();
+ trackingResult.addUnmatched(unmatchedIssue);
+
+ when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
+ when(inputPathCache.getFile("foo", "Action.java")).thenReturn(mock(DefaultInputFile.class));
+
+ decorator.doDecorate(file);
+
+ verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
+ verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
+
+ ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
+ verify(issueCache).put(argument.capture());
+
+ DefaultIssue issue = argument.getValue();
+ assertThat(issue.key()).isEqualTo("ABCDE");
+ assertThat(issue.isNew()).isFalse();
+ assertThat(issue.isEndOfLife()).isTrue();
+ }
+
+ @Test
+ public void manual_issues_should_be_moved_if_matching_line_found() throws Exception {
+ // INPUT : one issue existing during previous scan
+ PreviousIssue unmatchedIssue = new PreviousIssueFromDb(new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(6).setStatus("OPEN").setRuleKey("manual", "Performance"));
+ when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance"));
+
+ IssueTrackingResult trackingResult = new IssueTrackingResult();
+ trackingResult.addUnmatched(unmatchedIssue);
+
+ String originalSource = "public interface Action {\n"
+ + " void method1();\n"
+ + " void method2();\n"
+ + " void method3();\n"
+ + " void method4();\n"
+ + " void method5();\n" // Original issue here
+ + "}";
+ String newSource = "public interface Action {\n"
+ + " void method5();\n" // New issue here
+ + " void method1();\n"
+ + " void method2();\n"
+ + " void method3();\n"
+ + " void method4();\n"
+ + "}";
+ Resource file = mockHashes(originalSource, newSource);
+
+ when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
+
+ decorator.doDecorate(file);
+
+ verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
+ verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
+
+ ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
+ verify(issueCache).put(argument.capture());
+
+ DefaultIssue issue = argument.getValue();
+ assertThat(issue.line()).isEqualTo(2);
+ assertThat(issue.key()).isEqualTo("ABCDE");
+ assertThat(issue.isNew()).isFalse();
+ assertThat(issue.isEndOfLife()).isFalse();
+ assertThat(issue.isOnDisabledRule()).isFalse();
+ }
+
+ private Resource mockHashes(String originalSource, String newSource) {
+ DefaultInputFile inputFile = mock(DefaultInputFile.class);
+ byte[][] hashes = computeHashes(newSource);
+ when(inputFile.lineHashes()).thenReturn(hashes);
+ when(inputFile.key()).thenReturn("foo:Action.java");
+ when(inputPathCache.getFile("foo", "Action.java")).thenReturn(inputFile);
+ when(lastSnapshots.getLineHashes("foo:Action.java")).thenReturn(computeHexHashes(originalSource));
+ Resource file = File.create("Action.java");
+ return file;
+ }
+
+ @Test
+ public void manual_issues_should_be_untouched_if_already_closed() throws Exception {
+
+ // INPUT : one issue existing during previous scan
+ PreviousIssue unmatchedIssue = new PreviousIssueFromDb(new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(1).setStatus("CLOSED").setRuleKey("manual", "Performance"));
+ when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance"));
+
+ IssueTrackingResult trackingResult = new IssueTrackingResult();
+ trackingResult.addUnmatched(unmatchedIssue);
+
+ String originalSource = "public interface Action {}";
+ Resource file = mockHashes(originalSource, originalSource);
+
+ when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
+
+ decorator.doDecorate(file);
+
+ verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
+ verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
+
+ ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
+ verify(issueCache).put(argument.capture());
+
+ DefaultIssue issue = argument.getValue();
+ assertThat(issue.line()).isEqualTo(1);
+ assertThat(issue.key()).isEqualTo("ABCDE");
+ assertThat(issue.isNew()).isFalse();
+ assertThat(issue.isEndOfLife()).isFalse();
+ assertThat(issue.isOnDisabledRule()).isFalse();
+ assertThat(issue.status()).isEqualTo("CLOSED");
+ }
+
+ @Test
+ public void manual_issues_should_be_untouched_if_line_is_null() throws Exception {
+
+ // INPUT : one issue existing during previous scan
+ PreviousIssue unmatchedIssue = new PreviousIssueFromDb(new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(null).setStatus("OPEN").setRuleKey("manual", "Performance"));
+ when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance"));
+
+ IssueTrackingResult trackingResult = new IssueTrackingResult();
+ trackingResult.addUnmatched(unmatchedIssue);
+
+ String originalSource = "public interface Action {}";
+ Resource file = mockHashes(originalSource, originalSource);
+
+ when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
+
+ decorator.doDecorate(file);
+
+ verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
+ verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
+
+ ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
+ verify(issueCache).put(argument.capture());
+
+ DefaultIssue issue = argument.getValue();
+ assertThat(issue.line()).isEqualTo(null);
+ assertThat(issue.key()).isEqualTo("ABCDE");
+ assertThat(issue.isNew()).isFalse();
+ assertThat(issue.isEndOfLife()).isFalse();
+ assertThat(issue.isOnDisabledRule()).isFalse();
+ assertThat(issue.status()).isEqualTo("OPEN");
+ }
+
+ @Test
+ public void manual_issues_should_be_kept_if_matching_line_not_found() throws Exception {
+ // "Unmatched" issues existed in previous scan but not in current one -> they have to be closed
+
+ // INPUT : one issue existing during previous scan
+ final int issueOnLine = 6;
+ PreviousIssue unmatchedIssue = new PreviousIssueFromDb(new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(issueOnLine).setStatus("OPEN")
+ .setRuleKey("manual", "Performance"));
+ when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance"));
+
+ IssueTrackingResult trackingResult = new IssueTrackingResult();
+ trackingResult.addUnmatched(unmatchedIssue);
+
+ String originalSource = "public interface Action {\n"
+ + " void method1();\n"
+ + " void method2();\n"
+ + " void method3();\n"
+ + " void method4();\n"
+ + " void method5();\n" // Original issue here
+ + "}";
+ String newSource = "public interface Action {\n"
+ + " void method1();\n"
+ + " void method2();\n"
+ + " void method3();\n"
+ + " void method4();\n"
+ + " void method6();\n" // Poof, no method5 anymore
+ + "}";
+
+ Resource file = mockHashes(originalSource, newSource);
+
+ when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
+
+ decorator.doDecorate(file);
+
+ verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
+ verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
+
+ ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
+ verify(issueCache).put(argument.capture());
+
+ DefaultIssue issue = argument.getValue();
+ assertThat(issue.line()).isEqualTo(issueOnLine);
+ assertThat(issue.key()).isEqualTo("ABCDE");
+ assertThat(issue.isNew()).isFalse();
+ assertThat(issue.isEndOfLife()).isFalse();
+ assertThat(issue.isOnDisabledRule()).isFalse();
+ }
+
+ @Test
+ public void manual_issues_should_be_kept_if_multiple_matching_lines_found() throws Exception {
+ // "Unmatched" issues existed in previous scan but not in current one -> they have to be closed
+
+ // INPUT : one issue existing during previous scan
+ final int issueOnLine = 3;
+ PreviousIssue unmatchedIssue = new PreviousIssueFromDb(new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(issueOnLine).setStatus("OPEN")
+ .setRuleKey("manual", "Performance"));
+ when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance"));
+
+ IssueTrackingResult trackingResult = new IssueTrackingResult();
+ trackingResult.addUnmatched(unmatchedIssue);
+
+ String originalSource = "public class Action {\n"
+ + " void method1() {\n"
+ + " notify();\n" // initial issue
+ + " }\n"
+ + "}";
+ String newSource = "public class Action {\n"
+ + " \n"
+ + " void method1() {\n" // new issue will appear here
+ + " notify();\n"
+ + " }\n"
+ + " void method2() {\n"
+ + " notify();\n"
+ + " }\n"
+ + "}";
+ Resource file = mockHashes(originalSource, newSource);
+
+ when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
+
+ decorator.doDecorate(file);
+
+ verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
+ verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
+
+ ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
+ verify(issueCache).put(argument.capture());
+
+ DefaultIssue issue = argument.getValue();
+ assertThat(issue.line()).isEqualTo(issueOnLine);
+ assertThat(issue.key()).isEqualTo("ABCDE");
+ assertThat(issue.isNew()).isFalse();
+ assertThat(issue.isEndOfLife()).isFalse();
+ assertThat(issue.isOnDisabledRule()).isFalse();
+ }
+
+ @Test
+ public void manual_issues_should_be_closed_if_manual_rule_is_removed() throws Exception {
+ // "Unmatched" issues existed in previous scan but not in current one -> they have to be closed
+
+ // INPUT : one issue existing during previous scan
+ PreviousIssue unmatchedIssue = new PreviousIssueFromDb(new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(1).setStatus("OPEN").setRuleKey("manual", "Performance"));
+ when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance").setStatus(Rule.STATUS_REMOVED));
+
+ IssueTrackingResult trackingResult = new IssueTrackingResult();
+ trackingResult.addUnmatched(unmatchedIssue);
+
+ String source = "public interface Action {}";
+ Resource file = mockHashes(source, source);
+
+ when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
+
+ decorator.doDecorate(file);
+
+ verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
+ verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
+
+ ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
+ verify(issueCache).put(argument.capture());
+
+ DefaultIssue issue = argument.getValue();
+ assertThat(issue.key()).isEqualTo("ABCDE");
+ assertThat(issue.isNew()).isFalse();
+ assertThat(issue.isEndOfLife()).isTrue();
+ assertThat(issue.isOnDisabledRule()).isTrue();
+ }
+
+ @Test
+ public void manual_issues_should_be_closed_if_manual_rule_is_not_found() throws Exception {
+ // "Unmatched" issues existed in previous scan but not in current one -> they have to be closed
+
+ // INPUT : one issue existing during previous scan
+ PreviousIssue unmatchedIssue = new PreviousIssueFromDb(new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(1).setStatus("OPEN").setRuleKey("manual", "Performance"));
+ when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(null);
+
+ IssueTrackingResult trackingResult = new IssueTrackingResult();
+ trackingResult.addUnmatched(unmatchedIssue);
+
+ String source = "public interface Action {}";
+ Resource file = mockHashes(source, source);
+
+ when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
+
+ decorator.doDecorate(file);
+
+ verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
+ verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
+
+ ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
+ verify(issueCache).put(argument.capture());
+
+ DefaultIssue issue = argument.getValue();
+ assertThat(issue.key()).isEqualTo("ABCDE");
+ assertThat(issue.isNew()).isFalse();
+ assertThat(issue.isEndOfLife()).isTrue();
+ assertThat(issue.isOnDisabledRule()).isTrue();
+ }
+
+ @Test
+ public void manual_issues_should_be_closed_if_new_source_is_shorter() throws Exception {
+ // "Unmatched" issues existed in previous scan but not in current one -> they have to be closed
+
+ // INPUT : one issue existing during previous scan
+ PreviousIssue unmatchedIssue = new PreviousIssueFromDb(new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(6).setStatus("OPEN").setRuleKey("manual", "Performance"));
+ when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(null);
+
+ IssueTrackingResult trackingResult = new IssueTrackingResult();
+ trackingResult.addUnmatched(unmatchedIssue);
+
+ String originalSource = "public interface Action {\n"
+ + " void method1();\n"
+ + " void method2();\n"
+ + " void method3();\n"
+ + " void method4();\n"
+ + " void method5();\n"
+ + "}";
+ String newSource = "public interface Action {\n"
+ + " void method1();\n"
+ + " void method2();\n"
+ + "}";
+ Resource file = mockHashes(originalSource, newSource);
+
+ when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
+
+ decorator.doDecorate(file);
+
+ verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
+ verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
+
+ ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
+ verify(issueCache).put(argument.capture());
+
+ DefaultIssue issue = argument.getValue();
+ verify(updater).setResolution(eq(issue), eq(Issue.RESOLUTION_REMOVED), any(IssueChangeContext.class));
+ verify(updater).setStatus(eq(issue), eq(Issue.STATUS_CLOSED), any(IssueChangeContext.class));
+
+ assertThat(issue.key()).isEqualTo("ABCDE");
+ assertThat(issue.isNew()).isFalse();
+ assertThat(issue.isEndOfLife()).isTrue();
+ assertThat(issue.isOnDisabledRule()).isTrue();
+ }
+
+ @Test
+ public void should_register_issues_on_deleted_components() throws Exception {
+ Project project = new Project("struts");
+ DefaultIssue openIssue = new DefaultIssue();
+ when(issueCache.byComponent("struts")).thenReturn(Arrays.asList(openIssue));
+ IssueDto deadIssue = new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey("squid", "AvoidCycle");
+ when(initialOpenIssues.selectAllIssues()).thenReturn(Arrays.asList(deadIssue));
+
+ decorator.doDecorate(project);
+
+ // the dead issue must be closed -> apply automatic transition, notify handlers and add to cache
+ verify(workflow, times(2)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
+ verify(handlers, times(2)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
+ verify(issueCache, times(2)).put(any(DefaultIssue.class));
+
+ verify(issueCache).put(argThat(new ArgumentMatcher<DefaultIssue>() {
+ @Override
+ public boolean matches(Object o) {
+ DefaultIssue dead = (DefaultIssue) o;
+ return "ABCDE".equals(dead.key()) && !dead.isNew() && dead.isEndOfLife();
+ }
+ }));
+ }
+
+ @Test
+ public void merge_matched_issue() throws Exception {
+ PreviousIssue previousIssue = new PreviousIssueFromDb(new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey("squid", "AvoidCycle")
+ .setLine(10).setSeverity("MAJOR").setMessage("Message").setEffortToFix(1.5).setDebt(1L).setProjectKey("sample"));
+ DefaultIssue issue = new DefaultIssue();
+
+ IssueTrackingResult trackingResult = mock(IssueTrackingResult.class);
+ when(trackingResult.matched()).thenReturn(newArrayList(issue));
+ when(trackingResult.matching(eq(issue))).thenReturn(previousIssue);
+ decorator.mergeMatched(trackingResult);
+
+ verify(updater).setPastSeverity(eq(issue), eq("MAJOR"), any(IssueChangeContext.class));
+ verify(updater).setPastLine(eq(issue), eq(10));
+ verify(updater).setPastMessage(eq(issue), eq("Message"), any(IssueChangeContext.class));
+ verify(updater).setPastEffortToFix(eq(issue), eq(1.5), any(IssueChangeContext.class));
+ verify(updater).setPastTechnicalDebt(eq(issue), eq(Duration.create(1L)), any(IssueChangeContext.class));
+ verify(updater).setPastProject(eq(issue), eq("sample"), any(IssueChangeContext.class));
+ }
+
+ @Test
+ public void merge_matched_issue_on_manual_severity() throws Exception {
+ PreviousIssue previousIssue = new PreviousIssueFromDb(new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey("squid", "AvoidCycle")
+ .setLine(10).setManualSeverity(true).setSeverity("MAJOR").setMessage("Message").setEffortToFix(1.5).setDebt(1L));
+ DefaultIssue issue = new DefaultIssue();
+
+ IssueTrackingResult trackingResult = mock(IssueTrackingResult.class);
+ when(trackingResult.matched()).thenReturn(newArrayList(issue));
+ when(trackingResult.matching(eq(issue))).thenReturn(previousIssue);
+ decorator.mergeMatched(trackingResult);
+
+ assertThat(issue.manualSeverity()).isTrue();
+ assertThat(issue.severity()).isEqualTo("MAJOR");
+ verify(updater, never()).setPastSeverity(eq(issue), anyString(), any(IssueChangeContext.class));
+ }
+
+ @Test
+ public void merge_issue_changelog_with_previous_changelog() throws Exception {
+ when(initialOpenIssues.selectChangelog("ABCDE")).thenReturn(newArrayList(new IssueChangeDto().setIssueKey("ABCD").setCreatedAt(System2.INSTANCE.now())));
+
+ PreviousIssue previousIssue = new PreviousIssueFromDb(new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey("squid", "AvoidCycle")
+ .setLine(10).setMessage("Message").setEffortToFix(1.5).setDebt(1L).setCreatedAt(System2.INSTANCE.now()));
+ DefaultIssue issue = new DefaultIssue();
+
+ IssueTrackingResult trackingResult = mock(IssueTrackingResult.class);
+ when(trackingResult.matched()).thenReturn(newArrayList(issue));
+ when(trackingResult.matching(eq(issue))).thenReturn(previousIssue);
+ decorator.mergeMatched(trackingResult);
+
+ assertThat(issue.changes()).hasSize(1);
+ }
+
+ private byte[][] computeHashes(String source) {
+ String[] lines = source.split("\n");
+ byte[][] hashes = new byte[lines.length][];
+ for (int i = 0; i < lines.length; i++) {
+ hashes[i] = DigestUtils.md5(lines[i].replaceAll("[\t ]", ""));
+ }
+ return hashes;
+ }
+
+ 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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.base.Charsets;
+import com.google.common.collect.Lists;
+import com.google.common.io.Resources;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.junit.Before;
+import org.junit.Test;
+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.batch.scan.LastLineHashes;
+import org.sonar.core.issue.db.IssueDto;
+
+import java.io.IOException;
+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 {
+
+ IssueTracking tracking;
+ Resource project;
+ SourceHashHolder sourceHashHolder;
+ LastLineHashes lastSnapshots;
+ long violationId = 0;
+
+ @Before
+ public void before() {
+ lastSnapshots = mock(LastLineHashes.class);
+
+ project = mock(Project.class);
+ tracking = new IssueTracking();
+ }
+
+ @Test
+ public void key_should_be_the_prioritary_field_to_check() {
+ PreviousIssueFromDb referenceIssue1 = newReferenceIssue("message", 10, "squid", "AvoidCycle", "checksum1");
+ referenceIssue1.getDto().setKee("100");
+ PreviousIssueFromDb 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.<PreviousIssue>newArrayList(referenceIssue1, referenceIssue2), null, result);
+ // same key
+ assertThat(result.matching(newIssue)).isSameAs(referenceIssue2);
+ }
+
+ @Test
+ public void checksum_should_have_greater_priority_than_line() {
+ PreviousIssue referenceIssue1 = newReferenceIssue("message", 1, "squid", "AvoidCycle", "checksum1");
+ PreviousIssue 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");
+ PreviousIssue 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");
+ PreviousIssue 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");
+ PreviousIssue 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);
+ PreviousIssue 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");
+ PreviousIssue 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");
+ PreviousIssue 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");
+ PreviousIssue 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");
+ PreviousIssue 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");
+ PreviousIssue 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");
+ PreviousIssue 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");
+ PreviousIssue 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);
+ PreviousIssue 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);
+ }
+
+ /**
+ * SONAR-3072
+ */
+ @Test
+ public void should_track_issues_based_on_blocks_recognition_on_example1() throws Exception {
+ initLastHashes("example1-v1", "example1-v2");
+
+ PreviousIssue referenceIssue1 = newReferenceIssue("Indentation", 7, "squid", "AvoidCycle", null);
+ PreviousIssue 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");
+
+ PreviousIssue 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");
+
+ PreviousIssue referenceIssue1 = newReferenceIssue("Avoid unused local variables such as 'j'.", 6, "squid", "AvoidCycle", "63c11570fc0a76434156be5f8138fa03");
+ PreviousIssue referenceIssue2 = newReferenceIssue("Avoid unused private methods such as 'myMethod()'.", 13, "squid", "NullDeref", "ef23288705d1ef1e512448ace287586e");
+ PreviousIssue 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() throws Exception {
+ sourceHashHolder = mock(SourceHashHolder.class);
+
+ PreviousIssue 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"), Charsets.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 PreviousIssueFromDb 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 PreviousIssueFromDb(referenceIssue);
+ }
+
+ private void initLastHashes(String reference, String newSource) throws IOException {
+ DefaultInputFile inputFile = mock(DefaultInputFile.class);
+ byte[][] hashes = computeHashes(load(newSource));
+ when(inputFile.lineHashes()).thenReturn(hashes);
+ when(inputFile.key()).thenReturn("foo:Action.java");
+ when(lastSnapshots.getLineHashes("foo:Action.java")).thenReturn(computeHexHashes(load(reference)));
+ sourceHashHolder = new SourceHashHolder(inputFile, lastSnapshots);
+ }
+
+ private byte[][] computeHashes(String source) {
+ String[] lines = source.split("\n");
+ byte[][] hashes = new byte[lines.length][];
+ for (int i = 0; i < lines.length; i++) {
+ hashes[i] = DigestUtils.md5(lines[i].replaceAll("[\t ]", ""));
+ }
+ return hashes;
+ }
+
+ 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;
+ }
+}
--- /dev/null
+/*
+ * 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 org.junit.Test;
+
+import static org.apache.commons.codec.digest.DigestUtils.md5Hex;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class RollingFileHashesTest {
+
+ @Test
+ public void test_equals() {
+ RollingFileHashes a = RollingFileHashes.create(FileHashes.create(new String[] {md5Hex("line0"), md5Hex("line1"), md5Hex("line2")}), 1);
+ RollingFileHashes b = RollingFileHashes.create(FileHashes.create(new String[] {md5Hex("line0"), md5Hex("line1"), md5Hex("line2"), md5Hex("line3")}), 1);
+
+ assertThat(a.getHash(1) == b.getHash(1)).isTrue();
+ assertThat(a.getHash(2) == b.getHash(2)).isTrue();
+ assertThat(a.getHash(3) == b.getHash(3)).isFalse();
+
+ RollingFileHashes c = RollingFileHashes.create(FileHashes.create(new String[] {md5Hex("line-1"), md5Hex("line0"), md5Hex("line1"), md5Hex("line2"), md5Hex("line3")}), 1);
+ assertThat(a.getHash(1) == c.getHash(2)).isFalse();
+ assertThat(a.getHash(2) == c.getHash(3)).isTrue();
+ }
+
+}
--- /dev/null
+/*
+ * 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 org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.batch.scan.LastLineHashes;
+
+import static org.apache.commons.codec.digest.DigestUtils.md5;
+import static org.apache.commons.codec.digest.DigestUtils.md5Hex;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class SourceHashHolderTest {
+
+ SourceHashHolder sourceHashHolder;
+
+ LastLineHashes lastSnapshots;
+ DefaultInputFile file;
+
+ @Before
+ public void setUp() {
+ lastSnapshots = mock(LastLineHashes.class);
+ file = mock(DefaultInputFile.class);
+
+ sourceHashHolder = new SourceHashHolder(file, lastSnapshots);
+ }
+
+ @Test
+ public void should_lazy_load_line_hashes() {
+ final String source = "source";
+ when(file.lineHashes()).thenReturn(new byte[][] {md5(source), null});
+
+ assertThat(sourceHashHolder.getHashedSource().getHash(1)).isEqualTo(md5Hex(source));
+ assertThat(sourceHashHolder.getHashedSource().getHash(2)).isEqualTo("");
+ verify(file).lineHashes();
+ verify(file).key();
+ verify(file).status();
+
+ assertThat(sourceHashHolder.getHashedSource().getHash(1)).isEqualTo(md5Hex(source));
+ Mockito.verifyNoMoreInteractions(file);
+ }
+
+ @Test
+ public void should_lazy_load_reference_hashes_when_status_changed() {
+ final String source = "source";
+ String key = "foo:src/Foo.java";
+ when(file.lineHashes()).thenReturn(new byte[][] {md5(source)});
+ when(file.key()).thenReturn(key);
+ when(file.status()).thenReturn(InputFile.Status.CHANGED);
+ when(lastSnapshots.getLineHashes(key)).thenReturn(new String[] {md5Hex(source)});
+
+ assertThat(sourceHashHolder.getHashedReference().getHash(1)).isEqualTo(md5Hex(source));
+ verify(lastSnapshots).getLineHashes(key);
+
+ assertThat(sourceHashHolder.getHashedReference().getHash(1)).isEqualTo(md5Hex(source));
+ Mockito.verifyNoMoreInteractions(lastSnapshots);
+ }
+
+ @Test
+ public void should_not_load_reference_hashes_when_status_same() {
+ final String source = "source";
+ String key = "foo:src/Foo.java";
+ when(file.lineHashes()).thenReturn(new byte[][] {md5(source)});
+ when(file.key()).thenReturn(key);
+ when(file.status()).thenReturn(InputFile.Status.SAME);
+
+ assertThat(sourceHashHolder.getHashedReference().getHash(1)).isEqualTo(md5Hex(source));
+ assertThat(sourceHashHolder.getHashedReference().getHash(1)).isEqualTo(md5Hex(source));
+ Mockito.verifyNoMoreInteractions(lastSnapshots);
+ }
+
+ @Test
+ public void no_reference_hashes_when_status_added() {
+ final String source = "source";
+ String key = "foo:src/Foo.java";
+ when(file.lineHashes()).thenReturn(new byte[][] {md5(source)});
+ when(file.key()).thenReturn(key);
+ when(file.status()).thenReturn(InputFile.Status.ADDED);
+
+ assertThat(sourceHashHolder.getHashedReference()).isNull();
+ Mockito.verifyNoMoreInteractions(lastSnapshots);
+ }
+
+}
.newScanTask(new File(projectDir, "sonar-project.properties"))
.start();
- assertThat(result.issues()).hasSize(26);
+ assertThat(result.issues()).hasSize(14);
}
@Test
.property("sonar.xoo.internalKey", "OneIssuePerLine.internal")
.start();
- assertThat(result.issues()).hasSize(26 /* 26 lines */+ 3 /* 3 files */);
+ assertThat(result.issues()).hasSize(14 /* 8 + 6 lines */+ 2 /* 2 files */);
}
@Test
.property("sonar.issue.ignore.allfile.1.fileRegexp", "object")
.start();
- assertThat(result.issues()).hasSize(20);
+ assertThat(result.issues()).hasSize(8);
}
@Test
package org.sonar.batch.mediumtest.issues;
import com.google.common.collect.ImmutableMap;
+import org.apache.commons.codec.digest.DigestUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.sonar.batch.mediumtest.BatchMediumTester;
import org.sonar.batch.mediumtest.TaskResult;
import org.sonar.batch.protocol.input.ActiveRule;
+import org.sonar.batch.protocol.input.issues.PreviousIssue;
import org.sonar.xoo.XooPlugin;
import java.io.File;
.addDefaultQProfile("xoo", "Sonar Way")
.activateRule(new ActiveRule("xoo", "OneIssuePerLine", "One issue per line", "MAJOR", "OneIssuePerLine.internal", "xoo"))
.bootstrapProperties(ImmutableMap.of("sonar.analysis.mode", "sensor"))
+ .addPreviousIssue(new PreviousIssue().setKey("xyz")
+ .setComponentKey("sample:xources/hello/HelloJava.xoo")
+ .setRuleKey("xoo", "OneIssuePerLine")
+ .setLine(1)
+ .setOverriddenSeverity("MAJOR")
+ .setChecksum(DigestUtils.md5Hex("packagehello;"))
+ .setStatus("OPEN"))
.build();
@Before
.property("sonar.issuesReport.console.enable", "true")
.start();
- assertThat(result.issues()).hasSize(26);
+ assertThat(result.issues()).hasSize(14);
}
}
+++ /dev/null
-/*
- * 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.referential;
-
-import com.google.common.collect.Maps;
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.api.batch.bootstrap.ProjectDefinition;
-import org.sonar.api.batch.bootstrap.ProjectReactor;
-import org.sonar.api.database.DatabaseSession;
-import org.sonar.batch.bootstrap.AnalysisMode;
-import org.sonar.batch.bootstrap.ServerClient;
-import org.sonar.batch.bootstrap.TaskProperties;
-import org.sonar.batch.rule.ModuleQProfiles;
-
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-public class DefaultProjectReferentialsLoaderTest {
-
- private DefaultProjectReferentialsLoader loader;
- private ServerClient serverClient;
- private AnalysisMode analysisMode;
- private ProjectReactor reactor;
- private TaskProperties taskProperties;
-
- @Before
- public void prepare() {
- serverClient = mock(ServerClient.class);
- analysisMode = mock(AnalysisMode.class);
- loader = new DefaultProjectReferentialsLoader(mock(DatabaseSession.class), serverClient, analysisMode);
- loader = spy(loader);
- doReturn(null).when(loader).lastSnapshotCreationDate(anyString());
- when(serverClient.request(anyString())).thenReturn("{}");
- taskProperties = new TaskProperties(Maps.<String, String>newHashMap(), "");
- }
-
- @Test
- public void passPreviewParameter() {
- reactor = new ProjectReactor(ProjectDefinition.create().setKey("foo"));
- when(analysisMode.isPreview()).thenReturn(false);
- loader.load(reactor, taskProperties);
- verify(serverClient).request("/batch/project?key=foo&preview=false");
-
- when(analysisMode.isPreview()).thenReturn(true);
- loader.load(reactor, taskProperties);
- verify(serverClient).request("/batch/project?key=foo&preview=true");
- }
-
- @Test
- public void passAndEncodeProjectKeyParameter() {
- reactor = new ProjectReactor(ProjectDefinition.create().setKey("foo bàr"));
- loader.load(reactor, taskProperties);
- verify(serverClient).request("/batch/project?key=foo+b%C3%A0r&preview=false");
- }
-
- @Test
- public void passAndEncodeProfileParameter() {
- reactor = new ProjectReactor(ProjectDefinition.create().setKey("foo"));
- taskProperties.properties().put(ModuleQProfiles.SONAR_PROFILE_PROP, "my-profile#2");
- loader.load(reactor, taskProperties);
- verify(serverClient).request("/batch/project?key=foo&profile=my-profile%232&preview=false");
- }
-
-}
--- /dev/null
+/*
+ * 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.repository;
+
+import org.sonar.batch.repository.DefaultProjectReferentialsLoader;
+
+import com.google.common.collect.Maps;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.batch.bootstrap.ProjectDefinition;
+import org.sonar.api.batch.bootstrap.ProjectReactor;
+import org.sonar.api.database.DatabaseSession;
+import org.sonar.batch.bootstrap.AnalysisMode;
+import org.sonar.batch.bootstrap.ServerClient;
+import org.sonar.batch.bootstrap.TaskProperties;
+import org.sonar.batch.rule.ModuleQProfiles;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class DefaultProjectReferentialsLoaderTest {
+
+ private DefaultProjectReferentialsLoader loader;
+ private ServerClient serverClient;
+ private AnalysisMode analysisMode;
+ private ProjectReactor reactor;
+ private TaskProperties taskProperties;
+
+ @Before
+ public void prepare() {
+ serverClient = mock(ServerClient.class);
+ analysisMode = mock(AnalysisMode.class);
+ loader = new DefaultProjectReferentialsLoader(mock(DatabaseSession.class), serverClient, analysisMode);
+ loader = spy(loader);
+ doReturn(null).when(loader).lastSnapshotCreationDate(anyString());
+ when(serverClient.request(anyString())).thenReturn("{}");
+ taskProperties = new TaskProperties(Maps.<String, String>newHashMap(), "");
+ }
+
+ @Test
+ public void passPreviewParameter() {
+ reactor = new ProjectReactor(ProjectDefinition.create().setKey("foo"));
+ when(analysisMode.isPreview()).thenReturn(false);
+ loader.load(reactor, taskProperties);
+ verify(serverClient).request("/batch/project?key=foo&preview=false");
+
+ when(analysisMode.isPreview()).thenReturn(true);
+ loader.load(reactor, taskProperties);
+ verify(serverClient).request("/batch/project?key=foo&preview=true");
+ }
+
+ @Test
+ public void passAndEncodeProjectKeyParameter() {
+ reactor = new ProjectReactor(ProjectDefinition.create().setKey("foo bàr"));
+ loader.load(reactor, taskProperties);
+ verify(serverClient).request("/batch/project?key=foo+b%C3%A0r&preview=false");
+ }
+
+ @Test
+ public void passAndEncodeProfileParameter() {
+ reactor = new ProjectReactor(ProjectDefinition.create().setKey("foo"));
+ taskProperties.properties().put(ModuleQProfiles.SONAR_PROFILE_PROP, "my-profile#2");
+ loader.load(reactor, taskProperties);
+ verify(serverClient).request("/batch/project?key=foo&profile=my-profile%232&preview=false");
+ }
+
+}
import org.sonar.api.utils.MessageException;
import org.sonar.batch.bootstrap.AnalysisMode;
import org.sonar.batch.bootstrap.GlobalSettings;
-import org.sonar.batch.protocol.input.ProjectReferentials;
+import org.sonar.batch.protocol.input.ProjectRepository;
import java.util.List;
@Rule
public ExpectedException thrown = ExpectedException.none();
- ProjectReferentials projectRef;
+ ProjectRepository projectRef;
private AnalysisMode mode;
@Before
public void before() {
- projectRef = new ProjectReferentials();
+ projectRef = new ProjectRepository();
mode = mock(AnalysisMode.class);
}
*/
package org.sonar.batch.scan;
+import org.sonar.batch.repository.ProjectRepositoriesLoader;
+
import org.junit.Before;
import org.junit.Test;
import org.sonar.api.BatchExtension;
import org.sonar.batch.bootstrap.TaskProperties;
import org.sonar.batch.profiling.PhasesSumUpTimeProfiler;
import org.sonar.batch.protocol.input.GlobalReferentials;
-import org.sonar.batch.protocol.input.ProjectReferentials;
-import org.sonar.batch.referential.ProjectReferentialsLoader;
+import org.sonar.batch.protocol.input.ProjectRepository;
import org.sonar.batch.scan.maven.MavenPluginExecutor;
import java.util.Collections;
GlobalReferentials globalRef = new GlobalReferentials();
settings = new GlobalSettings(bootstrapProperties, new PropertyDefinitions(), globalRef, analysisMode);
parentContainer.add(settings);
- ProjectReferentialsLoader projectReferentialsLoader = new ProjectReferentialsLoader() {
+ ProjectRepositoriesLoader projectReferentialsLoader = new ProjectRepositoriesLoader() {
@Override
- public ProjectReferentials load(ProjectReactor reactor, TaskProperties taskProperties) {
- return new ProjectReferentials();
+ public ProjectRepository load(ProjectReactor reactor, TaskProperties taskProperties) {
+ return new ProjectRepository();
}
};
parentContainer.add(projectReferentialsLoader);
import org.sonar.batch.bootstrap.BootstrapProperties;
import org.sonar.batch.bootstrap.GlobalSettings;
import org.sonar.batch.protocol.input.GlobalReferentials;
-import org.sonar.batch.protocol.input.ProjectReferentials;
+import org.sonar.batch.protocol.input.ProjectRepository;
import java.util.Collections;
@Rule
public ExpectedException thrown = ExpectedException.none();
- ProjectReferentials projectRef;
+ ProjectRepository projectRef;
ProjectDefinition project = ProjectDefinition.create().setKey("struts");
GlobalSettings bootstrapProps;
@Before
public void prepare() {
- projectRef = new ProjectReferentials();
+ projectRef = new ProjectRepository();
mode = mock(AnalysisMode.class);
bootstrapProps = new GlobalSettings(new BootstrapProperties(Collections.<String, String>emptyMap()), new PropertyDefinitions(), new GlobalReferentials(), mode);
}
assertThat(metadata.lines).isEqualTo(4);
assertThat(metadata.hash).isEqualTo(md5Hex("föo\nbàr\n\u1D11Ebaßz\n"));
assertThat(metadata.originalLineOffsets).containsOnly(0, 5, 10, 18);
- assertThat(metadata.lineHashes[0]).containsOnly(md5("föo"));
- assertThat(metadata.lineHashes[1]).containsOnly(md5("bàr"));
- assertThat(metadata.lineHashes[2]).containsOnly(md5("\u1D11Ebaßz"));
+ assertThat(metadata.lineHashes[0]).containsExactly(md5("föo"));
+ assertThat(metadata.lineHashes[1]).containsExactly(md5("bàr"));
+ assertThat(metadata.lineHashes[2]).containsExactly(md5("\u1D11Ebaßz"));
assertThat(metadata.lineHashes[3]).isNull();
}
assertThat(metadata.lines).isEqualTo(4);
assertThat(metadata.hash).isEqualTo(md5Hex("föo\nbàr\n\u1D11Ebaßz\n"));
assertThat(metadata.originalLineOffsets).containsOnly(0, 5, 10, 18);
- assertThat(metadata.lineHashes[0]).containsOnly(md5("föo"));
- assertThat(metadata.lineHashes[1]).containsOnly(md5("bàr"));
- assertThat(metadata.lineHashes[2]).containsOnly(md5("\u1D11Ebaßz"));
+ assertThat(metadata.lineHashes[0]).containsExactly(md5("föo"));
+ assertThat(metadata.lineHashes[1]).containsExactly(md5("bàr"));
+ assertThat(metadata.lineHashes[2]).containsExactly(md5("\u1D11Ebaßz"));
assertThat(metadata.lineHashes[3]).isNull();
}
package org.sonar.batch.scan.filesystem;
import org.junit.Test;
-import org.sonar.batch.protocol.input.ProjectReferentials;
+import org.sonar.batch.protocol.input.ProjectRepository;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
public class StatusDetectionFactoryTest {
@Test
public void testCreate() throws Exception {
- StatusDetectionFactory factory = new StatusDetectionFactory(mock(ProjectReferentials.class));
+ StatusDetectionFactory factory = new StatusDetectionFactory(mock(ProjectRepository.class));
StatusDetection detection = factory.create();
assertThat(detection).isNotNull();
}
import org.junit.Test;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.batch.protocol.input.FileData;
-import org.sonar.batch.protocol.input.ProjectReferentials;
+import org.sonar.batch.protocol.input.ProjectRepository;
import static org.assertj.core.api.Assertions.assertThat;
public class StatusDetectionTest {
@Test
public void detect_status() throws Exception {
- ProjectReferentials ref = new ProjectReferentials();
+ ProjectRepository ref = new ProjectRepository();
ref.addFileData("foo", "src/Foo.java", new FileData("ABCDE", true, null, null, null));
ref.addFileData("foo", "src/Bar.java", new FileData("FGHIJ", true, null, null, null));
StatusDetection statusDetection = new StatusDetection(ref);
-lines:8
ncloc:3
complexity:1
-lines:5
ncloc:5
complexity:2
--- /dev/null
+package example1;
+
+public class Toto {
+
+ public void doSomething() {
+ // doSomething
+ }
+
+ public void doSomethingElse() {
+ // doSomethingElse
+ }
+}
--- /dev/null
+package example1;
+
+public class Toto {
+
+ public Toto(){}
+
+ public void doSomethingNew() {
+ // doSomethingNew
+ }
+
+ public void doSomethingElseNew() {
+ // doSomethingElseNew
+ }
+
+ public void doSomething() {
+ // doSomething
+ }
+
+ public void doSomethingElse() {
+ // doSomethingElse
+ }
+}
--- /dev/null
+package example2;
+
+public class Toto {
+ void method1() {
+ System.out.println("toto");
+ }
+}
--- /dev/null
+package example2;
+
+public class Toto {
+
+ void method2() {
+ System.out.println("toto");
+ }
+
+ void method1() {
+ System.out.println("toto");
+ }
+
+ void method3() {
+ System.out.println("toto");
+ }
+}
--- /dev/null
+package sample;
+
+public class Sample {
+
+ public Sample(int i) {
+ int j = i+1; // violation: unused local variable
+ }
+
+ public boolean avoidUtilityClass() {
+ return true;
+ }
+
+ private String myMethod() { // violation : unused private method
+ return "hello";
+ }
+}
--- /dev/null
+package sample;
+
+public class Sample {
+
+ public Sample(int i) {
+ int j = i+1; // still the same violation: unused local variable
+ }
+
+ public boolean avoidUtilityClass() {
+ return true;
+ }
+
+ private String myMethod() { // violation "unused private method" is fixed because it's called in newViolation
+ return "hello";
+ }
+
+ public void newViolation() {
+ String msg = myMethod(); // new violation : msg is an unused variable
+ }
+}
* for example :
* <pre>
* DefaultFileSystem fs = new DefaultFileSystem();
- * fs.add(new DefaultInputFile("src/foo/bar.php"));
+ * fs.add(new DefaultInputFile("myprojectKey", "src/foo/bar.php"));
* </pre>
*
* @since 4.2