diff --git a/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java b/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java index ccd0da4e19..c3c7b00149 100644 --- a/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java +++ b/extensions/flac/src/androidTest/java/com/google/android/exoplayer2/ext/flac/FlacPlaybackTest.java @@ -34,6 +34,7 @@ import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.ProgressiveMediaSource; import com.google.android.exoplayer2.testutil.CapturingAudioSink; +import com.google.android.exoplayer2.testutil.DumpFileAsserts; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import org.junit.Before; import org.junit.Test; @@ -80,8 +81,8 @@ public class FlacPlaybackTest { throw testPlaybackRunnable.playbackException; } - audioSink.assertOutput( - ApplicationProvider.getApplicationContext(), fileName + ".audiosink.dump"); + DumpFileAsserts.assertOutput( + ApplicationProvider.getApplicationContext(), audioSink, fileName + ".audiosink.dump"); } private static class TestPlaybackRunnable implements Player.EventListener, Runnable { diff --git a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/WebvttExtractorTest.java b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/WebvttExtractorTest.java index 5f1169e222..5da804a996 100644 --- a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/WebvttExtractorTest.java +++ b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/WebvttExtractorTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.extractor.ExtractorInput; +import com.google.android.exoplayer2.testutil.DumpFileAsserts; import com.google.android.exoplayer2.testutil.FakeExtractorInput; import com.google.android.exoplayer2.testutil.FakeExtractorOutput; import com.google.android.exoplayer2.testutil.TestUtil; @@ -83,8 +84,10 @@ public class WebvttExtractorTest { // The output has a ~5s sampleTime and a large, negative subsampleOffset because the cue // timestamps are ~10 days ahead of the PTS (due to wrapping) so the offset is used to ensure // they're rendered at the right time. - output.assertOutput( - ApplicationProvider.getApplicationContext(), "webvtt/with_x-timestamp-map_header.dump"); + DumpFileAsserts.assertOutput( + ApplicationProvider.getApplicationContext(), + output, + "webvtt/with_x-timestamp-map_header.dump"); } private static boolean sniffData(byte[] data) throws IOException { diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/CapturingAudioSink.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/CapturingAudioSink.java index ead5bd24d9..bf1ffdce0e 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/CapturingAudioSink.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/CapturingAudioSink.java @@ -15,18 +15,11 @@ */ package com.google.android.exoplayer2.testutil; -import static com.google.common.truth.Truth.assertWithMessage; - -import android.content.Context; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.audio.AudioSink; import com.google.android.exoplayer2.audio.ForwardingAudioSink; -import com.google.android.exoplayer2.util.Assertions; -import java.io.File; -import java.io.IOException; -import java.io.PrintWriter; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; @@ -35,15 +28,6 @@ import java.util.List; /** A {@link ForwardingAudioSink} that captures configuration, discontinuity and buffer events. */ public final class CapturingAudioSink extends ForwardingAudioSink implements Dumper.Dumpable { - /** - * If true, makes {@link #assertOutput(Context, String)} method write the output to a file, rather - * than validating that the output matches the dump file. - * - *

The output file is written to the test apk's external storage directory, which is typically: - * {@code /sdcard/Android/data/${package-under-test}.test/files/}. - */ - private static final boolean WRITE_DUMP = false; - private final List interceptedData; @Nullable private ByteBuffer currentBuffer; @@ -98,30 +82,6 @@ public final class CapturingAudioSink extends ForwardingAudioSink implements Dum super.reset(); } - /** - * Asserts that dump of this sink is equal to expected dump which is read from {@code dumpFile}. - * - *

If assertion fails because of an intended change in the output or a new dump file needs to - * be created, set {@link #WRITE_DUMP} flag to true and run the test again. Instead of assertion, - * actual dump will be written to {@code dumpFile}. This new dump file needs to be copied to the - * project, {@code library/src/androidTest/assets} folder manually. - */ - public void assertOutput(Context context, String dumpFile) throws IOException { - String actual = new Dumper().add(this).toString(); - - if (WRITE_DUMP) { - File directory = context.getExternalFilesDir(null); - File file = new File(directory, dumpFile); - Assertions.checkStateNotNull(file.getParentFile()).mkdirs(); - PrintWriter out = new PrintWriter(file); - out.print(actual); - out.close(); - } else { - String expected = TestUtil.getString(context, dumpFile); - assertWithMessage(dumpFile).that(actual).isEqualTo(expected); - } - } - @Override public void dump(Dumper dumper) { for (int i = 0; i < interceptedData.size(); i++) { diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/DumpFileAsserts.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/DumpFileAsserts.java new file mode 100644 index 0000000000..42884ec6f4 --- /dev/null +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/DumpFileAsserts.java @@ -0,0 +1,110 @@ +/* + * 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 static com.google.common.truth.Truth.assertWithMessage; + +import android.content.Context; +import androidx.annotation.IntDef; +import com.google.android.exoplayer2.util.Assertions; +import com.google.common.base.StandardSystemProperty; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Helper class to enable assertions based on golden-data dump files. + * + *

Allows the golden files to be easily updated with new data (see more info in the docs on + * {@link #DUMP_FILE_ACTION}). + * + *

Compatible with {@link Dumper.Dumpable} but can also be used directly with Strings generated + * through different means. + */ +public class DumpFileAsserts { + + private static final String DUMP_UPDATE_INSTRUCTIONS = + "To update the dump file, change DumpFileAsserts#DUMP_FILE_ACTION to WRITE_TO_LOCAL (for" + + " Robolectric tests) or WRITE_TO_DEVICE (for instrumentation tests) and re-run the" + + " test."; + + /** Possible actions to take with the dumps passed to {@link #assertOutput}. */ + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef( + flag = true, + value = {COMPARE_WITH_EXISTING, WRITE_TO_LOCAL, WRITE_TO_DEVICE}) + private @interface DumpFilesAction {} + /** Compare output with existing dump file. */ + private static final int COMPARE_WITH_EXISTING = 0; + /** + * Write output to the project folder {@code testdata/src/test}. + * + *

Enabling this option works when tests are run in Android Studio. It may not work when the + * tests are run in another environment. + */ + private static final int WRITE_TO_LOCAL = 1; + /** Write output to folder {@code /storage/emulated/0/Android/data} of device. */ + private static final int WRITE_TO_DEVICE = 1 << 1; + + @DumpFilesAction private static final int DUMP_FILE_ACTION = COMPARE_WITH_EXISTING; + + private DumpFileAsserts() {} + + public static void assertOutput(Context context, Dumper.Dumpable actual, String dumpFile) + throws IOException { + assertOutput(context, new Dumper().add(actual).toString(), dumpFile); + } + + /** + * Asserts that {@code actual} is equal to the contents of {@code dumpFile}. + * + *

If the assertion fails because of an intended change in the output or a new dump file needs + * to be created, set {@link #DUMP_FILE_ACTION} to {@link #WRITE_TO_LOCAL} for local tests and to + * {@link #WRITE_TO_DEVICE} for instrumentation tests, and run the test again. Instead of + * assertion, {@code actual} will be written to {@code dumpFile}. For instrumentation tests, this + * new dump file needs to be copied to the project {@code testdata/src/test} folder manually. + */ + public static void assertOutput(Context context, String actual, String dumpFile) + throws IOException { + if (DUMP_FILE_ACTION == COMPARE_WITH_EXISTING) { + String expected; + try { + expected = TestUtil.getString(context, dumpFile); + } catch (FileNotFoundException e) { + throw new IOException("Dump file not found. " + DUMP_UPDATE_INSTRUCTIONS, e); + } + assertWithMessage( + "Actual data doesn't match dump file: %s\n%s", dumpFile, DUMP_UPDATE_INSTRUCTIONS) + .that(actual) + .isEqualTo(expected); + } else { + File file = + DUMP_FILE_ACTION == WRITE_TO_LOCAL + ? new File(StandardSystemProperty.USER_DIR.value(), "../../testdata/src/test") + : context.getExternalFilesDir(null); + file = new File(file, dumpFile); + Assertions.checkStateNotNull(file.getParentFile()).mkdirs(); + PrintWriter out = new PrintWriter(file); + out.print(actual); + out.close(); + } + } +} diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExtractorAsserts.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExtractorAsserts.java index 7411016177..c396e41f4a 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExtractorAsserts.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExtractorAsserts.java @@ -326,9 +326,11 @@ public final class ExtractorAsserts { FakeExtractorOutput extractorOutput = consumeTestData(extractor, input, 0, true, deduplicateConsecutiveFormats); if (simulateUnknownLength) { - extractorOutput.assertOutput(context, dumpFilesPrefix + UNKNOWN_LENGTH_EXTENSION); + DumpFileAsserts.assertOutput( + context, extractorOutput, dumpFilesPrefix + UNKNOWN_LENGTH_EXTENSION); } else { - extractorOutput.assertOutput(context, dumpFilesPrefix + ".0" + DUMP_EXTENSION); + DumpFileAsserts.assertOutput( + context, extractorOutput, dumpFilesPrefix + ".0" + DUMP_EXTENSION); } // Seeking to (timeUs=0, position=0) should always work, and cause the same data to be output. @@ -336,9 +338,11 @@ public final class ExtractorAsserts { input.reset(); consumeTestData(extractor, input, /* timeUs= */ 0, extractorOutput, false); if (simulateUnknownLength) { - extractorOutput.assertOutput(context, dumpFilesPrefix + UNKNOWN_LENGTH_EXTENSION); + DumpFileAsserts.assertOutput( + context, extractorOutput, dumpFilesPrefix + UNKNOWN_LENGTH_EXTENSION); } else { - extractorOutput.assertOutput(context, dumpFilesPrefix + ".0" + DUMP_EXTENSION); + DumpFileAsserts.assertOutput( + context, extractorOutput, dumpFilesPrefix + ".0" + DUMP_EXTENSION); } SeekMap seekMap = Assertions.checkNotNull(extractorOutput.seekMap); @@ -357,9 +361,11 @@ public final class ExtractorAsserts { extractorOutput.clearTrackOutputs(); consumeTestData(extractor, input, timeUs, extractorOutput, false); if (simulateUnknownLength && timeUs == 0) { - extractorOutput.assertOutput(context, dumpFilesPrefix + UNKNOWN_LENGTH_EXTENSION); + DumpFileAsserts.assertOutput( + context, extractorOutput, dumpFilesPrefix + UNKNOWN_LENGTH_EXTENSION); } else { - extractorOutput.assertOutput(context, dumpFilesPrefix + '.' + j + DUMP_EXTENSION); + DumpFileAsserts.assertOutput( + context, extractorOutput, dumpFilesPrefix + '.' + j + DUMP_EXTENSION); } } } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExtractorOutput.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExtractorOutput.java index 6df4fc4dd2..6253ae4daa 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExtractorOutput.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeExtractorOutput.java @@ -16,57 +16,17 @@ package com.google.android.exoplayer2.testutil; import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assertWithMessage; -import android.content.Context; import android.util.SparseArray; -import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.SeekMap; -import com.google.android.exoplayer2.util.Assertions; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.PrintWriter; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** A fake {@link ExtractorOutput}. */ public final class FakeExtractorOutput implements ExtractorOutput, Dumper.Dumpable { - private static final String DUMP_UPDATE_INSTRUCTIONS = - "To update the dump file, change FakeExtractorOutput#DUMP_FILE_ACTION to WRITE_TO_LOCAL (for" - + " Robolectric tests) or WRITE_TO_DEVICE (for instrumentation tests) and re-run the" - + " test."; - - /** - * Possible actions to take with the dumps generated from this {@code FakeExtractorOutput} in - * {@link #assertOutput(Context, String)}. - */ - @Documented - @Retention(RetentionPolicy.SOURCE) - @IntDef( - flag = true, - value = {COMPARE_WITH_EXISTING, WRITE_TO_LOCAL, WRITE_TO_DEVICE}) - private @interface DumpFilesAction {} - /** Compare output with existing dump file. */ - private static final int COMPARE_WITH_EXISTING = 0; - /** - * Write output to the project folder {@code testdata/src/test/assets}. - * - *

Enabling this option works when tests are run in Android Studio. It may not work when the - * tests are run in another environment. - */ - private static final int WRITE_TO_LOCAL = 1; - /** Write output to folder {@code /storage/emulated/0/Android/data} of device. */ - private static final int WRITE_TO_DEVICE = 1 << 1; - - @DumpFilesAction private static final int DUMP_FILE_ACTION = COMPARE_WITH_EXISTING; - public final SparseArray trackOutputs; private final FakeTrackOutput.Factory trackOutputFactory; @@ -124,44 +84,6 @@ public final class FakeExtractorOutput implements ExtractorOutput, Dumper.Dumpab } } - /** - * Asserts that dump of this {@link FakeExtractorOutput} is equal to expected dump which is read - * from {@code dumpFile}. - * - *

If assertion fails because of an intended change in the output or a new dump file needs to - * be created, set {@link #DUMP_FILE_ACTION} to {@link #WRITE_TO_LOCAL} for local tests and to - * {@link #WRITE_TO_DEVICE} for instrumentation tests, and run the test again. Instead of - * assertion, actual dump will be written to {@code dumpFile}. For instrumentation tests, this new - * dump file needs to be copied to the project {@code testdata/src/test/assets} folder manually. - */ - public void assertOutput(Context context, String dumpFile) throws IOException { - String actual = new Dumper().add(this).toString(); - - if (DUMP_FILE_ACTION == COMPARE_WITH_EXISTING) { - String expected; - try { - expected = TestUtil.getString(context, dumpFile); - } catch (FileNotFoundException e) { - throw new IOException("Dump file not found. " + DUMP_UPDATE_INSTRUCTIONS, e); - } - assertWithMessage( - "Extractor output doesn't match dump file: %s\n%s", - dumpFile, DUMP_UPDATE_INSTRUCTIONS) - .that(actual) - .isEqualTo(expected); - } else { - File file = - DUMP_FILE_ACTION == WRITE_TO_LOCAL - ? new File(System.getProperty("user.dir"), "../../testdata/src/test/assets") - : context.getExternalFilesDir(null); - file = new File(file, dumpFile); - Assertions.checkStateNotNull(file.getParentFile()).mkdirs(); - PrintWriter out = new PrintWriter(file); - out.print(actual); - out.close(); - } - } - @Override public void dump(Dumper dumper) { if (seekMap != null) {