diff --git a/tests/cts/hostside/Android.mk b/tests/cts/hostside/Android.mk new file mode 100644 index 0000000000..6637d6186f --- /dev/null +++ b/tests/cts/hostside/Android.mk @@ -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)) diff --git a/tests/cts/hostside/app/Android.mk b/tests/cts/hostside/app/Android.mk new file mode 100644 index 0000000000..29b620d2c3 --- /dev/null +++ b/tests/cts/hostside/app/Android.mk @@ -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) diff --git a/tests/cts/hostside/app/AndroidManifest.xml b/tests/cts/hostside/app/AndroidManifest.xml new file mode 100644 index 0000000000..cdde7dcb34 --- /dev/null +++ b/tests/cts/hostside/app/AndroidManifest.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyActivity.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyActivity.java new file mode 100644 index 0000000000..375c8523c8 --- /dev/null +++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyActivity.java @@ -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 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); + } +} diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java new file mode 100644 index 0000000000..1a12aaa14f --- /dev/null +++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyVpnService.java @@ -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(); + } +} diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/UdpReflector.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/UdpReflector.java new file mode 100644 index 0000000000..a730fed08a --- /dev/null +++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/UdpReflector.java @@ -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()); + } +} diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java new file mode 100644 index 0000000000..cdd370ee67 --- /dev/null +++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java @@ -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"); + } +} diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTests.java new file mode 100644 index 0000000000..a7698f36ac --- /dev/null +++ b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTests.java @@ -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 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()); + } + } +}