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;
public class IssueTracking implements BatchExtension {
- public IssueTrackingResult track(SourceHashHolder sourceHashHolder, Collection<IssueDto> dbIssues, Collection<DefaultIssue> newIssues) {
+ /**
+ * @param sourceHashHolder Null when working on resource that is not a file (directory/project)
+ */
+ public IssueTrackingResult track(@Nullable SourceHashHolder sourceHashHolder, Collection<IssueDto> dbIssues, Collection<DefaultIssue> newIssues) {
IssueTrackingResult result = new IssueTrackingResult();
- setChecksumOnNewIssues(newIssues, sourceHashHolder);
+ if (sourceHashHolder != null) {
+ setChecksumOnNewIssues(newIssues, sourceHashHolder);
+ }
// Map new issues with old ones
mapIssues(newIssues, dbIssues, sourceHashHolder, result);
if (issues.isEmpty()) {
return;
}
- List<String> 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()));
+ }
}
}
// 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);
private void mapNewissues(SourceHashHolder sourceHashHolder, Collection<DefaultIssue> newIssues, IssueTrackingResult result) {
- HashedSequenceComparator<StringText> hashedComparator = new HashedSequenceComparator<StringText>(StringTextComparator.IGNORE_WHITESPACE);
- IssueTrackingBlocksRecognizer rec = new IssueTrackingBlocksRecognizer(sourceHashHolder.getHashedReference(), sourceHashHolder.getHashedSource(), hashedComparator);
+ IssueTrackingBlocksRecognizer rec = new IssueTrackingBlocksRecognizer(sourceHashHolder.getHashedReference(), sourceHashHolder.getHashedSource());
- RollingHashSequence<HashedSequence<StringText>> a = RollingHashSequence.wrap(sourceHashHolder.getHashedReference(), hashedComparator, 5);
- RollingHashSequence<HashedSequence<StringText>> b = RollingHashSequence.wrap(sourceHashHolder.getHashedSource(), hashedComparator, 5);
- RollingHashSequenceComparator<HashedSequence<StringText>> cmp = new RollingHashSequenceComparator<HashedSequence<StringText>>(hashedComparator);
+ RollingFileHashes a = RollingFileHashes.create(sourceHashHolder.getHashedReference(), 5);
+ RollingFileHashes b = RollingFileHashes.create(sourceHashHolder.getHashedSource(), 5);
Multimap<Integer, DefaultIssue> newIssuesByLines = newIssuesByLines(newIssues, rec, result);
Multimap<Integer, IssueDto> lastIssuesByLines = lastIssuesByLines(result.unmatched(), rec);
Map<Integer, HashOccurrence> map = Maps.newHashMap();
for (Integer line : lastIssuesByLines.keySet()) {
- int hash = cmp.hash(a, line - 1);
+ int hash = a.getHash(line);
HashOccurrence hashOccurrence = map.get(hash);
if (hashOccurrence == null) {
// first occurrence in A
}
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;
List<LinePair> 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));
}
}
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;
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;
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;
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;
// all the issues that are not closed in db before starting this module scan, including manual issues
Collection<IssueDto> dbOpenIssues = initialOpenIssues.selectAndRemoveIssues(resource.getEffectiveKey());
- SourceHashHolder sourceHashHolder = 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);
*/
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<StringText> hashedReference;
- private HashedSequence<StringText> 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<StringText> getHashedReference() {
- initHashesIfNull(hashedReference);
- return hashedReference;
- }
-
- public HashedSequence<StringText> 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<Integer> getNewLinesMatching(Integer originLine) {
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.plugins.core.issue.tracking;
+
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Multimap;
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.lang.ObjectUtils;
+
+import java.util.Collection;
+
+/**
+ * Wraps a {@link Sequence} to assign hash codes to elements.
+ */
+public final class FileHashes {
+
+ private final String[] hashes;
+ private final Multimap<String, Integer> linesByHash;
+
+ private FileHashes(String[] hashes, Multimap<String, Integer> linesByHash) {
+ this.hashes = hashes;
+ this.linesByHash = linesByHash;
+ }
+
+ public static FileHashes create(String[] hashes) {
+ int size = hashes.length;
+ Multimap<String, Integer> linesByHash = LinkedHashMultimap.create();
+ for (int i = 0; i < size; i++) {
+ // indices in array are shifted one line before
+ linesByHash.put(hashes[i], i + 1);
+ }
+ return new FileHashes(hashes, linesByHash);
+ }
+
+ public static FileHashes create(byte[][] hashes) {
+ int size = hashes.length;
+ Multimap<String, Integer> linesByHash = LinkedHashMultimap.create();
+ String[] hexHashes = new String[size];
+ for (int i = 0; i < size; i++) {
+ String hash = hashes[i] != null ? Hex.encodeHexString(hashes[i]) : "";
+ hexHashes[i] = hash;
+ // indices in array are shifted one line before
+ linesByHash.put(hash, i + 1);
+ }
+ return new FileHashes(hexHashes, linesByHash);
+ }
+
+ public int length() {
+ return hashes.length;
+ }
+
+ public Collection<Integer> getLinesForHash(String hash) {
+ return linesByHash.get(hash);
+ }
+
+ public String getHash(int line) {
+ // indices in array are shifted one line before
+ return (String) ObjectUtils.defaultIfNull(hashes[line - 1], "");
+ }
+}
+++ /dev/null
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.plugins.core.issue.tracking;
-
-import 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<S extends Sequence> implements Sequence {
-
- final S base;
- final int[] hashes;
- final Multimap<Integer, Integer> linesByHash;
-
- private HashedSequence(S base, int[] hashes, Multimap<Integer, Integer> linesByHash) {
- this.base = base;
- this.hashes = hashes;
- this.linesByHash = linesByHash;
- }
-
- public static <S extends Sequence> HashedSequence<S> wrap(S base, SequenceComparator<S> cmp) {
- int size = base.length();
- int[] hashes = new int[size];
- Multimap<Integer, Integer> 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<S>(base, hashes, linesByHash);
- }
-
- @Override
- public int length() {
- return base.length();
- }
-
- public Collection<Integer> getLinesForHash(Integer hash) {
- return linesByHash.get(hash);
- }
-
- public Integer getHash(Integer line) {
- // indices in array are shifted one line before
- return hashes[line - 1];
- }
-}
+++ /dev/null
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.plugins.core.issue.tracking;
-
-/**
- * Wrap another {@link SequenceComparator} for use with {@link HashedSequence}.
- */
-public class HashedSequenceComparator<S extends Sequence> implements SequenceComparator<HashedSequence<S>> {
-
- private final SequenceComparator<? super S> cmp;
-
- public HashedSequenceComparator(SequenceComparator<? super S> cmp) {
- this.cmp = cmp;
- }
-
- @Override
- public boolean equals(HashedSequence<S> a, int ai, HashedSequence<S> 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<S> seq, int i) {
- return seq.hashes[i];
- }
-
-}
*/
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<StringText> a;
- private final HashedSequence<StringText> b;
- private final HashedSequenceComparator<StringText> 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<StringText>(StringTextComparator.IGNORE_WHITESPACE);
- }
+ private final FileHashes a;
+ private final FileHashes b;
- public IssueTrackingBlocksRecognizer(HashedSequence<StringText> a, HashedSequence<StringText> b, HashedSequenceComparator<StringText> cmp) {
+ public IssueTrackingBlocksRecognizer(FileHashes a, FileHashes b) {
this.a = a;
this.b = b;
- this.cmp = cmp;
}
public boolean isValidLineInReference(@Nullable Integer line) {
}
/**
- * @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++;
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.plugins.core.issue.tracking;
+
+/**
+ * Compute hashes of block around each line
+ */
+public class RollingFileHashes {
+
+ final int[] rollingHashes;
+
+ public static RollingFileHashes create(FileHashes hashes, int halfBlockSize) {
+ int size = hashes.length();
+ int[] rollingHashes = new int[size];
+
+ RollingHashCalculator hashCalulator = new RollingHashCalculator(halfBlockSize * 2 + 1);
+ for (int i = 1; i <= Math.min(size, halfBlockSize + 1); i++) {
+ hashCalulator.add(hashes.getHash(i).hashCode());
+ }
+ for (int i = 1; i <= size; i++) {
+ rollingHashes[i - 1] = hashCalulator.getHash();
+ if (i - halfBlockSize > 0) {
+ hashCalulator.remove(hashes.getHash(i - halfBlockSize).hashCode());
+ }
+ if (i + 1 + halfBlockSize <= size) {
+ hashCalulator.add(hashes.getHash(i + 1 + halfBlockSize).hashCode());
+ } else {
+ hashCalulator.add(0);
+ }
+ }
+
+ return new RollingFileHashes(rollingHashes);
+ }
+
+ public int getHash(int line) {
+ return rollingHashes[line - 1];
+ }
+
+ private RollingFileHashes(int[] hashes) {
+ this.rollingHashes = hashes;
+ }
+
+ private static class RollingHashCalculator {
+
+ private static final int PRIME_BASE = 31;
+
+ private final int power;
+ private int hash;
+
+ public RollingHashCalculator(int size) {
+ int pow = 1;
+ for (int i = 0; i < size - 1; i++) {
+ pow = pow * PRIME_BASE;
+ }
+ this.power = pow;
+ }
+
+ public void add(int value) {
+ hash = hash * PRIME_BASE + value;
+ }
+
+ public void remove(int value) {
+ hash = hash - power * value;
+ }
+
+ public int getHash() {
+ return hash;
+ }
+
+ }
+
+}
+++ /dev/null
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.plugins.core.issue.tracking;
-
-/**
- * Wraps a {@link Sequence} to assign hash codes to elements.
- */
-public class RollingHashSequence<S extends Sequence> implements Sequence {
-
- final S base;
- final int[] hashes;
-
- public static <S extends Sequence> RollingHashSequence<S> wrap(S base, SequenceComparator<S> 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<S>(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;
- }
-
- }
-
-}
+++ /dev/null
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.plugins.core.issue.tracking;
-
-/**
- * Wrap another {@link SequenceComparator} for use with {@link RollingHashSequence}.
- */
-public class RollingHashSequenceComparator<S extends Sequence> implements SequenceComparator<RollingHashSequence<S>> {
-
- private final SequenceComparator<? super S> cmp;
-
- public RollingHashSequenceComparator(SequenceComparator<? super S> cmp) {
- this.cmp = cmp;
- }
-
- @Override
- public boolean equals(RollingHashSequence<S> a, int ai, RollingHashSequence<S> 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<S> seq, int i) {
- return seq.hashes[i];
- }
-
-}
+++ /dev/null
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.plugins.core.issue.tracking;
-
-/**
- * Arbitrary sequence of elements.
- */
-public interface Sequence {
-
- /**
- * @return total number of items in the sequence
- */
- int length();
-
-}
+++ /dev/null
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.plugins.core.issue.tracking;
-
-/**
- * Equivalence function for a {@link Sequence}.
- */
-public interface SequenceComparator<S extends Sequence> {
-
- /**
- * 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);
-
-}
+++ /dev/null
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.plugins.core.issue.tracking;
-
-import com.google.common.collect.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<String> checksums, @Nullable Integer line) {
- if (line == null || line < 1 || line > checksums.size()) {
- return null;
- }
- return checksums.get(line - 1);
- }
-
- public static List<String> lineChecksumsOfFile(String file) {
- List<String> 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);
- }
-
-}
+++ /dev/null
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.plugins.core.issue.tracking;
-
-import com.google.common.collect.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<Integer> 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<Integer> lineMap(String buf, int ptr, int end) {
- List<Integer> 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;
- }
-
-}
+++ /dev/null
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.plugins.core.issue.tracking;
-
-/**
- * Equivalence function for {@link StringText}.
- */
-public abstract class StringTextComparator implements SequenceComparator<StringText> {
-
- /**
- * 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;
- }
-
-}
*/
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;
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;
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 {
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() {
initialOpenIssues,
tracking,
lastSnapshots,
- index,
handlers,
workflow,
updater,
- mock(Project.class),
+ new Project("foo"),
perspectives,
profile,
- ruleFinder);
+ ruleFinder,
+ inputPathCache);
}
@Test
@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"));
+ " 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);
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");
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);
@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");
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);
@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;
+ " 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);
@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;
+ " 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);
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");
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);
@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");
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);
@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");
+ " 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);
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;
+ }
+
}
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;
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();
@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");
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);
}
*/
@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);
}
*/
@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();
}
*/
@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);
}
*/
@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);
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();
*/
@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);
@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");
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;
+ }
}
*/
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);
}
+
}
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;
@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);
}
}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.plugins.core.issue.tracking;
+
+import org.junit.Test;
+
+import static org.apache.commons.codec.digest.DigestUtils.md5Hex;
+import static org.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();
+ }
+
+}
+++ /dev/null
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.plugins.core.issue.tracking;
-
-import org.junit.Test;
-import 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<StringText> seq2 = RollingHashSequence.wrap(seq, cmp, 1);
- RollingHashSequenceComparator<StringText> cmp2 = new RollingHashSequenceComparator<StringText>(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<StringText> a = RollingHashSequence.wrap(new StringText("line0 \n line1 \n line2"), baseCmp, 1);
- RollingHashSequence<StringText> b = RollingHashSequence.wrap(new StringText("line0 \n line1 \n line2 \n line3"), baseCmp, 1);
- RollingHashSequenceComparator<StringText> cmp = new RollingHashSequenceComparator<StringText>(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();
- }
-
-}
+++ /dev/null
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.plugins.core.issue.tracking;
-
-import org.junit.Test;
-import 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<String> 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<String> encoding = SourceChecksum.lineChecksumsOfFile("Привет Мир");
- assertThat(encoding).hasSize(1);
- assertThat(encoding.get(0)).isEqualTo("5ba3a45e1299ede07f56e5531351be52");
- }
-
- @Test
- public void shouldSplitLinesAndIgnoreSpaces() {
- List<String> crlf = SourceChecksum.lineChecksumsOfFile("Hello\r\nWorld");
- List<String> lf = SourceChecksum.lineChecksumsOfFile("Hello\nWorld");
- List<String> 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() {"));
- }
-}
+++ /dev/null
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.plugins.core.issue.tracking;
-
-import org.junit.Test;
-import 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));
- }
-
-}
+++ /dev/null
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.plugins.core.issue.tracking;
-
-import org.junit.Test;
-import 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);
- }
-
-}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.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();
+ }
+ }
+}
+++ /dev/null
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.batch.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());
- }
-}
ResourceKeyMigration.class,
DefaultFileLinesContextFactory.class,
ProjectLock.class,
- LastSnapshots.class,
+ LastLineHashes.class,
Caches.class,
SnapshotCache.class,
ResourceCache.class,
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.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");
+ }
+
+}
+++ /dev/null
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.batch.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;
- }
-}
+++ /dev/null
-<dataset>
- <projects id="100" kee="myproject:org/foo/Bar.c" enabled="[true]" scope="FIL" qualifier="FIL" language="c"/>
- <snapshots id="1000" project_id="100" status="P" islast="[false]" purge_status="[null]"/>
- <snapshots id="1100" project_id="100" status="P" islast="[true]" purge_status="[null]"/>
- <snapshot_sources ID="10000" SNAPSHOT_ID="1100" DATA="this is bar"/>
-</dataset>
\ No newline at end of file
+++ /dev/null
-<dataset>
-
-</dataset>
\ No newline at end of file