Commit cc6f321d authored by Stephane Nicoll's avatar Stephane Nicoll

Improve detection of NoSuchMethodError on Java 13

Closes gh-17544
parent 3a49996d
...@@ -31,12 +31,14 @@ import org.springframework.util.ClassUtils; ...@@ -31,12 +31,14 @@ import org.springframework.util.ClassUtils;
* NoSuchMethodErrors}. * NoSuchMethodErrors}.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Stephane Nicoll
*/ */
class NoSuchMethodFailureAnalyzer extends AbstractFailureAnalyzer<NoSuchMethodError> { class NoSuchMethodFailureAnalyzer extends AbstractFailureAnalyzer<NoSuchMethodError> {
@Override @Override
protected FailureAnalysis analyze(Throwable rootFailure, NoSuchMethodError cause) { protected FailureAnalysis analyze(Throwable rootFailure, NoSuchMethodError cause) {
String className = extractClassName(cause); String message = cleanMessage(cause);
String className = extractClassName(message);
if (className == null) { if (className == null) {
return null; return null;
} }
...@@ -48,24 +50,33 @@ class NoSuchMethodFailureAnalyzer extends AbstractFailureAnalyzer<NoSuchMethodEr ...@@ -48,24 +50,33 @@ class NoSuchMethodFailureAnalyzer extends AbstractFailureAnalyzer<NoSuchMethodEr
if (actual == null) { if (actual == null) {
return null; return null;
} }
String description = getDescription(cause, className, candidates, actual); String description = getDescription(cause, message, className, candidates, actual);
return new FailureAnalysis(description, return new FailureAnalysis(description,
"Correct the classpath of your application so that it contains a single, compatible version of " "Correct the classpath of your application so that it contains a single, compatible version of "
+ className, + className,
cause); cause);
} }
private String extractClassName(NoSuchMethodError cause) { private String cleanMessage(NoSuchMethodError error) {
int descriptorIndex = cause.getMessage().indexOf('('); int loadedFromIndex = error.getMessage().indexOf(" (loaded from");
if (loadedFromIndex == -1) {
return error.getMessage();
}
return error.getMessage().substring(0, loadedFromIndex);
}
private String extractClassName(String message) {
int descriptorIndex = message.indexOf('(');
if (descriptorIndex == -1) { if (descriptorIndex == -1) {
return null; return null;
} }
String classAndMethodName = cause.getMessage().substring(0, descriptorIndex); String classAndMethodName = message.substring(0, descriptorIndex);
int methodNameIndex = classAndMethodName.lastIndexOf('.'); int methodNameIndex = classAndMethodName.lastIndexOf('.');
if (methodNameIndex == -1) { if (methodNameIndex == -1) {
return null; return null;
} }
return classAndMethodName.substring(0, methodNameIndex); String className = classAndMethodName.substring(0, methodNameIndex);
return className.replace('/', '.');
} }
private List<URL> findCandidates(String className) { private List<URL> findCandidates(String className) {
...@@ -87,7 +98,8 @@ class NoSuchMethodFailureAnalyzer extends AbstractFailureAnalyzer<NoSuchMethodEr ...@@ -87,7 +98,8 @@ class NoSuchMethodFailureAnalyzer extends AbstractFailureAnalyzer<NoSuchMethodEr
} }
} }
private String getDescription(NoSuchMethodError cause, String className, List<URL> candidates, URL actual) { private String getDescription(NoSuchMethodError cause, String message, String className, List<URL> candidates,
URL actual) {
StringWriter description = new StringWriter(); StringWriter description = new StringWriter();
PrintWriter writer = new PrintWriter(description); PrintWriter writer = new PrintWriter(description);
writer.println("An attempt was made to call a method that does not" writer.println("An attempt was made to call a method that does not"
...@@ -99,7 +111,7 @@ class NoSuchMethodFailureAnalyzer extends AbstractFailureAnalyzer<NoSuchMethodEr ...@@ -99,7 +111,7 @@ class NoSuchMethodFailureAnalyzer extends AbstractFailureAnalyzer<NoSuchMethodEr
writer.println("The following method did not exist:"); writer.println("The following method did not exist:");
writer.println(); writer.println();
writer.print(" "); writer.print(" ");
writer.println(cause.getMessage()); writer.println(message);
writer.println(); writer.println();
writer.println("The method's class, " + className + ", is available from the following locations:"); writer.println("The method's class, " + className + ", is available from the following locations:");
writer.println(); writer.println();
......
...@@ -20,6 +20,9 @@ import javax.servlet.ServletContext; ...@@ -20,6 +20,9 @@ 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.junit.jupiter.api.condition.DisabledOnJre;
import org.junit.jupiter.api.condition.EnabledOnJre;
import org.junit.jupiter.api.condition.JRE;
import org.springframework.boot.diagnostics.FailureAnalysis; import org.springframework.boot.diagnostics.FailureAnalysis;
import org.springframework.boot.testsupport.classpath.ClassPathOverrides; import org.springframework.boot.testsupport.classpath.ClassPathOverrides;
...@@ -36,16 +39,29 @@ import static org.mockito.Mockito.mock; ...@@ -36,16 +39,29 @@ import static org.mockito.Mockito.mock;
class NoSuchMethodFailureAnalyzerTests { class NoSuchMethodFailureAnalyzerTests {
@Test @Test
void noSuchMethodErrorIsAnalyzed() { @EnabledOnJre({ JRE.JAVA_8, JRE.JAVA_11, JRE.JAVA_12 })
void noSuchMethodErrorIsAnalyzedJava8To12() {
testNoSuchMethodErrorFailureAnalysis(
"javax.servlet.ServletContext.addServlet(Ljava/lang/String;Ljavax/servlet/Servlet;)"
+ "Ljavax/servlet/ServletRegistration$Dynamic;");
}
@Test
@DisabledOnJre({ JRE.JAVA_8, JRE.JAVA_11, JRE.JAVA_12 })
void noSuchMethodErrorIsAnalyzedJava13AndLater() {
testNoSuchMethodErrorFailureAnalysis(
"javax/servlet/ServletContext.addServlet(Ljava/lang/String;Ljavax/servlet/Servlet;)"
+ "Ljavax/servlet/ServletRegistration$Dynamic;");
}
private void testNoSuchMethodErrorFailureAnalysis(String expectedMethodRepresentation) {
Throwable failure = createFailure(); Throwable failure = createFailure();
assertThat(failure).isNotNull(); assertThat(failure).isNotNull();
FailureAnalysis analysis = new NoSuchMethodFailureAnalyzer().analyze(failure); FailureAnalysis analysis = new NoSuchMethodFailureAnalyzer().analyze(failure);
assertThat(analysis).isNotNull(); assertThat(analysis).isNotNull();
assertThat(analysis.getDescription()) assertThat(analysis.getDescription())
.contains(NoSuchMethodFailureAnalyzerTests.class.getName() + ".createFailure(") .contains(NoSuchMethodFailureAnalyzerTests.class.getName() + ".createFailure(")
.contains("javax.servlet.ServletContext.addServlet(Ljava/lang/String;Ljavax/servlet/Servlet;)" .contains(expectedMethodRepresentation).contains("class, javax.servlet.ServletContext,");
+ "Ljavax/servlet/ServletRegistration$Dynamic;")
.contains("class, javax.servlet.ServletContext,");
} }
private Throwable createFailure() { private Throwable createFailure() {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment