Skip to content

Commit 0a3aa7e

Browse files
Throw exception in case of invalid Observations with TestObservationRegistry (#5307)
Logs can be very easily missed in tests. This switches to throwing an exception which will be much more noticeable. See gh-5300
1 parent 4d8cab7 commit 0a3aa7e

File tree

4 files changed

+130
-80
lines changed

4 files changed

+130
-80
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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.observation.tck;
17+
18+
import io.micrometer.observation.Observation;
19+
import io.micrometer.observation.Observation.Context;
20+
21+
/**
22+
* A {@link RuntimeException} that can be thrown when an invalid {@link Observation}
23+
* detected.
24+
*
25+
* @author Jonatan Ivanov
26+
* @since 1.14.0
27+
*/
28+
public class InvalidObservationException extends RuntimeException {
29+
30+
private final Context context;
31+
32+
InvalidObservationException(String message, Context context) {
33+
super(message);
34+
this.context = context;
35+
}
36+
37+
public Context getContext() {
38+
return context;
39+
}
40+
41+
}

micrometer-observation-test/src/main/java/io/micrometer/observation/tck/ObservationValidator.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616
package io.micrometer.observation.tck;
1717

1818
import io.micrometer.common.lang.Nullable;
19-
import io.micrometer.common.util.internal.logging.InternalLogger;
20-
import io.micrometer.common.util.internal.logging.InternalLoggerFactory;
2119
import io.micrometer.observation.Observation.Context;
2220
import io.micrometer.observation.Observation.Event;
2321
import io.micrometer.observation.ObservationHandler;
@@ -35,14 +33,12 @@
3533
*/
3634
class ObservationValidator implements ObservationHandler<Context> {
3735

38-
private static final InternalLogger LOGGER = InternalLoggerFactory.getInstance(ObservationValidator.class);
39-
4036
private final Consumer<ValidationResult> consumer;
4137

4238
private final Predicate<Context> supportsContextPredicate;
4339

4440
ObservationValidator() {
45-
this(validationResult -> LOGGER.warn(validationResult.toString()));
41+
this(ObservationValidator::throwInvalidObservationException);
4642
}
4743

4844
ObservationValidator(Consumer<ValidationResult> consumer) {
@@ -116,6 +112,10 @@ else if (status.isStopped()) {
116112
return status;
117113
}
118114

115+
private static void throwInvalidObservationException(ValidationResult validationResult) {
116+
throw new InvalidObservationException(validationResult.getMessage(), validationResult.getContext());
117+
}
118+
119119
static class ValidationResult {
120120

121121
private final String message;

micrometer-observation-test/src/test/java/io/micrometer/observation/tck/ObservationValidatorTests.java

Lines changed: 83 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,9 @@
1919
import io.micrometer.observation.Observation.Event;
2020
import io.micrometer.observation.Observation.Scope;
2121
import io.micrometer.observation.ObservationRegistry;
22-
import io.micrometer.observation.tck.ObservationValidator.ValidationResult;
23-
import org.junit.jupiter.api.BeforeEach;
2422
import org.junit.jupiter.api.Test;
2523

26-
import java.util.function.Consumer;
27-
28-
import static org.assertj.core.api.Assertions.assertThat;
24+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
2925

3026
/**
3127
* Tests for {@link ObservationValidator}.
@@ -34,109 +30,141 @@
3430
*/
3531
class ObservationValidatorTests {
3632

37-
private TestConsumer testConsumer;
38-
39-
private ObservationRegistry registry;
40-
41-
@BeforeEach
42-
void setUp() {
43-
testConsumer = new TestConsumer();
44-
registry = ObservationRegistry.create();
45-
registry.observationConfig().observationHandler(new ObservationValidator(testConsumer));
46-
}
33+
private final ObservationRegistry registry = TestObservationRegistry.create();
4734

4835
@Test
4936
void doubleStartShouldBeInvalid() {
50-
Observation.start("test", registry).start();
51-
assertThat(testConsumer.toString()).isEqualTo("Invalid start: Observation has already been started");
37+
assertThatThrownBy(() -> Observation.start("test", registry).start())
38+
.isExactlyInstanceOf(InvalidObservationException.class)
39+
.hasNoCause()
40+
.hasMessage("Invalid start: Observation has already been started");
5241
}
5342

5443
@Test
5544
void stopBeforeStartShouldBeInvalid() {
56-
Observation.createNotStarted("test", registry).stop();
57-
assertThat(testConsumer.toString()).isEqualTo("Invalid stop: Observation has not been started yet");
45+
assertThatThrownBy(() -> Observation.createNotStarted("test", registry).stop())
46+
.isExactlyInstanceOf(InvalidObservationException.class)
47+
.hasNoCause()
48+
.hasMessage("Invalid stop: Observation has not been started yet");
5849
}
5950

6051
@Test
6152
void errorBeforeStartShouldBeInvalid() {
62-
Observation.createNotStarted("test", registry).error(new RuntimeException());
63-
assertThat(testConsumer.toString()).isEqualTo("Invalid error signal: Observation has not been started yet");
53+
assertThatThrownBy(() -> Observation.createNotStarted("test", registry).error(new RuntimeException()))
54+
.isExactlyInstanceOf(InvalidObservationException.class)
55+
.hasNoCause()
56+
.hasMessage("Invalid error signal: Observation has not been started yet");
6457
}
6558

6659
@Test
6760
void eventBeforeStartShouldBeInvalid() {
68-
Observation.createNotStarted("test", registry).event(Event.of("test"));
69-
assertThat(testConsumer.toString()).isEqualTo("Invalid event signal: Observation has not been started yet");
61+
assertThatThrownBy(() -> Observation.createNotStarted("test", registry).event(Event.of("test")))
62+
.isExactlyInstanceOf(InvalidObservationException.class)
63+
.hasNoCause()
64+
.hasMessage("Invalid event signal: Observation has not been started yet");
7065
}
7166

7267
@Test
68+
@SuppressWarnings("resource")
7369
void scopeBeforeStartShouldBeInvalid() {
74-
Scope scope = Observation.createNotStarted("test", registry).openScope();
75-
scope.reset();
76-
scope.close();
77-
assertThat(testConsumer.toString()).isEqualTo("Invalid scope opening: Observation has not been started yet\n"
78-
+ "Invalid scope resetting: Observation has not been started yet\n"
79-
+ "Invalid scope closing: Observation has not been started yet");
70+
// Since openScope throws an exception, reset and close can't happen
71+
assertThatThrownBy(() -> Observation.createNotStarted("test", registry).openScope())
72+
.isExactlyInstanceOf(InvalidObservationException.class)
73+
.hasNoCause()
74+
.hasMessage("Invalid scope opening: Observation has not been started yet");
8075
}
8176

8277
@Test
8378
void observeAfterStartShouldBeInvalid() {
84-
Observation.start("test", registry).observe(() -> "");
85-
assertThat(testConsumer.toString()).isEqualTo("Invalid start: Observation has already been started");
79+
assertThatThrownBy(() -> Observation.start("test", registry).observe(() -> ""))
80+
.isExactlyInstanceOf(InvalidObservationException.class)
81+
.hasNoCause()
82+
.hasMessage("Invalid start: Observation has already been started");
8683
}
8784

8885
@Test
8986
void doubleStopShouldBeInvalid() {
90-
Observation observation = Observation.start("test", registry);
91-
observation.stop();
92-
observation.stop();
93-
assertThat(testConsumer.toString()).isEqualTo("Invalid stop: Observation has already been stopped");
87+
assertThatThrownBy(() -> {
88+
Observation observation = Observation.start("test", registry);
89+
observation.stop();
90+
observation.stop();
91+
}).isExactlyInstanceOf(InvalidObservationException.class)
92+
.hasNoCause()
93+
.hasMessage("Invalid stop: Observation has already been stopped");
9494
}
9595

9696
@Test
9797
void errorAfterStopShouldBeInvalid() {
98-
Observation observation = Observation.start("test", registry);
99-
observation.stop();
100-
observation.error(new RuntimeException());
101-
assertThat(testConsumer.toString()).isEqualTo("Invalid error signal: Observation has already been stopped");
98+
assertThatThrownBy(() -> {
99+
Observation observation = Observation.start("test", registry);
100+
observation.stop();
101+
observation.error(new RuntimeException());
102+
}).isExactlyInstanceOf(InvalidObservationException.class)
103+
.hasNoCause()
104+
.hasMessage("Invalid error signal: Observation has already been stopped");
102105
}
103106

104107
@Test
105108
void eventAfterStopShouldBeInvalid() {
106-
Observation observation = Observation.start("test", registry);
107-
observation.stop();
108-
observation.event(Event.of("test"));
109-
assertThat(testConsumer.toString()).isEqualTo("Invalid event signal: Observation has already been stopped");
109+
assertThatThrownBy(() -> {
110+
Observation observation = Observation.start("test", registry);
111+
observation.stop();
112+
observation.event(Event.of("test"));
113+
}).isExactlyInstanceOf(InvalidObservationException.class)
114+
.hasNoCause()
115+
.hasMessage("Invalid event signal: Observation has already been stopped");
110116
}
111117

112118
@Test
113-
void scopeAfterStopShouldBeInvalid() {
114-
Observation observation = Observation.start("test", registry);
115-
observation.stop();
116-
Scope scope = observation.openScope();
117-
scope.reset();
118-
scope.close();
119-
assertThat(testConsumer.toString()).isEqualTo("Invalid scope opening: Observation has already been stopped\n"
120-
+ "Invalid scope resetting: Observation has already been stopped\n"
121-
+ "Invalid scope closing: Observation has already been stopped");
119+
@SuppressWarnings("resource")
120+
void scopeOpenAfterStopShouldBeInvalid() {
121+
assertThatThrownBy(() -> {
122+
Observation observation = Observation.start("test", registry);
123+
observation.stop();
124+
observation.openScope();
125+
}).isExactlyInstanceOf(InvalidObservationException.class)
126+
.hasNoCause()
127+
.hasMessage("Invalid scope opening: Observation has already been stopped");
128+
}
129+
130+
@Test
131+
@SuppressWarnings("resource")
132+
void scopeResetAfterStopShouldBeInvalid() {
133+
assertThatThrownBy(() -> {
134+
Observation observation = Observation.start("test", registry);
135+
Scope scope = observation.openScope();
136+
observation.stop();
137+
scope.reset();
138+
}).isExactlyInstanceOf(InvalidObservationException.class)
139+
.hasNoCause()
140+
.hasMessage("Invalid scope resetting: Observation has already been stopped");
141+
}
142+
143+
@Test
144+
void scopeCloseAfterStopShouldBeInvalid() {
145+
assertThatThrownBy(() -> {
146+
Observation observation = Observation.start("test", registry);
147+
Scope scope = observation.openScope();
148+
observation.stop();
149+
scope.close();
150+
}).isExactlyInstanceOf(InvalidObservationException.class)
151+
.hasNoCause()
152+
.hasMessage("Invalid scope closing: Observation has already been stopped");
122153
}
123154

124155
@Test
125156
void startEventStopShouldBeValid() {
126157
Observation.start("test", registry).event(Event.of("test")).stop();
127-
assertThat(testConsumer.toString()).isEmpty();
128158
}
129159

130160
@Test
131161
void startEventErrorStopShouldBeValid() {
132162
Observation.start("test", registry).event(Event.of("test")).error(new RuntimeException()).stop();
133-
assertThat(testConsumer.toString()).isEmpty();
134163
}
135164

136165
@Test
137166
void startErrorEventStopShouldBeValid() {
138167
Observation.start("test", registry).error(new RuntimeException()).event(Event.of("test")).stop();
139-
assertThat(testConsumer.toString()).isEmpty();
140168
}
141169

142170
@Test
@@ -145,7 +173,6 @@ void startScopeEventStopShouldBeValid() {
145173
observation.openScope().close();
146174
observation.event(Event.of("test"));
147175
observation.stop();
148-
assertThat(testConsumer.toString()).isEmpty();
149176
}
150177

151178
@Test
@@ -156,7 +183,6 @@ void startScopeEventErrorStopShouldBeValid() {
156183
observation.error(new RuntimeException());
157184
scope.close();
158185
observation.stop();
159-
assertThat(testConsumer.toString()).isEmpty();
160186
}
161187

162188
@Test
@@ -167,23 +193,6 @@ void startScopeErrorEventStopShouldBeValid() {
167193
observation.event(Event.of("test"));
168194
scope.close();
169195
observation.stop();
170-
assertThat(testConsumer.toString()).isEmpty();
171-
}
172-
173-
static class TestConsumer implements Consumer<ValidationResult> {
174-
175-
private final StringBuilder stringBuilder = new StringBuilder();
176-
177-
@Override
178-
public void accept(ValidationResult validationResult) {
179-
stringBuilder.append(validationResult.getMessage()).append("\n");
180-
}
181-
182-
@Override
183-
public String toString() {
184-
return stringBuilder.toString().trim();
185-
}
186-
187196
}
188197

189198
}

micrometer-observation-test/src/test/java/io/micrometer/observation/tck/TestObservationRegistryAssertTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ void should_not_break_on_multiple_threads() {
6464

6565
@Test
6666
void should_fail_when_observation_not_started() {
67-
Observation.createNotStarted("foo", registry).stop();
67+
Observation.createNotStarted("foo", registry);
6868

6969
thenThrownBy(
7070
() -> TestObservationRegistryAssert.assertThat(registry).hasSingleObservationThat().hasBeenStarted())

0 commit comments

Comments
 (0)