Explorar el Código

Migrating 3 more tests from groovy to java (#7781)

Continuation of #7768.

---------

Co-authored-by: Mateusz Rzeszutek <mrzeszutek@splunk.com>
jason plumb hace 2 años
padre
commit
13d5246489

+ 0 - 69
instrumentation-annotations-support/src/test/groovy/io/opentelemetry/instrumentation/api/annotation/support/AnnotationReflectionHelperTest.groovy

@@ -1,69 +0,0 @@
-/*
- * Copyright The OpenTelemetry Authors
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package io.opentelemetry.instrumentation.api.annotation.support
-
-import groovy.transform.CompileStatic
-import spock.lang.Specification
-
-import java.lang.invoke.MethodHandles
-
-class AnnotationReflectionHelperTest extends Specification {
-
-  def "returns the annotation type by name"() {
-    given:
-    def cls = AnnotationReflectionHelper.forNameOrNull(
-      AnnotationReflectionHelperTest.classLoader,
-      "io.opentelemetry.instrumentation.api.annotation.support.CustomAnnotation")
-
-    expect:
-    cls == CustomAnnotation
-  }
-
-  def "returns null for an annotation not found"() {
-    given:
-    def cls = AnnotationReflectionHelper.forNameOrNull(
-      AnnotationReflectionHelperTest.classLoader,
-      "io.opentelemetry.instrumentation.api.annotation.support.NonExistentAnnotation")
-
-    expect:
-    cls == null
-  }
-
-  def "returns null for a class that is not an annotation"() {
-    given:
-    def cls = AnnotationReflectionHelper.forNameOrNull(
-      AnnotationReflectionHelperTest.classLoader,
-      "java.util.List")
-
-    expect:
-    cls == null
-  }
-
-  // Using @CompileStatic to ensure that groovy does not call MethodHandles.lookup using method
-  // handles. MethodHandles.lookup is caller sensitive. Test fails with lookup where the caller is
-  // an anonymous class.
-  @CompileStatic
-  private static MethodHandles.Lookup getLookup() {
-    return MethodHandles.lookup()
-  }
-
-  def "returns bound functional interface to annotation element"() {
-    given:
-    def function = AnnotationReflectionHelper.bindAnnotationElementMethod(
-      getLookup(),
-      CustomAnnotation,
-      "value",
-      String
-    )
-    def annotation = Annotated.getDeclaredAnnotation(CustomAnnotation)
-
-    expect:
-    function.apply(annotation) == "Value"
-  }
-
-  @CustomAnnotation(value = "Value")
-  class Annotated {}
-}

+ 0 - 303
instrumentation-annotations-support/src/test/groovy/io/opentelemetry/instrumentation/api/annotation/support/AttributeBindingFactoryTest.groovy

@@ -1,303 +0,0 @@
-/*
- * Copyright The OpenTelemetry Authors
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package io.opentelemetry.instrumentation.api.annotation.support
-
-import io.opentelemetry.api.common.AttributeType
-import io.opentelemetry.api.common.AttributesBuilder
-import spock.lang.Specification
-
-class AttributeBindingFactoryTest extends Specification {
-
-  AttributesBuilder setter = Mock()
-
-  def "creates attribute binding for String"() {
-    when:
-    AttributeBindingFactory.createBinding("key", String).apply(setter, "value")
-
-    then:
-    1 * setter.put({ it.getType() == AttributeType.STRING && it.getKey() == "key" }, "value")
-  }
-
-  def "creates attribute binding for int"() {
-    when:
-    AttributeBindingFactory.createBinding("key", int).apply(setter, 1234)
-
-    then:
-    1 * setter.put({ it.getType() == AttributeType.LONG && it.getKey() == "key" }, 1234L)
-  }
-
-  def "creates attribute binding for Integer"() {
-    when:
-    AttributeBindingFactory.createBinding("key", Integer).apply(setter, 1234)
-
-    then:
-    1 * setter.put({ it.getType() == AttributeType.LONG && it.getKey() == "key" }, 1234L)
-  }
-
-  def "creates attribute binding for long"() {
-    when:
-    AttributeBindingFactory.createBinding("key", long).apply(setter, 1234L)
-
-    then:
-    1 * setter.put({ it.getType() == AttributeType.LONG && it.getKey() == "key" }, 1234L)
-  }
-
-  def "creates attribute binding for Long"() {
-    when:
-    AttributeBindingFactory.createBinding("key", Long).apply(setter, 1234L)
-
-    then:
-    1 * setter.put({ it.getType() == AttributeType.LONG && it.getKey() == "key" }, 1234L)
-  }
-
-  def "creates attribute binding for float"() {
-    when:
-    AttributeBindingFactory.createBinding("key", float).apply(setter, 1234.0F)
-
-    then:
-    1 * setter.put({ it.getType() == AttributeType.DOUBLE && it.getKey() == "key" }, 1234.0)
-  }
-
-  def "creates attribute binding for Float"() {
-    when:
-    AttributeBindingFactory.createBinding("key", Float).apply(setter, 1234.0F)
-
-    then:
-    1 * setter.put({ it.getType() == AttributeType.DOUBLE && it.getKey() == "key" }, 1234.0)
-  }
-
-  def "creates attribute binding for double"() {
-    when:
-    AttributeBindingFactory.createBinding("key", double).apply(setter, Double.valueOf(1234.0))
-
-    then:
-    1 * setter.put({ it.getType() == AttributeType.DOUBLE && it.getKey() == "key" }, Double.valueOf(1234.0))
-  }
-
-  def "creates attribute binding for Double"() {
-    when:
-    AttributeBindingFactory.createBinding("key", Double).apply(setter, Double.valueOf(1234.0))
-
-    then:
-    1 * setter.put({ it.getType() == AttributeType.DOUBLE && it.getKey() == "key" }, Double.valueOf(1234.0))
-  }
-
-  def "creates attribute binding for boolean"() {
-    when:
-    AttributeBindingFactory.createBinding("key", boolean).apply(setter, true)
-
-    then:
-    1 * setter.put({ it.getType() == AttributeType.BOOLEAN && it.getKey() == "key" }, true)
-  }
-
-  def "creates attribute binding for Boolean"() {
-    when:
-    AttributeBindingFactory.createBinding("key", Boolean).apply(setter, true)
-
-    then:
-    1 * setter.put({ it.getType() == AttributeType.BOOLEAN && it.getKey() == "key" }, true)
-  }
-
-  def "creates attribute binding for String[]"() {
-    when:
-    AttributeBindingFactory.createBinding("key", String[]).apply(setter, ["x", "y", "z", null] as String[])
-
-    then:
-    1 * setter.put({ it.getType() == AttributeType.STRING_ARRAY && it.getKey() == "key" }, ["x", "y", "z", null])
-  }
-
-  def "creates attribute binding for int[]"() {
-    when:
-    AttributeBindingFactory.createBinding("key", int[]).apply(setter, [1, 2, 3] as int[])
-
-    then:
-    1 * setter.put({ it.getType() == AttributeType.LONG_ARRAY && it.getKey() == "key" }, [1L, 2L, 3L])
-  }
-
-  def "creates attribute binding for Integer[]"() {
-    when:
-    AttributeBindingFactory.createBinding("key", Integer[]).apply(setter, [1, 2, 3, null] as Integer[])
-
-    then:
-    1 * setter.put({ it.getType() == AttributeType.LONG_ARRAY && it.getKey() == "key" }, [1L, 2L, 3L, null])
-  }
-
-  def "creates attribute binding for long[]"() {
-    when:
-    AttributeBindingFactory.createBinding("key", long[]).apply(setter, [1L, 2L, 3L] as long[])
-
-    then:
-    1 * setter.put({ it.getType() == AttributeType.LONG_ARRAY && it.getKey() == "key" }, [1L, 2L, 3L])
-  }
-
-  def "creates attribute binding for Long[]"() {
-    when:
-    AttributeBindingFactory.createBinding("key", Long[]).apply(setter, [1L, 2L, 3L, null] as Long[])
-
-    then:
-    1 * setter.put({ it.getType() == AttributeType.LONG_ARRAY && it.getKey() == "key" }, [1L, 2L, 3L, null])
-  }
-
-  def "creates attribute binding for float[]"() {
-    when:
-    AttributeBindingFactory.createBinding("key", float[]).apply(setter, [1.0F, 2.0F, 3.0F] as float[])
-
-    then:
-    1 * setter.put({ it.getType() == AttributeType.DOUBLE_ARRAY && it.getKey() == "key" }, [1.0, 2.0, 3.0] as List<Double>)
-  }
-
-  def "creates attribute binding for Float[]"() {
-    when:
-    AttributeBindingFactory.createBinding("key", Float[]).apply(setter, [1.0F, 2.0F, 3.0F, null] as Float[])
-
-    then:
-    1 * setter.put({ it.getType() == AttributeType.DOUBLE_ARRAY && it.getKey() == "key" }, [1.0, 2.0, 3.0, null] as List<Double>)
-  }
-
-  def "creates attribute binding for double[]"() {
-    when:
-    AttributeBindingFactory.createBinding("key", double[]).apply(setter, [1.0, 2.0, 3.0] as double[])
-
-    then:
-    1 * setter.put({ it.getType() == AttributeType.DOUBLE_ARRAY && it.getKey() == "key" }, [1.0, 2.0, 3.0] as List<Double>)
-  }
-
-  def "creates attribute binding for Double[]"() {
-    when:
-    AttributeBindingFactory.createBinding("key", Double[]).apply(setter, [1.0, 2.0, 3.0, null] as Double[])
-
-    then:
-    1 * setter.put({ it.getType() == AttributeType.DOUBLE_ARRAY && it.getKey() == "key" }, [1.0, 2.0, 3.0, null] as List<Double>)
-  }
-
-  def "creates attribute binding for boolean[]"() {
-    when:
-    AttributeBindingFactory.createBinding("key", boolean[]).apply(setter, [true, false] as boolean[])
-
-    then:
-    1 * setter.put({ it.getType() == AttributeType.BOOLEAN_ARRAY && it.getKey() == "key" }, [true, false] as List<Boolean>)
-  }
-
-  def "creates attribute binding for Boolean[]"() {
-    when:
-    AttributeBindingFactory.createBinding("key", Boolean[]).apply(setter, [true, false, null] as Boolean[])
-
-    then:
-    1 * setter.put({ it.getType() == AttributeType.BOOLEAN_ARRAY && it.getKey() == "key" }, [true, false, null] as List<Boolean>)
-  }
-
-  def "creates default attribute binding"() {
-    when:
-    AttributeBindingFactory.createBinding("key", TestClass).apply(setter, new TestClass("foo"))
-
-    then:
-    1 * setter.put({ it.getType() == AttributeType.STRING && it.getKey() == "key" }, "TestClass{value = foo}")
-  }
-
-  def "creates default attribute binding for array"() {
-    when:
-    AttributeBindingFactory.createBinding("key", TestClass[]).apply(setter, [new TestClass("foo"), new TestClass("bar"), null] as TestClass[])
-
-    then:
-    1 * setter.put({ it.getType() == AttributeType.STRING_ARRAY && it.getKey() == "key" }, ["TestClass{value = foo}", "TestClass{value = bar}", null])
-  }
-
-  def "creates attribute binding for List<String>"() {
-    when:
-    def type = TestFields.getDeclaredField("stringList").getGenericType()
-    AttributeBindingFactory.createBinding("key", type).apply(setter, ["x", "y", "z"])
-
-    then:
-    1 * setter.put({ it.getType() == AttributeType.STRING_ARRAY && it.getKey() == "key" }, ["x", "y", "z"])
-  }
-
-  def "creates attribute binding for List<Integer>"() {
-    when:
-    def type = TestFields.getDeclaredField("integerList").getGenericType()
-    AttributeBindingFactory.createBinding("key", type).apply(setter, [1, 2, 3])
-
-    then:
-    1 * setter.put({ it.getType() == AttributeType.LONG_ARRAY && it.getKey() == "key" }, [1L, 2L, 3L])
-  }
-
-  def "creates attribute binding for List<Long>"() {
-    when:
-    def type = TestFields.getDeclaredField("longList").getGenericType()
-    AttributeBindingFactory.createBinding("key", type).apply(setter, [1L, 2L, 3L])
-
-    then:
-    1 * setter.put({ it.getType() == AttributeType.LONG_ARRAY && it.getKey() == "key" }, [1L, 2L, 3L])
-  }
-
-  def "creates attribute binding for List<Float>"() {
-    when:
-    def type = TestFields.getDeclaredField("floatList").getGenericType()
-    AttributeBindingFactory.createBinding("key", type).apply(setter, [1.0F, 2.0F, 3.0F])
-
-    then:
-    1 * setter.put({ it.getType() == AttributeType.DOUBLE_ARRAY && it.getKey() == "key" }, [1.0, 2.0, 3.0])
-  }
-
-  def "creates attribute binding for List<Double>"() {
-    when:
-    def type = TestFields.getDeclaredField("doubleList").getGenericType()
-    AttributeBindingFactory.createBinding("key", type).apply(setter, [1.0, 2.0, 3.0])
-
-    then:
-    1 * setter.put({ it.getType() == AttributeType.DOUBLE_ARRAY && it.getKey() == "key" }, [1.0, 2.0, 3.0])
-  }
-
-  def "creates attribute binding for List<Boolean>"() {
-    when:
-    def type = TestFields.getDeclaredField("booleanList").getGenericType()
-    AttributeBindingFactory.createBinding("key", type).apply(setter, [true, false, null])
-
-    then:
-    1 * setter.put({ it.getType() == AttributeType.BOOLEAN_ARRAY && it.getKey() == "key" }, [true, false, null])
-  }
-
-  def "creates attribute binding for List<?>"() {
-    when:
-    def type = TestFields.getDeclaredField("otherList").getGenericType()
-    AttributeBindingFactory.createBinding("key", type).apply(setter, [new TestClass("foo"), new TestClass("bar")])
-
-    then:
-    1 * setter.put({ it.getType() == AttributeType.STRING_ARRAY && it.getKey() == "key" }, ["TestClass{value = foo}", "TestClass{value = bar}"])
-  }
-
-  def "creates attribute binding for ArrayList<Long>"() {
-    when:
-    def type = TestFields.getDeclaredField("longArrayList").getGenericType()
-    AttributeBindingFactory.createBinding("key", type).apply(setter, [1L, 2L, 3L])
-
-    then:
-    1 * setter.put({ it.getType() == AttributeType.LONG_ARRAY && it.getKey() == "key" }, [1L, 2L, 3L])
-  }
-
-  class TestClass {
-    final String value
-
-    TestClass(String value) {
-      this.value = value
-    }
-
-    @Override
-    String toString() {
-      return "TestClass{value = " + value + "}"
-    }
-  }
-
-  class TestFields {
-    List<String> stringList
-    List<Long> longList
-    List<Double> doubleList
-    List<Boolean> booleanList
-    List<Integer> integerList
-    List<Float> floatList
-    List<TestClass> otherList
-    ArrayList<Long> longArrayList
-  }
-}

+ 0 - 209
instrumentation-annotations-support/src/test/groovy/io/opentelemetry/instrumentation/api/annotation/support/MethodSpanAttributesExtractorTest.groovy

@@ -1,209 +0,0 @@
-/*
- * Copyright The OpenTelemetry Authors
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package io.opentelemetry.instrumentation.api.annotation.support
-
-import io.opentelemetry.api.common.AttributesBuilder
-import io.opentelemetry.context.Context
-import io.opentelemetry.instrumentation.api.internal.cache.Cache
-import spock.lang.Specification
-
-import java.lang.reflect.Method
-import java.util.function.Function
-
-class MethodSpanAttributesExtractorTest extends Specification {
-
-  def "extracts attributes for method with attribute names"() {
-    given:
-    def request = new Object()
-    def context = Context.root()
-    def method = TestClass.getDeclaredMethod("method", String, String, String)
-    AttributesBuilder builder = Mock()
-
-    Cache<Method, AttributeBindings> cache = Mock {
-      1 * computeIfAbsent(method, _ as Function<Method, AttributeBindings>) >> { m, fn -> fn.apply(m) }
-    }
-
-    def extractor = new MethodSpanAttributesExtractor<Object, Object>(
-      { r -> method },
-      { m, p -> ["x", "y", "z"] as String[] },
-      { r -> ["a", "b", "c"] as String[] },
-      cache
-    )
-
-    when:
-    extractor.onStart(builder, context, request)
-
-    then:
-    1 * builder.put({ it.getKey() == "x" }, "a")
-    1 * builder.put({ it.getKey() == "y" }, "b")
-    1 * builder.put({ it.getKey() == "z" }, "c")
-  }
-
-  def "does not extract attributes for empty attribute name array"() {
-    given:
-    def request = new Object()
-    def context = Context.root()
-    def method = TestClass.getDeclaredMethod("method", String, String, String)
-    AttributesBuilder builder = Mock()
-
-    Cache<Method, AttributeBindings> cache = Mock {
-      1 * computeIfAbsent(method, _ as Function<Method, AttributeBindings>) >> { m, fn -> fn.apply(m) }
-    }
-
-    def extractor = new MethodSpanAttributesExtractor<Object, Object>(
-      { r -> method },
-      { m, p -> new String[0] },
-      { r -> ["a", "b", "c"] as String[] },
-      cache
-    )
-
-    when:
-    extractor.onStart(builder, context, request)
-
-    then:
-    0 * builder.put(*_)
-  }
-
-  def "does not extract attributes for method with attribute names array with fewer elements than parameters"() {
-    given:
-    def request = new Object()
-    def context = Context.root()
-    def method = TestClass.getDeclaredMethod("method", String, String, String)
-    AttributesBuilder builder = Mock()
-
-    Cache<Method, AttributeBindings> cache = Mock {
-      1 * computeIfAbsent(method, _ as Function<Method, AttributeBindings>) >> { m, fn -> fn.apply(m) }
-    }
-
-    def extractor = new MethodSpanAttributesExtractor<Object, Object>(
-      { r -> method },
-      { m, p -> ["x", "y"] as String[] },
-      { r -> ["a", "b", "c"] as String[] },
-      cache
-    )
-
-    when:
-    extractor.onStart(builder, context, request)
-
-    then:
-    0 * builder.put(*_)
-  }
-
-  def "extracts attributes for method with attribute names array with null element"() {
-    given:
-    def request = new Object()
-    def context = Context.root()
-    def method = TestClass.getDeclaredMethod("method", String, String, String)
-    AttributesBuilder builder = Mock()
-
-    Cache<Method, AttributeBindings> cache = Mock {
-      1 * computeIfAbsent(method, _ as Function<Method, AttributeBindings>) >> { m, fn -> fn.apply(m) }
-    }
-
-    def extractor = new MethodSpanAttributesExtractor<Object, Object>(
-      { r -> method },
-      { m, p -> ["x", null, "z"] as String[] },
-      { r -> ["a", "b", "c"] as String[] },
-      cache
-    )
-
-    when:
-    extractor.onStart(builder, context, request)
-
-    then:
-    1 * builder.put({ it.getKey() == "x" }, "a")
-    1 * builder.put({ it.getKey() == "z" }, "c")
-    0 * builder.put(_, "b")
-  }
-
-  def "does not extracts attribute for method with null argument"() {
-    given:
-    def request = new Object()
-    def context = Context.root()
-    def method = TestClass.getDeclaredMethod("method", String, String, String)
-    AttributesBuilder builder = Mock()
-
-    Cache<Method, AttributeBindings> cache = Mock {
-      1 * computeIfAbsent(method, _ as Function<Method, AttributeBindings>) >> { m, fn -> fn.apply(m) }
-    }
-
-    def extractor = new MethodSpanAttributesExtractor<Object, Object>(
-      { r -> method },
-      { m, p -> ["x", "y", "z"] as String[] },
-      { r -> ["a", "b", null] as String[] },
-      cache
-    )
-
-    when:
-    extractor.onStart(builder, context, request)
-
-    then:
-    1 * builder.put({ it.getKey() == "x" }, "a")
-    1 * builder.put({ it.getKey() == "y" }, "b")
-    0 * builder.put({ it.getKey() == "z" }, _)
-  }
-
-  def "applies cached bindings"() {
-    given:
-    def request = new Object()
-    def context = Context.root()
-    def method = TestClass.getDeclaredMethod("method", String, String, String)
-    AttributesBuilder builder = Mock()
-
-    AttributeBindings bindings = Mock {
-      1 * isEmpty() >> false
-    }
-    Cache<Method, AttributeBindings> cache = Mock {
-      1 * computeIfAbsent(method, _ as Function<Method, AttributeBindings>) >> bindings
-    }
-
-    def extractor = new MethodSpanAttributesExtractor<Object, Object>(
-      { r -> method },
-      { m, p -> throw new Exception() },
-      { r -> ["a", "b", "c"] as String[] },
-      cache
-    )
-
-    when:
-    extractor.onStart(builder, context, request)
-
-    then:
-    1 * bindings.apply(_, ["a", "b", "c"])
-  }
-
-  def "does not apply cached empty bindings"() {
-    given:
-    def request = new Object()
-    def context = Context.root()
-    def method = TestClass.getDeclaredMethod("method", String, String, String)
-    AttributesBuilder builder = Mock()
-
-    AttributeBindings bindings = Mock {
-      1 * isEmpty() >> true
-    }
-    Cache<Method, AttributeBindings> cache = Mock {
-      1 * computeIfAbsent(method, _ as Function<Method, AttributeBindings>) >> bindings
-    }
-
-    def extractor = new MethodSpanAttributesExtractor<Object, Object>(
-      { r -> method },
-      { m, p -> throw new Exception() },
-      { r -> throw new Exception() },
-      cache
-    )
-
-    when:
-    extractor.onStart(builder, context, request)
-
-    then:
-    0 * bindings.apply(_, _)
-  }
-
-  class TestClass {
-    @SuppressWarnings("unused")
-    void method(String x, String y, String z) {}
-  }
-}

+ 57 - 0
instrumentation-annotations-support/src/test/java/io/opentelemetry/instrumentation/api/annotation/support/AnnotationReflectionHelperTest.java

@@ -0,0 +1,57 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.api.annotation.support;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.lang.annotation.Annotation;
+import java.lang.invoke.MethodHandles;
+import java.util.function.Function;
+import org.junit.jupiter.api.Test;
+
+class AnnotationReflectionHelperTest {
+
+  @Test
+  void returnTheAnnotationTypeByName() {
+    Class<? extends Annotation> clazz =
+        AnnotationReflectionHelper.forNameOrNull(
+            AnnotationReflectionHelperTest.class.getClassLoader(),
+            "io.opentelemetry.instrumentation.api.annotation.support.CustomAnnotation");
+    assertThat(clazz).isEqualTo(CustomAnnotation.class);
+  }
+
+  @Test
+  void returnsNullForAnAnnotationNotFound() {
+    Class<? extends Annotation> clazz =
+        AnnotationReflectionHelper.forNameOrNull(
+            AnnotationReflectionHelperTest.class.getClassLoader(),
+            "io.opentelemetry.instrumentation.api.annotation.support.NonExistentAnnotation");
+    assertThat(clazz).isNull();
+  }
+
+  @Test
+  void returnsNullForClassThatIsNotAnnotation() {
+    Class<? extends Annotation> clazz =
+        AnnotationReflectionHelper.forNameOrNull(
+            AnnotationReflectionHelperTest.class.getClassLoader(), "java.util.List");
+    assertThat(clazz).isNull();
+  }
+
+  @Test
+  void returnsBoundFunctionalInterfaceToAnnotationElement() throws Throwable {
+    Function<Annotation, String> function =
+        AnnotationReflectionHelper.bindAnnotationElementMethod(
+            MethodHandles.lookup(), CustomAnnotation.class, "value", String.class);
+    CustomAnnotation annotation = Annotated.class.getDeclaredAnnotation(CustomAnnotation.class);
+
+    String result = function.apply(annotation);
+
+    assertThat(result).isEqualTo("Value");
+  }
+
+  @CustomAnnotation(value = "Value")
+  static class Annotated {}
+}

+ 293 - 0
instrumentation-annotations-support/src/test/java/io/opentelemetry/instrumentation/api/annotation/support/AttributeBindingFactoryTest.java

@@ -0,0 +1,293 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.api.annotation.support;
+
+import static io.opentelemetry.api.common.AttributeKey.booleanArrayKey;
+import static io.opentelemetry.api.common.AttributeKey.booleanKey;
+import static io.opentelemetry.api.common.AttributeKey.doubleArrayKey;
+import static io.opentelemetry.api.common.AttributeKey.doubleKey;
+import static io.opentelemetry.api.common.AttributeKey.longArrayKey;
+import static io.opentelemetry.api.common.AttributeKey.longKey;
+import static io.opentelemetry.api.common.AttributeKey.stringArrayKey;
+import static io.opentelemetry.api.common.AttributeKey.stringKey;
+import static org.mockito.Mockito.verify;
+
+import io.opentelemetry.api.common.AttributesBuilder;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+class AttributeBindingFactoryTest {
+
+  @Mock AttributesBuilder setter;
+
+  @Test
+  void createAttributeBindingForString() {
+    AttributeBindingFactory.createBinding("key", String.class).apply(setter, "value");
+    verify(setter).put(stringKey("key"), "value");
+  }
+
+  @Test
+  void createAttributeBindingForIntPrimitive() {
+    AttributeBindingFactory.createBinding("key", int.class).apply(setter, 1234);
+    verify(setter).put(longKey("key"), 1234L);
+  }
+
+  @Test
+  void createAttributeBindingForInteger() {
+    AttributeBindingFactory.createBinding("key", Integer.class).apply(setter, 1234);
+    verify(setter).put(longKey("key"), 1234L);
+  }
+
+  @Test
+  void createAttributeBindingForLongPrimitive() {
+    AttributeBindingFactory.createBinding("key", long.class).apply(setter, 1234L);
+    verify(setter).put(longKey("key"), 1234L);
+  }
+
+  @Test
+  void createAttributeBindingForLong() {
+    AttributeBindingFactory.createBinding("key", Long.class).apply(setter, 1234L);
+    verify(setter).put(longKey("key"), 1234L);
+  }
+
+  @Test
+  void createAttributeBindingForFloatPrimitive() {
+    AttributeBindingFactory.createBinding("key", float.class).apply(setter, 1234.0F);
+    verify(setter).put(doubleKey("key"), 1234.0);
+  }
+
+  @Test
+  void createAttributeBindingForFloat() {
+    AttributeBindingFactory.createBinding("key", Float.class).apply(setter, 1234.0F);
+    verify(setter).put(doubleKey("key"), 1234.0);
+  }
+
+  @Test
+  void createAttributeBindingForDoublePrimitive() {
+    AttributeBindingFactory.createBinding("key", double.class).apply(setter, 1234.0);
+    verify(setter).put(doubleKey("key"), 1234.0);
+  }
+
+  @Test
+  void createAttributeBindingForDouble() {
+    AttributeBindingFactory.createBinding("key", Double.class).apply(setter, 1234.0);
+    verify(setter).put(doubleKey("key"), 1234.0);
+  }
+
+  @Test
+  void createAttributeBindingForBooleanPrimitive() {
+    AttributeBindingFactory.createBinding("key", boolean.class).apply(setter, true);
+    verify(setter).put(booleanKey("key"), true);
+  }
+
+  @Test
+  void createAttributeBindingForBoolean() {
+    AttributeBindingFactory.createBinding("key", Boolean.class).apply(setter, true);
+    verify(setter).put(booleanKey("key"), true);
+  }
+
+  @Test
+  void createAttributeBindingForStringArray() {
+    String[] value = {"x", "y", "z", null};
+    List<String> expected = Arrays.asList(value);
+    AttributeBindingFactory.createBinding("key", String[].class).apply(setter, value);
+    verify(setter).put(stringArrayKey("key"), expected);
+  }
+
+  @Test
+  void createAttributeBindingForPrimitiveIntArray() {
+    int[] value = {1, 2, 3};
+    List<Long> expected = Arrays.asList(1L, 2L, 3L);
+    AttributeBindingFactory.createBinding("key", int[].class).apply(setter, value);
+    verify(setter).put(longArrayKey("key"), expected);
+  }
+
+  @Test
+  void createAttributeBindingForIntegerArray() {
+    Integer[] value = {1, 2, 3};
+    List<Long> expected = Arrays.asList(1L, 2L, 3L);
+    AttributeBindingFactory.createBinding("key", Integer[].class).apply(setter, value);
+    verify(setter).put(longArrayKey("key"), expected);
+  }
+
+  @Test
+  void createAttributeBindingForPrimitiveLongArray() {
+    long[] value = {1, 2, 3};
+    List<Long> expected = Arrays.asList(1L, 2L, 3L);
+    AttributeBindingFactory.createBinding("key", long[].class).apply(setter, value);
+    verify(setter).put(longArrayKey("key"), expected);
+  }
+
+  @Test
+  void createAttributeBindingForLongArray() {
+    Long[] value = {1L, 2L, 3L};
+    List<Long> expected = Arrays.asList(1L, 2L, 3L);
+    AttributeBindingFactory.createBinding("key", Long[].class).apply(setter, value);
+    verify(setter).put(longArrayKey("key"), expected);
+  }
+
+  @Test
+  void createAttributeBindingForPrimitiveFloatArray() {
+    float[] value = {1f, 2f, 3f};
+    List<Double> expected = Arrays.asList(1.0, 2.0, 3.0);
+    AttributeBindingFactory.createBinding("key", float[].class).apply(setter, value);
+    verify(setter).put(doubleArrayKey("key"), expected);
+  }
+
+  @Test
+  void createAttributeBindingForFloatArray() {
+    Float[] value = {1f, 2f, 3f};
+    List<Double> expected = Arrays.asList(1.0, 2.0, 3.0);
+    AttributeBindingFactory.createBinding("key", Float[].class).apply(setter, value);
+    verify(setter).put(doubleArrayKey("key"), expected);
+  }
+
+  @Test
+  void createAttributeBindingForPrimitiveDoubleArray() {
+    double[] value = {1f, 2f, 3f};
+    List<Double> expected = Arrays.asList(1.0, 2.0, 3.0);
+    AttributeBindingFactory.createBinding("key", double[].class).apply(setter, value);
+    verify(setter).put(doubleArrayKey("key"), expected);
+  }
+
+  @Test
+  void createAttributeBindingForDoubleArray() {
+    Double[] value = {1.0, 2.0, 3.0};
+    List<Double> expected = Arrays.asList(1.0, 2.0, 3.0);
+    AttributeBindingFactory.createBinding("key", Double[].class).apply(setter, value);
+    verify(setter).put(doubleArrayKey("key"), expected);
+  }
+
+  @Test
+  void createAttributeBindingForPrimitiveBooleanArray() {
+    boolean[] value = {true, false};
+    List<Boolean> expected = Arrays.asList(true, false);
+    AttributeBindingFactory.createBinding("key", boolean[].class).apply(setter, value);
+    verify(setter).put(booleanArrayKey("key"), expected);
+  }
+
+  @Test
+  void createAttributeBindingForBooleanArray() {
+    Boolean[] value = {true, false};
+    List<Boolean> expected = Arrays.asList(true, false);
+    AttributeBindingFactory.createBinding("key", Boolean[].class).apply(setter, value);
+    verify(setter).put(booleanArrayKey("key"), expected);
+  }
+
+  @Test
+  void createDefaultAttributeBinding() {
+    AttributeBindingFactory.createBinding("key", TestClass.class)
+        .apply(setter, new TestClass("foo"));
+    verify(setter).put(stringKey("key"), "TestClass{value = foo}");
+  }
+
+  @Test
+  void createDefaultAttributeBindingForArray() {
+    List<String> expected = Arrays.asList("TestClass{value = foo}", "TestClass{value = bar}", null);
+    AttributeBindingFactory.createBinding("key", TestClass[].class)
+        .apply(setter, new TestClass[] {new TestClass("foo"), new TestClass("bar"), null});
+    verify(setter).put(stringArrayKey("key"), expected);
+  }
+
+  @Test
+  void createAttributeBindingForStringList() throws Exception {
+    List<String> value = Arrays.asList("x", "y", "z");
+    Type type = TestFields.class.getDeclaredField("stringList").getGenericType();
+    AttributeBindingFactory.createBinding("key", type).apply(setter, value);
+    verify(setter).put(stringArrayKey("key"), value);
+  }
+
+  @Test
+  void createAttributeBindingForIntegerList() throws Exception {
+    List<Integer> value = Arrays.asList(1, 2, 3);
+    List<Long> expected = Arrays.asList(1L, 2L, 3L);
+    Type type = TestFields.class.getDeclaredField("integerList").getGenericType();
+    AttributeBindingFactory.createBinding("key", type).apply(setter, value);
+    verify(setter).put(longArrayKey("key"), expected);
+  }
+
+  @Test
+  void createAttributeBindingForLongList() throws Exception {
+    List<Long> value = Arrays.asList(1L, 2L, 3L);
+    Type type = TestFields.class.getDeclaredField("longList").getGenericType();
+    AttributeBindingFactory.createBinding("key", type).apply(setter, value);
+    verify(setter).put(longArrayKey("key"), value);
+  }
+
+  @Test
+  void createAttributeBindingForFloatList() throws Exception {
+    List<Float> value = Arrays.asList(1.0f, 2.0f, 3.0f);
+    List<Double> expected = Arrays.asList(1.0, 2.0, 3.0);
+    Type type = TestFields.class.getDeclaredField("floatList").getGenericType();
+    AttributeBindingFactory.createBinding("key", type).apply(setter, value);
+    verify(setter).put(doubleArrayKey("key"), expected);
+  }
+
+  @Test
+  void createAttributeBindingForDoubleList() throws Exception {
+    List<Double> value = Arrays.asList(1.0, 2.0, 3.0);
+    List<Double> expected = Arrays.asList(1.0, 2.0, 3.0);
+    Type type = TestFields.class.getDeclaredField("doubleList").getGenericType();
+    AttributeBindingFactory.createBinding("key", type).apply(setter, value);
+    verify(setter).put(doubleArrayKey("key"), expected);
+  }
+
+  @Test
+  void createAttributeBindingForBooleanList() throws Exception {
+    List<Boolean> value = Arrays.asList(true, false, null);
+    Type type = TestFields.class.getDeclaredField("booleanList").getGenericType();
+    AttributeBindingFactory.createBinding("key", type).apply(setter, value);
+    verify(setter).put(booleanArrayKey("key"), value);
+  }
+
+  @Test
+  void createAttributeBindingForGenericList() throws Exception {
+    List<TestClass> value = Arrays.asList(new TestClass("foo"), new TestClass("bar"));
+    List<String> expected = Arrays.asList("TestClass{value = foo}", "TestClass{value = bar}");
+    Type type = TestFields.class.getDeclaredField("otherList").getGenericType();
+    AttributeBindingFactory.createBinding("key", type).apply(setter, value);
+    verify(setter).put(stringArrayKey("key"), expected);
+  }
+
+  @Test
+  void createAttributeBindingForLongArrayList() throws Exception {
+    List<Long> value = Arrays.asList(1L, 2L, 3L);
+    Type type = TestFields.class.getDeclaredField("longArrayList").getGenericType();
+    AttributeBindingFactory.createBinding("key", type).apply(setter, value);
+    verify(setter).put(longArrayKey("key"), value);
+  }
+
+  static class TestClass {
+    final String value;
+
+    TestClass(String value) {
+      this.value = value;
+    }
+
+    @Override
+    public String toString() {
+      return "TestClass{value = " + value + "}";
+    }
+  }
+
+  static class TestFields {
+    List<String> stringList;
+    List<Long> longList;
+    List<Double> doubleList;
+    List<Boolean> booleanList;
+    List<Integer> integerList;
+    List<Float> floatList;
+    List<TestClass> otherList;
+    ArrayList<Long> longArrayList;
+  }
+}

+ 180 - 0
instrumentation-annotations-support/src/test/java/io/opentelemetry/instrumentation/api/annotation/support/MethodSpanAttributesExtractorTest.java

@@ -0,0 +1,180 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.api.annotation.support;
+
+import static io.opentelemetry.api.common.AttributeKey.stringKey;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import io.opentelemetry.api.common.AttributesBuilder;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.instrumentation.api.internal.cache.Cache;
+import java.lang.reflect.Method;
+import java.util.function.Function;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+class MethodSpanAttributesExtractorTest {
+
+  Object request = new Object();
+  Context context = Context.root();
+  Method method = TestClass.getMethod();
+
+  @Mock AttributesBuilder builder;
+  @Mock Cache<Method, AttributeBindings> cache;
+
+  @BeforeEach
+  void setup() {
+    lenient()
+        .doAnswer(
+            invocation -> {
+              Method m = invocation.getArgument(0);
+              Function<Method, AttributeBindings> fn = invocation.getArgument(1);
+              return fn.apply(m);
+            })
+        .when(cache)
+        .computeIfAbsent(any(), any());
+  }
+
+  @Test
+  void extractAttributesForMethodWithAttributeNames() {
+    MethodSpanAttributesExtractor<Object, Object> extractor =
+        new MethodSpanAttributesExtractor<>(
+            r -> method,
+            (m, p) -> new String[] {"x", "y", "z"},
+            r -> new String[] {"a", "b", "c"},
+            cache);
+
+    extractor.onStart(builder, context, request);
+
+    verify(builder).put(stringKey("x"), "a");
+    verify(builder).put(stringKey("y"), "b");
+    verify(builder).put(stringKey("z"), "c");
+  }
+
+  @Test
+  void doesNotExtractAttributesForEmptyAttributeNameArray() {
+    MethodSpanAttributesExtractor<Object, Object> extractor =
+        new MethodSpanAttributesExtractor<>(
+            r -> method, (m, p) -> new String[0], r -> new String[] {"a", "b", "c"}, cache);
+
+    extractor.onStart(builder, context, request);
+    verifyNoInteractions(builder);
+  }
+
+  @Test
+  void doesNotExtractAttributesForMethodWithAttributeNamesArrayWithFewerElementsThanParams() {
+    MethodSpanAttributesExtractor<Object, Object> extractor =
+        new MethodSpanAttributesExtractor<>(
+            r -> method,
+            (m, p) -> new String[] {"x", "y"},
+            r -> new String[] {"a", "b", "c"},
+            cache);
+
+    extractor.onStart(builder, context, request);
+    verifyNoInteractions(builder);
+  }
+
+  @Test
+  void extractsAttributesForMethodWithAttributeNamesArrayWithNullElement() {
+    MethodSpanAttributesExtractor<Object, Object> extractor =
+        new MethodSpanAttributesExtractor<>(
+            r -> method,
+            (m, p) -> new String[] {"x", null, "z"},
+            r -> new String[] {"a", "b", "c"},
+            cache);
+
+    extractor.onStart(builder, context, request);
+
+    verify(builder).put(stringKey("x"), "a");
+    verify(builder).put(stringKey("z"), "c");
+    verifyNoMoreInteractions(builder);
+  }
+
+  @Test
+  void doesNotExtractAttributeForMethodWithNullArgument() {
+    MethodSpanAttributesExtractor<Object, Object> extractor =
+        new MethodSpanAttributesExtractor<>(
+            r -> method,
+            (m, p) -> new String[] {"x", "y", "z"},
+            r -> new String[] {"a", "b", null},
+            cache);
+
+    extractor.onStart(builder, context, request);
+
+    verify(builder).put(stringKey("x"), "a");
+    verify(builder).put(stringKey("y"), "b");
+    verifyNoMoreInteractions(builder);
+  }
+
+  @Test
+  void appliesCachedBindings() {
+    AttributeBindings bindings = mock(AttributeBindings.class);
+    when(bindings.isEmpty()).thenReturn(false);
+    doAnswer(invocation -> bindings).when(cache).computeIfAbsent(any(), any());
+
+    MethodSpanAttributesExtractor<Object, Object> extractor =
+        new MethodSpanAttributesExtractor<>(
+            r -> method,
+            (m, p) -> {
+              throw new RuntimeException();
+            },
+            r -> new String[] {"a", "b", "c"},
+            cache);
+
+    extractor.onStart(builder, context, request);
+
+    verify(bindings).apply(builder, new Object[] {"a", "b", "c"});
+  }
+
+  @Test
+  void doesNotApplyCachedEmptyBindings() {
+    AttributeBindings bindings = mock(AttributeBindings.class);
+    when(bindings.isEmpty()).thenReturn(true);
+    doAnswer(invocation -> bindings).when(cache).computeIfAbsent(any(), any());
+
+    MethodSpanAttributesExtractor<Object, Object> extractor =
+        new MethodSpanAttributesExtractor<>(
+            r -> method,
+            (m, p) -> {
+              throw new RuntimeException();
+            },
+            r -> {
+              throw new RuntimeException();
+            },
+            cache);
+
+    extractor.onStart(builder, context, request);
+
+    verify(bindings, never()).apply(isA(AttributesBuilder.class), isA(Object[].class));
+  }
+
+  static class TestClass {
+    public static Method getMethod() {
+      try {
+        return TestClass.class.getDeclaredMethod(
+            "method", String.class, String.class, String.class);
+      } catch (NoSuchMethodException e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    @SuppressWarnings("unused")
+    void method(String x, String y, String z) {}
+  }
+}