Repository: open-telemetry/opentelemetry-java
File: sdk/testing/src/main/java/io/opentelemetry/sdk/testing/assertj/SpanDataAssert.java (Java)

Code before:
  public SpanDataAssert hasAttributesSatisfyingExactly(AttributeAssertion... assertions) {
    return hasAttributesSatisfyingExactly(Arrays.asList(assertions));
  }

  /**
   * Asserts the span has attributes matching all {@code assertions} and no more. Assertions can be
   * created using methods like {@link OpenTelemetryAssertions#satisfies(AttributeKey,
   * OpenTelemetryAssertions.LongAssertConsumer)}.
   */
  public SpanDataAssert hasAttributesSatisfyingExactly(Iterable<AttributeAssertion> assertions) {
    AssertUtil.assertAttributesExactly(
        actual.getAttributes(),
        assertions,
        String.format("span [%s] attribute keys", actual.getName()));
    return this;
  }

  /**
   * Asserts the span has an exception event for the given {@link Throwable}. The stack trace is not
   * matched against.
   */
  // Workaround "passing @Nullable parameter 'stackTrace' where @NonNull is required", Nullaway
  // seems to think assertThat is supposed to be passed NonNull even though we know that can't be
  // true for assertions.
  @SuppressWarnings("NullAway")
  public SpanDataAssert hasException(Throwable exception) {
    EventData exceptionEvent =
        actual.getEvents().stream()
            .filter(event -> event.getName().equals(EXCEPTION_EVENT_NAME))
            .findFirst()
            .orElse(null);

    if (exceptionEvent == null) {
      failWithMessage(
          "Expected span [%s] to have an exception event but only had events <%s>",
          actual.getName(), actual.getEvents());
      // Never executed but to reduce IntelliJ warnings.
      return this;
    }

    assertThat(exceptionEvent.getAttributes())
        .as("exception.type")
        .containsEntry(EXCEPTION_TYPE, exception.getClass().getCanonicalName());
    if (exception.getMessage() != null) {
      assertThat(exceptionEvent.getAttributes())
          .as("exception.message")
          .containsEntry(EXCEPTION_MESSAGE, exception.getMessage());
    }

    // Exceptions used in assertions always have a different stack trace, just confirm it was
    // recorded.

Review comment (suggestion):
This is another case of: https://github.com/open-telemetry/opentelemetry-java/pull/7359#pullrequestreview-2915235129

I support retaining the ability to add the `@Nullable` annotation to parameters, and think its useful here. Also, there was never any followup to #7359 to update `VERSIONING.md` to disallow this type of change, so it's still allowed.

Code after:
  public SpanDataAssert hasAttributesSatisfyingExactly(AttributeAssertion... assertions) {
    return hasAttributesSatisfyingExactly(Arrays.asList(assertions));
  }

  /**
   * Asserts the span has attributes matching all {@code assertions} and no more. Assertions can be
   * created using methods like {@link OpenTelemetryAssertions#satisfies(AttributeKey,
   * OpenTelemetryAssertions.LongAssertConsumer)}.
   */
  public SpanDataAssert hasAttributesSatisfyingExactly(Iterable<AttributeAssertion> assertions) {
    AssertUtil.assertAttributesExactly(
        actual.getAttributes(),
        assertions,
        String.format("span [%s] attribute keys", actual.getName()));
    return this;
  }

  /**
   * Asserts the span has an exception event for the given {@link Throwable}. The stack trace is not
   * matched against. If {@code exception} is {@code null}, asserts the span has no exception event.
   */
  // Workaround "passing @Nullable parameter 'stackTrace' where @NonNull is required", Nullaway
  // seems to think assertThat is supposed to be passed NonNull even though we know that can't be
  // true for assertions.
  @SuppressWarnings("NullAway")
  public SpanDataAssert hasException(@Nullable Throwable exception) {
    EventData exceptionEvent =
        actual.getEvents().stream()
            .filter(event -> event.getName().equals(EXCEPTION_EVENT_NAME))
            .findFirst()
            .orElse(null);

    if (exception == null) {
      if (exceptionEvent != null) {
        failWithMessage(
            "Expected span [%s] to have no exception event but had <%s>",
            actual.getName(), exceptionEvent);
      }
      return this;
    }

    if (exceptionEvent == null) {
      failWithMessage(
          "Expected span [%s] to have an exception event but only had events <%s>",
          actual.getName(), actual.getEvents());
      // Never executed but to reduce IntelliJ warnings.
      return this;
    }

    assertThat(exceptionEvent.getAttributes())
        .as("exception.type")