diff options
14 files changed, 309 insertions, 172 deletions
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java index c9c65815691..2e42100b525 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/CorePlugin.java @@ -78,7 +78,6 @@ import org.sonar.plugins.core.timemachine.NewCoverageFileAnalyzer; import org.sonar.plugins.core.timemachine.NewItCoverageFileAnalyzer; import org.sonar.plugins.core.timemachine.NewOverallCoverageFileAnalyzer; import org.sonar.plugins.core.timemachine.NewViolationsDecorator; -import org.sonar.plugins.core.timemachine.ReferenceAnalysis; import org.sonar.plugins.core.timemachine.TendencyDecorator; import org.sonar.plugins.core.timemachine.TimeMachineConfigurationPersister; import org.sonar.plugins.core.timemachine.VariationDecorator; @@ -488,7 +487,6 @@ public final class CorePlugin extends SonarPlugin { FilesDecorator.class, ReviewNotifications.class, ReviewWorkflowDecorator.class, - ReferenceAnalysis.class, ManualMeasureDecorator.class, ManualViolationInjector.class, ViolationSeverityUpdater.class, diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ViolationTrackingDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ViolationTrackingDecorator.java index e2e52396996..9cd1e10a075 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ViolationTrackingDecorator.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ViolationTrackingDecorator.java @@ -39,6 +39,7 @@ import org.sonar.api.resources.Project; import org.sonar.api.resources.Resource; import org.sonar.api.rules.Violation; import org.sonar.api.violations.ViolationQuery; +import org.sonar.batch.scan.LastSnapshots; import org.sonar.plugins.core.timemachine.tracking.HashedSequence; import org.sonar.plugins.core.timemachine.tracking.HashedSequenceComparator; import org.sonar.plugins.core.timemachine.tracking.RollingHashSequence; @@ -46,6 +47,8 @@ import org.sonar.plugins.core.timemachine.tracking.RollingHashSequenceComparator import org.sonar.plugins.core.timemachine.tracking.StringText; import org.sonar.plugins.core.timemachine.tracking.StringTextComparator; +import javax.annotation.Nullable; + import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -56,7 +59,7 @@ import java.util.Set; @DependsUpon({DecoratorBarriers.END_OF_VIOLATIONS_GENERATION, DecoratorBarriers.START_VIOLATION_TRACKING}) @DependedUpon(DecoratorBarriers.END_OF_VIOLATION_TRACKING) public class ViolationTrackingDecorator implements Decorator { - private ReferenceAnalysis referenceAnalysis; + private LastSnapshots lastSnapshots; private Map<Violation, RuleFailureModel> referenceViolationsMap = Maps.newIdentityHashMap(); private SonarIndex index; private Project project; @@ -64,10 +67,10 @@ public class ViolationTrackingDecorator implements Decorator { /** * Live collection of unmapped past violations. */ - private Set<RuleFailureModel> unmappedPastViolations = Sets.newHashSet(); + private Set<RuleFailureModel> unmappedLastViolations = Sets.newHashSet(); - public ViolationTrackingDecorator(Project project, ReferenceAnalysis referenceAnalysis, SonarIndex index) { - this.referenceAnalysis = referenceAnalysis; + public ViolationTrackingDecorator(Project project, LastSnapshots lastSnapshots, SonarIndex index) { + this.lastSnapshots = lastSnapshots; this.index = index; this.project = project; } @@ -89,8 +92,8 @@ public class ViolationTrackingDecorator implements Decorator { // Load new violations List<Violation> newViolations = prepareNewViolations(context, source); - // Load reference violations - List<RuleFailureModel> referenceViolations = referenceAnalysis.getViolations(resource); + // Load the violations of the last available analysis + List<RuleFailureModel> referenceViolations = lastSnapshots.getViolations(resource); // Map new violations with old ones mapViolations(newViolations, referenceViolations, source, resource); @@ -111,39 +114,45 @@ public class ViolationTrackingDecorator implements Decorator { } @VisibleForTesting - Map<Violation, RuleFailureModel> mapViolations(List<Violation> newViolations, List<RuleFailureModel> pastViolations) { - return mapViolations(newViolations, pastViolations, null, null); + Map<Violation, RuleFailureModel> mapViolations(List<Violation> newViolations, @Nullable List<RuleFailureModel> lastViolations) { + return mapViolations(newViolations, lastViolations, null, null); } @VisibleForTesting - Map<Violation, RuleFailureModel> mapViolations(List<Violation> newViolations, List<RuleFailureModel> pastViolations, String source, Resource resource) { - unmappedPastViolations.addAll(pastViolations); - - Multimap<Integer, RuleFailureModel> pastViolationsByRule = LinkedHashMultimap.create(); - for (RuleFailureModel pastViolation : pastViolations) { - pastViolationsByRule.put(pastViolation.getRuleId(), pastViolation); - } - - // Match the permanent id of the violation. This id is for example set explicitly when injecting manual violations - for (Violation newViolation : newViolations) { - mapViolation(newViolation, - findPastViolationWithSamePermanentId(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())), - pastViolationsByRule, referenceViolationsMap); - } + Map<Violation, RuleFailureModel> mapViolations(List<Violation> newViolations, @Nullable List<RuleFailureModel> lastViolations, + @Nullable String source, @Nullable Resource resource) { + boolean hasLastScan = false; + Multimap<Integer, RuleFailureModel> lastViolationsByRule = LinkedHashMultimap.create(); + + if (lastViolations != null) { + hasLastScan = true; + unmappedLastViolations.addAll(lastViolations); + + for (RuleFailureModel lastViolation : lastViolations) { + lastViolationsByRule.put(lastViolation.getRuleId(), lastViolation); + } - // Try first to match violations on same rule with same line and with same checksum (but not necessarily with same message) - for (Violation newViolation : newViolations) { - if (isNotAlreadyMapped(newViolation)) { + // Match the permanent id of the violation. This id is for example set explicitly when injecting manual violations + for (Violation newViolation : newViolations) { mapViolation(newViolation, - findPastViolationWithSameLineAndChecksum(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())), - pastViolationsByRule, referenceViolationsMap); + findLastViolationWithSamePermanentId(newViolation, lastViolationsByRule.get(newViolation.getRule().getId())), + lastViolationsByRule, referenceViolationsMap); + } + + // Try first to match violations on same rule with same line and with same checksum (but not necessarily with same message) + for (Violation newViolation : newViolations) { + if (isNotAlreadyMapped(newViolation)) { + mapViolation(newViolation, + findLastViolationWithSameLineAndChecksum(newViolation, lastViolationsByRule.get(newViolation.getRule().getId())), + lastViolationsByRule, referenceViolationsMap); + } } } // If each new violation matches an old one we can stop the matching mechanism if (referenceViolationsMap.size() != newViolations.size()) { - if (source != null && resource != null) { - String referenceSource = referenceAnalysis.getSource(resource); + if (source != null && resource != null && hasLastScan) { + String referenceSource = lastSnapshots.getSource(resource); if (referenceSource != null) { HashedSequence<StringText> hashedReference = HashedSequence.wrap(new StringText(referenceSource), StringTextComparator.IGNORE_WHITESPACE); HashedSequence<StringText> hashedSource = HashedSequence.wrap(new StringText(source), StringTextComparator.IGNORE_WHITESPACE); @@ -152,7 +161,7 @@ public class ViolationTrackingDecorator implements Decorator { ViolationTrackingBlocksRecognizer rec = new ViolationTrackingBlocksRecognizer(hashedReference, hashedSource, hashedComparator); Multimap<Integer, Violation> newViolationsByLines = newViolationsByLines(newViolations, rec); - Multimap<Integer, RuleFailureModel> pastViolationsByLines = pastViolationsByLines(unmappedPastViolations, rec); + Multimap<Integer, RuleFailureModel> lastViolationsByLines = lastViolationsByLines(unmappedLastViolations, rec); RollingHashSequence<HashedSequence<StringText>> a = RollingHashSequence.wrap(hashedReference, hashedComparator, 5); RollingHashSequence<HashedSequence<StringText>> b = RollingHashSequence.wrap(hashedSource, hashedComparator, 5); @@ -160,7 +169,7 @@ public class ViolationTrackingDecorator implements Decorator { Map<Integer, HashOccurrence> map = Maps.newHashMap(); - for (Integer line : pastViolationsByLines.keySet()) { + for (Integer line : lastViolationsByLines.keySet()) { int hash = cmp.hash(a, line - 1); HashOccurrence hashOccurrence = map.get(hash); if (hashOccurrence == null) { @@ -186,16 +195,16 @@ public class ViolationTrackingDecorator implements Decorator { for (HashOccurrence hashOccurrence : map.values()) { if (hashOccurrence.countA == 1 && hashOccurrence.countB == 1) { // Guaranteed that lineA has been moved to lineB, so we can map all violations on lineA to all violations on lineB - map(newViolationsByLines.get(hashOccurrence.lineB), pastViolationsByLines.get(hashOccurrence.lineA), pastViolationsByRule); - pastViolationsByLines.removeAll(hashOccurrence.lineA); + map(newViolationsByLines.get(hashOccurrence.lineB), lastViolationsByLines.get(hashOccurrence.lineA), lastViolationsByRule); + lastViolationsByLines.removeAll(hashOccurrence.lineA); newViolationsByLines.removeAll(hashOccurrence.lineB); } } // Check if remaining number of lines exceeds threshold - if (pastViolationsByLines.keySet().size() * newViolationsByLines.keySet().size() < 250000) { + if (lastViolationsByLines.keySet().size() * newViolationsByLines.keySet().size() < 250000) { List<LinePair> possibleLinePairs = Lists.newArrayList(); - for (Integer oldLine : pastViolationsByLines.keySet()) { + for (Integer oldLine : lastViolationsByLines.keySet()) { for (Integer newLine : newViolationsByLines.keySet()) { int weight = rec.computeLengthOfMaximalBlock(oldLine - 1, newLine - 1); possibleLinePairs.add(new LinePair(oldLine, newLine, weight)); @@ -204,7 +213,7 @@ public class ViolationTrackingDecorator implements Decorator { Collections.sort(possibleLinePairs, LINE_PAIR_COMPARATOR); for (LinePair linePair : possibleLinePairs) { // High probability that lineA has been moved to lineB, so we can map all violations on lineA to all violations on lineB - map(newViolationsByLines.get(linePair.lineB), pastViolationsByLines.get(linePair.lineA), pastViolationsByRule); + map(newViolationsByLines.get(linePair.lineB), lastViolationsByLines.get(linePair.lineA), lastViolationsByRule); } } } @@ -214,8 +223,8 @@ public class ViolationTrackingDecorator implements Decorator { for (Violation newViolation : newViolations) { if (isNotAlreadyMapped(newViolation)) { mapViolation(newViolation, - findPastViolationWithSameChecksumAndMessage(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())), - pastViolationsByRule, referenceViolationsMap); + findLastViolationWithSameChecksumAndMessage(newViolation, lastViolationsByRule.get(newViolation.getRule().getId())), + lastViolationsByRule, referenceViolationsMap); } } @@ -223,8 +232,8 @@ public class ViolationTrackingDecorator implements Decorator { for (Violation newViolation : newViolations) { if (isNotAlreadyMapped(newViolation)) { mapViolation(newViolation, - findPastViolationWithSameLineAndMessage(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())), - pastViolationsByRule, referenceViolationsMap); + findLastViolationWithSameLineAndMessage(newViolation, lastViolationsByRule.get(newViolation.getRule().getId())), + lastViolationsByRule, referenceViolationsMap); } } @@ -233,23 +242,22 @@ public class ViolationTrackingDecorator implements Decorator { for (Violation newViolation : newViolations) { if (isNotAlreadyMapped(newViolation)) { mapViolation(newViolation, - findPastViolationWithSameChecksum(newViolation, pastViolationsByRule.get(newViolation.getRule().getId())), - pastViolationsByRule, referenceViolationsMap); + findLastViolationWithSameChecksum(newViolation, lastViolationsByRule.get(newViolation.getRule().getId())), + lastViolationsByRule, referenceViolationsMap); } } } - unmappedPastViolations.clear(); - + unmappedLastViolations.clear(); return referenceViolationsMap; } - private void map(Collection<Violation> newViolations, Collection<RuleFailureModel> pastViolations, Multimap<Integer, RuleFailureModel> pastViolationsByRule) { + private void map(Collection<Violation> newViolations, Collection<RuleFailureModel> lastViolations, Multimap<Integer, RuleFailureModel> lastViolationsByRule) { for (Violation newViolation : newViolations) { if (isNotAlreadyMapped(newViolation)) { - for (RuleFailureModel pastViolation : pastViolations) { + for (RuleFailureModel pastViolation : lastViolations) { if (isNotAlreadyMapped(pastViolation) && Objects.equal(newViolation.getRule().getId(), pastViolation.getRuleId())) { - mapViolation(newViolation, pastViolation, pastViolationsByRule, referenceViolationsMap); + mapViolation(newViolation, pastViolation, lastViolationsByRule, referenceViolationsMap); break; } } @@ -269,14 +277,14 @@ public class ViolationTrackingDecorator implements Decorator { return newViolationsByLines; } - private Multimap<Integer, RuleFailureModel> pastViolationsByLines(Collection<RuleFailureModel> pastViolations, ViolationTrackingBlocksRecognizer rec) { - Multimap<Integer, RuleFailureModel> pastViolationsByLines = LinkedHashMultimap.create(); - for (RuleFailureModel pastViolation : pastViolations) { + private Multimap<Integer, RuleFailureModel> lastViolationsByLines(Collection<RuleFailureModel> lastViolations, ViolationTrackingBlocksRecognizer rec) { + Multimap<Integer, RuleFailureModel> lastViolationsByLines = LinkedHashMultimap.create(); + for (RuleFailureModel pastViolation : lastViolations) { if (rec.isValidLineInSource(pastViolation.getLine())) { - pastViolationsByLines.put(pastViolation.getLine(), pastViolation); + lastViolationsByLines.put(pastViolation.getLine(), pastViolation); } } - return pastViolationsByLines; + return lastViolationsByLines; } private static final Comparator<LinePair> LINE_PAIR_COMPARATOR = new Comparator<LinePair>() { @@ -305,15 +313,15 @@ public class ViolationTrackingDecorator implements Decorator { } private boolean isNotAlreadyMapped(RuleFailureModel pastViolation) { - return unmappedPastViolations.contains(pastViolation); + return unmappedLastViolations.contains(pastViolation); } private boolean isNotAlreadyMapped(Violation newViolation) { return !referenceViolationsMap.containsKey(newViolation); } - private RuleFailureModel findPastViolationWithSameChecksum(Violation newViolation, Collection<RuleFailureModel> pastViolations) { - for (RuleFailureModel pastViolation : pastViolations) { + private RuleFailureModel findLastViolationWithSameChecksum(Violation newViolation, Collection<RuleFailureModel> lastViolations) { + for (RuleFailureModel pastViolation : lastViolations) { if (isSameChecksum(newViolation, pastViolation)) { return pastViolation; } @@ -321,8 +329,8 @@ public class ViolationTrackingDecorator implements Decorator { return null; } - private RuleFailureModel findPastViolationWithSameLineAndMessage(Violation newViolation, Collection<RuleFailureModel> pastViolations) { - for (RuleFailureModel pastViolation : pastViolations) { + private RuleFailureModel findLastViolationWithSameLineAndMessage(Violation newViolation, Collection<RuleFailureModel> lastViolations) { + for (RuleFailureModel pastViolation : lastViolations) { if (isSameLine(newViolation, pastViolation) && isSameMessage(newViolation, pastViolation)) { return pastViolation; } @@ -330,8 +338,8 @@ public class ViolationTrackingDecorator implements Decorator { return null; } - private RuleFailureModel findPastViolationWithSameChecksumAndMessage(Violation newViolation, Collection<RuleFailureModel> pastViolations) { - for (RuleFailureModel pastViolation : pastViolations) { + private RuleFailureModel findLastViolationWithSameChecksumAndMessage(Violation newViolation, Collection<RuleFailureModel> lastViolations) { + for (RuleFailureModel pastViolation : lastViolations) { if (isSameChecksum(newViolation, pastViolation) && isSameMessage(newViolation, pastViolation)) { return pastViolation; } @@ -339,8 +347,8 @@ public class ViolationTrackingDecorator implements Decorator { return null; } - private RuleFailureModel findPastViolationWithSameLineAndChecksum(Violation newViolation, Collection<RuleFailureModel> pastViolations) { - for (RuleFailureModel pastViolation : pastViolations) { + private RuleFailureModel findLastViolationWithSameLineAndChecksum(Violation newViolation, Collection<RuleFailureModel> lastViolations) { + for (RuleFailureModel pastViolation : lastViolations) { if (isSameLine(newViolation, pastViolation) && isSameChecksum(newViolation, pastViolation)) { return pastViolation; } @@ -348,8 +356,8 @@ public class ViolationTrackingDecorator implements Decorator { return null; } - private RuleFailureModel findPastViolationWithSamePermanentId(Violation newViolation, Collection<RuleFailureModel> pastViolations) { - for (RuleFailureModel pastViolation : pastViolations) { + private RuleFailureModel findLastViolationWithSamePermanentId(Violation newViolation, Collection<RuleFailureModel> lastViolations) { + for (RuleFailureModel pastViolation : lastViolations) { if (isSamePermanentId(newViolation, pastViolation)) { return pastViolation; } @@ -374,16 +382,16 @@ public class ViolationTrackingDecorator implements Decorator { } private void mapViolation(Violation newViolation, RuleFailureModel pastViolation, - Multimap<Integer, RuleFailureModel> pastViolationsByRule, Map<Violation, RuleFailureModel> violationMap) { + Multimap<Integer, RuleFailureModel> lastViolationsByRule, Map<Violation, RuleFailureModel> violationMap) { if (pastViolation != null) { newViolation.setCreatedAt(pastViolation.getCreatedAt()); newViolation.setPermanentId(pastViolation.getPermanentId()); newViolation.setSwitchedOff(pastViolation.isSwitchedOff()); newViolation.setPersonId(pastViolation.getPersonId()); newViolation.setNew(false); - pastViolationsByRule.remove(newViolation.getRule().getId(), pastViolation); + lastViolationsByRule.remove(newViolation.getRule().getId(), pastViolation); violationMap.put(newViolation, pastViolation); - unmappedPastViolations.remove(pastViolation); + unmappedLastViolations.remove(pastViolation); } else { newViolation.setNew(true); newViolation.setCreatedAt(project.getAnalysisDate()); diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/ReferenceAnalysisTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/ReferenceAnalysisTest.java deleted file mode 100644 index 14b6922ca4b..00000000000 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/ReferenceAnalysisTest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Sonar, open source software quality management tool. - * Copyright (C) 2008-2012 SonarSource - * mailto:contact AT sonarsource DOT com - * - * Sonar 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. - * - * Sonar 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 Sonar; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 - */ -package org.sonar.plugins.core.timemachine; - -import org.junit.Test; -import org.sonar.api.resources.JavaFile; -import org.sonar.api.resources.Resource; -import org.sonar.jpa.test.AbstractDbUnitTestCase; - -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; - -public class ReferenceAnalysisTest extends AbstractDbUnitTestCase { - - @Test - public void test() { - setupData("shared"); - - ReferenceAnalysis referenceAnalysis = new ReferenceAnalysis(getSession()); - - Resource resource = new JavaFile(""); - - resource.setEffectiveKey("project:org.foo.Bar"); - assertThat(referenceAnalysis.getViolations(resource).size(), is(1)); - assertThat(referenceAnalysis.getSource(resource), is("this is the file content")); - - resource.setEffectiveKey("project:no-such-resource"); - assertThat(referenceAnalysis.getViolations(resource).size(), is(0)); - assertThat(referenceAnalysis.getSource(resource), is("")); - } - -} diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/ViolationTrackingTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/ViolationTrackingTest.java index a84f402595b..617287da75e 100644 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/ViolationTrackingTest.java +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/timemachine/ViolationTrackingTest.java @@ -28,6 +28,7 @@ import org.sonar.api.resources.Project; import org.sonar.api.rules.Rule; import org.sonar.api.rules.Violation; import org.sonar.api.utils.DateUtils; +import org.sonar.batch.scan.LastSnapshots; import java.io.IOException; import java.util.Arrays; @@ -47,19 +48,19 @@ public class ViolationTrackingTest { private ViolationTrackingDecorator decorator; private Project project; - private ReferenceAnalysis referenceAnalysis; + private LastSnapshots lastSnapshots; @Before public void setUp() { project = mock(Project.class); when(project.getAnalysisDate()).thenReturn(analysisDate); - referenceAnalysis = mock(ReferenceAnalysis.class); - decorator = new ViolationTrackingDecorator(project, referenceAnalysis, null); + lastSnapshots = mock(LastSnapshots.class); + decorator = new ViolationTrackingDecorator(project, lastSnapshots, null); } @Test public void pastViolationNotAssiciatedWithLineShouldNotCauseNPE() throws Exception { - when(referenceAnalysis.getSource(project)).thenReturn(load("example2-v1")); + when(lastSnapshots.getSource(project)).thenReturn(load("example2-v1")); String source = load("example2-v2"); RuleFailureModel referenceViolation1 = newReferenceViolation("2 branches need to be covered", null, 50); @@ -78,7 +79,7 @@ public class ViolationTrackingTest { @Test public void newViolationNotAssiciatedWithLineShouldNotCauseNPE() throws Exception { - when(referenceAnalysis.getSource(project)).thenReturn(load("example2-v1")); + when(lastSnapshots.getSource(project)).thenReturn(load("example2-v1")); String source = load("example2-v2"); RuleFailureModel referenceViolation1 = newReferenceViolation("Indentation", 7, 50); @@ -100,7 +101,7 @@ public class ViolationTrackingTest { */ @Test public void violationNotAssociatedWithLine() throws Exception { - when(referenceAnalysis.getSource(project)).thenReturn(load("example2-v1")); + when(lastSnapshots.getSource(project)).thenReturn(load("example2-v1")); String source = load("example2-v2"); RuleFailureModel referenceViolation1 = newReferenceViolation("2 branches need to be covered", null, 50); @@ -121,7 +122,7 @@ public class ViolationTrackingTest { */ @Test public void example1() throws Exception { - when(referenceAnalysis.getSource(project)).thenReturn(load("example1-v1")); + when(lastSnapshots.getSource(project)).thenReturn(load("example1-v1")); String source = load("example1-v2"); RuleFailureModel referenceViolation1 = newReferenceViolation("Indentation", 7, 50); @@ -150,7 +151,7 @@ public class ViolationTrackingTest { */ @Test public void example2() throws Exception { - when(referenceAnalysis.getSource(project)).thenReturn(load("example2-v1")); + when(lastSnapshots.getSource(project)).thenReturn(load("example2-v1")); String source = load("example2-v2"); RuleFailureModel referenceViolation1 = newReferenceViolation("SystemPrintln", 5, 50); diff --git a/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/timemachine/ReferenceAnalysisTest/shared.xml b/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/timemachine/ReferenceAnalysisTest/shared.xml deleted file mode 100644 index cca1757cb78..00000000000 --- a/plugins/sonar-core-plugin/src/test/resources/org/sonar/plugins/core/timemachine/ReferenceAnalysisTest/shared.xml +++ /dev/null @@ -1,16 +0,0 @@ -<dataset> - - <projects id="200" scope="FIL" qualifier="CLA" kee="project:org.foo.Bar" root_id="[null]" - name="Bar" long_name="org.foo.Bar" description="[null]" - enabled="true" language="java" copy_resource_id="[null]" person_id="[null]" /> - - <snapshots purge_status="[null]" period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" id="1000" project_id="200" parent_snapshot_id="[null]" root_project_id="100" root_snapshot_id="[null]" - scope="FIL" qualifier="CLA" created_at="2008-11-01 13:58:00.00" build_date="2008-11-01 13:58:00.00" version="[null]" path="" - status="P" islast="true" depth="3" /> - - <rule_failures switched_off="false" permanent_id="1" ID="1" SNAPSHOT_ID="1000" RULE_ID="30" FAILURE_LEVEL="3" MESSAGE="old message" LINE="10" COST="[null]" - created_at="2008-11-01 13:58:00.00" checksum="[null]" person_id="[null]"/> - - <snapshot_sources ID="1" SNAPSHOT_ID="1000" DATA="this is the file content"/> - -</dataset> diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java index 181f7703ab3..fda39f6c801 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java @@ -33,6 +33,7 @@ import org.sonar.api.utils.SonarException; import org.sonar.batch.bootstrapper.EnvironmentInformation; import java.io.File; +import java.io.IOException; import java.io.InputStream; import java.net.URI; @@ -59,19 +60,23 @@ public class ServerClient implements BatchComponent { InputSupplier<InputStream> inputSupplier = doRequest(pathStartingWithSlash); Files.copy(inputSupplier, toFile); } catch (HttpDownloader.HttpException he) { - throw handleHttpException(he); - } catch (Exception e) { + throw handleHttpException(pathStartingWithSlash, he); + } catch (IOException e) { throw new SonarException(String.format("Unable to download '%s' to: %s", pathStartingWithSlash, toFile), e); } } public String request(String pathStartingWithSlash) { + return request(pathStartingWithSlash, true); + } + + public String request(String pathStartingWithSlash, boolean wrapHttpException) { InputSupplier<InputStream> inputSupplier = doRequest(pathStartingWithSlash); try { return IOUtils.toString(inputSupplier.getInput(), "UTF-8"); - } catch (HttpDownloader.HttpException he) { - throw handleHttpException(he); - } catch (Exception e) { + } catch (HttpDownloader.HttpException e) { + throw (wrapHttpException ? handleHttpException(pathStartingWithSlash, e) : e); + } catch (IOException e) { throw new SonarException(String.format("Unable to request: %s", pathStartingWithSlash), e); } } @@ -96,14 +101,14 @@ public class ServerClient implements BatchComponent { } } - private SonarException handleHttpException(HttpDownloader.HttpException he) { + private RuntimeException handleHttpException(String uri, HttpDownloader.HttpException he) { if (he.getResponseCode() == 401) { - throw new SonarException(String.format(getMessageWhenNotAuthorized(), CoreProperties.LOGIN, CoreProperties.PASSWORD)); + return new SonarException(String.format(getMessageWhenNotAuthorized(), CoreProperties.LOGIN, CoreProperties.PASSWORD)); } - throw new SonarException(String.format("Fail to execute request [code=%s, url=%s]", he.getResponseCode(), he.getUri()), he); + return new SonarException(String.format("Fail to execute request [code=%s, url=%s]", he.getResponseCode(), he.getUri()), he); } - private String getMessageWhenNotAuthorized(){ + private String getMessageWhenNotAuthorized() { String login = settings.getProperty(CoreProperties.LOGIN); String password = settings.getProperty(CoreProperties.PASSWORD); if (StringUtils.isEmpty(login) && StringUtils.isEmpty(password)) { diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ReferenceAnalysis.java b/sonar-batch/src/main/java/org/sonar/batch/scan/LastSnapshots.java index c5a44fd82b0..490ce77ffea 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/ReferenceAnalysis.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/LastSnapshots.java @@ -17,38 +17,75 @@ * License along with Sonar; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 */ -package org.sonar.plugins.core.timemachine; +package org.sonar.batch.scan; -import org.sonar.api.BatchExtension; +import org.sonar.api.BatchComponent; +import org.sonar.api.CoreProperties; +import org.sonar.api.config.Settings; import org.sonar.api.database.DatabaseSession; import org.sonar.api.database.model.ResourceModel; import org.sonar.api.database.model.RuleFailureModel; import org.sonar.api.database.model.Snapshot; import org.sonar.api.database.model.SnapshotSource; import org.sonar.api.resources.Resource; +import org.sonar.api.resources.ResourceUtils; +import org.sonar.api.utils.HttpDownloader; +import org.sonar.batch.bootstrap.ServerClient; +import javax.annotation.CheckForNull; import javax.persistence.Query; import java.util.Collections; import java.util.List; -public class ReferenceAnalysis implements BatchExtension { +public class LastSnapshots implements BatchComponent { - private DatabaseSession session; + private final Settings settings; + private final DatabaseSession session; + private final ServerClient server; - public ReferenceAnalysis(DatabaseSession session) { + public LastSnapshots(Settings settings, DatabaseSession session, ServerClient server) { + this.settings = settings; this.session = session; + this.server = server; } + /** + * Return null if this is the first scan (no last scan). + */ + @CheckForNull public List<RuleFailureModel> getViolations(Resource resource) { Snapshot snapshot = getSnapshot(resource); if (snapshot != null) { return session.getResults(RuleFailureModel.class, "snapshotId", snapshot.getId()); } - return Collections.emptyList(); + return null; } public String getSource(Resource resource) { + String source = ""; + if (ResourceUtils.isFile(resource)) { + if (settings.getBoolean(CoreProperties.DRY_RUN)) { + source = loadSourceFromWs(resource); + } else { + source = loadSourceFromDb(resource); + } + } + return source; + } + + private String loadSourceFromWs(Resource resource) { + try { + return server.request("/api/sources?resource=" + resource.getEffectiveKey() + "&format=txt", false); + } catch (HttpDownloader.HttpException he) { + if (he.getResponseCode() == 404) { + return ""; + } + throw he; + } + } + + private String loadSourceFromDb(Resource resource) { Snapshot snapshot = getSnapshot(resource); if (snapshot != null) { SnapshotSource source = session.getSingleResult(SnapshotSource.class, "snapshotId", snapshot.getId()); diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ScanContainer.java index dcb25cbdeaa..e898e87d53f 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ScanContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ScanContainer.java @@ -94,6 +94,7 @@ public class ScanContainer extends Container { } container.addSingleton(Languages.class); container.addSingleton(RulesDao.class); + container.addSingleton(LastSnapshots.class); // file system container.addSingleton(PathResolver.class); 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 new file mode 100644 index 00000000000..62e756b8c7d --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/scan/LastSnapshotsTest.java @@ -0,0 +1,126 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2012 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.batch.scan; + +import org.junit.Test; +import org.sonar.api.CoreProperties; +import org.sonar.api.config.Settings; +import org.sonar.api.database.model.RuleFailureModel; +import org.sonar.api.resources.File; +import org.sonar.api.utils.HttpDownloader; +import org.sonar.batch.bootstrap.ServerClient; +import org.sonar.jpa.test.AbstractDbUnitTestCase; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; + +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 extends AbstractDbUnitTestCase { + + @Test + public void should_return_null_violations_if_no_last_snapshot() { + setupData("no_last_snapshot"); + ServerClient server = mock(ServerClient.class); + + LastSnapshots lastSnapshots = new LastSnapshots(new Settings(), getSession(), server); + + assertThat(lastSnapshots.getViolations(new File("org/foo", "Bar.c"))).isNull(); + verifyZeroInteractions(server); + } + + @Test + public void should_get_violations_of_last_snapshot() { + setupData("last_snapshot"); + ServerClient server = mock(ServerClient.class); + + LastSnapshots lastSnapshots = new LastSnapshots(new Settings(), getSession(), server); + + List<RuleFailureModel> violations = lastSnapshots.getViolations(newFile()); + assertThat(violations).hasSize(1); + assertThat(violations.get(0).getChecksum()).isEqualTo("ABCDE"); + verifyZeroInteractions(server); + } + + @Test + public void should_get_source_of_last_snapshot() { + setupData("last_snapshot"); + ServerClient server = mock(ServerClient.class); + + LastSnapshots lastSnapshots = new LastSnapshots(new Settings(), getSession(), server); + + assertThat(lastSnapshots.getSource(newFile())).isEqualTo("this is bar"); + verifyZeroInteractions(server); + } + + @Test + public void should_return_empty_source_if_no_last_snapshot() { + setupData("no_last_snapshot"); + ServerClient server = mock(ServerClient.class); + + LastSnapshots lastSnapshots = new LastSnapshots(new Settings(), getSession(), server); + + assertThat(lastSnapshots.getSource(newFile())).isEqualTo(""); + verifyZeroInteractions(server); + } + + @Test + public void should_download_source_from_ws_if_dry_run() { + setupData("last_snapshot"); + ServerClient server = mock(ServerClient.class); + when(server.request(anyString(), eq(false))).thenReturn("downloaded source of Bar.c"); + + Settings settings = new Settings(); + settings.setProperty(CoreProperties.DRY_RUN, true); + LastSnapshots lastSnapshots = new LastSnapshots(settings, getSession(), server); + + String source = lastSnapshots.getSource(newFile()); + assertThat(source).isEqualTo("downloaded source of Bar.c"); + verify(server).request("/api/sources?resource=myproject:org/foo/Bar.c&format=txt", false); + } + + @Test + public void should_return_empty_source_if_dry_run_and_no_last_snapshot() throws URISyntaxException { + setupData("last_snapshot"); + ServerClient server = mock(ServerClient.class); + when(server.request(anyString(), eq(false))).thenThrow(new HttpDownloader.HttpException(new URI(""), 404)); + + Settings settings = new Settings(); + settings.setProperty(CoreProperties.DRY_RUN, true); + LastSnapshots lastSnapshots = new LastSnapshots(settings, getSession(), server); + + String source = lastSnapshots.getSource(newFile()); + assertThat(source).isEqualTo(""); + verify(server).request("/api/sources?resource=myproject:org/foo/Bar.c&format=txt", false); + } + + private File newFile() { + File file = new File("org/foo", "Bar.c"); + file.setEffectiveKey("myproject:org/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 new file mode 100644 index 00000000000..6cc2ea5b8a5 --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/scan/LastSnapshotsTest/last_snapshot.xml @@ -0,0 +1,11 @@ +<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"/> + <rule_failures ID="1000000" SNAPSHOT_ID="1100" RULE_ID="1" + switched_off="[null]" permanent_id="[null]" FAILURE_LEVEL="2" + MESSAGE="msg1" LINE="[null]" COST="[null]" + created_at="2008-12-02 13:58:00.00" + checksum="ABCDE" person_id="[null]"/> +</dataset>
\ 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 new file mode 100644 index 00000000000..84d67a04385 --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/scan/LastSnapshotsTest/no_last_snapshot.xml @@ -0,0 +1,3 @@ +<dataset> + +</dataset>
\ No newline at end of file diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/DryRunDatabaseFactory.java b/sonar-core/src/main/java/org/sonar/core/persistence/DryRunDatabaseFactory.java index c3d9717302e..b11d350e0bc 100644 --- a/sonar-core/src/main/java/org/sonar/core/persistence/DryRunDatabaseFactory.java +++ b/sonar-core/src/main/java/org/sonar/core/persistence/DryRunDatabaseFactory.java @@ -24,7 +24,9 @@ import org.apache.commons.dbcp.BasicDataSource; import org.sonar.api.ServerComponent; import org.sonar.api.platform.ServerFileSystem; import org.sonar.api.utils.SonarException; +import org.sonar.core.review.ReviewDto; +import javax.annotation.Nullable; import javax.sql.DataSource; import java.io.File; @@ -46,14 +48,14 @@ public class DryRunDatabaseFactory implements ServerComponent { this.serverFileSystem = serverFileSystem; } - public byte[] createDatabaseForDryRun(long projectId) { + public byte[] createDatabaseForDryRun(@Nullable Long projectId) { String name = serverFileSystem.getTempDir().getAbsolutePath() + "db-" + System.nanoTime(); try { DataSource source = database.getDataSource(); BasicDataSource destination = create(DIALECT, DRIVER, USER, PASSWORD, URL + name); - copy(source, destination); + copy(source, destination, projectId); close(destination); return dbFileContent(name); @@ -62,18 +64,28 @@ public class DryRunDatabaseFactory implements ServerComponent { } } - private void copy(DataSource source, DataSource dest) { - new DbTemplate() - .copyTable(source, dest, "active_rules") - .copyTable(source, dest, "active_rule_parameters") - .copyTable(source, dest, "characteristics") - .copyTable(source, dest, "characteristic_edges") - .copyTable(source, dest, "characteristic_properties") - .copyTable(source, dest, "metrics") - .copyTable(source, dest, "quality_models") - .copyTable(source, dest, "rules") - .copyTable(source, dest, "rules_parameters") - .copyTable(source, dest, "rules_profiles"); + private void copy(DataSource source, DataSource dest, @Nullable Long projectId) { + DbTemplate template = new DbTemplate(); + template + .copyTable(source, dest, "active_rules") + .copyTable(source, dest, "active_rule_parameters") + .copyTable(source, dest, "characteristics") + .copyTable(source, dest, "characteristic_edges") + .copyTable(source, dest, "characteristic_properties") + .copyTable(source, dest, "metrics") + .copyTable(source, dest, "quality_models") + .copyTable(source, dest, "rules") + .copyTable(source, dest, "rules_parameters") + .copyTable(source, dest, "rules_profiles"); + if (projectId != null) { + String snapshotCondition = "islast=" + database.getDialect().getTrueSqlValue() + " and (project_id=" + projectId + " or root_project_id=" + projectId + ")"; + template + .copyTable(source, dest, "projects", "(id=" + projectId + " or root_id=" + projectId + ")") + .copyTable(source, dest, "reviews", "project_id=" + projectId, "status<>'" + ReviewDto.STATUS_CLOSED + "'") + .copyTable(source, dest, "rule_failures", "snapshot_id in (select id from snapshots where " + snapshotCondition + ")") + .copyTable(source, dest, "snapshots", snapshotCondition) + ; + } } private BasicDataSource create(String dialect, String driver, String user, String password, String url) { diff --git a/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java b/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java index 3d19e74337f..c2bb38c09a7 100644 --- a/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java +++ b/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java @@ -517,7 +517,7 @@ public final class JRubyFacade { } } - public byte[] createDatabaseForDryRun(long projectId) { + public byte[] createDatabaseForDryRun(@Nullable Long projectId) { return get(DryRunDatabaseFactory.class).createDatabaseForDryRun(projectId); } diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/batch_bootstrap_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/batch_bootstrap_controller.rb index 9f2d570cacc..9c2055bbffe 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/batch_bootstrap_controller.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/batch_bootstrap_controller.rb @@ -25,7 +25,7 @@ class BatchBootstrapController < Api::ApiController def db require_parameters :project project = load_project() - db_content = java_facade.createDatabaseForDryRun(project.id) + db_content = java_facade.createDatabaseForDryRun(project ? project.id : nil) send_data String.from_java_bytes(db_content) end |