From: Sébastien Lesaint Date: Thu, 3 Dec 2015 18:27:58 +0000 (+0100) Subject: SONAR-6997 add VisitException and show current Component info in logs X-Git-Tag: 5.3-RC1~60 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=refs%2Fpull%2F678%2Fhead;p=sonarqube.git SONAR-6997 add VisitException and show current Component info in logs --- diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/component/DepthTraversalTypeAwareCrawler.java b/server/sonar-server/src/main/java/org/sonar/server/computation/component/DepthTraversalTypeAwareCrawler.java index 1ede3d04c47..2882a03a93b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/component/DepthTraversalTypeAwareCrawler.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/component/DepthTraversalTypeAwareCrawler.java @@ -36,6 +36,14 @@ public final class DepthTraversalTypeAwareCrawler implements ComponentCrawler { @Override public void visit(Component component) { + try { + visitImpl(component); + } catch (RuntimeException e) { + VisitException.rethrowOrWrap(e, "Visit of Component %s:%s failed", component.getType(), component.getKey()); + } + } + + private void visitImpl(Component component) { if (!verifyDepth(component)) { return; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/component/PathAwareCrawler.java b/server/sonar-server/src/main/java/org/sonar/server/computation/component/PathAwareCrawler.java index 8a2e33e4d19..5c92e471a19 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/component/PathAwareCrawler.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/component/PathAwareCrawler.java @@ -19,6 +19,12 @@ */ package org.sonar.server.computation.component; +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import javax.annotation.Nonnull; +import javax.annotation.concurrent.Immutable; + +import static com.google.common.collect.FluentIterable.from; import static java.lang.String.format; import static java.util.Objects.requireNonNull; import static org.sonar.server.computation.component.ComponentVisitor.Order.POST_ORDER; @@ -31,7 +37,6 @@ import static org.sonar.server.computation.component.ComponentVisitor.Order.PRE_ * As for {@link DepthTraversalTypeAwareCrawler}, this crawler supports max depth visit and ordering. */ public final class PathAwareCrawler implements ComponentCrawler { - private final PathAwareVisitor visitor; private final DequeBasedPath stack = new DequeBasedPath<>(); @@ -41,6 +46,17 @@ public final class PathAwareCrawler implements ComponentCrawler { @Override public void visit(Component component) { + try { + visitImpl(component); + } catch (RuntimeException e) { + VisitException.rethrowOrWrap( + e, + "Visit failed for Component %s:%s%s", + component.getType(), component.getKey(), new ComponentPathPrinter<>(stack)); + } + } + + private void visitImpl(Component component) { if (!verifyDepth(component)) { return; } @@ -123,4 +139,44 @@ public final class PathAwareCrawler implements ComponentCrawler { } } + /** + * A simple object wrapping the currentPath allowing to compute the string representing the path only if + * the VisitException is actually built (ie. method {@link ComponentPathPrinter#toString()} is called + * by the internal {@link String#format(String, Object...)} of + * {@link VisitException#rethrowOrWrap(RuntimeException, String, Object...)}. + */ + @Immutable + private static final class ComponentPathPrinter { + + private static final Joiner PATH_ELEMENTS_JOINER = Joiner.on("->"); + + private final DequeBasedPath currentPath; + + private ComponentPathPrinter(DequeBasedPath currentPath) { + this.currentPath = currentPath; + } + + @Override + public String toString() { + if (currentPath.isRoot()) { + return ""; + } + return " located " + toKeyPath(currentPath); + } + + private static String toKeyPath(Iterable> currentPath) { + return PATH_ELEMENTS_JOINER.join(from(currentPath).transform(PathElementToComponentAsString.INSTANCE).skip(1)); + } + + private enum PathElementToComponentAsString implements Function, String> { + INSTANCE; + + @Override + @Nonnull + public String apply(@Nonnull PathAwareVisitor.PathElement input) { + return format("%s:%s", input.getComponent().getType(), input.getComponent().getKey()); + } + } + } + } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/component/VisitException.java b/server/sonar-server/src/main/java/org/sonar/server/computation/component/VisitException.java new file mode 100644 index 00000000000..1cd04fdae62 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/component/VisitException.java @@ -0,0 +1,47 @@ +/* + * 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.server.computation.component; + +import static java.lang.String.format; + +/** + * Wrapper {@link RuntimeException} of any {@link RuntimeException} thrown during the visit of the Component tree. + * + * Use {@link #rethrowOrWrap(RuntimeException, String, Object...)} to avoid having {@link VisitException} as cause of + * another {@link VisitException} + */ +public class VisitException extends RuntimeException { + public VisitException(String message, RuntimeException cause) { + super(message, cause); + } + + /** + * @param e the {@link RuntimeException} to wrap unless it is an instance of {@link VisitException} + * @param pattern a {@link String#format(String, Object...)} pattern + * @param args optional {@link String#format(String, Object...)} arguments + */ + public static void rethrowOrWrap(RuntimeException e, String pattern, Object... args) { + if (e instanceof VisitException) { + throw e; + } else { + throw new VisitException(format(pattern, args), e); + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/component/VisitorsCrawler.java b/server/sonar-server/src/main/java/org/sonar/server/computation/component/VisitorsCrawler.java index 0b030291281..52ded7e54d6 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/component/VisitorsCrawler.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/component/VisitorsCrawler.java @@ -59,6 +59,18 @@ public class VisitorsCrawler implements ComponentCrawler { @Override public void visit(final Component component) { + try { + visitImpl(component); + } catch (RuntimeException e) { + VisitException.rethrowOrWrap( + e, + "Visit of Component %s:" + + "%s failed", + component.getType(), component.getKey()); + } + } + + private void visitImpl(Component component) { MatchVisitorMaxDepth visitorMaxDepth = MatchVisitorMaxDepth.forComponent(component); List preOrderVisitorWrappersToExecute = from(preOrderVisitorWrappers).filter(visitorMaxDepth).toList(); List postOrderVisitorWrappersToExecute = from(postOrderVisitorWrappers).filter(visitorMaxDepth).toList(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/LoadDuplicationsFromReportStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/LoadDuplicationsFromReportStepTest.java index a57e5807d24..5515924be2e 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/step/LoadDuplicationsFromReportStepTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/step/LoadDuplicationsFromReportStepTest.java @@ -27,6 +27,7 @@ import org.sonar.batch.protocol.output.BatchReport; import org.sonar.server.computation.batch.BatchReportReaderRule; import org.sonar.server.computation.batch.TreeRootHolderRule; import org.sonar.server.computation.component.Component; +import org.sonar.server.computation.component.VisitException; import org.sonar.server.computation.duplication.Duplicate; import org.sonar.server.computation.duplication.Duplication; import org.sonar.server.computation.duplication.DuplicationRepositoryRule; @@ -38,6 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.sonar.server.computation.component.Component.Type.FILE; import static org.sonar.server.computation.component.Component.Type.PROJECT; import static org.sonar.server.computation.component.ReportComponent.builder; +import static org.sonar.test.ExceptionCauseMatcher.hasType; public class LoadDuplicationsFromReportStepTest { private static final int LINE = 2; @@ -135,8 +137,8 @@ public class LoadDuplicationsFromReportStepTest { int line = 2; reportReader.putDuplications(FILE_1_REF, createDuplication(singleLineTextRange(line), createInProjectDuplicate(666, line + 1))); - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("Component with ref '666' can't be found"); + expectedException.expect(VisitException.class); + expectedException.expectCause(hasType(IllegalArgumentException.class).andMessage("Component with ref '666' can't be found")); underTest.execute(); } @@ -146,8 +148,8 @@ public class LoadDuplicationsFromReportStepTest { int line = 2; reportReader.putDuplications(FILE_1_REF, createDuplication(singleLineTextRange(line), createInProjectDuplicate(FILE_1_REF, line + 1))); - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("file and otherFile Components can not be the same"); + expectedException.expect(VisitException.class); + expectedException.expectCause(hasType(IllegalArgumentException.class).andMessage("file and otherFile Components can not be the same")); underTest.execute(); } @@ -196,4 +198,5 @@ public class LoadDuplicationsFromReportStepTest { private void assertNoDuplication(int fileRef) { assertThat(duplicationRepository.getDuplications(fileRef)).isEmpty(); } + } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistProjectLinksStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistProjectLinksStepTest.java index cc51e596f44..a758a74525e 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistProjectLinksStepTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/step/PersistProjectLinksStepTest.java @@ -34,6 +34,7 @@ import org.sonar.server.computation.batch.BatchReportReaderRule; import org.sonar.server.computation.batch.TreeRootHolderRule; import org.sonar.server.computation.component.Component; import org.sonar.server.computation.component.ReportComponent; +import org.sonar.server.computation.component.VisitException; import org.sonar.test.DbTests; import static org.assertj.core.api.Assertions.assertThat; @@ -207,9 +208,10 @@ public class PersistProjectLinksStepTest extends BaseStepTest { try { step.execute(); - failBecauseExceptionWasNotThrown(IllegalArgumentException.class); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessage("Link of type 'homepage' has already been declared on component 'ABCD'"); + failBecauseExceptionWasNotThrown(VisitException.class); + } catch (VisitException e) { + assertThat(e.getCause()).isInstanceOf(IllegalArgumentException.class); + assertThat(e.getCause()).hasMessage("Link of type 'homepage' has already been declared on component 'ABCD'"); } } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/QualityGateLoadingStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/QualityGateLoadingStepTest.java index b50e8c69f6d..9924557f2db 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/step/QualityGateLoadingStepTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/step/QualityGateLoadingStepTest.java @@ -29,17 +29,20 @@ import org.sonar.server.computation.batch.TreeRootHolderRule; import org.sonar.server.computation.component.Component; import org.sonar.server.computation.component.ReportComponent; import org.sonar.server.computation.component.SettingsRepository; +import org.sonar.server.computation.component.VisitException; import org.sonar.server.computation.qualitygate.Condition; import org.sonar.server.computation.qualitygate.MutableQualityGateHolderRule; import org.sonar.server.computation.qualitygate.QualityGate; import org.sonar.server.computation.qualitygate.QualityGateService; +import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.guava.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import static org.sonar.test.ExceptionCauseMatcher.hasType; public class QualityGateLoadingStepTest { private static final String PROJECT_KEY = "project key"; @@ -74,8 +77,10 @@ public class QualityGateLoadingStepTest { @Test public void execute_sets_default_QualityGate_when_property_value_is_not_a_long() { - expectedException.expect(IllegalStateException.class); - expectedException.expectMessage(String.format("Unsupported value (%s) in property sonar.qualitygate", "10 sds")); + expectedException.expect(VisitException.class); + expectedException.expectCause( + hasType(IllegalStateException.class) + .andMessage(format("Unsupported value (%s) in property sonar.qualitygate", "10 sds"))); treeRootHolder.setRoot(PROJECT_ALONE); when(settingsRepository.getSettings(PROJECT_ALONE)).thenReturn(new Settings().setProperty("sonar.qualitygate", "10 sds"));