diff --git a/library/datasource/src/androidTest/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceContractTest.java b/library/datasource/src/androidTest/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceContractTest.java index 0fb570f8b9..0976234a63 100644 --- a/library/datasource/src/androidTest/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceContractTest.java +++ b/library/datasource/src/androidTest/java/com/google/android/exoplayer2/upstream/DefaultHttpDataSourceContractTest.java @@ -20,6 +20,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.testutil.DataSourceContractTest; import com.google.android.exoplayer2.testutil.HttpDataSourceTestEnv; import com.google.common.collect.ImmutableList; +import org.junit.Ignore; import org.junit.Rule; import org.junit.runner.RunWith; @@ -43,4 +44,8 @@ public class DefaultHttpDataSourceContractTest extends DataSourceContractTest { protected Uri getNotFoundUri() { return Uri.parse(httpDataSourceTestEnv.getNonexistentUrl()); } + + @Override + @Ignore("internal b/205811776") + public void getResponseHeaders_noNullKeysOrValues() {} } 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 b119512840..07faf15a6d 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 @@ -19,6 +19,7 @@ import static com.google.android.exoplayer2.util.Assertions.checkArgument; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Util.castNonNull; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -39,10 +40,12 @@ import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; +import com.google.common.base.Ascii; import com.google.common.collect.ImmutableList; import java.io.IOException; import java.util.Arrays; import java.util.List; +import java.util.Map; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.junit.Ignore; import org.junit.Rule; @@ -503,6 +506,62 @@ public abstract class DataSourceContractTest { assertThat(dataSource.getUri()).isNull(); } + @Test + public void getResponseHeaders_noNullKeysOrValues() throws Exception { + ImmutableList resources = getTestResources(); + Assertions.checkArgument(!resources.isEmpty(), "Must provide at least one test resource."); + + for (int i = 0; i < resources.size(); i++) { + additionalFailureInfo.setInfo(getFailureLabel(resources, i)); + TestResource resource = resources.get(i); + DataSource dataSource = createDataSource(); + try { + dataSource.open(new DataSpec(resource.getUri())); + + Map> responseHeaders = dataSource.getResponseHeaders(); + assertThat(responseHeaders).doesNotContainKey(null); + assertThat(responseHeaders.values()).doesNotContain(null); + for (List value : responseHeaders.values()) { + assertThat(value).doesNotContain(null); + } + } finally { + dataSource.close(); + } + additionalFailureInfo.setInfo(null); + } + } + + @Test + public void getResponseHeaders_caseInsensitive() throws Exception { + ImmutableList resources = getTestResources(); + Assertions.checkArgument(!resources.isEmpty(), "Must provide at least one test resource."); + + for (int i = 0; i < resources.size(); i++) { + additionalFailureInfo.setInfo(getFailureLabel(resources, i)); + TestResource resource = resources.get(i); + DataSource dataSource = createDataSource(); + try { + dataSource.open(new DataSpec(resource.getUri())); + + Map> responseHeaders = dataSource.getResponseHeaders(); + for (String key : responseHeaders.keySet()) { + // TODO(internal b/205811776): Remove this when DefaultHttpDataSource is fixed to not + // return a null key. + if (key == null) { + continue; + } + String caseFlippedKey = invertAsciiCaseOfEveryOtherCharacter(key); + assertWithMessage("key='%s', caseFlippedKey='%s'", key, caseFlippedKey) + .that(responseHeaders.get(caseFlippedKey)) + .isEqualTo(responseHeaders.get(key)); + } + } finally { + dataSource.close(); + } + additionalFailureInfo.setInfo(null); + } + } + @Test public void getResponseHeaders_isEmptyWhileNotOpen() throws Exception { ImmutableList resources = getTestResources(); @@ -548,6 +607,28 @@ public abstract class DataSourceContractTest { } } + private static String invertAsciiCaseOfEveryOtherCharacter(String input) { + StringBuilder result = new StringBuilder(); + for (int i = 0; i < input.length(); i++) { + result.append(i % 2 == 0 ? invertAsciiCase(input.charAt(i)) : input.charAt(i)); + } + return result.toString(); + } + + /** + * Returns {@code c} in the opposite case if it's an ASCII character, otherwise returns {@code c} + * unchanged. + */ + private static char invertAsciiCase(char c) { + if (Ascii.isUpperCase(c)) { + return Ascii.toLowerCase(c); + } else if (Ascii.isLowerCase(c)) { + return Ascii.toUpperCase(c); + } else { + return c; + } + } + /** Information about a resource that can be used to test the {@link DataSource} instance. */ public static final class TestResource {