From acedbcb8fa5588ca7f433fed7ce36e98a827f813 Mon Sep 17 00:00:00 2001 From: Brett Chabot Date: Wed, 17 Jun 2009 22:37:02 -0700 Subject: [PATCH] Add hosttestlib library. hosttestlib is a simple JUnit extension framework for tests that need to interact externally (ie from a host machine) with an Android device. --- tools/hosttestlib/Android.mk | 28 +++ .../src/com/android/hosttest/DeviceTest.java | 51 +++++ .../com/android/hosttest/DeviceTestCase.java | 71 ++++++ .../android/hosttest/DeviceTestRunner.java | 212 ++++++++++++++++++ .../com/android/hosttest/DeviceTestSuite.java | 89 ++++++++ 5 files changed, 451 insertions(+) create mode 100644 tools/hosttestlib/Android.mk create mode 100644 tools/hosttestlib/src/com/android/hosttest/DeviceTest.java create mode 100644 tools/hosttestlib/src/com/android/hosttest/DeviceTestCase.java create mode 100644 tools/hosttestlib/src/com/android/hosttest/DeviceTestRunner.java create mode 100644 tools/hosttestlib/src/com/android/hosttest/DeviceTestSuite.java diff --git a/tools/hosttestlib/Android.mk b/tools/hosttestlib/Android.mk new file mode 100644 index 000000000..2184d3bb6 --- /dev/null +++ b/tools/hosttestlib/Android.mk @@ -0,0 +1,28 @@ +# Copyright (C) 2009 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 lib. +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_MODULE := hosttestlib + +LOCAL_JAVA_LIBRARIES := ddmlib junit + +include $(BUILD_HOST_JAVA_LIBRARY) + + diff --git a/tools/hosttestlib/src/com/android/hosttest/DeviceTest.java b/tools/hosttestlib/src/com/android/hosttest/DeviceTest.java new file mode 100644 index 000000000..a782380e4 --- /dev/null +++ b/tools/hosttestlib/src/com/android/hosttest/DeviceTest.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2009 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.hosttest; + +import com.android.ddmlib.IDevice; + +import junit.framework.Test; + +/** + * Helper JUnit test that stores reference to a Android device and test data. + */ +public interface DeviceTest extends Test { + + /** + * Sets the device under test + * @param device the Android device to test + */ + public void setDevice(IDevice device); + + /** + * Retrieves the Android device under test + * @return the {@link IDevice} device. + */ + public IDevice getDevice(); + + /** + * Retrieves host file system path that contains test app files + * @return {@link String} containing path, or null + */ + public String getTestAppPath(); + + /** + * Sets host file system path that contains test app files + * @param path absolute file system path to test data files + */ + public void setTestAppPath(String path); +} diff --git a/tools/hosttestlib/src/com/android/hosttest/DeviceTestCase.java b/tools/hosttestlib/src/com/android/hosttest/DeviceTestCase.java new file mode 100644 index 000000000..42535d8e6 --- /dev/null +++ b/tools/hosttestlib/src/com/android/hosttest/DeviceTestCase.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2009 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.hosttest; + +import com.android.ddmlib.IDevice; + +import junit.framework.TestCase; + +/** + * Helper JUnit test case that stores reference to an Android device and test data. + * + * Can be extended to verify an Android device's response to various adb commands. + */ +public abstract class DeviceTestCase extends TestCase implements DeviceTest { + + /** Android device under test */ + private IDevice mDevice = null; + /** optionally, used to store path to test data files */ + private String mTestDataPath = null; + + protected DeviceTestCase() { + } + + /** + * {@inheritDoc} + */ + public void setDevice(IDevice device) { + mDevice = device; + } + + /** + * {@inheritDoc} + */ + public IDevice getDevice() { + return mDevice; + } + + /** + * {@inheritDoc} + */ + public String getTestAppPath() { + return mTestDataPath; + } + + /** + * {@inheritDoc} + */ + public void setTestAppPath(String path) { + mTestDataPath = path; + } + + @Override + protected void setUp() throws Exception { + // ensure device has been set before test is run + assertNotNull(getDevice()); + } +} diff --git a/tools/hosttestlib/src/com/android/hosttest/DeviceTestRunner.java b/tools/hosttestlib/src/com/android/hosttest/DeviceTestRunner.java new file mode 100644 index 000000000..ab4ce5f38 --- /dev/null +++ b/tools/hosttestlib/src/com/android/hosttest/DeviceTestRunner.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2009 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.hosttest; + +import java.util.ArrayList; +import java.util.List; + +import junit.framework.Test; +import junit.framework.TestResult; +import junit.textui.TestRunner; + +import com.android.ddmlib.AndroidDebugBridge; +import com.android.ddmlib.IDevice; +import com.android.ddmlib.Log; +import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener; + +/** + * Command line interface for running DeviceTest tests. + * + * Extends junit.textui.TestRunner to handle optional -s (device serial) and -p (test data) + * arguments, and then pass their values to the instantiated DeviceTests. + * + * Provided test class must be a DeviceTest. + * + * @see junit.textui.TestRunner for more information on command line syntax. + */ +public class DeviceTestRunner extends TestRunner { + + private static final String LOG_TAG = "DeviceTestRunner"; + private String mDeviceSerial = null; + private IDevice mDevice = null; + private String mTestDataPath = null; + + private DeviceTestRunner() { + } + + /** + * Starts the test run. + * Extracts out DeviceTestCase specific command line arguments, then passes control to parent + * TestRunner. + * @param args command line arguments + * @return {@link TestResult} + */ + @Override + public TestResult start(String[] args) throws Exception { + // holds unprocessed arguments to pass to parent + List parentArgs = new ArrayList(); + for (int i=0; i < args.length; i++) { + if (args[i].equals("-s")) { + i++; + mDeviceSerial = extractArg(args, i); + } else if (args[i].equals("-p")) { + i++; + mTestDataPath = extractArg(args, i); + } else { + // unrecognized arg, must be for parent + parentArgs.add(args[i]); + } + } + mDevice = connectToDevice(mDeviceSerial); + return super.start(parentArgs.toArray(new String[parentArgs.size()])); + } + + private String extractArg(String[] args, int index) { + if (args.length <= index) { + printUsage(); + throw new IllegalArgumentException("Error: not enough arguments"); + } + return args[index]; + } + + /** + * Initializes DDMS library, and connects to specified Android device + * @param deviceSerial + * @return {@link IDevice} + * @throws IllegalArgumentException if specified device cannot be found + */ + private IDevice connectToDevice(String deviceSerial) { + // initialize DDMS with no clientSupport aka debugger support + AndroidDebugBridge.init(false /* clientSupport */); + AndroidDebugBridge adbBridge = AndroidDebugBridge.createBridge(); + for (IDevice device : adbBridge.getDevices()) { + if (deviceSerial == null) { + return device; + } else if (deviceSerial.equals(device.getSerialNumber())) { + return device; + } + } + System.out.println("Waiting for device..."); + NewDeviceListener listener = new NewDeviceListener(deviceSerial); + AndroidDebugBridge.addDeviceChangeListener(listener); + IDevice device = listener.waitForDevice(5000); + AndroidDebugBridge.removeDeviceChangeListener(listener); + if (device == null) { + throw new IllegalArgumentException("Could not connect to device"); + } + return device; + } + + /** + * Listener for new Android devices + */ + private static class NewDeviceListener implements IDeviceChangeListener { + private IDevice mDevice; + private String mSerial; + + public NewDeviceListener(String serial) { + mSerial = serial; + } + + public void deviceChanged(IDevice device, int changeMask) { + } + + public void deviceConnected(IDevice device) { + if (mSerial == null) { + setDevice(device); + } else if (mSerial.equals(device.getSerialNumber())) { + setDevice(device); + } + } + + private synchronized void setDevice(IDevice device) { + mDevice = device; + notify(); + } + + public void deviceDisconnected(IDevice device) { + } + + public IDevice waitForDevice(long waitTime) { + synchronized(this) { + if (mDevice == null) { + try { + wait(waitTime); + } catch (InterruptedException e) { + System.out.println("Waiting for device interrupted"); + } + } + } + return mDevice; + } + } + + /** + * Main entry point. + * + * Establishes connection to provided adb device and runs tests + * + * @param args expects: + * test class to run + * optionally, device serial number. If unspecified, will connect to first device found + * optionally, file system path to test data files + */ + public static void main(String[] args) { + DeviceTestRunner aTestRunner = new DeviceTestRunner(); + try { + TestResult r = aTestRunner.start(args); + if (!r.wasSuccessful()) + System.exit(FAILURE_EXIT); + System.exit(SUCCESS_EXIT); + } catch(Exception e) { + System.err.println(e.getMessage()); + System.exit(EXCEPTION_EXIT); + } + } + + private static void printUsage() { + System.out.println("Usage: DeviceTestRunner [-s device_serial] " + + "[-p test_data_path]"); + } + + /** + * Override parent to set DeviceTest data + */ + @Override + public TestResult doRun(Test test, boolean wait) { + if (test instanceof DeviceTest) { + DeviceTest deviceTest = (DeviceTest)test; + deviceTest.setDevice(mDevice); + deviceTest.setTestAppPath(mTestDataPath); + } else { + Log.w(LOG_TAG, String.format("%s test class is not a DeviceTest.", + test.getClass().getName())); + } + return super.doRun(test, wait); + } + + /** + * Override parent to create DeviceTestSuite wrapper, instead of TestSuite + */ + @Override + protected TestResult runSingleMethod(String testCase, String method, boolean wait) + throws Exception { + Class testClass = loadSuiteClass(testCase); + Test test = DeviceTestSuite.createTest(testClass, method); + return doRun(test, wait); + } +} diff --git a/tools/hosttestlib/src/com/android/hosttest/DeviceTestSuite.java b/tools/hosttestlib/src/com/android/hosttest/DeviceTestSuite.java new file mode 100644 index 000000000..65bdaaafe --- /dev/null +++ b/tools/hosttestlib/src/com/android/hosttest/DeviceTestSuite.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2009 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.hosttest; + +import com.android.ddmlib.IDevice; + +import junit.framework.Test; +import junit.framework.TestResult; +import junit.framework.TestSuite; + +/** + * Helper JUnit test suite that stores reference to an Android device and test data. + */ +public class DeviceTestSuite extends TestSuite implements DeviceTest { + + private IDevice mDevice = null; + private String mTestDataPath = null; + + public DeviceTestSuite(Class testClass) { + super(testClass); + } + + public DeviceTestSuite() { + super(); + } + + /** + * Adds the tests from the given class to the suite + */ + @Override + public void addTestSuite(Class testClass) { + addTest(new DeviceTestSuite(testClass)); + } + + /** + * Overrides parent method to pass in device and test app path to included test + */ + @Override + public void runTest(Test test, TestResult result) { + if (test instanceof DeviceTest) { + DeviceTest deviceTest = (DeviceTest)test; + deviceTest.setDevice(mDevice); + deviceTest.setTestAppPath(mTestDataPath); + } + test.run(result); + } + + /** + * {@inheritDoc} + */ + public IDevice getDevice() { + return mDevice; + } + + /** + * {@inheritDoc} + */ + public String getTestAppPath() { + return mTestDataPath; + } + + /** + * {@inheritDoc} + */ + public void setDevice(IDevice device) { + mDevice = device; + } + + /** + * {@inheritDoc} + */ + public void setTestAppPath(String path) { + mTestDataPath = path; + } +}