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
This commit is contained in:
glaznev@webrtc.org 2015-01-02 19:51:12 +00:00
parent 896888b7e4
commit 8390c2762e
8 changed files with 490 additions and 2 deletions

View File

@ -14,7 +14,7 @@ Prerequisites:
Example of building & using the app:
cd <path/to/libjingle>/src
cd <path/to/webrtc>/src
ninja -C out/Debug AppRTCDemo
adb install -r out/Debug/AppRTCDemo-debug.apk

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.appspot.apprtc.test"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="13" android:targetSdkVersion="21" />
<instrumentation
android:name="android.test.InstrumentationTestRunner"
android:targetPackage="org.appspot.apprtc" />
<application>
<uses-library android:name="android.test.runner" />
</application>
</manifest>

View File

@ -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 <path/to/webrtc>/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

View File

@ -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

View File

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="AppRTCDemoTest" default="help">
<!-- The local.properties file is created and updated by the 'android' tool.
It contains the path to the SDK. It should *NOT* be checked into
Version Control Systems. -->
<property file="local.properties" />
<!-- The ant.properties file can be created by you. It is only edited by the
'android' tool to add properties to it.
This is the place to change some Ant specific build properties.
Here are some properties you may want to change/update:
source.dir
The name of the source directory. Default is 'src'.
out.dir
The name of the output directory. Default is 'bin'.
For other overridable properties, look at the beginning of the rules
files in the SDK, at tools/ant/build.xml
Properties related to the SDK location or the project target should
be updated using the 'android' tool with the 'update' action.
This file is an integral part of the build system for your
application and should be checked into Version Control Systems.
-->
<property file="ant.properties" />
<!-- if sdk.dir was not set from one of the property file, then
get it from the ANDROID_HOME env var.
This must be done before we load project.properties since
the proguard config can use sdk.dir -->
<property environment="env" />
<condition property="sdk.dir" value="${env.ANDROID_SDK_ROOT}">
<isset property="env.ANDROID_SDK_ROOT" />
</condition>
<!-- The project.properties file is created and updated by the 'android'
tool, as well as ADT.
This contains project specific properties such as project target, and library
dependencies. Lower level build properties are stored in ant.properties
(or in .classpath for Eclipse projects).
This file is an integral part of the build system for your
application and should be checked into Version Control Systems. -->
<loadproperties srcFile="project.properties" />
<!-- quick check on sdk.dir -->
<fail
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
unless="sdk.dir"
/>
<!--
Import per project custom build rules if present at the root of the project.
This is the place to put custom intermediary targets such as:
-pre-build
-pre-compile
-post-compile (This is typically used for code obfuscation.
Compiled code location: ${out.classes.absolute.dir}
If this is not done in place, override ${out.dex.input.absolute.dir})
-post-package
-post-build
-pre-clean
-->
<import file="custom_rules.xml" optional="true" />
<!-- Import the actual build file.
To customize existing targets, there are two options:
- Customize only one target:
- copy/paste the target into this file, *before* the
<import> task.
- customize it to your needs.
- Customize the whole content of build.xml
- copy/paste the content of the rules files (minus the top node)
into this file, replacing the <import> task.
- customize to your needs.
***********************
****** IMPORTANT ******
***********************
In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
in order to avoid having your file be overridden by tools such as "android update project"
-->
<!-- version-tag: 1 -->
<import file="${sdk.dir}/tools/ant/build.xml" />
</project>

View File

@ -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

View File

@ -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<IceCandidate> iceCandidates = new LinkedList<IceCandidate>();
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<PeerConnection.IceServer> iceServers =
new LinkedList<PeerConnection.IceServer>();
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.");
}
}

View File

@ -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"
],
}