Commit 68d64484 authored by Zhiqiang Zhang's avatar Zhiqiang Zhang Committed by Commit Bot

Unit tests for CastSessionController

This CL adds unit tests to CastSessionController.

Also the updateNamespaces() method is fixed to accept null
ApplicationMetadata or namespaces.

Change-Id: I3b435b22423a036c80d8237ec4a3f56983c9c0c4
Reviewed-on: https://chromium-review.googlesource.com/c/1328563
Commit-Queue: Zhiqiang Zhang <zqzhang@chromium.org>
Reviewed-by: default avatarThomas Guilbert <tguilbert@chromium.org>
Cr-Commit-Position: refs/heads/master@{#607115}
parent 537a7bb3
...@@ -302,10 +302,6 @@ public abstract class CafBaseMediaRouteProvider ...@@ -302,10 +302,6 @@ public abstract class CafBaseMediaRouteProvider
abstract public BaseSessionController sessionController(); abstract public BaseSessionController sessionController();
public CafMessageHandler getMessageHandler() {
return null;
}
/** Adds a route for bookkeeping. */ /** Adds a route for bookkeeping. */
protected void addRoute( protected void addRoute(
MediaRoute route, String origin, int tabId, int nativeRequestId, boolean wasLaunched) { MediaRoute route, String origin, int tabId, int nativeRequestId, boolean wasLaunched) {
......
...@@ -6,6 +6,7 @@ package org.chromium.chrome.browser.media.router.caf; ...@@ -6,6 +6,7 @@ package org.chromium.chrome.browser.media.router.caf;
import static org.chromium.chrome.browser.media.router.caf.CastUtils.isSameOrigin; import static org.chromium.chrome.browser.media.router.caf.CastUtils.isSameOrigin;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v7.media.MediaRouter; import android.support.v7.media.MediaRouter;
...@@ -141,7 +142,7 @@ public class CafMediaRouteProvider extends CafBaseMediaRouteProvider { ...@@ -141,7 +142,7 @@ public class CafMediaRouteProvider extends CafBaseMediaRouteProvider {
clientRecord.pendingMessages.clear(); clientRecord.pendingMessages.clear();
} }
@Override @NonNull
public CafMessageHandler getMessageHandler() { public CafMessageHandler getMessageHandler() {
return mMessageHandler; return mMessageHandler;
} }
......
...@@ -4,6 +4,9 @@ ...@@ -4,6 +4,9 @@
package org.chromium.chrome.browser.media.router.caf; package org.chromium.chrome.browser.media.router.caf;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import com.google.android.gms.cast.ApplicationMetadata; import com.google.android.gms.cast.ApplicationMetadata;
import com.google.android.gms.cast.Cast; import com.google.android.gms.cast.Cast;
import com.google.android.gms.cast.CastDevice; import com.google.android.gms.cast.CastDevice;
...@@ -21,7 +24,7 @@ public class CastSessionController extends BaseSessionController { ...@@ -21,7 +24,7 @@ public class CastSessionController extends BaseSessionController {
private static final String TAG = "CafSessionCtrl"; private static final String TAG = "CafSessionCtrl";
private List<String> mNamespaces = new ArrayList<String>(); private List<String> mNamespaces = new ArrayList<String>();
private final CastListener mCastListener; private CastListener mCastListener;
public CastSessionController(CafBaseMediaRouteProvider provider) { public CastSessionController(CafBaseMediaRouteProvider provider) {
super(provider); super(provider);
...@@ -32,6 +35,12 @@ public class CastSessionController extends BaseSessionController { ...@@ -32,6 +35,12 @@ public class CastSessionController extends BaseSessionController {
return mNamespaces; return mNamespaces;
} }
/** Init nested fields for testing. The reason is that nested classes are bound to the original
* instance instead of the spyed instance. */
void initNestedFieldsForTesting() {
mCastListener = new CastListener();
}
@Override @Override
public void attachToCastSession(CastSession session) { public void attachToCastSession(CastSession session) {
super.attachToCastSession(session); super.attachToCastSession(session);
...@@ -50,9 +59,7 @@ public class CastSessionController extends BaseSessionController { ...@@ -50,9 +59,7 @@ public class CastSessionController extends BaseSessionController {
@Override @Override
public void onSessionEnded() { public void onSessionEnded() {
CafMessageHandler messageHandler = getMessageHandler(); getMessageHandler().onSessionEnded();
if (messageHandler == null) return;
messageHandler.onSessionEnded();
super.onSessionEnded(); super.onSessionEnded();
} }
...@@ -70,25 +77,26 @@ public class CastSessionController extends BaseSessionController { ...@@ -70,25 +77,26 @@ public class CastSessionController extends BaseSessionController {
@Override @Override
public void onVolumeChanged() { public void onVolumeChanged() {
CastSessionController.this.onApplicationStatusChanged(); CastSessionController.this.onApplicationStatusChanged();
CafMessageHandler messageHandler = getMessageHandler(); getMessageHandler().onVolumeChanged();
if (messageHandler == null) return;
messageHandler.onVolumeChanged();
} }
} }
private void onApplicationStatusChanged() { private void onApplicationStatusChanged() {
updateNamespaces(); updateNamespaces();
CafMessageHandler messageHandler = getMessageHandler(); getMessageHandler().broadcastClientMessage(
if (messageHandler != null) { "update_session", getMessageHandler().buildSessionMessage());
messageHandler.broadcastClientMessage(
"update_session", messageHandler.buildSessionMessage());
}
} }
private void updateNamespaces() { @VisibleForTesting
void updateNamespaces() {
if (!isConnected()) return; if (!isConnected()) return;
if (getSession().getApplicationMetadata() == null
|| getSession().getApplicationMetadata().getSupportedNamespaces() == null) {
return;
}
Set<String> namespacesToAdd = Set<String> namespacesToAdd =
new HashSet<>(getSession().getApplicationMetadata().getSupportedNamespaces()); new HashSet<>(getSession().getApplicationMetadata().getSupportedNamespaces());
Set<String> namespacesToRemove = new HashSet<String>(mNamespaces); Set<String> namespacesToRemove = new HashSet<String>(mNamespaces);
...@@ -129,10 +137,10 @@ public class CastSessionController extends BaseSessionController { ...@@ -129,10 +137,10 @@ public class CastSessionController extends BaseSessionController {
@Override @Override
protected void onMessageReceived(CastDevice castDevice, String namespace, String message) { protected void onMessageReceived(CastDevice castDevice, String namespace, String message) {
super.onMessageReceived(castDevice, namespace, message); super.onMessageReceived(castDevice, namespace, message);
CafMessageHandler messageHandler = getMessageHandler(); getMessageHandler().onMessageReceived(namespace, message);
if (messageHandler != null) messageHandler.onMessageReceived(namespace, message);
} }
@NonNull
private CafMessageHandler getMessageHandler() { private CafMessageHandler getMessageHandler() {
return ((CafMediaRouteProvider) getProvider()).getMessageHandler(); return ((CafMediaRouteProvider) getProvider()).getMessageHandler();
} }
......
...@@ -2358,6 +2358,7 @@ chrome_junit_test_java_sources = [ ...@@ -2358,6 +2358,7 @@ chrome_junit_test_java_sources = [
"junit/src/org/chromium/chrome/browser/media/router/caf/ShadowMediaRouter.java", "junit/src/org/chromium/chrome/browser/media/router/caf/ShadowMediaRouter.java",
"junit/src/org/chromium/chrome/browser/media/router/caf/ShadowCastContext.java", "junit/src/org/chromium/chrome/browser/media/router/caf/ShadowCastContext.java",
"junit/src/org/chromium/chrome/browser/media/router/caf/BaseSessionControllerTest.java", "junit/src/org/chromium/chrome/browser/media/router/caf/BaseSessionControllerTest.java",
"junit/src/org/chromium/chrome/browser/media/router/caf/CastSessionControllerTest.java",
"junit/src/org/chromium/chrome/browser/media/ui/MediaImageManagerTest.java", "junit/src/org/chromium/chrome/browser/media/ui/MediaImageManagerTest.java",
"junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationActionsUpdatedTest.java", "junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationActionsUpdatedTest.java",
"junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationButtonComputationTest.java", "junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationButtonComputationTest.java",
......
// Copyright 2018 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.chrome.browser.media.router.caf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.content.Context;
import com.google.android.gms.cast.ApplicationMetadata;
import com.google.android.gms.cast.Cast;
import com.google.android.gms.cast.CastDevice;
import com.google.android.gms.cast.framework.CastSession;
import com.google.android.gms.cast.framework.media.RemoteMediaClient;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.media.router.CastSessionUtil;
import org.chromium.chrome.browser.media.router.MediaSink;
import org.chromium.chrome.browser.media.router.MediaSource;
import java.util.ArrayList;
import java.util.List;
/**
* Robolectric tests for CastSessionController.
*/
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class CastSessionControllerTest {
private static final String PRESENTATION_ID = "presentation-id";
private static final String ORIGIN = "https://example.com/";
private static final int TAB_ID = 1;
private static final String APP_ID = "12345678";
@Mock
private CastDevice mCastDevice;
@Mock
private CafMediaRouteProvider mProvider;
@Mock
private CafNotificationController mNotificationController;
@Mock
private MediaSource mSource;
@Mock
private MediaSink mSink;
@Mock
private CastSession mCastSession;
@Mock
private RemoteMediaClient mRemoteMediaClient;
@Mock
private CafMessageHandler mMessageHandler;
@Mock
private ApplicationMetadata mApplicationMetadata;
private CastSessionController mController;
private CreateRouteRequestInfo mRequestInfo;
private MediaRouterTestHelper mMediaRouterHelper;
private Context mContext;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mMediaRouterHelper = new MediaRouterTestHelper();
mController = spy(new CastSessionController(mProvider));
mController.mNotificationController = mNotificationController;
mController.initNestedFieldsForTesting();
doReturn(mRemoteMediaClient).when(mCastSession).getRemoteMediaClient();
doReturn(true).when(mCastSession).isConnected();
doReturn(mApplicationMetadata).when(mCastSession).getApplicationMetadata();
doReturn(mCastDevice).when(mCastSession).getCastDevice();
doReturn(mMessageHandler).when(mProvider).getMessageHandler();
doReturn("session_message").when(mMessageHandler).buildSessionMessage();
}
@Test
public void testSessionAttachment() {
// Attaching to a session.
mController.attachToCastSession(mCastSession);
verify(mCastSession).addCastListener(any(Cast.Listener.class));
assertSame(mController.getSession(), mCastSession);
// Detaching from a session.
mController.detachFromCastSession();
verify(mCastSession).removeCastListener(any(Cast.Listener.class));
assertNull(mController.getSession());
}
@Test
public void testOnSessionEnded() {
mController.attachToCastSession(mCastSession);
mController.onSessionEnded();
verify(mMessageHandler).onSessionEnded();
}
@Test
public void testCastListener() {
InOrder inOrder = inOrder(mController, mMessageHandler);
ArgumentCaptor<Cast.Listener> castListenerCaptor =
ArgumentCaptor.forClass(Cast.Listener.class);
mController.attachToCastSession(mCastSession);
verify(mCastSession).addCastListener(castListenerCaptor.capture());
// When the application status is changed, the namespaces should be updated and a session
// message needs to be broadcasted.
castListenerCaptor.getValue().onApplicationStatusChanged();
inOrder.verify(mController).updateNamespaces();
inOrder.verify(mMessageHandler).broadcastClientMessage("update_session", "session_message");
// When the application metadata is changed, the namespaces should be updated and a session
// message needs to be broadcasted.
castListenerCaptor.getValue().onApplicationMetadataChanged(mApplicationMetadata);
inOrder.verify(mController).updateNamespaces();
inOrder.verify(mMessageHandler).broadcastClientMessage("update_session", "session_message");
// When the volume is changed, the namespaces should be updated and a session message and a
// volume change message needs to be broadcasted.
castListenerCaptor.getValue().onVolumeChanged();
inOrder.verify(mController).updateNamespaces();
inOrder.verify(mMessageHandler).broadcastClientMessage("update_session", "session_message");
inOrder.verify(mMessageHandler).onVolumeChanged();
}
@Test
public void testUpdateNamespaces() throws Exception {
org.robolectric.shadows.ShadowLog.stream = System.out;
InOrder inOrder = inOrder(mCastSession);
mController.attachToCastSession(mCastSession);
List<String> namespaces = new ArrayList<>();
List<String> observedNamespaces = new ArrayList<>();
doReturn(namespaces).when(mApplicationMetadata).getSupportedNamespaces();
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) {
observedNamespaces.add((String) (invocation.getArguments()[0]));
return null;
}
})
.when(mCastSession)
.setMessageReceivedCallbacks(
any(String.class), any(Cast.MessageReceivedCallback.class));
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) {
observedNamespaces.remove((String) (invocation.getArguments()[0]));
return null;
}
})
.when(mCastSession)
.removeMessageReceivedCallbacks(any(String.class));
// New namespaces added.
namespaces.add("namespace1");
namespaces.add("namespace2");
mController.updateNamespaces();
assertNamespacesContainsExactly(observedNamespaces, "namespace1", "namespace2");
// Namespaces unchanged.
mController.updateNamespaces();
assertNamespacesContainsExactly(observedNamespaces, "namespace1", "namespace2");
// New namespace added.
namespaces.add("namespace3");
mController.updateNamespaces();
assertNamespacesContainsExactly(
observedNamespaces, "namespace1", "namespace2", "namespace3");
// Namespaces removed.
namespaces.remove("namespace1");
namespaces.remove("namespace3");
mController.updateNamespaces();
assertNamespacesContainsExactly(observedNamespaces, "namespace2");
}
@Test
public void testOnMessageReceived() {
mController.attachToCastSession(mCastSession);
// Non-media namespaces should be forwarded to the message handler. RemoteMediaClient only
// listens to the media namespace thus it shouldn't be notified.
mController.onMessageReceived(mCastDevice, "namespace", "message");
verify(mRemoteMediaClient, never())
.onMessageReceived(any(CastDevice.class), anyString(), anyString());
verify(mMessageHandler).onMessageReceived("namespace", "message");
// Media namespaces should be both forwarded to the message handler and RemoteMediaClient.
mController.onMessageReceived(mCastDevice, CastSessionUtil.MEDIA_NAMESPACE, "message");
verify(mRemoteMediaClient)
.onMessageReceived(mCastDevice, CastSessionUtil.MEDIA_NAMESPACE, "message");
verify(mMessageHandler).onMessageReceived(CastSessionUtil.MEDIA_NAMESPACE, "message");
}
private void assertNamespacesContainsExactly(
List<String> namespaces, String... expectedNamespaces) {
assertEquals(namespaces.size(), expectedNamespaces.length);
for (String namespace : expectedNamespaces) {
assertTrue(namespaces.contains(namespace));
}
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment