Skip to content

Commit 1678881

Browse files
committed
Apply changes to and/KeyValues, add tests
1 parent e0329c5 commit 1678881

File tree

6 files changed

+155
-87
lines changed

6 files changed

+155
-87
lines changed

micrometer-commons/src/main/java/io/micrometer/common/KeyValues.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,13 +133,15 @@ public <E> KeyValues and(@Nullable Iterable<E> elements, Function<E, String> key
133133
* @return a new {@code KeyValues} instance
134134
*/
135135
public KeyValues and(@Nullable Iterable<? extends KeyValue> keyValues) {
136-
if (keyValues == null || keyValues == EMPTY || !keyValues.iterator().hasNext()) {
136+
if (keyValues == null || keyValues == EMPTY) {
137137
return this;
138138
}
139-
140-
if (this.keyValues.length == 0) {
139+
else if (this.keyValues.length == 0) {
141140
return KeyValues.of(keyValues);
142141
}
142+
else if (!keyValues.iterator().hasNext()) {
143+
return this;
144+
}
143145

144146
return and(KeyValues.of(keyValues).keyValues);
145147
}
@@ -258,12 +260,15 @@ public static <E> KeyValues of(@Nullable Iterable<E> elements, Function<E, Strin
258260
* @return a new {@code KeyValues} instance
259261
*/
260262
public static KeyValues of(@Nullable Iterable<? extends KeyValue> keyValues) {
261-
if (keyValues == null || keyValues == EMPTY || !keyValues.iterator().hasNext()) {
263+
if (keyValues == null || keyValues == EMPTY) {
262264
return KeyValues.empty();
263265
}
264266
else if (keyValues instanceof KeyValues) {
265267
return (KeyValues) keyValues;
266268
}
269+
else if (!keyValues.iterator().hasNext()) {
270+
return KeyValues.empty();
271+
}
267272
else if (keyValues instanceof Collection) {
268273
Collection<? extends KeyValue> keyValuesCollection = (Collection<? extends KeyValue>) keyValues;
269274
return new KeyValues(keyValuesCollection.toArray(new KeyValue[0]));
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
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.common;
17+
18+
import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
19+
import org.junit.jupiter.params.ParameterizedTest;
20+
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
24+
@Retention(RetentionPolicy.RUNTIME)
25+
@ParameterizedTest
26+
@DisabledIfSystemProperty(named = "java.vm.name", matches = AllocationTest.JAVA_VM_NAME_J9_REGEX,
27+
disabledReason = "Sun ThreadMXBean with allocation counter not available")
28+
public @interface AllocationTest {
29+
30+
// Should match "Eclipse OpenJ9 VM" and "IBM J9 VM"
31+
String JAVA_VM_NAME_J9_REGEX = ".*J9 VM$";
32+
33+
}

micrometer-commons/src/test/java/io/micrometer/common/KeyValuesTest.java

Lines changed: 33 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,8 @@
1818

1919
import com.sun.management.ThreadMXBean;
2020
import org.junit.jupiter.api.Test;
21-
import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
22-
import org.junit.jupiter.api.condition.EnabledForJreRange;
23-
import org.junit.jupiter.api.condition.EnabledIf;
24-
import org.junit.jupiter.api.condition.JRE;
21+
import org.junit.jupiter.params.ParameterizedTest;
22+
import org.junit.jupiter.params.provider.MethodSource;
2523

2624
import java.lang.management.ManagementFactory;
2725
import java.util.*;
@@ -40,9 +38,6 @@
4038
*/
4139
class KeyValuesTest {
4240

43-
// Should match "Eclipse OpenJ9 VM" and "IBM J9 VM"
44-
private static final String JAVA_VM_NAME_J9_REGEX = ".*J9 VM$";
45-
4641
@Test
4742
void dedup() {
4843
assertThat(KeyValues.of("k1", "v1", "k2", "v2")).containsExactly(KeyValue.of("k1", "v1"),
@@ -341,72 +336,56 @@ void emptyShouldNotContainKeyValues() {
341336
}
342337

343338
// gh-3313
344-
@Test
345-
@DisabledIfSystemProperty(named = "java.vm.name", matches = JAVA_VM_NAME_J9_REGEX,
346-
disabledReason = "Sun ThreadMXBean with allocation counter not available")
347-
@EnabledForJreRange(max = JRE.JAVA_18)
348-
void andEmptyDoesNotAllocate() {
349-
andEmptyDoesNotAllocate(0);
350-
}
339+
@AllocationTest
340+
@MethodSource("emptyNull_noAllocationArgs")
341+
void anyKeyValuesAnd_noAllocation(Iterable<KeyValue> arg) {
342+
KeyValues keyValues = KeyValues.of("a", "b");
351343

352-
// gh-3313
353-
// See https://github.com/micrometer-metrics/micrometer/issues/3436
354-
@Test
355-
@DisabledIfSystemProperty(named = "java.vm.name", matches = JAVA_VM_NAME_J9_REGEX,
356-
disabledReason = "Sun ThreadMXBean with allocation counter not available")
357-
@EnabledIf("java19")
358-
void andEmptyDoesNotAllocateOnJava19() {
359-
andEmptyDoesNotAllocate(16);
344+
assertNoAllocations(() -> keyValues.and(arg));
360345
}
361346

362-
static boolean java19() {
363-
return "19".equals(System.getProperty("java.version"));
364-
}
347+
@ParameterizedTest
348+
@MethodSource("emptyNull_noAllocationArgs")
349+
void anyKeyValuesAnd_sameAsThis(Iterable<KeyValue> arg) {
350+
KeyValues tags = KeyValues.of("a", "b");
351+
KeyValues combined = tags.and(arg);
365352

366-
private void andEmptyDoesNotAllocate(int expectedAllocatedBytes) {
367-
ThreadMXBean threadMXBean = (ThreadMXBean) ManagementFactory.getThreadMXBean();
368-
long currentThreadId = Thread.currentThread().getId();
369-
KeyValues keyValues = KeyValues.of("a", "b");
370-
KeyValues extraKeyValues = KeyValues.empty();
353+
assertThat(combined).isSameAs(tags);
354+
}
371355

372-
long allocatedBytesBefore = threadMXBean.getThreadAllocatedBytes(currentThreadId);
373-
KeyValues combined = keyValues.and(extraKeyValues);
374-
long allocatedBytes = threadMXBean.getThreadAllocatedBytes(currentThreadId) - allocatedBytesBefore;
356+
static Stream<Iterable<KeyValue>> emptyNull_noAllocationArgs() {
357+
// Note, new ArrayList<>() etc will allocate an iterator
358+
return Stream.of(KeyValues.empty(), Collections.emptyList(), null);
359+
}
375360

376-
assertThat(combined).isEqualTo(keyValues);
377-
assertThat(allocatedBytes).isEqualTo(expectedAllocatedBytes);
361+
@AllocationTest
362+
@MethodSource("nonEmptyKeyValues_noAllocationArgs")
363+
@MethodSource("emptyNull_noAllocationArgs")
364+
void emptyAnd_noAllocation(Iterable<KeyValue> arg) {
365+
assertNoAllocations(() -> KeyValues.empty().and(arg));
378366
}
379367

380368
// gh-3313
381-
@Test
382-
@DisabledIfSystemProperty(named = "java.vm.name", matches = JAVA_VM_NAME_J9_REGEX,
383-
disabledReason = "Sun ThreadMXBean with allocation counter not available")
384-
@EnabledForJreRange(max = JRE.JAVA_18)
385-
void ofEmptyDoesNotAllocate() {
386-
ofEmptyDoesNotAllocate(0);
369+
@AllocationTest
370+
@MethodSource("nonEmptyKeyValues_noAllocationArgs")
371+
@MethodSource("emptyNull_noAllocationArgs")
372+
void of_noAllocation(Iterable<KeyValue> arg) {
373+
assertNoAllocations(() -> KeyValues.of(arg));
387374
}
388375

389-
// gh-3313
390-
// See https://github.com/micrometer-metrics/micrometer/issues/3436
391-
@Test
392-
@DisabledIfSystemProperty(named = "java.vm.name", matches = JAVA_VM_NAME_J9_REGEX,
393-
disabledReason = "Sun ThreadMXBean with allocation counter not available")
394-
@EnabledIf("java19")
395-
void ofEmptyDoesNotAllocateOnJava19() {
396-
ofEmptyDoesNotAllocate(16);
376+
static Stream<Iterable<KeyValue>> nonEmptyKeyValues_noAllocationArgs() {
377+
return Stream.of(KeyValues.of("any", "thing"));
397378
}
398379

399-
private void ofEmptyDoesNotAllocate(int expectedAllocatedBytes) {
380+
private void assertNoAllocations(Runnable runnable) {
400381
ThreadMXBean threadMXBean = (ThreadMXBean) ManagementFactory.getThreadMXBean();
401382
long currentThreadId = Thread.currentThread().getId();
402-
KeyValues extraKeyValues = KeyValues.empty();
403383

404384
long allocatedBytesBefore = threadMXBean.getThreadAllocatedBytes(currentThreadId);
405-
KeyValues of = KeyValues.of(extraKeyValues);
385+
runnable.run();
406386
long allocatedBytes = threadMXBean.getThreadAllocatedBytes(currentThreadId) - allocatedBytesBefore;
407387

408-
assertThat(of).isEqualTo(KeyValues.empty());
409-
assertThat(allocatedBytes).isEqualTo(expectedAllocatedBytes);
388+
assertThat(allocatedBytes).isZero();
410389
}
411390

412391
private void assertKeyValues(KeyValues keyValues, String... expectedKeyValues) {

micrometer-core/src/main/java/io/micrometer/core/instrument/Tags.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,13 +112,15 @@ public Tags and(@Nullable Tag... tags) {
112112
* @return a new {@code Tags} instance
113113
*/
114114
public Tags and(@Nullable Iterable<? extends Tag> tags) {
115-
if (tags == null || tags == EMPTY || !tags.iterator().hasNext()) {
115+
if (tags == null || tags == EMPTY) {
116116
return this;
117117
}
118-
119-
if (this.tags.length == 0) {
118+
else if (this.tags.length == 0) {
120119
return Tags.of(tags);
121120
}
121+
else if (!tags.iterator().hasNext()) {
122+
return this;
123+
}
122124

123125
return and(Tags.of(tags).tags);
124126
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
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.core;
17+
18+
import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
19+
import org.junit.jupiter.params.ParameterizedTest;
20+
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
24+
@Retention(RetentionPolicy.RUNTIME)
25+
@ParameterizedTest
26+
@DisabledIfSystemProperty(named = "java.vm.name", matches = AllocationTest.JAVA_VM_NAME_J9_REGEX,
27+
disabledReason = "Sun ThreadMXBean with allocation counter not available")
28+
public @interface AllocationTest {
29+
30+
// Should match "Eclipse OpenJ9 VM" and "IBM J9 VM"
31+
String JAVA_VM_NAME_J9_REGEX = ".*J9 VM$";
32+
33+
}

micrometer-core/src/test/java/io/micrometer/core/instrument/TagsTest.java

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,17 @@
1616
package io.micrometer.core.instrument;
1717

1818
import com.sun.management.ThreadMXBean;
19+
import io.micrometer.core.AllocationTest;
1920
import io.micrometer.core.Issue;
2021
import org.junit.jupiter.api.Test;
21-
import org.junit.jupiter.api.condition.*;
22+
import org.junit.jupiter.params.ParameterizedTest;
23+
import org.junit.jupiter.params.provider.MethodSource;
2224

2325
import java.lang.management.ManagementFactory;
2426
import java.util.*;
2527
import java.util.stream.Stream;
2628

27-
import static org.assertj.core.api.Assertions.assertThat;
28-
import static org.assertj.core.api.Assertions.assertThatThrownBy;
29+
import static org.assertj.core.api.Assertions.*;
2930

3031
/**
3132
* Tests for {@link Tags}.
@@ -37,9 +38,6 @@
3738
*/
3839
class TagsTest {
3940

40-
// Should match "Eclipse OpenJ9 VM" and "IBM J9 VM"
41-
private static final String JAVA_VM_NAME_J9_REGEX = ".*J9 VM$";
42-
4341
@Test
4442
void dedup() {
4543
assertThat(Tags.of("k1", "v1", "k2", "v2")).containsExactly(Tag.of("k1", "v1"), Tag.of("k2", "v2"));
@@ -334,39 +332,57 @@ void emptyShouldNotContainTags() {
334332
assertThat(Tags.empty().iterator()).isExhausted();
335333
}
336334

337-
@Test
338335
@Issue("#3313")
339-
@DisabledIfSystemProperty(named = "java.vm.name", matches = JAVA_VM_NAME_J9_REGEX,
340-
disabledReason = "Sun ThreadMXBean with allocation counter not available")
341-
void andEmptyDoesNotAllocate() {
342-
ThreadMXBean threadMXBean = (ThreadMXBean) ManagementFactory.getThreadMXBean();
343-
long currentThreadId = Thread.currentThread().getId();
336+
@AllocationTest
337+
@MethodSource("emptyNull_noAllocationArgs")
338+
void anyTagsAnd_noAllocation(Iterable<Tag> arg) {
344339
Tags tags = Tags.of("a", "b");
345-
Tags extraTags = Tags.empty();
346340

347-
long allocatedBytesBefore = threadMXBean.getThreadAllocatedBytes(currentThreadId);
348-
Tags combined = tags.and(extraTags);
349-
long allocatedBytes = threadMXBean.getThreadAllocatedBytes(currentThreadId) - allocatedBytesBefore;
341+
assertNoAllocations(() -> tags.and(arg));
342+
}
343+
344+
@ParameterizedTest
345+
@MethodSource("emptyNull_noAllocationArgs")
346+
void anyTagsAnd_sameAsThis(Iterable<Tag> arg) {
347+
Tags tags = Tags.of("a", "b");
348+
Tags combined = tags.and(arg);
350349

351-
assertThat(combined).isEqualTo(tags);
352-
assertThat(allocatedBytes).isEqualTo(0);
350+
assertThat(combined).isSameAs(tags);
351+
}
352+
353+
static Stream<Iterable<Tag>> emptyNull_noAllocationArgs() {
354+
// Note, new ArrayList<>() etc will allocate an iterator
355+
return Stream.of(Tags.empty(), Collections.emptyList(), null);
356+
}
357+
358+
@AllocationTest
359+
@MethodSource("nonEmptyTags_noAllocationArgs")
360+
@MethodSource("emptyNull_noAllocationArgs")
361+
void emptyAnd_noAllocation(Iterable<Tag> arg) {
362+
assertNoAllocations(() -> Tags.empty().and(arg));
353363
}
354364

355-
@Test
356365
@Issue("#3313")
357-
@DisabledIfSystemProperty(named = "java.vm.name", matches = JAVA_VM_NAME_J9_REGEX,
358-
disabledReason = "Sun ThreadMXBean with allocation counter not available")
359-
void ofEmptyDoesNotAllocate() {
366+
@AllocationTest
367+
@MethodSource("nonEmptyTags_noAllocationArgs")
368+
@MethodSource("emptyNull_noAllocationArgs")
369+
void of_noAllocation(Iterable<Tag> arg) {
370+
assertNoAllocations(() -> Tags.of(arg));
371+
}
372+
373+
static Stream<Iterable<Tag>> nonEmptyTags_noAllocationArgs() {
374+
return Stream.of(Tags.of("any", "thing"));
375+
}
376+
377+
private void assertNoAllocations(Runnable runnable) {
360378
ThreadMXBean threadMXBean = (ThreadMXBean) ManagementFactory.getThreadMXBean();
361379
long currentThreadId = Thread.currentThread().getId();
362-
Tags extraTags = Tags.empty();
363380

364381
long allocatedBytesBefore = threadMXBean.getThreadAllocatedBytes(currentThreadId);
365-
Tags of = Tags.of(extraTags);
382+
runnable.run();
366383
long allocatedBytes = threadMXBean.getThreadAllocatedBytes(currentThreadId) - allocatedBytesBefore;
367384

368-
assertThat(of).isEqualTo(Tags.empty());
369-
assertThat(allocatedBytes).isEqualTo(0);
385+
assertThat(allocatedBytes).isZero();
370386
}
371387

372388
private void assertTags(Tags tags, String... keyValues) {

0 commit comments

Comments
 (0)