Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Test utilities and scenarios for working with scopes #123

Merged
merged 12 commits into from
Jul 6, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
import java.util.Map;

import io.micrometer.context.ContextSnapshot.Scope;
import io.micrometer.context.observation.Observation;
import io.micrometer.context.observation.ObservationScopeThreadLocalHolder;
import io.micrometer.context.observation.ObservationThreadLocalAccessor;
import io.micrometer.scopedvalue.ScopeHolder;
import io.micrometer.scopedvalue.ScopedValue;
import io.micrometer.scopedvalue.ScopedValueThreadLocalAccessor;
import org.assertj.core.api.BDDAssertions;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -287,29 +287,31 @@ void toString_should_include_values() {

@Test
void should_work_with_scope_based_thread_local_accessor() {
this.registry.registerContextAccessor(new TestContextAccessor());
this.registry.registerThreadLocalAccessor(new ObservationThreadLocalAccessor());

String key = ObservationThreadLocalAccessor.KEY;
Observation observation = new Observation();
Map<String, Observation> sourceContext = Collections.singletonMap(key, observation);

then(ObservationScopeThreadLocalHolder.getCurrentObservation()).isNull();
try (Scope scope1 = ContextSnapshot.setAllThreadLocalsFrom(sourceContext, this.registry)) {
then(ObservationScopeThreadLocalHolder.getCurrentObservation()).isSameAs(observation);
try (Scope scope2 = ContextSnapshot.setAllThreadLocalsFrom(Collections.emptyMap(), this.registry)) {
then(ObservationScopeThreadLocalHolder.getCurrentObservation()).isSameAs(observation);
// TODO: This should work like this in the future
// then(ObservationScopeThreadLocalHolder.getCurrentObservation()).as("We're
// resetting the observation").isNull();
// then(ObservationScopeThreadLocalHolder.getValue()).as("This is the
// 'null' scope").isNotNull();
TestContextAccessor accessor = new TestContextAccessor();
this.registry.registerContextAccessor(accessor);
this.registry.registerThreadLocalAccessor(new ScopedValueThreadLocalAccessor());

ScopedValue value = ScopedValue.create("value");

assertThat(ScopeHolder.currentValue()).isNull();

Map<String, ScopedValue> sourceContext = Collections.singletonMap(ScopedValueThreadLocalAccessor.KEY, value);

try (ContextSnapshot.Scope outer = ContextSnapshot.setAllThreadLocalsFrom(sourceContext, this.registry)) {
assertThat(ScopeHolder.currentValue()).isEqualTo(value);
try (ContextSnapshot.Scope inner = ContextSnapshot.setAllThreadLocalsFrom(Collections.emptyMap(),
this.registry)) {
assertThat(ScopeHolder.currentValue()).isEqualTo(value);
// The new API allows the following to happen when clearing behavior is
// specified on ContextSnapshotFactory
// assertThat(ScopeHolder.currentValue().get()).isNull();
}
then(ObservationScopeThreadLocalHolder.getCurrentObservation()).as("We're back to previous observation")
.isSameAs(observation);
assertThat(ScopeHolder.currentValue()).isEqualTo(value);
}
then(ObservationScopeThreadLocalHolder.getCurrentObservation()).as("There was no observation at the beginning")
.isNull();
assertThat(ScopeHolder.currentValue()).isNull();

this.registry.removeContextAccessor(accessor);
this.registry.removeThreadLocalAccessor(ScopedValueThreadLocalAccessor.KEY);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@
import java.util.Map;

import io.micrometer.context.ContextSnapshot.Scope;
import io.micrometer.context.observation.Observation;
import io.micrometer.context.observation.ObservationScopeThreadLocalHolder;
import io.micrometer.context.observation.ObservationThreadLocalAccessor;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
Expand All @@ -36,16 +33,12 @@
* Unit tests for {@link DefaultContextSnapshot}.
*
* @author Rossen Stoyanchev
* @author Dariusz Jędrzejczyk
*/
public class DefaultContextSnapshotTests {

private final ContextRegistry registry = new ContextRegistry();

private final ContextSnapshotFactory snapshotFactory = ContextSnapshotFactory.builder()
.contextRegistry(registry)
.clearMissing(false)
.build();

@ParameterizedTest(name = "clearMissing={0}")
@ValueSource(booleans = { true, false })
void should_propagate_thread_local(boolean clearMissing) {
Expand Down Expand Up @@ -347,32 +340,6 @@ void should_clear_missing_thread_local() {
assertThat(fooThreadLocal.get()).isEqualTo("present");
}

@Test
void should_work_with_scope_based_thread_local_accessor() {
registry.registerContextAccessor(new TestContextAccessor());
registry.registerThreadLocalAccessor(new ObservationThreadLocalAccessor());

String key = ObservationThreadLocalAccessor.KEY;
Observation observation = new Observation();
Map<String, Observation> sourceContext = Collections.singletonMap(key, observation);

then(ObservationScopeThreadLocalHolder.getCurrentObservation()).isNull();
try (Scope scope1 = snapshotFactory.setThreadLocalsFrom(sourceContext)) {
then(ObservationScopeThreadLocalHolder.getCurrentObservation()).isSameAs(observation);
try (Scope scope2 = snapshotFactory.setThreadLocalsFrom(Collections.emptyMap())) {
then(ObservationScopeThreadLocalHolder.getCurrentObservation())
.as("We're resetting the observation")
.isNull();
then(ObservationScopeThreadLocalHolder.getValue()).as("This is the 'null' scope").isNotNull();
}
then(ObservationScopeThreadLocalHolder.getCurrentObservation()).as("We're back to previous observation")
.isSameAs(observation);
}
then(ObservationScopeThreadLocalHolder.getCurrentObservation())
.as("There was no observation at the beginning")
.isNull();
}

@Test
void should_clear_only_selected_thread_locals_when_filter_in_set() {
ThreadLocal<String> fooThreadLocal = new ThreadLocal<>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/**
* Copyright 2023 the original author or authors.
*
* 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
*
* https://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 io.micrometer.context;

import java.util.Collections;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;

import io.micrometer.scopedvalue.Scope;
import io.micrometer.scopedvalue.ScopedValue;
import io.micrometer.scopedvalue.ScopeHolder;
import io.micrometer.scopedvalue.ScopedValueThreadLocalAccessor;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Tests for {@link ContextSnapshotFactory} when used in scoped scenarios.
*
* @author Dariusz Jędrzejczyk
*/
public class ScopedValueSnapshotTests {

private final ContextRegistry registry = new ContextRegistry();

private final ContextSnapshotFactory snapshotFactory = ContextSnapshotFactory.builder()
.contextRegistry(registry)
.build();

@BeforeEach
void initializeThreadLocalAccessors() {
registry.registerThreadLocalAccessor(new ScopedValueThreadLocalAccessor());
}

@AfterEach
void cleanupThreadLocals() {
ScopeHolder.remove();
registry.removeThreadLocalAccessor(ScopedValueThreadLocalAccessor.KEY);
}

@Test
void scopeWorksInAnotherThreadWhenWrapping() throws Exception {
AtomicReference<ScopedValue> valueInNewThread = new AtomicReference<>();
ScopedValue scopedValue = ScopedValue.create("hello");

assertThat(ScopeHolder.currentValue()).isNull();

try (Scope scope = Scope.open(scopedValue)) {
assertThat(ScopeHolder.currentValue()).isEqualTo(scopedValue);
Runnable wrapped = snapshotFactory.captureAll()
.wrap(() -> valueInNewThread.set(ScopeHolder.currentValue()));
Thread t = new Thread(wrapped);
t.start();
t.join();
}

assertThat(valueInNewThread.get()).isEqualTo(scopedValue);
assertThat(ScopeHolder.currentValue()).isNull();
}

@Test
void nestedScopeWorksInAnotherThreadWhenWrapping() throws Exception {
AtomicReference<ScopedValue> value1InNewThreadBefore = new AtomicReference<>();
AtomicReference<ScopedValue> value1InNewThreadAfter = new AtomicReference<>();
AtomicReference<ScopedValue> value2InNewThread = new AtomicReference<>();

ScopedValue v1 = ScopedValue.create("val1");
ScopedValue v2 = ScopedValue.create("val2");

assertThat(ScopeHolder.currentValue()).isNull();

Thread t;

try (Scope v1Scope = Scope.open(v1)) {
assertThat(ScopeHolder.currentValue()).isEqualTo(v1);
try (Scope v2scope1T1 = Scope.open(v2)) {
assertThat(ScopeHolder.currentValue()).isEqualTo(v2);
try (Scope v2scope2T1 = Scope.open(v2)) {
assertThat(ScopeHolder.currentValue()).isEqualTo(v2);
Runnable runnable = () -> {
value1InNewThreadBefore.set(ScopeHolder.currentValue());
try (Scope v2scopeT2 = Scope.open(v2)) {
value2InNewThread.set(ScopeHolder.currentValue());
}
value1InNewThreadAfter.set(ScopeHolder.currentValue());
};

Runnable wrapped = snapshotFactory.captureAll().wrap(runnable);
t = new Thread(wrapped);
t.start();

assertThat(ScopeHolder.currentValue()).isEqualTo(v2);
assertThat(ScopeHolder.get()).isEqualTo(v2scope2T1);
}
assertThat(ScopeHolder.currentValue()).isEqualTo(v2);
assertThat(ScopeHolder.get()).isEqualTo(v2scope1T1);
}

assertThat(ScopeHolder.currentValue()).isEqualTo(v1);

try (Scope childScope3 = Scope.open(v2)) {
assertThat(ScopeHolder.currentValue()).isEqualTo(v2);
assertThat(ScopeHolder.get()).isEqualTo(childScope3);
}

t.join();
assertThat(ScopeHolder.currentValue()).isEqualTo(v1);
}

assertThat(value1InNewThreadBefore.get()).isEqualTo(v2);
assertThat(value1InNewThreadAfter.get()).isEqualTo(v2);
assertThat(value2InNewThread.get()).isEqualTo(v2);
assertThat(ScopeHolder.currentValue()).isNull();
}

@Test
void shouldProperlyClearInNestedScope() {
TestContextAccessor accessor = new TestContextAccessor();
ContextSnapshotFactory snapshotFactory = ContextSnapshotFactory.builder()
.contextRegistry(registry)
.clearMissing(true)
.build();
registry.registerContextAccessor(accessor);
ScopedValue value = ScopedValue.create("value");

assertThat(ScopeHolder.currentValue()).isNull();

Map<String, ScopedValue> sourceContext = Collections.singletonMap(ScopedValueThreadLocalAccessor.KEY, value);

try (ContextSnapshot.Scope outer = snapshotFactory.setThreadLocalsFrom(sourceContext)) {
assertThat(ScopeHolder.currentValue()).isEqualTo(value);
try (ContextSnapshot.Scope inner = snapshotFactory.setThreadLocalsFrom(Collections.emptyMap())) {
assertThat(ScopeHolder.currentValue().get()).isNull();
}
assertThat(ScopeHolder.currentValue()).isEqualTo(value);
}
assertThat(ScopeHolder.currentValue()).isNull();

registry.removeContextAccessor(accessor);
}

}

This file was deleted.

This file was deleted.

Loading