mirror of
https://github.com/square/okhttp.git
synced 2025-08-07 12:42:57 +03:00
Use our test rule instead of the Maven Surefire listener
Closes: https://github.com/square/okhttp/issues/4894
This commit is contained in:
@@ -137,7 +137,6 @@ subprojects { project ->
|
|||||||
def platformJavaHome = System.getProperty('test.java.home')
|
def platformJavaHome = System.getProperty('test.java.home')
|
||||||
|
|
||||||
test {
|
test {
|
||||||
jvmArgs += "-Dlistener=okhttp3.testing.InstallUncaughtExceptionHandlerListener"
|
|
||||||
jvmArgs += "-Dokhttp.platform=$platform"
|
jvmArgs += "-Dokhttp.platform=$platform"
|
||||||
|
|
||||||
if (platformJavaHome != null) {
|
if (platformJavaHome != null) {
|
||||||
|
@@ -24,13 +24,20 @@ import org.junit.runners.model.Statement
|
|||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
/** Apply this rule to tests that need an OkHttpClient instance. */
|
/**
|
||||||
|
* Apply this rule to all tests. It adds additional checks for leaked resources and uncaught
|
||||||
|
* exceptions.
|
||||||
|
*
|
||||||
|
* Use [newClient] as a factory for a OkHttpClient instances. These instances are specifically
|
||||||
|
* configured for testing.
|
||||||
|
*/
|
||||||
class OkHttpClientTestRule : TestRule {
|
class OkHttpClientTestRule : TestRule {
|
||||||
private val clientEventsList = mutableListOf<String>()
|
private val clientEventsList = mutableListOf<String>()
|
||||||
private var testClient: OkHttpClient? = null
|
private var testClient: OkHttpClient? = null
|
||||||
|
private var uncaughtException: Throwable? = null
|
||||||
|
|
||||||
fun wrap(eventListener: EventListener) = object : EventListener.Factory {
|
fun wrap(eventListener: EventListener) = object : EventListener.Factory {
|
||||||
override fun create(call: Call): EventListener = ClientRuleEventListener(eventListener) { addEvent(it) }
|
override fun create(call: Call) = ClientRuleEventListener(eventListener) { addEvent(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,7 +55,7 @@ class OkHttpClientTestRule : TestRule {
|
|||||||
client = OkHttpClient.Builder()
|
client = OkHttpClient.Builder()
|
||||||
.dns(SINGLE_INET_ADDRESS_DNS) // Prevent unexpected fallback addresses.
|
.dns(SINGLE_INET_ADDRESS_DNS) // Prevent unexpected fallback addresses.
|
||||||
.eventListenerFactory(object : EventListener.Factory {
|
.eventListenerFactory(object : EventListener.Factory {
|
||||||
override fun create(call: Call): EventListener = ClientRuleEventListener { addEvent(it) }
|
override fun create(call: Call) = ClientRuleEventListener { addEvent(it) }
|
||||||
})
|
})
|
||||||
.build()
|
.build()
|
||||||
testClient = client
|
testClient = client
|
||||||
@@ -60,8 +67,14 @@ class OkHttpClientTestRule : TestRule {
|
|||||||
return newClient().newBuilder()
|
return newClient().newBuilder()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized private fun addEvent(it: String) {
|
@Synchronized private fun addEvent(event: String) {
|
||||||
clientEventsList.add(it)
|
clientEventsList.add(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized private fun initUncaughtException(throwable: Throwable) {
|
||||||
|
if (uncaughtException == null) {
|
||||||
|
uncaughtException = throwable
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ensureAllConnectionsReleased() {
|
fun ensureAllConnectionsReleased() {
|
||||||
@@ -75,7 +88,7 @@ class OkHttpClientTestRule : TestRule {
|
|||||||
private fun ensureAllTaskQueuesIdle() {
|
private fun ensureAllTaskQueuesIdle() {
|
||||||
for (queue in TaskRunner.INSTANCE.activeQueues()) {
|
for (queue in TaskRunner.INSTANCE.activeQueues()) {
|
||||||
assertThat(queue.awaitIdle(TimeUnit.MILLISECONDS.toNanos(1000L)))
|
assertThat(queue.awaitIdle(TimeUnit.MILLISECONDS.toNanos(1000L)))
|
||||||
.withFailMessage("Queue still active after 1000ms")
|
.withFailMessage("Queue still active after 1000 ms")
|
||||||
.isTrue()
|
.isTrue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,13 +96,21 @@ class OkHttpClientTestRule : TestRule {
|
|||||||
override fun apply(base: Statement, description: Description): Statement {
|
override fun apply(base: Statement, description: Description): Statement {
|
||||||
return object : Statement() {
|
return object : Statement() {
|
||||||
override fun evaluate() {
|
override fun evaluate() {
|
||||||
|
val defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
|
||||||
|
Thread.setDefaultUncaughtExceptionHandler { _, throwable ->
|
||||||
|
initUncaughtException(throwable)
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
base.evaluate()
|
base.evaluate()
|
||||||
|
if (uncaughtException != null) {
|
||||||
|
throw AssertionError("uncaught exception thrown during test", uncaughtException)
|
||||||
|
}
|
||||||
logEventsIfFlaky(description)
|
logEventsIfFlaky(description)
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
logEvents()
|
logEvents()
|
||||||
throw t
|
throw t
|
||||||
} finally {
|
} finally {
|
||||||
|
Thread.setDefaultUncaughtExceptionHandler(defaultUncaughtExceptionHandler)
|
||||||
ensureAllConnectionsReleased()
|
ensureAllConnectionsReleased()
|
||||||
releaseClient()
|
releaseClient()
|
||||||
ensureAllTaskQueuesIdle()
|
ensureAllTaskQueuesIdle()
|
||||||
|
@@ -1,75 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2015 Square, Inc.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package okhttp3.testing;
|
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import org.junit.internal.Throwables;
|
|
||||||
import org.junit.runner.Description;
|
|
||||||
import org.junit.runner.Result;
|
|
||||||
import org.junit.runner.notification.RunListener;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link org.junit.runner.notification.RunListener} used to install an aggressive default {@link
|
|
||||||
* java.lang.Thread.UncaughtExceptionHandler} similar to the one found on Android. No exceptions
|
|
||||||
* should escape from OkHttp that might cause apps to be killed or tests to fail on Android.
|
|
||||||
*/
|
|
||||||
public class InstallUncaughtExceptionHandlerListener extends RunListener {
|
|
||||||
|
|
||||||
private Thread.UncaughtExceptionHandler oldDefaultUncaughtExceptionHandler;
|
|
||||||
private Description lastTestStarted;
|
|
||||||
private final Map<Throwable, String> exceptions = new LinkedHashMap<>();
|
|
||||||
|
|
||||||
@Override public void testRunStarted(Description description) {
|
|
||||||
System.err.println("Installing aggressive uncaught exception handler");
|
|
||||||
oldDefaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
|
|
||||||
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
|
|
||||||
StringWriter errorText = new StringWriter(256);
|
|
||||||
errorText.append("Uncaught exception in OkHttp thread \"");
|
|
||||||
errorText.append(thread.getName());
|
|
||||||
errorText.append("\"\n");
|
|
||||||
throwable.printStackTrace(new PrintWriter(errorText));
|
|
||||||
errorText.append("\n");
|
|
||||||
if (lastTestStarted != null) {
|
|
||||||
errorText.append("Last test to start was: ");
|
|
||||||
errorText.append(lastTestStarted.getDisplayName());
|
|
||||||
errorText.append("\n");
|
|
||||||
}
|
|
||||||
System.err.print(errorText.toString());
|
|
||||||
|
|
||||||
synchronized (exceptions) {
|
|
||||||
exceptions.put(throwable, lastTestStarted.getDisplayName());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public void testStarted(Description description) {
|
|
||||||
lastTestStarted = description;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public void testRunFinished(Result result) throws Exception {
|
|
||||||
Thread.setDefaultUncaughtExceptionHandler(oldDefaultUncaughtExceptionHandler);
|
|
||||||
System.err.println("Uninstalled aggressive uncaught exception handler");
|
|
||||||
|
|
||||||
synchronized (exceptions) {
|
|
||||||
if (!exceptions.isEmpty()) {
|
|
||||||
throw Throwables.rethrowAsException(exceptions.keySet().iterator().next());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Square, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package okhttp3
|
||||||
|
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.Assert.fail
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.Description
|
||||||
|
import org.junit.runners.model.Statement
|
||||||
|
|
||||||
|
class OkHttpClientTestRuleTest {
|
||||||
|
@Test fun uncaughtException() {
|
||||||
|
val testRule = OkHttpClientTestRule()
|
||||||
|
val description = Description.createTestDescription(
|
||||||
|
OkHttpClientTestRuleTest::class.java, "test")
|
||||||
|
val statement = testRule.apply(object : Statement() {
|
||||||
|
override fun evaluate() {
|
||||||
|
val thread = object : Thread() {
|
||||||
|
override fun run() {
|
||||||
|
throw RuntimeException("boom!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thread.start()
|
||||||
|
thread.join()
|
||||||
|
}
|
||||||
|
}, description)
|
||||||
|
|
||||||
|
try {
|
||||||
|
statement.evaluate()
|
||||||
|
fail()
|
||||||
|
} catch (expected: AssertionError) {
|
||||||
|
assertThat(expected).hasMessage("uncaught exception thrown during test")
|
||||||
|
assertThat(expected.cause).hasMessage("boom!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user