Skip to content

Commit ed7c668

Browse files
BlockHound integration (#1821)
* BlockHound integration for Dispatchers.Default and Dispatchers.IO * Use JNA-based self-attach mechanism in order to workaround a problem when tools.jar is loader by multiple classloaders * publication-validator is renamed to integration-testing; * Each test is now in a separate source set, which allows for more flexibility in their configuration; for example, failing to set `dryRun=true` doesn't prevent tests other than NPM to run, and it is possible to run the tests (and their dependencies) separately. * Add an integration test for coroutine debugger java agent Fixes #1060 Fixes #1031 Co-authored-by: Vsevolod Tolstopyatov <[email protected]>
1 parent e0a6533 commit ed7c668

File tree

21 files changed

+276
-65
lines changed

21 files changed

+276
-65
lines changed

build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ apply from: rootProject.file("gradle/experimental.gradle")
88
def rootModule = "kotlinx.coroutines"
99
def coreModule = "kotlinx-coroutines-core"
1010
// Not applicable for Kotlin plugin
11-
def sourceless = ['kotlinx.coroutines', 'site', 'kotlinx-coroutines-bom', 'publication-validator']
12-
def internal = ['kotlinx.coroutines', 'site', 'benchmarks', 'js-stub', 'stdlib-stubs', 'publication-validator']
11+
def sourceless = ['kotlinx.coroutines', 'site', 'kotlinx-coroutines-bom', 'integration-testing']
12+
def internal = ['kotlinx.coroutines', 'site', 'benchmarks', 'js-stub', 'stdlib-stubs', 'integration-testing']
1313
// Not published
1414
def unpublished = internal + ['example-frontend-js', 'android-unit-tests']
1515

gradle.properties

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ knit_version=0.1.3
1414
html_version=0.6.8
1515
lincheck_version=2.5.3
1616
dokka_version=0.9.16-rdev-2-mpp-hacks
17-
byte_buddy_version=1.9.3
17+
byte_buddy_version=1.10.7
1818
reactor_vesion=3.2.5.RELEASE
1919
reactive_streams_version=1.0.2
2020
rxjava2_version=2.2.8
2121
javafx_version=11.0.2
2222
javafx_plugin_version=0.0.8
2323
binary_compatibility_validator_version=0.2.2
24+
blockhound_version=1.0.2.RELEASE
25+
jna_version=5.5.0
2426

2527
# Android versions
2628
android_version=4.1.1.4

integration-testing/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Integration tests
2+
3+
This is a supplementary subproject of kotlinx.coroutines that provides
4+
integration tests.
5+
6+
The tests are the following:
7+
* `NpmPublicationValidator` tests that version of NPM artifact is correct and that it has neither source nor package dependencies on atomicfu
8+
In order for the test to work, one needs to run gradle with `-PdryRun=true`.
9+
`-PdryRun` affects `npmPublish` so that it only provides a packed publication
10+
and does not in fact attempt to send the build for publication.
11+
* `MavenPublicationValidator` depends on the published artifacts and tests artifacts binary content and absence of atomicfu in the classpath
12+
* `DebugAgentTest` checks that the coroutine debugger can be run as a Java agent.
13+
14+
All the available tests can be run with `integration-testing:test`.

integration-testing/build.gradle

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
apply from: rootProject.file("gradle/compile-jvm.gradle")
6+
7+
repositories {
8+
mavenLocal()
9+
mavenCentral()
10+
}
11+
12+
sourceSets {
13+
npmTest {
14+
kotlin
15+
compileClasspath += sourceSets.test.runtimeClasspath
16+
runtimeClasspath += sourceSets.test.runtimeClasspath
17+
}
18+
mavenTest {
19+
kotlin
20+
compileClasspath += sourceSets.test.runtimeClasspath
21+
runtimeClasspath += sourceSets.test.runtimeClasspath
22+
}
23+
debugAgentTest {
24+
kotlin
25+
compileClasspath += sourceSets.test.runtimeClasspath
26+
runtimeClasspath += sourceSets.test.runtimeClasspath
27+
}
28+
}
29+
30+
task npmTest(type: Test) {
31+
def sourceSet = sourceSets.npmTest
32+
environment "projectRoot", project.rootDir
33+
environment "deployVersion", version
34+
def dryRunNpm = project.properties['dryRun']
35+
def doRun = dryRunNpm == "true" // so that we don't accidentally publish anything, especially before the test
36+
onlyIf { doRun }
37+
if (doRun) { // `onlyIf` only affects execution of the task, not the dependency subtree
38+
dependsOn(project(':').getTasksByName("publishNpm", true))
39+
}
40+
testClassesDirs = sourceSet.output.classesDirs
41+
classpath = sourceSet.runtimeClasspath
42+
}
43+
44+
task mavenTest(type: Test) {
45+
def sourceSet = sourceSets.mavenTest
46+
dependsOn(project(':').getTasksByName("publishToMavenLocal", true))
47+
dependsOn.remove(project(':').getTasksByName("dokka", true))
48+
testClassesDirs = sourceSet.output.classesDirs
49+
classpath = sourceSet.runtimeClasspath
50+
}
51+
52+
task debugAgentTest(type: Test) {
53+
def sourceSet = sourceSets.debugAgentTest
54+
dependsOn(project(':kotlinx-coroutines-debug').shadowJar)
55+
jvmArgs ('-javaagent:' + project(':kotlinx-coroutines-debug').shadowJar.outputs.files.getFiles()[0])
56+
testClassesDirs = sourceSet.output.classesDirs
57+
classpath = sourceSet.runtimeClasspath
58+
}
59+
60+
dependencies {
61+
testCompile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
62+
testCompile 'junit:junit:4.12'
63+
npmTestCompile 'org.apache.commons:commons-compress:1.18'
64+
npmTestCompile 'com.google.code.gson:gson:2.8.5'
65+
mavenTestRuntimeOnly project(':kotlinx-coroutines-core')
66+
mavenTestRuntimeOnly project(':kotlinx-coroutines-android')
67+
debugAgentTestCompile project(':kotlinx-coroutines-core')
68+
debugAgentTestCompile project(':kotlinx-coroutines-debug')
69+
}
70+
71+
compileTestKotlin {
72+
kotlinOptions.jvmTarget = "1.8"
73+
}
74+
75+
test {
76+
dependsOn([npmTest, mavenTest, debugAgentTest])
77+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
import org.junit.*
5+
import kotlinx.coroutines.*
6+
import kotlinx.coroutines.debug.*
7+
import java.io.*
8+
9+
class DebugAgentTest {
10+
11+
@Test
12+
fun agentDumpsCoroutines() = runBlocking {
13+
val baos = ByteArrayOutputStream()
14+
DebugProbes.dumpCoroutines(PrintStream(baos))
15+
// if the agent works, then dumps should contain something,
16+
// at least the fact that this test is running.
17+
Assert.assertTrue(baos.toString().contains("agentDumpsCoroutines"))
18+
}
19+
20+
}
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package kotlinx.coroutines.validator
66

77
import org.junit.*
88
import org.junit.Assert.assertTrue
9-
import java.io.*
109
import java.util.jar.*
1110

1211
class MavenPublicationValidator {

kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -951,3 +951,20 @@ internal class CoroutineScheduler(
951951
TERMINATED
952952
}
953953
}
954+
955+
/**
956+
* Checks if the thread is part of a thread pool that supports coroutines.
957+
* This function is needed for integration with BlockHound.
958+
*/
959+
@Suppress("UNUSED")
960+
@JvmName("isSchedulerWorker")
961+
internal fun isSchedulerWorker(thread: Thread) = thread is CoroutineScheduler.Worker
962+
963+
/**
964+
* Checks if the thread is running a CPU-bound task.
965+
* This function is needed for integration with BlockHound.
966+
*/
967+
@Suppress("UNUSED")
968+
@JvmName("mayNotBlock")
969+
internal fun mayNotBlock(thread: Thread) = thread is CoroutineScheduler.Worker &&
970+
thread.state == CoroutineScheduler.WorkerState.CPU_ACQUIRED

kotlinx-coroutines-debug/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ suspension stacktraces.
1313
Additionally, it is possible to process the list of such coroutines via [DebugProbes.dumpCoroutinesInfo] or dump isolated parts
1414
of coroutines hierarchy referenced by a [Job] or [CoroutineScope] instances using [DebugProbes.printJob] and [DebugProbes.printScope] respectively.
1515

16+
This module also provides an automatic [BlockHound](https://github.com/reactor/BlockHound) integration
17+
that detects when a blocking operation was called in a coroutine context that prohibits it. In order to use it,
18+
please follow the BlockHound [quick start guide](
19+
https://github.com/reactor/BlockHound/blob/1.0.2.RELEASE/docs/quick_start.md).
20+
1621
### Using in your project
1722

1823
Add `kotlinx-coroutines-debug` to your project test dependencies:

kotlinx-coroutines-debug/api/kotlinx-coroutines-debug.api

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ public final class kotlinx/coroutines/debug/CoroutineInfo {
88
public fun toString ()Ljava/lang/String;
99
}
1010

11+
public final class kotlinx/coroutines/debug/CoroutinesBlockHoundIntegration : reactor/blockhound/integration/BlockHoundIntegration {
12+
public fun <init> ()V
13+
public fun applyTo (Lreactor/blockhound/BlockHound$Builder;)V
14+
}
15+
1116
public final class kotlinx/coroutines/debug/DebugProbes {
1217
public static final field INSTANCE Lkotlinx/coroutines/debug/DebugProbes;
1318
public final fun dumpCoroutines (Ljava/io/PrintStream;)V

0 commit comments

Comments
 (0)