-
Notifications
You must be signed in to change notification settings - Fork 829
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
194 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
192 changes: 192 additions & 0 deletions
192
opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/ProxyingSpanTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.opencensusshim; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.mockito.Mockito.times; | ||
|
||
import io.opentelemetry.api.trace.Span; | ||
import java.lang.reflect.InvocationTargetException; | ||
import java.lang.reflect.Method; | ||
import java.lang.reflect.Modifier; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.Stream; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.params.ParameterizedTest; | ||
import org.junit.jupiter.params.provider.Arguments; | ||
import org.junit.jupiter.params.provider.MethodSource; | ||
import org.mockito.Mockito; | ||
import org.mockito.verification.VerificationMode; | ||
|
||
class ProxyingSpanTest { | ||
|
||
/* | ||
Verifies all methods on the otel Span interface are under test. | ||
Each case is enumerated in proxyMethodsProvider() to avoid false-positives (bad reflection) and maximize | ||
flexibility for special cases (e.g. getSpanContext() and isRecording()) | ||
*/ | ||
@Test | ||
void verifyAllMethodsAreUnderTest() { | ||
List<Method> methods = | ||
proxyMethodsProvider() | ||
.map( | ||
pm -> { | ||
try { | ||
return getInterfaceMethod( | ||
Span.class, (String) pm.get()[0], (Class<?>[]) pm.get()[1]); | ||
} catch (NoSuchMethodException e) { | ||
throw new RuntimeException(e); | ||
} | ||
}) | ||
.collect(Collectors.toList()); | ||
|
||
assertThat(methods) | ||
.describedAs("all interface methods are being tested") | ||
.containsAll(allInterfaceMethods(Span.class)); | ||
assertThat(allInterfaceMethods(Span.class)) | ||
.describedAs("all tested methods are on the Span interface") | ||
.containsAll(methods); | ||
} | ||
|
||
@ParameterizedTest | ||
@MethodSource("proxyMethodsProvider") | ||
void testit(String name, Class<?>[] params, VerificationMode timesCalled) | ||
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { | ||
Method method = getInterfaceMethod(Span.class, name, params); | ||
Span mockedSpan = Mockito.spy(Span.current()); | ||
OpenTelemetrySpanImpl span = new OpenTelemetrySpanImpl(mockedSpan); | ||
assertProxied(span, mockedSpan, method, timesCalled); | ||
} | ||
|
||
static List<Method> allInterfaceMethods(Class<?> clazz) { | ||
return Arrays.stream(clazz.getDeclaredMethods()) | ||
.filter(m -> Modifier.isPublic(m.getModifiers()) && !Modifier.isStatic(m.getModifiers())) | ||
.collect(Collectors.toList()); | ||
} | ||
|
||
static Stream<Arguments> proxyMethodsProvider() { | ||
// todo fill in remaining methods | ||
return Stream.of( | ||
Arguments.of("end", new Class<?>[] {}, times(1)), | ||
Arguments.of( | ||
"end", new Class<?>[] {long.class, java.util.concurrent.TimeUnit.class}, times(1)), | ||
Arguments.of("end", new Class<?>[] {java.time.Instant.class}, times(1)), | ||
Arguments.of("setAttribute", new Class<?>[] {String.class, String.class}, times(1)), | ||
Arguments.of( | ||
"setAttribute", | ||
new Class<?>[] {io.opentelemetry.api.common.AttributeKey.class, int.class}, | ||
times(1)), | ||
Arguments.of( | ||
"setAttribute", | ||
new Class<?>[] {io.opentelemetry.api.common.AttributeKey.class, Object.class}, | ||
times(1)), | ||
Arguments.of("setAttribute", new Class<?>[] {String.class, long.class}, times(1)), | ||
Arguments.of("setAttribute", new Class<?>[] {String.class, double.class}, times(1)), | ||
Arguments.of("setAttribute", new Class<?>[] {String.class, boolean.class}, times(1)), | ||
Arguments.of( | ||
"recordException", | ||
new Class<?>[] {Throwable.class, io.opentelemetry.api.common.Attributes.class}, | ||
times(1)), | ||
Arguments.of("recordException", new Class<?>[] {Throwable.class}, times(1)), | ||
Arguments.of( | ||
"setAllAttributes", | ||
new Class<?>[] {io.opentelemetry.api.common.Attributes.class}, | ||
times(1)), | ||
Arguments.of("updateName", new Class<?>[] {String.class}, times(1)), | ||
Arguments.of( | ||
"storeInContext", new Class<?>[] {io.opentelemetry.context.Context.class}, times(1)), | ||
Arguments.of("addEvent", new Class<?>[] {String.class, java.time.Instant.class}, times(1)), | ||
Arguments.of( | ||
"addEvent", | ||
new Class<?>[] {String.class, long.class, java.util.concurrent.TimeUnit.class}, | ||
times(1)), | ||
Arguments.of( | ||
"addEvent", | ||
new Class<?>[] { | ||
String.class, io.opentelemetry.api.common.Attributes.class, java.time.Instant.class | ||
}, | ||
times(1)), | ||
Arguments.of("addEvent", new Class<?>[] {String.class}, times(1)), | ||
Arguments.of( | ||
"addEvent", | ||
new Class<?>[] { | ||
String.class, | ||
io.opentelemetry.api.common.Attributes.class, | ||
long.class, | ||
java.util.concurrent.TimeUnit.class | ||
}, | ||
times(1)), | ||
Arguments.of( | ||
"addEvent", | ||
new Class<?>[] {String.class, io.opentelemetry.api.common.Attributes.class}, | ||
times(1)), | ||
Arguments.of( | ||
"setStatus", | ||
new Class<?>[] {io.opentelemetry.api.trace.StatusCode.class, String.class}, | ||
times(1)), | ||
Arguments.of( | ||
"setStatus", new Class<?>[] {io.opentelemetry.api.trace.StatusCode.class}, times(1)), | ||
// | ||
// special cases | ||
// | ||
// called never -- it's shared between OC and Otel Span types and is always true, so returns | ||
// `true` | ||
Arguments.of("isRecording", new Class<?>[] {}, times(0)), | ||
// called twice: once in constructor, then once during proxy | ||
Arguments.of("getSpanContext", new Class<?>[] {}, times(2))); | ||
} | ||
|
||
// gets default values for all cases, as mockito can't mock wrappers or primitives, including | ||
// String | ||
static Object valueLookup(Class<?> clazz) { | ||
if (clazz == int.class || clazz == Integer.class) { | ||
return Integer.valueOf(0).intValue(); | ||
} else if (clazz == long.class || clazz == Long.class) { | ||
return Long.valueOf(0L).longValue(); | ||
} else if (clazz == double.class || clazz == Double.class) { | ||
return Double.valueOf(0d).doubleValue(); | ||
} else if (clazz == char.class || clazz == Character.class) { | ||
return Character.valueOf('\0').charValue(); | ||
} else if (clazz == boolean.class || clazz == Boolean.class) { | ||
return Boolean.valueOf(false).booleanValue(); | ||
} else if (clazz == float.class || clazz == Float.class) { | ||
return Float.valueOf(0f).floatValue(); | ||
} else if (clazz == String.class) { | ||
return ""; | ||
} else if (clazz == byte.class || clazz == Byte.class) { | ||
return Byte.valueOf((byte) 0).byteValue(); | ||
} else if (clazz == short.class || clazz == Short.class) { | ||
return Short.valueOf((short) 0).shortValue(); | ||
} else { | ||
return Mockito.mock(clazz); | ||
} | ||
} | ||
|
||
static <T> Method getInterfaceMethod(Class<T> proxy, String name, Class<?>[] params) | ||
throws NoSuchMethodException { | ||
Method method = proxy.getMethod(name, params); | ||
assertThat(method).isNotNull(); | ||
return method; | ||
} | ||
|
||
static <T> void assertProxied(T proxy, T delegate, Method method, VerificationMode mode) | ||
throws InvocationTargetException, IllegalAccessException { | ||
// Get parameters for method | ||
Class<?>[] parameterTypes = method.getParameterTypes(); | ||
Object[] arguments = new Object[parameterTypes.length]; | ||
for (int j = 0; j < arguments.length; j++) { | ||
arguments[j] = valueLookup(parameterTypes[j]); | ||
} | ||
|
||
// Invoke wrapper method | ||
method.invoke(proxy, arguments); | ||
|
||
// Ensure method was called on delegate exactly once with the correct arguments | ||
method.invoke(Mockito.verify(delegate, mode), arguments); | ||
} | ||
} |