Add analysisException field to TransformationTestResult new Builder.

Having this in place means that analysis exceptions can be swallowed
or thrown as needed.

PiperOrigin-RevId: 434788802
This commit is contained in:
samrobinson 2022-03-15 17:26:52 +00:00 committed by Ian Baker
parent c2af562922
commit e8293915b3
3 changed files with 184 additions and 36 deletions

View file

@ -15,31 +15,117 @@
*/
package com.google.android.exoplayer2.transformer;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
/** A test only class for holding the details of a test transformation. */
public class TransformationTestResult {
/** Represents an unset or unknown SSIM score. */
public static final double SSIM_UNSET = -1.0d;
/** A builder for {@link TransformationTestResult}. */
public static class Builder {
private final TransformationResult transformationResult;
@Nullable private String filePath;
@Nullable private Exception analysisException;
private long elapsedTimeMs;
private double ssim;
/** Creates a new {@link Builder}. */
public Builder(TransformationResult transformationResult) {
this.transformationResult = transformationResult;
this.elapsedTimeMs = C.TIME_UNSET;
this.ssim = SSIM_UNSET;
}
/**
* Sets the file path of the output file.
*
* <p>{@code null} represents an unset or unknown value.
*
* @param filePath The path.
* @return This {@link Builder}.
*/
public Builder setFilePath(@Nullable String filePath) {
this.filePath = filePath;
return this;
}
/**
* Sets the amount of time taken to perform the transformation in milliseconds. {@link
* C#TIME_UNSET} if unset.
*
* <p>{@link C#TIME_UNSET} represents an unset or unknown value.
*
* @param elapsedTimeMs The time, in ms.
* @return This {@link Builder}.
*/
public Builder setElapsedTimeMs(long elapsedTimeMs) {
this.elapsedTimeMs = elapsedTimeMs;
return this;
}
/**
* Sets the SSIM of the output file, compared to input file.
*
* <p>{@link #SSIM_UNSET} represents an unset or unknown value.
*
* @param ssim The structural similarity index.
* @return This {@link Builder}.
*/
public Builder setSsim(double ssim) {
this.ssim = ssim;
return this;
}
/**
* Sets an {@link Exception} that occurred during post-transformation analysis.
*
* <p>{@code null} represents an unset or unknown value.
*
* @param analysisException The {@link Exception} thrown during analysis.
* @return This {@link Builder}.
*/
public Builder setAnalysisException(@Nullable Exception analysisException) {
this.analysisException = analysisException;
return this;
}
/** Builds the {@link TransformationTestResult} instance. */
public TransformationTestResult build() {
return new TransformationTestResult(
transformationResult, filePath, elapsedTimeMs, ssim, analysisException);
}
}
public final TransformationResult transformationResult;
public final String filePath;
/** The amount of time taken to perform the transformation in milliseconds. */
public final long transformationDurationMs;
@Nullable public final String filePath;
/**
* The amount of time taken to perform the transformation in milliseconds. {@link C#TIME_UNSET} if
* unset.
*/
public final long elapsedTimeMs;
/** The SSIM score of the transformation, {@link #SSIM_UNSET} if unavailable. */
public final double ssim;
/**
* The {@link Exception} that was thrown during post-tranformation analysis, or {@code null} if
* nothing was thrown.
*/
@Nullable public final Exception analysisException;
public TransformationTestResult(
TransformationResult transformationResult, String filePath, long transformationDurationMs) {
this(transformationResult, filePath, transformationDurationMs, /* ssim= */ SSIM_UNSET);
}
public TransformationTestResult(
private TransformationTestResult(
TransformationResult transformationResult,
String filePath,
long transformationDurationMs,
double ssim) {
@Nullable String filePath,
long elapsedTimeMs,
double ssim,
@Nullable Exception analysisException) {
this.transformationResult = transformationResult;
this.filePath = filePath;
this.transformationDurationMs = transformationDurationMs;
this.elapsedTimeMs = elapsedTimeMs;
this.ssim = ssim;
this.analysisException = analysisException;
}
}

View file

@ -39,6 +39,7 @@ import org.json.JSONObject;
/** An android instrumentation test runner for {@link Transformer}. */
public class TransformerAndroidTestRunner {
private static final String TAG_PREFIX = "TransformerAndroidTest_";
/** The default transformation timeout value. */
public static final int DEFAULT_TIMEOUT_SECONDS = 120;
@ -49,6 +50,7 @@ public class TransformerAndroidTestRunner {
private final Transformer transformer;
private boolean calculateSsim;
private int timeoutSeconds;
private boolean suppressAnalysisExceptions;
/**
* Creates a {@link Builder}.
@ -93,9 +95,29 @@ public class TransformerAndroidTestRunner {
return this;
}
/**
* Sets whether the runner should suppress any {@link Exception} that occurs as a result of
* post-transformation analysis, such as SSIM calculation.
*
* <p>Regardless of this value, analysis exceptions are attached to the analysis file.
*
* <p>It's recommended to add a comment explaining why this suppression is needed, ideally with
* a bug number.
*
* <p>The default value is {@code false}.
*
* @param suppressAnalysisExceptions Whether to suppress analysis exceptions.
* @return This {@link Builder}.
*/
public Builder setSuppressAnalysisExceptions(boolean suppressAnalysisExceptions) {
this.suppressAnalysisExceptions = suppressAnalysisExceptions;
return this;
}
/** Builds the {@link TransformerAndroidTestRunner}. */
public TransformerAndroidTestRunner build() {
return new TransformerAndroidTestRunner(context, transformer, timeoutSeconds, calculateSsim);
return new TransformerAndroidTestRunner(
context, transformer, timeoutSeconds, calculateSsim, suppressAnalysisExceptions);
}
}
@ -103,13 +125,19 @@ public class TransformerAndroidTestRunner {
private final Transformer transformer;
private final int timeoutSeconds;
private final boolean calculateSsim;
private final boolean suppressAnalysisExceptions;
private TransformerAndroidTestRunner(
Context context, Transformer transformer, int timeoutSeconds, boolean calculateSsim) {
Context context,
Transformer transformer,
int timeoutSeconds,
boolean calculateSsim,
boolean suppressAnalysisExceptions) {
this.context = context;
this.transformer = transformer;
this.timeoutSeconds = timeoutSeconds;
this.calculateSsim = calculateSsim;
this.suppressAnalysisExceptions = suppressAnalysisExceptions;
}
/**
@ -126,6 +154,9 @@ public class TransformerAndroidTestRunner {
try {
TransformationTestResult transformationTestResult = runInternal(testId, uriString);
resultJson.put("transformationResult", getTestResultJson(transformationTestResult));
if (!suppressAnalysisExceptions && transformationTestResult.analysisException != null) {
throw transformationTestResult.analysisException;
}
return transformationTestResult;
} catch (Exception e) {
resultJson.put("exception", getExceptionJson(e));
@ -147,11 +178,10 @@ public class TransformerAndroidTestRunner {
* complete.
* @throws TransformationException If an exception occurs as a result of the transformation.
* @throws IllegalArgumentException If the path is invalid.
* @throws IllegalStateException If this method is called from the wrong thread.
* @throws IllegalStateException If a transformation is already in progress.
* @throws Exception If the transformation did not complete.
* @throws IllegalStateException If an unexpected exception occurs when starting a transformation.
*/
private TransformationTestResult runInternal(String testId, String uriString) throws Exception {
private TransformationTestResult runInternal(String testId, String uriString)
throws InterruptedException, IOException, TimeoutException, TransformationException {
AtomicReference<@NullableType TransformationException> transformationExceptionReference =
new AtomicReference<>();
AtomicReference<@NullableType Exception> unexpectedExceptionReference = new AtomicReference<>();
@ -201,11 +231,12 @@ public class TransformerAndroidTestRunner {
if (!countDownLatch.await(timeoutSeconds, SECONDS)) {
throw new TimeoutException("Transformer timed out after " + timeoutSeconds + " seconds.");
}
long transformationDurationMs = SystemClock.DEFAULT.elapsedRealtime() - startTimeMs;
long elapsedTimeMs = SystemClock.DEFAULT.elapsedRealtime() - startTimeMs;
@Nullable Exception unexpectedException = unexpectedExceptionReference.get();
if (unexpectedException != null) {
throw unexpectedException;
throw new IllegalStateException(
"Unexpected exception starting the transformer.", unexpectedException);
}
@Nullable
@ -222,16 +253,31 @@ public class TransformerAndroidTestRunner {
.setFileSizeBytes(outputVideoFile.length())
.build();
if (!calculateSsim) {
return new TransformationTestResult(
transformationResult, outputVideoFile.getPath(), transformationDurationMs);
TransformationTestResult.Builder resultBuilder =
new TransformationTestResult.Builder(transformationResult)
.setFilePath(outputVideoFile.getPath())
.setElapsedTimeMs(elapsedTimeMs);
try {
if (calculateSsim) {
double ssim =
SsimHelper.calculate(
context, /* expectedVideoPath= */ uriString, outputVideoFile.getPath());
resultBuilder.setSsim(ssim);
}
} catch (InterruptedException interruptedException) {
// InterruptedException is a special unexpected case because it is not related to Ssim
// calculation, so it should be thrown, rather than processed as part of the
// TransformationTestResult.
throw interruptedException;
} catch (Exception analysisException) {
// Catch all (checked and unchecked) exceptions throw by the SsimHelper and process them as
// part of the TransformationTestResult.
resultBuilder.setAnalysisException(analysisException);
Log.e(TAG_PREFIX + testId, "SSIM calculation failed.", analysisException);
}
double ssim =
SsimHelper.calculate(
context, /* expectedVideoPath= */ uriString, outputVideoFile.getPath());
return new TransformationTestResult(
transformationResult, outputVideoFile.getPath(), transformationDurationMs, ssim);
return resultBuilder.build();
}
private static void writeTestSummaryToFile(Context context, String testId, JSONObject resultJson)
@ -241,7 +287,7 @@ public class TransformerAndroidTestRunner {
String analysisContents = resultJson.toString(/* indentSpaces= */ 2);
// Log contents as well as writing to file, for easier visibility on individual device testing.
Log.i("TransformerAndroidTest_" + testId, analysisContents);
Log.i(TAG_PREFIX + testId, analysisContents);
File analysisFile =
AndroidTestUtil.createExternalCacheFile(context, /* fileName= */ testId + "-result.txt");
@ -272,10 +318,16 @@ public class TransformerAndroidTestRunner {
if (transformationResult.averageVideoBitrate != C.RATE_UNSET_INT) {
transformationResultJson.put("averageVideoBitrate", transformationResult.averageVideoBitrate);
}
if (testResult.elapsedTimeMs != C.TIME_UNSET) {
transformationResultJson.put("elapsedTimeMs", testResult.elapsedTimeMs);
}
if (testResult.ssim != TransformationTestResult.SSIM_UNSET) {
transformationResultJson.put("ssim", testResult.ssim);
}
transformationResultJson.put("transformationDurationMs", testResult.transformationDurationMs);
if (testResult.analysisException != null) {
transformationResultJson.put(
"analysisException", getExceptionJson(testResult.analysisException));
}
return transformationResultJson;
}

View file

@ -47,8 +47,10 @@ public class TransformationTest {
Context context = ApplicationProvider.getApplicationContext();
Transformer transformer = new Transformer.Builder(context).build();
// TODO(b/223381524): Enable Ssim calculation after fixing queueInputBuffer exception.
// TODO(b/223381524): Remove analysis failure suppression after ssim calculation doesn't fail.
new TransformerAndroidTestRunner.Builder(context, transformer)
.setCalculateSsim(true)
.setSuppressAnalysisExceptions(true)
.build()
.run(testId, MP4_ASSET_WITH_INCREASING_TIMESTAMPS_URI_STRING);
}
@ -87,8 +89,10 @@ public class TransformationTest {
}
})
.build();
// TODO(b/223381524): Enable Ssim calculation after fixing queueInputBuffer exception.
// TODO(b/223381524): Remove analysis failure suppression after ssim calculation doesn't fail.
new TransformerAndroidTestRunner.Builder(context, transformer)
.setCalculateSsim(true)
.setSuppressAnalysisExceptions(true)
.build()
.run(testId, MP4_ASSET_WITH_INCREASING_TIMESTAMPS_URI_STRING);
}
@ -97,10 +101,12 @@ public class TransformationTest {
public void transform4K60() throws Exception {
final String testId = TAG + "_transform4K60";
// TODO(b/223381524): Enable Ssim calculation after fixing queueInputBuffer exception.
Context context = ApplicationProvider.getApplicationContext();
Transformer transformer = new Transformer.Builder(context).build();
// TODO(b/223381524): Remove analysis failure suppression after ssim calculation doesn't fail.
new TransformerAndroidTestRunner.Builder(context, transformer)
.setCalculateSsim(true)
.setSuppressAnalysisExceptions(true)
.build()
.run(testId, MP4_REMOTE_4K60_PORTRAIT_URI_STRING);
}
@ -109,10 +115,12 @@ public class TransformationTest {
public void transformNoAudio() throws Exception {
final String testId = TAG + "_transformNoAudio";
// TODO(b/223381524): Enable Ssim calculation after fixing queueInputBuffer exception.
Context context = ApplicationProvider.getApplicationContext();
Transformer transformer = new Transformer.Builder(context).setRemoveAudio(true).build();
// TODO(b/223381524): Remove analysis failure suppression after ssim calculation doesn't fail.
new TransformerAndroidTestRunner.Builder(context, transformer)
.setCalculateSsim(true)
.setSuppressAnalysisExceptions(true)
.build()
.run(testId, MP4_ASSET_WITH_INCREASING_TIMESTAMPS_URI_STRING);
}
@ -144,8 +152,10 @@ public class TransformationTest {
.setTransformationRequest(
new TransformationRequest.Builder().setFlattenForSlowMotion(true).build())
.build();
// TODO(b/223381524): Enable Ssim calculation after fixing queueInputBuffer exception.
// TODO(b/223381524): Remove analysis failure suppression after ssim calculation doesn't fail.
new TransformerAndroidTestRunner.Builder(context, transformer)
.setCalculateSsim(true)
.setSuppressAnalysisExceptions(true)
.build()
.run(testId, MP4_ASSET_SEF_URI_STRING);
}