Create a DumpFileAsserts from the dumpfile logic in FakeExtractorOutput

Also use it to replace the same logic in CapturingAudioSink

PiperOrigin-RevId: 325969455
This commit is contained in:
ibaker 2020-08-11 08:26:30 +01:00 committed by kim-vde
parent 361e5d9326
commit da4d55635c
6 changed files with 130 additions and 128 deletions

View file

@ -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 {

View file

@ -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 {

View file

@ -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.
*
* <p>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<Dumper.Dumpable> 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}.
*
* <p>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++) {

View file

@ -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.
*
* <p>Allows the golden files to be easily updated with new data (see more info in the docs on
* {@link #DUMP_FILE_ACTION}).
*
* <p>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}.
*
* <p>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}.
*
* <p>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();
}
}
}

View file

@ -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);
}
}
}

View file

@ -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}.
*
* <p>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<FakeTrackOutput> 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}.
*
* <p>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) {