auto import from //branches/cupcake_rel/...@140373

This commit is contained in:
The Android Open Source Project
2009-03-18 17:39:43 -07:00
parent 59008ebc2c
commit 6ffae015b4
43 changed files with 4575 additions and 888 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -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)
ifneq ($(TARGET_PRODUCT),sim)
# HAL module implemenation, not prelinked and stored in
# hw/<SENSORS_HARDWARE_MODULE_ID>.<ro.hardware>.so
include $(CLEAR_VARS)
LOCAL_PRELINK_MODULE := false
LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
LOCAL_SHARED_LIBRARIES := liblog libcutils
LOCAL_SRC_FILES := sensors_qemu.c
LOCAL_MODULE := sensors.goldfish
include $(BUILD_SHARED_LIBRARY)
endif

View File

@@ -0,0 +1,591 @@
/*
* 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.
*/
/* this implements a sensors hardware library for the Android emulator.
* the following code should be built as a shared library that will be
* placed into /system/lib/hw/sensors.goldfish.so
*
* it will be loaded by the code in hardware/libhardware/hardware.c
* which is itself called from com_android_server_SensorService.cpp
*/
/* we connect with the emulator through the "sensors" qemud service
*/
#define SENSORS_SERVICE_NAME "sensors"
#define LOG_TAG "QemuSensors"
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <cutils/log.h>
#include <cutils/sockets.h>
#include <hardware/sensors.h>
#if 0
#define D(...) LOGD(__VA_ARGS__)
#else
#define D(...) ((void)0)
#endif
#define E(...) LOGE(__VA_ARGS__)
#include <hardware/qemud.h>
/** SENSOR IDS AND NAMES
**/
#define MAX_NUM_SENSORS 4
#define SUPPORTED_SENSORS ((1<<MAX_NUM_SENSORS)-1)
#define ID_BASE SENSORS_HANDLE_BASE
#define ID_ACCELERATION (ID_BASE+0)
#define ID_MAGNETIC_FIELD (ID_BASE+1)
#define ID_ORIENTATION (ID_BASE+2)
#define ID_TEMPERATURE (ID_BASE+3)
#define SENSORS_ACCELERATION (1 << ID_ACCELERATION)
#define SENSORS_MAGNETIC_FIELD (1 << ID_MAGNETIC_FIELD)
#define SENSORS_ORIENTATION (1 << ID_ORIENTATION)
#define SENSORS_TEMPERATURE (1 << ID_TEMPERATURE)
#define ID_CHECK(x) ((unsigned)((x)-ID_BASE) < 4)
#define SENSORS_LIST \
SENSOR_(ACCELERATION,"acceleration") \
SENSOR_(MAGNETIC_FIELD,"magnetic-field") \
SENSOR_(ORIENTATION,"orientation") \
SENSOR_(TEMPERATURE,"temperature") \
static const struct {
const char* name;
int id; } _sensorIds[MAX_NUM_SENSORS] =
{
#define SENSOR_(x,y) { y, ID_##x },
SENSORS_LIST
#undef SENSOR_
};
static const char*
_sensorIdToName( int id )
{
int nn;
for (nn = 0; nn < MAX_NUM_SENSORS; nn++)
if (id == _sensorIds[nn].id)
return _sensorIds[nn].name;
return "<UNKNOWN>";
}
static int
_sensorIdFromName( const char* name )
{
int nn;
if (name == NULL)
return -1;
for (nn = 0; nn < MAX_NUM_SENSORS; nn++)
if (!strcmp(name, _sensorIds[nn].name))
return _sensorIds[nn].id;
return -1;
}
/** SENSORS CONTROL DEVICE
**
** This one is used to send commands to the sensors drivers.
** We implement this by sending directly commands to the emulator
** through the QEMUD channel.
**/
typedef struct SensorControl {
struct sensors_control_device_t device;
int fd;
uint32_t active_sensors;
} SensorControl;
/* this must return a file descriptor that will be used to read
* the sensors data (it is passed to data__data_open() below
*/
static int
control__open_data_source(struct sensors_control_device_t *dev)
{
SensorControl* ctl = (void*)dev;
if (ctl->fd < 0) {
ctl->fd = qemud_channel_open(SENSORS_SERVICE_NAME);
}
D("%s: fd=%d", __FUNCTION__, ctl->fd);
return ctl->fd;
}
static int
control__activate(struct sensors_control_device_t *dev,
int handle,
int enabled)
{
SensorControl* ctl = (void*)dev;
uint32_t mask, sensors, active, new_sensors, changed;
char command[128];
int ret;
D("%s: handle=%s (%d) enabled=%d", __FUNCTION__,
_sensorIdToName(handle), handle, enabled);
if (!ID_CHECK(handle)) {
E("%s: bad handle ID", __FUNCTION__);
return -1;
}
mask = (1<<handle);
sensors = enabled ? mask : 0;
active = ctl->active_sensors;
new_sensors = (active & ~mask) | (sensors & mask);
changed = active ^ new_sensors;
if (!changed)
return 0;
snprintf(command, sizeof command, "set:%s:%d",
_sensorIdToName(handle), enabled != 0);
if (ctl->fd < 0) {
ctl->fd = qemud_channel_open(SENSORS_SERVICE_NAME);
}
ret = qemud_channel_send(ctl->fd, command, -1);
if (ret < 0)
return -1;
ctl->active_sensors = new_sensors;
return 0;
}
static int
control__set_delay(struct sensors_control_device_t *dev, int32_t ms)
{
SensorControl* ctl = (void*)dev;
char command[128];
D("%s: dev=%p delay-ms=%d", __FUNCTION__, dev, ms);
snprintf(command, sizeof command, "set-delay:%d", ms);
return qemud_channel_send(ctl->fd, command, -1);
}
/* this function is used to force-stop the blocking read() in
* data__poll. In order to keep the implementation as simple
* as possible here, we send a command to the emulator which
* shall send back an appropriate data block to the system.
*/
static int
control__wake(struct sensors_control_device_t *dev)
{
SensorControl* ctl = (void*)dev;
D("%s: dev=%p", __FUNCTION__, dev);
return qemud_channel_send(ctl->fd, "wake", -1);
}
static int
control__close(struct hw_device_t *dev)
{
SensorControl* ctl = (void*)dev;
close(ctl->fd);
free(ctl);
return 0;
}
/** SENSORS DATA DEVICE
**
** This one is used to read sensor data from the hardware.
** We implement this by simply reading the data from the
** emulator through the QEMUD channel.
**/
typedef struct SensorData {
struct sensors_data_device_t device;
sensors_data_t sensors[MAX_NUM_SENSORS];
int events_fd;
uint32_t pendingSensors;
int64_t timeStart;
int64_t timeOffset;
} SensorData;
/* return the current time in nanoseconds */
static int64_t
data__now_ns(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (int64_t)ts.tv_sec * 1000000000 + ts.tv_nsec;
}
static int
data__data_open(struct sensors_data_device_t *dev, int fd)
{
SensorData* data = (void*)dev;
int i;
D("%s: dev=%p fd=%d", __FUNCTION__, dev, fd);
memset(&data->sensors, 0, sizeof(data->sensors));
for (i=0 ; i<MAX_NUM_SENSORS ; i++) {
data->sensors[i].vector.status = SENSOR_STATUS_ACCURACY_HIGH;
}
data->pendingSensors = 0;
data->timeStart = 0;
data->timeOffset = 0;
data->events_fd = dup(fd);
return 0;
}
static int
data__data_close(struct sensors_data_device_t *dev)
{
SensorData* data = (void*)dev;
D("%s: dev=%p", __FUNCTION__, dev);
if (data->events_fd > 0) {
close(data->events_fd);
data->events_fd = -1;
}
return 0;
}
static int
pick_sensor(SensorData* data,
sensors_data_t* values)
{
uint32_t mask = SUPPORTED_SENSORS;
while (mask) {
uint32_t i = 31 - __builtin_clz(mask);
mask &= ~(1<<i);
if (data->pendingSensors & (1<<i)) {
data->pendingSensors &= ~(1<<i);
*values = data->sensors[i];
values->sensor = (1<<i);
LOGD_IF(0, "%s: %d [%f, %f, %f]", __FUNCTION__,
(1<<i),
values->vector.x,
values->vector.y,
values->vector.z);
return i;
}
}
LOGE("No sensor to return!!! pendingSensors=%08x", data->pendingSensors);
// we may end-up in a busy loop, slow things down, just in case.
usleep(100000);
return -1;
}
static int
data__poll(struct sensors_data_device_t *dev, sensors_data_t* values)
{
SensorData* data = (void*)dev;
int fd = data->events_fd;
D("%s: data=%p", __FUNCTION__, dev);
// there are pending sensors, returns them now...
if (data->pendingSensors) {
return pick_sensor(data, values);
}
// wait until we get a complete event for an enabled sensor
uint32_t new_sensors = 0;
while (1) {
/* read the next event */
char buff[256];
int len = qemud_channel_recv(data->events_fd, buff, sizeof buff-1);
float params[3];
int64_t event_time;
if (len < 0)
continue;
buff[len] = 0;
/* "wake" is sent from the emulator to exit this loop. This shall
* really be because another thread called "control__wake" in this
* process.
*/
if (!strcmp((const char*)data, "wake")) {
return 0x7FFFFFFF;
}
/* "acceleration:<x>:<y>:<z>" corresponds to an acceleration event */
if (sscanf(buff, "acceleration:%g:%g:%g", params+0, params+1, params+2) == 3) {
new_sensors |= SENSORS_ACCELERATION;
data->sensors[ID_ACCELERATION].acceleration.x = params[0];
data->sensors[ID_ACCELERATION].acceleration.y = params[1];
data->sensors[ID_ACCELERATION].acceleration.z = params[2];
continue;
}
/* "orientation:<azimuth>:<pitch>:<roll>" is sent when orientation changes */
if (sscanf(buff, "orientation:%g:%g:%g", params+0, params+1, params+2) == 3) {
new_sensors |= SENSORS_ORIENTATION;
data->sensors[ID_ORIENTATION].orientation.azimuth = params[0];
data->sensors[ID_ORIENTATION].orientation.pitch = params[1];
data->sensors[ID_ORIENTATION].orientation.roll = params[2];
continue;
}
/* "magnetic:<x>:<y>:<z>" is sent for the params of the magnetic field */
if (sscanf(buff, "magnetic:%g:%g:%g", params+0, params+1, params+2) == 3) {
new_sensors |= SENSORS_MAGNETIC_FIELD;
data->sensors[ID_MAGNETIC_FIELD].magnetic.x = params[0];
data->sensors[ID_MAGNETIC_FIELD].magnetic.y = params[1];
data->sensors[ID_MAGNETIC_FIELD].magnetic.z = params[2];
continue;
}
/* "temperature:<celsius>" */
if (sscanf(buff, "temperature:%g", params+0) == 2) {
new_sensors |= SENSORS_TEMPERATURE;
data->sensors[ID_TEMPERATURE].temperature = params[0];
continue;
}
/* "sync:<time>" is sent after a series of sensor events.
* where 'time' is expressed in micro-seconds and corresponds
* to the VM time when the real poll occured.
*/
if (sscanf(buff, "sync:%lld", &event_time) == 1) {
if (new_sensors) {
data->pendingSensors = new_sensors;
int64_t t = event_time * 1000LL; /* convert to nano-seconds */
/* use the time at the first sync: as the base for later
* time values */
if (data->timeStart == 0) {
data->timeStart = data__now_ns();
data->timeOffset = data->timeStart - t;
}
t += data->timeOffset;
while (new_sensors) {
uint32_t i = 31 - __builtin_clz(new_sensors);
new_sensors &= ~(1<<i);
data->sensors[i].time = t;
}
return pick_sensor(data, values);
} else {
D("huh ? sync without any sensor data ?");
}
continue;
}
D("huh ? unsupported command");
}
}
static int
data__close(struct hw_device_t *dev)
{
SensorData* data = (SensorData*)dev;
if (data) {
if (data->events_fd > 0) {
//LOGD("(device close) about to close fd=%d", data->events_fd);
close(data->events_fd);
}
free(data);
}
return 0;
}
/** MODULE REGISTRATION SUPPORT
**
** This is required so that hardware/libhardware/hardware.c
** will dlopen() this library appropriately.
**/
/*
* the following is the list of all supported sensors.
* this table is used to build sSensorList declared below
* according to which hardware sensors are reported as
* available from the emulator (see get_sensors_list below)
*
* note: numerical values for maxRange/resolution/power were
* taken from the reference AK8976A implementation
*/
static const struct sensor_t sSensorListInit[] = {
{ .name = "Goldfish 3-axis Accelerometer",
.vendor = "The Android Open Source Project",
.version = 1,
.handle = ID_ACCELERATION,
.type = SENSOR_TYPE_ACCELEROMETER,
.maxRange = 2.8f,
.resolution = 1.0f/4032.0f,
.power = 3.0f,
.reserved = {}
},
{ .name = "Goldfish 3-axis Magnetic field sensor",
.vendor = "The Android Open Source Project",
.version = 1,
.handle = ID_MAGNETIC_FIELD,
.type = SENSOR_TYPE_MAGNETIC_FIELD,
.maxRange = 2000.0f,
.resolution = 1.0f,
.power = 6.7f,
.reserved = {}
},
{ .name = "Goldfish Orientation sensor",
.vendor = "The Android Open Source Project",
.version = 1,
.handle = ID_ORIENTATION,
.type = SENSOR_TYPE_ORIENTATION,
.maxRange = 360.0f,
.resolution = 1.0f,
.power = 9.7f,
.reserved = {}
},
{ .name = "Goldfish Temperature sensor",
.vendor = "The Android Open Source Project",
.version = 1,
.handle = ID_TEMPERATURE,
.type = SENSOR_TYPE_TEMPERATURE,
.maxRange = 80.0f,
.resolution = 1.0f,
.power = 0.0f,
.reserved = {}
},
};
static struct sensor_t sSensorList[MAX_NUM_SENSORS];
static uint32_t sensors__get_sensors_list(struct sensors_module_t* module,
struct sensor_t const** list)
{
int fd = qemud_channel_open(SENSORS_SERVICE_NAME);
char buffer[12];
int mask, nn, count;
int ret;
if (fd < 0) {
E("%s: no qemud connection", __FUNCTION__);
return 0;
}
ret = qemud_channel_send(fd, "list-sensors", -1);
if (ret < 0) {
E("%s: could not query sensor list: %s", __FUNCTION__,
strerror(errno));
close(fd);
return 0;
}
ret = qemud_channel_recv(fd, buffer, sizeof buffer-1);
if (ret < 0) {
E("%s: could not receive sensor list: %s", __FUNCTION__,
strerror(errno));
close(fd);
return 0;
}
buffer[ret] = 0;
close(fd);
/* the result is a integer used as a mask for available sensors */
mask = atoi(buffer);
count = 0;
for (nn = 0; nn < MAX_NUM_SENSORS; nn++) {
if (((1 << nn) & mask) == 0)
continue;
sSensorList[count++] = sSensorListInit[nn];
}
D("%s: returned %d sensors (mask=%d)", __FUNCTION__, count, mask);
*list = sSensorList;
return count;
}
static int
open_sensors(const struct hw_module_t* module,
const char* name,
struct hw_device_t* *device)
{
int status = -EINVAL;
D("%s: name=%s", __FUNCTION__, name);
if (!strcmp(name, SENSORS_HARDWARE_CONTROL))
{
SensorControl *dev = malloc(sizeof(*dev));
memset(dev, 0, sizeof(*dev));
dev->device.common.tag = HARDWARE_DEVICE_TAG;
dev->device.common.version = 0;
dev->device.common.module = (struct hw_module_t*) module;
dev->device.common.close = control__close;
dev->device.open_data_source = control__open_data_source;
dev->device.activate = control__activate;
dev->device.set_delay = control__set_delay;
dev->device.wake = control__wake;
dev->fd = -1;
*device = &dev->device.common;
status = 0;
}
else if (!strcmp(name, SENSORS_HARDWARE_DATA)) {
SensorData *dev = malloc(sizeof(*dev));
memset(dev, 0, sizeof(*dev));
dev->device.common.tag = HARDWARE_DEVICE_TAG;
dev->device.common.version = 0;
dev->device.common.module = (struct hw_module_t*) module;
dev->device.common.close = data__close;
dev->device.data_open = data__data_open;
dev->device.data_close = data__data_close;
dev->device.poll = data__poll;
dev->events_fd = -1;
*device = &dev->device.common;
status = 0;
}
return status;
}
static struct hw_module_methods_t sensors_module_methods = {
.open = open_sensors
};
const struct sensors_module_t HAL_MODULE_INFO_SYM = {
.common = {
.tag = HARDWARE_MODULE_TAG,
.version_major = 1,
.version_minor = 0,
.id = SENSORS_HARDWARE_MODULE_ID,
.name = "Goldfish SENSORS Module",
.author = "The Android Open Source Project",
.methods = &sensors_module_methods,
},
.get_sensors_list = sensors__get_sensors_list
};

View File

@@ -1,6 +1,9 @@
Building the pdk (platform development kit) Building the pdk (platform development kit)
1) get a cupcake source tree 1) get a cupcake source tree with all the normal tools... and add doxygen
(We currently support version 1.4.6)
sudo apt-get install doxygen
2) from the root 2) from the root
. build/envsetup.sh . build/envsetup.sh

View File

@@ -550,7 +550,7 @@ public class SoftKeyboard extends InputMethodService
boolean typedWordValid) { boolean typedWordValid) {
if (suggestions != null && suggestions.size() > 0) { if (suggestions != null && suggestions.size() > 0) {
setCandidatesViewShown(true); setCandidatesViewShown(true);
} else if (isFullscreenMode()) { } else if (isExtractViewShown()) {
setCandidatesViewShown(true); setCandidatesViewShown(true);
} }
if (mCandidateView != null) { if (mCandidateView != null) {

19
testrunner/Android.mk Normal file
View File

@@ -0,0 +1,19 @@
#
# Install a list of test definitions on device
#
# where to install the sample files on the device
#
local_target_dir := $(TARGET_OUT_DATA)/testinfo
LOCAL_PATH := $(call my-dir)
########################
include $(CLEAR_VARS)
LOCAL_MODULE := tests.xml
LOCAL_MODULE_TAGS := tests
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_PATH := $(local_target_dir)
LOCAL_SRC_FILES := $(LOCAL_MODULE)
include $(BUILD_PREBUILT)

View File

@@ -0,0 +1,45 @@
#!/usr/bin/python2.4
#
#
# Copyright 2008, 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.
"""Contains utility functions for interacting with the Android build system."""
# Python imports
import os
# local imports
import errors
import logger
def GetTop():
"""Returns the full pathname of the "top" of the Android development tree.
Assumes build environment has been properly configured by envsetup &
lunch/choosecombo.
Returns:
the absolute file path of the Android build root.
Raises:
AbortError: if Android build root could not be found.
"""
# TODO: does this need to be reimplemented to be like gettop() in envsetup.sh
root_path = os.getenv('ANDROID_BUILD_TOP')
if root_path is None:
logger.Log('Error: ANDROID_BUILD_TOP not defined. Please run envsetup.sh')
raise errors.AbortError
return root_path

280
testrunner/runtest.py Executable file
View File

@@ -0,0 +1,280 @@
#!/usr/bin/python2.4
#
# Copyright 2008, 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.
"""Command line utility for running a pre-defined test.
Based on previous <androidroot>/development/tools/runtest shell script.
"""
# Python imports
import glob
import optparse
import os
from sets import Set
import sys
# local imports
import adb_interface
import android_build
import coverage
import errors
import logger
import run_command
import test_defs
class TestRunner(object):
"""Command line utility class for running pre-defined Android test(s)."""
# file path to android core platform tests, relative to android build root
# TODO move these test data files to another directory
_CORE_TEST_PATH = os.path.join("development", "testrunner", "tests.xml")
# vendor glob file path patterns to tests, relative to android
# build root
_VENDOR_TEST_PATH = os.path.join("vendor", "*", "tests", "testinfo",
"tests.xml")
_RUNTEST_USAGE = (
"usage: runtest.py [options] short-test-name[s]\n\n"
"The runtest script works in two ways. You can query it "
"for a list of tests, or you can launch one or more tests.")
def _ProcessOptions(self):
"""Processes command-line options."""
# TODO error messages on once-only or mutually-exclusive options.
user_test_default = os.path.join(os.environ.get("HOME"), ".android",
"tests.xml")
parser = optparse.OptionParser(usage=self._RUNTEST_USAGE)
parser.add_option("-l", "--list-tests", dest="only_list_tests",
default=False, action="store_true",
help="To view the list of tests")
parser.add_option("-b", "--skip-build", dest="skip_build", default=False,
action="store_true", help="Skip build - just launch")
parser.add_option("-n", "--skip_execute", dest="preview", default=False,
action="store_true",
help="Do not execute, just preview commands")
parser.add_option("-r", "--raw-mode", dest="raw_mode", default=False,
action="store_true",
help="Raw mode (for output to other tools)")
parser.add_option("-a", "--suite-assign", dest="suite_assign_mode",
default=False, action="store_true",
help="Suite assignment (for details & usage see "
"InstrumentationTestRunner)")
parser.add_option("-v", "--verbose", dest="verbose", default=False,
action="store_true",
help="Increase verbosity of %s" % sys.argv[0])
parser.add_option("-w", "--wait-for-debugger", dest="wait_for_debugger",
default=False, action="store_true",
help="Wait for debugger before launching tests")
parser.add_option("-c", "--test-class", dest="test_class",
help="Restrict test to a specific class")
parser.add_option("-m", "--test-method", dest="test_method",
help="Restrict test to a specific method")
parser.add_option("-u", "--user-tests-file", dest="user_tests_file",
metavar="FILE", default=user_test_default,
help="Alternate source of user test definitions")
parser.add_option("-o", "--coverage", dest="coverage",
default=False, action="store_true",
help="Generate code coverage metrics for test(s)")
parser.add_option("-t", "--all-tests", dest="all_tests",
default=False, action="store_true",
help="Run all defined tests")
parser.add_option("--continuous", dest="continuous_tests",
default=False, action="store_true",
help="Run all tests defined as part of the continuous "
"test set")
group = optparse.OptionGroup(
parser, "Targets", "Use these options to direct tests to a specific "
"Android target")
group.add_option("-e", "--emulator", dest="emulator", default=False,
action="store_true", help="use emulator")
group.add_option("-d", "--device", dest="device", default=False,
action="store_true", help="use device")
group.add_option("-s", "--serial", dest="serial",
help="use specific serial")
parser.add_option_group(group)
self._options, self._test_args = parser.parse_args()
if (not self._options.only_list_tests and not self._options.all_tests
and not self._options.continuous_tests and len(self._test_args) < 1):
parser.print_help()
logger.SilentLog("at least one test name must be specified")
raise errors.AbortError
self._adb = adb_interface.AdbInterface()
if self._options.emulator:
self._adb.SetEmulatorTarget()
elif self._options.device:
self._adb.SetDeviceTarget()
elif self._options.serial is not None:
self._adb.SetTargetSerial(self._options.serial)
if self._options.verbose:
logger.SetVerbose(True)
self._root_path = android_build.GetTop()
self._known_tests = self._ReadTests()
self._coverage_gen = coverage.CoverageGenerator(
android_root_path=self._root_path, adb_interface=self._adb)
def _ReadTests(self):
"""Parses the set of test definition data.
Returns:
A TestDefinitions object that contains the set of parsed tests.
Raises:
AbortError: If a fatal error occurred when parsing the tests.
"""
core_test_path = os.path.join(self._root_path, self._CORE_TEST_PATH)
try:
known_tests = test_defs.TestDefinitions()
known_tests.Parse(core_test_path)
# read all <android root>/vendor/*/tests/testinfo/tests.xml paths
vendor_tests_pattern = os.path.join(self._root_path,
self._VENDOR_TEST_PATH)
test_file_paths = glob.glob(vendor_tests_pattern)
for test_file_path in test_file_paths:
known_tests.Parse(test_file_path)
if os.path.isfile(self._options.user_tests_file):
known_tests.Parse(self._options.user_tests_file)
return known_tests
except errors.ParseError:
raise errors.AbortError
def _DumpTests(self):
"""Prints out set of defined tests."""
print "The following tests are currently defined:"
for test in self._known_tests:
print test.GetName()
def _DoBuild(self):
logger.SilentLog("Building tests...")
target_set = Set()
for test_suite in self._GetTestsToRun():
self._AddBuildTarget(test_suite.GetBuildPath(), target_set)
if target_set:
if self._options.coverage:
self._coverage_gen.EnableCoverageBuild()
self._AddBuildTarget(self._coverage_gen.GetEmmaBuildPath(), target_set)
target_build_string = " ".join(list(target_set))
logger.Log("Building %s" % target_build_string)
cmd = 'ONE_SHOT_MAKEFILE="%s" make -C "%s" files' % (target_build_string,
self._root_path)
if not self._options.preview:
run_command.RunCommand(cmd, return_output=False)
logger.Log("Syncing to device...")
self._adb.Sync()
def _AddBuildTarget(self, build_dir, target_set):
if build_dir is not None:
build_file_path = os.path.join(build_dir, "Android.mk")
if os.path.isfile(os.path.join(self._root_path, build_file_path)):
target_set.add(build_file_path)
def _GetTestsToRun(self):
"""Get a list of TestSuite objects to run, based on command line args."""
if self._options.all_tests:
return self._known_tests.GetTests()
if self._options.continuous_tests:
return self._known_tests.GetContinuousTests()
tests = []
for name in self._test_args:
test = self._known_tests.GetTest(name)
if test is None:
logger.Log("Error: Could not find test %s" % name)
self._DumpTests()
raise errors.AbortError
tests.append(test)
return tests
def _RunTest(self, test_suite):
"""Run the provided test suite.
Builds up an adb instrument command using provided input arguments.
Args:
test_suite: TestSuite to run
"""
test_class = test_suite.GetClassName()
if self._options.test_class is not None:
test_class = self._options.test_class
if self._options.test_method is not None:
test_class = "%s#%s" % (test_class, self._options.test_method)
instrumentation_args = {}
if test_class is not None:
instrumentation_args["class"] = test_class
if self._options.wait_for_debugger:
instrumentation_args["debug"] = "true"
if self._options.suite_assign_mode:
instrumentation_args["suiteAssignment"] = "true"
if self._options.coverage:
instrumentation_args["coverage"] = "true"
if self._options.preview:
adb_cmd = self._adb.PreviewInstrumentationCommand(
package_name=test_suite.GetPackageName(),
runner_name=test_suite.GetRunnerName(),
raw_mode=self._options.raw_mode,
instrumentation_args=instrumentation_args)
logger.Log(adb_cmd)
else:
self._adb.StartInstrumentationNoResults(
package_name=test_suite.GetPackageName(),
runner_name=test_suite.GetRunnerName(),
raw_mode=self._options.raw_mode,
instrumentation_args=instrumentation_args)
if self._options.coverage and test_suite.GetTargetName() is not None:
coverage_file = self._coverage_gen.ExtractReport(test_suite)
if coverage_file is not None:
logger.Log("Coverage report generated at %s" % coverage_file)
def RunTests(self):
"""Main entry method - executes the tests according to command line args."""
try:
run_command.SetAbortOnError()
self._ProcessOptions()
if self._options.only_list_tests:
self._DumpTests()
return
if not self._options.skip_build:
self._DoBuild()
for test_suite in self._GetTestsToRun():
self._RunTest(test_suite)
except KeyboardInterrupt:
logger.Log("Exiting...")
except errors.AbortError:
logger.SilentLog("Exiting due to AbortError...")
except errors.WaitForResponseTimedOutError:
logger.Log("Timed out waiting for response")
def RunTests():
runner = TestRunner()
runner.RunTests()
if __name__ == "__main__":
RunTests()

View File

@@ -87,6 +87,12 @@ These attributes map to the following commands:
coverage_target="ApiDemos" coverage_target="ApiDemos"
continuous="true" /> continuous="true" />
<test name="launchperf"
build_path="development/apps/launchperf"
package="com.android.launchperf"
class="com.android.launchperf.SimpleActivityLaunchPerformance"
coverage_target="framework" />
<!-- targeted framework tests --> <!-- targeted framework tests -->
<test name="heap" <test name="heap"
build_path="frameworks/base/tests/AndroidTests" build_path="frameworks/base/tests/AndroidTests"
@@ -114,6 +120,11 @@ These attributes map to the following commands:
class="android.content.AbstractTableMergerTest" class="android.content.AbstractTableMergerTest"
coverage_target="framework" /> coverage_target="framework" />
<test name="imf"
build_path="frameworks/base/tests/ImfTest"
package="com.android.imftest.tests"
coverage_target="framework"
continuous="true" />
<!-- selected app tests --> <!-- selected app tests -->
<test name="browser" <test name="browser"
@@ -169,12 +180,19 @@ These attributes map to the following commands:
runner=".MediaFrameworkTestRunner" runner=".MediaFrameworkTestRunner"
coverage_target="framework" coverage_target="framework"
continuous="true" /> continuous="true" />
<test name="mediaunit" <test name="mediaunit"
build_path="frameworks/base/media/tests/MediaFrameworkTest" build_path="frameworks/base/media/tests/MediaFrameworkTest"
package="com.android.mediaframeworktest" package="com.android.mediaframeworktest"
runner=".MediaFrameworkUnitTestRunner" runner=".MediaFrameworkUnitTestRunner"
coverage_target="framework" /> coverage_target="framework" />
<test name="musicplayer"
build_path="packages/apps/Music"
package="com.android.music.tests"
runner=".MusicPlayerFunctionalTestRunner"
coverage_target="Music"
continuous="true" />
<!-- obsolete? <!-- obsolete?
<test name="mediaprov" <test name="mediaprov"

View File

@@ -27,7 +27,14 @@ import com.android.ddmlib.MultiLineReceiver;
* <p>Expects the following output: * <p>Expects the following output:
* *
* <p>If fatal error occurred when attempted to run the tests: * <p>If fatal error occurred when attempted to run the tests:
* <pre> INSTRUMENTATION_FAILED: </pre> * <pre>
* INSTRUMENTATION_STATUS: Error=error Message
* INSTRUMENTATION_FAILED:
* </pre>
* <p>or
* <pre>
* INSTRUMENTATION_RESULT: shortMsg=error Message
* </pre>
* *
* <p>Otherwise, expect a series of test results, each one containing a set of status key/value * <p>Otherwise, expect a series of test results, each one containing a set of status key/value
* pairs, delimited by a start(1)/pass(0)/fail(-2)/error(-1) status code result. At end of test * pairs, delimited by a start(1)/pass(0)/fail(-2)/error(-1) status code result. At end of test
@@ -56,6 +63,8 @@ public class InstrumentationResultParser extends MultiLineReceiver {
private static final String CLASS = "class"; private static final String CLASS = "class";
private static final String STACK = "stack"; private static final String STACK = "stack";
private static final String NUMTESTS = "numtests"; private static final String NUMTESTS = "numtests";
private static final String ERROR = "Error";
private static final String SHORTMSG = "shortMsg";
} }
/** Test result status codes. */ /** Test result status codes. */
@@ -71,6 +80,8 @@ public class InstrumentationResultParser extends MultiLineReceiver {
private static final String STATUS = "INSTRUMENTATION_STATUS: "; private static final String STATUS = "INSTRUMENTATION_STATUS: ";
private static final String STATUS_CODE = "INSTRUMENTATION_STATUS_CODE: "; private static final String STATUS_CODE = "INSTRUMENTATION_STATUS_CODE: ";
private static final String STATUS_FAILED = "INSTRUMENTATION_FAILED: "; private static final String STATUS_FAILED = "INSTRUMENTATION_FAILED: ";
private static final String CODE = "INSTRUMENTATION_CODE: ";
private static final String RESULT = "INSTRUMENTATION_RESULT: ";
private static final String TIME_REPORT = "Time: "; private static final String TIME_REPORT = "Time: ";
} }
@@ -90,6 +101,23 @@ public class InstrumentationResultParser extends MultiLineReceiver {
boolean isComplete() { boolean isComplete() {
return mCode != null && mTestName != null && mTestClass != null; return mCode != null && mTestName != null && mTestClass != null;
} }
/** Provides a more user readable string for TestResult, if possible */
@Override
public String toString() {
StringBuilder output = new StringBuilder();
if (mTestClass != null ) {
output.append(mTestClass);
output.append('#');
}
if (mTestName != null) {
output.append(mTestName);
}
if (output.length() > 0) {
return output.toString();
}
return "unknown result";
}
} }
/** Stores the status values for the test result currently being parsed */ /** Stores the status values for the test result currently being parsed */
@@ -130,6 +158,8 @@ public class InstrumentationResultParser extends MultiLineReceiver {
public void processNewLines(String[] lines) { public void processNewLines(String[] lines) {
for (String line : lines) { for (String line : lines) {
parse(line); parse(line);
// in verbose mode, dump all adb output to log
Log.v(LOG_TAG, line);
} }
} }
@@ -160,9 +190,15 @@ public class InstrumentationResultParser extends MultiLineReceiver {
// Previous status key-value has been collected. Store it. // Previous status key-value has been collected. Store it.
submitCurrentKeyValue(); submitCurrentKeyValue();
parseKey(line, Prefixes.STATUS.length()); parseKey(line, Prefixes.STATUS.length());
} else if (line.startsWith(Prefixes.STATUS_FAILED)) { } else if (line.startsWith(Prefixes.RESULT)) {
Log.e(LOG_TAG, "test run failed " + line); // Previous status key-value has been collected. Store it.
mTestListener.testRunFailed(line); submitCurrentKeyValue();
parseKey(line, Prefixes.RESULT.length());
} else if (line.startsWith(Prefixes.STATUS_FAILED) ||
line.startsWith(Prefixes.CODE)) {
// Previous status key-value has been collected. Store it.
submitCurrentKeyValue();
// just ignore the remaining data on this line
} else if (line.startsWith(Prefixes.TIME_REPORT)) { } else if (line.startsWith(Prefixes.TIME_REPORT)) {
parseTime(line, Prefixes.TIME_REPORT.length()); parseTime(line, Prefixes.TIME_REPORT.length());
} else { } else {
@@ -186,19 +222,19 @@ public class InstrumentationResultParser extends MultiLineReceiver {
if (mCurrentKey.equals(StatusKeys.CLASS)) { if (mCurrentKey.equals(StatusKeys.CLASS)) {
testInfo.mTestClass = statusValue.trim(); testInfo.mTestClass = statusValue.trim();
} } else if (mCurrentKey.equals(StatusKeys.TEST)) {
else if (mCurrentKey.equals(StatusKeys.TEST)) {
testInfo.mTestName = statusValue.trim(); testInfo.mTestName = statusValue.trim();
} } else if (mCurrentKey.equals(StatusKeys.NUMTESTS)) {
else if (mCurrentKey.equals(StatusKeys.NUMTESTS)) {
try { try {
testInfo.mNumTests = Integer.parseInt(statusValue); testInfo.mNumTests = Integer.parseInt(statusValue);
} } catch (NumberFormatException e) {
catch (NumberFormatException e) {
Log.e(LOG_TAG, "Unexpected integer number of tests, received " + statusValue); Log.e(LOG_TAG, "Unexpected integer number of tests, received " + statusValue);
} }
} } else if (mCurrentKey.equals(StatusKeys.ERROR) ||
else if (mCurrentKey.equals(StatusKeys.STACK)) { mCurrentKey.equals(StatusKeys.SHORTMSG)) {
// test run must have failed
handleTestRunFailed(statusValue);
} else if (mCurrentKey.equals(StatusKeys.STACK)) {
testInfo.mStackTrace = statusValue; testInfo.mStackTrace = statusValue;
} }
@@ -229,7 +265,7 @@ public class InstrumentationResultParser extends MultiLineReceiver {
int endKeyPos = line.indexOf('=', keyStartPos); int endKeyPos = line.indexOf('=', keyStartPos);
if (endKeyPos != -1) { if (endKeyPos != -1) {
mCurrentKey = line.substring(keyStartPos, endKeyPos).trim(); mCurrentKey = line.substring(keyStartPos, endKeyPos).trim();
parseValue(line, endKeyPos+1); parseValue(line, endKeyPos + 1);
} }
} }
@@ -252,8 +288,7 @@ public class InstrumentationResultParser extends MultiLineReceiver {
TestResult testInfo = getCurrentTestInfo(); TestResult testInfo = getCurrentTestInfo();
try { try {
testInfo.mCode = Integer.parseInt(value); testInfo.mCode = Integer.parseInt(value);
} } catch (NumberFormatException e) {
catch (NumberFormatException e) {
Log.e(LOG_TAG, "Expected integer status code, received: " + value); Log.e(LOG_TAG, "Expected integer status code, received: " + value);
} }
@@ -286,7 +321,7 @@ public class InstrumentationResultParser extends MultiLineReceiver {
*/ */
private void reportResult(TestResult testInfo) { private void reportResult(TestResult testInfo) {
if (!testInfo.isComplete()) { if (!testInfo.isComplete()) {
Log.e(LOG_TAG, "invalid instrumentation status bundle " + testInfo.toString()); Log.w(LOG_TAG, "invalid instrumentation status bundle " + testInfo.toString());
return; return;
} }
reportTestRunStarted(testInfo); reportTestRunStarted(testInfo);
@@ -337,8 +372,7 @@ public class InstrumentationResultParser extends MultiLineReceiver {
private String getTrace(TestResult testInfo) { private String getTrace(TestResult testInfo) {
if (testInfo.mStackTrace != null) { if (testInfo.mStackTrace != null) {
return testInfo.mStackTrace; return testInfo.mStackTrace;
} } else {
else {
Log.e(LOG_TAG, "Could not find stack trace for failed test "); Log.e(LOG_TAG, "Could not find stack trace for failed test ");
return new Throwable("Unknown failure").toString(); return new Throwable("Unknown failure").toString();
} }
@@ -351,13 +385,19 @@ public class InstrumentationResultParser extends MultiLineReceiver {
String timeString = line.substring(startPos); String timeString = line.substring(startPos);
try { try {
float timeSeconds = Float.parseFloat(timeString); float timeSeconds = Float.parseFloat(timeString);
mTestTime = (long)(timeSeconds * 1000); mTestTime = (long) (timeSeconds * 1000);
} } catch (NumberFormatException e) {
catch (NumberFormatException e) {
Log.e(LOG_TAG, "Unexpected time format " + timeString); Log.e(LOG_TAG, "Unexpected time format " + timeString);
} }
} }
/**
* Process a instrumentation run failure
*/
private void handleTestRunFailed(String errorMsg) {
mTestListener.testRunFailed(errorMsg == null ? "Unknown error" : errorMsg);
}
/** /**
* Called by parent when adb session is complete. * Called by parent when adb session is complete.
*/ */

View File

@@ -21,27 +21,35 @@ import com.android.ddmlib.IDevice;
import com.android.ddmlib.Log; import com.android.ddmlib.Log;
import java.io.IOException; import java.io.IOException;
import java.util.Hashtable;
import java.util.Map;
import java.util.Map.Entry;
/** /**
* Runs a Android test command remotely and reports results. * Runs a Android test command remotely and reports results.
*/ */
public class RemoteAndroidTestRunner { public class RemoteAndroidTestRunner {
private static final char CLASS_SEPARATOR = ',';
private static final char METHOD_SEPARATOR = '#';
private static final char RUNNER_SEPARATOR = '/';
private String mClassArg;
private final String mPackageName; private final String mPackageName;
private final String mRunnerName; private final String mRunnerName;
private String mExtraArgs;
private boolean mLogOnlyMode;
private IDevice mRemoteDevice; private IDevice mRemoteDevice;
/** map of name-value instrumentation argument pairs */
private Map<String, String> mArgMap;
private InstrumentationResultParser mParser; private InstrumentationResultParser mParser;
private static final String LOG_TAG = "RemoteAndroidTest"; private static final String LOG_TAG = "RemoteAndroidTest";
private static final String DEFAULT_RUNNER_NAME = private static final String DEFAULT_RUNNER_NAME = "android.test.InstrumentationTestRunner";
"android.test.InstrumentationTestRunner";
private static final char CLASS_SEPARATOR = ',';
private static final char METHOD_SEPARATOR = '#';
private static final char RUNNER_SEPARATOR = '/';
// defined instrumentation argument names
private static final String CLASS_ARG_NAME = "class";
private static final String LOG_ARG_NAME = "log";
private static final String DEBUG_ARG_NAME = "debug";
private static final String COVERAGE_ARG_NAME = "coverage";
/** /**
* Creates a remote Android test runner. * Creates a remote Android test runner.
* *
@@ -56,12 +64,10 @@ public class RemoteAndroidTestRunner {
mPackageName = packageName; mPackageName = packageName;
mRunnerName = runnerName; mRunnerName = runnerName;
mRemoteDevice = remoteDevice; mRemoteDevice = remoteDevice;
mClassArg = null; mArgMap = new Hashtable<String, String>();
mExtraArgs = "";
mLogOnlyMode = false;
} }
/** /**
* Alternate constructor. Uses default instrumentation runner. * Alternate constructor. Uses default instrumentation runner.
* *
@@ -72,7 +78,7 @@ public class RemoteAndroidTestRunner {
IDevice remoteDevice) { IDevice remoteDevice) {
this(packageName, null, remoteDevice); this(packageName, null, remoteDevice);
} }
/** /**
* Returns the application package name. * Returns the application package name.
*/ */
@@ -89,14 +95,14 @@ public class RemoteAndroidTestRunner {
} }
return mRunnerName; return mRunnerName;
} }
/** /**
* Returns the complete instrumentation component path. * Returns the complete instrumentation component path.
*/ */
private String getRunnerPath() { private String getRunnerPath() {
return getPackageName() + RUNNER_SEPARATOR + getRunnerName(); return getPackageName() + RUNNER_SEPARATOR + getRunnerName();
} }
/** /**
* Sets to run only tests in this class * Sets to run only tests in this class
* Must be called before 'run'. * Must be called before 'run'.
@@ -104,7 +110,7 @@ public class RemoteAndroidTestRunner {
* @param className fully qualified class name (eg x.y.z) * @param className fully qualified class name (eg x.y.z)
*/ */
public void setClassName(String className) { public void setClassName(String className) {
mClassArg = className; addInstrumentationArg(CLASS_ARG_NAME, className);
} }
/** /**
@@ -119,15 +125,15 @@ public class RemoteAndroidTestRunner {
public void setClassNames(String[] classNames) { public void setClassNames(String[] classNames) {
StringBuilder classArgBuilder = new StringBuilder(); StringBuilder classArgBuilder = new StringBuilder();
for (int i=0; i < classNames.length; i++) { for (int i = 0; i < classNames.length; i++) {
if (i != 0) { if (i != 0) {
classArgBuilder.append(CLASS_SEPARATOR); classArgBuilder.append(CLASS_SEPARATOR);
} }
classArgBuilder.append(classNames[i]); classArgBuilder.append(classNames[i]);
} }
mClassArg = classArgBuilder.toString(); setClassName(classArgBuilder.toString());
} }
/** /**
* Sets to run only specified test method * Sets to run only specified test method
* Must be called before 'run'. * Must be called before 'run'.
@@ -136,47 +142,70 @@ public class RemoteAndroidTestRunner {
* @param testName method name * @param testName method name
*/ */
public void setMethodName(String className, String testName) { public void setMethodName(String className, String testName) {
mClassArg = className + METHOD_SEPARATOR + testName; setClassName(className + METHOD_SEPARATOR + testName);
} }
/** /**
* Sets extra arguments to include in instrumentation command. * Adds a argument to include in instrumentation command.
* Must be called before 'run'. * <p/>
* Must be called before 'run'. If an argument with given name has already been provided, it's
* value will be overridden.
* *
* @param instrumentationArgs must not be null * @param name the name of the instrumentation bundle argument
* @param value the value of the argument
*/ */
public void setExtraArgs(String instrumentationArgs) { public void addInstrumentationArg(String name, String value) {
if (instrumentationArgs == null) { if (name == null || value == null) {
throw new IllegalArgumentException("instrumentationArgs cannot be null"); throw new IllegalArgumentException("name or value arguments cannot be null");
} }
mExtraArgs = instrumentationArgs; mArgMap.put(name, value);
} }
/** /**
* Returns the extra instrumentation arguments. * Adds a boolean argument to include in instrumentation command.
* <p/>
* @see RemoteAndroidTestRunner#addInstrumentationArg
*
* @param name the name of the instrumentation bundle argument
* @param value the value of the argument
*/ */
public String getExtraArgs() { public void addBooleanArg(String name, boolean value) {
return mExtraArgs; addInstrumentationArg(name, Boolean.toString(value));
} }
/** /**
* Sets this test run to log only mode - skips test execution. * Sets this test run to log only mode - skips test execution.
*/ */
public void setLogOnly(boolean logOnly) { public void setLogOnly(boolean logOnly) {
mLogOnlyMode = logOnly; addBooleanArg(LOG_ARG_NAME, logOnly);
} }
/**
* Sets this debug mode of this test run. If true, the Android test runner will wait for a
* debugger to attach before proceeding with test execution.
*/
public void setDebug(boolean debug) {
addBooleanArg(DEBUG_ARG_NAME, debug);
}
/**
* Sets this code coverage mode of this test run.
*/
public void setCoverage(boolean coverage) {
addBooleanArg(COVERAGE_ARG_NAME, coverage);
}
/** /**
* Execute this test run. * Execute this test run.
* *
* @param listener listens for test results * @param listener listens for test results
*/ */
public void run(ITestRunListener listener) { public void run(ITestRunListener listener) {
final String runCaseCommandStr = "am instrument -w -r " final String runCaseCommandStr = String.format("am instrument -w -r %s %s",
+ getClassCmd() + " " + getLogCmd() + " " + getExtraArgs() + " " + getRunnerPath(); getArgsCommand(), getRunnerPath());
Log.d(LOG_TAG, runCaseCommandStr); Log.d(LOG_TAG, runCaseCommandStr);
mParser = new InstrumentationResultParser(listener); mParser = new InstrumentationResultParser(listener);
try { try {
mRemoteDevice.executeShellCommand(runCaseCommandStr, mParser); mRemoteDevice.executeShellCommand(runCaseCommandStr, mParser);
} catch (IOException e) { } catch (IOException e) {
@@ -184,7 +213,7 @@ public class RemoteAndroidTestRunner {
listener.testRunFailed(e.toString()); listener.testRunFailed(e.toString());
} }
} }
/** /**
* Requests cancellation of this test run. * Requests cancellation of this test run.
*/ */
@@ -193,36 +222,19 @@ public class RemoteAndroidTestRunner {
mParser.cancel(); mParser.cancel();
} }
} }
/**
* Returns the test class argument.
*/
private String getClassArg() {
return mClassArg;
}
/**
* Returns the full instrumentation command which specifies the test classes to execute.
* Returns an empty string if no classes were specified.
*/
private String getClassCmd() {
String classArg = getClassArg();
if (classArg != null) {
return "-e class " + classArg;
}
return "";
}
/** /**
* Returns the full command to enable log only mode - if specified. Otherwise returns an * Returns the full instrumentation command line syntax for the provided instrumentation
* empty string. * arguments.
* Returns an empty string if no arguments were specified.
*/ */
private String getLogCmd() { private String getArgsCommand() {
if (mLogOnlyMode) { StringBuilder commandBuilder = new StringBuilder();
return "-e log true"; for (Entry<String, String> argPair : mArgMap.entrySet()) {
} final String argCmd = String.format(" -e %s %s", argPair.getKey(),
else { argPair.getValue());
return ""; commandBuilder.append(argCmd);
} }
return commandBuilder.toString();
} }
} }

View File

@@ -103,9 +103,43 @@ public class InstrumentationResultParserTest extends TestCase {
injectTestString(timeString); injectTestString(timeString);
assertEquals(4900, mTestResult.mTestTime); assertEquals(4900, mTestResult.mTestTime);
} }
/**
* Test basic parsing of a test run failure.
*/
public void testRunFailed() {
StringBuilder output = new StringBuilder();
final String errorMessage = "Unable to find instrumentation info";
addStatusKey(output, "Error", errorMessage);
addStatusCode(output, "-1");
output.append("INSTRUMENTATION_FAILED: com.dummy/android.test.InstrumentationTestRunner");
addLineBreak(output);
injectTestString(output.toString());
assertEquals(errorMessage, mTestResult.mRunFailedMessage);
}
/**
* Test parsing of a test run failure, where an instrumentation component failed to load
* Parsing input takes the from of INSTRUMENTATION_RESULT: fff
*/
public void testRunFailedResult() {
StringBuilder output = new StringBuilder();
final String errorMessage = "Unable to instantiate instrumentation";
output.append("INSTRUMENTATION_RESULT: shortMsg=");
output.append(errorMessage);
addLineBreak(output);
output.append("INSTRUMENTATION_CODE: 0");
addLineBreak(output);
injectTestString(output.toString());
assertEquals(errorMessage, mTestResult.mRunFailedMessage);
}
/** /**
* builds a common test result using TEST_NAME and TEST_CLASS. * Builds a common test result using TEST_NAME and TEST_CLASS.
*/ */
private StringBuilder buildCommonResult() { private StringBuilder buildCommonResult() {
StringBuilder output = new StringBuilder(); StringBuilder output = new StringBuilder();
@@ -146,6 +180,13 @@ public class InstrumentationResultParserTest extends TestCase {
outputBuilder.append(key); outputBuilder.append(key);
outputBuilder.append('='); outputBuilder.append('=');
outputBuilder.append(value); outputBuilder.append(value);
addLineBreak(outputBuilder);
}
/**
* Append line break characters to output
*/
private void addLineBreak(StringBuilder outputBuilder) {
outputBuilder.append("\r\n"); outputBuilder.append("\r\n");
} }
@@ -164,7 +205,7 @@ public class InstrumentationResultParserTest extends TestCase {
private void addStatusCode(StringBuilder outputBuilder, String value) { private void addStatusCode(StringBuilder outputBuilder, String value) {
outputBuilder.append("INSTRUMENTATION_STATUS_CODE: "); outputBuilder.append("INSTRUMENTATION_STATUS_CODE: ");
outputBuilder.append(value); outputBuilder.append(value);
outputBuilder.append("\r\n"); addLineBreak(outputBuilder);
} }
/** /**
@@ -197,11 +238,14 @@ public class InstrumentationResultParserTest extends TestCase {
TestFailure mTestStatus; TestFailure mTestStatus;
String mTrace; String mTrace;
boolean mStopped; boolean mStopped;
/** stores the error message provided to testRunFailed */
String mRunFailedMessage;
VerifyingTestResult() { VerifyingTestResult() {
mNumTestsRun = 0; mNumTestsRun = 0;
mTestStatus = null; mTestStatus = null;
mStopped = false; mStopped = false;
mRunFailedMessage = null;
} }
public void testEnded(TestIdentifier test) { public void testEnded(TestIdentifier test) {
@@ -238,8 +282,7 @@ public class InstrumentationResultParserTest extends TestCase {
} }
public void testRunFailed(String errorMessage) { public void testRunFailed(String errorMessage) {
// ignored mRunFailedMessage = errorMessage;
} }
} }
} }

View File

@@ -17,18 +17,17 @@
package com.android.ddmlib.testrunner; package com.android.ddmlib.testrunner;
import com.android.ddmlib.Client; import com.android.ddmlib.Client;
import com.android.ddmlib.Device.DeviceState;
import com.android.ddmlib.FileListingService; import com.android.ddmlib.FileListingService;
import com.android.ddmlib.IDevice; import com.android.ddmlib.IDevice;
import com.android.ddmlib.IShellOutputReceiver; import com.android.ddmlib.IShellOutputReceiver;
import com.android.ddmlib.log.LogReceiver;
import com.android.ddmlib.RawImage; import com.android.ddmlib.RawImage;
import com.android.ddmlib.SyncService; import com.android.ddmlib.SyncService;
import com.android.ddmlib.Device.DeviceState;
import com.android.ddmlib.log.LogReceiver;
import junit.framework.TestCase;
import java.io.IOException; import java.io.IOException;
import java.util.Map; import java.util.Map;
import junit.framework.TestCase;
/** /**
* Tests RemoteAndroidTestRunner. * Tests RemoteAndroidTestRunner.
@@ -82,14 +81,15 @@ public class RemoteAndroidTestRunnerTest extends TestCase {
} }
/** /**
* Test the building of the instrumentation runner command with extra args set. * Test the building of the instrumentation runner command with extra argument added.
*/ */
public void testRunWithExtraArgs() { public void testRunWithAddInstrumentationArg() {
final String extraArgs = "blah"; final String extraArgName = "blah";
mRunner.setExtraArgs(extraArgs); final String extraArgValue = "blahValue";
mRunner.addInstrumentationArg(extraArgName, extraArgValue);
mRunner.run(new EmptyListener()); mRunner.run(new EmptyListener());
assertStringsEquals(String.format("am instrument -w -r %s %s/%s", extraArgs, assertStringsEquals(String.format("am instrument -w -r -e %s %s %s/%s", extraArgName,
TEST_PACKAGE, TEST_RUNNER), mMockDevice.getLastShellCommand()); extraArgValue, TEST_PACKAGE, TEST_RUNNER), mMockDevice.getLastShellCommand());
} }
@@ -243,6 +243,5 @@ public class RemoteAndroidTestRunnerTest extends TestCase {
public void testStarted(TestIdentifier test) { public void testStarted(TestIdentifier test) {
// ignore // ignore
} }
} }
} }

View File

@@ -40,7 +40,8 @@ Require-Bundle: com.android.ide.eclipse.ddms,
org.eclipse.wst.sse.ui, org.eclipse.wst.sse.ui,
org.eclipse.wst.xml.core, org.eclipse.wst.xml.core,
org.eclipse.wst.xml.ui, org.eclipse.wst.xml.ui,
org.eclipse.jdt.junit org.eclipse.jdt.junit,
org.eclipse.jdt.junit.runtime
Eclipse-LazyStart: true Eclipse-LazyStart: true
Export-Package: com.android.ide.eclipse.adt, Export-Package: com.android.ide.eclipse.adt,
com.android.ide.eclipse.adt.build;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.adt.build;x-friends:="com.android.ide.eclipse.tests",

View File

@@ -510,4 +510,59 @@
type="org.eclipse.jdt.junit.launchconfig"> type="org.eclipse.jdt.junit.launchconfig">
</launchDelegate> </launchDelegate>
</extension> </extension>
<extension
point="org.eclipse.debug.core.launchConfigurationTypes">
<launchConfigurationType
delegate="com.android.ide.eclipse.adt.launch.junit.AndroidJUnitLaunchConfigDelegate"
id="com.android.ide.eclipse.adt.junit.launchConfigurationType"
modes="run,debug"
name="Android Instrumentation"
public="true"
sourceLocatorId="org.eclipse.jdt.launching.sourceLocator.JavaSourceLookupDirector"
sourcePathComputerId="org.eclipse.jdt.launching.sourceLookup.javaSourcePathComputer">
</launchConfigurationType>
</extension>
<extension
point="org.eclipse.debug.ui.launchConfigurationTypeImages">
<launchConfigurationTypeImage
configTypeID="com.android.ide.eclipse.adt.junit.launchConfigurationType"
icon="icons/android.png"
id="com.android.ide.eclipse.adt.junit.launchConfigurationTypeImage">
</launchConfigurationTypeImage>
</extension>
<extension
point="org.eclipse.debug.ui.launchConfigurationTabGroups">
<launchConfigurationTabGroup
class="com.android.ide.eclipse.adt.launch.junit.AndroidJUnitTabGroup"
description="Android Instrumentation"
id="com.android.ide.eclipse.adt.junit.AndroidJUnitLaunchConfigTabGroup"
type="com.android.ide.eclipse.adt.junit.launchConfigurationType"/>
</extension>
<extension
point="org.eclipse.debug.ui.launchShortcuts">
<shortcut
class="com.android.ide.eclipse.adt.launch.junit.AndroidJUnitLaunchShortcut"
icon="icons/android.png"
id="com.android.ide.eclipse.adt.junit.launchShortcut"
label="Android Instrumentation"
modes="run,debug">
<contextualLaunch>
<enablement>
<with variable="selection">
<count value="1"/>
<iterate>
<adapt type="org.eclipse.jdt.core.IJavaElement">
<test property="org.eclipse.jdt.core.isInJavaProjectWithNature" value="com.android.ide.eclipse.adt.AndroidNature"/>
<test property="org.eclipse.jdt.core.hasTypeOnClasspath" value="junit.framework.Test"/>
<test property="org.eclipse.jdt.junit.canLaunchAsJUnit" forcePluginActivation="true"/>
</adapt>
</iterate>
</with>
</enablement>
</contextualLaunch>
<configurationType
id="com.android.ide.eclipse.adt.junit.launchConfigurationType">
</configurationType>
</shortcut>
</extension>
</plugin> </plugin>

View File

@@ -1040,6 +1040,16 @@ public class AdtPlugin extends AbstractUIPlugin {
mSdkIsLoaded = LoadStatus.LOADED; mSdkIsLoaded = LoadStatus.LOADED;
progress.setTaskName("Check Projects"); progress.setTaskName("Check Projects");
ArrayList<IJavaProject> list = new ArrayList<IJavaProject>();
for (IJavaProject javaProject : mPostLoadProjectsToResolve) {
if (javaProject.getProject().isOpen()) {
list.add(javaProject);
}
}
// done with this list.
mPostLoadProjectsToResolve.clear();
// check the projects that need checking. // check the projects that need checking.
// The method modifies the list (it removes the project that // The method modifies the list (it removes the project that
@@ -1047,14 +1057,13 @@ public class AdtPlugin extends AbstractUIPlugin {
AndroidClasspathContainerInitializer.checkProjectsCache( AndroidClasspathContainerInitializer.checkProjectsCache(
mPostLoadProjectsToCheck); mPostLoadProjectsToCheck);
mPostLoadProjectsToResolve.addAll(mPostLoadProjectsToCheck); list.addAll(mPostLoadProjectsToCheck);
// update the project that needs recompiling. // update the project that needs recompiling.
if (mPostLoadProjectsToResolve.size() > 0) { if (list.size() > 0) {
IJavaProject[] array = mPostLoadProjectsToResolve.toArray( IJavaProject[] array = list.toArray(
new IJavaProject[mPostLoadProjectsToResolve.size()]); new IJavaProject[list.size()]);
AndroidClasspathContainerInitializer.updateProjects(array); AndroidClasspathContainerInitializer.updateProjects(array);
mPostLoadProjectsToResolve.clear();
} }
progress.worked(10); progress.worked(10);

View File

@@ -222,6 +222,7 @@ public class PreCompilerBuilder extends BaseBuilder {
PreCompilerDeltaVisitor dv = null; PreCompilerDeltaVisitor dv = null;
String javaPackage = null; String javaPackage = null;
int minSdkVersion = AndroidManifestParser.INVALID_MIN_SDK;
if (kind == FULL_BUILD) { if (kind == FULL_BUILD) {
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
@@ -253,6 +254,7 @@ public class PreCompilerBuilder extends BaseBuilder {
// get the java package from the visitor // get the java package from the visitor
javaPackage = dv.getManifestPackage(); javaPackage = dv.getManifestPackage();
minSdkVersion = dv.getMinSdkVersion();
} }
} }
@@ -276,7 +278,7 @@ public class PreCompilerBuilder extends BaseBuilder {
if (manifest == null) { if (manifest == null) {
String msg = String.format(Messages.s_File_Missing, String msg = String.format(Messages.s_File_Missing,
AndroidConstants.FN_ANDROID_MANIFEST); AndroidConstants.FN_ANDROID_MANIFEST);
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg); AdtPlugin.printErrorToConsole(project, msg);
markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
// This interrupts the build. The next builders will not run. // This interrupts the build. The next builders will not run.
@@ -304,19 +306,34 @@ public class PreCompilerBuilder extends BaseBuilder {
// get the java package from the parser // get the java package from the parser
javaPackage = parser.getPackage(); javaPackage = parser.getPackage();
minSdkVersion = parser.getApiLevelRequirement();
} }
if (javaPackage == null || javaPackage.length() == 0) { if (minSdkVersion != AndroidManifestParser.INVALID_MIN_SDK &&
// looks like the AndroidManifest file isn't valid. minSdkVersion < projectTarget.getApiVersionNumber()) {
String msg = String.format(Messages.s_Doesnt_Declare_Package_Error, // check it against the target api level
AndroidConstants.FN_ANDROID_MANIFEST); String msg = String.format(
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, "Manifest min SDK version (%1$d) is lower than project target API level (%2$d)",
msg); minSdkVersion, projectTarget.getApiVersionNumber());
AdtPlugin.printErrorToConsole(project, msg);
BaseProjectHelper.addMarker(manifest, AdtConstants.MARKER_ADT, msg,
IMarker.SEVERITY_ERROR);
// This interrupts the build. The next builders will not run. // This interrupts the build. The next builders will not run.
stopBuild(msg); stopBuild(msg);
} }
if (javaPackage == null || javaPackage.length() == 0) {
// looks like the AndroidManifest file isn't valid.
String msg = String.format(Messages.s_Doesnt_Declare_Package_Error,
AndroidConstants.FN_ANDROID_MANIFEST);
AdtPlugin.printErrorToConsole(project, msg);
markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
// This interrupts the build. The next builders will not run.
stopBuild(msg);
}
// at this point we have the java package. We need to make sure it's not a different // at this point we have the java package. We need to make sure it's not a different
// package than the previous one that were built. // package than the previous one that were built.
if (javaPackage.equals(mManifestPackage) == false) { if (javaPackage.equals(mManifestPackage) == false) {

View File

@@ -72,8 +72,10 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
/** Manifest check/parsing flag. */ /** Manifest check/parsing flag. */
private boolean mCheckedManifestXml = false; private boolean mCheckedManifestXml = false;
/** Application Pacakge, gathered from the parsing of the manifest */ /** Application Package, gathered from the parsing of the manifest */
private String mJavaPackage = null; private String mJavaPackage = null;
/** minSDKVersion attribute value, gathered from the parsing of the manifest */
private int mMinSdkVersion = AndroidManifestParser.INVALID_MIN_SDK;
// Internal usage fields. // Internal usage fields.
/** /**
@@ -137,6 +139,22 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
return mJavaPackage; return mJavaPackage;
} }
/**
* Returns the minSDkVersion attribute from the manifest if it was checked/parsed.
* <p/>
* This can return {@link AndroidManifestParser#INVALID_MIN_SDK} in two cases:
* <ul>
* <li>The manifest was not part of the resource change delta, and the manifest was
* not checked/parsed ({@link #getCheckedManifestXml()} returns <code>false</code>)</li>
* <li>The manifest was parsed ({@link #getCheckedManifestXml()} returns <code>true</code>),
* but the package declaration is missing</li>
* </ul>
* @return the minSdkVersion or {@link AndroidManifestParser#INVALID_MIN_SDK}.
*/
public int getMinSdkVersion() {
return mMinSdkVersion;
}
/* /*
* (non-Javadoc) * (non-Javadoc)
* *
@@ -184,6 +202,7 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
if (parser != null) { if (parser != null) {
mJavaPackage = parser.getPackage(); mJavaPackage = parser.getPackage();
mMinSdkVersion = parser.getApiLevelRequirement();
} }
mCheckedManifestXml = true; mCheckedManifestXml = true;

View File

@@ -307,7 +307,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
* <code>DEBUG_MODE</code>. * <code>DEBUG_MODE</code>.
* @param apk the resource to the apk to launch. * @param apk the resource to the apk to launch.
* @param debuggable the debuggable value of the app, or null if not set. * @param debuggable the debuggable value of the app, or null if not set.
* @param requiredApiVersionNumber the api version required by the app, or -1 if none. * @param requiredApiVersionNumber the api version required by the app, or
* {@link AndroidManifestParser#INVALID_MIN_SDK} if none.
* @param launchAction the action to perform after app sync * @param launchAction the action to perform after app sync
* @param config the launch configuration * @param config the launch configuration
* @param launch the launch object * @param launch the launch object
@@ -638,20 +639,21 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
String deviceApiVersionName = device.getProperty(IDevice.PROP_BUILD_VERSION); String deviceApiVersionName = device.getProperty(IDevice.PROP_BUILD_VERSION);
String value = device.getProperty(IDevice.PROP_BUILD_VERSION_NUMBER); String value = device.getProperty(IDevice.PROP_BUILD_VERSION_NUMBER);
int deviceApiVersionNumber = 0; int deviceApiVersionNumber = AndroidManifestParser.INVALID_MIN_SDK;
try { try {
deviceApiVersionNumber = Integer.parseInt(value); deviceApiVersionNumber = Integer.parseInt(value);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
// pass, we'll keep the deviceVersionNumber value at 0. // pass, we'll keep the deviceVersionNumber value at 0.
} }
if (launchInfo.getRequiredApiVersionNumber() == 0) { if (launchInfo.getRequiredApiVersionNumber() == AndroidManifestParser.INVALID_MIN_SDK) {
// warn the API level requirement is not set. // warn the API level requirement is not set.
AdtPlugin.printErrorToConsole(launchInfo.getProject(), AdtPlugin.printErrorToConsole(launchInfo.getProject(),
"WARNING: Application does not specify an API level requirement!"); "WARNING: Application does not specify an API level requirement!");
// and display the target device API level (if known) // and display the target device API level (if known)
if (deviceApiVersionName == null || deviceApiVersionNumber == 0) { if (deviceApiVersionName == null ||
deviceApiVersionNumber == AndroidManifestParser.INVALID_MIN_SDK) {
AdtPlugin.printErrorToConsole(launchInfo.getProject(), AdtPlugin.printErrorToConsole(launchInfo.getProject(),
"WARNING: Unknown device API version!"); "WARNING: Unknown device API version!");
} else { } else {
@@ -660,7 +662,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
deviceApiVersionName)); deviceApiVersionName));
} }
} else { // app requires a specific API level } else { // app requires a specific API level
if (deviceApiVersionName == null || deviceApiVersionNumber == 0) { if (deviceApiVersionName == null ||
deviceApiVersionNumber == AndroidManifestParser.INVALID_MIN_SDK) {
AdtPlugin.printToConsole(launchInfo.getProject(), AdtPlugin.printToConsole(launchInfo.getProject(),
"WARNING: Unknown device API version!"); "WARNING: Unknown device API version!");
} else if (deviceApiVersionNumber < launchInfo.getRequiredApiVersionNumber()) { } else if (deviceApiVersionNumber < launchInfo.getRequiredApiVersionNumber()) {

View File

@@ -16,12 +16,13 @@
package com.android.ide.eclipse.adt.launch; package com.android.ide.eclipse.adt.launch;
import com.android.ddmlib.IDevice;
import com.android.ide.eclipse.common.project.AndroidManifestParser;
import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IProgressMonitor;
import com.android.ddmlib.IDevice;
/** /**
* A delayed launch waiting for a device to be present or ready before the * A delayed launch waiting for a device to be present or ready before the
* application is launched. * application is launched.
@@ -50,7 +51,8 @@ public final class DelayedLaunchInfo {
/** debuggable attribute of the manifest file. */ /** debuggable attribute of the manifest file. */
private final Boolean mDebuggable; private final Boolean mDebuggable;
/** Required ApiVersionNumber by the app. 0 means no requirements */ /** Required ApiVersionNumber by the app. {@link AndroidManifestParser#INVALID_MIN_SDK} means
* no requirements */
private final int mRequiredApiVersionNumber; private final int mRequiredApiVersionNumber;
private InstallRetryMode mRetryMode = InstallRetryMode.NEVER; private InstallRetryMode mRetryMode = InstallRetryMode.NEVER;
@@ -81,7 +83,8 @@ public final class DelayedLaunchInfo {
* @param launchAction action to perform after app install * @param launchAction action to perform after app install
* @param pack IFile to the package (.apk) file * @param pack IFile to the package (.apk) file
* @param debuggable debuggable attribute of the app's manifest file. * @param debuggable debuggable attribute of the app's manifest file.
* @param requiredApiVersionNumber required SDK version by the app. 0 means no requirements. * @param requiredApiVersionNumber required SDK version by the app.
* {@link AndroidManifestParser#INVALID_MIN_SDK} means no requirements.
* @param launch the launch object * @param launch the launch object
* @param monitor progress monitor for launch * @param monitor progress monitor for launch
*/ */

View File

@@ -55,6 +55,11 @@ import org.eclipse.swt.widgets.Text;
*/ */
public class MainLaunchConfigTab extends AbstractLaunchConfigurationTab { public class MainLaunchConfigTab extends AbstractLaunchConfigurationTab {
/**
*
*/
public static final String LAUNCH_TAB_IMAGE = "mainLaunchTab.png";
protected static final String EMPTY_STRING = ""; //$NON-NLS-1$ protected static final String EMPTY_STRING = ""; //$NON-NLS-1$
protected Text mProjText; protected Text mProjText;
@@ -194,7 +199,7 @@ public class MainLaunchConfigTab extends AbstractLaunchConfigurationTab {
@Override @Override
public Image getImage() { public Image getImage() {
return AdtPlugin.getImageLoader().loadImage("mainLaunchTab.png", null); return AdtPlugin.getImageLoader().loadImage(LAUNCH_TAB_IMAGE, null);
} }
@@ -310,21 +315,8 @@ public class MainLaunchConfigTab extends AbstractLaunchConfigurationTab {
} }
mProjText.setText(projectName); mProjText.setText(projectName);
// get the list of projects IProject proj = mProjectChooserHelper.getAndroidProject(projectName);
IJavaProject[] projects = mProjectChooserHelper.getAndroidProjects(null); loadActivities(proj);
if (projects != null) {
// look for the currently selected project
IProject proj = null;
for (IJavaProject p : projects) {
if (p.getElementName().equals(projectName)) {
proj = p.getProject();
break;
}
}
loadActivities(proj);
}
// load the launch action. // load the launch action.
mLaunchAction = LaunchConfigDelegate.DEFAULT_LAUNCH_ACTION; mLaunchAction = LaunchConfigDelegate.DEFAULT_LAUNCH_ACTION;

View File

@@ -0,0 +1,263 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
*
* 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.ide.eclipse.adt.launch.junit;
import com.android.ddmlib.IDevice;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.launch.DelayedLaunchInfo;
import com.android.ide.eclipse.adt.launch.IAndroidLaunchAction;
import com.android.ide.eclipse.adt.launch.junit.runtime.AndroidJUnitLaunchInfo;
import com.android.ide.eclipse.adt.launch.junit.runtime.RemoteADTTestRunner;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.core.model.IStreamsProxy;
import org.eclipse.jdt.junit.launcher.JUnitLaunchConfigurationDelegate;
import org.eclipse.jdt.launching.IVMRunner;
import org.eclipse.jdt.launching.VMRunnerConfiguration;
/**
* A launch action that executes a instrumentation test run on an Android device.
*/
class AndroidJUnitLaunchAction implements IAndroidLaunchAction {
private String mTestPackage;
private String mRunner;
/**
* Creates a AndroidJUnitLaunchAction.
*
* @param testPackage the Android application package that contains the tests to run
* @param runner the InstrumentationTestRunner that will execute the tests
*/
public AndroidJUnitLaunchAction(String testPackage, String runner) {
mTestPackage = testPackage;
mRunner = runner;
}
/**
* Launch a instrumentation test run on given Android device.
* Reuses JDT JUnit launch delegate so results can be communicated back to JDT JUnit UI.
*
* @see com.android.ide.eclipse.adt.launch.IAndroidLaunchAction#doLaunchAction(com.android.ide.eclipse.adt.launch.AndroidLaunchController.DelayedLaunchInfo, com.android.ddmlib.Device)
*/
public boolean doLaunchAction(DelayedLaunchInfo info, IDevice device) {
String msg = String.format("Launching instrumentation %s on device %s", mRunner,
device.getSerialNumber());
AdtPlugin.printToConsole(info.getProject(), msg);
try {
JUnitLaunchDelegate junitDelegate = new JUnitLaunchDelegate(info, device);
final String mode = info.isDebugMode() ? ILaunchManager.DEBUG_MODE :
ILaunchManager.RUN_MODE;
junitDelegate.launch(info.getLaunch().getLaunchConfiguration(), mode, info.getLaunch(),
info.getMonitor());
// TODO: need to add AMReceiver-type functionality somewhere
} catch (CoreException e) {
AdtPlugin.printErrorToConsole(info.getProject(), "Failed to launch test");
}
return true;
}
/**
* {@inheritDoc}
*/
public String getLaunchDescription() {
return String.format("%s JUnit launch", mRunner);
}
/**
* Extends the JDT JUnit launch delegate to allow for JUnit UI reuse.
*/
private class JUnitLaunchDelegate extends JUnitLaunchConfigurationDelegate {
private IDevice mDevice;
private DelayedLaunchInfo mLaunchInfo;
public JUnitLaunchDelegate(DelayedLaunchInfo info, IDevice device) {
mLaunchInfo = info;
mDevice = device;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.junit.launcher.JUnitLaunchConfigurationDelegate#launch(org.eclipse.debug.core.ILaunchConfiguration, java.lang.String, org.eclipse.debug.core.ILaunch, org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
public synchronized void launch(ILaunchConfiguration configuration, String mode,
ILaunch launch, IProgressMonitor monitor) throws CoreException {
// TODO: is progress monitor adjustment needed here?
super.launch(configuration, mode, launch, monitor);
}
/* (non-Javadoc)
* @see org.eclipse.jdt.junit.launcher.JUnitLaunchConfigurationDelegate#verifyMainTypeName(org.eclipse.debug.core.ILaunchConfiguration)
*/
@Override
public String verifyMainTypeName(ILaunchConfiguration configuration) throws CoreException {
return "com.android.ide.eclipse.adt.junit.internal.runner.RemoteAndroidTestRunner"; //$NON-NLS-1$
}
/**
* Overrides parent to return a VM Runner implementation which launches a thread, rather
* than a separate VM process
*/
@Override
public IVMRunner getVMRunner(ILaunchConfiguration configuration, String mode)
throws CoreException {
return new VMTestRunner(new AndroidJUnitLaunchInfo(mLaunchInfo.getProject(),
mTestPackage, mRunner, mLaunchInfo.isDebugMode(), mDevice));
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.LaunchConfigurationDelegate#getLaunch(org.eclipse.debug.core.ILaunchConfiguration, java.lang.String)
*/
@Override
public ILaunch getLaunch(ILaunchConfiguration configuration, String mode)
throws CoreException {
return mLaunchInfo.getLaunch();
}
}
/**
* Provides a VM runner implementation which starts a thread implementation of a launch process
*/
private static class VMTestRunner implements IVMRunner {
private final AndroidJUnitLaunchInfo mJUnitInfo;
VMTestRunner(AndroidJUnitLaunchInfo info) {
mJUnitInfo = info;
}
public void run(final VMRunnerConfiguration config, ILaunch launch,
IProgressMonitor monitor) throws CoreException {
TestRunnerProcess runnerProcess =
new TestRunnerProcess(config, launch, mJUnitInfo);
runnerProcess.start();
launch.addProcess(runnerProcess);
}
}
/**
* Launch process that executes the tests.
*/
private static class TestRunnerProcess extends Thread implements IProcess {
private final VMRunnerConfiguration mRunConfig;
private final ILaunch mLaunch;
private final AndroidJUnitLaunchInfo mJUnitInfo;
private RemoteADTTestRunner mTestRunner = null;
private boolean mIsTerminated = false;
TestRunnerProcess(VMRunnerConfiguration runConfig, ILaunch launch,
AndroidJUnitLaunchInfo info) {
mRunConfig = runConfig;
mLaunch = launch;
mJUnitInfo = info;
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.IProcess#getAttribute(java.lang.String)
*/
public String getAttribute(String key) {
return null;
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.IProcess#getExitValue()
*/
public int getExitValue() throws DebugException {
return 0;
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.IProcess#getLabel()
*/
public String getLabel() {
return mLaunch.getLaunchMode();
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.IProcess#getLaunch()
*/
public ILaunch getLaunch() {
return mLaunch;
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.IProcess#getStreamsProxy()
*/
public IStreamsProxy getStreamsProxy() {
return null;
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.IProcess#setAttribute(java.lang.String,
* java.lang.String)
*/
public void setAttribute(String key, String value) {
// ignore
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
*/
@SuppressWarnings("unchecked")
public Object getAdapter(Class adapter) {
return null;
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.ITerminate#canTerminate()
*/
public boolean canTerminate() {
return true;
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.ITerminate#isTerminated()
*/
public boolean isTerminated() {
return mIsTerminated;
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.ITerminate#terminate()
*/
public void terminate() throws DebugException {
if (mTestRunner != null) {
mTestRunner.terminate();
}
mIsTerminated = true;
}
/**
* Launches a test runner that will communicate results back to JDT JUnit UI
*/
@Override
public void run() {
mTestRunner = new RemoteADTTestRunner();
mTestRunner.runTests(mRunConfig.getProgramArguments(), mJUnitInfo);
}
}
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
*
* 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.ide.eclipse.adt.launch.junit;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.launch.AndroidLaunch;
import com.android.ide.eclipse.adt.launch.AndroidLaunchController;
import com.android.ide.eclipse.adt.launch.IAndroidLaunchAction;
import com.android.ide.eclipse.adt.launch.LaunchConfigDelegate;
import com.android.ide.eclipse.adt.launch.AndroidLaunchConfiguration;
import com.android.ide.eclipse.common.project.AndroidManifestParser;
import com.android.ide.eclipse.common.project.BaseProjectHelper;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.jdt.internal.junit.launcher.JUnitLaunchConfigurationConstants;
import org.eclipse.jdt.internal.junit.launcher.TestKindRegistry;
/**
* Run configuration that can execute JUnit tests on an Android platform
* <p/>
* Will deploy apps on target Android platform by reusing functionality from ADT
* LaunchConfigDelegate, and then run JUnits tests by reusing functionality from JDT
* JUnitLaunchConfigDelegate.
*/
@SuppressWarnings("restriction") //$NON-NLS-1$
public class AndroidJUnitLaunchConfigDelegate extends LaunchConfigDelegate {
/** Launch config attribute that stores instrumentation runner */
static final String ATTR_INSTR_NAME = AdtPlugin.PLUGIN_ID + ".instrumentation"; //$NON-NLS-1$
private static final String EMPTY_STRING = ""; //$NON-NLS-1$
@Override
protected void doLaunch(final ILaunchConfiguration configuration, final String mode,
IProgressMonitor monitor, IProject project, final AndroidLaunch androidLaunch,
AndroidLaunchConfiguration config, AndroidLaunchController controller,
IFile applicationPackage, AndroidManifestParser manifestParser) {
String testPackage = manifestParser.getPackage();
String runner = getRunnerFromConfig(configuration);
if (runner == null) {
AdtPlugin.displayError("Android Launch",
"An instrumention test runner is not specified!");
androidLaunch.stopLaunch();
return;
}
IAndroidLaunchAction junitLaunch = new AndroidJUnitLaunchAction(testPackage, runner);
controller.launch(project, mode, applicationPackage, manifestParser.getPackage(),
manifestParser.getDebuggable(), manifestParser.getApiLevelRequirement(),
junitLaunch, config, androidLaunch, monitor);
}
private String getRunnerFromConfig(ILaunchConfiguration configuration) {
String runner = EMPTY_STRING;
try {
runner = configuration.getAttribute(ATTR_INSTR_NAME, EMPTY_STRING);
} catch (CoreException e) {
AdtPlugin.log(e, "Error when retrieving instrumentation info from launch config"); //$NON-NLS-1$
}
if (runner.length() < 1) {
return null;
}
return runner;
}
/**
* Helper method to return the set of instrumentations for the Android project
*
* @param project the {@link IProject} to get instrumentations for
* @return null if no error occurred parsing instrumentations
*/
static String[] getInstrumentationsForProject(IProject project) {
if (project != null) {
try {
// parse the manifest for the list of instrumentations
AndroidManifestParser manifestParser = AndroidManifestParser.parse(
BaseProjectHelper.getJavaProject(project), null /* errorListener */,
true /* gatherData */, false /* markErrors */);
if (manifestParser != null) {
return manifestParser.getInstrumentations();
}
} catch (CoreException e) {
AdtPlugin.log(e, "%s: Error parsing AndroidManifest.xml", //$NON-NLS-1$
project.getName());
}
}
return null;
}
/**
* Helper method to set JUnit-related attributes expected by JDT JUnit runner
*
* @param config the launch configuration to modify
*/
static void setJUnitDefaults(ILaunchConfigurationWorkingCopy config) {
// set the test runner to JUnit3 to placate JDT JUnit runner logic
config.setAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_RUNNER_KIND,
TestKindRegistry.JUNIT3_TEST_KIND_ID);
}
}

View File

@@ -0,0 +1,967 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
*
* 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.ide.eclipse.adt.launch.junit;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.launch.MainLaunchConfigTab;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.project.ProjectChooserHelper;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.ui.AbstractLaunchConfigurationTab;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaModel;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.ISourceReference;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.junit.Messages;
import org.eclipse.jdt.internal.junit.launcher.ITestKind;
import org.eclipse.jdt.internal.junit.launcher.JUnitLaunchConfigurationConstants;
import org.eclipse.jdt.internal.junit.launcher.JUnitMigrationDelegate;
import org.eclipse.jdt.internal.junit.launcher.TestKindRegistry;
import org.eclipse.jdt.internal.junit.launcher.TestSelectionDialog;
import org.eclipse.jdt.internal.junit.ui.JUnitMessages;
import org.eclipse.jdt.internal.junit.util.LayoutUtil;
import org.eclipse.jdt.internal.junit.util.TestSearchEngine;
import org.eclipse.jdt.internal.ui.wizards.TypedElementSelectionValidator;
import org.eclipse.jdt.internal.ui.wizards.TypedViewerFilter;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import org.eclipse.jdt.ui.JavaElementComparator;
import org.eclipse.jdt.ui.JavaElementLabelProvider;
import org.eclipse.jdt.ui.StandardJavaElementContentProvider;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.ElementTreeSelectionDialog;
import org.eclipse.ui.dialogs.SelectionDialog;
import java.lang.reflect.InvocationTargetException;
/**
* The launch config UI tab for Android JUnit
* <p/>
* Based on org.eclipse.jdt.junit.launcher.JUnitLaunchConfigurationTab
*/
@SuppressWarnings("restriction")
public class AndroidJUnitLaunchConfigurationTab extends AbstractLaunchConfigurationTab {
// Project UI widgets
private Label mProjLabel;
private Text mProjText;
private Button mProjButton;
// Test class UI widgets
private Text mTestText;
private Button mSearchButton;
private String mOriginalTestMethodName;
private Label mTestMethodLabel;
private Text mContainerText;
private IJavaElement mContainerElement;
private final ILabelProvider mJavaElementLabelProvider = new JavaElementLabelProvider();
private Button mContainerSearchButton;
private Button mTestContainerRadioButton;
private Button mTestRadioButton;
private Label mTestLabel;
// Android specific members
private Image mTabIcon = null;
private Combo mInstrumentationCombo;
private static final String EMPTY_STRING = ""; //$NON-NLS-1$
private String[] mInstrumentations = null;
private ProjectChooserHelper mProjectChooserHelper;
/* (non-Javadoc)
* @see org.eclipse.debug.ui.ILaunchConfigurationTab#createControl(org.eclipse.swt.widgets.Composite)
*/
public void createControl(Composite parent) {
mProjectChooserHelper = new ProjectChooserHelper(parent.getShell());
Composite comp = new Composite(parent, SWT.NONE);
setControl(comp);
GridLayout topLayout = new GridLayout();
topLayout.numColumns = 3;
comp.setLayout(topLayout);
createSingleTestSection(comp);
createTestContainerSelectionGroup(comp);
createSpacer(comp);
createInstrumentationGroup(comp);
createSpacer(comp);
Dialog.applyDialogFont(comp);
// TODO: add help link here when available
//PlatformUI.getWorkbench().getHelpSystem().setHelp(getControl(),
// IJUnitHelpContextIds.LAUNCH_CONFIGURATION_DIALOG_JUNIT_MAIN_TAB);
validatePage();
}
private void createSpacer(Composite comp) {
Label label = new Label(comp, SWT.NONE);
GridData gd = new GridData();
gd.horizontalSpan = 3;
label.setLayoutData(gd);
}
private void createSingleTestSection(Composite comp) {
mTestRadioButton = new Button(comp, SWT.RADIO);
mTestRadioButton.setText(JUnitMessages.JUnitLaunchConfigurationTab_label_oneTest);
GridData gd = new GridData();
gd.horizontalSpan = 3;
mTestRadioButton.setLayoutData(gd);
mTestRadioButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
if (mTestRadioButton.getSelection()) {
testModeChanged();
}
}
});
mProjLabel = new Label(comp, SWT.NONE);
mProjLabel.setText(JUnitMessages.JUnitLaunchConfigurationTab_label_project);
gd = new GridData();
gd.horizontalIndent = 25;
mProjLabel.setLayoutData(gd);
mProjText = new Text(comp, SWT.SINGLE | SWT.BORDER);
mProjText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mProjText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent evt) {
validatePage();
updateLaunchConfigurationDialog();
mSearchButton.setEnabled(mTestRadioButton.getSelection() &&
mProjText.getText().length() > 0);
}
});
mProjButton = new Button(comp, SWT.PUSH);
mProjButton.setText(JUnitMessages.JUnitLaunchConfigurationTab_label_browse);
mProjButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent evt) {
handleProjectButtonSelected();
}
});
setButtonGridData(mProjButton);
mTestLabel = new Label(comp, SWT.NONE);
gd = new GridData();
gd.horizontalIndent = 25;
mTestLabel.setLayoutData(gd);
mTestLabel.setText(JUnitMessages.JUnitLaunchConfigurationTab_label_test);
mTestText = new Text(comp, SWT.SINGLE | SWT.BORDER);
mTestText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mTestText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent evt) {
validatePage();
updateLaunchConfigurationDialog();
}
});
mSearchButton = new Button(comp, SWT.PUSH);
mSearchButton.setEnabled(mProjText.getText().length() > 0);
mSearchButton.setText(JUnitMessages.JUnitLaunchConfigurationTab_label_search);
mSearchButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent evt) {
handleSearchButtonSelected();
}
});
setButtonGridData(mSearchButton);
new Label(comp, SWT.NONE);
mTestMethodLabel = new Label(comp, SWT.NONE);
mTestMethodLabel.setText(""); //$NON-NLS-1$
gd = new GridData();
gd.horizontalSpan = 2;
mTestMethodLabel.setLayoutData(gd);
}
private void createTestContainerSelectionGroup(Composite comp) {
mTestContainerRadioButton = new Button(comp, SWT.RADIO);
mTestContainerRadioButton.setText(
JUnitMessages.JUnitLaunchConfigurationTab_label_containerTest);
GridData gd = new GridData();
gd.horizontalSpan = 3;
mTestContainerRadioButton.setLayoutData(gd);
mTestContainerRadioButton.addSelectionListener(new SelectionListener() {
public void widgetSelected(SelectionEvent e) {
if (mTestContainerRadioButton.getSelection()) {
testModeChanged();
}
}
public void widgetDefaultSelected(SelectionEvent e) {
}
});
mContainerText = new Text(comp, SWT.SINGLE | SWT.BORDER | SWT.READ_ONLY);
gd = new GridData(GridData.FILL_HORIZONTAL);
gd.horizontalIndent = 25;
gd.horizontalSpan = 2;
mContainerText.setLayoutData(gd);
mContainerText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent evt) {
updateLaunchConfigurationDialog();
}
});
mContainerSearchButton = new Button(comp, SWT.PUSH);
mContainerSearchButton.setText(JUnitMessages.JUnitLaunchConfigurationTab_label_search);
mContainerSearchButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent evt) {
handleContainerSearchButtonSelected();
}
});
setButtonGridData(mContainerSearchButton);
}
private void createInstrumentationGroup(Composite comp) {
Label loaderLabel = new Label(comp, SWT.NONE);
loaderLabel.setText("Instrumentation runner:");
GridData gd = new GridData();
gd.horizontalIndent = 0;
loaderLabel.setLayoutData(gd);
mInstrumentationCombo = new Combo(comp, SWT.DROP_DOWN | SWT.READ_ONLY);
gd = new GridData(GridData.FILL_HORIZONTAL);
mInstrumentationCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mInstrumentationCombo.clearSelection();
mInstrumentationCombo.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
validatePage();
updateLaunchConfigurationDialog();
}
});
}
private void handleContainerSearchButtonSelected() {
IJavaElement javaElement = chooseContainer(mContainerElement);
if (javaElement != null) {
setContainerElement(javaElement);
}
}
private void setContainerElement(IJavaElement javaElement) {
mContainerElement = javaElement;
mContainerText.setText(getPresentationName(javaElement));
validatePage();
updateLaunchConfigurationDialog();
}
/* (non-Javadoc)
* @see org.eclipse.debug.ui.ILaunchConfigurationTab#initializeFrom(org.eclipse.debug.core.ILaunchConfiguration)
*/
public void initializeFrom(ILaunchConfiguration config) {
String projectName = updateProjectFromConfig(config);
String containerHandle = EMPTY_STRING;
try {
containerHandle = config.getAttribute(
JUnitLaunchConfigurationConstants.ATTR_TEST_CONTAINER, EMPTY_STRING);
} catch (CoreException ce) {
// ignore
}
if (containerHandle.length() > 0) {
updateTestContainerFromConfig(config);
} else {
updateTestTypeFromConfig(config);
}
IProject proj = mProjectChooserHelper.getAndroidProject(projectName);
loadInstrumentations(proj);
updateInstrumentationFromConfig(config);
validatePage();
}
private void updateInstrumentationFromConfig(ILaunchConfiguration config) {
boolean found = false;
try {
String currentInstrumentation = config.getAttribute(
AndroidJUnitLaunchConfigDelegate.ATTR_INSTR_NAME, EMPTY_STRING);
if (mInstrumentations != null) {
// look for the name of the instrumentation in the combo.
for (int i = 0; i < mInstrumentations.length; i++) {
if (currentInstrumentation.equals(mInstrumentations[i])) {
found = true;
mInstrumentationCombo.select(i);
break;
}
}
}
} catch (CoreException ce) {
// ignore
}
if (!found) {
mInstrumentationCombo.clearSelection();
}
}
private String updateProjectFromConfig(ILaunchConfiguration config) {
String projectName = EMPTY_STRING;
try {
projectName = config.getAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME,
EMPTY_STRING);
} catch (CoreException ce) {
// ignore
}
mProjText.setText(projectName);
return projectName;
}
private void updateTestTypeFromConfig(ILaunchConfiguration config) {
String testTypeName = EMPTY_STRING;
mOriginalTestMethodName = EMPTY_STRING;
try {
testTypeName = config.getAttribute(
IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME, ""); //$NON-NLS-1$
mOriginalTestMethodName = config.getAttribute(
JUnitLaunchConfigurationConstants.ATTR_TEST_METHOD_NAME, ""); //$NON-NLS-1$
} catch (CoreException ce) {
// ignore
}
mTestRadioButton.setSelection(true);
setEnableSingleTestGroup(true);
setEnableContainerTestGroup(false);
mTestContainerRadioButton.setSelection(false);
mTestText.setText(testTypeName);
mContainerText.setText(EMPTY_STRING);
setTestMethodLabel(mOriginalTestMethodName);
}
private void setTestMethodLabel(String testMethodName) {
if (!EMPTY_STRING.equals(testMethodName)) {
mTestMethodLabel.setText(
JUnitMessages.JUnitLaunchConfigurationTab_label_method +
mOriginalTestMethodName);
} else {
mTestMethodLabel.setText(EMPTY_STRING);
}
}
private void updateTestContainerFromConfig(ILaunchConfiguration config) {
String containerHandle = EMPTY_STRING;
IJavaElement containerElement = null;
try {
containerHandle = config.getAttribute(
JUnitLaunchConfigurationConstants.ATTR_TEST_CONTAINER, EMPTY_STRING);
if (containerHandle.length() > 0) {
containerElement = JavaCore.create(containerHandle);
}
} catch (CoreException ce) {
// ignore
}
if (containerElement != null) {
mContainerElement = containerElement;
}
mTestContainerRadioButton.setSelection(true);
setEnableSingleTestGroup(false);
setEnableContainerTestGroup(true);
mTestRadioButton.setSelection(false);
if (mContainerElement != null) {
mContainerText.setText(getPresentationName(mContainerElement));
}
mTestText.setText(EMPTY_STRING);
}
/*
* (non-Javadoc)
* @see org.eclipse.debug.ui.ILaunchConfigurationTab#performApply(org.eclipse.debug.core.ILaunchConfigurationWorkingCopy)
*/
public void performApply(ILaunchConfigurationWorkingCopy config) {
if (mTestContainerRadioButton.getSelection() && mContainerElement != null) {
config.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME,
mContainerElement.getJavaProject().getElementName());
config.setAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_CONTAINER,
mContainerElement.getHandleIdentifier());
config.setAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME,
EMPTY_STRING);
//workaround for Eclipse bug 65399
config.setAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_METHOD_NAME,
EMPTY_STRING);
} else {
config.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME,
mProjText.getText());
config.setAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME,
mTestText.getText());
config.setAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_CONTAINER,
EMPTY_STRING);
config.setAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_METHOD_NAME,
mOriginalTestMethodName);
}
try {
mapResources(config);
} catch (CoreException e) {
// TODO: does the real error need to be extracted out of CoreException
AdtPlugin.log(e, "Error occurred saving configuration");
}
AndroidJUnitLaunchConfigDelegate.setJUnitDefaults(config);
config.setAttribute(AndroidJUnitLaunchConfigDelegate.ATTR_INSTR_NAME,
getSelectedInstrumentation());
}
private void mapResources(ILaunchConfigurationWorkingCopy config) throws CoreException {
JUnitMigrationDelegate.mapResources(config);
}
/* (non-Javadoc)
* @see org.eclipse.debug.ui.AbstractLaunchConfigurationTab#dispose()
*/
@Override
public void dispose() {
super.dispose();
if (mTabIcon != null) {
mTabIcon.dispose();
mTabIcon = null;
}
mJavaElementLabelProvider.dispose();
}
/* (non-Javadoc)
* @see org.eclipse.debug.ui.AbstractLaunchConfigurationTab#getImage()
*/
@Override
public Image getImage() {
// reuse icon from the Android App Launch config tab
if (mTabIcon == null) {
mTabIcon= AdtPlugin.getImageLoader().loadImage(MainLaunchConfigTab.LAUNCH_TAB_IMAGE,
null);
}
return mTabIcon;
}
/**
* Show a dialog that lists all main types
*/
private void handleSearchButtonSelected() {
Shell shell = getShell();
IJavaProject javaProject = getJavaProject();
IType[] types = new IType[0];
boolean[] radioSetting = new boolean[2];
try {
// fix for Eclipse bug 66922 Wrong radio behaviour when switching
// remember the selected radio button
radioSetting[0] = mTestRadioButton.getSelection();
radioSetting[1] = mTestContainerRadioButton.getSelection();
types = TestSearchEngine.findTests(getLaunchConfigurationDialog(), javaProject,
getTestKind());
} catch (InterruptedException e) {
setErrorMessage(e.getMessage());
return;
} catch (InvocationTargetException e) {
AdtPlugin.log(e.getTargetException(), "Error finding test types");
return;
} finally {
mTestRadioButton.setSelection(radioSetting[0]);
mTestContainerRadioButton.setSelection(radioSetting[1]);
}
SelectionDialog dialog = new TestSelectionDialog(shell, types);
dialog.setTitle(JUnitMessages.JUnitLaunchConfigurationTab_testdialog_title);
dialog.setMessage(JUnitMessages.JUnitLaunchConfigurationTab_testdialog_message);
if (dialog.open() == Window.CANCEL) {
return;
}
Object[] results = dialog.getResult();
if ((results == null) || (results.length < 1)) {
return;
}
IType type = (IType) results[0];
if (type != null) {
mTestText.setText(type.getFullyQualifiedName('.'));
javaProject = type.getJavaProject();
mProjText.setText(javaProject.getElementName());
}
}
private ITestKind getTestKind() {
// harddcode this to JUnit 3
return TestKindRegistry.getDefault().getKind(TestKindRegistry.JUNIT3_TEST_KIND_ID);
}
/**
* Show a dialog that lets the user select a Android project. This in turn provides
* context for the main type, allowing the user to key a main type name, or
* constraining the search for main types to the specified project.
*/
private void handleProjectButtonSelected() {
IJavaProject project = mProjectChooserHelper.chooseJavaProject(getProjectName());
if (project == null) {
return;
}
String projectName = project.getElementName();
mProjText.setText(projectName);
loadInstrumentations(project.getProject());
}
/**
* Return the IJavaProject corresponding to the project name in the project name
* text field, or null if the text does not match a Android project name.
*/
private IJavaProject getJavaProject() {
String projectName = getProjectName();
return getJavaModel().getJavaProject(projectName);
}
/**
* Returns the name of the currently specified project. Null if no project is selected.
*/
private String getProjectName() {
String projectName = mProjText.getText().trim();
if (projectName.length() < 1) {
return null;
}
return projectName;
}
/**
* Convenience method to get the workspace root.
*/
private IWorkspaceRoot getWorkspaceRoot() {
return ResourcesPlugin.getWorkspace().getRoot();
}
/**
* Convenience method to get access to the java model.
*/
private IJavaModel getJavaModel() {
return JavaCore.create(getWorkspaceRoot());
}
/* (non-Javadoc)
* @see org.eclipse.debug.ui.AbstractLaunchConfigurationTab#isValid(org.eclipse.debug.core.ILaunchConfiguration)
*/
@Override
public boolean isValid(ILaunchConfiguration config) {
validatePage();
return getErrorMessage() == null;
}
private void testModeChanged() {
boolean isSingleTestMode = mTestRadioButton.getSelection();
setEnableSingleTestGroup(isSingleTestMode);
setEnableContainerTestGroup(!isSingleTestMode);
if (!isSingleTestMode && mContainerText.getText().length() == 0) {
String projText = mProjText.getText();
if (Path.EMPTY.isValidSegment(projText)) {
IJavaProject javaProject = getJavaModel().getJavaProject(projText);
if (javaProject != null && javaProject.exists()) {
setContainerElement(javaProject);
}
}
}
validatePage();
updateLaunchConfigurationDialog();
}
private void validatePage() {
setErrorMessage(null);
setMessage(null);
if (mTestContainerRadioButton.getSelection()) {
if (mContainerElement == null) {
setErrorMessage(JUnitMessages.JUnitLaunchConfigurationTab_error_noContainer);
return;
}
validateJavaProject(mContainerElement.getJavaProject());
return;
}
String projectName = mProjText.getText().trim();
if (projectName.length() == 0) {
setErrorMessage(JUnitMessages.JUnitLaunchConfigurationTab_error_projectnotdefined);
return;
}
IStatus status = ResourcesPlugin.getWorkspace().validatePath(IPath.SEPARATOR + projectName,
IResource.PROJECT);
if (!status.isOK() || !Path.ROOT.isValidSegment(projectName)) {
setErrorMessage(Messages.format(
JUnitMessages.JUnitLaunchConfigurationTab_error_invalidProjectName,
projectName));
return;
}
IProject project = getWorkspaceRoot().getProject(projectName);
if (!project.exists()) {
setErrorMessage(JUnitMessages.JUnitLaunchConfigurationTab_error_projectnotexists);
return;
}
IJavaProject javaProject = JavaCore.create(project);
validateJavaProject(javaProject);
try {
if (!project.hasNature(AndroidConstants.NATURE)) {
setErrorMessage("Specified project is not an Android project");
return;
}
String className = mTestText.getText().trim();
if (className.length() == 0) {
setErrorMessage(JUnitMessages.JUnitLaunchConfigurationTab_error_testnotdefined);
return;
}
if (javaProject.findType(className) == null) {
setErrorMessage(Messages.format(
JUnitMessages.JUnitLaunchConfigurationTab_error_test_class_not_found,
new String[] { className, projectName }));
return;
}
} catch (CoreException e) {
AdtPlugin.log(e, "validatePage failed");
}
validateInstrumentation(javaProject);
}
private void validateJavaProject(IJavaProject javaProject) {
if (!TestSearchEngine.hasTestCaseType(javaProject)) {
setErrorMessage(JUnitMessages.JUnitLaunchConfigurationTab_error_testcasenotonpath);
return;
}
}
private void validateInstrumentation(IJavaProject javaProject) {
if (mInstrumentations == null || mInstrumentations.length < 1) {
setErrorMessage("Specified project has no defined instrumentations");
}
String instrumentation = getSelectedInstrumentation();
if (instrumentation == null) {
setErrorMessage("Instrumentation not specified");
}
}
private String getSelectedInstrumentation() {
int selectionIndex = mInstrumentationCombo.getSelectionIndex();
if (mInstrumentations != null && selectionIndex >= 0 &&
selectionIndex < mInstrumentations.length) {
return mInstrumentations[selectionIndex];
}
return null;
}
private void setEnableContainerTestGroup(boolean enabled) {
mContainerSearchButton.setEnabled(enabled);
mContainerText.setEnabled(enabled);
}
private void setEnableSingleTestGroup(boolean enabled) {
mProjLabel.setEnabled(enabled);
mProjText.setEnabled(enabled);
mProjButton.setEnabled(enabled);
mTestLabel.setEnabled(enabled);
mTestText.setEnabled(enabled);
mSearchButton.setEnabled(enabled && mProjText.getText().length() > 0);
mTestMethodLabel.setEnabled(enabled);
}
/* (non-Javadoc)
* @see org.eclipse.debug.ui.ILaunchConfigurationTab#setDefaults(org.eclipse.debug.core.ILaunchConfigurationWorkingCopy)
*/
public void setDefaults(ILaunchConfigurationWorkingCopy config) {
IJavaElement javaElement = getContext();
if (javaElement != null) {
initializeJavaProject(javaElement, config);
} else {
// We set empty attributes for project & main type so that when one config is
// compared to another, the existence of empty attributes doesn't cause an
// incorrect result (the performApply() method can result in empty values
// for these attributes being set on a config if there is nothing in the
// corresponding text boxes)
config.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, EMPTY_STRING);
config.setAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_CONTAINER,
EMPTY_STRING);
}
initializeTestAttributes(javaElement, config);
}
private void initializeTestAttributes(IJavaElement javaElement,
ILaunchConfigurationWorkingCopy config) {
if (javaElement != null && javaElement.getElementType() < IJavaElement.COMPILATION_UNIT) {
initializeTestContainer(javaElement, config);
} else {
initializeTestType(javaElement, config);
}
}
private void initializeTestContainer(IJavaElement javaElement,
ILaunchConfigurationWorkingCopy config) {
config.setAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_CONTAINER,
javaElement.getHandleIdentifier());
initializeName(config, javaElement.getElementName());
}
private void initializeName(ILaunchConfigurationWorkingCopy config, String name) {
if (name == null) {
name = EMPTY_STRING;
}
if (name.length() > 0) {
int index = name.lastIndexOf('.');
if (index > 0) {
name = name.substring(index + 1);
}
name = getLaunchConfigurationDialog().generateName(name);
config.rename(name);
}
}
/**
* Sets the main type & name attributes on the working copy based on the IJavaElement
*/
private void initializeTestType(IJavaElement javaElement,
ILaunchConfigurationWorkingCopy config) {
String name = EMPTY_STRING;
String testKindId = null;
try {
// only do a search for compilation units or class files or source references
if (javaElement instanceof ISourceReference) {
ITestKind testKind = TestKindRegistry.getContainerTestKind(javaElement);
testKindId = testKind.getId();
IType[] types = TestSearchEngine.findTests(getLaunchConfigurationDialog(),
javaElement, testKind);
if ((types == null) || (types.length < 1)) {
return;
}
// Simply grab the first main type found in the searched element
name = types[0].getFullyQualifiedName('.');
}
} catch (InterruptedException ie) {
// ignore
} catch (InvocationTargetException ite) {
// ignore
}
config.setAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME, name);
if (testKindId != null) {
config.setAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_RUNNER_KIND,
testKindId);
}
initializeName(config, name);
}
/* (non-Javadoc)
* @see org.eclipse.debug.ui.ILaunchConfigurationTab#getName()
*/
public String getName() {
return JUnitMessages.JUnitLaunchConfigurationTab_tab_label;
}
@SuppressWarnings("unchecked")
private IJavaElement chooseContainer(IJavaElement initElement) {
Class[] acceptedClasses = new Class[] { IPackageFragmentRoot.class, IJavaProject.class,
IPackageFragment.class };
TypedElementSelectionValidator validator = new TypedElementSelectionValidator(
acceptedClasses, false) {
@Override
public boolean isSelectedValid(Object element) {
return true;
}
};
acceptedClasses = new Class[] { IJavaModel.class, IPackageFragmentRoot.class,
IJavaProject.class, IPackageFragment.class };
ViewerFilter filter = new TypedViewerFilter(acceptedClasses) {
@Override
public boolean select(Viewer viewer, Object parent, Object element) {
if (element instanceof IPackageFragmentRoot &&
((IPackageFragmentRoot) element).isArchive()) {
return false;
}
try {
if (element instanceof IPackageFragment &&
!((IPackageFragment) element).hasChildren()) {
return false;
}
} catch (JavaModelException e) {
return false;
}
return super.select(viewer, parent, element);
}
};
StandardJavaElementContentProvider provider = new StandardJavaElementContentProvider();
ILabelProvider labelProvider = new JavaElementLabelProvider(
JavaElementLabelProvider.SHOW_DEFAULT);
ElementTreeSelectionDialog dialog = new ElementTreeSelectionDialog(getShell(),
labelProvider, provider);
dialog.setValidator(validator);
dialog.setComparator(new JavaElementComparator());
dialog.setTitle(JUnitMessages.JUnitLaunchConfigurationTab_folderdialog_title);
dialog.setMessage(JUnitMessages.JUnitLaunchConfigurationTab_folderdialog_message);
dialog.addFilter(filter);
dialog.setInput(JavaCore.create(getWorkspaceRoot()));
dialog.setInitialSelection(initElement);
dialog.setAllowMultiple(false);
if (dialog.open() == Window.OK) {
Object element = dialog.getFirstResult();
return (IJavaElement) element;
}
return null;
}
private String getPresentationName(IJavaElement element) {
return mJavaElementLabelProvider.getText(element);
}
/**
* Returns the current Java element context from which to initialize
* default settings, or <code>null</code> if none.
*
* @return Java element context.
*/
private IJavaElement getContext() {
IWorkbenchWindow activeWorkbenchWindow =
PlatformUI.getWorkbench().getActiveWorkbenchWindow();
if (activeWorkbenchWindow == null) {
return null;
}
IWorkbenchPage page = activeWorkbenchWindow.getActivePage();
if (page != null) {
ISelection selection = page.getSelection();
if (selection instanceof IStructuredSelection) {
IStructuredSelection ss = (IStructuredSelection) selection;
if (!ss.isEmpty()) {
Object obj = ss.getFirstElement();
if (obj instanceof IJavaElement) {
return (IJavaElement) obj;
}
if (obj instanceof IResource) {
IJavaElement je = JavaCore.create((IResource) obj);
if (je == null) {
IProject pro = ((IResource) obj).getProject();
je = JavaCore.create(pro);
}
if (je != null) {
return je;
}
}
}
}
IEditorPart part = page.getActiveEditor();
if (part != null) {
IEditorInput input = part.getEditorInput();
return (IJavaElement) input.getAdapter(IJavaElement.class);
}
}
return null;
}
private void initializeJavaProject(IJavaElement javaElement,
ILaunchConfigurationWorkingCopy config) {
IJavaProject javaProject = javaElement.getJavaProject();
String name = null;
if (javaProject != null && javaProject.exists()) {
name = javaProject.getElementName();
}
config.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, name);
}
private void setButtonGridData(Button button) {
GridData gridData = new GridData();
button.setLayoutData(gridData);
LayoutUtil.setButtonDimensionHint(button);
}
/* (non-Javadoc)
* @see org.eclipse.debug.ui.AbstractLaunchConfigurationTab#getId()
*/
@Override
public String getId() {
return "com.android.ide.eclipse.adt.launch.AndroidJUnitLaunchConfigurationTab"; //$NON-NLS-1$
}
/**
* Loads the UI with the instrumentations of the specified project, and stores the
* activities in <code>mActivities</code>.
* <p/>
* First activity is selected by default if present.
*
* @param project the {@link IProject} to load the instrumentations from.
*/
private void loadInstrumentations(IProject project) {
mInstrumentations = AndroidJUnitLaunchConfigDelegate.getInstrumentationsForProject(project);
if (mInstrumentations != null) {
mInstrumentationCombo.removeAll();
for (String instrumentation : mInstrumentations) {
mInstrumentationCombo.add(instrumentation);
}
// the selection will be set when we update the ui from the current
// config object.
return;
}
// if we reach this point, either project is null, or we got an exception during
// the parsing. In either case, we empty the instrumentation list.
mInstrumentations = null;
mInstrumentationCombo.removeAll();
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
*
* 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.ide.eclipse.adt.launch.junit;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.junit.launcher.JUnitLaunchShortcut;
/**
* Launch shortcut to launch debug/run Android JUnit configuration directly.
*/
public class AndroidJUnitLaunchShortcut extends JUnitLaunchShortcut {
@Override
protected String getLaunchConfigurationTypeId() {
return "com.android.ide.eclipse.adt.junit.launchConfigurationType"; //$NON-NLS-1$
}
/**
* Creates a default Android JUnit launch configuration. Sets the instrumentation runner to the
* first instrumentation found in the AndroidManifest.
*/
@Override
protected ILaunchConfigurationWorkingCopy createLaunchConfiguration(IJavaElement element)
throws CoreException {
ILaunchConfigurationWorkingCopy config = super.createLaunchConfiguration(element);
IProject project = element.getResource().getProject();
String[] instrumentations =
AndroidJUnitLaunchConfigDelegate.getInstrumentationsForProject(project);
if (instrumentations != null && instrumentations.length > 0) {
// just pick the first runner
config.setAttribute(AndroidJUnitLaunchConfigDelegate.ATTR_INSTR_NAME,
instrumentations[0]);
}
AndroidJUnitLaunchConfigDelegate.setJUnitDefaults(config);
return config;
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
*
* 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.ide.eclipse.adt.launch.junit;
import org.eclipse.debug.ui.AbstractLaunchConfigurationTabGroup;
import org.eclipse.debug.ui.CommonTab;
import org.eclipse.debug.ui.ILaunchConfigurationDialog;
import org.eclipse.debug.ui.ILaunchConfigurationTab;
import com.android.ide.eclipse.adt.launch.EmulatorConfigTab;
/**
* Tab group object for Android JUnit launch configuration type.
*/
public class AndroidJUnitTabGroup extends AbstractLaunchConfigurationTabGroup {
/**
* Creates the UI tabs for the Android JUnit configuration
*/
public void createTabs(ILaunchConfigurationDialog dialog, String mode) {
ILaunchConfigurationTab[] tabs = new ILaunchConfigurationTab[] {
new AndroidJUnitLaunchConfigurationTab(),
new EmulatorConfigTab(),
new CommonTab()
};
setTabs(tabs);
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
*
* 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.ide.eclipse.adt.launch.junit.runtime;
import org.eclipse.core.resources.IProject;
import com.android.ddmlib.IDevice;
/**
* Contains info about Android JUnit launch
*/
public class AndroidJUnitLaunchInfo {
private final IProject mProject;
private final String mTestPackage;
private final String mRunner;
private final boolean mDebugMode;
private final IDevice mDevice;
public AndroidJUnitLaunchInfo(IProject project, String testPackage, String runner,
boolean debugMode, IDevice device) {
mProject = project;
mTestPackage = testPackage;
mRunner = runner;
mDebugMode = debugMode;
mDevice = device;
}
public IProject getProject() {
return mProject;
}
public String getTestPackage() {
return mTestPackage;
}
public String getRunner() {
return mRunner;
}
public boolean isDebugMode() {
return mDebugMode;
}
public IDevice getDevice() {
return mDevice;
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
*
* 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.ide.eclipse.adt.launch.junit.runtime;
import org.eclipse.jdt.internal.junit.runner.ITestIdentifier;
import org.eclipse.jdt.internal.junit.runner.ITestReference;
import org.eclipse.jdt.internal.junit.runner.TestExecution;
/**
* Base implementation of the Eclipse {@link ITestReference} and {@link ITestIdentifier} interfaces
* for Android tests.
* <p/>
* Provides generic equality/hashcode services
*/
@SuppressWarnings("restriction") //$NON-NLS-1$
abstract class AndroidTestReference implements ITestReference, ITestIdentifier {
/**
* Gets the {@link ITestIdentifier} for this test reference.
*/
public ITestIdentifier getIdentifier() {
// this class serves as its own test identifier
return this;
}
/**
* Not supported.
*/
public void run(TestExecution execution) {
throw new UnsupportedOperationException();
}
/**
* Compares {@link ITestIdentifier} using names
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof ITestIdentifier) {
ITestIdentifier testid = (ITestIdentifier) obj;
return getName().equals(testid.getName());
}
return false;
}
@Override
public int hashCode() {
return getName().hashCode();
}
}

View File

@@ -0,0 +1,220 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
*
* 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.ide.eclipse.adt.launch.junit.runtime;
import com.android.ddmlib.testrunner.ITestRunListener;
import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
import com.android.ddmlib.testrunner.TestIdentifier;
import com.android.ide.eclipse.adt.AdtPlugin;
import org.eclipse.jdt.internal.junit.runner.MessageIds;
import org.eclipse.jdt.internal.junit.runner.RemoteTestRunner;
import org.eclipse.jdt.internal.junit.runner.TestExecution;
import org.eclipse.jdt.internal.junit.runner.TestReferenceFailure;
/**
* Supports Eclipse JUnit execution of Android tests.
* <p/>
* Communicates back to a Eclipse JDT JUnit client via a socket connection.
*
* @see org.eclipse.jdt.internal.junit.runner.RemoteTestRunner for more details on the protocol
*/
@SuppressWarnings("restriction")
public class RemoteADTTestRunner extends RemoteTestRunner {
private AndroidJUnitLaunchInfo mLaunchInfo;
private TestExecution mExecution;
/**
* Initialize the JDT JUnit test runner parameters from the {@code args}.
*
* @param args name-value pair of arguments to pass to parent JUnit runner.
* @param launchInfo the Android specific test launch info
*/
protected void init(String[] args, AndroidJUnitLaunchInfo launchInfo) {
defaultInit(args);
mLaunchInfo = launchInfo;
}
/**
* Runs a set of tests, and reports back results using parent class.
* <p/>
* JDT Unit expects to be sent data in the following sequence:
* <ol>
* <li>The total number of tests to be executed.</li>
* <li>The test 'tree' data about the tests to be executed, which is composed of the set of
* test class names, the number of tests in each class, and the names of each test in the
* class.</li>
* <li>The test execution result for each test method. Expects individual notifications of
* the test execution start, any failures, and the end of the test execution.</li>
* <li>The end of the test run, with its elapsed time.</li>
* </ol>
* <p/>
* In order to satisfy this, this method performs two actual Android instrumentation runs.
* The first is a 'log only' run that will collect the test tree data, without actually
* executing the tests, and send it back to JDT JUnit. The second is the actual test execution,
* whose results will be communicated back in real-time to JDT JUnit.
*
* @param testClassNames array of fully qualified test class names to execute. Cannot be empty.
* @param testName test to execute. If null, will be ignored.
* @param execution used to report test progress
*/
@Override
public void runTests(String[] testClassNames, String testName, TestExecution execution) {
// hold onto this execution reference so it can be used to report test progress
mExecution = execution;
RemoteAndroidTestRunner runner = new RemoteAndroidTestRunner(mLaunchInfo.getTestPackage(),
mLaunchInfo.getRunner(), mLaunchInfo.getDevice());
if (testClassNames != null && testClassNames.length > 0) {
if (testName != null) {
runner.setMethodName(testClassNames[0], testName);
} else {
runner.setClassNames(testClassNames);
}
}
// set log only to first collect test case info, so Eclipse has correct test case count/
// tree info
runner.setLogOnly(true);
TestCollector collector = new TestCollector();
runner.run(collector);
if (collector.getErrorMessage() != null) {
// error occurred during test collection.
reportError(collector.getErrorMessage());
// abort here
}
notifyTestRunStarted(collector.getTestCaseCount());
collector.sendTrees(this);
// now do real execution
runner.setLogOnly(false);
if (mLaunchInfo.isDebugMode()) {
runner.setDebug(true);
}
runner.run(new TestRunListener());
}
/**
* Main entry method to run tests
*
* @param programArgs JDT JUnit program arguments to be processed by parent
* @param junitInfo the {@link AndroidJUnitLaunchInfo} containing info about this test ru
*/
public void runTests(String[] programArgs, AndroidJUnitLaunchInfo junitInfo) {
init(programArgs, junitInfo);
run();
}
/**
* Stop the current test run.
*/
public void terminate() {
stop();
}
@Override
protected void stop() {
if (mExecution != null) {
mExecution.stop();
}
}
private void notifyTestRunEnded(long elapsedTime) {
// copy from parent - not ideal, but method is private
sendMessage(MessageIds.TEST_RUN_END + elapsedTime);
flush();
//shutDown();
}
/**
* @param errorMessage
*/
private void reportError(String errorMessage) {
AdtPlugin.printErrorToConsole(mLaunchInfo.getProject(),
String.format("Test run failed: %s", errorMessage));
// is this needed?
//notifyTestRunStopped(-1);
}
/**
* TestRunListener that communicates results in real-time back to JDT JUnit
*/
private class TestRunListener implements ITestRunListener {
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testEnded(com.android.ddmlib.testrunner.TestIdentifier)
*/
public void testEnded(TestIdentifier test) {
mExecution.getListener().notifyTestEnded(new TestCaseReference(test));
}
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testFailed(com.android.ddmlib.testrunner.ITestRunListener.TestFailure, com.android.ddmlib.testrunner.TestIdentifier, java.lang.String)
*/
public void testFailed(TestFailure status, TestIdentifier test, String trace) {
String statusString;
if (status == TestFailure.ERROR) {
statusString = MessageIds.TEST_ERROR;
} else {
statusString = MessageIds.TEST_FAILED;
}
TestReferenceFailure failure =
new TestReferenceFailure(new TestCaseReference(test),
statusString, trace, null);
mExecution.getListener().notifyTestFailed(failure);
}
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testRunEnded(long)
*/
public void testRunEnded(long elapsedTime) {
notifyTestRunEnded(elapsedTime);
AdtPlugin.printToConsole(mLaunchInfo.getProject(), "Test run complete");
}
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testRunFailed(java.lang.String)
*/
public void testRunFailed(String errorMessage) {
reportError(errorMessage);
}
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testRunStarted(int)
*/
public void testRunStarted(int testCount) {
// ignore
}
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testRunStopped(long)
*/
public void testRunStopped(long elapsedTime) {
notifyTestRunStopped(elapsedTime);
AdtPlugin.printToConsole(mLaunchInfo.getProject(), "Test run stopped");
}
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testStarted(com.android.ddmlib.testrunner.TestIdentifier)
*/
public void testStarted(TestIdentifier test) {
TestCaseReference testId = new TestCaseReference(test);
mExecution.getListener().notifyTestStarted(testId);
}
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
*
* 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.ide.eclipse.adt.launch.junit.runtime;
import com.android.ddmlib.testrunner.TestIdentifier;
import org.eclipse.jdt.internal.junit.runner.IVisitsTestTrees;
import org.eclipse.jdt.internal.junit.runner.MessageIds;
import java.text.MessageFormat;
/**
* Reference for a single Android test method.
*/
@SuppressWarnings("restriction")
class TestCaseReference extends AndroidTestReference {
private final String mClassName;
private final String mTestName;
/**
* Creates a TestCaseReference from a class and method name
*/
TestCaseReference(String className, String testName) {
mClassName = className;
mTestName = testName;
}
/**
* Creates a TestCaseReference from a {@link TestIdentifier}
* @param test
*/
TestCaseReference(TestIdentifier test) {
mClassName = test.getClassName();
mTestName = test.getTestName();
}
/**
* Returns a count of the number of test cases referenced. Is always one for this class.
*/
public int countTestCases() {
return 1;
}
/**
* Sends test identifier and test count information for this test
*
* @param notified the {@link IVisitsTestTrees} to send test info to
*/
public void sendTree(IVisitsTestTrees notified) {
notified.visitTreeEntry(getIdentifier(), false, countTestCases());
}
/**
* Returns the identifier of this test, in a format expected by JDT JUnit
*/
public String getName() {
return MessageFormat.format(MessageIds.TEST_IDENTIFIER_MESSAGE_FORMAT,
new Object[] { mTestName, mClassName});
}
}

View File

@@ -0,0 +1,124 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
*
* 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.ide.eclipse.adt.launch.junit.runtime;
import com.android.ddmlib.testrunner.ITestRunListener;
import com.android.ddmlib.testrunner.TestIdentifier;
import org.eclipse.jdt.internal.junit.runner.ITestReference;
import org.eclipse.jdt.internal.junit.runner.IVisitsTestTrees;
import java.util.HashMap;
import java.util.Map;
/**
* Collects info about tests to be executed by listening to the results of an Android test run.
*/
@SuppressWarnings("restriction")
class TestCollector implements ITestRunListener {
private int mTotalTestCount;
/** test name to test suite reference map. */
private Map<String, TestSuiteReference> mTestTree;
private String mErrorMessage = null;
TestCollector() {
mTotalTestCount = 0;
mTestTree = new HashMap<String, TestSuiteReference>();
}
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testEnded(com.android.ddmlib.testrunner.TestIdentifier)
*/
public void testEnded(TestIdentifier test) {
// ignore
}
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testFailed(com.android.ddmlib.testrunner.ITestRunListener.TestFailure, com.android.ddmlib.testrunner.TestIdentifier, java.lang.String)
*/
public void testFailed(TestFailure status, TestIdentifier test, String trace) {
// ignore - should be impossible since this is only collecting test information
}
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testRunEnded(long)
*/
public void testRunEnded(long elapsedTime) {
// ignore
}
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testRunFailed(java.lang.String)
*/
public void testRunFailed(String errorMessage) {
mErrorMessage = errorMessage;
}
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testRunStarted(int)
*/
public void testRunStarted(int testCount) {
mTotalTestCount = testCount;
}
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testRunStopped(long)
*/
public void testRunStopped(long elapsedTime) {
// ignore
}
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testStarted(com.android.ddmlib.testrunner.TestIdentifier)
*/
public void testStarted(TestIdentifier test) {
TestSuiteReference suiteRef = mTestTree.get(test.getClassName());
if (suiteRef == null) {
// this test suite has not been seen before, create it
suiteRef = new TestSuiteReference(test.getClassName());
mTestTree.put(test.getClassName(), suiteRef);
}
suiteRef.addTest(new TestCaseReference(test));
}
/**
* Returns the total test count in the test run.
*/
public int getTestCaseCount() {
return mTotalTestCount;
}
/**
* Sends info about the test tree to be executed (ie the suites and their enclosed tests)
*
* @param notified the {@link IVisitsTestTrees} to send test data to
*/
public void sendTrees(IVisitsTestTrees notified) {
for (ITestReference ref : mTestTree.values()) {
ref.sendTree(notified);
}
}
/**
* Returns the error message that was reported when collecting test info.
* Returns <code>null</code> if no error occurred.
*/
public String getErrorMessage() {
return mErrorMessage;
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
*
* 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.ide.eclipse.adt.launch.junit.runtime;
import org.eclipse.jdt.internal.junit.runner.IVisitsTestTrees;
import java.util.List;
import java.util.ArrayList;
/**
* Reference for an Android test suite aka class.
*/
@SuppressWarnings("restriction")
class TestSuiteReference extends AndroidTestReference {
private final String mClassName;
private List<TestCaseReference> mTests;
/**
* Creates a TestSuiteReference
*
* @param className the fully qualified name of the test class
*/
TestSuiteReference(String className) {
mClassName = className;
mTests = new ArrayList<TestCaseReference>();
}
/**
* Returns a count of the number of test cases included in this suite.
*/
public int countTestCases() {
return mTests.size();
}
/**
* Sends test identifier and test count information for this test class, and all its included
* test methods.
*
* @param notified the {@link IVisitsTestTrees} to send test info too
*/
public void sendTree(IVisitsTestTrees notified) {
notified.visitTreeEntry(getIdentifier(), true, countTestCases());
for (TestCaseReference ref : mTests) {
ref.sendTree(notified);
}
}
/**
* Return the name of this test class.
*/
public String getName() {
return mClassName;
}
/**
* Adds a test method to this suite.
*
* @param testRef the {@link TestCaseReference} to add
*/
void addTest(TestCaseReference testRef) {
mTests.add(testRef);
}
}

View File

@@ -487,6 +487,15 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit
IJavaProject javaProject = projects.get(i); IJavaProject javaProject = projects.get(i);
IProject iProject = javaProject.getProject(); IProject iProject = javaProject.getProject();
// check if the project is opened
if (iProject.isOpen() == false) {
// remove from the list
// we do not increment i in this case.
projects.remove(i);
continue;
}
// get the target from the project and its paths // get the target from the project and its paths
IAndroidTarget target = Sdk.getCurrent().getTarget(javaProject.getProject()); IAndroidTarget target = Sdk.getCurrent().getTarget(javaProject.getProject());
if (target == null) { if (target == null) {

View File

@@ -829,7 +829,7 @@ public class NewProjectCreationPage extends WizardPage {
String packageName = null; String packageName = null;
String activityName = null; String activityName = null;
int minSdkVersion = 0; // 0 means no minSdkVersion provided in the manifest int minSdkVersion = AndroidManifestParser.INVALID_MIN_SDK;
try { try {
packageName = manifestData.getPackage(); packageName = manifestData.getPackage();
minSdkVersion = manifestData.getApiLevelRequirement(); minSdkVersion = manifestData.getApiLevelRequirement();
@@ -927,7 +927,7 @@ public class NewProjectCreationPage extends WizardPage {
} }
} }
if (!foundTarget && minSdkVersion > 0) { if (!foundTarget && minSdkVersion != AndroidManifestParser.INVALID_MIN_SDK) {
try { try {
for (IAndroidTarget target : mSdkTargetSelector.getTargets()) { for (IAndroidTarget target : mSdkTargetSelector.getTargets()) {
if (target.getApiVersionNumber() == minSdkVersion) { if (target.getApiVersionNumber() == minSdkVersion) {
@@ -954,7 +954,8 @@ public class NewProjectCreationPage extends WizardPage {
if (!foundTarget) { if (!foundTarget) {
mInternalMinSdkVersionUpdate = true; mInternalMinSdkVersionUpdate = true;
mMinSdkVersionField.setText( mMinSdkVersionField.setText(
minSdkVersion <= 0 ? "" : Integer.toString(minSdkVersion)); //$NON-NLS-1$ minSdkVersion == AndroidManifestParser.INVALID_MIN_SDK ? "" :
Integer.toString(minSdkVersion)); //$NON-NLS-1$
mInternalMinSdkVersionUpdate = false; mInternalMinSdkVersionUpdate = false;
} }
} }
@@ -1148,7 +1149,7 @@ public class NewProjectCreationPage extends WizardPage {
return MSG_NONE; return MSG_NONE;
} }
int version = -1; int version = AndroidManifestParser.INVALID_MIN_SDK;
try { try {
// If not empty, it must be a valid integer > 0 // If not empty, it must be a valid integer > 0
version = Integer.parseInt(getMinSdkVersion()); version = Integer.parseInt(getMinSdkVersion());

View File

@@ -72,6 +72,8 @@ public class AndroidManifestParser {
private final static String ACTION_MAIN = "android.intent.action.MAIN"; //$NON-NLS-1$ private final static String ACTION_MAIN = "android.intent.action.MAIN"; //$NON-NLS-1$
private final static String CATEGORY_LAUNCHER = "android.intent.category.LAUNCHER"; //$NON-NLS-1$ private final static String CATEGORY_LAUNCHER = "android.intent.category.LAUNCHER"; //$NON-NLS-1$
public final static int INVALID_MIN_SDK = -1;
/** /**
* XML error & data handler used when parsing the AndroidManifest.xml file. * XML error & data handler used when parsing the AndroidManifest.xml file.
* <p/> * <p/>
@@ -92,8 +94,9 @@ public class AndroidManifestParser {
private Set<String> mProcesses = null; private Set<String> mProcesses = null;
/** debuggable attribute value. If null, the attribute is not present. */ /** debuggable attribute value. If null, the attribute is not present. */
private Boolean mDebuggable = null; private Boolean mDebuggable = null;
/** API level requirement. if 0 the attribute was not present. */ /** API level requirement. if {@link AndroidManifestParser#INVALID_MIN_SDK}
private int mApiLevelRequirement = 0; * the attribute was not present. */
private int mApiLevelRequirement = INVALID_MIN_SDK;
/** List of all instrumentations declared by the manifest */ /** List of all instrumentations declared by the manifest */
private final ArrayList<String> mInstrumentations = new ArrayList<String>(); private final ArrayList<String> mInstrumentations = new ArrayList<String>();
/** List of all libraries in use declared by the manifest */ /** List of all libraries in use declared by the manifest */
@@ -171,7 +174,8 @@ public class AndroidManifestParser {
} }
/** /**
* Returns the <code>minSdkVersion</code> attribute, or 0 if it's not set. * Returns the <code>minSdkVersion</code> attribute, or
* {@link AndroidManifestParser#INVALID_MIN_SDK} if it's not set.
*/ */
int getApiLevelRequirement() { int getApiLevelRequirement() {
return mApiLevelRequirement; return mApiLevelRequirement;
@@ -750,7 +754,8 @@ public class AndroidManifestParser {
} }
/** /**
* Returns the <code>minSdkVersion</code> attribute, or 0 if it's not set. * Returns the <code>minSdkVersion</code> attribute, or {@link #INVALID_MIN_SDK}
* if it's not set.
*/ */
public int getApiLevelRequirement() { public int getApiLevelRequirement() {
return mApiLevelRequirement; return mApiLevelRequirement;

View File

@@ -16,8 +16,7 @@
package com.android.ide.eclipse.common.project; package com.android.ide.eclipse.common.project;
import com.android.ide.eclipse.common.project.BaseProjectHelper; import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.jdt.core.IJavaModel; import org.eclipse.jdt.core.IJavaModel;
@@ -82,7 +81,7 @@ public class ProjectChooserHelper {
// open the dialog and return the object selected if OK was clicked, or null otherwise // open the dialog and return the object selected if OK was clicked, or null otherwise
if (dialog.open() == Window.OK) { if (dialog.open() == Window.OK) {
return (IJavaProject)dialog.getFirstResult(); return (IJavaProject) dialog.getFirstResult();
} }
return null; return null;
} }
@@ -107,4 +106,24 @@ public class ProjectChooserHelper {
return mAndroidProjects; return mAndroidProjects;
} }
/**
* Helper method to get the Android project with the given name
*
* @param projectName the name of the project to find
* @return the {@link IProject} for the Android project. <code>null</code> if not found.
*/
public IProject getAndroidProject(String projectName) {
IProject iproject = null;
IJavaProject[] javaProjects = getAndroidProjects(null);
if (javaProjects != null) {
for (IJavaProject javaProject : javaProjects) {
if (javaProject.getElementName().equals(projectName)) {
iproject = javaProject.getProject();
break;
}
}
}
return iproject;
}
} }

View File

@@ -461,5 +461,13 @@ public class FileMock implements IFile {
public void setHidden(boolean isHidden) throws CoreException { public void setHidden(boolean isHidden) throws CoreException {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public boolean isHidden(int options) {
throw new NotImplementedException();
}
public boolean isTeamPrivateMember(int options) {
throw new NotImplementedException();
}
} }

View File

@@ -74,7 +74,8 @@ public final class FolderMock implements IFolder {
// -------- UNIMPLEMENTED METHODS ---------------- // -------- UNIMPLEMENTED METHODS ----------------
public void create(boolean force, boolean local, IProgressMonitor monitor) throws CoreException { public void create(boolean force, boolean local, IProgressMonitor monitor)
throws CoreException {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@@ -106,8 +107,8 @@ public final class FolderMock implements IFolder {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public void move(IPath destination, boolean force, boolean keepHistory, IProgressMonitor monitor) public void move(IPath destination, boolean force, boolean keepHistory,
throws CoreException { IProgressMonitor monitor) throws CoreException {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@@ -225,7 +226,8 @@ public final class FolderMock implements IFolder {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public void deleteMarkers(String type, boolean includeSubtypes, int depth) throws CoreException { public void deleteMarkers(String type, boolean includeSubtypes, int depth)
throws CoreException {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@@ -428,24 +430,31 @@ public final class FolderMock implements IFolder {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public Map<?,?> getPersistentProperties() throws CoreException { public Map<?,?> getPersistentProperties() throws CoreException {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public Map<?,?> getSessionProperties() throws CoreException { public Map<?,?> getSessionProperties() throws CoreException {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public boolean isDerived(int options) { public boolean isDerived(int options) {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public boolean isHidden() { public boolean isHidden() {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public void setHidden(boolean isHidden) throws CoreException { public void setHidden(boolean isHidden) throws CoreException {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public boolean isHidden(int options) {
throw new NotImplementedException();
}
public boolean isTeamPrivateMember(int options) {
throw new NotImplementedException();
}
} }

View File

@@ -42,6 +42,14 @@ import sun.reflect.generics.reflectiveObjects.NotImplementedException;
import java.net.URI; import java.net.URI;
import java.util.Map; import java.util.Map;
/**
* Mock implementation of {@link IProject}.
* <p/>Supported methods:
* <ul>
* <li>{@link #build(int kind, IProgressMonitor monitor)}</li>
* <li>{@link #members(int kind, String builderName, Map args, IProgressMonitor monitor)}</li>
* </ul>
*/
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public class ProjectMock implements IProject { public class ProjectMock implements IProject {
@@ -265,7 +273,8 @@ public class ProjectMock implements IProject {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public void deleteMarkers(String type, boolean includeSubtypes, int depth) throws CoreException { public void deleteMarkers(String type, boolean includeSubtypes, int depth)
throws CoreException {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@@ -473,29 +482,36 @@ public class ProjectMock implements IProject {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public void create(IProjectDescription description, int updateFlags, public void create(IProjectDescription description, int updateFlags,
IProgressMonitor monitor) throws CoreException { IProgressMonitor monitor) throws CoreException {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public Map<?,?> getPersistentProperties() throws CoreException { public Map<?,?> getPersistentProperties() throws CoreException {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public Map<?,?> getSessionProperties() throws CoreException { public Map<?,?> getSessionProperties() throws CoreException {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public boolean isDerived(int options) { public boolean isDerived(int options) {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public boolean isHidden() { public boolean isHidden() {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public void setHidden(boolean isHidden) throws CoreException { public void setHidden(boolean isHidden) throws CoreException {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public boolean isHidden(int options) {
throw new NotImplementedException();
}
public boolean isTeamPrivateMember(int options) {
throw new NotImplementedException();
}
} }

View File

@@ -36,89 +36,99 @@ from.
__author__ = 'jmatt@google.com (Justin Mattson)' __author__ = 'jmatt@google.com (Justin Mattson)'
from optparse import OptionParser import optparse
import os import os
import stat import stat
import sys import sys
import zipfile import zipfile
from zipfile import ZipFile
import divide_and_compress_constants import divide_and_compress_constants
def Main(argv):
parser = CreateOptionsParser()
(options, args) = parser.parse_args()
VerifyArguments(options, parser)
zipper = DirectoryZipper(options.destination,
options.sourcefiles,
ParseSize(options.filesize),
options.compress)
zipper.StartCompress()
def CreateOptionsParser(): def CreateOptionsParser():
rtn = OptionParser() """Creates the parser for command line arguments.
Returns:
A configured optparse.OptionParser object.
"""
rtn = optparse.OptionParser()
rtn.add_option('-s', '--sourcefiles', dest='sourcefiles', default=None, rtn.add_option('-s', '--sourcefiles', dest='sourcefiles', default=None,
help='The directory containing the files to compress') help='The directory containing the files to compress')
rtn.add_option('-d', '--destination', dest='destination', default=None, rtn.add_option('-d', '--destination', dest='destination', default=None,
help=('Where to put the archive files, this should not be' help=('Where to put the archive files, this should not be'
' a child of where the source files exist.')) ' a child of where the source files exist.'))
rtn.add_option('-f', '--filesize', dest='filesize', default='1M', rtn.add_option('-f', '--filesize', dest='filesize', default='1M',
help=('Maximum size of archive files. A number followed by' help=('Maximum size of archive files. A number followed by '
'a magnitude indicator, eg. 1000000B == one million ' 'a magnitude indicator either "B", "K", "M", or "G". '
'BYTES, 500K == five hundred KILOBYTES, 1.2M == one ' 'Examples:\n 1000000B == one million BYTES\n'
'point two MEGABYTES. 1M == 1048576 BYTES')) ' 1.2M == one point two MEGABYTES\n'
' 1M == 1048576 BYTES'))
rtn.add_option('-n', '--nocompress', action='store_false', dest='compress', rtn.add_option('-n', '--nocompress', action='store_false', dest='compress',
default=True, default=True,
help=('Whether the archive files should be compressed, or ' help=('Whether the archive files should be compressed, or '
'just a concatenation of the source files')) 'just a concatenation of the source files'))
return rtn return rtn
def VerifyArguments(options, parser): def VerifyArguments(options, parser):
"""Runs simple checks on correctness of commandline arguments.
Args:
options: The command line options passed.
parser: The parser object used to parse the command string.
"""
try: try:
if options.sourcefiles is None or options.destination is None: if options.sourcefiles is None or options.destination is None:
parser.print_help() parser.print_help()
sys.exit(-1) sys.exit(-1)
except (AttributeError), err: except AttributeError:
parser.print_help() parser.print_help()
sys.exit(-1) sys.exit(-1)
def ParseSize(size_str): def ParseSize(size_str):
"""Parse the file size argument from a string to a number of bytes.
Args:
size_str: The string representation of the file size.
Returns:
The file size in bytes.
Raises:
ValueError: Raises an error if the numeric or qualifier portions of the
file size argument is invalid.
"""
if len(size_str) < 2: if len(size_str) < 2:
raise ValueError(('filesize argument not understood, please include' raise ValueError(('filesize argument not understood, please include'
' a numeric value and magnitude indicator')) ' a numeric value and magnitude indicator'))
magnitude = size_str[len(size_str)-1:] magnitude = size_str[-1]
if not magnitude in ('K', 'B', 'M'): if not magnitude in ('B', 'K', 'M', 'G'):
raise ValueError(('filesize magnitude indicator not valid, must be \'K\',' raise ValueError(('filesize magnitude indicator not valid, must be "B",'
'\'B\', or \'M\'')) '"K","M", or "G"'))
numeral = float(size_str[0:len(size_str)-1]) numeral = float(size_str[:-1])
if magnitude == 'K': if magnitude == 'K':
numeral *= 1024 numeral *= 1024
elif magnitude == 'M': elif magnitude == 'M':
numeral *= 1048576 numeral *= 1048576
elif magnitude == 'G':
numeral *= 1073741824
return int(numeral) return int(numeral)
class DirectoryZipper(object): class DirectoryZipper(object):
"""Class to compress a directory and all its sub-directories.""" """Class to compress a directory and all its sub-directories."""
current_archive = None
output_dir = None
base_path = None
max_size = None
compress = None
index_fp = None
def __init__(self, output_path, base_dir, archive_size, enable_compression): def __init__(self, output_path, base_dir, archive_size, enable_compression):
"""DirectoryZipper constructor. """DirectoryZipper constructor.
Args: Args:
output_path: the path to write the archives and index file to output_path: A string, the path to write the archives and index file to.
base_dir: the directory to compress base_dir: A string, the directory to compress.
archive_size: the maximum size, in bytes, of a single archive file archive_size: An number, the maximum size, in bytes, of a single
enable_compression: whether or not compression should be enabled, if archive file.
disabled, the files will be written into an uncompresed zip enable_compression: A boolean, whether or not compression should be
enabled, if disabled, the files will be written into an uncompresed
zip.
""" """
self.output_dir = output_path self.output_dir = output_path
self.current_archive = '0.zip' self.current_archive = '0.zip'
@@ -126,6 +136,9 @@ class DirectoryZipper(object):
self.max_size = archive_size self.max_size = archive_size
self.compress = enable_compression self.compress = enable_compression
# Set index_fp to None, because we don't know what it will be yet.
self.index_fp = None
def StartCompress(self): def StartCompress(self):
"""Start compress of the directory. """Start compress of the directory.
@@ -133,7 +146,7 @@ class DirectoryZipper(object):
specified output directory. It will also produce an 'index.txt' file in the specified output directory. It will also produce an 'index.txt' file in the
output directory that maps from file to archive. output directory that maps from file to archive.
""" """
self.index_fp = open(''.join([self.output_dir, 'main.py']), 'w') self.index_fp = open(os.path.join(self.output_dir, 'main.py'), 'w')
self.index_fp.write(divide_and_compress_constants.file_preamble) self.index_fp.write(divide_and_compress_constants.file_preamble)
os.path.walk(self.base_path, self.CompressDirectory, 1) os.path.walk(self.base_path, self.CompressDirectory, 1)
self.index_fp.write(divide_and_compress_constants.file_endpiece) self.index_fp.write(divide_and_compress_constants.file_endpiece)
@@ -149,37 +162,32 @@ class DirectoryZipper(object):
Args: Args:
archive_path: Path to the archive to modify. This archive should not be archive_path: Path to the archive to modify. This archive should not be
open elsewhere, since it will need to be deleted. open elsewhere, since it will need to be deleted.
Return:
A new ZipFile object that points to the modified archive file Returns:
A new ZipFile object that points to the modified archive file.
""" """
if archive_path is None: if archive_path is None:
archive_path = ''.join([self.output_dir, self.current_archive]) archive_path = os.path.join(self.output_dir, self.current_archive)
# Move the old file and create a new one at its old location # Move the old file and create a new one at its old location.
ext_offset = archive_path.rfind('.') root, ext = os.path.splitext(archive_path)
old_archive = ''.join([archive_path[0:ext_offset], '-old', old_archive = ''.join([root, '-old', ext])
archive_path[ext_offset:]])
os.rename(archive_path, old_archive) os.rename(archive_path, old_archive)
old_fp = self.OpenZipFileAtPath(old_archive, mode='r') old_fp = self.OpenZipFileAtPath(old_archive, mode='r')
# By default, store uncompressed.
compress_bit = zipfile.ZIP_STORED
if self.compress: if self.compress:
new_fp = self.OpenZipFileAtPath(archive_path, compress_bit = zipfile.ZIP_DEFLATED
mode='w', new_fp = self.OpenZipFileAtPath(archive_path,
compress=zipfile.ZIP_DEFLATED) mode='w',
else: compress=compress_bit)
new_fp = self.OpenZipFileAtPath(archive_path,
mode='w',
compress=zipfile.ZIP_STORED)
# Read the old archive in a new archive, except the last one
zip_members = enumerate(old_fp.infolist())
num_members = len(old_fp.infolist())
while num_members > 1:
this_member = zip_members.next()[1]
new_fp.writestr(this_member.filename, old_fp.read(this_member.filename))
num_members -= 1
# Close files and delete the old one # Read the old archive in a new archive, except the last one.
for zip_member in old_fp.infolist()[:-1]:
new_fp.writestr(zip_member, old_fp.read(zip_member.filename))
# Close files and delete the old one.
old_fp.close() old_fp.close()
new_fp.close() new_fp.close()
os.unlink(old_archive) os.unlink(old_archive)
@@ -193,11 +201,11 @@ class DirectoryZipper(object):
mode = 'w' mode = 'w'
if mode == 'r': if mode == 'r':
return ZipFile(path, mode) return zipfile.ZipFile(path, mode)
else: else:
return ZipFile(path, mode, compress) return zipfile.ZipFile(path, mode, compress)
def CompressDirectory(self, irrelevant, dir_path, dir_contents): def CompressDirectory(self, unused_id, dir_path, dir_contents):
"""Method to compress the given directory. """Method to compress the given directory.
This method compresses the directory 'dir_path'. It will add to an existing This method compresses the directory 'dir_path'. It will add to an existing
@@ -206,40 +214,35 @@ class DirectoryZipper(object):
mapping of files to archives to the self.index_fp file descriptor mapping of files to archives to the self.index_fp file descriptor
Args: Args:
irrelevant: a numeric identifier passed by the os.path.walk method, this unused_id: A numeric identifier passed by the os.path.walk method, this
is not used by this method is not used by this method.
dir_path: the path to the directory to compress dir_path: A string, the path to the directory to compress.
dir_contents: a list of directory contents to be compressed dir_contents: A list of directory contents to be compressed.
""" """
# Construct the queue of files to be added that this method will use
# construct the queue of files to be added that this method will use
# it seems that dir_contents is given in reverse alphabetical order, # it seems that dir_contents is given in reverse alphabetical order,
# so put them in alphabetical order by inserting to front of the list # so put them in alphabetical order by inserting to front of the list.
dir_contents.sort() dir_contents.sort()
zip_queue = [] zip_queue = []
if dir_path[len(dir_path) - 1:] == os.sep: for filename in dir_contents:
for filename in dir_contents: zip_queue.append(os.path.join(dir_path, filename))
zip_queue.append(''.join([dir_path, filename]))
else:
for filename in dir_contents:
zip_queue.append(''.join([dir_path, os.sep, filename]))
compress_bit = zipfile.ZIP_DEFLATED compress_bit = zipfile.ZIP_DEFLATED
if not self.compress: if not self.compress:
compress_bit = zipfile.ZIP_STORED compress_bit = zipfile.ZIP_STORED
# zip all files in this directory, adding to existing archives and creating # Zip all files in this directory, adding to existing archives and creating
# as necessary # as necessary.
while len(zip_queue) > 0: while zip_queue:
target_file = zip_queue[0] target_file = zip_queue[0]
if os.path.isfile(target_file): if os.path.isfile(target_file):
self.AddFileToArchive(target_file, compress_bit) self.AddFileToArchive(target_file, compress_bit)
# see if adding the new file made our archive too large # See if adding the new file made our archive too large.
if not self.ArchiveIsValid(): if not self.ArchiveIsValid():
# IF fixing fails, the last added file was to large, skip it # IF fixing fails, the last added file was to large, skip it
# ELSE the current archive filled normally, make a new one and try # ELSE the current archive filled normally, make a new one and try
# adding the file again # adding the file again.
if not self.FixArchive('SIZE'): if not self.FixArchive('SIZE'):
zip_queue.pop(0) zip_queue.pop(0)
else: else:
@@ -248,7 +251,7 @@ class DirectoryZipper(object):
0:self.current_archive.rfind('.zip')]) + 1) 0:self.current_archive.rfind('.zip')]) + 1)
else: else:
# if this the first file in the archive, write an index record # Write an index record if necessary.
self.WriteIndexRecord() self.WriteIndexRecord()
zip_queue.pop(0) zip_queue.pop(0)
else: else:
@@ -260,10 +263,10 @@ class DirectoryZipper(object):
Only write an index record if this is the first file to go into archive Only write an index record if this is the first file to go into archive
Returns: Returns:
True if an archive record is written, False if it isn't True if an archive record is written, False if it isn't.
""" """
archive = self.OpenZipFileAtPath( archive = self.OpenZipFileAtPath(
''.join([self.output_dir, self.current_archive]), 'r') os.path.join(self.output_dir, self.current_archive), 'r')
archive_index = archive.infolist() archive_index = archive.infolist()
if len(archive_index) == 1: if len(archive_index) == 1:
self.index_fp.write( self.index_fp.write(
@@ -279,54 +282,56 @@ class DirectoryZipper(object):
"""Make the archive compliant. """Make the archive compliant.
Args: Args:
problem: the reason the archive is invalid problem: An enum, the reason the archive is invalid.
Returns: Returns:
Whether the file(s) removed to fix the archive could conceivably be Whether the file(s) removed to fix the archive could conceivably be
in an archive, but for some reason can't be added to this one. in an archive, but for some reason can't be added to this one.
""" """
archive_path = ''.join([self.output_dir, self.current_archive]) archive_path = os.path.join(self.output_dir, self.current_archive)
rtn_value = None return_value = None
if problem == 'SIZE': if problem == 'SIZE':
archive_obj = self.OpenZipFileAtPath(archive_path, mode='r') archive_obj = self.OpenZipFileAtPath(archive_path, mode='r')
num_archive_files = len(archive_obj.infolist()) num_archive_files = len(archive_obj.infolist())
# IF there is a single file, that means its too large to compress, # IF there is a single file, that means its too large to compress,
# delete the created archive # delete the created archive
# ELSE do normal finalization # ELSE do normal finalization.
if num_archive_files == 1: if num_archive_files == 1:
print ('WARNING: %s%s is too large to store.' % ( print ('WARNING: %s%s is too large to store.' % (
self.base_path, archive_obj.infolist()[0].filename)) self.base_path, archive_obj.infolist()[0].filename))
archive_obj.close() archive_obj.close()
os.unlink(archive_path) os.unlink(archive_path)
rtn_value = False return_value = False
else: else:
self.RemoveLastFile(''.join([self.output_dir, self.current_archive]))
archive_obj.close() archive_obj.close()
self.RemoveLastFile(
os.path.join(self.output_dir, self.current_archive))
print 'Final archive size for %s is %i' % ( print 'Final archive size for %s is %i' % (
self.current_archive, os.stat(archive_path)[stat.ST_SIZE]) self.current_archive, os.path.getsize(archive_path))
rtn_value = True return_value = True
return rtn_value return return_value
def AddFileToArchive(self, filepath, compress_bit): def AddFileToArchive(self, filepath, compress_bit):
"""Add the file at filepath to the current archive. """Add the file at filepath to the current archive.
Args: Args:
filepath: the path of the file to add filepath: A string, the path of the file to add.
compress_bit: whether or not this fiel should be compressed when added compress_bit: A boolean, whether or not this file should be compressed
when added.
Returns: Returns:
True if the file could be added (typically because this is a file) or True if the file could be added (typically because this is a file) or
False if it couldn't be added (typically because its a directory) False if it couldn't be added (typically because its a directory).
""" """
curr_archive_path = ''.join([self.output_dir, self.current_archive]) curr_archive_path = os.path.join(self.output_dir, self.current_archive)
if os.path.isfile(filepath): if os.path.isfile(filepath) and not os.path.islink(filepath):
if os.stat(filepath)[stat.ST_SIZE] > 1048576: if os.path.getsize(filepath) > 1048576:
print 'Warning: %s is potentially too large to serve on GAE' % filepath print 'Warning: %s is potentially too large to serve on GAE' % filepath
archive = self.OpenZipFileAtPath(curr_archive_path, archive = self.OpenZipFileAtPath(curr_archive_path,
compress=compress_bit) compress=compress_bit)
# add the file to the archive # Add the file to the archive.
archive.write(filepath, filepath[len(self.base_path):]) archive.write(filepath, filepath[len(self.base_path):])
archive.close() archive.close()
return True return True
@@ -340,13 +345,22 @@ class DirectoryZipper(object):
The thought is that eventually this will do additional validation The thought is that eventually this will do additional validation
Returns: Returns:
True if the archive is valid, False if its not True if the archive is valid, False if its not.
""" """
archive_path = ''.join([self.output_dir, self.current_archive]) archive_path = os.path.join(self.output_dir, self.current_archive)
if os.stat(archive_path)[stat.ST_SIZE] > self.max_size: return os.path.getsize(archive_path) <= self.max_size
return False
else:
return True def main(argv):
parser = CreateOptionsParser()
(options, unused_args) = parser.parse_args(args=argv[1:])
VerifyArguments(options, parser)
zipper = DirectoryZipper(options.destination,
options.sourcefiles,
ParseSize(options.filesize),
options.compress)
zipper.StartCompress()
if __name__ == '__main__': if __name__ == '__main__':
Main(sys.argv) main(sys.argv)

View File

@@ -19,42 +19,40 @@
__author__ = 'jmatt@google.com (Justin Mattson)' __author__ = 'jmatt@google.com (Justin Mattson)'
file_preamble = ('#!/usr/bin/env python\n' file_preamble = """#!/usr/bin/env python
'#\n' #
'# Copyright 2008 Google Inc.\n' # Copyright 2008 Google Inc.
'#\n' #
'# Licensed under the Apache License, Version 2.0 (the' # Licensed under the Apache License, Version 2.0 (the "License");
'\"License");\n' # you may not use this file except in compliance with the License.
'# you may not use this file except in compliance with the ' # You may obtain a copy of the License at
'License.\n' #
'# You may obtain a copy of the License at\n' # http://www.apache.org/licenses/LICENSE-2.0
'#\n' #
'# http://www.apache.org/licenses/LICENSE-2.0\n' # Unless required by applicable law or agreed to in writing, software
'#\n' # distributed under the License is distributed on an \"AS IS\" BASIS,
'# Unless required by applicable law or agreed to in writing,' # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
' software\n' # See the License for the specific language governing permissions and
'# distributed under the License is distributed on an \"AS' # limitations under the License.
'IS\" BASIS,\n' #
'# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either '
'express or implied.\n'
'# See the License for the specific language governing'
' permissions and\n'
'# limitations under the License.\n'
'#\n\n'
'import wsgiref.handlers\n'
'from google.appengine.ext import zipserve\n'
'from google.appengine.ext import webapp\n'
'import memcache_zipserve\n\n\n'
'class MainHandler(webapp.RequestHandler):\n\n'
' def get(self):\n'
' self.response.out.write(\'Hello world!\')\n\n'
'def main():\n'
' application = webapp.WSGIApplication([(\'/(.*)\','
' memcache_zipserve.create_handler([')
file_endpiece = ('])),\n' import wsgiref.handlers\n'
'],\n' from google.appengine.ext import zipserve\n'
'debug=False)\n' from google.appengine.ext import webapp\n'
' wsgiref.handlers.CGIHandler().run(application)\n\n' import memcache_zipserve\n\n\n'
'if __name__ == \'__main__\':\n' class MainHandler(webapp.RequestHandler):
' main()')
def get(self):
self.response.out.write('Hello world!')
def main():
application = webapp.WSGIApplication(['/(.*)',
memcache_zipserve.create_handler(["""
file_endpiece = """])),
],
debug=False)
wsgiref.handlers.CGIHandler().run(application)
if __name__ == __main__:
main()"""

View File

@@ -17,7 +17,7 @@
"""Tests for divide_and_compress.py. """Tests for divide_and_compress.py.
TODO: Add tests for module methods. TODO(jmatt): Add tests for module methods.
""" """
__author__ = 'jmatt@google.com (Justin Mattson)' __author__ = 'jmatt@google.com (Justin Mattson)'
@@ -26,10 +26,9 @@ import os
import stat import stat
import unittest import unittest
import zipfile import zipfile
from zipfile import ZipFile
import divide_and_compress import divide_and_compress
from mox import mox import mox
class BagOfParts(object): class BagOfParts(object):
@@ -58,6 +57,10 @@ class ValidAndRemoveTests(unittest.TestCase):
'sdjfljkgsc n;iself') 'sdjfljkgsc n;iself')
self.files = {'file1': file1, 'file2': file2} self.files = {'file1': file1, 'file2': file2}
def tearDown(self):
"""Remove any stubs we've created."""
self.my_mox.UnsetStubs()
def testArchiveIsValid(self): def testArchiveIsValid(self):
"""Test the DirectoryZipper.ArchiveIsValid method. """Test the DirectoryZipper.ArchiveIsValid method.
@@ -119,7 +122,7 @@ class ValidAndRemoveTests(unittest.TestCase):
A configured mocked A configured mocked
""" """
source_zip = self.my_mox.CreateMock(ZipFile) source_zip = self.my_mox.CreateMock(zipfile.ZipFile)
source_zip.infolist().AndReturn([self.files['file1'], self.files['file1']]) source_zip.infolist().AndReturn([self.files['file1'], self.files['file1']])
source_zip.infolist().AndReturn([self.files['file1'], self.files['file1']]) source_zip.infolist().AndReturn([self.files['file1'], self.files['file1']])
source_zip.read(self.files['file1'].filename).AndReturn( source_zip.read(self.files['file1'].filename).AndReturn(
@@ -137,16 +140,12 @@ class ValidAndRemoveTests(unittest.TestCase):
A configured mocked A configured mocked
""" """
dest_zip = mox.MockObject(ZipFile) dest_zip = mox.MockObject(zipfile.ZipFile)
dest_zip.writestr(self.files['file1'].filename, dest_zip.writestr(self.files['file1'].filename,
self.files['file1'].contents) self.files['file1'].contents)
dest_zip.close() dest_zip.close()
return dest_zip return dest_zip
def tearDown(self):
"""Remove any stubs we've created."""
self.my_mox.UnsetStubs()
class FixArchiveTests(unittest.TestCase): class FixArchiveTests(unittest.TestCase):
"""Tests for the DirectoryZipper.FixArchive method.""" """Tests for the DirectoryZipper.FixArchive method."""
@@ -158,6 +157,10 @@ class FixArchiveTests(unittest.TestCase):
self.file1.filename = 'file1.txt' self.file1.filename = 'file1.txt'
self.file1.contents = 'This is a test file' self.file1.contents = 'This is a test file'
def tearDown(self):
"""Unset any mocks that we've created."""
self.my_mox.UnsetStubs()
def _InitMultiFileData(self): def _InitMultiFileData(self):
"""Create an array of mock file objects. """Create an array of mock file objects.
@@ -211,7 +214,7 @@ class FixArchiveTests(unittest.TestCase):
Returns: Returns:
A configured mock object A configured mock object
""" """
mock_zip = self.my_mox.CreateMock(ZipFile) mock_zip = self.my_mox.CreateMock(zipfile.ZipFile)
mock_zip.infolist().AndReturn([self.file1]) mock_zip.infolist().AndReturn([self.file1])
mock_zip.infolist().AndReturn([self.file1]) mock_zip.infolist().AndReturn([self.file1])
mock_zip.close() mock_zip.close()
@@ -250,15 +253,11 @@ class FixArchiveTests(unittest.TestCase):
A configured mock object A configured mock object
""" """
self._InitMultiFileData() self._InitMultiFileData()
mock_zip = self.my_mox.CreateMock(ZipFile) mock_zip = self.my_mox.CreateMock(zipfile.ZipFile)
mock_zip.infolist().AndReturn(self.multi_file_dir) mock_zip.infolist().AndReturn(self.multi_file_dir)
mock_zip.close() mock_zip.close()
return mock_zip return mock_zip
def tearDown(self):
"""Unset any mocks that we've created."""
self.my_mox.UnsetStubs()
class AddFileToArchiveTest(unittest.TestCase): class AddFileToArchiveTest(unittest.TestCase):
"""Test behavior of method to add a file to an archive.""" """Test behavior of method to add a file to an archive."""
@@ -270,6 +269,9 @@ class AddFileToArchiveTest(unittest.TestCase):
self.file_to_add = 'file.txt' self.file_to_add = 'file.txt'
self.input_dir = '/foo/bar/baz/' self.input_dir = '/foo/bar/baz/'
def tearDown(self):
self.my_mox.UnsetStubs()
def testAddFileToArchive(self): def testAddFileToArchive(self):
"""Test the DirectoryZipper.AddFileToArchive method. """Test the DirectoryZipper.AddFileToArchive method.
@@ -312,15 +314,12 @@ class AddFileToArchiveTest(unittest.TestCase):
Returns: Returns:
A configured mock object A configured mock object
""" """
archive_mock = self.my_mox.CreateMock(ZipFile) archive_mock = self.my_mox.CreateMock(zipfile.ZipFile)
archive_mock.write(''.join([self.input_dir, self.file_to_add]), archive_mock.write(''.join([self.input_dir, self.file_to_add]),
self.file_to_add) self.file_to_add)
archive_mock.close() archive_mock.close()
return archive_mock return archive_mock
def tearDown(self):
self.my_mox.UnsetStubs()
class CompressDirectoryTest(unittest.TestCase): class CompressDirectoryTest(unittest.TestCase):
"""Test the master method of the class. """Test the master method of the class.

View File

@@ -28,7 +28,7 @@ final class PlatformTarget implements IAndroidTarget {
/** String used to get a hash to the platform target */ /** String used to get a hash to the platform target */
private final static String PLATFORM_HASH = "android-%d"; private final static String PLATFORM_HASH = "android-%d";
private final static String PLATFORM_VENDOR = "Android"; private final static String PLATFORM_VENDOR = "Android Open Source Project";
private final static String PLATFORM_NAME = "Android %s"; private final static String PLATFORM_NAME = "Android %s";
private final String mLocation; private final String mLocation;