auto import from //branches/cupcake_rel/...@140373
This commit is contained in:
File diff suppressed because it is too large
Load Diff
28
emulator/sensors/Android.mk
Normal file
28
emulator/sensors/Android.mk
Normal 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
|
||||
591
emulator/sensors/sensors_qemu.c
Normal file
591
emulator/sensors/sensors_qemu.c
Normal 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
|
||||
};
|
||||
@@ -1,6 +1,9 @@
|
||||
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
|
||||
. build/envsetup.sh
|
||||
|
||||
@@ -550,7 +550,7 @@ public class SoftKeyboard extends InputMethodService
|
||||
boolean typedWordValid) {
|
||||
if (suggestions != null && suggestions.size() > 0) {
|
||||
setCandidatesViewShown(true);
|
||||
} else if (isFullscreenMode()) {
|
||||
} else if (isExtractViewShown()) {
|
||||
setCandidatesViewShown(true);
|
||||
}
|
||||
if (mCandidateView != null) {
|
||||
|
||||
19
testrunner/Android.mk
Normal file
19
testrunner/Android.mk
Normal 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)
|
||||
45
testrunner/android_build.py
Normal file
45
testrunner/android_build.py
Normal 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
280
testrunner/runtest.py
Executable 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()
|
||||
@@ -87,6 +87,12 @@ These attributes map to the following commands:
|
||||
coverage_target="ApiDemos"
|
||||
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 -->
|
||||
<test name="heap"
|
||||
build_path="frameworks/base/tests/AndroidTests"
|
||||
@@ -114,6 +120,11 @@ These attributes map to the following commands:
|
||||
class="android.content.AbstractTableMergerTest"
|
||||
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 -->
|
||||
<test name="browser"
|
||||
@@ -176,6 +187,13 @@ These attributes map to the following commands:
|
||||
runner=".MediaFrameworkUnitTestRunner"
|
||||
coverage_target="framework" />
|
||||
|
||||
<test name="musicplayer"
|
||||
build_path="packages/apps/Music"
|
||||
package="com.android.music.tests"
|
||||
runner=".MusicPlayerFunctionalTestRunner"
|
||||
coverage_target="Music"
|
||||
continuous="true" />
|
||||
|
||||
<!-- obsolete?
|
||||
<test name="mediaprov"
|
||||
build_path="tests/MediaProvider"
|
||||
|
||||
@@ -27,7 +27,14 @@ import com.android.ddmlib.MultiLineReceiver;
|
||||
* <p>Expects the following output:
|
||||
*
|
||||
* <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
|
||||
* 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 STACK = "stack";
|
||||
private static final String NUMTESTS = "numtests";
|
||||
private static final String ERROR = "Error";
|
||||
private static final String SHORTMSG = "shortMsg";
|
||||
}
|
||||
|
||||
/** 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_CODE = "INSTRUMENTATION_STATUS_CODE: ";
|
||||
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: ";
|
||||
}
|
||||
|
||||
@@ -90,6 +101,23 @@ public class InstrumentationResultParser extends MultiLineReceiver {
|
||||
boolean isComplete() {
|
||||
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 */
|
||||
@@ -130,6 +158,8 @@ public class InstrumentationResultParser extends MultiLineReceiver {
|
||||
public void processNewLines(String[] lines) {
|
||||
for (String line : lines) {
|
||||
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.
|
||||
submitCurrentKeyValue();
|
||||
parseKey(line, Prefixes.STATUS.length());
|
||||
} else if (line.startsWith(Prefixes.STATUS_FAILED)) {
|
||||
Log.e(LOG_TAG, "test run failed " + line);
|
||||
mTestListener.testRunFailed(line);
|
||||
} else if (line.startsWith(Prefixes.RESULT)) {
|
||||
// Previous status key-value has been collected. Store it.
|
||||
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)) {
|
||||
parseTime(line, Prefixes.TIME_REPORT.length());
|
||||
} else {
|
||||
@@ -186,19 +222,19 @@ public class InstrumentationResultParser extends MultiLineReceiver {
|
||||
|
||||
if (mCurrentKey.equals(StatusKeys.CLASS)) {
|
||||
testInfo.mTestClass = statusValue.trim();
|
||||
}
|
||||
else if (mCurrentKey.equals(StatusKeys.TEST)) {
|
||||
} else if (mCurrentKey.equals(StatusKeys.TEST)) {
|
||||
testInfo.mTestName = statusValue.trim();
|
||||
}
|
||||
else if (mCurrentKey.equals(StatusKeys.NUMTESTS)) {
|
||||
} else if (mCurrentKey.equals(StatusKeys.NUMTESTS)) {
|
||||
try {
|
||||
testInfo.mNumTests = Integer.parseInt(statusValue);
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
} catch (NumberFormatException e) {
|
||||
Log.e(LOG_TAG, "Unexpected integer number of tests, received " + statusValue);
|
||||
}
|
||||
}
|
||||
else if (mCurrentKey.equals(StatusKeys.STACK)) {
|
||||
} else if (mCurrentKey.equals(StatusKeys.ERROR) ||
|
||||
mCurrentKey.equals(StatusKeys.SHORTMSG)) {
|
||||
// test run must have failed
|
||||
handleTestRunFailed(statusValue);
|
||||
} else if (mCurrentKey.equals(StatusKeys.STACK)) {
|
||||
testInfo.mStackTrace = statusValue;
|
||||
}
|
||||
|
||||
@@ -252,8 +288,7 @@ public class InstrumentationResultParser extends MultiLineReceiver {
|
||||
TestResult testInfo = getCurrentTestInfo();
|
||||
try {
|
||||
testInfo.mCode = Integer.parseInt(value);
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
} catch (NumberFormatException e) {
|
||||
Log.e(LOG_TAG, "Expected integer status code, received: " + value);
|
||||
}
|
||||
|
||||
@@ -286,7 +321,7 @@ public class InstrumentationResultParser extends MultiLineReceiver {
|
||||
*/
|
||||
private void reportResult(TestResult testInfo) {
|
||||
if (!testInfo.isComplete()) {
|
||||
Log.e(LOG_TAG, "invalid instrumentation status bundle " + testInfo.toString());
|
||||
Log.w(LOG_TAG, "invalid instrumentation status bundle " + testInfo.toString());
|
||||
return;
|
||||
}
|
||||
reportTestRunStarted(testInfo);
|
||||
@@ -337,8 +372,7 @@ public class InstrumentationResultParser extends MultiLineReceiver {
|
||||
private String getTrace(TestResult testInfo) {
|
||||
if (testInfo.mStackTrace != null) {
|
||||
return testInfo.mStackTrace;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
Log.e(LOG_TAG, "Could not find stack trace for failed test ");
|
||||
return new Throwable("Unknown failure").toString();
|
||||
}
|
||||
@@ -352,12 +386,18 @@ public class InstrumentationResultParser extends MultiLineReceiver {
|
||||
try {
|
||||
float timeSeconds = Float.parseFloat(timeString);
|
||||
mTestTime = (long) (timeSeconds * 1000);
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
} catch (NumberFormatException e) {
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -21,26 +21,34 @@ import com.android.ddmlib.IDevice;
|
||||
import com.android.ddmlib.Log;
|
||||
|
||||
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.
|
||||
*/
|
||||
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 mRunnerName;
|
||||
private String mExtraArgs;
|
||||
private boolean mLogOnlyMode;
|
||||
private IDevice mRemoteDevice;
|
||||
/** map of name-value instrumentation argument pairs */
|
||||
private Map<String, String> mArgMap;
|
||||
private InstrumentationResultParser mParser;
|
||||
|
||||
private static final String LOG_TAG = "RemoteAndroidTest";
|
||||
private static final String DEFAULT_RUNNER_NAME =
|
||||
"android.test.InstrumentationTestRunner";
|
||||
private static final String DEFAULT_RUNNER_NAME = "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.
|
||||
@@ -57,9 +65,7 @@ public class RemoteAndroidTestRunner {
|
||||
mPackageName = packageName;
|
||||
mRunnerName = runnerName;
|
||||
mRemoteDevice = remoteDevice;
|
||||
mClassArg = null;
|
||||
mExtraArgs = "";
|
||||
mLogOnlyMode = false;
|
||||
mArgMap = new Hashtable<String, String>();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,7 +110,7 @@ public class RemoteAndroidTestRunner {
|
||||
* @param className fully qualified class name (eg x.y.z)
|
||||
*/
|
||||
public void setClassName(String className) {
|
||||
mClassArg = className;
|
||||
addInstrumentationArg(CLASS_ARG_NAME, className);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,7 +131,7 @@ public class RemoteAndroidTestRunner {
|
||||
}
|
||||
classArgBuilder.append(classNames[i]);
|
||||
}
|
||||
mClassArg = classArgBuilder.toString();
|
||||
setClassName(classArgBuilder.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,34 +142,57 @@ public class RemoteAndroidTestRunner {
|
||||
* @param testName method name
|
||||
*/
|
||||
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.
|
||||
* Must be called before 'run'.
|
||||
* Adds a argument to include in instrumentation command.
|
||||
* <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) {
|
||||
if (instrumentationArgs == null) {
|
||||
throw new IllegalArgumentException("instrumentationArgs cannot be null");
|
||||
public void addInstrumentationArg(String name, String value) {
|
||||
if (name == null || value == 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() {
|
||||
return mExtraArgs;
|
||||
public void addBooleanArg(String name, boolean value) {
|
||||
addInstrumentationArg(name, Boolean.toString(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this test run to log only mode - skips test execution.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -172,8 +201,8 @@ public class RemoteAndroidTestRunner {
|
||||
* @param listener listens for test results
|
||||
*/
|
||||
public void run(ITestRunListener listener) {
|
||||
final String runCaseCommandStr = "am instrument -w -r "
|
||||
+ getClassCmd() + " " + getLogCmd() + " " + getExtraArgs() + " " + getRunnerPath();
|
||||
final String runCaseCommandStr = String.format("am instrument -w -r %s %s",
|
||||
getArgsCommand(), getRunnerPath());
|
||||
Log.d(LOG_TAG, runCaseCommandStr);
|
||||
mParser = new InstrumentationResultParser(listener);
|
||||
|
||||
@@ -195,34 +224,17 @@ public class RemoteAndroidTestRunner {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the test class argument.
|
||||
* Returns the full instrumentation command line syntax for the provided instrumentation
|
||||
* arguments.
|
||||
* Returns an empty string if no arguments were specified.
|
||||
*/
|
||||
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
|
||||
* empty string.
|
||||
*/
|
||||
private String getLogCmd() {
|
||||
if (mLogOnlyMode) {
|
||||
return "-e log true";
|
||||
}
|
||||
else {
|
||||
return "";
|
||||
private String getArgsCommand() {
|
||||
StringBuilder commandBuilder = new StringBuilder();
|
||||
for (Entry<String, String> argPair : mArgMap.entrySet()) {
|
||||
final String argCmd = String.format(" -e %s %s", argPair.getKey(),
|
||||
argPair.getValue());
|
||||
commandBuilder.append(argCmd);
|
||||
}
|
||||
return commandBuilder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +105,41 @@ public class InstrumentationResultParserTest extends TestCase {
|
||||
}
|
||||
|
||||
/**
|
||||
* builds a common test result using TEST_NAME and TEST_CLASS.
|
||||
* 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.
|
||||
*/
|
||||
private StringBuilder buildCommonResult() {
|
||||
StringBuilder output = new StringBuilder();
|
||||
@@ -146,6 +180,13 @@ public class InstrumentationResultParserTest extends TestCase {
|
||||
outputBuilder.append(key);
|
||||
outputBuilder.append('=');
|
||||
outputBuilder.append(value);
|
||||
addLineBreak(outputBuilder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append line break characters to output
|
||||
*/
|
||||
private void addLineBreak(StringBuilder outputBuilder) {
|
||||
outputBuilder.append("\r\n");
|
||||
}
|
||||
|
||||
@@ -164,7 +205,7 @@ public class InstrumentationResultParserTest extends TestCase {
|
||||
private void addStatusCode(StringBuilder outputBuilder, String value) {
|
||||
outputBuilder.append("INSTRUMENTATION_STATUS_CODE: ");
|
||||
outputBuilder.append(value);
|
||||
outputBuilder.append("\r\n");
|
||||
addLineBreak(outputBuilder);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -197,11 +238,14 @@ public class InstrumentationResultParserTest extends TestCase {
|
||||
TestFailure mTestStatus;
|
||||
String mTrace;
|
||||
boolean mStopped;
|
||||
/** stores the error message provided to testRunFailed */
|
||||
String mRunFailedMessage;
|
||||
|
||||
VerifyingTestResult() {
|
||||
mNumTestsRun = 0;
|
||||
mTestStatus = null;
|
||||
mStopped = false;
|
||||
mRunFailedMessage = null;
|
||||
}
|
||||
|
||||
public void testEnded(TestIdentifier test) {
|
||||
@@ -238,8 +282,7 @@ public class InstrumentationResultParserTest extends TestCase {
|
||||
}
|
||||
|
||||
public void testRunFailed(String errorMessage) {
|
||||
// ignored
|
||||
mRunFailedMessage = errorMessage;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,18 +17,17 @@
|
||||
package com.android.ddmlib.testrunner;
|
||||
|
||||
import com.android.ddmlib.Client;
|
||||
import com.android.ddmlib.Device.DeviceState;
|
||||
import com.android.ddmlib.FileListingService;
|
||||
import com.android.ddmlib.IDevice;
|
||||
import com.android.ddmlib.IShellOutputReceiver;
|
||||
import com.android.ddmlib.log.LogReceiver;
|
||||
import com.android.ddmlib.RawImage;
|
||||
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.util.Map;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
final String extraArgs = "blah";
|
||||
mRunner.setExtraArgs(extraArgs);
|
||||
public void testRunWithAddInstrumentationArg() {
|
||||
final String extraArgName = "blah";
|
||||
final String extraArgValue = "blahValue";
|
||||
mRunner.addInstrumentationArg(extraArgName, extraArgValue);
|
||||
mRunner.run(new EmptyListener());
|
||||
assertStringsEquals(String.format("am instrument -w -r %s %s/%s", extraArgs,
|
||||
TEST_PACKAGE, TEST_RUNNER), mMockDevice.getLastShellCommand());
|
||||
assertStringsEquals(String.format("am instrument -w -r -e %s %s %s/%s", extraArgName,
|
||||
extraArgValue, TEST_PACKAGE, TEST_RUNNER), mMockDevice.getLastShellCommand());
|
||||
}
|
||||
|
||||
|
||||
@@ -243,6 +243,5 @@ public class RemoteAndroidTestRunnerTest extends TestCase {
|
||||
public void testStarted(TestIdentifier test) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,8 @@ Require-Bundle: com.android.ide.eclipse.ddms,
|
||||
org.eclipse.wst.sse.ui,
|
||||
org.eclipse.wst.xml.core,
|
||||
org.eclipse.wst.xml.ui,
|
||||
org.eclipse.jdt.junit
|
||||
org.eclipse.jdt.junit,
|
||||
org.eclipse.jdt.junit.runtime
|
||||
Eclipse-LazyStart: true
|
||||
Export-Package: com.android.ide.eclipse.adt,
|
||||
com.android.ide.eclipse.adt.build;x-friends:="com.android.ide.eclipse.tests",
|
||||
|
||||
@@ -510,4 +510,59 @@
|
||||
type="org.eclipse.jdt.junit.launchconfig">
|
||||
</launchDelegate>
|
||||
</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>
|
||||
|
||||
@@ -1041,20 +1041,29 @@ public class AdtPlugin extends AbstractUIPlugin {
|
||||
|
||||
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.
|
||||
// The method modifies the list (it removes the project that
|
||||
// do not need to be resolved again).
|
||||
AndroidClasspathContainerInitializer.checkProjectsCache(
|
||||
mPostLoadProjectsToCheck);
|
||||
|
||||
mPostLoadProjectsToResolve.addAll(mPostLoadProjectsToCheck);
|
||||
list.addAll(mPostLoadProjectsToCheck);
|
||||
|
||||
// update the project that needs recompiling.
|
||||
if (mPostLoadProjectsToResolve.size() > 0) {
|
||||
IJavaProject[] array = mPostLoadProjectsToResolve.toArray(
|
||||
new IJavaProject[mPostLoadProjectsToResolve.size()]);
|
||||
if (list.size() > 0) {
|
||||
IJavaProject[] array = list.toArray(
|
||||
new IJavaProject[list.size()]);
|
||||
AndroidClasspathContainerInitializer.updateProjects(array);
|
||||
mPostLoadProjectsToResolve.clear();
|
||||
}
|
||||
|
||||
progress.worked(10);
|
||||
|
||||
@@ -222,6 +222,7 @@ public class PreCompilerBuilder extends BaseBuilder {
|
||||
|
||||
PreCompilerDeltaVisitor dv = null;
|
||||
String javaPackage = null;
|
||||
int minSdkVersion = AndroidManifestParser.INVALID_MIN_SDK;
|
||||
|
||||
if (kind == FULL_BUILD) {
|
||||
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
|
||||
@@ -253,6 +254,7 @@ public class PreCompilerBuilder extends BaseBuilder {
|
||||
|
||||
// get the java package from the visitor
|
||||
javaPackage = dv.getManifestPackage();
|
||||
minSdkVersion = dv.getMinSdkVersion();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,7 +278,7 @@ public class PreCompilerBuilder extends BaseBuilder {
|
||||
if (manifest == null) {
|
||||
String msg = String.format(Messages.s_File_Missing,
|
||||
AndroidConstants.FN_ANDROID_MANIFEST);
|
||||
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg);
|
||||
AdtPlugin.printErrorToConsole(project, msg);
|
||||
markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
|
||||
|
||||
// This interrupts the build. The next builders will not run.
|
||||
@@ -304,14 +306,29 @@ public class PreCompilerBuilder extends BaseBuilder {
|
||||
|
||||
// get the java package from the parser
|
||||
javaPackage = parser.getPackage();
|
||||
minSdkVersion = parser.getApiLevelRequirement();
|
||||
}
|
||||
|
||||
if (minSdkVersion != AndroidManifestParser.INVALID_MIN_SDK &&
|
||||
minSdkVersion < projectTarget.getApiVersionNumber()) {
|
||||
// check it against the target api level
|
||||
String msg = String.format(
|
||||
"Manifest min SDK version (%1$d) is lower than project target API level (%2$d)",
|
||||
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.
|
||||
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.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
|
||||
msg);
|
||||
AdtPlugin.printErrorToConsole(project, msg);
|
||||
markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
|
||||
|
||||
// This interrupts the build. The next builders will not run.
|
||||
stopBuild(msg);
|
||||
|
||||
@@ -72,8 +72,10 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
|
||||
/** Manifest check/parsing flag. */
|
||||
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;
|
||||
/** minSDKVersion attribute value, gathered from the parsing of the manifest */
|
||||
private int mMinSdkVersion = AndroidManifestParser.INVALID_MIN_SDK;
|
||||
|
||||
// Internal usage fields.
|
||||
/**
|
||||
@@ -137,6 +139,22 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
|
||||
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)
|
||||
*
|
||||
@@ -184,6 +202,7 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
|
||||
|
||||
if (parser != null) {
|
||||
mJavaPackage = parser.getPackage();
|
||||
mMinSdkVersion = parser.getApiLevelRequirement();
|
||||
}
|
||||
|
||||
mCheckedManifestXml = true;
|
||||
|
||||
@@ -307,7 +307,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
|
||||
* <code>DEBUG_MODE</code>.
|
||||
* @param apk the resource to the apk to launch.
|
||||
* @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 config the launch configuration
|
||||
* @param launch the launch object
|
||||
@@ -638,20 +639,21 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
|
||||
|
||||
String deviceApiVersionName = device.getProperty(IDevice.PROP_BUILD_VERSION);
|
||||
String value = device.getProperty(IDevice.PROP_BUILD_VERSION_NUMBER);
|
||||
int deviceApiVersionNumber = 0;
|
||||
int deviceApiVersionNumber = AndroidManifestParser.INVALID_MIN_SDK;
|
||||
try {
|
||||
deviceApiVersionNumber = Integer.parseInt(value);
|
||||
} catch (NumberFormatException e) {
|
||||
// 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.
|
||||
AdtPlugin.printErrorToConsole(launchInfo.getProject(),
|
||||
"WARNING: Application does not specify an API level requirement!");
|
||||
|
||||
// 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(),
|
||||
"WARNING: Unknown device API version!");
|
||||
} else {
|
||||
@@ -660,7 +662,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
|
||||
deviceApiVersionName));
|
||||
}
|
||||
} else { // app requires a specific API level
|
||||
if (deviceApiVersionName == null || deviceApiVersionNumber == 0) {
|
||||
if (deviceApiVersionName == null ||
|
||||
deviceApiVersionNumber == AndroidManifestParser.INVALID_MIN_SDK) {
|
||||
AdtPlugin.printToConsole(launchInfo.getProject(),
|
||||
"WARNING: Unknown device API version!");
|
||||
} else if (deviceApiVersionNumber < launchInfo.getRequiredApiVersionNumber()) {
|
||||
|
||||
@@ -16,12 +16,13 @@
|
||||
|
||||
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.IProject;
|
||||
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
|
||||
* application is launched.
|
||||
@@ -50,7 +51,8 @@ public final class DelayedLaunchInfo {
|
||||
/** debuggable attribute of the manifest file. */
|
||||
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 InstallRetryMode mRetryMode = InstallRetryMode.NEVER;
|
||||
@@ -81,7 +83,8 @@ public final class DelayedLaunchInfo {
|
||||
* @param launchAction action to perform after app install
|
||||
* @param pack IFile to the package (.apk) 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 monitor progress monitor for launch
|
||||
*/
|
||||
|
||||
@@ -55,6 +55,11 @@ import org.eclipse.swt.widgets.Text;
|
||||
*/
|
||||
public class MainLaunchConfigTab extends AbstractLaunchConfigurationTab {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public static final String LAUNCH_TAB_IMAGE = "mainLaunchTab.png";
|
||||
|
||||
protected static final String EMPTY_STRING = ""; //$NON-NLS-1$
|
||||
|
||||
protected Text mProjText;
|
||||
@@ -194,7 +199,7 @@ public class MainLaunchConfigTab extends AbstractLaunchConfigurationTab {
|
||||
|
||||
@Override
|
||||
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);
|
||||
|
||||
// get the list of projects
|
||||
IJavaProject[] projects = mProjectChooserHelper.getAndroidProjects(null);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
IProject proj = mProjectChooserHelper.getAndroidProject(projectName);
|
||||
loadActivities(proj);
|
||||
}
|
||||
|
||||
// load the launch action.
|
||||
mLaunchAction = LaunchConfigDelegate.DEFAULT_LAUNCH_ACTION;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -487,6 +487,15 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit
|
||||
IJavaProject javaProject = projects.get(i);
|
||||
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
|
||||
IAndroidTarget target = Sdk.getCurrent().getTarget(javaProject.getProject());
|
||||
if (target == null) {
|
||||
|
||||
@@ -829,7 +829,7 @@ public class NewProjectCreationPage extends WizardPage {
|
||||
|
||||
String packageName = null;
|
||||
String activityName = null;
|
||||
int minSdkVersion = 0; // 0 means no minSdkVersion provided in the manifest
|
||||
int minSdkVersion = AndroidManifestParser.INVALID_MIN_SDK;
|
||||
try {
|
||||
packageName = manifestData.getPackage();
|
||||
minSdkVersion = manifestData.getApiLevelRequirement();
|
||||
@@ -927,7 +927,7 @@ public class NewProjectCreationPage extends WizardPage {
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundTarget && minSdkVersion > 0) {
|
||||
if (!foundTarget && minSdkVersion != AndroidManifestParser.INVALID_MIN_SDK) {
|
||||
try {
|
||||
for (IAndroidTarget target : mSdkTargetSelector.getTargets()) {
|
||||
if (target.getApiVersionNumber() == minSdkVersion) {
|
||||
@@ -954,7 +954,8 @@ public class NewProjectCreationPage extends WizardPage {
|
||||
if (!foundTarget) {
|
||||
mInternalMinSdkVersionUpdate = true;
|
||||
mMinSdkVersionField.setText(
|
||||
minSdkVersion <= 0 ? "" : Integer.toString(minSdkVersion)); //$NON-NLS-1$
|
||||
minSdkVersion == AndroidManifestParser.INVALID_MIN_SDK ? "" :
|
||||
Integer.toString(minSdkVersion)); //$NON-NLS-1$
|
||||
mInternalMinSdkVersionUpdate = false;
|
||||
}
|
||||
}
|
||||
@@ -1148,7 +1149,7 @@ public class NewProjectCreationPage extends WizardPage {
|
||||
return MSG_NONE;
|
||||
}
|
||||
|
||||
int version = -1;
|
||||
int version = AndroidManifestParser.INVALID_MIN_SDK;
|
||||
try {
|
||||
// If not empty, it must be a valid integer > 0
|
||||
version = Integer.parseInt(getMinSdkVersion());
|
||||
|
||||
@@ -72,6 +72,8 @@ public class AndroidManifestParser {
|
||||
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$
|
||||
|
||||
public final static int INVALID_MIN_SDK = -1;
|
||||
|
||||
/**
|
||||
* XML error & data handler used when parsing the AndroidManifest.xml file.
|
||||
* <p/>
|
||||
@@ -92,8 +94,9 @@ public class AndroidManifestParser {
|
||||
private Set<String> mProcesses = null;
|
||||
/** debuggable attribute value. If null, the attribute is not present. */
|
||||
private Boolean mDebuggable = null;
|
||||
/** API level requirement. if 0 the attribute was not present. */
|
||||
private int mApiLevelRequirement = 0;
|
||||
/** API level requirement. if {@link AndroidManifestParser#INVALID_MIN_SDK}
|
||||
* the attribute was not present. */
|
||||
private int mApiLevelRequirement = INVALID_MIN_SDK;
|
||||
/** List of all instrumentations declared by the manifest */
|
||||
private final ArrayList<String> mInstrumentations = new ArrayList<String>();
|
||||
/** 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() {
|
||||
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() {
|
||||
return mApiLevelRequirement;
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
|
||||
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.ResourcesPlugin;
|
||||
import org.eclipse.jdt.core.IJavaModel;
|
||||
@@ -107,4 +106,24 @@ public class ProjectChooserHelper {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -461,5 +461,13 @@ public class FileMock implements IFile {
|
||||
public void setHidden(boolean isHidden) throws CoreException {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public boolean isHidden(int options) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public boolean isTeamPrivateMember(int options) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,8 @@ public final class FolderMock implements IFolder {
|
||||
|
||||
// -------- 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();
|
||||
}
|
||||
|
||||
@@ -106,8 +107,8 @@ public final class FolderMock implements IFolder {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void move(IPath destination, boolean force, boolean keepHistory, IProgressMonitor monitor)
|
||||
throws CoreException {
|
||||
public void move(IPath destination, boolean force, boolean keepHistory,
|
||||
IProgressMonitor monitor) throws CoreException {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -225,7 +226,8 @@ public final class FolderMock implements IFolder {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -448,4 +450,11 @@ public final class FolderMock implements IFolder {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public boolean isHidden(int options) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public boolean isTeamPrivateMember(int options) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,14 @@ import sun.reflect.generics.reflectiveObjects.NotImplementedException;
|
||||
import java.net.URI;
|
||||
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")
|
||||
public class ProjectMock implements IProject {
|
||||
|
||||
@@ -265,7 +273,8 @@ public class ProjectMock implements IProject {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -498,4 +507,11 @@ public class ProjectMock implements IProject {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public boolean isHidden(int options) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public boolean isTeamPrivateMember(int options) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,28 +36,21 @@ from.
|
||||
|
||||
__author__ = 'jmatt@google.com (Justin Mattson)'
|
||||
|
||||
from optparse import OptionParser
|
||||
import optparse
|
||||
import os
|
||||
import stat
|
||||
import sys
|
||||
import zipfile
|
||||
from zipfile import ZipFile
|
||||
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():
|
||||
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,
|
||||
help='The directory containing the files to compress')
|
||||
rtn.add_option('-d', '--destination', dest='destination', default=None,
|
||||
@@ -65,9 +58,10 @@ def CreateOptionsParser():
|
||||
' a child of where the source files exist.'))
|
||||
rtn.add_option('-f', '--filesize', dest='filesize', default='1M',
|
||||
help=('Maximum size of archive files. A number followed by '
|
||||
'a magnitude indicator, eg. 1000000B == one million '
|
||||
'BYTES, 500K == five hundred KILOBYTES, 1.2M == one '
|
||||
'point two MEGABYTES. 1M == 1048576 BYTES'))
|
||||
'a magnitude indicator either "B", "K", "M", or "G". '
|
||||
'Examples:\n 1000000B == one million BYTES\n'
|
||||
' 1.2M == one point two MEGABYTES\n'
|
||||
' 1M == 1048576 BYTES'))
|
||||
rtn.add_option('-n', '--nocompress', action='store_false', dest='compress',
|
||||
default=True,
|
||||
help=('Whether the archive files should be compressed, or '
|
||||
@@ -76,49 +70,65 @@ def CreateOptionsParser():
|
||||
|
||||
|
||||
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:
|
||||
if options.sourcefiles is None or options.destination is None:
|
||||
parser.print_help()
|
||||
sys.exit(-1)
|
||||
except (AttributeError), err:
|
||||
except AttributeError:
|
||||
parser.print_help()
|
||||
sys.exit(-1)
|
||||
|
||||
|
||||
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:
|
||||
raise ValueError(('filesize argument not understood, please include'
|
||||
' a numeric value and magnitude indicator'))
|
||||
magnitude = size_str[len(size_str)-1:]
|
||||
if not magnitude in ('K', 'B', 'M'):
|
||||
raise ValueError(('filesize magnitude indicator not valid, must be \'K\','
|
||||
'\'B\', or \'M\''))
|
||||
numeral = float(size_str[0:len(size_str)-1])
|
||||
magnitude = size_str[-1]
|
||||
if not magnitude in ('B', 'K', 'M', 'G'):
|
||||
raise ValueError(('filesize magnitude indicator not valid, must be "B",'
|
||||
'"K","M", or "G"'))
|
||||
numeral = float(size_str[:-1])
|
||||
if magnitude == 'K':
|
||||
numeral *= 1024
|
||||
elif magnitude == 'M':
|
||||
numeral *= 1048576
|
||||
elif magnitude == 'G':
|
||||
numeral *= 1073741824
|
||||
return int(numeral)
|
||||
|
||||
|
||||
class DirectoryZipper(object):
|
||||
"""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):
|
||||
"""DirectoryZipper constructor.
|
||||
|
||||
Args:
|
||||
output_path: the path to write the archives and index file to
|
||||
base_dir: the directory to compress
|
||||
archive_size: the maximum size, in bytes, of a single archive file
|
||||
enable_compression: whether or not compression should be enabled, if
|
||||
disabled, the files will be written into an uncompresed zip
|
||||
output_path: A string, the path to write the archives and index file to.
|
||||
base_dir: A string, the directory to compress.
|
||||
archive_size: An number, the maximum size, in bytes, of a single
|
||||
archive file.
|
||||
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.current_archive = '0.zip'
|
||||
@@ -126,6 +136,9 @@ class DirectoryZipper(object):
|
||||
self.max_size = archive_size
|
||||
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):
|
||||
"""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
|
||||
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)
|
||||
os.path.walk(self.base_path, self.CompressDirectory, 1)
|
||||
self.index_fp.write(divide_and_compress_constants.file_endpiece)
|
||||
@@ -149,37 +162,32 @@ class DirectoryZipper(object):
|
||||
Args:
|
||||
archive_path: Path to the archive to modify. This archive should not be
|
||||
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:
|
||||
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
|
||||
ext_offset = archive_path.rfind('.')
|
||||
old_archive = ''.join([archive_path[0:ext_offset], '-old',
|
||||
archive_path[ext_offset:]])
|
||||
# Move the old file and create a new one at its old location.
|
||||
root, ext = os.path.splitext(archive_path)
|
||||
old_archive = ''.join([root, '-old', ext])
|
||||
os.rename(archive_path, old_archive)
|
||||
old_fp = self.OpenZipFileAtPath(old_archive, mode='r')
|
||||
|
||||
# By default, store uncompressed.
|
||||
compress_bit = zipfile.ZIP_STORED
|
||||
if self.compress:
|
||||
compress_bit = zipfile.ZIP_DEFLATED
|
||||
new_fp = self.OpenZipFileAtPath(archive_path,
|
||||
mode='w',
|
||||
compress=zipfile.ZIP_DEFLATED)
|
||||
else:
|
||||
new_fp = self.OpenZipFileAtPath(archive_path,
|
||||
mode='w',
|
||||
compress=zipfile.ZIP_STORED)
|
||||
compress=compress_bit)
|
||||
|
||||
# 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
|
||||
# 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
|
||||
# Close files and delete the old one.
|
||||
old_fp.close()
|
||||
new_fp.close()
|
||||
os.unlink(old_archive)
|
||||
@@ -193,11 +201,11 @@ class DirectoryZipper(object):
|
||||
mode = 'w'
|
||||
|
||||
if mode == 'r':
|
||||
return ZipFile(path, mode)
|
||||
return zipfile.ZipFile(path, mode)
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
Args:
|
||||
irrelevant: a numeric identifier passed by the os.path.walk method, this
|
||||
is not used by this method
|
||||
dir_path: the path to the directory to compress
|
||||
dir_contents: a list of directory contents to be compressed
|
||||
unused_id: A numeric identifier passed by the os.path.walk method, this
|
||||
is not used by this method.
|
||||
dir_path: A string, the path to the directory to compress.
|
||||
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,
|
||||
# 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()
|
||||
zip_queue = []
|
||||
if dir_path[len(dir_path) - 1:] == os.sep:
|
||||
for filename in dir_contents:
|
||||
zip_queue.append(''.join([dir_path, filename]))
|
||||
else:
|
||||
for filename in dir_contents:
|
||||
zip_queue.append(''.join([dir_path, os.sep, filename]))
|
||||
zip_queue.append(os.path.join(dir_path, filename))
|
||||
compress_bit = zipfile.ZIP_DEFLATED
|
||||
if not self.compress:
|
||||
compress_bit = zipfile.ZIP_STORED
|
||||
|
||||
# zip all files in this directory, adding to existing archives and creating
|
||||
# as necessary
|
||||
while len(zip_queue) > 0:
|
||||
# Zip all files in this directory, adding to existing archives and creating
|
||||
# as necessary.
|
||||
while zip_queue:
|
||||
target_file = zip_queue[0]
|
||||
if os.path.isfile(target_file):
|
||||
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 fixing fails, the last added file was to large, skip it
|
||||
# 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'):
|
||||
zip_queue.pop(0)
|
||||
else:
|
||||
@@ -248,7 +251,7 @@ class DirectoryZipper(object):
|
||||
0:self.current_archive.rfind('.zip')]) + 1)
|
||||
else:
|
||||
|
||||
# if this the first file in the archive, write an index record
|
||||
# Write an index record if necessary.
|
||||
self.WriteIndexRecord()
|
||||
zip_queue.pop(0)
|
||||
else:
|
||||
@@ -260,10 +263,10 @@ class DirectoryZipper(object):
|
||||
Only write an index record if this is the first file to go into archive
|
||||
|
||||
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(
|
||||
''.join([self.output_dir, self.current_archive]), 'r')
|
||||
os.path.join(self.output_dir, self.current_archive), 'r')
|
||||
archive_index = archive.infolist()
|
||||
if len(archive_index) == 1:
|
||||
self.index_fp.write(
|
||||
@@ -279,14 +282,14 @@ class DirectoryZipper(object):
|
||||
"""Make the archive compliant.
|
||||
|
||||
Args:
|
||||
problem: the reason the archive is invalid
|
||||
problem: An enum, the reason the archive is invalid.
|
||||
|
||||
Returns:
|
||||
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.
|
||||
"""
|
||||
archive_path = ''.join([self.output_dir, self.current_archive])
|
||||
rtn_value = None
|
||||
archive_path = os.path.join(self.output_dir, self.current_archive)
|
||||
return_value = None
|
||||
|
||||
if problem == 'SIZE':
|
||||
archive_obj = self.OpenZipFileAtPath(archive_path, mode='r')
|
||||
@@ -294,39 +297,41 @@ class DirectoryZipper(object):
|
||||
|
||||
# IF there is a single file, that means its too large to compress,
|
||||
# delete the created archive
|
||||
# ELSE do normal finalization
|
||||
# ELSE do normal finalization.
|
||||
if num_archive_files == 1:
|
||||
print ('WARNING: %s%s is too large to store.' % (
|
||||
self.base_path, archive_obj.infolist()[0].filename))
|
||||
archive_obj.close()
|
||||
os.unlink(archive_path)
|
||||
rtn_value = False
|
||||
return_value = False
|
||||
else:
|
||||
self.RemoveLastFile(''.join([self.output_dir, self.current_archive]))
|
||||
archive_obj.close()
|
||||
self.RemoveLastFile(
|
||||
os.path.join(self.output_dir, self.current_archive))
|
||||
print 'Final archive size for %s is %i' % (
|
||||
self.current_archive, os.stat(archive_path)[stat.ST_SIZE])
|
||||
rtn_value = True
|
||||
return rtn_value
|
||||
self.current_archive, os.path.getsize(archive_path))
|
||||
return_value = True
|
||||
return return_value
|
||||
|
||||
def AddFileToArchive(self, filepath, compress_bit):
|
||||
"""Add the file at filepath to the current archive.
|
||||
|
||||
Args:
|
||||
filepath: the path of the file to add
|
||||
compress_bit: whether or not this fiel should be compressed when added
|
||||
filepath: A string, the path of the file to add.
|
||||
compress_bit: A boolean, whether or not this file should be compressed
|
||||
when added.
|
||||
|
||||
Returns:
|
||||
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])
|
||||
if os.path.isfile(filepath):
|
||||
if os.stat(filepath)[stat.ST_SIZE] > 1048576:
|
||||
curr_archive_path = os.path.join(self.output_dir, self.current_archive)
|
||||
if os.path.isfile(filepath) and not os.path.islink(filepath):
|
||||
if os.path.getsize(filepath) > 1048576:
|
||||
print 'Warning: %s is potentially too large to serve on GAE' % filepath
|
||||
archive = self.OpenZipFileAtPath(curr_archive_path,
|
||||
compress=compress_bit)
|
||||
# add the file to the archive
|
||||
# Add the file to the archive.
|
||||
archive.write(filepath, filepath[len(self.base_path):])
|
||||
archive.close()
|
||||
return True
|
||||
@@ -340,13 +345,22 @@ class DirectoryZipper(object):
|
||||
The thought is that eventually this will do additional validation
|
||||
|
||||
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])
|
||||
if os.stat(archive_path)[stat.ST_SIZE] > self.max_size:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
archive_path = os.path.join(self.output_dir, self.current_archive)
|
||||
return os.path.getsize(archive_path) <= self.max_size
|
||||
|
||||
|
||||
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__':
|
||||
Main(sys.argv)
|
||||
main(sys.argv)
|
||||
|
||||
@@ -19,42 +19,40 @@
|
||||
|
||||
__author__ = 'jmatt@google.com (Justin Mattson)'
|
||||
|
||||
file_preamble = ('#!/usr/bin/env python\n'
|
||||
'#\n'
|
||||
'# Copyright 2008 Google Inc.\n'
|
||||
'#\n'
|
||||
'# Licensed under the Apache License, Version 2.0 (the'
|
||||
'\"License");\n'
|
||||
'# you may not use this file except in compliance with the '
|
||||
'License.\n'
|
||||
'# You may obtain a copy of the License at\n'
|
||||
'#\n'
|
||||
'# http://www.apache.org/licenses/LICENSE-2.0\n'
|
||||
'#\n'
|
||||
'# Unless required by applicable law or agreed to in writing,'
|
||||
' software\n'
|
||||
'# distributed under the License is distributed on an \"AS'
|
||||
'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_preamble = """#!/usr/bin/env python
|
||||
#
|
||||
# Copyright 2008 Google Inc.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
file_endpiece = ('])),\n'
|
||||
'],\n'
|
||||
'debug=False)\n'
|
||||
' wsgiref.handlers.CGIHandler().run(application)\n\n'
|
||||
'if __name__ == \'__main__\':\n'
|
||||
' main()')
|
||||
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):
|
||||
|
||||
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()"""
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
"""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)'
|
||||
@@ -26,10 +26,9 @@ import os
|
||||
import stat
|
||||
import unittest
|
||||
import zipfile
|
||||
from zipfile import ZipFile
|
||||
|
||||
import divide_and_compress
|
||||
from mox import mox
|
||||
import mox
|
||||
|
||||
|
||||
class BagOfParts(object):
|
||||
@@ -58,6 +57,10 @@ class ValidAndRemoveTests(unittest.TestCase):
|
||||
'sdjfljkgsc n;iself')
|
||||
self.files = {'file1': file1, 'file2': file2}
|
||||
|
||||
def tearDown(self):
|
||||
"""Remove any stubs we've created."""
|
||||
self.my_mox.UnsetStubs()
|
||||
|
||||
def testArchiveIsValid(self):
|
||||
"""Test the DirectoryZipper.ArchiveIsValid method.
|
||||
|
||||
@@ -119,7 +122,7 @@ class ValidAndRemoveTests(unittest.TestCase):
|
||||
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.read(self.files['file1'].filename).AndReturn(
|
||||
@@ -137,16 +140,12 @@ class ValidAndRemoveTests(unittest.TestCase):
|
||||
A configured mocked
|
||||
"""
|
||||
|
||||
dest_zip = mox.MockObject(ZipFile)
|
||||
dest_zip = mox.MockObject(zipfile.ZipFile)
|
||||
dest_zip.writestr(self.files['file1'].filename,
|
||||
self.files['file1'].contents)
|
||||
dest_zip.close()
|
||||
return dest_zip
|
||||
|
||||
def tearDown(self):
|
||||
"""Remove any stubs we've created."""
|
||||
self.my_mox.UnsetStubs()
|
||||
|
||||
|
||||
class FixArchiveTests(unittest.TestCase):
|
||||
"""Tests for the DirectoryZipper.FixArchive method."""
|
||||
@@ -158,6 +157,10 @@ class FixArchiveTests(unittest.TestCase):
|
||||
self.file1.filename = 'file1.txt'
|
||||
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):
|
||||
"""Create an array of mock file objects.
|
||||
|
||||
@@ -211,7 +214,7 @@ class FixArchiveTests(unittest.TestCase):
|
||||
Returns:
|
||||
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.close()
|
||||
@@ -250,15 +253,11 @@ class FixArchiveTests(unittest.TestCase):
|
||||
A configured mock object
|
||||
"""
|
||||
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.close()
|
||||
return mock_zip
|
||||
|
||||
def tearDown(self):
|
||||
"""Unset any mocks that we've created."""
|
||||
self.my_mox.UnsetStubs()
|
||||
|
||||
|
||||
class AddFileToArchiveTest(unittest.TestCase):
|
||||
"""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.input_dir = '/foo/bar/baz/'
|
||||
|
||||
def tearDown(self):
|
||||
self.my_mox.UnsetStubs()
|
||||
|
||||
def testAddFileToArchive(self):
|
||||
"""Test the DirectoryZipper.AddFileToArchive method.
|
||||
|
||||
@@ -312,15 +314,12 @@ class AddFileToArchiveTest(unittest.TestCase):
|
||||
Returns:
|
||||
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]),
|
||||
self.file_to_add)
|
||||
archive_mock.close()
|
||||
return archive_mock
|
||||
|
||||
def tearDown(self):
|
||||
self.my_mox.UnsetStubs()
|
||||
|
||||
|
||||
class CompressDirectoryTest(unittest.TestCase):
|
||||
"""Test the master method of the class.
|
||||
@@ -28,7 +28,7 @@ final class PlatformTarget implements IAndroidTarget {
|
||||
/** String used to get a hash to the platform target */
|
||||
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 String mLocation;
|
||||
|
||||
Reference in New Issue
Block a user