зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1572245 - Add TestRuntimeService to restart runtime in tests. r=owlish
This patch adds a new test service class that can be used to more easily write a test that needs to run multiple runtimes or needs to reset the runtime. The service also includes an optional Instance class that can be used to control the service and send and receive messages from the remote runtime. Differential Revision: https://phabricator.services.mozilla.com/D126542
This commit is contained in:
Родитель
c8006ed003
Коммит
36efcb63bd
|
@ -39,15 +39,15 @@
|
|||
|
||||
<!-- This is needed for ParentCrashTest -->
|
||||
<service
|
||||
android:name=".crash.RemoteGeckoService"
|
||||
android:name=".crash.RuntimeCrashTestService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:process=":gecko">
|
||||
android:process=":crashtest">
|
||||
</service>
|
||||
|
||||
<!-- This is needed for ProfileLockedTest -->
|
||||
<service android:name=".TestProfileLockService$p0" android:enabled="true" android:exported="false" android:process=":profiletest0" />
|
||||
<service android:name=".TestProfileLockService$p1" android:enabled="true" android:exported="false" android:process=":profiletest1" />
|
||||
<!-- Used to run multiple runtimes during tests -->
|
||||
<service android:name=".TestRuntimeService$instance0" android:enabled="true" android:exported="false" android:process=":runtime0" />
|
||||
<service android:name=".TestRuntimeService$instance1" android:enabled="true" android:exported="false" android:process=":runtime1" />
|
||||
|
||||
<!-- This is used to run xpcshell tests -->
|
||||
<service android:name=".XpcshellTestRunnerService$i0" android:enabled="true" android:exported="true" android:process=":xpcshell0"/>
|
||||
|
|
|
@ -105,10 +105,13 @@ open class BaseSessionTest(noErrorCollector: Boolean = false) {
|
|||
const val SHOW_DYNAMIC_TOOLBAR_HTML_PATH = "/assets/www/showDynamicToolbar.html"
|
||||
|
||||
const val TEST_ENDPOINT = GeckoSessionTestRule.TEST_ENDPOINT
|
||||
const val TEST_HOST = GeckoSessionTestRule.TEST_HOST
|
||||
}
|
||||
|
||||
@get:Rule val sessionRule = GeckoSessionTestRule()
|
||||
|
||||
@get:Rule var temporaryProfile = TemporaryProfileRule()
|
||||
|
||||
@get:Rule val errors = ErrorCollector()
|
||||
|
||||
val mainSession get() = sessionRule.session
|
||||
|
|
|
@ -1,113 +1,41 @@
|
|||
package org.mozilla.geckoview.test
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.os.*
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.MediumTest
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import org.hamcrest.CoreMatchers.equalTo
|
||||
import org.junit.Assert.assertThat
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.geckoview.GeckoResult
|
||||
import java.io.File
|
||||
|
||||
import org.mozilla.geckoview.test.TestRuntimeService.RuntimeInstance
|
||||
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ClosedSessionAtStart
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@MediumTest
|
||||
class ProfileLockedTest {
|
||||
class ProfileLockedTest : BaseSessionTest() {
|
||||
private val targetContext
|
||||
get() = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
|
||||
@get:Rule
|
||||
var folder = TemporaryFolder()
|
||||
|
||||
/**
|
||||
* Starts GeckoRuntime in the process given in input, and waits for the MESSAGE_PAGE_STOP
|
||||
* event that's fired when the first GeckoSession receives the onPageStop event.
|
||||
*
|
||||
* We wait for a page load to make sure that everything started up correctly (as opposed
|
||||
* to quitting during the startup procedure).
|
||||
*/
|
||||
class RuntimeInstance<T>(
|
||||
private val context: Context,
|
||||
private val service: Class<T>,
|
||||
private val profileFolder: File) : ServiceConnection {
|
||||
|
||||
var isConnected = false
|
||||
var disconnected = GeckoResult<Void>()
|
||||
var started = GeckoResult<Void>()
|
||||
var quitted = GeckoResult<Void>()
|
||||
|
||||
override fun onServiceConnected(name: ComponentName, binder: IBinder) {
|
||||
val handler = object : Handler(Looper.getMainLooper()) {
|
||||
override fun handleMessage(msg: Message) {
|
||||
when (msg.what) {
|
||||
TestProfileLockService.MESSAGE_PAGE_STOP -> started.complete(null)
|
||||
TestProfileLockService.MESSAGE_QUIT -> {
|
||||
quitted.complete(null)
|
||||
// No reason to keep the service around anymore
|
||||
context.unbindService(this@RuntimeInstance)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val messenger = Messenger(handler)
|
||||
val service = Messenger(binder)
|
||||
|
||||
val msg = Message.obtain(null, TestProfileLockService.MESSAGE_REGISTER)
|
||||
msg.replyTo = messenger
|
||||
service.send(msg)
|
||||
|
||||
isConnected = true
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
isConnected = false
|
||||
context.unbindService(this)
|
||||
disconnected.complete(null)
|
||||
}
|
||||
|
||||
fun start() {
|
||||
val intent = Intent(context, service)
|
||||
intent.putExtra("args", "-profile ${profileFolder.absolutePath}")
|
||||
|
||||
context.bindService(intent, this, Context.BIND_AUTO_CREATE)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ClosedSessionAtStart
|
||||
fun profileLocked() {
|
||||
val profileFolder = folder.newFolder("profile-locked-test")
|
||||
|
||||
val runtime0 = RuntimeInstance(targetContext,
|
||||
TestProfileLockService.p0::class.java,
|
||||
profileFolder)
|
||||
val runtime1 = RuntimeInstance(targetContext,
|
||||
TestProfileLockService.p1::class.java,
|
||||
profileFolder)
|
||||
val runtime0 = RuntimeInstance.start(
|
||||
targetContext, TestRuntimeService.instance0::class.java, temporaryProfile.get())
|
||||
|
||||
// Start the first runtime and wait until it's ready
|
||||
runtime0.start()
|
||||
runtime0.started.pollDefault()
|
||||
sessionRule.waitForResult(runtime0.started)
|
||||
|
||||
assertThat("The service should be connected now", runtime0.isConnected, equalTo(true))
|
||||
|
||||
// Now start a _second_ runtime with the same profile folder, this will kill the first
|
||||
// runtime
|
||||
runtime1.start()
|
||||
val runtime1 = RuntimeInstance.start(
|
||||
targetContext, TestRuntimeService.instance1::class.java, temporaryProfile.get())
|
||||
|
||||
// Wait for the first runtime to disconnect
|
||||
runtime0.disconnected.pollDefault()
|
||||
sessionRule.waitForResult(runtime0.disconnected)
|
||||
|
||||
// GeckoRuntime will quit after killing the offending process
|
||||
runtime1.quitted.pollDefault()
|
||||
sessionRule.waitForResult(runtime1.quitted)
|
||||
|
||||
assertThat("The service shouldn't be connected anymore",
|
||||
runtime0.isConnected, equalTo(false))
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package org.mozilla.geckoview.test;
|
||||
|
||||
import org.junit.rules.ExternalResource;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.mozilla.geckoview.test.rule.TestHarnessException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/** Lazily provides a temporary profile folder for tests. */
|
||||
public class TemporaryProfileRule extends ExternalResource {
|
||||
TemporaryFolder mTemporaryFolder;
|
||||
File mProfileFolder;
|
||||
|
||||
@Override
|
||||
protected void after() {
|
||||
if (mTemporaryFolder != null) {
|
||||
mTemporaryFolder.delete();
|
||||
mProfileFolder = null;
|
||||
}
|
||||
}
|
||||
|
||||
public File get() {
|
||||
if (mProfileFolder == null) {
|
||||
mTemporaryFolder = new TemporaryFolder();
|
||||
try {
|
||||
mTemporaryFolder.create();
|
||||
mProfileFolder = mTemporaryFolder.newFolder("test-profile");
|
||||
} catch (IOException ex) {
|
||||
throw new TestHarnessException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
return mProfileFolder;
|
||||
}
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.geckoview.test;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.mozilla.geckoview.GeckoRuntime;
|
||||
import org.mozilla.geckoview.GeckoRuntimeSettings;
|
||||
import org.mozilla.geckoview.GeckoSession;
|
||||
|
||||
public class TestProfileLockService extends Service implements
|
||||
GeckoSession.ProgressDelegate,
|
||||
GeckoRuntime.Delegate {
|
||||
public static final class p0 extends TestProfileLockService {}
|
||||
public static final class p1 extends TestProfileLockService {}
|
||||
|
||||
// Used by the client to register themselves
|
||||
public static final int MESSAGE_REGISTER = 1;
|
||||
// Sent when the first page load completes
|
||||
public static final int MESSAGE_PAGE_STOP = 2;
|
||||
// Sent when GeckoRuntime exits
|
||||
public static final int MESSAGE_QUIT = 3;
|
||||
|
||||
private GeckoRuntime mRuntime;
|
||||
|
||||
private Messenger mClient;
|
||||
|
||||
private class Handler extends android.os.Handler {
|
||||
@Override
|
||||
public void handleMessage(@NonNull final Message msg) {
|
||||
switch (msg.what) {
|
||||
case MESSAGE_REGISTER:
|
||||
mClient = msg.replyTo;
|
||||
return;
|
||||
default:
|
||||
throw new IllegalStateException("Unknown message: " + msg.what);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final Messenger mMessenger = new Messenger(new Handler());
|
||||
|
||||
@Override
|
||||
public void onShutdown() {
|
||||
final Message message = Message.obtain(null, MESSAGE_QUIT);
|
||||
try {
|
||||
mClient.send(message);
|
||||
} catch (final RemoteException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageStop(@NonNull final GeckoSession session, final boolean success) {
|
||||
final Message message = Message.obtain(null, MESSAGE_PAGE_STOP);
|
||||
try {
|
||||
mClient.send(message);
|
||||
} catch (final RemoteException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
// Sometimes the service doesn't die on it's own so we need to kill it here.
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(final Intent intent) {
|
||||
// Request to be killed as soon as the client unbinds.
|
||||
stopSelf();
|
||||
|
||||
if (mRuntime != null) {
|
||||
// We only expect one client
|
||||
throw new RuntimeException("Multiple clients !?");
|
||||
}
|
||||
|
||||
final GeckoRuntimeSettings settings = new GeckoRuntimeSettings.Builder()
|
||||
.extras(intent.getExtras())
|
||||
.build();
|
||||
mRuntime = GeckoRuntime.create(getApplicationContext(), settings);
|
||||
|
||||
mRuntime.setDelegate(this);
|
||||
|
||||
final GeckoSession session = new GeckoSession();
|
||||
session.setProgressDelegate(this);
|
||||
session.open(mRuntime);
|
||||
|
||||
return mMessenger.getBinder();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,412 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.geckoview.test;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.mozilla.gecko.util.GeckoBundle;
|
||||
import org.mozilla.geckoview.GeckoResult;
|
||||
import org.mozilla.geckoview.GeckoRuntime;
|
||||
import org.mozilla.geckoview.GeckoRuntimeSettings;
|
||||
import org.mozilla.geckoview.GeckoSession;
|
||||
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class TestRuntimeService extends Service implements
|
||||
GeckoSession.ProgressDelegate,
|
||||
GeckoRuntime.Delegate {
|
||||
// Used by the client to register themselves
|
||||
public static final int MESSAGE_REGISTER = 1;
|
||||
// Sent when the first page load completes
|
||||
public static final int MESSAGE_INIT_COMPLETE = 2;
|
||||
// Sent when GeckoRuntime exits
|
||||
public static final int MESSAGE_QUIT = 3;
|
||||
// Reload current session
|
||||
public static final int MESSAGE_RELOAD = 4;
|
||||
// Load URI in current session
|
||||
public static final int MESSAGE_LOAD_URI = 5;
|
||||
// Receive a reply for a message
|
||||
public static final int MESSAGE_REPLY = 6;
|
||||
// Execute action on the remote service
|
||||
public static final int MESSAGE_PAGE_STOP = 7;
|
||||
|
||||
// Used by clients to know the first safe ID that can be used
|
||||
// for additional message types
|
||||
public static final int FIRST_SAFE_MESSAGE = MESSAGE_PAGE_STOP + 1;
|
||||
|
||||
// Generic service instances
|
||||
public static final class instance0 extends TestRuntimeService {}
|
||||
public static final class instance1 extends TestRuntimeService {}
|
||||
|
||||
protected GeckoRuntime mRuntime;
|
||||
protected GeckoSession mSession;
|
||||
protected GeckoBundle mTestData;
|
||||
|
||||
private Messenger mClient;
|
||||
|
||||
private class TestHandler extends Handler {
|
||||
@Override
|
||||
public void handleMessage(@NonNull final Message msg) {
|
||||
final Bundle msgData = msg.getData();
|
||||
final GeckoBundle data = msgData != null
|
||||
? GeckoBundle.fromBundle(msgData.getBundle("data"))
|
||||
: null;
|
||||
final String id = msgData != null ? msgData.getString("id") : null;
|
||||
|
||||
switch (msg.what) {
|
||||
case MESSAGE_REGISTER:
|
||||
mClient = msg.replyTo;
|
||||
return;
|
||||
case MESSAGE_QUIT:
|
||||
// Unceremoniously exit
|
||||
System.exit(0);
|
||||
return;
|
||||
case MESSAGE_RELOAD:
|
||||
mSession.reload();
|
||||
break;
|
||||
case MESSAGE_LOAD_URI:
|
||||
mSession.loadUri(data.getString("uri"));
|
||||
break;
|
||||
default: {
|
||||
final GeckoResult<GeckoBundle> result =
|
||||
TestRuntimeService.this.handleMessage(msg.what, data);
|
||||
if (result != null) {
|
||||
result.accept(bundle -> {
|
||||
final GeckoBundle reply = new GeckoBundle();
|
||||
reply.putString("id", id);
|
||||
reply.putBundle("data", bundle);
|
||||
TestRuntimeService.this.sendMessage(MESSAGE_REPLY, reply);
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final Messenger mMessenger = new Messenger(new TestHandler());
|
||||
|
||||
@Override
|
||||
public void onShutdown() {
|
||||
sendMessage(MESSAGE_QUIT);
|
||||
}
|
||||
|
||||
protected void sendMessage(final int message) {
|
||||
sendMessage(message, null);
|
||||
}
|
||||
|
||||
protected void sendMessage(final int message, final GeckoBundle bundle) {
|
||||
if (mClient == null) {
|
||||
throw new IllegalStateException("Service is not connected yet!");
|
||||
}
|
||||
|
||||
Message msg = Message.obtain(null, message);
|
||||
msg.replyTo = mMessenger;
|
||||
if (bundle != null) {
|
||||
msg.setData(bundle.toBundle());
|
||||
}
|
||||
|
||||
try {
|
||||
mClient.send(msg);
|
||||
} catch (RemoteException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean mFirstPageStop = true;
|
||||
|
||||
@Override
|
||||
public void onPageStop(@NonNull final GeckoSession session, final boolean success) {
|
||||
// Notify the subclass that the session is ready to use
|
||||
if (success && mFirstPageStop) {
|
||||
onSessionReady(session);
|
||||
mFirstPageStop = false;
|
||||
sendMessage(MESSAGE_INIT_COMPLETE);
|
||||
} else {
|
||||
sendMessage(MESSAGE_PAGE_STOP);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onSessionReady(final GeckoSession session) {}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
// Sometimes the service doesn't die on it's own so we need to kill it here.
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(final Intent intent) {
|
||||
// Request to be killed as soon as the client unbinds.
|
||||
stopSelf();
|
||||
|
||||
if (mRuntime != null) {
|
||||
// We only expect one client
|
||||
throw new RuntimeException("Multiple clients !?");
|
||||
}
|
||||
|
||||
mRuntime = createRuntime(getApplicationContext(), intent);
|
||||
mRuntime.setDelegate(this);
|
||||
|
||||
if (intent.hasExtra("test-data")) {
|
||||
mTestData = GeckoBundle.fromBundle(intent.getBundleExtra("test-data"));
|
||||
}
|
||||
|
||||
mSession = createSession(intent);
|
||||
mSession.setProgressDelegate(this);
|
||||
mSession.open(mRuntime);
|
||||
|
||||
return mMessenger.getBinder();
|
||||
}
|
||||
|
||||
/** Override this to handle custom messages. */
|
||||
protected GeckoResult<GeckoBundle> handleMessage(final int messageId, final GeckoBundle data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Override this to change the default runtime */
|
||||
protected GeckoRuntime createRuntime(final @NonNull Context context,
|
||||
final @NonNull Intent intent) {
|
||||
return GeckoRuntime.create(context,
|
||||
new GeckoRuntimeSettings.Builder().extras(intent.getExtras()).build());
|
||||
}
|
||||
|
||||
/** Override this to change the default session */
|
||||
protected GeckoSession createSession(final Intent intent) {
|
||||
return new GeckoSession();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts GeckoRuntime in the process given in input, and waits for the MESSAGE_INIT_COMPLETE
|
||||
* event that's fired when the first GeckoSession receives the onPageStop event.
|
||||
*
|
||||
* We wait for a page load to make sure that everything started up correctly (as opposed
|
||||
* to quitting during the startup procedure).
|
||||
*/
|
||||
public static class RuntimeInstance<T> {
|
||||
public boolean isConnected = false;
|
||||
public GeckoResult<Void> disconnected = new GeckoResult<>();
|
||||
public GeckoResult<Void> started = new GeckoResult<>();
|
||||
public GeckoResult<Void> quitted = new GeckoResult<>();
|
||||
public final Context context;
|
||||
public final Class<T> service;
|
||||
|
||||
private final File mProfileFolder;
|
||||
private final GeckoBundle mTestData;
|
||||
private final ClientHandler mClientHandler = new ClientHandler();
|
||||
private Messenger mMessenger;
|
||||
private Messenger mServiceMessenger;
|
||||
private GeckoResult<Void> mPageStop = null;
|
||||
|
||||
private Map<String, GeckoResult<GeckoBundle>> mPendingMessages = new HashMap<>();
|
||||
|
||||
protected RuntimeInstance(
|
||||
final Context context,
|
||||
final Class<T> service,
|
||||
final File profileFolder) {
|
||||
this(context, service, profileFolder, null);
|
||||
}
|
||||
|
||||
protected RuntimeInstance(
|
||||
final Context context,
|
||||
final Class<T> service,
|
||||
final File profileFolder,
|
||||
final GeckoBundle testData) {
|
||||
this.context = context;
|
||||
this.service = service;
|
||||
mProfileFolder = profileFolder;
|
||||
mTestData = testData;
|
||||
}
|
||||
|
||||
public static <T> RuntimeInstance<T> start(
|
||||
final Context context,
|
||||
final Class<T> service,
|
||||
final File profileFolder) {
|
||||
RuntimeInstance<T> instance = new RuntimeInstance<>(context, service, profileFolder);
|
||||
instance.sendIntent();
|
||||
return instance;
|
||||
}
|
||||
|
||||
class ClientHandler extends Handler implements ServiceConnection {
|
||||
@Override
|
||||
public void handleMessage(@NonNull Message msg) {
|
||||
switch (msg.what) {
|
||||
case MESSAGE_INIT_COMPLETE:
|
||||
started.complete(null);
|
||||
break;
|
||||
case MESSAGE_QUIT:
|
||||
quitted.complete(null);
|
||||
// No reason to keep the service around anymore
|
||||
context.unbindService(mClientHandler);
|
||||
break;
|
||||
case MESSAGE_REPLY:
|
||||
final String messageId = msg.getData().getString("id");
|
||||
final Bundle data = msg.getData().getBundle("data");
|
||||
mPendingMessages.remove(messageId).complete(GeckoBundle.fromBundle(data));
|
||||
break;
|
||||
case MESSAGE_PAGE_STOP:
|
||||
if (mPageStop != null) {
|
||||
mPageStop.complete(null);
|
||||
mPageStop = null;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
RuntimeInstance.this.handleMessage(msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder binder) {
|
||||
mMessenger = new Messenger(mClientHandler);
|
||||
mServiceMessenger = new Messenger(binder);
|
||||
isConnected = true;
|
||||
|
||||
RuntimeInstance.this.sendMessage(MESSAGE_REGISTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
isConnected = false;
|
||||
context.unbindService(this);
|
||||
disconnected.complete(null);
|
||||
}
|
||||
}
|
||||
|
||||
/** Override this to handle additional messages. */
|
||||
protected void handleMessage(Message msg) {}
|
||||
|
||||
/** Override to modify the intent sent to the service */
|
||||
protected Intent createIntent(final Context context) {
|
||||
return new Intent(context, service);
|
||||
}
|
||||
|
||||
private GeckoResult<GeckoBundle> sendMessageInternal(
|
||||
final int message,
|
||||
final GeckoBundle bundle,
|
||||
final GeckoResult<GeckoBundle> result) {
|
||||
if (!isConnected) {
|
||||
throw new IllegalStateException("Service is not connected yet!");
|
||||
}
|
||||
|
||||
final String messageId = UUID.randomUUID().toString();
|
||||
GeckoBundle data = new GeckoBundle();
|
||||
data.putString("id", messageId);
|
||||
if (bundle != null) {
|
||||
data.putBundle("data", bundle);
|
||||
}
|
||||
|
||||
Message msg = Message.obtain(null, message);
|
||||
msg.replyTo = mMessenger;
|
||||
msg.setData(data.toBundle());
|
||||
|
||||
if (result != null) {
|
||||
mPendingMessages.put(messageId, result);
|
||||
}
|
||||
|
||||
try {
|
||||
mServiceMessenger.send(msg);
|
||||
} catch (RemoteException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private GeckoResult<Void> waitForPageStop() {
|
||||
if (mPageStop == null) {
|
||||
mPageStop = new GeckoResult<>();
|
||||
}
|
||||
return mPageStop;
|
||||
}
|
||||
|
||||
protected GeckoResult<GeckoBundle> query(final int message) {
|
||||
return query(message, null);
|
||||
}
|
||||
|
||||
protected GeckoResult<GeckoBundle> query(final int message, final GeckoBundle bundle) {
|
||||
final GeckoResult<GeckoBundle> result = new GeckoResult<>();
|
||||
return sendMessageInternal(message, bundle, result);
|
||||
}
|
||||
|
||||
protected void sendMessage(final int message) {
|
||||
sendMessage(message, null);
|
||||
}
|
||||
|
||||
protected void sendMessage(final int message,
|
||||
final GeckoBundle bundle) {
|
||||
sendMessageInternal(message, bundle, null);
|
||||
}
|
||||
|
||||
protected void sendIntent() {
|
||||
final Intent intent = createIntent(context);
|
||||
intent.putExtra("args", "-profile " + mProfileFolder.getAbsolutePath());
|
||||
if (mTestData != null) {
|
||||
intent.putExtra("test-data", mTestData.toBundle());
|
||||
}
|
||||
context.bindService(intent, mClientHandler, Context.BIND_AUTO_CREATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Quits the current runtime.
|
||||
*
|
||||
* @return a {@link GeckoResult} that is resolved when the service fully disconnects.
|
||||
*/
|
||||
public GeckoResult<Void> quit() {
|
||||
sendMessage(MESSAGE_QUIT);
|
||||
return disconnected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the current session.
|
||||
*
|
||||
* @return A {@link GeckoResult} that is resolved when the page is fully reloaded.
|
||||
*/
|
||||
public GeckoResult<Void> reload() {
|
||||
sendMessage(MESSAGE_RELOAD);
|
||||
return waitForPageStop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a test path in the current session.
|
||||
*
|
||||
* @return A {@link GeckoResult} that is resolved when the page is fully loaded.
|
||||
*/
|
||||
public GeckoResult<Void> loadTestPath(final String path) {
|
||||
return loadUri(GeckoSessionTestRule.TEST_ENDPOINT + path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an arbitrary URI in the current session.
|
||||
*
|
||||
* @return A {@link GeckoResult} that is resolved when the page is fully loaded.
|
||||
*/
|
||||
public GeckoResult<Void> loadUri(final String uri) {
|
||||
return started.then(unused -> {
|
||||
final GeckoBundle data = new GeckoBundle(1);
|
||||
data.putString("uri", uri);
|
||||
sendMessage(MESSAGE_LOAD_URI, data);
|
||||
return waitForPageStop();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,65 +1,42 @@
|
|||
package org.mozilla.geckoview.test.crash
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Message
|
||||
import android.os.Messenger
|
||||
import androidx.test.annotation.UiThreadTest
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.filters.MediumTest
|
||||
import androidx.test.rule.ServiceTestRule
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.hamcrest.Matchers.equalTo
|
||||
import org.hamcrest.Matchers.notNullValue
|
||||
import org.junit.Assert.assertThat
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Assume.assumeThat
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.geckoview.test.BaseSessionTest
|
||||
import org.mozilla.geckoview.test.TestCrashHandler
|
||||
import org.mozilla.geckoview.test.util.Environment
|
||||
import org.mozilla.geckoview.test.TestRuntimeService
|
||||
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ClosedSessionAtStart
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@MediumTest
|
||||
class ParentCrashTest {
|
||||
lateinit var messenger: Messenger
|
||||
val env = Environment()
|
||||
class ParentCrashTest : BaseSessionTest() {
|
||||
private val targetContext
|
||||
get() = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
|
||||
@get:Rule val rule = ServiceTestRule()
|
||||
|
||||
@get:Rule
|
||||
var folder = TemporaryFolder()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val intent = Intent(context, RemoteGeckoService::class.java)
|
||||
|
||||
// We need to run in a different profile so we don't conflict with other tests running
|
||||
// in parallel in other processes.
|
||||
val profileFolder = folder.newFolder("remote-gecko-test")
|
||||
intent.putExtra("args", "-profile ${profileFolder.absolutePath}")
|
||||
|
||||
val binder = rule.bindService(intent)
|
||||
messenger = Messenger(binder)
|
||||
assertThat("messenger should not be null", binder, notNullValue())
|
||||
}
|
||||
private val timeout
|
||||
get() = sessionRule.env.defaultTimeoutMillis
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
@ClosedSessionAtStart
|
||||
fun crashParent() {
|
||||
// TODO: Bug 1673956
|
||||
assumeThat(env.isFission, equalTo(false))
|
||||
val client = TestCrashHandler.Client(InstrumentationRegistry.getInstrumentation().targetContext)
|
||||
assumeThat(sessionRule.env.isFission, equalTo(false))
|
||||
val client = TestCrashHandler.Client(targetContext)
|
||||
|
||||
assertTrue(client.connect(env.defaultTimeoutMillis))
|
||||
assertTrue(client.connect(timeout))
|
||||
client.setEvalNextCrashDump(/* expectFatal */ true)
|
||||
|
||||
messenger.send(Message.obtain(null, RemoteGeckoService.CMD_CRASH_PARENT_NATIVE))
|
||||
val runtime = TestRuntimeService.RuntimeInstance.start(
|
||||
targetContext, RuntimeCrashTestService::class.java, temporaryProfile.get())
|
||||
runtime.loadUri("about:crashparent")
|
||||
|
||||
var evalResult = client.getEvalResult(env.defaultTimeoutMillis)
|
||||
val evalResult = client.getEvalResult(timeout)
|
||||
assertTrue(evalResult.mMsg, evalResult.mResult)
|
||||
|
||||
client.disconnect()
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
package org.mozilla.geckoview.test.crash
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.*
|
||||
import org.mozilla.geckoview.GeckoRuntime
|
||||
import org.mozilla.geckoview.GeckoRuntimeSettings
|
||||
import org.mozilla.geckoview.GeckoSession
|
||||
import org.mozilla.geckoview.GeckoSessionSettings
|
||||
import org.mozilla.geckoview.test.TestCrashHandler
|
||||
|
||||
class RemoteGeckoService : Service() {
|
||||
companion object {
|
||||
val CMD_CRASH_PARENT_NATIVE = 1
|
||||
val CMD_CRASH_CONTENT_NATIVE = 2
|
||||
var runtime: GeckoRuntime? = null
|
||||
}
|
||||
|
||||
var session: GeckoSession? = null;
|
||||
|
||||
class TestHandler: Handler(Looper.getMainLooper()) {
|
||||
override fun handleMessage(msg: Message) {
|
||||
when (msg.what) {
|
||||
CMD_CRASH_PARENT_NATIVE -> {
|
||||
val settings = GeckoSessionSettings()
|
||||
val session = GeckoSession(settings)
|
||||
session.open(runtime!!)
|
||||
session.loadUri("about:crashparent")
|
||||
}
|
||||
CMD_CRASH_CONTENT_NATIVE -> {
|
||||
val settings = GeckoSessionSettings.Builder()
|
||||
.build()
|
||||
val session = GeckoSession(settings)
|
||||
session.open(runtime!!)
|
||||
session.loadUri("about:crashcontent")
|
||||
}
|
||||
else -> {
|
||||
throw RuntimeException("Unhandled command")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val handler = Messenger(TestHandler())
|
||||
|
||||
override fun onBind(intent: Intent): IBinder {
|
||||
if (runtime == null) {
|
||||
runtime = GeckoRuntime.create(this.applicationContext,
|
||||
GeckoRuntimeSettings.Builder()
|
||||
.extras(intent.extras!!)
|
||||
.crashHandler(TestCrashHandler::class.java).build())
|
||||
}
|
||||
|
||||
return handler.binder
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package org.mozilla.geckoview.test.crash
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import org.mozilla.geckoview.GeckoRuntime
|
||||
import org.mozilla.geckoview.GeckoRuntimeSettings
|
||||
import org.mozilla.geckoview.test.TestCrashHandler
|
||||
import org.mozilla.geckoview.test.TestRuntimeService
|
||||
import java.io.File
|
||||
|
||||
class RuntimeCrashTestService : TestRuntimeService() {
|
||||
override fun createRuntime(context: Context, intent: Intent): GeckoRuntime {
|
||||
return GeckoRuntime.create(this.applicationContext,
|
||||
GeckoRuntimeSettings.Builder()
|
||||
.extras(intent.extras!!)
|
||||
.crashHandler(TestCrashHandler::class.java).build())
|
||||
}
|
||||
}
|
|
@ -104,7 +104,8 @@ public class GeckoSessionTestRule implements TestRule {
|
|||
private static final String LOGTAG = "GeckoSessionTestRule";
|
||||
|
||||
private static final int TEST_PORT = 4245;
|
||||
public static final String TEST_ENDPOINT = "http://localhost:" + TEST_PORT;
|
||||
public static final String TEST_HOST = "localhost";
|
||||
public static final String TEST_ENDPOINT = "http://" + TEST_HOST + ":" + TEST_PORT;
|
||||
|
||||
private static final Method sOnPageStart;
|
||||
private static final Method sOnPageStop;
|
||||
|
|
Загрузка…
Ссылка в новой задаче