blob: 5972fd7080f9be99ab115e1e285850ef33453d3f [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package androidx.javascriptengine;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import com.google.common.util.concurrent.ListenableFuture;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.nio.charset.StandardCharsets;
import java.util.Vector;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/** Instrumentation test for JavaScriptSandbox. */
@RunWith(AndroidJUnit4.class)
public class WebViewJavaScriptSandboxTest {
// This value is somewhat arbitrary. It might need bumping if V8 snapshots become significantly
// larger in future. However, we don't want it too large as that will make the tests slower and
// require more memory.
private static final long REASONABLE_HEAP_SIZE = 100 * 1024 * 1024;
@Before
public void setUp() throws Throwable {
Assume.assumeTrue(JavaScriptSandbox.isSupported());
}
@Test
@MediumTest
public void testSimpleJsEvaluation() throws Throwable {
final String code = "\"PASS\"";
final String expected = "PASS";
Context context = ApplicationProvider.getApplicationContext();
ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
JavaScriptSandbox.createConnectedInstanceAsync(context);
try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS);
JavaScriptIsolate jsIsolate = jsSandbox.createIsolate()) {
ListenableFuture<String> resultFuture = jsIsolate.evaluateJavaScriptAsync(code);
String result = resultFuture.get(5, TimeUnit.SECONDS);
Assert.assertEquals(expected, result);
}
}
@Test
@MediumTest
public void testClosingOneIsolate() throws Throwable {
final String code = "'PASS'";
final String expected = "PASS";
Context context = ApplicationProvider.getApplicationContext();
ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
JavaScriptSandbox.createConnectedInstanceAsync(context);
try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS);
JavaScriptIsolate jsIsolate1 = jsSandbox.createIsolate()) {
JavaScriptIsolate jsIsolate2 = jsSandbox.createIsolate();
jsIsolate2.close();
ListenableFuture<String> resultFuture = jsIsolate1.evaluateJavaScriptAsync(code);
String result = resultFuture.get(5, TimeUnit.SECONDS);
Assert.assertEquals(expected, result);
}
}
@Test
@MediumTest
public void testEvaluationInTwoIsolates() throws Throwable {
final String code1 = "this.x = 'PASS';\n";
final String expected1 = "PASS";
final String code2 = "this.x = 'SUPER_PASS';\n";
final String expected2 = "SUPER_PASS";
Context context = ApplicationProvider.getApplicationContext();
ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
JavaScriptSandbox.createConnectedInstanceAsync(context);
try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS);
JavaScriptIsolate jsIsolate1 = jsSandbox.createIsolate();
JavaScriptIsolate jsIsolate2 = jsSandbox.createIsolate()) {
ListenableFuture<String> resultFuture1 = jsIsolate1.evaluateJavaScriptAsync(code1);
String result1 = resultFuture1.get(5, TimeUnit.SECONDS);
ListenableFuture<String> resultFuture2 = jsIsolate2.evaluateJavaScriptAsync(code2);
String result2 = resultFuture2.get(5, TimeUnit.SECONDS);
Assert.assertEquals(expected1, result1);
Assert.assertEquals(expected2, result2);
}
}
@Test
@MediumTest
public void testTwoIsolatesDoNotShareEnvironment() throws Throwable {
final String code1 = "this.y = 'PASS';\n";
final String expected1 = "PASS";
final String code2 = "this.y = this.y + ' PASS';\n";
final String expected2 = "undefined PASS";
Context context = ApplicationProvider.getApplicationContext();
ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
JavaScriptSandbox.createConnectedInstanceAsync(context);
try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS);
JavaScriptIsolate jsIsolate1 = jsSandbox.createIsolate();
JavaScriptIsolate jsIsolate2 = jsSandbox.createIsolate()) {
ListenableFuture<String> resultFuture1 = jsIsolate1.evaluateJavaScriptAsync(code1);
String result1 = resultFuture1.get(5, TimeUnit.SECONDS);
ListenableFuture<String> resultFuture2 = jsIsolate2.evaluateJavaScriptAsync(code2);
String result2 = resultFuture2.get(5, TimeUnit.SECONDS);
Assert.assertEquals(expected1, result1);
Assert.assertEquals(expected2, result2);
}
}
@Test
@MediumTest
public void testTwoExecutionsShareEnvironment() throws Throwable {
final String code1 = "this.z = 'PASS';\n";
final String expected1 = "PASS";
final String code2 = "this.z = this.z + ' PASS';\n";
final String expected2 = "PASS PASS";
Context context = ApplicationProvider.getApplicationContext();
ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
JavaScriptSandbox.createConnectedInstanceAsync(context);
try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS);
JavaScriptIsolate jsIsolate1 = jsSandbox.createIsolate()) {
ListenableFuture<String> resultFuture1 = jsIsolate1.evaluateJavaScriptAsync(code1);
String result1 = resultFuture1.get(5, TimeUnit.SECONDS);
ListenableFuture<String> resultFuture2 = jsIsolate1.evaluateJavaScriptAsync(code2);
String result2 = resultFuture2.get(5, TimeUnit.SECONDS);
Assert.assertEquals(expected1, result1);
Assert.assertEquals(expected2, result2);
}
}
@Test
@MediumTest
public void testJsEvaluationError() throws Throwable {
final String code = "throw new WebAssembly.LinkError('RandomLinkError');";
final String contains = "RandomLinkError";
Context context = ApplicationProvider.getApplicationContext();
ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
JavaScriptSandbox.createConnectedInstanceAsync(context);
try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS);
JavaScriptIsolate jsIsolate = jsSandbox.createIsolate()) {
ListenableFuture<String> resultFuture = jsIsolate.evaluateJavaScriptAsync(code);
boolean isOfCorrectType = false;
String error = "";
try {
resultFuture.get(5, TimeUnit.SECONDS);
Assert.fail("Should have thrown.");
} catch (ExecutionException e) {
isOfCorrectType = e.getCause().getClass().equals(EvaluationFailedException.class);
error = e.getCause().getMessage();
}
Assert.assertTrue(isOfCorrectType);
Assert.assertTrue(error.contains(contains));
}
}
@Test
@MediumTest
public void testInfiniteLoop() throws Throwable {
final String code = "while(true){}";
Context context = ApplicationProvider.getApplicationContext();
ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
JavaScriptSandbox.createConnectedInstanceAsync(context);
try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS)) {
Assume.assumeTrue(
jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_ISOLATE_TERMINATION));
ListenableFuture<String> resultFuture;
try (JavaScriptIsolate jsIsolate = jsSandbox.createIsolate()) {
resultFuture = jsIsolate.evaluateJavaScriptAsync(code);
}
try {
resultFuture.get(5, TimeUnit.SECONDS);
Assert.fail("Should have thrown.");
} catch (ExecutionException e) {
if (!(e.getCause() instanceof IsolateTerminatedException)) {
throw e;
}
}
}
}
@Test
@MediumTest
public void testMultipleInfiniteLoops() throws Throwable {
final String code = "while(true){}";
final int num_of_evaluations = 10;
Context context = ApplicationProvider.getApplicationContext();
ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
JavaScriptSandbox.createConnectedInstanceAsync(context);
try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS)) {
Assume.assumeTrue(
jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_ISOLATE_TERMINATION));
Vector<ListenableFuture<String>> resultFutures = new Vector<ListenableFuture<String>>();
try (JavaScriptIsolate jsIsolate = jsSandbox.createIsolate()) {
for (int i = 0; i < num_of_evaluations; i++) {
ListenableFuture<String> resultFuture = jsIsolate.evaluateJavaScriptAsync(code);
resultFutures.add(resultFuture);
}
}
for (int i = 0; i < num_of_evaluations; i++) {
try {
resultFutures.elementAt(i).get(5, TimeUnit.SECONDS);
Assert.fail("Should have thrown.");
} catch (ExecutionException e) {
if (!(e.getCause() instanceof IsolateTerminatedException)) {
throw e;
}
}
}
}
}
@Test
@MediumTest
public void testSimpleArrayBuffer() throws Throwable {
final String provideString = "Hello World";
final byte[] bytes = provideString.getBytes(StandardCharsets.US_ASCII);
final String code = ""
+ "function ab2str(buf) {"
+ " return String.fromCharCode.apply(null, new Uint8Array(buf));"
+ "}"
+ "android.consumeNamedDataAsArrayBuffer(\"id-1\").then((value) => {"
+ " return ab2str(value);"
+ "});";
Context context = ApplicationProvider.getApplicationContext();
ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
JavaScriptSandbox.createConnectedInstanceAsync(context);
try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS);
JavaScriptIsolate jsIsolate = jsSandbox.createIsolate()) {
Assume.assumeTrue(
jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_PROMISE_RETURN));
Assume.assumeTrue(jsSandbox.isFeatureSupported(
JavaScriptSandbox.JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER));
boolean provideNamedDataReturn = jsIsolate.provideNamedData("id-1", bytes);
Assert.assertTrue(provideNamedDataReturn);
ListenableFuture<String> resultFuture1 = jsIsolate.evaluateJavaScriptAsync(code);
String result = resultFuture1.get(5, TimeUnit.SECONDS);
Assert.assertEquals(provideString, result);
}
}
@Test
@MediumTest
public void testArrayBufferWasmCompilation() throws Throwable {
final String success = "success";
// The bytes of a minimal WebAssembly module, courtesy of v8/test/cctest/test-api-wasm.cc
final byte[] bytes = {0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00};
final String code = ""
+ "android.consumeNamedDataAsArrayBuffer(\"id-1\").then((value) => {"
+ " return WebAssembly.compile(value).then((module) => {"
+ " return \"success\";"
+ " });"
+ "});";
Context context = ApplicationProvider.getApplicationContext();
ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
JavaScriptSandbox.createConnectedInstanceAsync(context);
try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS);
JavaScriptIsolate jsIsolate = jsSandbox.createIsolate()) {
Assume.assumeTrue(
jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_PROMISE_RETURN));
Assume.assumeTrue(jsSandbox.isFeatureSupported(
JavaScriptSandbox.JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER));
Assume.assumeTrue(
jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_WASM_COMPILATION));
boolean provideNamedDataReturn = jsIsolate.provideNamedData("id-1", bytes);
Assert.assertTrue(provideNamedDataReturn);
ListenableFuture<String> resultFuture1 = jsIsolate.evaluateJavaScriptAsync(code);
String result = resultFuture1.get(5, TimeUnit.SECONDS);
Assert.assertEquals(success, result);
}
}
@Test
@MediumTest
public void testPromiseReturn() throws Throwable {
final String code = "Promise.resolve(\"PASS\")";
final String expected = "PASS";
Context context = ApplicationProvider.getApplicationContext();
ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
JavaScriptSandbox.createConnectedInstanceAsync(context);
try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS);
JavaScriptIsolate jsIsolate = jsSandbox.createIsolate()) {
Assume.assumeTrue(
jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_PROMISE_RETURN));
ListenableFuture<String> resultFuture = jsIsolate.evaluateJavaScriptAsync(code);
String result = resultFuture.get(5, TimeUnit.SECONDS);
Assert.assertEquals(expected, result);
}
}
@Test
@MediumTest
public void testPromiseReturnLaterResolve() throws Throwable {
final String code1 = "var promiseResolve, promiseReject;"
+ "new Promise(function(resolve, reject){"
+ " promiseResolve = resolve;"
+ " promiseReject = reject;"
+ "});";
final String code2 = "promiseResolve(\"PASS\");";
final String expected = "PASS";
Context context = ApplicationProvider.getApplicationContext();
ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
JavaScriptSandbox.createConnectedInstanceAsync(context);
try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS);
JavaScriptIsolate jsIsolate = jsSandbox.createIsolate()) {
Assume.assumeTrue(
jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_PROMISE_RETURN));
ListenableFuture<String> resultFuture1 = jsIsolate.evaluateJavaScriptAsync(code1);
ListenableFuture<String> resultFuture2 = jsIsolate.evaluateJavaScriptAsync(code2);
String result = resultFuture1.get(5, TimeUnit.SECONDS);
Assert.assertEquals(expected, result);
}
}
@Test
@MediumTest
public void testNestedConsumeNamedDataAsArrayBuffer() throws Throwable {
final String success = "success";
// The bytes of a minimal WebAssembly module, courtesy of v8/test/cctest/test-api-wasm.cc
final byte[] bytes = {0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00};
final String code = ""
+ "android.consumeNamedDataAsArrayBuffer(\"id-1\").then((value) => {"
+ " return android.consumeNamedDataAsArrayBuffer(\"id-2\").then((value) => {"
+ " return android.consumeNamedDataAsArrayBuffer(\"id-3\").then((value) => {"
+ " return android.consumeNamedDataAsArrayBuffer(\"id-4\").then((value) => {"
+ " return android.consumeNamedDataAsArrayBuffer(\"id-5\").then((value) => {"
+ " return \"success\";"
+ " }, (error) => {"
+ " return error.message;"
+ " });"
+ " });"
+ " });"
+ " });"
+ "});";
Context context = ApplicationProvider.getApplicationContext();
ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
JavaScriptSandbox.createConnectedInstanceAsync(context);
try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS);
JavaScriptIsolate jsIsolate = jsSandbox.createIsolate()) {
Assume.assumeTrue(
jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_PROMISE_RETURN));
Assume.assumeTrue(jsSandbox.isFeatureSupported(
JavaScriptSandbox.JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER));
jsIsolate.provideNamedData("id-1", bytes);
jsIsolate.provideNamedData("id-2", bytes);
jsIsolate.provideNamedData("id-3", bytes);
jsIsolate.provideNamedData("id-4", bytes);
jsIsolate.provideNamedData("id-5", bytes);
Thread.sleep(1000);
ListenableFuture<String> resultFuture1 = jsIsolate.evaluateJavaScriptAsync(code);
String result = resultFuture1.get(5, TimeUnit.SECONDS);
Assert.assertEquals(success, result);
}
}
@Test
@MediumTest
public void testPromiseEvaluationThrow() throws Throwable {
final String provideString = "Hello World";
final byte[] bytes = provideString.getBytes(StandardCharsets.US_ASCII);
final String code = ""
+ "android.consumeNamedDataAsArrayBuffer(\"id-1\").catch((error) => {"
+ " throw new WebAssembly.LinkError('RandomLinkError');"
+ "});";
final String contains = "RandomLinkError";
Context context = ApplicationProvider.getApplicationContext();
ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
JavaScriptSandbox.createConnectedInstanceAsync(context);
try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS);
JavaScriptIsolate jsIsolate = jsSandbox.createIsolate()) {
Assume.assumeTrue(
jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_PROMISE_RETURN));
Assume.assumeTrue(jsSandbox.isFeatureSupported(
JavaScriptSandbox.JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER));
ListenableFuture<String> resultFuture = jsIsolate.evaluateJavaScriptAsync(code);
try {
resultFuture.get(5, TimeUnit.SECONDS);
Assert.fail("Should have thrown.");
} catch (ExecutionException e) {
if (!(e.getCause() instanceof EvaluationFailedException)) {
throw e;
}
Assert.assertTrue(e.getCause().getMessage().contains(contains));
}
}
}
@Test
@MediumTest
public void testEvaluationThrowsWhenSandboxDead() throws Throwable {
final String code = "while(true){}";
Context context = ApplicationProvider.getApplicationContext();
ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
JavaScriptSandbox.createConnectedInstanceAsync(context);
try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS);
JavaScriptIsolate jsIsolate = jsSandbox.createIsolate()) {
ListenableFuture<String> resultFuture = jsIsolate.evaluateJavaScriptAsync(code);
jsSandbox.close();
try {
resultFuture.get(5, TimeUnit.SECONDS);
Assert.fail("Should have thrown.");
} catch (ExecutionException e) {
if (!(e.getCause() instanceof IsolateTerminatedException)) {
throw e;
}
}
}
}
@Test
@MediumTest
public void testMultipleSandboxesCannotCoexist() throws Throwable {
Context context = ApplicationProvider.getApplicationContext();
final String contains = "already bound";
ListenableFuture<JavaScriptSandbox> jsSandboxFuture1 =
JavaScriptSandbox.createConnectedInstanceAsync(context);
try (JavaScriptSandbox jsSandbox1 = jsSandboxFuture1.get(5, TimeUnit.SECONDS)) {
ListenableFuture<JavaScriptSandbox> jsSandboxFuture2 =
JavaScriptSandbox.createConnectedInstanceAsync(context);
try {
try (JavaScriptSandbox jsSandbox2 = jsSandboxFuture2.get(5, TimeUnit.SECONDS)) {
Assert.fail("Should have thrown.");
}
} catch (ExecutionException e) {
if (!(e.getCause() instanceof RuntimeException)) {
throw e;
}
Assert.assertTrue(e.getCause().getMessage().contains(contains));
}
}
}
@Test
@MediumTest
public void testSandboxCanBeCreatedAfterClosed() throws Throwable {
final String code = "\"PASS\"";
final String expected = "PASS";
final int num_of_startups = 2;
Context context = ApplicationProvider.getApplicationContext();
for (int i = 0; i < num_of_startups; i++) {
ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
JavaScriptSandbox.createConnectedInstanceAsync(context);
try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS);
JavaScriptIsolate jsIsolate = jsSandbox.createIsolate()) {
ListenableFuture<String> resultFuture = jsIsolate.evaluateJavaScriptAsync(code);
String result = resultFuture.get(5, TimeUnit.SECONDS);
Assert.assertEquals(expected, result);
}
}
}
@Test
@MediumTest
public void testHeapSizeAdjustment() throws Throwable {
final String code = "\"PASS\"";
final String expected = "PASS";
final long[] heapSizes = {
0,
REASONABLE_HEAP_SIZE,
REASONABLE_HEAP_SIZE - 1,
REASONABLE_HEAP_SIZE + 1,
REASONABLE_HEAP_SIZE + 4095,
REASONABLE_HEAP_SIZE + 4096,
REASONABLE_HEAP_SIZE + 65535,
REASONABLE_HEAP_SIZE + 65536,
1L << 50,
};
Context context = ApplicationProvider.getApplicationContext();
ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
JavaScriptSandbox.createConnectedInstanceAsync(context);
try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS)) {
Assume.assumeTrue(jsSandbox.isFeatureSupported(
JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE));
for (long heapSize : heapSizes) {
IsolateStartupParameters isolateStartupParameters = new IsolateStartupParameters();
isolateStartupParameters.setMaxHeapSizeBytes(heapSize);
try (JavaScriptIsolate jsIsolate =
jsSandbox.createIsolate(isolateStartupParameters)) {
ListenableFuture<String> resultFuture = jsIsolate.evaluateJavaScriptAsync(code);
String result = resultFuture.get(5, TimeUnit.SECONDS);
Assert.assertEquals(expected, result);
} catch (Throwable e) {
throw new AssertionError(
"Failed to evaluate JavaScript using max heap size setting " + heapSize,
e);
}
}
}
}
@Test
@MediumTest
public void testHeapSizeEnforced() throws Throwable {
final long maxHeapSize = REASONABLE_HEAP_SIZE;
// We need to beat the v8 optimizer to ensure it really allocates the required memory.
// Note that we're allocating an array of elements - not bytes.
final String code = "this.array = Array(" + maxHeapSize + ").fill(Math.random(), 0);"
+ "var arrayLength = this.array.length;"
+ "var sum = 0;"
+ "for (var i = 0; i < arrayLength; i++) {"
+ " sum+=this.array[i];"
+ "}";
Context context = ApplicationProvider.getApplicationContext();
ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
JavaScriptSandbox.createConnectedInstanceAsync(context);
try (JavaScriptSandbox jsSandbox = jsSandboxFuture.get(5, TimeUnit.SECONDS)) {
Assume.assumeTrue(jsSandbox.isFeatureSupported(
JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE));
IsolateStartupParameters isolateStartupParameters = new IsolateStartupParameters();
isolateStartupParameters.setMaxHeapSizeBytes(maxHeapSize);
try (JavaScriptIsolate jsIsolate = jsSandbox.createIsolate(isolateStartupParameters)) {
ListenableFuture<String> resultFuture = jsIsolate.evaluateJavaScriptAsync(code);
try {
resultFuture.get(10, TimeUnit.SECONDS);
Assert.fail("Should have thrown.");
} catch (ExecutionException e) {
if (!(e.getCause() instanceof SandboxDeadException)) {
throw e;
}
}
}
}
}
}