am b79b5e8d: Merge "Add a CTS test for the VPN API." into lmp-mr1-dev

* commit 'b79b5e8d868cdf28f7edd1b7e37dc380df43a91f':
  Add a CTS test for the VPN API.
This commit is contained in:
Lorenzo Colitti
2014-12-03 20:42:03 +00:00
committed by Android Git Automerger
8 changed files with 808 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
# Copyright (C) 2014 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# Only compile source java files in this apk.
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_MODULE := CtsHostsideNetworkTests
LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed-prebuilt
LOCAL_CTS_TEST_PACKAGE := android.net.hostsidenetwork
include $(BUILD_CTS_HOST_JAVA_LIBRARY)
# Build the test APKs using their own makefiles
include $(call all-makefiles-under,$(LOCAL_PATH))

View File

@@ -0,0 +1,32 @@
#
# Copyright (C) 2014 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
LOCAL_SDK_VERSION := current
LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner ub-uiautomator
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := CtsHostsideNetworkTestsApp
LOCAL_PROGUARD_ENABLED := disabled
LOCAL_DEX_PREOPT := false
include $(BUILD_PACKAGE)

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2014 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.cts.net.hostside">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<application>
<uses-library android:name="android.test.runner" />
<activity android:name=".MyActivity" />
<service android:name=".MyVpnService"
android:permission="android.permission.BIND_VPN_SERVICE">
<intent-filter>
<action android:name="android.net.VpnService"/>
</intent-filter>
</service>
</application>
<instrumentation
android:name="android.support.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.cts.net.hostside" />
</manifest>

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.cts.net.hostside;
import android.app.Activity;
import android.content.Intent;
import android.net.VpnService;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.view.WindowManager;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
public class MyActivity extends Activity {
private final LinkedBlockingQueue<Integer> mResult = new LinkedBlockingQueue<>(1);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
| WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (mResult.offer(resultCode) == false) {
throw new RuntimeException("Queue is full! This should never happen");
}
}
public Integer getResult(int timeoutMs) throws InterruptedException {
return mResult.poll(timeoutMs, TimeUnit.MILLISECONDS);
}
}

View File

@@ -0,0 +1,155 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.cts.net.hostside;
import android.content.Intent;
import android.net.VpnService;
import android.os.ParcelFileDescriptor;
import android.content.pm.PackageManager.NameNotFoundException;
import android.text.TextUtils;
import android.util.Log;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
public class MyVpnService extends VpnService {
private static String TAG = "MyVpnService";
private static int MTU = 1799;
private ParcelFileDescriptor mFd = null;
private UdpReflector mUdpReflector = null;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String packageName = getPackageName();
String cmd = intent.getStringExtra(packageName + ".cmd");
if ("disconnect".equals(cmd)) {
stop();
} else if ("connect".equals(cmd)) {
start(packageName, intent);
}
return START_NOT_STICKY;
}
private void start(String packageName, Intent intent) {
Builder builder = new Builder();
String addresses = intent.getStringExtra(packageName + ".addresses");
if (addresses != null) {
String[] addressArray = addresses.split(",");
for (int i = 0; i < addressArray.length; i++) {
String[] prefixAndMask = addressArray[i].split("/");
try {
InetAddress address = InetAddress.getByName(prefixAndMask[0]);
int prefixLength = Integer.parseInt(prefixAndMask[1]);
builder.addAddress(address, prefixLength);
} catch (UnknownHostException|NumberFormatException|
ArrayIndexOutOfBoundsException e) {
continue;
}
}
}
String routes = intent.getStringExtra(packageName + ".routes");
if (routes != null) {
String[] routeArray = routes.split(",");
for (int i = 0; i < routeArray.length; i++) {
String[] prefixAndMask = routeArray[i].split("/");
try {
InetAddress address = InetAddress.getByName(prefixAndMask[0]);
int prefixLength = Integer.parseInt(prefixAndMask[1]);
builder.addRoute(address, prefixLength);
} catch (UnknownHostException|NumberFormatException|
ArrayIndexOutOfBoundsException e) {
continue;
}
}
}
String allowed = intent.getStringExtra(packageName + ".allowedapplications");
if (allowed != null) {
String[] packageArray = allowed.split(",");
for (int i = 0; i < packageArray.length; i++) {
String allowedPackage = packageArray[i];
if (!TextUtils.isEmpty(allowedPackage)) {
try {
builder.addAllowedApplication(allowedPackage);
} catch(NameNotFoundException e) {
continue;
}
}
}
}
String disallowed = intent.getStringExtra(packageName + ".disallowedapplications");
if (disallowed != null) {
String[] packageArray = disallowed.split(",");
for (int i = 0; i < packageArray.length; i++) {
String disallowedPackage = packageArray[i];
if (!TextUtils.isEmpty(disallowedPackage)) {
try {
builder.addDisallowedApplication(disallowedPackage);
} catch(NameNotFoundException e) {
continue;
}
}
}
}
builder.setMtu(MTU);
builder.setBlocking(true);
builder.setSession("MyVpnService");
Log.i(TAG, "Establishing VPN,"
+ " addresses=" + addresses
+ " routes=" + routes
+ " allowedApplications=" + allowed
+ " disallowedApplications=" + disallowed);
mFd = builder.establish();
Log.i(TAG, "Established, fd=" + (mFd == null ? "null" : mFd.getFd()));
mUdpReflector = new UdpReflector(mFd.getFileDescriptor(), MTU);
mUdpReflector.start();
}
private void stop() {
if (mUdpReflector != null) {
mUdpReflector.interrupt();
mUdpReflector = null;
}
try {
if (mFd != null) {
Log.i(TAG, "Closing filedescriptor");
mFd.close();
}
} catch(IOException e) {
} finally {
mFd = null;
}
}
@Override
public void onDestroy() {
stop();
super.onDestroy();
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.cts.net.hostside;
import android.system.Os;
import android.system.ErrnoException;
import android.util.Log;
import java.io.FileDescriptor;
import java.io.IOException;
public class UdpReflector extends Thread {
private static int IPV4_HEADER_LENGTH = 20;
private static int IPV6_HEADER_LENGTH = 40;
private static int UDP_HEADER_LENGTH = 8;
private static int IPV4_PROTO_OFFSET = 9;
private static int IPV6_PROTO_OFFSET = 6;
private static int IPPROTO_UDP = 17;
private static int IPV4_ADDR_OFFSET = 12;
private static int IPV6_ADDR_OFFSET = 8;
private static int IPV4_ADDR_LENGTH = 4;
private static int IPV6_ADDR_LENGTH = 16;
private static String TAG = "UdpReflector";
private FileDescriptor mFd;
private byte[] mBuf;
public UdpReflector(FileDescriptor fd, int mtu) {
super("UdpReflector");
mFd = fd;
mBuf = new byte[mtu];
}
private static void swapBytes(byte[] buf, int pos1, int pos2, int len) {
for (int i = 0; i < len; i++) {
byte b = buf[pos1 + i];
buf[pos1 + i] = buf[pos2 + i];
buf[pos2 + i] = b;
}
}
/** Reads one packet from our mFd, and possibly writes the packet back. */
private void processPacket() {
int len;
try {
len = Os.read(mFd, mBuf, 0, mBuf.length);
} catch (ErrnoException|IOException e) {
Log.e(TAG, "Error reading packet: " + e.getMessage());
return;
}
int version = mBuf[0] >> 4;
int addressOffset, protoOffset, headerLength, addressLength;
if (version == 4) {
headerLength = IPV4_HEADER_LENGTH;
protoOffset = IPV4_PROTO_OFFSET;
addressOffset = IPV4_ADDR_OFFSET;
addressLength = IPV4_ADDR_LENGTH;
} else if (version == 6) {
headerLength = IPV6_HEADER_LENGTH;
protoOffset = IPV6_PROTO_OFFSET;
addressOffset = IPV6_ADDR_OFFSET;
addressLength = IPV6_ADDR_LENGTH;
} else {
return;
}
if (len < headerLength + UDP_HEADER_LENGTH || mBuf[protoOffset] != IPPROTO_UDP) {
return;
}
// Swap src and dst IP addresses.
swapBytes(mBuf, addressOffset, addressOffset + addressLength, addressLength);
// Swap dst and src ports.
int portOffset = headerLength;
swapBytes(mBuf, portOffset, portOffset + 2, 2);
// Send the packet back. We don't need to recalculate the checksum because we didn't change
// the packet bytes, we only moved them around.
try {
len = Os.write(mFd, mBuf, 0, len);
} catch (ErrnoException|IOException e) {
Log.e(TAG, "Error writing packet: " + e.getMessage());
}
}
public void run() {
Log.i(TAG, "UdpReflector starting fd=" + mFd + " valid=" + mFd.valid());
while (!interrupted() && mFd.valid()) {
processPacket();
}
Log.i(TAG, "UdpReflector exiting fd=" + mFd + " valid=" + mFd.valid());
}
}

View File

@@ -0,0 +1,281 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.cts.net.hostside;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.VpnService;
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject;
import android.support.test.uiautomator.UiObjectNotFoundException;
import android.support.test.uiautomator.UiScrollable;
import android.support.test.uiautomator.UiSelector;
import android.test.MoreAsserts;
import android.test.InstrumentationTestCase;
import android.text.TextUtils;
import android.util.Log;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.Inet6Address;
import java.util.concurrent.atomic.AtomicReference;
/**
* Tests for {@link DocumentsProvider} and interaction with platform intents
* like {@link Intent#ACTION_OPEN_DOCUMENT}.
*/
public class VpnTest extends InstrumentationTestCase {
public static String TAG = "VpnTest";
public static int TIMEOUT_MS = 3 * 1000;
private UiDevice mDevice;
private MyActivity mActivity;
private String mPackageName;
private ConnectivityManager mCM;
Network mNetwork;
NetworkCallback mCallback;
final Object mLock = new Object();
private boolean supportedHardware() {
final PackageManager pm = getInstrumentation().getContext().getPackageManager();
return !pm.hasSystemFeature("android.hardware.type.television") &&
!pm.hasSystemFeature("android.hardware.type.watch");
}
@Override
public void setUp() throws Exception {
super.setUp();
mNetwork = null;
mCallback = null;
mDevice = UiDevice.getInstance(getInstrumentation());
mActivity = launchActivity(getInstrumentation().getTargetContext().getPackageName(),
MyActivity.class, null);
mPackageName = mActivity.getPackageName();
mCM = (ConnectivityManager) mActivity.getSystemService(mActivity.CONNECTIVITY_SERVICE);
mDevice.waitForIdle();
}
@Override
public void tearDown() throws Exception {
if (mCallback != null) {
mCM.unregisterNetworkCallback(mCallback);
}
Log.i(TAG, "Stopping VPN");
stopVpn();
mActivity.finish();
super.tearDown();
}
private void prepareVpn() throws Exception {
final int REQUEST_ID = 42;
// Attempt to prepare.
Log.i(TAG, "Preparing VPN");
Intent intent = VpnService.prepare(mActivity);
if (intent != null) {
// Start the confirmation dialog and click OK.
mActivity.startActivityForResult(intent, REQUEST_ID);
mDevice.waitForIdle();
String packageName = intent.getComponent().getPackageName();
String resourceIdRegex = "android:id/button1$|button_start_vpn";
final UiObject okButton = new UiObject(new UiSelector()
.className("android.widget.Button")
.packageName(packageName)
.resourceIdMatches(resourceIdRegex));
if (okButton.waitForExists(TIMEOUT_MS) == false) {
mActivity.finishActivity(REQUEST_ID);
fail("VpnService.prepare returned an Intent for '" + intent.getComponent() + "' " +
"to display the VPN confirmation dialog, but this test could not find the " +
"button to allow the VPN application to connect. Please ensure that the " +
"component displays a button with a resource ID matching the regexp: '" +
resourceIdRegex + "'.");
}
// Click the button and wait for RESULT_OK.
okButton.click();
try {
int result = mActivity.getResult(TIMEOUT_MS);
if (result != MyActivity.RESULT_OK) {
fail("The VPN confirmation dialog did not return RESULT_OK when clicking on " +
"the button matching the regular expression '" + resourceIdRegex +
"' of " + intent.getComponent() + "'. Please ensure that clicking on " +
"that button allows the VPN application to connect. " +
"Return value: " + result);
}
} catch (InterruptedException e) {
fail("VPN confirmation dialog did not return after " + TIMEOUT_MS + "ms");
}
// Now we should be prepared.
intent = VpnService.prepare(mActivity);
if (intent != null) {
fail("VpnService.prepare returned non-null even after the VPN dialog " +
intent.getComponent() + "returned RESULT_OK.");
}
}
}
private void startVpn(
String[] addresses, String[] routes,
String allowedApplications, String disallowedApplications) throws Exception {
prepareVpn();
// Register a callback so we will be notified when our VPN comes up.
final NetworkRequest request = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_VPN)
.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build();
mCallback = new NetworkCallback() {
public void onAvailable(Network network) {
synchronized (mLock) {
Log.i(TAG, "Got available callback for network=" + network);
mNetwork = network;
mLock.notify();
}
}
};
mCM.registerNetworkCallback(request, mCallback); // Unregistered in tearDown.
// Start the service and wait up for TIMEOUT_MS ms for the VPN to come up.
Intent intent = new Intent(mActivity, MyVpnService.class)
.putExtra(mPackageName + ".cmd", "connect")
.putExtra(mPackageName + ".addresses", TextUtils.join(",", addresses))
.putExtra(mPackageName + ".routes", TextUtils.join(",", routes))
.putExtra(mPackageName + ".allowedapplications", allowedApplications)
.putExtra(mPackageName + ".disallowedapplications", disallowedApplications);
mActivity.startService(intent);
synchronized (mLock) {
if (mNetwork == null) {
mLock.wait(TIMEOUT_MS);
}
}
if (mNetwork == null) {
fail("VPN did not become available after " + TIMEOUT_MS + "ms");
}
// Unfortunately, when the available callback fires, the VPN UID ranges are not yet
// configured. Give the system some time to do so. http://b/18436087 .
try { Thread.sleep(300); } catch(InterruptedException e) {}
}
private void stopVpn() {
// Simply calling mActivity.stopService() won't stop the service, because the system binds
// to the service for the purpose of sending it a revoke command if another VPN comes up,
// and stopping a bound service has no effect. Instead, "start" the service again with an
// Intent that tells it to disconnect.
Intent intent = new Intent(mActivity, MyVpnService.class)
.putExtra(mPackageName + ".cmd", "disconnect");
mActivity.startService(intent);
}
private static void checkUdpEcho(
String to, String expectedFrom, boolean expectReply) throws IOException {
DatagramSocket s;
InetAddress address = InetAddress.getByName(to);
if (address instanceof Inet6Address) { // http://b/18094870
s = new DatagramSocket(0, InetAddress.getByName("::"));
} else {
s = new DatagramSocket();
}
s.setSoTimeout(100); // ms.
String msg = "Hello, world!";
DatagramPacket p = new DatagramPacket(msg.getBytes(), msg.length());
s.connect(address, 7);
if (expectedFrom != null) {
assertEquals("Unexpected source address: ",
expectedFrom, s.getLocalAddress().getHostAddress());
}
try {
if (expectReply) {
s.send(p);
s.receive(p);
MoreAsserts.assertEquals(msg.getBytes(), p.getData());
} else {
try {
s.send(p);
s.receive(p);
fail("Received unexpected reply");
} catch(IOException expected) {}
}
} finally {
s.close();
}
}
private static void expectUdpEcho(String to, String expectedFrom) throws IOException {
checkUdpEcho(to, expectedFrom, true);
}
private static void expectNoUdpEcho(String to) throws IOException {
checkUdpEcho(to, null, false);
}
public void testDefault() throws Exception {
if (!supportedHardware()) return;
startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
new String[] {"192.0.2.0/24", "2001:db8::/32"},
"", "");
expectUdpEcho("192.0.2.251", "192.0.2.2");
expectUdpEcho("2001:db8:dead:beef::f00", "2001:db8:1:2::ffe");
}
public void testAppAllowed() throws Exception {
if (!supportedHardware()) return;
startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
new String[] {"0.0.0.0/0", "::/0"},
mPackageName, "");
expectUdpEcho("192.0.2.251", "192.0.2.2");
expectUdpEcho("2001:db8:dead:beef::f00", "2001:db8:1:2::ffe");
}
public void testAppDisallowed() throws Exception {
if (!supportedHardware()) return;
startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
new String[] {"192.0.2.0/24", "2001:db8::/32"},
"", mPackageName);
expectNoUdpEcho("192.0.2.251");
expectNoUdpEcho("2001:db8:dead:beef::f00");
}
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.cts.net;
import com.android.cts.tradefed.build.CtsBuildHelper;
import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
import com.android.ddmlib.testrunner.TestIdentifier;
import com.android.ddmlib.testrunner.TestResult;
import com.android.ddmlib.testrunner.TestResult.TestStatus;
import com.android.ddmlib.testrunner.TestRunResult;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.testtype.DeviceTestCase;
import com.android.tradefed.testtype.IAbi;
import com.android.tradefed.testtype.IAbiReceiver;
import com.android.tradefed.testtype.IBuildReceiver;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.result.CollectingTestListener;
import java.util.Map;
public class HostsideNetworkTests extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
private static final String TEST_PKG = "com.android.cts.net.hostside";
private static final String TEST_APK = "CtsHostsideNetworkTestsApp.apk";
private IAbi mAbi;
private CtsBuildHelper mCtsBuild;
@Override
public void setAbi(IAbi abi) {
mAbi = abi;
}
@Override
public void setBuild(IBuildInfo buildInfo) {
mCtsBuild = CtsBuildHelper.createBuildHelper(buildInfo);
}
@Override
protected void setUp() throws Exception {
super.setUp();
assertNotNull(mAbi);
assertNotNull(mCtsBuild);
getDevice().uninstallPackage(TEST_PKG);
assertNull(getDevice().installPackage(mCtsBuild.getTestApp(TEST_APK), false));
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
getDevice().uninstallPackage(TEST_PKG);
}
public void testVpn() throws Exception {
runDeviceTests(TEST_PKG, ".VpnTest");
}
public void runDeviceTests(String packageName, String testClassName)
throws DeviceNotAvailableException {
RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(packageName,
"android.support.test.runner.AndroidJUnitRunner", getDevice().getIDevice());
final CollectingTestListener listener = new CollectingTestListener();
getDevice().runInstrumentationTests(testRunner, listener);
final TestRunResult result = listener.getCurrentRunResults();
if (result.isRunFailure()) {
throw new AssertionError("Failed to successfully run device tests for "
+ result.getName() + ": " + result.getRunFailureMessage());
}
if (result.hasFailedTests()) {
// build a meaningful error message
StringBuilder errorBuilder = new StringBuilder("on-device tests failed:\n");
for (Map.Entry<TestIdentifier, TestResult> resultEntry :
result.getTestResults().entrySet()) {
if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
errorBuilder.append(resultEntry.getKey().toString());
errorBuilder.append(":\n");
errorBuilder.append(resultEntry.getValue().getStackTrace());
}
}
throw new AssertionError(errorBuilder.toString());
}
}
}