mirror of
https://github.com/square/okhttp.git
synced 2025-08-08 23:42:08 +03:00
Check for matching ConnectionEvent (#7612)
* Check for matching ConnectionEvent * Fixes for API file * Fix tests * Fix cancel event logic
This commit is contained in:
@@ -30,8 +30,8 @@ sealed class CallEvent {
|
||||
val name: String
|
||||
get() = javaClass.simpleName
|
||||
|
||||
/** Returns the open event that this close event closes, or null if this is not a close event. */
|
||||
open fun closes(timestampNs: Long): CallEvent? = null
|
||||
/** Returns if the event closes this event, or null if this is no open event. */
|
||||
open fun closes(event: CallEvent): Boolean? = null
|
||||
|
||||
data class ProxySelectStart(
|
||||
override val timestampNs: Long,
|
||||
@@ -44,7 +44,10 @@ sealed class CallEvent {
|
||||
override val call: Call,
|
||||
val url: HttpUrl,
|
||||
val proxies: List<Proxy>?
|
||||
) : CallEvent()
|
||||
) : CallEvent() {
|
||||
override fun closes(event: CallEvent): Boolean =
|
||||
event is ProxySelectStart && call == event.call && url == event.url
|
||||
}
|
||||
|
||||
data class DnsStart(
|
||||
override val timestampNs: Long,
|
||||
@@ -58,7 +61,8 @@ sealed class CallEvent {
|
||||
val domainName: String,
|
||||
val inetAddressList: List<InetAddress>
|
||||
) : CallEvent() {
|
||||
override fun closes(timestampNs: Long): CallEvent = DnsStart(timestampNs, call, domainName)
|
||||
override fun closes(event: CallEvent): Boolean =
|
||||
event is DnsStart && call == event.call && domainName == event.domainName
|
||||
}
|
||||
|
||||
data class ConnectStart(
|
||||
@@ -75,8 +79,8 @@ sealed class CallEvent {
|
||||
val proxy: Proxy?,
|
||||
val protocol: Protocol?
|
||||
) : CallEvent() {
|
||||
override fun closes(timestampNs: Long): CallEvent =
|
||||
ConnectStart(timestampNs, call, inetSocketAddress, proxy)
|
||||
override fun closes(event: CallEvent): Boolean =
|
||||
event is ConnectStart && call == event.call && inetSocketAddress == event.inetSocketAddress && proxy == event.proxy
|
||||
}
|
||||
|
||||
data class ConnectFailed(
|
||||
@@ -87,8 +91,8 @@ sealed class CallEvent {
|
||||
val protocol: Protocol?,
|
||||
val ioe: IOException
|
||||
) : CallEvent() {
|
||||
override fun closes(timestampNs: Long): CallEvent =
|
||||
ConnectStart(timestampNs, call, inetSocketAddress, proxy)
|
||||
override fun closes(event: CallEvent): Boolean =
|
||||
event is ConnectStart && call == event.call && inetSocketAddress == event.inetSocketAddress && proxy == event.proxy
|
||||
}
|
||||
|
||||
data class SecureConnectStart(
|
||||
@@ -101,7 +105,8 @@ sealed class CallEvent {
|
||||
override val call: Call,
|
||||
val handshake: Handshake?
|
||||
) : CallEvent() {
|
||||
override fun closes(timestampNs: Long): CallEvent = SecureConnectStart(timestampNs, call)
|
||||
override fun closes(event: CallEvent): Boolean =
|
||||
event is SecureConnectStart && call == event.call
|
||||
}
|
||||
|
||||
data class ConnectionAcquired(
|
||||
@@ -115,7 +120,8 @@ sealed class CallEvent {
|
||||
override val call: Call,
|
||||
val connection: Connection
|
||||
) : CallEvent() {
|
||||
override fun closes(timestampNs: Long): CallEvent = ConnectionAcquired(timestampNs, call, connection)
|
||||
override fun closes(event: CallEvent): Boolean =
|
||||
event is ConnectionAcquired && call == event.call && connection == event.connection
|
||||
}
|
||||
|
||||
data class CallStart(
|
||||
@@ -127,14 +133,18 @@ sealed class CallEvent {
|
||||
override val timestampNs: Long,
|
||||
override val call: Call
|
||||
) : CallEvent() {
|
||||
override fun closes(timestampNs: Long): CallEvent = CallStart(timestampNs, call)
|
||||
override fun closes(event: CallEvent): Boolean =
|
||||
event is CallStart && call == event.call
|
||||
}
|
||||
|
||||
data class CallFailed(
|
||||
override val timestampNs: Long,
|
||||
override val call: Call,
|
||||
val ioe: IOException
|
||||
) : CallEvent()
|
||||
) : CallEvent() {
|
||||
override fun closes(event: CallEvent): Boolean =
|
||||
event is CallStart && call == event.call
|
||||
}
|
||||
|
||||
data class Canceled(
|
||||
override val timestampNs: Long,
|
||||
@@ -151,7 +161,8 @@ sealed class CallEvent {
|
||||
override val call: Call,
|
||||
val headerLength: Long
|
||||
) : CallEvent() {
|
||||
override fun closes(timestampNs: Long): CallEvent = RequestHeadersStart(timestampNs, call)
|
||||
override fun closes(event: CallEvent): Boolean =
|
||||
event is RequestHeadersStart && call == event.call
|
||||
}
|
||||
|
||||
data class RequestBodyStart(
|
||||
@@ -164,14 +175,19 @@ sealed class CallEvent {
|
||||
override val call: Call,
|
||||
val bytesWritten: Long
|
||||
) : CallEvent() {
|
||||
override fun closes(timestampNs: Long): CallEvent = RequestBodyStart(timestampNs, call)
|
||||
override fun closes(event: CallEvent): Boolean =
|
||||
event is RequestBodyStart && call == event.call
|
||||
}
|
||||
|
||||
data class RequestFailed(
|
||||
override val timestampNs: Long,
|
||||
override val call: Call,
|
||||
val ioe: IOException
|
||||
) : CallEvent()
|
||||
) : CallEvent() {
|
||||
|
||||
override fun closes(event: CallEvent): Boolean =
|
||||
event is RequestHeadersStart && call == event.call
|
||||
}
|
||||
|
||||
data class ResponseHeadersStart(
|
||||
override val timestampNs: Long,
|
||||
@@ -183,7 +199,8 @@ sealed class CallEvent {
|
||||
override val call: Call,
|
||||
val headerLength: Long
|
||||
) : CallEvent() {
|
||||
override fun closes(timestampNs: Long): CallEvent = RequestHeadersStart(timestampNs, call)
|
||||
override fun closes(event: CallEvent): Boolean =
|
||||
event is ResponseHeadersStart && call == event.call
|
||||
}
|
||||
|
||||
data class ResponseBodyStart(
|
||||
@@ -196,7 +213,8 @@ sealed class CallEvent {
|
||||
override val call: Call,
|
||||
val bytesRead: Long
|
||||
) : CallEvent() {
|
||||
override fun closes(timestampNs: Long): CallEvent = ResponseBodyStart(timestampNs, call)
|
||||
override fun closes(event: CallEvent): Boolean =
|
||||
event is ResponseBodyStart && call == event.call
|
||||
}
|
||||
|
||||
data class ResponseFailed(
|
||||
|
@@ -16,16 +16,17 @@
|
||||
package okhttp3
|
||||
|
||||
import java.io.IOException
|
||||
import java.net.InetAddress
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.Proxy
|
||||
import okhttp3.internal.SuppressSignatureCheck
|
||||
|
||||
/** Data classes that correspond to each of the methods of [ConnectionListener]. */
|
||||
@SuppressSignatureCheck
|
||||
sealed class ConnectionEvent {
|
||||
abstract val timestampNs: Long
|
||||
open val connection: Connection? = null
|
||||
open val connection: Connection?
|
||||
get() = null
|
||||
|
||||
/** Returns if the event closes this event, or null if this is no open event. */
|
||||
open fun closes(event: ConnectionEvent): Boolean? = null
|
||||
|
||||
val name: String
|
||||
get() = javaClass.simpleName
|
||||
@@ -41,12 +42,20 @@ sealed class ConnectionEvent {
|
||||
val route: Route,
|
||||
val call: Call,
|
||||
val exception: IOException
|
||||
) : ConnectionEvent()
|
||||
) : ConnectionEvent() {
|
||||
override fun closes(event: ConnectionEvent): Boolean =
|
||||
event is ConnectStart && call == event.call && route == event.route
|
||||
}
|
||||
|
||||
data class ConnectEnd(
|
||||
override val timestampNs: Long,
|
||||
override val connection: Connection,
|
||||
) : ConnectionEvent()
|
||||
val route: Route,
|
||||
val call: Call,
|
||||
) : ConnectionEvent() {
|
||||
override fun closes(event: ConnectionEvent): Boolean =
|
||||
event is ConnectStart && call == event.call && route == event.route
|
||||
}
|
||||
|
||||
data class ConnectionClosed(
|
||||
override val timestampNs: Long,
|
||||
@@ -63,7 +72,11 @@ sealed class ConnectionEvent {
|
||||
override val timestampNs: Long,
|
||||
override val connection: Connection,
|
||||
val call: Call
|
||||
) : ConnectionEvent()
|
||||
) : ConnectionEvent() {
|
||||
|
||||
override fun closes(event: ConnectionEvent): Boolean =
|
||||
event is ConnectionAcquired && connection == event.connection && call == event.call
|
||||
}
|
||||
|
||||
data class NoNewExchanges(
|
||||
override val timestampNs: Long,
|
||||
|
@@ -173,7 +173,9 @@ class OkHttpClientTestRule : BeforeEachCallback, AfterEachCallback {
|
||||
// a test timeout failure.
|
||||
val waitTime = (entryTime + 1_000_000_000L - System.nanoTime())
|
||||
if (!queue.idleLatch().await(waitTime, TimeUnit.NANOSECONDS)) {
|
||||
TaskRunner.INSTANCE.cancelAll()
|
||||
synchronized (TaskRunner.INSTANCE) {
|
||||
TaskRunner.INSTANCE.cancelAll()
|
||||
}
|
||||
fail<Unit>("Queue still active after 1000 ms")
|
||||
}
|
||||
}
|
||||
|
@@ -19,12 +19,20 @@ import java.util.Deque
|
||||
import java.util.concurrent.ConcurrentLinkedDeque
|
||||
import java.util.concurrent.TimeUnit
|
||||
import okhttp3.ConnectionEvent.NoNewExchanges
|
||||
import okhttp3.internal.connection.RealCall
|
||||
import okhttp3.internal.connection.RealConnection
|
||||
import okio.IOException
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.data.Offset
|
||||
import org.junit.jupiter.api.Assertions
|
||||
|
||||
open class RecordingConnectionListener : ConnectionListener() {
|
||||
open class RecordingConnectionListener(
|
||||
/**
|
||||
* An override to ignore the normal order that is enforced.
|
||||
* EventListeners added by Interceptors will not see all events.
|
||||
*/
|
||||
private val enforceOrder: Boolean = true
|
||||
) : ConnectionListener() {
|
||||
val eventSequence: Deque<ConnectionEvent> = ConcurrentLinkedDeque()
|
||||
|
||||
private val forbiddenLocks = mutableSetOf<Any>()
|
||||
@@ -41,7 +49,7 @@ open class RecordingConnectionListener : ConnectionListener() {
|
||||
* Removes recorded events up to (and including) an event is found whose class equals [eventClass]
|
||||
* and returns it.
|
||||
*/
|
||||
fun <T> removeUpToEvent(eventClass: Class<T>): T {
|
||||
fun <T: ConnectionEvent> removeUpToEvent(eventClass: Class<T>): T {
|
||||
val fullEventSequence = eventSequence.toList()
|
||||
try {
|
||||
while (true) {
|
||||
@@ -106,15 +114,34 @@ open class RecordingConnectionListener : ConnectionListener() {
|
||||
.isFalse()
|
||||
}
|
||||
|
||||
if (enforceOrder) {
|
||||
checkForStartEvent(e)
|
||||
}
|
||||
|
||||
eventSequence.offer(e)
|
||||
}
|
||||
|
||||
private fun checkForStartEvent(e: ConnectionEvent) {
|
||||
if (eventSequence.isEmpty()) {
|
||||
assertThat(e).isInstanceOf(ConnectionEvent.ConnectStart::class.java)
|
||||
} else {
|
||||
eventSequence.forEach loop@ {
|
||||
when (e.closes(it)) {
|
||||
null -> return // no open event
|
||||
true -> return // found open event
|
||||
false -> return@loop // this is not the open event so continue
|
||||
}
|
||||
}
|
||||
Assertions.fail<Any>("event $e without matching start event")
|
||||
}
|
||||
}
|
||||
|
||||
override fun connectStart(route: Route, call: Call) = logEvent(ConnectionEvent.ConnectStart(System.nanoTime(), route, call))
|
||||
|
||||
override fun connectFailed(route: Route, call: Call, failure: IOException) = logEvent(ConnectionEvent.ConnectFailed(System.nanoTime(), route, call, failure))
|
||||
|
||||
override fun connectEnd(connection: Connection) {
|
||||
logEvent(ConnectionEvent.ConnectEnd(System.nanoTime(), connection))
|
||||
override fun connectEnd(connection: Connection, route: Route, call: RealCall) {
|
||||
logEvent(ConnectionEvent.ConnectEnd(System.nanoTime(), connection, route, call))
|
||||
}
|
||||
|
||||
override fun connectionClosed(connection: Connection) = logEvent(ConnectionEvent.ConnectionClosed(System.nanoTime(), connection))
|
||||
|
@@ -54,8 +54,16 @@ import okhttp3.CallEvent.SecureConnectStart
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.data.Offset
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Assertions.fail
|
||||
import org.junit.jupiter.api.fail
|
||||
|
||||
open class RecordingEventListener : EventListener() {
|
||||
open class RecordingEventListener(
|
||||
/**
|
||||
* An override to ignore the normal order that is enforced.
|
||||
* EventListeners added by Interceptors will not see all events.
|
||||
*/
|
||||
private val enforceOrder: Boolean = true
|
||||
) : EventListener() {
|
||||
val eventSequence: Deque<CallEvent> = ConcurrentLinkedDeque()
|
||||
|
||||
private val forbiddenLocks = mutableListOf<Any>()
|
||||
@@ -72,7 +80,7 @@ open class RecordingEventListener : EventListener() {
|
||||
* Removes recorded events up to (and including) an event is found whose class equals [eventClass]
|
||||
* and returns it.
|
||||
*/
|
||||
fun <T> removeUpToEvent(eventClass: Class<T>): T {
|
||||
fun <T : CallEvent> removeUpToEvent(eventClass: Class<T>): T {
|
||||
val fullEventSequence = eventSequence.toList()
|
||||
try {
|
||||
while (true) {
|
||||
@@ -132,14 +140,28 @@ open class RecordingEventListener : EventListener() {
|
||||
.isFalse()
|
||||
}
|
||||
|
||||
val startEvent = e.closes(-1L)
|
||||
if (startEvent != null) {
|
||||
assertTrue(eventSequence.any { it == e.closes(it.timestampNs) })
|
||||
if (enforceOrder) {
|
||||
checkForStartEvent(e)
|
||||
}
|
||||
|
||||
eventSequence.offer(e)
|
||||
}
|
||||
|
||||
private fun checkForStartEvent(e: CallEvent) {
|
||||
if (eventSequence.isEmpty()) {
|
||||
assertThat(e).isInstanceOfAny(CallStart::class.java, Canceled::class.java)
|
||||
} else {
|
||||
eventSequence.forEach loop@ {
|
||||
when (e.closes(it)) {
|
||||
null -> return // no open event
|
||||
true -> return // found open event
|
||||
false -> return@loop // this is not the open event so continue
|
||||
}
|
||||
}
|
||||
fail<Any>("event $e without matching start event")
|
||||
}
|
||||
}
|
||||
|
||||
override fun proxySelectStart(
|
||||
call: Call,
|
||||
url: HttpUrl
|
||||
|
Reference in New Issue
Block a user