From: Julien HENRY Date: Wed, 26 Nov 2014 14:09:22 +0000 (+0100) Subject: SONAR-5868 Issue tracking now use hash WS instead of raw sources X-Git-Tag: 5.0-RC1~117 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=0c8e5f2b0a7e36bdc7e0ee67b28982c0e7749995;p=sonarqube.git SONAR-5868 Issue tracking now use hash WS instead of raw sources --- diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTracking.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTracking.java index 0e26a7904cc..b470b424f84 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTracking.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTracking.java @@ -30,14 +30,8 @@ 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.HashedSequence; -import org.sonar.plugins.core.issue.tracking.HashedSequenceComparator; import org.sonar.plugins.core.issue.tracking.IssueTrackingBlocksRecognizer; -import org.sonar.plugins.core.issue.tracking.RollingHashSequence; -import org.sonar.plugins.core.issue.tracking.RollingHashSequenceComparator; -import org.sonar.plugins.core.issue.tracking.SourceChecksum; -import org.sonar.plugins.core.issue.tracking.StringText; -import org.sonar.plugins.core.issue.tracking.StringTextComparator; +import org.sonar.plugins.core.issue.tracking.RollingFileHashes; import javax.annotation.Nullable; @@ -49,10 +43,15 @@ import java.util.Map; public class IssueTracking implements BatchExtension { - public IssueTrackingResult track(SourceHashHolder sourceHashHolder, Collection dbIssues, Collection newIssues) { + /** + * @param sourceHashHolder Null when working on resource that is not a file (directory/project) + */ + public IssueTrackingResult track(@Nullable SourceHashHolder sourceHashHolder, Collection dbIssues, Collection newIssues) { IssueTrackingResult result = new IssueTrackingResult(); - setChecksumOnNewIssues(newIssues, sourceHashHolder); + if (sourceHashHolder != null) { + setChecksumOnNewIssues(newIssues, sourceHashHolder); + } // Map new issues with old ones mapIssues(newIssues, dbIssues, sourceHashHolder, result); @@ -63,9 +62,10 @@ public class IssueTracking implements BatchExtension { if (issues.isEmpty()) { return; } - List checksums = SourceChecksum.lineChecksumsOfFile(sourceHashHolder.getSource()); for (DefaultIssue issue : issues) { - issue.setChecksum(SourceChecksum.getChecksumForLine(checksums, issue.line())); + if (issue.line() != null) { + issue.setChecksum(sourceHashHolder.getHashedSource().getHash(issue.line())); + } } } @@ -80,7 +80,7 @@ public class IssueTracking implements BatchExtension { // If each new issue matches an old one we can stop the matching mechanism if (result.matched().size() != newIssues.size()) { - if (sourceHashHolder.hasBothReferenceAndCurrentSource() && hasLastScan) { + if (sourceHashHolder != null && sourceHashHolder.getHashedReference() != null && hasLastScan) { mapNewissues(sourceHashHolder, newIssues, result); } mapIssuesOnSameRule(newIssues, result); @@ -110,12 +110,10 @@ public class IssueTracking implements BatchExtension { private void mapNewissues(SourceHashHolder sourceHashHolder, Collection newIssues, IssueTrackingResult result) { - HashedSequenceComparator hashedComparator = new HashedSequenceComparator(StringTextComparator.IGNORE_WHITESPACE); - IssueTrackingBlocksRecognizer rec = new IssueTrackingBlocksRecognizer(sourceHashHolder.getHashedReference(), sourceHashHolder.getHashedSource(), hashedComparator); + IssueTrackingBlocksRecognizer rec = new IssueTrackingBlocksRecognizer(sourceHashHolder.getHashedReference(), sourceHashHolder.getHashedSource()); - RollingHashSequence> a = RollingHashSequence.wrap(sourceHashHolder.getHashedReference(), hashedComparator, 5); - RollingHashSequence> b = RollingHashSequence.wrap(sourceHashHolder.getHashedSource(), hashedComparator, 5); - RollingHashSequenceComparator> cmp = new RollingHashSequenceComparator>(hashedComparator); + RollingFileHashes a = RollingFileHashes.create(sourceHashHolder.getHashedReference(), 5); + RollingFileHashes b = RollingFileHashes.create(sourceHashHolder.getHashedSource(), 5); Multimap newIssuesByLines = newIssuesByLines(newIssues, rec, result); Multimap lastIssuesByLines = lastIssuesByLines(result.unmatched(), rec); @@ -123,7 +121,7 @@ public class IssueTracking implements BatchExtension { Map map = Maps.newHashMap(); for (Integer line : lastIssuesByLines.keySet()) { - int hash = cmp.hash(a, line - 1); + int hash = a.getHash(line); HashOccurrence hashOccurrence = map.get(hash); if (hashOccurrence == null) { // first occurrence in A @@ -137,7 +135,7 @@ public class IssueTracking implements BatchExtension { } for (Integer line : newIssuesByLines.keySet()) { - int hash = cmp.hash(b, line - 1); + int hash = b.getHash(line); HashOccurrence hashOccurrence = map.get(hash); if (hashOccurrence != null) { hashOccurrence.lineB = line; @@ -159,7 +157,7 @@ public class IssueTracking implements BatchExtension { List possibleLinePairs = Lists.newArrayList(); for (Integer oldLine : lastIssuesByLines.keySet()) { for (Integer newLine : newIssuesByLines.keySet()) { - int weight = rec.computeLengthOfMaximalBlock(oldLine - 1, newLine - 1); + int weight = rec.computeLengthOfMaximalBlock(oldLine, newLine); possibleLinePairs.add(new LinePair(oldLine, newLine, weight)); } } diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTrackingDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTrackingDecorator.java index 37276d00f8e..7c39348de1f 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTrackingDecorator.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTrackingDecorator.java @@ -30,13 +30,15 @@ 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.SonarIndex; +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; @@ -46,7 +48,8 @@ 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.LastSnapshots; +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; @@ -63,8 +66,7 @@ public class IssueTrackingDecorator implements Decorator { private final IssueCache issueCache; private final InitialOpenIssuesStack initialOpenIssues; private final IssueTracking tracking; - private final LastSnapshots lastSnapshots; - private final SonarIndex index; + private final LastLineHashes lastLineHashes; private final IssueHandlers handlers; private final IssueWorkflow workflow; private final IssueUpdater updater; @@ -72,23 +74,26 @@ public class IssueTrackingDecorator implements Decorator { 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, - LastSnapshots lastSnapshots, SonarIndex index, + LastLineHashes lastLineHashes, IssueHandlers handlers, IssueWorkflow workflow, IssueUpdater updater, Project project, ResourcePerspectives perspectives, RulesProfile rulesProfile, - RuleFinder ruleFinder) { + RuleFinder ruleFinder, InputPathCache inputPathCache) { this.issueCache = issueCache; this.initialOpenIssues = initialOpenIssues; this.tracking = tracking; - this.lastSnapshots = lastSnapshots; - this.index = index; + 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; @@ -120,7 +125,12 @@ public class IssueTrackingDecorator implements Decorator { // all the issues that are not closed in db before starting this module scan, including manual issues Collection dbOpenIssues = initialOpenIssues.selectAndRemoveIssues(resource.getEffectiveKey()); - SourceHashHolder sourceHashHolder = new SourceHashHolder(index, lastSnapshots, resource); + SourceHashHolder sourceHashHolder = null; + if (ResourceUtils.isFile(resource)) { + File sonarFile = (File) resource; + InputFile file = inputPathCache.getFile(project.getEffectiveKey(), sonarFile.getPath()); + sourceHashHolder = new SourceHashHolder((DefaultInputFile) file, lastLineHashes); + } IssueTrackingResult trackingResult = tracking.track(sourceHashHolder, dbOpenIssues, issues); diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/SourceHashHolder.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/SourceHashHolder.java index c10586fda05..ffb8be6b4ef 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/SourceHashHolder.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/SourceHashHolder.java @@ -19,77 +19,52 @@ */ package org.sonar.plugins.core.issue; -import org.apache.commons.lang.StringUtils; -import org.sonar.api.batch.SonarIndex; -import org.sonar.api.resources.Resource; -import org.sonar.batch.scan.LastSnapshots; -import org.sonar.plugins.core.issue.tracking.HashedSequence; -import org.sonar.plugins.core.issue.tracking.StringText; -import org.sonar.plugins.core.issue.tracking.StringTextComparator; +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 SonarIndex index; - private final LastSnapshots lastSnapshots; - private final Resource resource; - - private String source; - private boolean sourceInitialized; - private String referenceSource; - private boolean referenceSourceInitialized; + private final LastLineHashes lastSnapshots; - private HashedSequence hashedReference; - private HashedSequence hashedSource; + private FileHashes hashedReference; + private FileHashes hashedSource; + private DefaultInputFile inputFile; - public SourceHashHolder(SonarIndex index, LastSnapshots lastSnapshots, Resource resource) { - this.index = index; + public SourceHashHolder(DefaultInputFile inputFile, LastLineHashes lastSnapshots) { + this.inputFile = inputFile; this.lastSnapshots = lastSnapshots; - this.resource = resource; } private void initHashes() { - hashedReference = HashedSequence.wrap(new StringText(getReferenceSource()), StringTextComparator.IGNORE_WHITESPACE); - hashedSource = HashedSequence.wrap(new StringText(getSource()), StringTextComparator.IGNORE_WHITESPACE); - } - - public HashedSequence getHashedReference() { - initHashesIfNull(hashedReference); - return hashedReference; - } - - public HashedSequence getHashedSource() { - initHashesIfNull(hashedSource); - return hashedSource; - } - - public String getSource() { - if (!sourceInitialized) { - source = StringUtils.defaultString(index.getSource(resource), ""); - sourceInitialized = true; - } - return source; - } - - public String getReferenceSource() { - if (!referenceSourceInitialized) { - if (resource != null) { - referenceSource = lastSnapshots.getSource(resource); + 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; } - referenceSourceInitialized = true; } - return referenceSource; } - public boolean hasBothReferenceAndCurrentSource() { - return getSource() != null && getReferenceSource() != null; + @CheckForNull + public FileHashes getHashedReference() { + initHashes(); + return hashedReference; } - private void initHashesIfNull(Object required) { - if (required == null) { - initHashes(); - } + public FileHashes getHashedSource() { + initHashes(); + return hashedSource; } public Collection getNewLinesMatching(Integer originLine) { diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/FileHashes.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/FileHashes.java new file mode 100644 index 00000000000..d45ac2d65cc --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/FileHashes.java @@ -0,0 +1,77 @@ +/* + * 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 linesByHash; + + private FileHashes(String[] hashes, Multimap linesByHash) { + this.hashes = hashes; + this.linesByHash = linesByHash; + } + + public static FileHashes create(String[] hashes) { + int size = hashes.length; + Multimap 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 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 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], ""); + } +} diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/HashedSequence.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/HashedSequence.java deleted file mode 100644 index abfc23fc982..00000000000 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/HashedSequence.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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 java.util.Collection; - -/** - * Wraps a {@link Sequence} to assign hash codes to elements. - */ -public final class HashedSequence implements Sequence { - - final S base; - final int[] hashes; - final Multimap linesByHash; - - private HashedSequence(S base, int[] hashes, Multimap linesByHash) { - this.base = base; - this.hashes = hashes; - this.linesByHash = linesByHash; - } - - public static HashedSequence wrap(S base, SequenceComparator cmp) { - int size = base.length(); - int[] hashes = new int[size]; - Multimap linesByHash = LinkedHashMultimap.create(); - for (int i = 0; i < size; i++) { - hashes[i] = cmp.hash(base, i); - // indices in array are shifted one line before - linesByHash.put(hashes[i], i + 1); - } - return new HashedSequence(base, hashes, linesByHash); - } - - @Override - public int length() { - return base.length(); - } - - public Collection getLinesForHash(Integer hash) { - return linesByHash.get(hash); - } - - public Integer getHash(Integer line) { - // indices in array are shifted one line before - return hashes[line - 1]; - } -} diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/HashedSequenceComparator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/HashedSequenceComparator.java deleted file mode 100644 index 093cf962698..00000000000 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/HashedSequenceComparator.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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; - -/** - * Wrap another {@link SequenceComparator} for use with {@link HashedSequence}. - */ -public class HashedSequenceComparator implements SequenceComparator> { - - private final SequenceComparator cmp; - - public HashedSequenceComparator(SequenceComparator cmp) { - this.cmp = cmp; - } - - @Override - public boolean equals(HashedSequence a, int ai, HashedSequence b, int bi) { - if (a.hashes[ai] == b.hashes[bi]) { - return cmp.equals(a.base, ai, b.base, bi); - } - return false; - } - - @Override - public int hash(HashedSequence seq, int i) { - return seq.hashes[i]; - } - -} diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/IssueTrackingBlocksRecognizer.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/IssueTrackingBlocksRecognizer.java index 15d0bc15398..11611ad952d 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/IssueTrackingBlocksRecognizer.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/IssueTrackingBlocksRecognizer.java @@ -19,31 +19,16 @@ */ package org.sonar.plugins.core.issue.tracking; -import com.google.common.annotations.VisibleForTesting; -import org.sonar.plugins.core.issue.tracking.HashedSequence; -import org.sonar.plugins.core.issue.tracking.HashedSequenceComparator; -import org.sonar.plugins.core.issue.tracking.StringText; -import org.sonar.plugins.core.issue.tracking.StringTextComparator; - import javax.annotation.Nullable; public class IssueTrackingBlocksRecognizer { - private final HashedSequence a; - private final HashedSequence b; - private final HashedSequenceComparator cmp; - - @VisibleForTesting - public IssueTrackingBlocksRecognizer(String referenceSource, String source) { - this.a = HashedSequence.wrap(new StringText(referenceSource), StringTextComparator.IGNORE_WHITESPACE); - this.b = HashedSequence.wrap(new StringText(source), StringTextComparator.IGNORE_WHITESPACE); - this.cmp = new HashedSequenceComparator(StringTextComparator.IGNORE_WHITESPACE); - } + private final FileHashes a; + private final FileHashes b; - public IssueTrackingBlocksRecognizer(HashedSequence a, HashedSequence b, HashedSequenceComparator cmp) { + public IssueTrackingBlocksRecognizer(FileHashes a, FileHashes b) { this.a = a; this.b = b; - this.cmp = cmp; } public boolean isValidLineInReference(@Nullable Integer line) { @@ -55,24 +40,24 @@ public class IssueTrackingBlocksRecognizer { } /** - * @param startA number of line from first version of text (numbering starts from 0) - * @param startB number of line from second version of text (numbering starts from 0) + * @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 (!cmp.equals(a, startA, b, 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() && cmp.equals(a, ai, b, bi)) { + 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 && cmp.equals(a, ai, b, bi)) { + while (ai > 0 && bi > 0 && a.getHash(ai).equals(b.getHash(bi))) { ai--; bi--; length++; diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/RollingFileHashes.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/RollingFileHashes.java new file mode 100644 index 00000000000..313507a4d69 --- /dev/null +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/RollingFileHashes.java @@ -0,0 +1,89 @@ +/* + * 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; + } + + } + +} diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/RollingHashSequence.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/RollingHashSequence.java deleted file mode 100644 index cb3d7acfc09..00000000000 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/RollingHashSequence.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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; - -/** - * Wraps a {@link Sequence} to assign hash codes to elements. - */ -public class RollingHashSequence implements Sequence { - - final S base; - final int[] hashes; - - public static RollingHashSequence wrap(S base, SequenceComparator cmp, int lines) { - int size = base.length(); - int[] hashes = new int[size]; - - RollingHashCalculator hashCalulator = new RollingHashCalculator(lines * 2 + 1); - for (int i = 0; i <= Math.min(size - 1, lines); i++) { - hashCalulator.add(cmp.hash(base, i)); - } - for (int i = 0; i < size; i++) { - hashes[i] = hashCalulator.getHash(); - if (i - lines >= 0) { - hashCalulator.remove(cmp.hash(base, i - lines)); - } - if (i + lines + 1 < size) { - hashCalulator.add(cmp.hash(base, i + lines + 1)); - } else { - hashCalulator.add(0); - } - } - - return new RollingHashSequence(base, hashes); - } - - private RollingHashSequence(S base, int[] hashes) { - this.base = base; - this.hashes = hashes; - } - - @Override - public int length() { - return base.length(); - } - - 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; - } - - } - -} diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/RollingHashSequenceComparator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/RollingHashSequenceComparator.java deleted file mode 100644 index b3663b47d48..00000000000 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/RollingHashSequenceComparator.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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; - -/** - * Wrap another {@link SequenceComparator} for use with {@link RollingHashSequence}. - */ -public class RollingHashSequenceComparator implements SequenceComparator> { - - private final SequenceComparator cmp; - - public RollingHashSequenceComparator(SequenceComparator cmp) { - this.cmp = cmp; - } - - @Override - public boolean equals(RollingHashSequence a, int ai, RollingHashSequence b, int bi) { - if (a.hashes[ai] == b.hashes[bi]) { - return cmp.equals(a.base, ai, b.base, bi); - } - return false; - } - - @Override - public int hash(RollingHashSequence seq, int i) { - return seq.hashes[i]; - } - -} diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/Sequence.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/Sequence.java deleted file mode 100644 index e4f2b12e668..00000000000 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/Sequence.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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; - -/** - * Arbitrary sequence of elements. - */ -public interface Sequence { - - /** - * @return total number of items in the sequence - */ - int length(); - -} diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/SequenceComparator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/SequenceComparator.java deleted file mode 100644 index b2a860531a3..00000000000 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/SequenceComparator.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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; - -/** - * Equivalence function for a {@link Sequence}. - */ -public interface SequenceComparator { - - /** - * Compare two items to determine if they are equivalent. - */ - boolean equals(S a, int ai, S b, int bi); - - /** - * Get a hash value for an item in a sequence. - * - * If two items are equal according to this comparator's - * {@link #equals(Sequence, int, Sequence, int)} method, - * then this hash method must produce the same integer result for both items. - * However not required to have different hash values for different items. - */ - int hash(S seq, int i); - -} diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/SourceChecksum.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/SourceChecksum.java deleted file mode 100644 index 21c1bb207f7..00000000000 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/SourceChecksum.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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.Lists; -import org.apache.commons.codec.digest.DigestUtils; -import org.apache.commons.lang.StringUtils; - -import javax.annotation.Nullable; -import java.util.List; - -public final class SourceChecksum { - - private static final String SPACE_CHARS = "\t\n\r "; - - private SourceChecksum() { - // only static methods - } - - /** - * @param line line number (first line has number 1) - * @return checksum or null if checksum not exists for line - */ - public static String getChecksumForLine(List checksums, @Nullable Integer line) { - if (line == null || line < 1 || line > checksums.size()) { - return null; - } - return checksums.get(line - 1); - } - - public static List lineChecksumsOfFile(String file) { - List result = Lists.newArrayList(); - if (file != null) { - String[] lines = file.split("\r?\n|\r", -1); - for (String line : lines) { - result.add(lineChecksum(line)); - } - } - return result; - } - - public static String lineChecksum(String line) { - String reducedLine = StringUtils.replaceChars(line, SPACE_CHARS, ""); - return DigestUtils.md5Hex(reducedLine); - } - -} diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/StringText.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/StringText.java deleted file mode 100644 index 50ce2d4912f..00000000000 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/StringText.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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.Lists; - -import java.util.List; - -/** - * Text is a {@link Sequence} of lines. - */ -public class StringText implements Sequence { - - final String content; - - /** - * Map of line number to starting position within {@link #content}. - */ - final List lines; - - public StringText(String str) { - this.content = str; - this.lines = lineMap(content, 0, content.length()); - } - - @Override - public int length() { - return lines.size() - 2; - } - - private static List lineMap(String buf, int ptr, int end) { - List lines = Lists.newArrayList(); - lines.add(Integer.MIN_VALUE); - for (; ptr < end; ptr = nextLF(buf, ptr)) { - lines.add(ptr); - } - lines.add(end); - return lines; - } - - private static int nextLF(String b, int ptr) { - return next(b, ptr, '\n'); - } - - private static int next(final String b, int ptr, final char chrA) { - final int sz = b.length(); - while (ptr < sz) { - if (b.charAt(ptr++) == chrA) { - return ptr; - } - } - return ptr; - } - -} diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/StringTextComparator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/StringTextComparator.java deleted file mode 100644 index e7f63900ea1..00000000000 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/tracking/StringTextComparator.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * 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; - -/** - * Equivalence function for {@link StringText}. - */ -public abstract class StringTextComparator implements SequenceComparator { - - /** - * Ignores all whitespace. - */ - public static final StringTextComparator IGNORE_WHITESPACE = new StringTextComparator() { - - @Override - public boolean equals(StringText a, int ai, StringText b, int bi) { - ai++; - bi++; - int as = a.lines.get(ai); - int bs = b.lines.get(bi); - int ae = a.lines.get(ai + 1); - int be = b.lines.get(bi + 1); - ae = trimTrailingWhitespace(a.content, as, ae); - be = trimTrailingWhitespace(b.content, bs, be); - while ((as < ae) && (bs < be)) { - char ac = a.content.charAt(as); - char bc = b.content.charAt(bs); - while ((as < ae - 1) && (Character.isWhitespace(ac))) { - as++; - ac = a.content.charAt(as); - } - while ((bs < be - 1) && (Character.isWhitespace(bc))) { - bs++; - bc = b.content.charAt(bs); - } - if (ac != bc) { - return false; - } - as++; - bs++; - } - return (as == ae) && (bs == be); - } - - @Override - protected int hashRegion(String content, int start, int end) { - int hash = 5381; - for (; start < end; start++) { - char c = content.charAt(start); - if (!Character.isWhitespace(c)) { - hash = ((hash << 5) + hash) + (c & 0xff); - } - } - return hash; - } - - }; - - @Override - public int hash(StringText seq, int line) { - final int begin = seq.lines.get(line + 1); - final int end = seq.lines.get(line + 2); - return hashRegion(seq.content, begin, end); - } - - protected abstract int hashRegion(String content, int start, int end); - - public static int trimTrailingWhitespace(String content, int start, int end) { - end--; - while (start <= end && Character.isWhitespace(content.charAt(end))) { - end--; - } - return end + 1; - } - -} diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingDecoratorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingDecoratorTest.java index 7d4bc08c27a..844b2a8dfd8 100644 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingDecoratorTest.java +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingDecoratorTest.java @@ -19,12 +19,13 @@ */ 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.SonarIndex; +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; @@ -38,7 +39,8 @@ import org.sonar.api.rules.Rule; import org.sonar.api.rules.RuleFinder; import org.sonar.api.utils.Duration; import org.sonar.batch.issue.IssueCache; -import org.sonar.batch.scan.LastSnapshots; +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; @@ -55,11 +57,17 @@ import static com.google.common.collect.Lists.newArrayList; import static org.fest.assertions.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.*; -import static org.mockito.Mockito.anyString; +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 extends AbstractDaoTestCase { @@ -67,14 +75,14 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase { IssueCache issueCache = mock(IssueCache.class, RETURNS_MOCKS); InitialOpenIssuesStack initialOpenIssues = mock(InitialOpenIssuesStack.class); IssueTracking tracking = mock(IssueTracking.class, RETURNS_MOCKS); - LastSnapshots lastSnapshots = mock(LastSnapshots.class); - SonarIndex index = mock(SonarIndex.class); + 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() { @@ -83,14 +91,14 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase { initialOpenIssues, tracking, lastSnapshots, - index, handlers, workflow, updater, - mock(Project.class), + new Project("foo"), perspectives, profile, - ruleFinder); + ruleFinder, + inputPathCache); } @Test @@ -160,8 +168,6 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase { @Test public void manual_issues_should_be_moved_if_matching_line_found() throws Exception { - Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123); - // 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")); @@ -183,8 +189,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase { + " void method3();\n" + " void method4();\n" + "}"; - when(index.getSource(file)).thenReturn(newSource); - when(lastSnapshots.getSource(file)).thenReturn(originalSource); + Resource file = mockHashes(originalSource, newSource); when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult); @@ -204,9 +209,19 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase { 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 { - Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123); // INPUT : one issue existing during previous scan IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(1).setStatus("CLOSED").setRuleKey("manual", "Performance"); @@ -216,8 +231,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase { trackingResult.addUnmatched(unmatchedIssue); String originalSource = "public interface Action {}"; - when(index.getSource(file)).thenReturn(originalSource); - when(lastSnapshots.getSource(file)).thenReturn(originalSource); + Resource file = mockHashes(originalSource, originalSource); when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult); @@ -240,7 +254,6 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase { @Test public void manual_issues_should_be_untouched_if_line_is_null() throws Exception { - Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123); // INPUT : one issue existing during previous scan IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(null).setStatus("OPEN").setRuleKey("manual", "Performance"); @@ -250,8 +263,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase { trackingResult.addUnmatched(unmatchedIssue); String originalSource = "public interface Action {}"; - when(index.getSource(file)).thenReturn(originalSource); - when(lastSnapshots.getSource(file)).thenReturn(originalSource); + Resource file = mockHashes(originalSource, originalSource); when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult); @@ -275,7 +287,6 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase { @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 - Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123); // INPUT : one issue existing during previous scan final int issueOnLine = 6; @@ -299,8 +310,8 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase { + " void method4();\n" + " void method6();\n" // Poof, no method5 anymore + "}"; - when(index.getSource(file)).thenReturn(newSource); - when(lastSnapshots.getSource(file)).thenReturn(originalSource); + + Resource file = mockHashes(originalSource, newSource); when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult); @@ -323,7 +334,6 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase { @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 - Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123); // INPUT : one issue existing during previous scan final int issueOnLine = 3; @@ -347,8 +357,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase { + " notify();\n" + " }\n" + "}"; - when(index.getSource(file)).thenReturn(newSource); - when(lastSnapshots.getSource(file)).thenReturn(originalSource); + Resource file = mockHashes(originalSource, newSource); when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult); @@ -368,11 +377,9 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase { 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 - Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123); // INPUT : one issue existing during previous scan IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(1).setStatus("OPEN").setRuleKey("manual", "Performance"); @@ -382,8 +389,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase { trackingResult.addUnmatched(unmatchedIssue); String source = "public interface Action {}"; - when(index.getSource(file)).thenReturn(source); - when(lastSnapshots.getSource(file)).thenReturn(source); + Resource file = mockHashes(source, source); when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult); @@ -405,7 +411,6 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase { @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 - Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123); // INPUT : one issue existing during previous scan IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(1).setStatus("OPEN").setRuleKey("manual", "Performance"); @@ -415,8 +420,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase { trackingResult.addUnmatched(unmatchedIssue); String source = "public interface Action {}"; - when(index.getSource(file)).thenReturn(source); - when(lastSnapshots.getSource(file)).thenReturn(source); + Resource file = mockHashes(source, source); when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult); @@ -438,7 +442,6 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase { @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 - Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123); // INPUT : one issue existing during previous scan IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(6).setStatus("OPEN").setRuleKey("manual", "Performance"); @@ -458,8 +461,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase { + " void method1();\n" + " void method2();\n" + "}"; - when(index.getSource(file)).thenReturn(newSource); - when(lastSnapshots.getSource(file)).thenReturn(originalSource); + Resource file = mockHashes(originalSource, newSource); when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult); @@ -556,4 +558,22 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase { 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; + } + } diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingTest.java index 4b14f77c5b5..bef10585986 100644 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingTest.java +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingTest.java @@ -22,15 +22,16 @@ 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.SonarIndex; +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.LastSnapshots; +import org.sonar.batch.scan.LastLineHashes; import org.sonar.core.issue.db.IssueDto; import java.io.IOException; @@ -48,14 +49,12 @@ public class IssueTrackingTest { IssueTracking tracking; Resource project; SourceHashHolder sourceHashHolder; - SonarIndex index; - LastSnapshots lastSnapshots; + LastLineHashes lastSnapshots; long violationId = 0; @Before public void before() { - index = mock(SonarIndex.class); - lastSnapshots = mock(LastSnapshots.class); + lastSnapshots = mock(LastLineHashes.class); project = mock(Project.class); tracking = new IssueTracking(); @@ -77,8 +76,6 @@ public class IssueTrackingTest { @Test public void checksum_should_have_greater_priority_than_line() { - sourceHashHolder = new SourceHashHolder(index, lastSnapshots, null); - IssueDto referenceIssue1 = newReferenceIssue("message", 1, "squid", "AvoidCycle", "checksum1"); IssueDto referenceIssue2 = newReferenceIssue("message", 3, "squid", "AvoidCycle", "checksum2"); @@ -86,7 +83,7 @@ public class IssueTrackingTest { DefaultIssue newIssue2 = newDefaultIssue("message", 5, RuleKey.of("squid", "AvoidCycle"), "checksum2"); IssueTrackingResult result = new IssueTrackingResult(); - tracking.mapIssues(newArrayList(newIssue1, newIssue2), newArrayList(referenceIssue1, referenceIssue2), sourceHashHolder, result); + tracking.mapIssues(newArrayList(newIssue1, newIssue2), newArrayList(referenceIssue1, referenceIssue2), null, result); assertThat(result.matching(newIssue1)).isSameAs(referenceIssue1); assertThat(result.matching(newIssue2)).isSameAs(referenceIssue2); } @@ -96,61 +93,51 @@ public class IssueTrackingTest { */ @Test public void same_rule_and_null_line_and_checksum_but_different_messages() { - sourceHashHolder = new SourceHashHolder(index, lastSnapshots, null); - 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), sourceHashHolder, result); + 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() { - sourceHashHolder = new SourceHashHolder(index, lastSnapshots, null); - 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), sourceHashHolder, result); + tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), null, result); assertThat(result.matching(newIssue)).isSameAs(referenceIssue); } @Test public void same_rule_and_line_message() { - sourceHashHolder = new SourceHashHolder(index, lastSnapshots, null); - 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), sourceHashHolder, result); + tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), null, result); assertThat(result.matching(newIssue)).isSameAs(referenceIssue); } @Test public void should_ignore_reference_measure_without_checksum() { - sourceHashHolder = new SourceHashHolder(index, lastSnapshots, null); - 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), sourceHashHolder, result); + 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() { - sourceHashHolder = new SourceHashHolder(index, lastSnapshots, null); - 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), sourceHashHolder, result); + tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), null, result); assertThat(result.matching(newIssue)).isSameAs(referenceIssue); } @@ -159,80 +146,66 @@ public class IssueTrackingTest { */ @Test public void same_checksum_and_rule_but_different_line_and_different_message() { - sourceHashHolder = new SourceHashHolder(index, lastSnapshots, null); - 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), sourceHashHolder, result); + 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() { - sourceHashHolder = new SourceHashHolder(index, lastSnapshots, null); - 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), sourceHashHolder, result); + tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), null, result); assertThat(result.matching(newIssue)).isNull(); } @Test public void should_not_track_issue_if_different_rule() { - sourceHashHolder = new SourceHashHolder(index, lastSnapshots, null); - 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), sourceHashHolder, result); + tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), null, result); assertThat(result.matching(newIssue)).isNull(); } @Test public void should_compare_issues_with_database_format() { - sourceHashHolder = new SourceHashHolder(index, lastSnapshots, null); - // 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), sourceHashHolder, result); + 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 { - when(lastSnapshots.getSource(project)).thenReturn(load("example2-v1")); - when(index.getSource(project)).thenReturn(load("example2-v2")); - sourceHashHolder = new SourceHashHolder(index, lastSnapshots, project); + 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 = new IssueTrackingResult(); - tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), sourceHashHolder, result); + 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 { - when(lastSnapshots.getSource(project)).thenReturn(load("example2-v1")); - when(index.getSource(project)).thenReturn(load("example2-v2")); - sourceHashHolder = new SourceHashHolder(index, lastSnapshots, project); + 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 = new IssueTrackingResult(); - tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), sourceHashHolder, result); + IssueTrackingResult result = tracking.track(sourceHashHolder, newArrayList(referenceIssue), newArrayList(newIssue)); assertThat(result.matched()).isEmpty(); } @@ -242,15 +215,12 @@ public class IssueTrackingTest { */ @Test public void issue_not_associated_with_line() throws Exception { - when(lastSnapshots.getSource(project)).thenReturn(load("example2-v1")); - when(index.getSource(project)).thenReturn(load("example2-v2")); - sourceHashHolder = new SourceHashHolder(index, lastSnapshots, project); + 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 = new IssueTrackingResult(); - tracking.mapIssues(newArrayList(newIssue), newArrayList(referenceIssue), sourceHashHolder, result); + IssueTrackingResult result = tracking.track(sourceHashHolder, newArrayList(referenceIssue), newArrayList(newIssue)); assertThat(result.matching(newIssue)).isEqualTo(referenceIssue); } @@ -260,9 +230,7 @@ public class IssueTrackingTest { */ @Test public void should_track_issues_based_on_blocks_recognition_on_example1() throws Exception { - when(lastSnapshots.getSource(project)).thenReturn(load("example1-v1")); - when(index.getSource(project)).thenReturn(load("example1-v2")); - sourceHashHolder = new SourceHashHolder(index, lastSnapshots, project); + initLastHashes("example1-v1", "example1-v2"); IssueDto referenceIssue1 = newReferenceIssue("Indentation", 7, "squid", "AvoidCycle", null); IssueDto referenceIssue2 = newReferenceIssue("Indentation", 11, "squid", "AvoidCycle", null); @@ -272,8 +240,7 @@ public class IssueTrackingTest { DefaultIssue newIssue3 = newDefaultIssue("Indentation", 17, RuleKey.of("squid", "AvoidCycle"), null); DefaultIssue newIssue4 = newDefaultIssue("Indentation", 21, RuleKey.of("squid", "AvoidCycle"), null); - IssueTrackingResult result = new IssueTrackingResult(); - tracking.mapIssues(Arrays.asList(newIssue1, newIssue2, newIssue3, newIssue4), Arrays.asList(referenceIssue1, referenceIssue2), sourceHashHolder, result); + 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(); @@ -286,9 +253,7 @@ public class IssueTrackingTest { */ @Test public void should_track_issues_based_on_blocks_recognition_on_example2() throws Exception { - when(lastSnapshots.getSource(project)).thenReturn(load("example2-v1")); - when(index.getSource(project)).thenReturn(load("example2-v2")); - sourceHashHolder = new SourceHashHolder(index, lastSnapshots, project); + initLastHashes("example2-v1", "example2-v2"); IssueDto referenceIssue1 = newReferenceIssue("SystemPrintln", 5, "squid", "AvoidCycle", null); @@ -309,9 +274,7 @@ public class IssueTrackingTest { @Test public void should_track_issues_based_on_blocks_recognition_on_example3() throws Exception { - when(lastSnapshots.getSource(project)).thenReturn(load("example3-v1")); - when(index.getSource(project)).thenReturn(load("example3-v2")); - sourceHashHolder = new SourceHashHolder(index, lastSnapshots, project); + 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"); @@ -376,4 +339,31 @@ public class IssueTrackingTest { 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; + } } diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/SourceHashHolderTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/SourceHashHolderTest.java index 5687bc449f1..5c9c9c9a5ce 100644 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/SourceHashHolderTest.java +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/SourceHashHolderTest.java @@ -19,64 +19,89 @@ */ package org.sonar.plugins.core.issue; -import static org.mockito.Mockito.verify; - -import org.mockito.Mockito; import org.junit.Before; import org.junit.Test; -import org.sonar.api.batch.SonarIndex; -import org.sonar.api.resources.Resource; -import org.sonar.batch.scan.LastSnapshots; +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.fest.assertions.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; - SonarIndex index; - LastSnapshots lastSnapshots; - Resource resource; + LastLineHashes lastSnapshots; + DefaultInputFile file; @Before public void setUp() { - index = mock(SonarIndex.class); - lastSnapshots = mock(LastSnapshots.class); - resource = mock(Resource.class); + lastSnapshots = mock(LastLineHashes.class); + file = mock(DefaultInputFile.class); - sourceHashHolder = new SourceHashHolder(index, lastSnapshots, resource); + sourceHashHolder = new SourceHashHolder(file, lastSnapshots); } @Test - public void should_lazy_load_source() { + public void should_lazy_load_line_hashes() { final String source = "source"; - when(index.getSource(resource)).thenReturn(source); + when(file.lineHashes()).thenReturn(new byte[][] {md5(source), null}); - assertThat(sourceHashHolder.getSource()).isEqualTo(source); - verify(index).getSource(resource); + 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.getSource()).isEqualTo(source); - Mockito.verifyNoMoreInteractions(index); + assertThat(sourceHashHolder.getHashedSource().getHash(1)).isEqualTo(md5Hex(source)); + Mockito.verifyNoMoreInteractions(file); } @Test - public void should_lazy_load_reference_source() { + public void should_lazy_load_reference_hashes_when_status_changed() { final String source = "source"; - when(lastSnapshots.getSource(resource)).thenReturn(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.getReferenceSource()).isEqualTo(source); - verify(lastSnapshots).getSource(resource); + assertThat(sourceHashHolder.getHashedReference().getHash(1)).isEqualTo(md5Hex(source)); + Mockito.verifyNoMoreInteractions(lastSnapshots); + } - assertThat(sourceHashHolder.getReferenceSource()).isEqualTo(source); + @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 should_have_null_reference_source_for_null_resource() { - sourceHashHolder = new SourceHashHolder(index, lastSnapshots, null); + 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.getReferenceSource()).isNull(); + assertThat(sourceHashHolder.getHashedReference()).isNull(); Mockito.verifyNoMoreInteractions(lastSnapshots); } + } diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/IssueTrackingBlocksRecognizerTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/IssueTrackingBlocksRecognizerTest.java index 871402fe7d4..d48126b7d0c 100644 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/IssueTrackingBlocksRecognizerTest.java +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/IssueTrackingBlocksRecognizerTest.java @@ -20,7 +20,6 @@ package org.sonar.plugins.core.issue.tracking; import org.junit.Test; -import org.sonar.plugins.core.issue.tracking.IssueTrackingBlocksRecognizer; import static org.fest.assertions.Assertions.assertThat; @@ -28,23 +27,23 @@ public class IssueTrackingBlocksRecognizerTest { @Test public void test() { - assertThat(compute(t("abcde"), t("abcde"), 3, 3)).isEqualTo(5); - assertThat(compute(t("abcde"), t("abcd"), 3, 3)).isEqualTo(4); - assertThat(compute(t("bcde"), t("abcde"), 3, 3)).isEqualTo(0); - assertThat(compute(t("bcde"), t("abcde"), 2, 3)).isEqualTo(4); + 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(String a, String b, int ai, int bi) { + 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 String t(String text) { - StringBuilder sb = new StringBuilder(); + private static FileHashes t(String text) { + String[] array = new String[text.length()]; for (int i = 0; i < text.length(); i++) { - sb.append(text.charAt(i)).append('\n'); + array[i] = "" + text.charAt(i); } - return sb.toString(); + return FileHashes.create(array); } } diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/RollingFileHashesTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/RollingFileHashesTest.java new file mode 100644 index 00000000000..db1050c761b --- /dev/null +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/RollingFileHashesTest.java @@ -0,0 +1,43 @@ +/* + * 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.fest.assertions.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(); + } + +} diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/RollingHashSequenceTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/RollingHashSequenceTest.java deleted file mode 100644 index 6a4b69b6667..00000000000 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/RollingHashSequenceTest.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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 org.sonar.plugins.core.issue.tracking.RollingHashSequence; -import org.sonar.plugins.core.issue.tracking.RollingHashSequenceComparator; -import org.sonar.plugins.core.issue.tracking.StringText; -import org.sonar.plugins.core.issue.tracking.StringTextComparator; - -import static org.fest.assertions.Assertions.assertThat; - -public class RollingHashSequenceTest { - - @Test - public void test_hash() { - StringText seq = new StringText("line0 \n line1 \n line2"); - StringTextComparator cmp = StringTextComparator.IGNORE_WHITESPACE; - RollingHashSequence seq2 = RollingHashSequence.wrap(seq, cmp, 1); - RollingHashSequenceComparator cmp2 = new RollingHashSequenceComparator(cmp); - - assertThat(seq2.length()).isEqualTo(3); - assertThat(cmp2.hash(seq2, 0)).isEqualTo(cmp.hash(seq, 0) * 31 + cmp.hash(seq, 1)); - assertThat(cmp2.hash(seq2, 1)).isEqualTo((cmp.hash(seq, 0) * 31 + cmp.hash(seq, 1)) * 31 + cmp.hash(seq, 2)); - assertThat(cmp2.hash(seq2, 2)).isEqualTo((cmp.hash(seq, 1) * 31 + cmp.hash(seq, 2)) * 31); - } - - @Test - public void test_equals() { - StringTextComparator baseCmp = StringTextComparator.IGNORE_WHITESPACE; - RollingHashSequence a = RollingHashSequence.wrap(new StringText("line0 \n line1 \n line2"), baseCmp, 1); - RollingHashSequence b = RollingHashSequence.wrap(new StringText("line0 \n line1 \n line2 \n line3"), baseCmp, 1); - RollingHashSequenceComparator cmp = new RollingHashSequenceComparator(baseCmp); - - assertThat(cmp.equals(a, 0, b, 0)).isTrue(); - assertThat(cmp.equals(a, 1, b, 1)).isTrue(); - assertThat(cmp.equals(a, 2, b, 2)).isFalse(); - } - -} diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/SourceChecksumTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/SourceChecksumTest.java deleted file mode 100644 index e81b27798af..00000000000 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/SourceChecksumTest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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 org.sonar.plugins.core.issue.tracking.SourceChecksum; - -import java.util.List; - -import static org.fest.assertions.Assertions.assertThat; - -public class SourceChecksumTest { - @Test - public void shouldGetChecksumForLine() { - List checksums = SourceChecksum.lineChecksumsOfFile("line"); - assertThat(SourceChecksum.getChecksumForLine(checksums, null)).isNull(); - assertThat(SourceChecksum.getChecksumForLine(checksums, 0)).isNull(); - assertThat(SourceChecksum.getChecksumForLine(checksums, 1)).isNotNull(); - assertThat(SourceChecksum.getChecksumForLine(checksums, 2)).isNull(); - } - - /** - * See http://jira.codehaus.org/browse/SONAR-2358 - */ - @Test - public void shouldGenerateCorrectChecksums() { - List encoding = SourceChecksum.lineChecksumsOfFile("Привет Мир"); - assertThat(encoding).hasSize(1); - assertThat(encoding.get(0)).isEqualTo("5ba3a45e1299ede07f56e5531351be52"); - } - - @Test - public void shouldSplitLinesAndIgnoreSpaces() { - List crlf = SourceChecksum.lineChecksumsOfFile("Hello\r\nWorld"); - List lf = SourceChecksum.lineChecksumsOfFile("Hello\nWorld"); - List cr = SourceChecksum.lineChecksumsOfFile("Hello\rWorld"); - assertThat(crlf).hasSize(2); - assertThat(crlf.get(0)).isNotEqualTo(crlf.get(1)); - assertThat(lf).isEqualTo(crlf); - assertThat(cr).isEqualTo(crlf); - - assertThat(SourceChecksum.lineChecksum("\tvoid method() {\n")).isEqualTo(SourceChecksum.lineChecksum(" void method() {")); - } -} diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/StringTextComparatorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/StringTextComparatorTest.java deleted file mode 100644 index f0a1d027cab..00000000000 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/StringTextComparatorTest.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 org.sonar.plugins.core.issue.tracking.StringText; -import org.sonar.plugins.core.issue.tracking.StringTextComparator; - -import static org.fest.assertions.Assertions.assertThat; - - -public class StringTextComparatorTest { - - @Test - public void testEquals() { - StringTextComparator cmp = StringTextComparator.IGNORE_WHITESPACE; - - StringText a = new StringText("abc\nabc\na bc"); - StringText b = new StringText("abc\nabc d\nab c"); - - assertThat(cmp.equals(a, 0, b, 0)).as("abc == abc").isTrue(); - assertThat(cmp.equals(a, 1, b, 1)).as("abc != abc d").isFalse(); - assertThat(cmp.equals(a, 2, b, 2)).as("a bc == ab c").isTrue(); - assertThat(cmp.hash(a, 0)).isEqualTo(cmp.hash(b, 0)); - assertThat(cmp.hash(a, 2)).isEqualTo(cmp.hash(b, 2)); - } - -} diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/StringTextTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/StringTextTest.java deleted file mode 100644 index 765ee05554d..00000000000 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/tracking/StringTextTest.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 org.sonar.plugins.core.issue.tracking.StringText; - -import static org.fest.assertions.Assertions.assertThat; - -public class StringTextTest { - - @Test - public void testEmpty() { - StringText r = new StringText(""); - assertThat(r.length()).isEqualTo(0); - } - - @Test - public void testTwoLines() { - StringText r = new StringText("a\nb"); - assertThat(r.length()).isEqualTo(2); - } - -} diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/LastLineHashes.java b/sonar-batch/src/main/java/org/sonar/batch/scan/LastLineHashes.java new file mode 100644 index 00000000000..ed0e3adce3b --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/LastLineHashes.java @@ -0,0 +1,53 @@ +/* + * 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.scan; + +import com.google.common.base.Splitter; +import com.google.common.collect.Iterators; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.BatchComponent; +import org.sonar.api.utils.TimeProfiler; +import org.sonar.batch.bootstrap.ServerClient; + +public class LastLineHashes implements BatchComponent { + + private static final Logger LOG = LoggerFactory.getLogger(LastLineHashes.class); + + private final ServerClient server; + + public LastLineHashes(ServerClient server) { + this.server = server; + } + + public String[] getLineHashes(String fileKey) { + String hashesFromWs = loadHashesFromWs(fileKey); + return hashesFromWs != null ? Iterators.toArray(Splitter.on('\n').split(hashesFromWs).iterator(), String.class) : null; + } + + private String loadHashesFromWs(String fileKey) { + TimeProfiler profiler = new TimeProfiler(LOG).start("Load previous line hashes of: " + fileKey).setLevelToDebug(); + try { + return server.request("/api/sources/hash?key=" + ServerClient.encodeForUrl(fileKey)); + } finally { + profiler.stop(); + } + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/LastSnapshots.java b/sonar-batch/src/main/java/org/sonar/batch/scan/LastSnapshots.java deleted file mode 100644 index 8a5d2bf2ee7..00000000000 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/LastSnapshots.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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.scan; - -import org.apache.commons.lang.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.sonar.api.BatchComponent; -import org.sonar.api.resources.Resource; -import org.sonar.api.resources.ResourceUtils; -import org.sonar.api.utils.HttpDownloader; -import org.sonar.api.utils.TimeProfiler; -import org.sonar.batch.bootstrap.AnalysisMode; -import org.sonar.batch.bootstrap.ServerClient; -import org.sonar.core.source.db.SnapshotSourceDao; - -import javax.annotation.CheckForNull; - -public class LastSnapshots implements BatchComponent { - - private static final Logger LOG = LoggerFactory.getLogger(LastSnapshots.class); - - private final AnalysisMode analysisMode; - private final ServerClient server; - private final SnapshotSourceDao sourceDao; - - public LastSnapshots(AnalysisMode analysisMode, SnapshotSourceDao dao, ServerClient server) { - this.analysisMode = analysisMode; - this.sourceDao = dao; - this.server = server; - } - - public String getSource(Resource resource) { - String source = null; - if (ResourceUtils.isFile(resource)) { - if (analysisMode.isPreview()) { - source = loadSourceFromWs(resource); - } else { - source = loadSourceFromDb(resource); - } - } - return StringUtils.defaultString(source, ""); - } - - private String loadSourceFromWs(Resource resource) { - TimeProfiler profiler = new TimeProfiler(LOG).start("Load previous source code of: " + resource.getEffectiveKey()).setLevelToDebug(); - try { - return server - .request("/api/sources/raw?key=" + ServerClient.encodeForUrl(resource.getEffectiveKey()), "GET", false, analysisMode.getPreviewReadTimeoutSec() * 1000); - } catch (HttpDownloader.HttpException he) { - if (he.getResponseCode() == 404) { - return ""; - } - throw he; - } finally { - profiler.stop(); - } - } - - @CheckForNull - private String loadSourceFromDb(Resource resource) { - return sourceDao.selectSnapshotSourceByComponentKey(resource.getEffectiveKey()); - } -} diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java index e1a1467a95f..b22723814e0 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java @@ -149,7 +149,7 @@ public class ProjectScanContainer extends ComponentContainer { ResourceKeyMigration.class, DefaultFileLinesContextFactory.class, ProjectLock.class, - LastSnapshots.class, + LastLineHashes.class, Caches.class, SnapshotCache.class, ResourceCache.class, diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/LastLineHashesTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/LastLineHashesTest.java new file mode 100644 index 00000000000..7490781fc2d --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/scan/LastLineHashesTest.java @@ -0,0 +1,82 @@ +/* + * 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.scan; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.utils.HttpDownloader; +import org.sonar.batch.bootstrap.ServerClient; + +import java.net.URI; +import java.net.URISyntaxException; + +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class LastLineHashesTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Before + public void before() { + } + + @Test + public void should_download_source_from_ws_if_preview_mode() { + ServerClient server = mock(ServerClient.class); + when(server.request(anyString())).thenReturn("ae12\n\n43fb"); + + LastLineHashes lastSnapshots = new LastLineHashes(server); + + String[] hashes = lastSnapshots.getLineHashes("myproject:org/foo/Bar.c"); + assertThat(hashes).containsOnly("ae12", "", "43fb"); + verify(server).request("/api/sources/hash?key=myproject%3Aorg%2Ffoo%2FBar.c"); + } + + @Test + public void should_download_source_with_space_from_ws_if_preview_mode() { + ServerClient server = mock(ServerClient.class); + when(server.request(anyString())).thenReturn("ae12\n\n43fb"); + + LastLineHashes lastSnapshots = new LastLineHashes(server); + + String[] hashes = lastSnapshots.getLineHashes("myproject:org/foo/Foo Bar.c"); + assertThat(hashes).containsOnly("ae12", "", "43fb"); + verify(server).request("/api/sources/hash?key=myproject%3Aorg%2Ffoo%2FFoo+Bar.c"); + } + + @Test + public void should_fail_to_download_source_from_ws() throws URISyntaxException { + ServerClient server = mock(ServerClient.class); + when(server.request(anyString())).thenThrow(new HttpDownloader.HttpException(new URI(""), 500)); + + LastLineHashes lastSnapshots = new LastLineHashes(server); + + thrown.expect(HttpDownloader.HttpException.class); + lastSnapshots.getLineHashes("foo"); + } + +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/LastSnapshotsTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/LastSnapshotsTest.java deleted file mode 100644 index 719ae5c1775..00000000000 --- a/sonar-batch/src/test/java/org/sonar/batch/scan/LastSnapshotsTest.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * 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.scan; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.api.resources.File; -import org.sonar.api.resources.Project; -import org.sonar.api.utils.HttpDownloader; -import org.sonar.batch.bootstrap.AnalysisMode; -import org.sonar.batch.bootstrap.ServerClient; -import org.sonar.core.persistence.TestDatabase; -import org.sonar.core.source.db.SnapshotSourceDao; - -import java.net.URI; -import java.net.URISyntaxException; - -import static org.fest.assertions.Assertions.assertThat; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; - -public class LastSnapshotsTest { - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - @Rule - public TestDatabase db = new TestDatabase(); - - private AnalysisMode mode; - - @Before - public void before() { - mode = mock(AnalysisMode.class); - when(mode.getPreviewReadTimeoutSec()).thenReturn(30); - } - - @Test - public void should_get_source_of_last_snapshot() { - db.prepareDbUnit(getClass(), "last_snapshot.xml"); - - ServerClient server = mock(ServerClient.class); - LastSnapshots lastSnapshots = new LastSnapshots(mode, new SnapshotSourceDao(db.myBatis()), server); - - assertThat(lastSnapshots.getSource(newFile())).isEqualTo("this is bar"); - verifyZeroInteractions(server); - } - - @Test - public void should_return_empty_source_if_no_last_snapshot() { - db.prepareDbUnit(getClass(), "no_last_snapshot.xml"); - ServerClient server = mock(ServerClient.class); - - LastSnapshots lastSnapshots = new LastSnapshots(mode, new SnapshotSourceDao(db.myBatis()), server); - - assertThat(lastSnapshots.getSource(newFile())).isEqualTo(""); - verifyZeroInteractions(server); - } - - @Test - public void should_download_source_from_ws_if_preview_mode() { - db.prepareDbUnit(getClass(), "last_snapshot.xml"); - ServerClient server = mock(ServerClient.class); - when(server.request(anyString(), eq("GET"), eq(false), eq(30 * 1000))).thenReturn("downloaded source of Bar.c"); - - when(mode.isPreview()).thenReturn(true); - LastSnapshots lastSnapshots = new LastSnapshots(mode, new SnapshotSourceDao(db.myBatis()), server); - - String source = lastSnapshots.getSource(newFile()); - assertThat(source).isEqualTo("downloaded source of Bar.c"); - verify(server).request("/api/sources/raw?key=myproject%3Aorg%2Ffoo%2FBar.c", "GET", false, 30 * 1000); - } - - @Test - public void should_download_source_with_space_from_ws_if_preview_mode() { - db.prepareDbUnit(getClass(), "last_snapshot.xml"); - ServerClient server = mock(ServerClient.class); - when(server.request(anyString(), eq("GET"), eq(false), eq(30 * 1000))).thenReturn("downloaded source of Foo Bar.c"); - - when(mode.isPreview()).thenReturn(true); - LastSnapshots lastSnapshots = new LastSnapshots(mode, new SnapshotSourceDao(db.myBatis()), server); - - String source = lastSnapshots.getSource(newFile()); - assertThat(source).isEqualTo("downloaded source of Foo Bar.c"); - verify(server).request("/api/sources/raw?key=myproject%3Aorg%2Ffoo%2FBar.c", "GET", false, 30 * 1000); - } - - @Test - public void should_fail_to_download_source_from_ws() throws URISyntaxException { - db.prepareDbUnit(getClass(), "last_snapshot.xml"); - ServerClient server = mock(ServerClient.class); - when(server.request(anyString(), eq("GET"), eq(false), eq(30 * 1000))).thenThrow(new HttpDownloader.HttpException(new URI(""), 500)); - - when(mode.isPreview()).thenReturn(true); - LastSnapshots lastSnapshots = new LastSnapshots(mode, new SnapshotSourceDao(db.myBatis()), server); - - thrown.expect(HttpDownloader.HttpException.class); - lastSnapshots.getSource(newFile()); - } - - @Test - public void should_return_empty_source_if_preview_mode_and_no_last_snapshot() throws URISyntaxException { - db.prepareDbUnit(getClass(), "last_snapshot.xml"); - ServerClient server = mock(ServerClient.class); - when(server.request(anyString(), eq("GET"), eq(false), eq(30 * 1000))).thenThrow(new HttpDownloader.HttpException(new URI(""), 404)); - - when(mode.isPreview()).thenReturn(true); - LastSnapshots lastSnapshots = new LastSnapshots(mode, new SnapshotSourceDao(db.myBatis()), server); - - String source = lastSnapshots.getSource(newFileWithSpace()); - assertThat(source).isEqualTo(""); - verify(server).request("/api/sources/raw?key=myproject%3Aorg%2Ffoo%2FFoo+Bar.c", "GET", false, 30 * 1000); - } - - @Test - public void should_not_load_source_of_non_files() throws URISyntaxException { - db.prepareDbUnit(getClass(), "last_snapshot.xml"); - ServerClient server = mock(ServerClient.class); - - LastSnapshots lastSnapshots = new LastSnapshots(mode, new SnapshotSourceDao(db.myBatis()), server); - - String source = lastSnapshots.getSource(new Project("my-project")); - assertThat(source).isEqualTo(""); - } - - private File newFile() { - File file = new File("org/foo", "Bar.c"); - file.setEffectiveKey("myproject:org/foo/Bar.c"); - return file; - } - - private File newFileWithSpace() { - File file = new File("org/foo", "Foo Bar.c"); - file.setEffectiveKey("myproject:org/foo/Foo Bar.c"); - return file; - } -} diff --git a/sonar-batch/src/test/resources/org/sonar/batch/scan/LastSnapshotsTest/last_snapshot.xml b/sonar-batch/src/test/resources/org/sonar/batch/scan/LastSnapshotsTest/last_snapshot.xml deleted file mode 100644 index 8cc02f51187..00000000000 --- a/sonar-batch/src/test/resources/org/sonar/batch/scan/LastSnapshotsTest/last_snapshot.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/sonar-batch/src/test/resources/org/sonar/batch/scan/LastSnapshotsTest/no_last_snapshot.xml b/sonar-batch/src/test/resources/org/sonar/batch/scan/LastSnapshotsTest/no_last_snapshot.xml deleted file mode 100644 index 84d67a04385..00000000000 --- a/sonar-batch/src/test/resources/org/sonar/batch/scan/LastSnapshotsTest/no_last_snapshot.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file