diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/AdditionalFailureInfo.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/AdditionalFailureInfo.java new file mode 100644 index 0000000000..7818255075 --- /dev/null +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/AdditionalFailureInfo.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 + * + * http://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 com.google.android.exoplayer2.testutil; + +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import com.google.android.exoplayer2.util.Util; +import com.google.common.truth.Truth; +import java.util.concurrent.atomic.AtomicReference; +import org.checkerframework.checker.nullness.compatqual.NullableType; +import org.junit.Rule; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +/** + * A JUnit {@link Rule} that attaches additional info to any errors/exceptions thrown by the test. + * + *
This is useful for tests where the line-number from a stacktrace doesn't provide enough detail + * about the failure, for example when an assertion fails inside a loop. + * + *
This can be preferable to many calls to {@link Truth#assertWithMessage(String)} because it + * will also add info to errors/exceptions that bubble out from the system-under-test. + * + *
Includes special handling for {@link AssertionError} to ensure that test failures are + * correctly distinguished from test errors (all other errors/exceptions). + */ +@RequiresApi(19) +public final class AdditionalFailureInfo implements TestRule { + + private final AtomicReference<@NullableType String> info; + + public AdditionalFailureInfo() { + info = new AtomicReference<>(); + } + + /** + * Set the additional info to be added to any test failures. Pass {@code null} to skip adding any + * additional info. + * + *
Can be called from any thread.
+ */
+ public void setInfo(@Nullable String info) {
+ this.info.set(info);
+ }
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ try {
+ base.evaluate();
+ } catch (Throwable e) {
+ @Nullable String resourceInfo = AdditionalFailureInfo.this.info.get();
+ if (resourceInfo == null) {
+ throw e;
+ } else if (e instanceof AssertionError) {
+ // Deliberately prune the AssertionError from the causal chain and "replace" it with
+ // this new one by copying the stacktrace from old to new (so it looks like the new
+ // one was thrown from where the old one was thrown).
+ DiscreteAssertionError assertionError =
+ new DiscreteAssertionError(resourceInfo + "\n" + e.getMessage(), e.getCause());
+ assertionError.setStackTrace(e.getStackTrace());
+ throw assertionError;
+ } else {
+ Exception exception = new Exception(resourceInfo, e);
+ StackTraceElement[] stackTrace = exception.getStackTrace();
+ // Only include the top line of the stack trace (this method) - the rest will be
+ // uninteresting JUnit framework calls that obscure the true test failure.
+ if (stackTrace.length > 0) {
+ exception.setStackTrace(
+ Util.nullSafeArrayCopyOfRange(stackTrace, /* from= */ 0, /* to= */ 1));
+ }
+ throw exception;
+ }
+ }
+ }
+ };
+ }
+
+ /** An {@link AssertionError} that doesn't print its class name in stack traces. */
+ @SuppressWarnings("OverrideThrowableToString")
+ private static class DiscreteAssertionError extends AssertionError {
+
+ public DiscreteAssertionError(@Nullable String message, @Nullable Throwable cause) {
+ super(message, cause);
+ }
+
+ // To avoid printing the class name before the message. Inspired by Truth:
+ // https://github.com/google/truth/blob/152f3936/core/src/main/java/com/google/common/truth/Platform.java#L186-L192
+ @Override
+ public String toString() {
+ @Nullable String message = getLocalizedMessage();
+ return message != null ? message : super.toString();
+ }
+ }
+}
diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/DataSourceContractTest.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/DataSourceContractTest.java
index b0491af06a..ac11f11724 100644
--- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/DataSourceContractTest.java
+++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/DataSourceContractTest.java
@@ -16,11 +16,12 @@
package com.google.android.exoplayer2.testutil;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
-import static com.google.common.truth.Truth.assertWithMessage;
+import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import android.net.Uri;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
@@ -31,6 +32,7 @@ import java.io.IOException;
import java.util.List;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
/**
@@ -47,8 +49,11 @@ import org.junit.Test;
* in the implementation. The test should be overridden in the subclass and annotated {@link
* Ignore}, with a link to an issue to track fixing the implementation and un-ignoring the test.
*/
+@RequiresApi(19)
public abstract class DataSourceContractTest {
+ @Rule public final AdditionalFailureInfo additionalFailureInfo = new AdditionalFailureInfo();
+
/** Creates and returns an instance of the {@link DataSource}. */
protected abstract DataSource createDataSource();
@@ -74,19 +79,20 @@ public abstract class DataSourceContractTest {
public void unboundedDataSpec_readEverything() throws Exception {
ImmutableList JUnit's one is deprecated in favour of using {@link Assert#assertThrows}, but we need a
+ * {@link Rule} because we need to assert on the exception after it's been transformed by
+ * the {@link AdditionalFailureInfo} rule.
+ */
+ private static class ExpectedException implements TestRule {
+
+ @Nullable private Consumer A failure in this {@link Consumer} will cause the test to fail.
+ */
+ public void setAssertions(Consumer