| Index: components/devtools_bridge/test/android/javatests/src/org/chromium/components/devtools_bridge/LocalSessionBridge.java
|
| diff --git a/components/devtools_bridge/test/android/javatests/src/org/chromium/components/devtools_bridge/LocalSessionBridge.java b/components/devtools_bridge/test/android/javatests/src/org/chromium/components/devtools_bridge/LocalSessionBridge.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..d851abb59212c6119c72f7fcd482d2a9b09659bb
|
| --- /dev/null
|
| +++ b/components/devtools_bridge/test/android/javatests/src/org/chromium/components/devtools_bridge/LocalSessionBridge.java
|
| @@ -0,0 +1,432 @@
|
| +// Copyright 2014 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +package org.chromium.components.devtools_bridge;
|
| +
|
| +import android.util.Log;
|
| +
|
| +import java.io.IOException;
|
| +import java.util.ArrayList;
|
| +import java.util.List;
|
| +import java.util.concurrent.CountDownLatch;
|
| +import java.util.concurrent.ExecutionException;
|
| +import java.util.concurrent.Executors;
|
| +import java.util.concurrent.ScheduledExecutorService;
|
| +import java.util.concurrent.ScheduledFuture;
|
| +import java.util.concurrent.TimeUnit;
|
| +import java.util.concurrent.atomic.AtomicReference;
|
| +
|
| +/**
|
| + * Helper class designated for automatic and manual testing. Creates a pair of ClientSession and
|
| + * ServerSession on separate and threads binds them through and adapters that makes call the calls
|
| + * on correct threads (no serialization needed for communication).
|
| + */
|
| +public class LocalSessionBridge {
|
| + private static final String TAG = "LocalSessionBridge";
|
| +
|
| + private volatile int mDelayMs = 0;
|
| +
|
| + private final SessionDependencyFactory mFactory = new SessionDependencyFactory();
|
| +
|
| + private final ThreadedExecutor mServerExecutor = new ThreadedExecutor();
|
| + private final ThreadedExecutor mClientExecutor = new ThreadedExecutor();
|
| +
|
| + private final ServerSessionMock mServerSession;
|
| + private final ClientSessionMock mClientSession;
|
| +
|
| + private boolean mStarted = false;
|
| +
|
| + private final CountDownLatch mNegotiated = new CountDownLatch(2);
|
| + private final CountDownLatch mControlChannelOpened = new CountDownLatch(2);
|
| + private final CountDownLatch mClientAutoClosed = new CountDownLatch(1);
|
| + private final CountDownLatch mServerAutoClosed = new CountDownLatch(1);
|
| + private final CountDownLatch mTunnelConfirmed = new CountDownLatch(1);
|
| +
|
| + private int mServerAutoCloseTimeoutMs = -1;
|
| + private int mClientAutoCloseTimeoutMs = -1;
|
| +
|
| + public LocalSessionBridge(String serverSocketName, String clientSocketName) throws IOException {
|
| + mServerSession = new ServerSessionMock(serverSocketName);
|
| + mClientSession = new ClientSessionMock(mServerSession, clientSocketName);
|
| + }
|
| +
|
| + public void setMessageDeliveryDelayMs(int value) {
|
| + mDelayMs = value;
|
| + }
|
| +
|
| + void setClientAutoCloseTimeoutMs(int value) {
|
| + assert !isStarted();
|
| +
|
| + mClientAutoCloseTimeoutMs = value;
|
| + }
|
| +
|
| + void setServerAutoCloseTimeoutMs(int value) {
|
| + assert !isStarted();
|
| +
|
| + mServerAutoCloseTimeoutMs = value;
|
| + }
|
| +
|
| + public void dispose() {
|
| + if (isStarted()) stop();
|
| +
|
| + mServerExecutor.dispose();
|
| + mClientExecutor.dispose();
|
| + mFactory.dispose();
|
| + }
|
| +
|
| + public void start() {
|
| + start(new RTCConfiguration());
|
| + }
|
| +
|
| + public void start(final RTCConfiguration config) {
|
| + if (mServerAutoCloseTimeoutMs >= 0)
|
| + mServerSession.setAutoCloseTimeoutMs(mServerAutoCloseTimeoutMs);
|
| + if (mClientAutoCloseTimeoutMs >= 0)
|
| + mClientSession.setAutoCloseTimeoutMs(mClientAutoCloseTimeoutMs);
|
| + mClientExecutor.runSynchronously(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + mClientSession.start(config);
|
| + }
|
| + });
|
| + mStarted = true;
|
| + }
|
| +
|
| + private boolean isStarted() {
|
| + return mStarted;
|
| + }
|
| +
|
| + public void stop() {
|
| + mServerExecutor.runSynchronously(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + mServerSession.dispose();
|
| + }
|
| + });
|
| + mClientExecutor.runSynchronously(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + mClientSession.dispose();
|
| + }
|
| + });
|
| + mStarted = false;
|
| + }
|
| +
|
| + public void awaitNegotiated() throws InterruptedException {
|
| + mNegotiated.await();
|
| + }
|
| +
|
| + public void awaitControlChannelOpened() throws InterruptedException {
|
| + mControlChannelOpened.await();
|
| + }
|
| +
|
| + public void awaitClientAutoClosed() throws InterruptedException {
|
| + mClientAutoClosed.await();
|
| + }
|
| +
|
| + public void awaitServerAutoClosed() throws InterruptedException {
|
| + mServerAutoClosed.await();
|
| + }
|
| +
|
| + private class ServerSessionMock extends ServerSession {
|
| + public ServerSessionMock(String serverSocketName) {
|
| + super(mFactory, mServerExecutor, serverSocketName);
|
| + }
|
| +
|
| + public void setAutoCloseTimeoutMs(int value) {
|
| + mAutoCloseTimeoutMs = value;
|
| + }
|
| +
|
| + @Override
|
| + protected void onSessionNegotiated() {
|
| + Log.d(TAG, "Server negotiated");
|
| + mNegotiated.countDown();
|
| + super.onSessionNegotiated();
|
| + }
|
| +
|
| + @Override
|
| + protected void onControlChannelOpened() {
|
| + Log.d(TAG, "Server's control channel opened");
|
| + super.onControlChannelOpened();
|
| + mControlChannelOpened.countDown();
|
| + }
|
| +
|
| + @Override
|
| + protected void onIceCandidate(String candidate) {
|
| + Log.d(TAG, "Server's ICE candidate: " + candidate);
|
| + super.onIceCandidate(candidate);
|
| + }
|
| +
|
| + @Override
|
| + protected void closeSelf() {
|
| + Log.d(TAG, "Server autoclosed");
|
| + super.closeSelf();
|
| + mServerAutoClosed.countDown();
|
| + }
|
| +
|
| + @Override
|
| + protected SocketTunnelServer createSocketTunnelServer(String serverSocketName) {
|
| + SocketTunnelServer tunnel = super.createSocketTunnelServer(serverSocketName);
|
| + Log.d(TAG, "Server tunnel created on " + serverSocketName);
|
| + return tunnel;
|
| + }
|
| + }
|
| +
|
| + private class ClientSessionMock extends ClientSession {
|
| + public ClientSessionMock(ServerSession serverSession, String clientSocketName)
|
| + throws IOException {
|
| + super(mFactory,
|
| + mClientExecutor,
|
| + createServerSessionProxy(serverSession),
|
| + clientSocketName);
|
| + }
|
| +
|
| + public void setAutoCloseTimeoutMs(int value) {
|
| + mAutoCloseTimeoutMs = value;
|
| + }
|
| +
|
| + @Override
|
| + protected void onSessionNegotiated() {
|
| + Log.d(TAG, "Client negotiated");
|
| + mNegotiated.countDown();
|
| + super.onSessionNegotiated();
|
| + }
|
| +
|
| + @Override
|
| + protected void onControlChannelOpened() {
|
| + Log.d(TAG, "Client's control channel opened");
|
| + super.onControlChannelOpened();
|
| + mControlChannelOpened.countDown();
|
| + }
|
| +
|
| + @Override
|
| + protected void onIceCandidate(String candidate) {
|
| + Log.d(TAG, "Client's ICE candidate: " + candidate);
|
| + super.onIceCandidate(candidate);
|
| + }
|
| +
|
| + @Override
|
| + protected void closeSelf() {
|
| + Log.d(TAG, "Client autoclosed");
|
| + super.closeSelf();
|
| + mClientAutoClosed.countDown();
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Implementation of SessionBase.Executor on top of ScheduledExecutorService.
|
| + */
|
| + public static final class ThreadedExecutor implements SessionBase.Executor {
|
| + private final ScheduledExecutorService mExecutor =
|
| + Executors.newSingleThreadScheduledExecutor();
|
| + private final AtomicReference<Thread> mSessionThread = new AtomicReference<Thread>();
|
| +
|
| + @Override
|
| + public SessionBase.Cancellable postOnSessionThread(int delayMs, Runnable runnable) {
|
| + return new CancellableFuture(mExecutor.schedule(
|
| + new SessionThreadRunner(runnable), delayMs, TimeUnit.MILLISECONDS));
|
| + }
|
| +
|
| + @Override
|
| + public boolean isCalledOnSessionThread() {
|
| + return Thread.currentThread() == mSessionThread.get();
|
| + }
|
| +
|
| + public void runSynchronously(Runnable runnable) {
|
| + try {
|
| + mExecutor.submit(new SessionThreadRunner(runnable)).get();
|
| + } catch (InterruptedException e) {
|
| + Thread.currentThread().interrupt();
|
| + } catch (ExecutionException e) {
|
| + throw new RuntimeException(e);
|
| + }
|
| + }
|
| +
|
| + public void dispose() {
|
| + mExecutor.shutdownNow();
|
| + }
|
| +
|
| + private class SessionThreadRunner implements Runnable {
|
| + private final Runnable mRunnable;
|
| +
|
| + public SessionThreadRunner(Runnable runnable) {
|
| + mRunnable = runnable;
|
| + }
|
| +
|
| + @Override
|
| + public void run() {
|
| + Thread thread = mSessionThread.getAndSet(Thread.currentThread());
|
| + assert thread == null;
|
| + mRunnable.run();
|
| + thread = mSessionThread.getAndSet(null);
|
| + assert thread == Thread.currentThread();
|
| + }
|
| + }
|
| + }
|
| +
|
| + private static final class CancellableFuture implements SessionBase.Cancellable {
|
| + private final ScheduledFuture<?> mFuture;
|
| +
|
| + public CancellableFuture(ScheduledFuture<?> future) {
|
| + mFuture = future;
|
| + }
|
| +
|
| + @Override
|
| + public void cancel() {
|
| + mFuture.cancel(false);
|
| + }
|
| + }
|
| +
|
| + private ServerSessionProxy createServerSessionProxy(SessionBase.ServerSessionInterface proxee) {
|
| + return new ServerSessionProxy(mServerExecutor, mClientExecutor, proxee, mDelayMs);
|
| + }
|
| +
|
| + /**
|
| + * Helper proxy that binds client and server sessions living on different executors.
|
| + * Exchange java objects instead of serialized messages.
|
| + */
|
| + public static final class ServerSessionProxy implements SessionBase.ServerSessionInterface {
|
| + private final SessionBase.ServerSessionInterface mProxee;
|
| + private final SessionBase.Executor mServerExecutor;
|
| + private final SessionBase.Executor mClientExecutor;
|
| + private final int mDelayMs;
|
| +
|
| + public ServerSessionProxy(
|
| + SessionBase.Executor serverExecutor, SessionBase.Executor clientExecutor,
|
| + SessionBase.ServerSessionInterface proxee, int delayMs) {
|
| + mServerExecutor = serverExecutor;
|
| + mClientExecutor = clientExecutor;
|
| + mProxee = proxee;
|
| + mDelayMs = delayMs;
|
| + }
|
| +
|
| + public SessionBase.Executor serverExecutor() {
|
| + return mServerExecutor;
|
| + }
|
| +
|
| + public SessionBase.Executor clientExecutor() {
|
| + return mClientExecutor;
|
| + }
|
| +
|
| + @Override
|
| + public void startSession(final RTCConfiguration config,
|
| + final String offer,
|
| + final SessionBase.NegotiationCallback callback) {
|
| + Log.d(TAG, "Starting session: " + offer);
|
| + mServerExecutor.postOnSessionThread(mDelayMs, new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + mProxee.startSession(config, offer, wrap(callback));
|
| + }
|
| + });
|
| + }
|
| +
|
| + @Override
|
| + public void renegotiate(final String offer,
|
| + final SessionBase.NegotiationCallback callback) {
|
| + Log.d(TAG, "Renegotiation: " + offer);
|
| + mServerExecutor.postOnSessionThread(mDelayMs, new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + mProxee.renegotiate(offer, wrap(callback));
|
| + }
|
| + });
|
| + }
|
| +
|
| + @Override
|
| + public void iceExchange(final List<String> clientCandidates,
|
| + final SessionBase.IceExchangeCallback callback) {
|
| + Log.d(TAG, "Client ice candidates " + Integer.toString(clientCandidates.size()));
|
| + mServerExecutor.postOnSessionThread(mDelayMs, new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + mProxee.iceExchange(clientCandidates, wrap(callback));
|
| + }
|
| + });
|
| + }
|
| +
|
| + private NegotiationCallbackProxy wrap(SessionBase.NegotiationCallback callback) {
|
| + return new NegotiationCallbackProxy(callback, mClientExecutor, mDelayMs);
|
| + }
|
| +
|
| + private IceExchangeCallbackProxy wrap(SessionBase.IceExchangeCallback callback) {
|
| + return new IceExchangeCallbackProxy(callback, mClientExecutor, mDelayMs);
|
| + }
|
| + }
|
| +
|
| + private static final class NegotiationCallbackProxy implements SessionBase.NegotiationCallback {
|
| + private final SessionBase.NegotiationCallback mProxee;
|
| + private final SessionBase.Executor mClientExecutor;
|
| + private final int mDelayMs;
|
| +
|
| + public NegotiationCallbackProxy(SessionBase.NegotiationCallback callback,
|
| + SessionBase.Executor clientExecutor,
|
| + int delayMs) {
|
| + mProxee = callback;
|
| + mClientExecutor = clientExecutor;
|
| + mDelayMs = delayMs;
|
| + }
|
| +
|
| + @Override
|
| + public void onSuccess(final String answer) {
|
| + Log.d(TAG, "Sending answer: " + answer);
|
| + mClientExecutor.postOnSessionThread(mDelayMs, new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + mProxee.onSuccess(answer);
|
| + }
|
| + });
|
| + }
|
| +
|
| + @Override
|
| + public void onFailure(final String message) {
|
| + mClientExecutor.postOnSessionThread(mDelayMs, new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + mProxee.onFailure(message);
|
| + }
|
| + });
|
| + }
|
| + }
|
| +
|
| + private static final class IceExchangeCallbackProxy implements SessionBase.IceExchangeCallback {
|
| + private final SessionBase.IceExchangeCallback mProxee;
|
| + private final SessionBase.Executor mClientExecutor;
|
| + private final int mDelayMs;
|
| +
|
| + public IceExchangeCallbackProxy(SessionBase.IceExchangeCallback callback,
|
| + SessionBase.Executor clientExecutor,
|
| + int delayMs) {
|
| + mProxee = callback;
|
| + mClientExecutor = clientExecutor;
|
| + mDelayMs = delayMs;
|
| + }
|
| +
|
| + @Override
|
| + public void onSuccess(List<String> serverCandidates) {
|
| + Log.d(TAG, "Server ice candidates " + Integer.toString(serverCandidates.size()));
|
| +
|
| + final List<String> serverCandidatesCopy = new ArrayList<String>();
|
| + serverCandidatesCopy.addAll(serverCandidates);
|
| +
|
| + mClientExecutor.postOnSessionThread(mDelayMs, new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + mProxee.onSuccess(serverCandidatesCopy);
|
| + }
|
| + });
|
| + }
|
| +
|
| + @Override
|
| + public void onFailure(final String message) {
|
| + Log.d(TAG, "Ice exchange falure: " + message);
|
| + mClientExecutor.postOnSessionThread(mDelayMs, new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + mProxee.onFailure(message);
|
| + }
|
| + });
|
| + }
|
| + }
|
| +}
|
|
|