From 8390c2762e6be87e213c1cb0d65af480c52e2e39 Mon Sep 17 00:00:00 2001 From: "glaznev@webrtc.org" Date: Fri, 2 Jan 2015 19:51:12 +0000 Subject: [PATCH] Add two unit tests for Android AppRTCDemo. First unit test will create peer connection client, run for a few second, close it and verify that there were no any errors and local video was rendered. Second unit test will run peer connection in a loopback mode. To run the test from command line install AppRTCDemoTest.apk and execute the command: adb shell am instrument -w org.appspot.apprtc.test/android.test.InstrumentationTestRunner R=jiayl@webrtc.org Review URL: https://webrtc-codereview.appspot.com/33609004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@7991 4adac7df-926f-26a2-2b94-8c16560cd09d --- talk/examples/android/README | 2 +- .../examples/androidtests/AndroidManifest.xml | 17 ++ talk/examples/androidtests/README | 14 + talk/examples/androidtests/ant.properties | 18 ++ talk/examples/androidtests/build.xml | 92 ++++++ talk/examples/androidtests/project.properties | 16 + .../apprtc/test/PeerConnectionClientTest.java | 288 ++++++++++++++++++ talk/libjingle_examples.gyp | 45 ++- 8 files changed, 490 insertions(+), 2 deletions(-) create mode 100644 talk/examples/androidtests/AndroidManifest.xml create mode 100644 talk/examples/androidtests/README create mode 100644 talk/examples/androidtests/ant.properties create mode 100644 talk/examples/androidtests/build.xml create mode 100644 talk/examples/androidtests/project.properties create mode 100644 talk/examples/androidtests/src/org/appspot/apprtc/test/PeerConnectionClientTest.java diff --git a/talk/examples/android/README b/talk/examples/android/README index 5fefb945d8..ee47e1f672 100644 --- a/talk/examples/android/README +++ b/talk/examples/android/README @@ -14,7 +14,7 @@ Prerequisites: Example of building & using the app: -cd /src +cd /src ninja -C out/Debug AppRTCDemo adb install -r out/Debug/AppRTCDemo-debug.apk diff --git a/talk/examples/androidtests/AndroidManifest.xml b/talk/examples/androidtests/AndroidManifest.xml new file mode 100644 index 0000000000..f99f477a67 --- /dev/null +++ b/talk/examples/androidtests/AndroidManifest.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/talk/examples/androidtests/README b/talk/examples/androidtests/README new file mode 100644 index 0000000000..7018ebd21f --- /dev/null +++ b/talk/examples/androidtests/README @@ -0,0 +1,14 @@ +This directory contains an example unit test for Android AppRTCDemo. + +Example of building & using the app: + +- Build Android AppRTCDemo and AppRTCDemo unit test: +cd /src +ninja -C out/Debug AppRTCDemoTest + +- Install AppRTCDemo and AppRTCDemoTest: +adb install -r out/Debug/AppRTCDemo-debug.apk +adb install -r out/Debug/AppRTCDemoTest-debug.apk + +- Run unit tests: +adb shell am instrument -w org.appspot.apprtc.test/android.test.InstrumentationTestRunner \ No newline at end of file diff --git a/talk/examples/androidtests/ant.properties b/talk/examples/androidtests/ant.properties new file mode 100644 index 0000000000..ec7d042885 --- /dev/null +++ b/talk/examples/androidtests/ant.properties @@ -0,0 +1,18 @@ +# This file is used to override default values used by the Ant build system. +# +# This file must be checked into Version Control Systems, as it is +# integral to the build system of your project. + +# This file is only used by the Ant script. + +# You can use this to override default values such as +# 'source.dir' for the location of your java source folder and +# 'out.dir' for the location of your output folder. + +# You can also use it define how the release builds are signed by declaring +# the following properties: +# 'key.store' for the location of your keystore and +# 'key.alias' for the name of the key to use. +# The password will be asked during the build when you use the 'release' target. + +tested.project.dir=../android diff --git a/talk/examples/androidtests/build.xml b/talk/examples/androidtests/build.xml new file mode 100644 index 0000000000..036759bd96 --- /dev/null +++ b/talk/examples/androidtests/build.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/talk/examples/androidtests/project.properties b/talk/examples/androidtests/project.properties new file mode 100644 index 0000000000..47b70783f2 --- /dev/null +++ b/talk/examples/androidtests/project.properties @@ -0,0 +1,16 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-21 + +java.compilerargs=-Xlint:all -Werror diff --git a/talk/examples/androidtests/src/org/appspot/apprtc/test/PeerConnectionClientTest.java b/talk/examples/androidtests/src/org/appspot/apprtc/test/PeerConnectionClientTest.java new file mode 100644 index 0000000000..05d54dbef8 --- /dev/null +++ b/talk/examples/androidtests/src/org/appspot/apprtc/test/PeerConnectionClientTest.java @@ -0,0 +1,288 @@ +/* + * libjingle + * Copyright 2014, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.appspot.apprtc.test; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.appspot.apprtc.AppRTCClient.SignalingParameters; +import org.appspot.apprtc.PeerConnectionClient; +import org.appspot.apprtc.PeerConnectionClient.PeerConnectionEvents; +import org.webrtc.IceCandidate; +import org.webrtc.MediaConstraints; +import org.webrtc.PeerConnection; +import org.webrtc.PeerConnectionFactory; +import org.webrtc.SessionDescription; +import org.webrtc.VideoRenderer; + +import android.test.InstrumentationTestCase; +import android.util.Log; + +public class PeerConnectionClientTest extends InstrumentationTestCase + implements PeerConnectionEvents { + private static final String TAG = "RTCClientTest"; + private static final String STUN_SERVER = "stun:stun.l.google.com:19302"; + private static final int WAIT_TIMEOUT = 3000; + private static final int EXPECTED_VIDEO_FRAMES = 15; + + private volatile PeerConnectionClient pcClient; + private volatile boolean loopback; + private boolean isClosed; + private boolean isIceConnected; + private SessionDescription localSdp; + private List iceCandidates = new LinkedList(); + private final Object localSdpEvent = new Object(); + private final Object iceCandidateEvent = new Object(); + private final Object iceConnectedEvent = new Object(); + private final Object closeEvent = new Object(); + + // Mock renderer implementation. + private static class MockRenderer implements VideoRenderer.Callbacks { + private final CountDownLatch doneRendering; + private int width = -1; + private int height = -1; + private int numFramesDelivered = 0; + + public MockRenderer(int expectedFrames) { + doneRendering = new CountDownLatch(expectedFrames); + } + + @Override + public synchronized void setSize(int width, int height) { + Log.d(TAG, "Set size: " + width + " x " + height); + this.width = width; + this.height = height; + } + + @Override + public synchronized void renderFrame(VideoRenderer.I420Frame frame) { + // Check that video dimensions have been set. + if (numFramesDelivered == 0) { + assertTrue("Video dimensions were not set.", width > 0 && height > 0); + } + numFramesDelivered++; + doneRendering.countDown(); + } + + public boolean waitForFramesRendered(int timeoutMs) + throws InterruptedException { + doneRendering.await(timeoutMs, TimeUnit.MILLISECONDS); + return (doneRendering.getCount() <= 0); + } + } + + // Peer connection events implementation. + @Override + public void onLocalDescription(SessionDescription sdp) { + Log.d(TAG, "LocalSDP type: " + sdp.type); + synchronized (localSdpEvent) { + localSdp = sdp; + localSdpEvent.notifyAll(); + } + } + + @Override + public void onIceCandidate(IceCandidate candidate) { + Log.d(TAG, "IceCandidate: " + candidate.sdp); + synchronized(iceCandidateEvent) { + if (loopback) { + pcClient.addRemoteIceCandidate(candidate); + } + iceCandidates.add(candidate); + iceCandidateEvent.notifyAll(); + } + } + + @Override + public void onIceConnected() { + Log.d(TAG, "ICE Connected"); + synchronized(iceConnectedEvent) { + isIceConnected = true; + iceConnectedEvent.notifyAll(); + } + } + + @Override + public void onIceDisconnected() { + Log.d(TAG, "ICE Disconnected"); + synchronized(iceConnectedEvent) { + isIceConnected = false; + iceConnectedEvent.notifyAll(); + } + } + + @Override + public void onPeerConnectionClosed() { + Log.d(TAG, "PeerConnection closed"); + synchronized(closeEvent) { + isClosed = true; + closeEvent.notifyAll(); + } + } + + @Override + public void onPeerConnectionError(String description) { + fail("PC Error: " + description); + } + + // Helper wait functions. + private boolean waitForLocalSDP(int timeoutMs) + throws InterruptedException { + synchronized(localSdpEvent) { + if (localSdp == null) { + localSdpEvent.wait(timeoutMs); + } + return (localSdp != null); + } + } + + private boolean waitForIceCandidates(int timeoutMs) + throws InterruptedException { + synchronized(iceCandidateEvent) { + if (iceCandidates.size() == 0) { + iceCandidateEvent.wait(timeoutMs); + } + return (iceCandidates.size() > 0); + } + } + + private boolean waitForIceConnected(int timeoutMs) + throws InterruptedException { + synchronized(iceConnectedEvent) { + if (!isIceConnected) { + iceConnectedEvent.wait(timeoutMs); + } + return isIceConnected; + } + } + + private boolean waitForPeerConnectionClosed(int timeoutMs) + throws InterruptedException { + synchronized(closeEvent) { + if (!isClosed) { + closeEvent.wait(timeoutMs); + } + return isClosed; + } + } + + private SignalingParameters getTestSignalingParameters() { + List iceServers = + new LinkedList(); + PeerConnection.IceServer iceServer = new + PeerConnection.IceServer(STUN_SERVER, "", ""); + iceServers.add(iceServer); + MediaConstraints pcConstraints = new MediaConstraints(); + MediaConstraints videoConstraints = new MediaConstraints(); + MediaConstraints audioConstraints = new MediaConstraints(); + SignalingParameters signalingParameters = new SignalingParameters( + iceServers, true, + pcConstraints, videoConstraints, audioConstraints, + null, null, null, + null, null, + null, null); + return signalingParameters; + } + + // Unit tests. + @Override + protected void setUp() throws Exception { + Log.d(TAG, "setUp"); + super.setUp(); + pcClient = null; + localSdp = null; + iceCandidates.clear(); + isClosed = false; + isIceConnected = false; + loopback = false; + Log.d(TAG, "initializeAndroidGlobals"); + assertTrue(PeerConnectionFactory.initializeAndroidGlobals( + getInstrumentation().getContext(), true, true, true, null)); + } + + public void testInitiatorCreation() throws InterruptedException { + Log.d(TAG, "testInitiatorCreation"); + MockRenderer localRender = new MockRenderer(EXPECTED_VIDEO_FRAMES); + MockRenderer remoteRender = new MockRenderer(EXPECTED_VIDEO_FRAMES); + SignalingParameters signalingParameters = getTestSignalingParameters(); + + pcClient = new PeerConnectionClient( + localRender, remoteRender, signalingParameters, this, 1000); + pcClient.createOffer(); + + // Wait for local SDP and ice candidates set events. + assertTrue("Local SDP was not set.", waitForLocalSDP(WAIT_TIMEOUT)); + assertTrue("ICE candidates were not generated.", + waitForIceCandidates(WAIT_TIMEOUT)); + + // Check that local video frames were rendered. + assertTrue("Local video frames were not rendered.", + localRender.waitForFramesRendered(WAIT_TIMEOUT)); + + pcClient.close(); + assertTrue("PeerConnection close event was not received.", + waitForPeerConnectionClosed(WAIT_TIMEOUT)); + Log.d(TAG, "testInitiatorCreation Done."); + } + + public void testLoopback() throws InterruptedException { + Log.d(TAG, "testLoopback"); + MockRenderer localRender = new MockRenderer(EXPECTED_VIDEO_FRAMES); + MockRenderer remoteRender = new MockRenderer(EXPECTED_VIDEO_FRAMES); + SignalingParameters signalingParameters = getTestSignalingParameters(); + loopback = true; + pcClient = new PeerConnectionClient( + localRender, remoteRender, signalingParameters, this, 1000); + pcClient.createOffer(); + + // Wait for local SDP, rename it to answer and set as remote SDP. + assertTrue("Local SDP was not set.", waitForLocalSDP(WAIT_TIMEOUT)); + SessionDescription remoteSdp = new SessionDescription( + SessionDescription.Type.fromCanonicalForm("answer"), + localSdp.description); + pcClient.setRemoteDescription(remoteSdp); + + // Wait for ICE connection. + assertTrue("ICE connection failure.", waitForIceConnected(WAIT_TIMEOUT)); + + // Check that local video frames were rendered. + assertTrue("Local video frames were not rendered.", + localRender.waitForFramesRendered(WAIT_TIMEOUT)); + + // Check that remote video frames were rendered. + assertTrue("Remote video frames were not rendered.", + remoteRender.waitForFramesRendered(WAIT_TIMEOUT)); + + pcClient.close(); + assertTrue(waitForPeerConnectionClosed(WAIT_TIMEOUT)); + Log.d(TAG, "testLoopback Done."); + } + +} diff --git a/talk/libjingle_examples.gyp b/talk/libjingle_examples.gyp index 8f806f43bf..aba386b88b 100755 --- a/talk/libjingle_examples.gyp +++ b/talk/libjingle_examples.gyp @@ -282,7 +282,7 @@ ], 'actions': [ { - # TODO(fischman): convert from a custom script to a standard gyp + # TODO(glaznev): convert from a custom script to a standard gyp # apk build once chromium's apk-building gyp machinery can be used # (http://crbug.com/225101) 'action_name': 'build_apprtcdemo_apk', @@ -364,5 +364,48 @@ }, # target AppRTCDemo ], # targets }], # OS=="android" + + ['OS=="android"', { + 'targets': [ + { + 'target_name': 'AppRTCDemoTest', + 'type': 'none', + 'dependencies': [ + 'AppRTCDemo', + ], + 'actions': [ + { + # TODO(glaznev): convert from a custom script to a standard gyp + # apk build once chromium's apk-building gyp machinery can be used + # (http://crbug.com/225101) + 'action_name': 'build_apprtcdemotest_apk', + 'inputs' : [ + 'examples/androidtests/AndroidManifest.xml', + 'examples/androidtests/ant.properties', + 'examples/androidtests/build.xml', + 'examples/androidtests/project.properties', + 'examples/androidtests/src/org/appspot/apprtc/test/PeerConnectionClientTest.java', + ], + 'outputs': [ + '<(PRODUCT_DIR)/AppRTCDemoTest-debug.apk', + ], + 'variables': { + 'ant_log': '../../<(INTERMEDIATE_DIR)/ant.log', # ../.. to compensate for the cd examples/androidtests below. + }, + 'action': [ + 'bash', '-ec', + 'mkdir -p <(INTERMEDIATE_DIR) && ' # Must happen _before_ the cd below + 'cd examples/androidtests && ' + '{ ANDROID_SDK_ROOT=<(android_sdk_root) ' + 'ant debug > <(ant_log) 2>&1 || ' + ' { cat <(ant_log) ; exit 1; } } && ' + 'cd - > /dev/null && ' + 'cp examples/androidtests/bin/AppRTCDemoTest-debug.apk <(_outputs)' + ], + }, + ], + }, # target AppRTCDemoTest + ], # targets + }], # OS=="android" ], }