mirror of
https://github.com/square/okhttp.git
synced 2025-08-08 23:42:08 +03:00
Output client events after tagged @Flaky tests or failures (#5217)
Collect additional debugging info for know problem tests or after unplanned failures.
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 java.io.IOException
|
||||
import java.net.InetAddress
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.Proxy
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class ClientRuleEventListener(var logger: (String) -> Unit) : EventListener(),
|
||||
EventListener.Factory {
|
||||
private var startNs: Long = 0
|
||||
|
||||
override fun create(call: Call): EventListener = this
|
||||
|
||||
override fun callStart(call: Call) {
|
||||
startNs = System.nanoTime()
|
||||
|
||||
logWithTime("callStart: ${call.request()}")
|
||||
}
|
||||
|
||||
override fun dnsStart(call: Call, domainName: String) {
|
||||
logWithTime("dnsStart: $domainName")
|
||||
}
|
||||
|
||||
override fun dnsEnd(call: Call, domainName: String, inetAddressList: List<InetAddress>) {
|
||||
logWithTime("dnsEnd: $inetAddressList")
|
||||
}
|
||||
|
||||
override fun connectStart(call: Call, inetSocketAddress: InetSocketAddress, proxy: Proxy) {
|
||||
logWithTime("connectStart: $inetSocketAddress $proxy")
|
||||
}
|
||||
|
||||
override fun secureConnectStart(call: Call) {
|
||||
logWithTime("secureConnectStart")
|
||||
}
|
||||
|
||||
override fun secureConnectEnd(call: Call, handshake: Handshake?) {
|
||||
logWithTime("secureConnectEnd: $handshake")
|
||||
}
|
||||
|
||||
override fun connectEnd(
|
||||
call: Call,
|
||||
inetSocketAddress: InetSocketAddress,
|
||||
proxy: Proxy,
|
||||
protocol: Protocol?
|
||||
) {
|
||||
logWithTime("connectEnd: $protocol")
|
||||
}
|
||||
|
||||
override fun connectFailed(
|
||||
call: Call,
|
||||
inetSocketAddress: InetSocketAddress,
|
||||
proxy: Proxy,
|
||||
protocol: Protocol?,
|
||||
ioe: IOException
|
||||
) {
|
||||
logWithTime("connectFailed: $protocol $ioe")
|
||||
}
|
||||
|
||||
override fun connectionAcquired(call: Call, connection: Connection) {
|
||||
logWithTime("connectionAcquired: $connection")
|
||||
}
|
||||
|
||||
override fun connectionReleased(call: Call, connection: Connection) {
|
||||
logWithTime("connectionReleased")
|
||||
}
|
||||
|
||||
override fun requestHeadersStart(call: Call) {
|
||||
logWithTime("requestHeadersStart")
|
||||
}
|
||||
|
||||
override fun requestHeadersEnd(call: Call, request: Request) {
|
||||
logWithTime("requestHeadersEnd")
|
||||
}
|
||||
|
||||
override fun requestBodyStart(call: Call) {
|
||||
logWithTime("requestBodyStart")
|
||||
}
|
||||
|
||||
override fun requestBodyEnd(call: Call, byteCount: Long) {
|
||||
logWithTime("requestBodyEnd: byteCount=$byteCount")
|
||||
}
|
||||
|
||||
override fun requestFailed(call: Call, ioe: IOException) {
|
||||
logWithTime("requestFailed: $ioe")
|
||||
}
|
||||
|
||||
override fun responseHeadersStart(call: Call) {
|
||||
logWithTime("responseHeadersStart")
|
||||
}
|
||||
|
||||
override fun responseHeadersEnd(call: Call, response: Response) {
|
||||
logWithTime("responseHeadersEnd: $response")
|
||||
}
|
||||
|
||||
override fun responseBodyStart(call: Call) {
|
||||
logWithTime("responseBodyStart")
|
||||
}
|
||||
|
||||
override fun responseBodyEnd(call: Call, byteCount: Long) {
|
||||
logWithTime("responseBodyEnd: byteCount=$byteCount")
|
||||
}
|
||||
|
||||
override fun responseFailed(call: Call, ioe: IOException) {
|
||||
logWithTime("responseFailed: $ioe")
|
||||
}
|
||||
|
||||
override fun callEnd(call: Call) {
|
||||
logWithTime("callEnd")
|
||||
}
|
||||
|
||||
override fun callFailed(call: Call, ioe: IOException) {
|
||||
logWithTime("callFailed: $ioe")
|
||||
}
|
||||
|
||||
private fun logWithTime(message: String) {
|
||||
val timeMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs)
|
||||
logger.invoke("[$timeMs ms] $message")
|
||||
}
|
||||
}
|
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package okhttp3
|
||||
|
||||
import okhttp3.testing.Flaky
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.rules.TestRule
|
||||
import org.junit.runner.Description
|
||||
@@ -24,6 +25,7 @@ import java.util.concurrent.ConcurrentLinkedDeque
|
||||
|
||||
/** Apply this rule to tests that need an OkHttpClient instance. */
|
||||
class OkHttpClientTestRule : TestRule {
|
||||
private val clientEventsList = mutableListOf<String>()
|
||||
private var prototype: OkHttpClient? = null
|
||||
|
||||
/**
|
||||
@@ -40,7 +42,13 @@ class OkHttpClientTestRule : TestRule {
|
||||
}
|
||||
|
||||
fun newClientBuilder(): OkHttpClient.Builder {
|
||||
return checkNotNull(prototype) { "don't create clients in test initialization!" }.newBuilder()
|
||||
return checkNotNull(prototype) { "don't create clients in test initialization!" }
|
||||
.newBuilder()
|
||||
.eventListener(ClientRuleEventListener { addEvent(it) })
|
||||
}
|
||||
|
||||
@Synchronized private fun addEvent(it: String) {
|
||||
clientEventsList.add(it)
|
||||
}
|
||||
|
||||
fun ensureAllConnectionsReleased() {
|
||||
@@ -57,6 +65,10 @@ class OkHttpClientTestRule : TestRule {
|
||||
acquireClient()
|
||||
try {
|
||||
base.evaluate()
|
||||
logEventsIfFlaky(description)
|
||||
} catch (t: Throwable) {
|
||||
logEvents()
|
||||
throw t
|
||||
} finally {
|
||||
ensureAllConnectionsReleased()
|
||||
releaseClient()
|
||||
@@ -64,9 +76,7 @@ class OkHttpClientTestRule : TestRule {
|
||||
}
|
||||
|
||||
private fun acquireClient() {
|
||||
prototype = prototypes.poll() ?: OkHttpClient.Builder()
|
||||
.dns(SINGLE_INET_ADDRESS_DNS) // Prevent unexpected fallback addresses.
|
||||
.build()
|
||||
prototype = prototypes.poll() ?: freshClient()
|
||||
}
|
||||
|
||||
private fun releaseClient() {
|
||||
@@ -78,6 +88,26 @@ class OkHttpClientTestRule : TestRule {
|
||||
}
|
||||
}
|
||||
|
||||
private fun logEventsIfFlaky(description: Description) {
|
||||
if (isTestFlaky(description)) {
|
||||
logEvents()
|
||||
}
|
||||
}
|
||||
|
||||
private fun isTestFlaky(description: Description): Boolean {
|
||||
return description.annotations.any { it.annotationClass == Flaky::class } ||
|
||||
description.testClass.annotations.any { it.annotationClass == Flaky::class }
|
||||
}
|
||||
|
||||
@Synchronized private fun logEvents() {
|
||||
// Will be ineffective if test overrides the listener
|
||||
println("Events (${clientEventsList.size})")
|
||||
|
||||
for (e in clientEventsList) {
|
||||
println(e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called if a test is known to be leaky.
|
||||
*/
|
||||
@@ -107,5 +137,11 @@ class OkHttpClientTestRule : TestRule {
|
||||
return listOf(addresses[0])
|
||||
}
|
||||
}
|
||||
|
||||
private fun freshClient(): OkHttpClient {
|
||||
return OkHttpClient.Builder()
|
||||
.dns(SINGLE_INET_ADDRESS_DNS) // Prevent unexpected fallback addresses.
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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.testing
|
||||
|
||||
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
/**
|
||||
* Annotation marking a test as flaky, and requires extra logging and linking against
|
||||
* a known github issue. This does not ignore the failure.
|
||||
*/
|
||||
annotation class Flaky(val issues: Array<String> = [])
|
Reference in New Issue
Block a user