Include type hierarchy in NoSuchMethodError failure analysis

Closes gh-21587
pull/21605/head
Andy Wilkinson 5 years ago
parent 1975cf498c
commit 744b4d7c26

@ -100,6 +100,7 @@ dependencies {
testImplementation("org.postgresql:postgresql") testImplementation("org.postgresql:postgresql")
testImplementation("org.springframework:spring-context-support") testImplementation("org.springframework:spring-context-support")
testImplementation("org.springframework.data:spring-data-redis") testImplementation("org.springframework.data:spring-data-redis")
testImplementation("org.springframework.data:spring-data-r2dbc")
testImplementation("org.xerial:sqlite-jdbc") testImplementation("org.xerial:sqlite-jdbc")
testRuntimeOnly("org.junit.platform:junit-platform-launcher") testRuntimeOnly("org.junit.platform:junit-platform-launcher")

@ -19,6 +19,7 @@ package org.springframework.boot.diagnostics.analyzer;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import java.net.URL; import java.net.URL;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -58,11 +59,15 @@ class NoSuchMethodFailureAnalyzer extends AbstractFailureAnalyzer<NoSuchMethodEr
if (candidates == null) { if (candidates == null) {
return null; return null;
} }
URL actual = getActual(className); Class<?> type = load(className);
if (actual == null) { if (type == null) {
return null; return null;
} }
return new NoSuchMethodDescriptor(message, className, candidates, actual); List<ClassDescriptor> typeHierarchy = getTypeHierarchy(type);
if (typeHierarchy == null) {
return null;
}
return new NoSuchMethodDescriptor(message, className, candidates, typeHierarchy);
} }
private String cleanMessage(String message) { private String cleanMessage(String message) {
@ -104,10 +109,24 @@ class NoSuchMethodFailureAnalyzer extends AbstractFailureAnalyzer<NoSuchMethodEr
} }
} }
private URL getActual(String className) { private Class<?> load(String className) {
try { try {
return Class.forName(className, false, getClass().getClassLoader()).getProtectionDomain().getCodeSource() return Class.forName(className, false, getClass().getClassLoader());
.getLocation(); }
catch (Throwable ex) {
return null;
}
}
private List<ClassDescriptor> getTypeHierarchy(Class<?> type) {
try {
List<ClassDescriptor> typeHierarchy = new ArrayList<>();
while (type != null && !type.equals(Object.class)) {
typeHierarchy.add(new ClassDescriptor(type.getCanonicalName(),
type.getProtectionDomain().getCodeSource().getLocation()));
type = type.getSuperclass();
}
return typeHierarchy;
} }
catch (Throwable ex) { catch (Throwable ex) {
return null; return null;
@ -136,10 +155,15 @@ class NoSuchMethodFailureAnalyzer extends AbstractFailureAnalyzer<NoSuchMethodEr
writer.println(candidate); writer.println(candidate);
} }
writer.println(); writer.println();
writer.println("It was loaded from the following location:"); writer.println("The class hierarchy was loaded from the following locations:");
writer.println(); writer.println();
writer.print(" "); for (ClassDescriptor type : descriptor.getTypeHierarchy()) {
writer.println(descriptor.getActualLocation()); writer.print(" ");
writer.print(type.getName());
writer.print(": ");
writer.println(type.getLocation());
}
return description.toString(); return description.toString();
} }
@ -151,14 +175,14 @@ class NoSuchMethodFailureAnalyzer extends AbstractFailureAnalyzer<NoSuchMethodEr
private final List<URL> candidateLocations; private final List<URL> candidateLocations;
private final URL actualLocation; private final List<ClassDescriptor> typeHierarchy;
public NoSuchMethodDescriptor(String errorMessage, String className, List<URL> candidateLocations, public NoSuchMethodDescriptor(String errorMessage, String className, List<URL> candidateLocations,
URL actualLocation) { List<ClassDescriptor> typeHierarchy) {
this.errorMessage = errorMessage; this.errorMessage = errorMessage;
this.className = className; this.className = className;
this.candidateLocations = candidateLocations; this.candidateLocations = candidateLocations;
this.actualLocation = actualLocation; this.typeHierarchy = typeHierarchy;
} }
public String getErrorMessage() { public String getErrorMessage() {
@ -173,8 +197,29 @@ class NoSuchMethodFailureAnalyzer extends AbstractFailureAnalyzer<NoSuchMethodEr
return this.candidateLocations; return this.candidateLocations;
} }
public URL getActualLocation() { public List<ClassDescriptor> getTypeHierarchy() {
return this.actualLocation; return this.typeHierarchy;
}
}
protected static class ClassDescriptor {
private final String name;
private final URL location;
public ClassDescriptor(String name, URL location) {
this.name = name;
this.location = location;
}
public String getName() {
return this.name;
}
public URL getLocation() {
return this.location;
} }
} }

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,14 +16,18 @@
package org.springframework.boot.diagnostics.analyzer; package org.springframework.boot.diagnostics.analyzer;
import java.util.List;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServlet;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.diagnostics.FailureAnalysis; import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.boot.diagnostics.analyzer.NoSuchMethodFailureAnalyzer.ClassDescriptor;
import org.springframework.boot.diagnostics.analyzer.NoSuchMethodFailureAnalyzer.NoSuchMethodDescriptor; import org.springframework.boot.diagnostics.analyzer.NoSuchMethodFailureAnalyzer.NoSuchMethodDescriptor;
import org.springframework.boot.testsupport.classpath.ClassPathOverrides; import org.springframework.boot.testsupport.classpath.ClassPathOverrides;
import org.springframework.data.r2dbc.mapping.R2dbcMappingContext;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@ -34,7 +38,8 @@ import static org.mockito.Mockito.mock;
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Stephane Nicoll * @author Stephane Nicoll
*/ */
@ClassPathOverrides("javax.servlet:servlet-api:2.5") @ClassPathOverrides({ "javax.servlet:servlet-api:2.5",
"org.springframework.data:spring-data-relational:1.1.7.RELEASE" })
class NoSuchMethodFailureAnalyzerTests { class NoSuchMethodFailureAnalyzerTests {
@Test @Test
@ -48,7 +53,9 @@ class NoSuchMethodFailureAnalyzerTests {
+ "Ljavax/servlet/ServletRegistration$Dynamic;"); + "Ljavax/servlet/ServletRegistration$Dynamic;");
assertThat(descriptor.getClassName()).isEqualTo("javax.servlet.ServletContext"); assertThat(descriptor.getClassName()).isEqualTo("javax.servlet.ServletContext");
assertThat(descriptor.getCandidateLocations().size()).isGreaterThan(1); assertThat(descriptor.getCandidateLocations().size()).isGreaterThan(1);
assertThat(descriptor.getActualLocation()).asString().contains("servlet-api-2.5.jar"); List<ClassDescriptor> typeHierarchy = descriptor.getTypeHierarchy();
assertThat(typeHierarchy).hasSize(1);
assertThat(typeHierarchy.get(0).getLocation()).asString().contains("servlet-api-2.5.jar");
} }
@Test @Test
@ -62,7 +69,9 @@ class NoSuchMethodFailureAnalyzerTests {
+ "Ljavax/servlet/ServletRegistration$Dynamic;"); + "Ljavax/servlet/ServletRegistration$Dynamic;");
assertThat(descriptor.getClassName()).isEqualTo("javax.servlet.ServletContext"); assertThat(descriptor.getClassName()).isEqualTo("javax.servlet.ServletContext");
assertThat(descriptor.getCandidateLocations().size()).isGreaterThan(1); assertThat(descriptor.getCandidateLocations().size()).isGreaterThan(1);
assertThat(descriptor.getActualLocation()).asString().contains("servlet-api-2.5.jar"); List<ClassDescriptor> typeHierarchy = descriptor.getTypeHierarchy();
assertThat(typeHierarchy).hasSize(1);
assertThat(typeHierarchy.get(0).getLocation()).asString().contains("servlet-api-2.5.jar");
} }
@Test @Test
@ -76,11 +85,13 @@ class NoSuchMethodFailureAnalyzerTests {
+ "java.lang.String, javax.servlet.Servlet)'"); + "java.lang.String, javax.servlet.Servlet)'");
assertThat(descriptor.getClassName()).isEqualTo("javax.servlet.ServletContext"); assertThat(descriptor.getClassName()).isEqualTo("javax.servlet.ServletContext");
assertThat(descriptor.getCandidateLocations().size()).isGreaterThan(1); assertThat(descriptor.getCandidateLocations().size()).isGreaterThan(1);
assertThat(descriptor.getActualLocation()).asString().contains("servlet-api-2.5.jar"); List<ClassDescriptor> typeHierarchy = descriptor.getTypeHierarchy();
assertThat(typeHierarchy).hasSize(1);
assertThat(typeHierarchy.get(0).getLocation()).asString().contains("servlet-api-2.5.jar");
} }
@Test @Test
void noSuchMethodErrorIsAnalyzed() { void whenAMethodOnAnInterfaceIsMissingThenNoSuchMethodErrorIsAnalyzed() {
Throwable failure = createFailure(); Throwable failure = createFailure();
assertThat(failure).isNotNull(); assertThat(failure).isNotNull();
FailureAnalysis analysis = new NoSuchMethodFailureAnalyzer().analyze(failure); FailureAnalysis analysis = new NoSuchMethodFailureAnalyzer().analyze(failure);
@ -90,6 +101,20 @@ class NoSuchMethodFailureAnalyzerTests {
.contains("class, javax.servlet.ServletContext,"); .contains("class, javax.servlet.ServletContext,");
} }
@Test
void whenAnInheritedMethodIsMissingThenNoSuchMethodErrorIsAnalyzed() {
Throwable failure = createFailureForMissingInheritedMethod();
assertThat(failure).isNotNull();
FailureAnalysis analysis = new NoSuchMethodFailureAnalyzer().analyze(failure);
assertThat(analysis).isNotNull();
assertThat(analysis.getDescription()).contains(R2dbcMappingContext.class.getName() + ".<init>(")
.contains("setForceQuote(Z)V")
.contains("class, org.springframework.data.r2dbc.mapping.R2dbcMappingContext,")
.contains(" org.springframework.data.r2dbc.mapping.R2dbcMappingContext")
.contains(" org.springframework.data.relational.core.mapping.RelationalMappingContext")
.contains(" org.springframework.data.mapping.context.AbstractMappingContext");
}
private Throwable createFailure() { private Throwable createFailure() {
try { try {
ServletContext servletContext = mock(ServletContext.class); ServletContext servletContext = mock(ServletContext.class);
@ -102,4 +127,14 @@ class NoSuchMethodFailureAnalyzerTests {
} }
} }
private Throwable createFailureForMissingInheritedMethod() {
try {
new R2dbcMappingContext();
return null;
}
catch (Throwable ex) {
return ex;
}
}
} }

Loading…
Cancel
Save