Skip to content

Commit

Permalink
Include type hierarchy in NoSuchMethodError failure analysis
Browse files Browse the repository at this point in the history
Closes gh-21587
  • Loading branch information
wilkinsona committed May 27, 2020
1 parent 1975cf4 commit 744b4d7
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 20 deletions.
1 change: 1 addition & 0 deletions spring-boot-project/spring-boot/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ dependencies {
testImplementation("org.postgresql:postgresql")
testImplementation("org.springframework:spring-context-support")
testImplementation("org.springframework.data:spring-data-redis")
testImplementation("org.springframework.data:spring-data-r2dbc")
testImplementation("org.xerial:sqlite-jdbc")

testRuntimeOnly("org.junit.platform:junit-platform-launcher")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

Expand Down Expand Up @@ -58,11 +59,15 @@ protected NoSuchMethodDescriptor getNoSuchMethodDescriptor(String cause) {
if (candidates == null) {
return null;
}
URL actual = getActual(className);
if (actual == null) {
Class<?> type = load(className);
if (type == 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) {
Expand Down Expand Up @@ -104,10 +109,24 @@ private List<URL> findCandidates(String className) {
}
}

private URL getActual(String className) {
private Class<?> load(String className) {
try {
return Class.forName(className, false, getClass().getClassLoader()).getProtectionDomain().getCodeSource()
.getLocation();
return Class.forName(className, false, getClass().getClassLoader());
}
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) {
return null;
Expand Down Expand Up @@ -136,10 +155,15 @@ private String getDescription(NoSuchMethodError cause, NoSuchMethodDescriptor de
writer.println(candidate);
}
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.print(" ");
writer.println(descriptor.getActualLocation());
for (ClassDescriptor type : descriptor.getTypeHierarchy()) {
writer.print(" ");
writer.print(type.getName());
writer.print(": ");
writer.println(type.getLocation());
}

return description.toString();
}

Expand All @@ -151,14 +175,14 @@ protected static class NoSuchMethodDescriptor {

private final List<URL> candidateLocations;

private final URL actualLocation;
private final List<ClassDescriptor> typeHierarchy;

public NoSuchMethodDescriptor(String errorMessage, String className, List<URL> candidateLocations,
URL actualLocation) {
List<ClassDescriptor> typeHierarchy) {
this.errorMessage = errorMessage;
this.className = className;
this.candidateLocations = candidateLocations;
this.actualLocation = actualLocation;
this.typeHierarchy = typeHierarchy;
}

public String getErrorMessage() {
Expand All @@ -173,8 +197,29 @@ public List<URL> getCandidateLocations() {
return this.candidateLocations;
}

public URL getActualLocation() {
return this.actualLocation;
public List<ClassDescriptor> getTypeHierarchy() {
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;
}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -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");
* you may not use this file except in compliance with the License.
Expand All @@ -16,14 +16,18 @@

package org.springframework.boot.diagnostics.analyzer;

import java.util.List;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServlet;

import org.junit.jupiter.api.Test;

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.testsupport.classpath.ClassPathOverrides;
import org.springframework.data.r2dbc.mapping.R2dbcMappingContext;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
Expand All @@ -34,7 +38,8 @@
* @author Andy Wilkinson
* @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 {

@Test
Expand All @@ -48,7 +53,9 @@ void parseJava8ErrorMessage() {
+ "Ljavax/servlet/ServletRegistration$Dynamic;");
assertThat(descriptor.getClassName()).isEqualTo("javax.servlet.ServletContext");
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
Expand All @@ -62,7 +69,9 @@ void parseJavaOpenJ9ErrorMessage() {
+ "Ljavax/servlet/ServletRegistration$Dynamic;");
assertThat(descriptor.getClassName()).isEqualTo("javax.servlet.ServletContext");
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
Expand All @@ -76,11 +85,13 @@ void parseJavaImprovedHotspotErrorMessage() {
+ "java.lang.String, javax.servlet.Servlet)'");
assertThat(descriptor.getClassName()).isEqualTo("javax.servlet.ServletContext");
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
void noSuchMethodErrorIsAnalyzed() {
void whenAMethodOnAnInterfaceIsMissingThenNoSuchMethodErrorIsAnalyzed() {
Throwable failure = createFailure();
assertThat(failure).isNotNull();
FailureAnalysis analysis = new NoSuchMethodFailureAnalyzer().analyze(failure);
Expand All @@ -90,6 +101,20 @@ void noSuchMethodErrorIsAnalyzed() {
.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() {
try {
ServletContext servletContext = mock(ServletContext.class);
Expand All @@ -102,4 +127,14 @@ private Throwable createFailure() {
}
}

private Throwable createFailureForMissingInheritedMethod() {
try {
new R2dbcMappingContext();
return null;
}
catch (Throwable ex) {
return ex;
}
}

}

0 comments on commit 744b4d7

Please sign in to comment.