Skip to content

Commit 11f1016

Browse files
Separate tests that use reflection for java.lang
1 parent ffb902a commit 11f1016

File tree

3 files changed

+132
-64
lines changed

3 files changed

+132
-64
lines changed

micrometer-java21/build.gradle

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,18 @@ tasks.withType(JavaCompile).configureEach {
2323
options.release = 21
2424
}
2525

26-
test {
27-
// This hack is needed since VirtualThreadMetricsTests utilizes reflection against java.lang, see its javadoc
26+
task reflectiveTests(type: Test) {
27+
useJUnitPlatform {
28+
includeTags 'reflective'
29+
}
30+
31+
// This hack is needed since VirtualThreadMetricsReflectiveTests utilizes reflection against java.lang, see its javadoc
2832
jvmArgs += ['--add-opens', 'java.base/java.lang=ALL-UNNAMED']
2933
}
34+
35+
test {
36+
dependsOn reflectiveTests
37+
useJUnitPlatform {
38+
excludeTags 'reflective'
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
* Copyright 2024 VMware, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micrometer.java21.instrument.binder.jdk;
17+
18+
import io.micrometer.core.instrument.Counter;
19+
import io.micrometer.core.instrument.Tags;
20+
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
21+
import org.junit.jupiter.api.AfterEach;
22+
import org.junit.jupiter.api.BeforeEach;
23+
import org.junit.jupiter.api.Tag;
24+
import org.junit.jupiter.api.Test;
25+
26+
import java.lang.reflect.Constructor;
27+
import java.time.Duration;
28+
import java.util.concurrent.*;
29+
import java.util.concurrent.locks.LockSupport;
30+
31+
import static java.lang.Thread.State.WAITING;
32+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
33+
import static org.awaitility.Awaitility.await;
34+
35+
/**
36+
* Tests for {@link VirtualThreadMetrics}. If you run these tests from your IDE,
37+
* {@link #submitFailedEventsShouldBeRecorded()} might fail depending on your setup. This
38+
* is because the test (through {@link #virtualThreadFactoryFor(Executor)}) utilizes
39+
* reflection against the {@code java.lang} package which needs to be explicitly enabled.
40+
* If you run into such an issue you can either change your setup and let your IDE run the
41+
* tests utilizing the build system (Gradle) or add the following JVM arg to your test
42+
* config: {@code --add-opens java.base/java.lang=ALL-UNNAMED}
43+
*
44+
* @author Artyom Gabeev
45+
* @author Jonatan Ivanov
46+
*/
47+
@Tag("reflective")
48+
class VirtualThreadMetricsReflectiveTests {
49+
50+
private static final Tags TAGS = Tags.of("k", "v");
51+
52+
private SimpleMeterRegistry registry;
53+
54+
private VirtualThreadMetrics virtualThreadMetrics;
55+
56+
@BeforeEach
57+
void setUp() {
58+
registry = new SimpleMeterRegistry();
59+
virtualThreadMetrics = new VirtualThreadMetrics(TAGS);
60+
virtualThreadMetrics.bindTo(registry);
61+
}
62+
63+
@AfterEach
64+
void tearDown() {
65+
virtualThreadMetrics.close();
66+
}
67+
68+
/**
69+
* Uses a similar approach as the JDK tests to make starting or unparking a virtual
70+
* thread fail, see {@link #virtualThreadFactoryFor(Executor)} and <a href=
71+
* "https://github.com/openjdk/jdk/blob/fdfe503d016086cf78b5a8c27dbe45f0261c68ab/test/jdk/java/lang/Thread/virtual/JfrEvents.java#L143-L187">JfrEvents.java</a>
72+
*/
73+
@Test
74+
void submitFailedEventsShouldBeRecorded() {
75+
try (ExecutorService cachedPool = Executors.newCachedThreadPool()) {
76+
ThreadFactory factory = virtualThreadFactoryFor(cachedPool);
77+
Thread thread = factory.newThread(LockSupport::park);
78+
thread.start();
79+
80+
await().atMost(Duration.ofSeconds(2)).until(() -> thread.getState() == WAITING);
81+
cachedPool.shutdown();
82+
83+
// unpark, the pool was shut down, this should fail
84+
assertThatThrownBy(() -> LockSupport.unpark(thread)).isInstanceOf(RejectedExecutionException.class);
85+
86+
Counter counter = registry.get("jvm.threads.virtual.submit.failed").tags(TAGS).counter();
87+
await().atMost(Duration.ofSeconds(2)).until(() -> counter.count() == 1);
88+
89+
// park, the pool was shut down, this should fail
90+
assertThatThrownBy(() -> factory.newThread(LockSupport::park).start())
91+
.isInstanceOf(RejectedExecutionException.class);
92+
await().atMost(Duration.ofSeconds(2)).until(() -> counter.count() == 2);
93+
}
94+
}
95+
96+
/**
97+
* Creates a {@link ThreadFactory} for virtual threads. The created virtual threads
98+
* will be bound to the provided platform thread pool instead of a default
99+
* ForkJoinPool. At its current form, this is a hack, it utilizes reflection to supply
100+
* the platform thread pool. It seems though there is no other way of doing this, the
101+
* JDK tests are also utilizing reflection to do the same, see: <a href=
102+
* "https://github.com/openjdk/jdk/blob/fdfe503d016086cf78b5a8c27dbe45f0261c68ab/test/lib/jdk/test/lib/thread/VThreadScheduler.java#L71-L90">VThreadScheduler.java</a>
103+
* @param pool platform pool
104+
* @return virtual thread factory bound to the provided platform pool
105+
*/
106+
private static ThreadFactory virtualThreadFactoryFor(Executor pool) {
107+
try {
108+
Class<?> clazz = Class.forName("java.lang.ThreadBuilders$VirtualThreadBuilder");
109+
Constructor<?> constructor = clazz.getDeclaredConstructor(Executor.class);
110+
constructor.setAccessible(true);
111+
return ((Thread.Builder.OfVirtual) constructor.newInstance(pool)).factory();
112+
}
113+
catch (Exception e) {
114+
throw new RuntimeException(e);
115+
}
116+
}
117+
118+
}

micrometer-java21/src/test/java/io/micrometer/java21/instrument/binder/jdk/VirtualThreadMetricsTests.java

Lines changed: 1 addition & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -15,35 +15,24 @@
1515
*/
1616
package io.micrometer.java21.instrument.binder.jdk;
1717

18-
import io.micrometer.core.instrument.Counter;
1918
import io.micrometer.core.instrument.Tags;
2019
import io.micrometer.core.instrument.Timer;
2120
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
2221
import org.junit.jupiter.api.AfterEach;
2322
import org.junit.jupiter.api.BeforeEach;
2423
import org.junit.jupiter.api.Test;
2524

26-
import java.lang.reflect.Constructor;
2725
import java.time.Duration;
2826
import java.util.ArrayList;
2927
import java.util.List;
3028
import java.util.concurrent.*;
31-
import java.util.concurrent.locks.LockSupport;
3229

33-
import static java.lang.Thread.State.WAITING;
3430
import static java.util.concurrent.TimeUnit.MILLISECONDS;
3531
import static org.assertj.core.api.Assertions.assertThat;
36-
import static org.assertj.core.api.Assertions.assertThatThrownBy;
3732
import static org.awaitility.Awaitility.await;
3833

3934
/**
40-
* Tests for {@link VirtualThreadMetrics}. If you run these tests from your IDE,
41-
* {@link #submitFailedEventsShouldBeRecorded()} might fail depending on your setup. This
42-
* is because the test (through {@link #virtualThreadFactoryFor(Executor)}) utilizes
43-
* reflection against the java.lang package which needs to be explicitly enabled. If you
44-
* run into such an issue you can either: - Change your setup and let your IDE run the
45-
* tests utilizing the build system (Gradle) - Add the following JVM arg to your test
46-
* config: {@code --add-opens java.base/java.lang=ALL-UNNAMED}
35+
* Tests for {@link VirtualThreadMetrics}.
4736
*
4837
* @author Artyom Gabeev
4938
* @author Jonatan Ivanov
@@ -89,34 +78,6 @@ void pinnedEventsShouldBeRecorded() {
8978
}
9079
}
9180

92-
/**
93-
* Uses a similar approach as the JDK tests to make starting or unparking a virtual
94-
* thread fail, see {@link #virtualThreadFactoryFor(Executor)} and
95-
* https://github.com/openjdk/jdk/blob/fdfe503d016086cf78b5a8c27dbe45f0261c68ab/test/jdk/java/lang/Thread/virtual/JfrEvents.java#L143-L187
96-
*/
97-
@Test
98-
void submitFailedEventsShouldBeRecorded() {
99-
try (ExecutorService cachedPool = Executors.newCachedThreadPool()) {
100-
ThreadFactory factory = virtualThreadFactoryFor(cachedPool);
101-
Thread thread = factory.newThread(LockSupport::park);
102-
thread.start();
103-
104-
await().atMost(Duration.ofSeconds(2)).until(() -> thread.getState() == WAITING);
105-
cachedPool.shutdown();
106-
107-
// unpark, the pool was shut down, this should fail
108-
assertThatThrownBy(() -> LockSupport.unpark(thread)).isInstanceOf(RejectedExecutionException.class);
109-
110-
Counter counter = registry.get("jvm.threads.virtual.submit.failed").tags(TAGS).counter();
111-
await().atMost(Duration.ofSeconds(2)).until(() -> counter.count() == 1);
112-
113-
// park, the pool was shut down, this should fail
114-
assertThatThrownBy(() -> factory.newThread(LockSupport::park).start())
115-
.isInstanceOf(RejectedExecutionException.class);
116-
await().atMost(Duration.ofSeconds(2)).until(() -> counter.count() == 2);
117-
}
118-
}
119-
12081
private void pinCurrentThreadAndAwait(CountDownLatch latch) {
12182
synchronized (new Object()) { // assumes that synchronized pins the thread
12283
try {
@@ -151,26 +112,4 @@ private void waitFor(Future<?> future) {
151112
}
152113
}
153114

154-
/**
155-
* Creates a {@link ThreadFactory} for virtual threads. The created virtual threads
156-
* will be bound to the provided platform thread pool instead of a default
157-
* ForkJoinPool. At its current form, this is a hack, it utilizes reflection to supply
158-
* the platform thread pool. It seems though there is no other way of doing this, the
159-
* JDK tests are also utilizing reflection to do the same, see:
160-
* https://github.com/openjdk/jdk/blob/fdfe503d016086cf78b5a8c27dbe45f0261c68ab/test/lib/jdk/test/lib/thread/VThreadScheduler.java#L71-L90
161-
* @param pool platform pool
162-
* @return virtual thread factory bound to the provided platform pool
163-
*/
164-
private static ThreadFactory virtualThreadFactoryFor(Executor pool) {
165-
try {
166-
Class<?> clazz = Class.forName("java.lang.ThreadBuilders$VirtualThreadBuilder");
167-
Constructor<?> constructor = clazz.getDeclaredConstructor(Executor.class);
168-
constructor.setAccessible(true);
169-
return ((Thread.Builder.OfVirtual) constructor.newInstance(pool)).factory();
170-
}
171-
catch (Exception e) {
172-
throw new RuntimeException(e);
173-
}
174-
}
175-
176115
}

0 commit comments

Comments
 (0)