Initial support for testing the DeviceCommunicationService

Lots of support classes to enable local testing, without
a device or an emulator.
live-activity-data
cpfeiffer 2015-08-22 01:08:46 +02:00
parent cedd95186f
commit 77cad5c47f
17 changed files with 845 additions and 16 deletions

View File

@ -31,9 +31,16 @@ android {
// optional path to report (default will be lint-results.html in the builddir)
htmlOutput file("$project.buildDir/reports/lint/lint.html")
}
testOptions {
unitTests.returnDefaultValues = true
}
}
dependencies {
testCompile 'junit:junit:4.12'
testCompile "org.mockito:mockito-core:1.9.5"
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:21.0.3'
compile 'com.android.support:support-v4:21.0.3'
@ -41,9 +48,6 @@ dependencies {
compile 'org.slf4j:slf4j-api:1.7.7'
compile 'com.github.PhilJay:MPAndroidChart:2.1.0'
compile 'com.github.pfichtner:durationformatter:0.1.1'
testCompile 'junit:junit:4.12'
// testCompile "org.mockito:mockito-core:1.9.5"
}
check.dependsOn 'findbugs', 'pmd', 'lint'

View File

@ -21,8 +21,8 @@ import nodomain.freeyourgadget.gadgetbridge.database.ActivityDatabaseHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceService;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class GBApplication extends Application {
// Since this class must not log to slf4j, we use plain android.util.Log
@ -39,7 +39,7 @@ public class GBApplication extends Application {
}
protected DeviceService createDeviceService() {
return new GBDeviceService(this, DeviceCommunicationService.class);
return new GBDeviceService(this);
}
@Override
@ -55,6 +55,7 @@ public class GBApplication extends Application {
// StatusPrinter.print(lc);
// Logger logger = LoggerFactory.getLogger(GBApplication.class);
GB.environment = GBEnvironment.createDeviceEnvironment();
mActivityDatabaseHandler = new ActivityDatabaseHandler(context);
// for testing DB stuff
// SQLiteDatabase db = mActivityDatabaseHandler.getWritableDatabase();

View File

@ -0,0 +1,26 @@
package nodomain.freeyourgadget.gadgetbridge;
public class GBEnvironment {
private boolean localTest;
private boolean deviceTest;
public static GBEnvironment createLocalTestEnvironment() {
GBEnvironment env = new GBEnvironment();
env.localTest = true;
return env;
}
public static GBEnvironment createDeviceEnvironment() {
GBEnvironment env = new GBEnvironment();
return env;
}
public final boolean isTest() {
return localTest || deviceTest;
}
public boolean isLocalTest() {
return localTest;
}
}

View File

@ -12,17 +12,18 @@ import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
public class GBDeviceService implements DeviceService {
protected final Context mContext;
protected final Class<? extends Service> mServiceClass;
public GBDeviceService(Context context, Class<? extends Service> serviceClass) {
public GBDeviceService(Context context) {
mContext = context;
mServiceClass = serviceClass;
mServiceClass = DeviceCommunicationService.class;
}
private Intent createIntent() {
protected Intent createIntent() {
Intent startIntent = new Intent(mContext, mServiceClass);
return startIntent;
}

View File

@ -3,5 +3,5 @@ package nodomain.freeyourgadget.gadgetbridge.model;
public enum DeviceType {
UNKNOWN,
PEBBLE,
MIBAND
TEST, MIBAND
}

View File

@ -107,10 +107,12 @@ public class DeviceCommunicationService extends Service {
start(); // ensure started
String btDeviceAddress = intent.getStringExtra(EXTRA_DEVICE_ADDRESS);
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
if (btDeviceAddress == null) {
btDeviceAddress = sharedPrefs.getString("last_device_address", null);
} else {
sharedPrefs.edit().putString("last_device_address", btDeviceAddress).apply();
if (sharedPrefs != null) { // may be null in test cases
if (btDeviceAddress == null) {
btDeviceAddress = sharedPrefs.getString("last_device_address", null);
} else {
sharedPrefs.edit().putString("last_device_address", btDeviceAddress).apply();
}
}
if (btDeviceAddress != null && !isConnecting() && !isConnected()) {

View File

@ -5,6 +5,7 @@ import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.widget.Toast;
import java.lang.reflect.Constructor;
import java.util.EnumSet;
import nodomain.freeyourgadget.gadgetbridge.GBException;
@ -26,10 +27,17 @@ public class DeviceSupportFactory {
public synchronized DeviceSupport createDeviceSupport(String deviceAddress) throws GBException {
DeviceSupport deviceSupport;
if (deviceAddress.indexOf(":") == deviceAddress.lastIndexOf(":")) { // only one colon
deviceSupport = createTCPDeviceSupport(deviceAddress);
int indexFirstColon = deviceAddress.indexOf(":");
if (indexFirstColon > 0) {
if (indexFirstColon == deviceAddress.lastIndexOf(":")) { // only one colon
deviceSupport = createTCPDeviceSupport(deviceAddress);
} else {
// multiple colons -- bt?
deviceSupport = createBTDeviceSupport(deviceAddress);
}
} else {
deviceSupport = createBTDeviceSupport(deviceAddress);
// no colon at all, maybe a class name?
deviceSupport = createClassNameDeviceSupport(deviceAddress);
}
if (deviceSupport != null) {
@ -41,6 +49,21 @@ public class DeviceSupportFactory {
return null;
}
private DeviceSupport createClassNameDeviceSupport(String className) throws GBException {
try {
Class<?> deviceSupportClass = Class.forName(className);
Constructor<?> constructor = deviceSupportClass.getConstructor();
DeviceSupport support = (DeviceSupport) constructor.newInstance();
// has to create the device itself
support.setContext(null, null, mContext);
return support;
} catch (ClassNotFoundException e) {
return null; // not a class, or not known at least
} catch (Exception e) {
throw new GBException("Error creating DeviceSupport instance for " + className, e);
}
}
private void checkBtAvailability() {
if (mBtAdapter == null) {
GB.toast(mContext.getString(R.string.bluetooth_is_not_supported_), Toast.LENGTH_SHORT, GB.WARN);

View File

@ -24,6 +24,7 @@ import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.GBEnvironment;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenter;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventScreenshot;
@ -42,6 +43,7 @@ public class GB {
public static final int INFO = 1;
public static final int WARN = 2;
public static final int ERROR = 3;
public static GBEnvironment environment;
public static Notification createNotification(String text, Context context) {
Intent notificationIntent = new Intent(context, ControlCenter.class);
@ -224,6 +226,9 @@ public class GB {
* @param ex optional exception to be logged
*/
public static void toast(final Context context, final String message, final int displayTime, final int severity, final Throwable ex) {
if (env().isLocalTest()) {
return;
}
Looper mainLooper = Looper.getMainLooper();
if (Thread.currentThread() == mainLooper.getThread()) {
log(message, severity, ex);
@ -291,4 +296,8 @@ public class GB {
NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
nm.notify(NOTIFICATION_ID_INSTALL, notification);
}
public static GBEnvironment env() {
return environment;
}
}

View File

@ -0,0 +1,95 @@
package nodomain.freeyourgadget.gadgetbridge.service;
import android.app.Application;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import junit.framework.Assert;
import org.junit.After;
import org.junit.Before;
import nodomain.freeyourgadget.gadgetbridge.test.GBMockApplication;
import nodomain.freeyourgadget.gadgetbridge.test.GBMockContext;
import nodomain.freeyourgadget.gadgetbridge.test.GBMockPackageManager;
import nodomain.freeyourgadget.gadgetbridge.test.MockHelper;
public abstract class AbstractServiceTestCase<T extends Service> {
private static final int ID = -1; // currently not supported
private Class<T> mServiceClass;
private T mServiceInstance;
private Context mContext;
private Application mApplication;
private boolean wasStarted;
private PackageManager mPackageManager;
private NotificationManager mNotificationManager;
private MockHelper mMockHelper;
protected AbstractServiceTestCase(Class<T> serviceClass) {
mServiceClass = serviceClass;
Assert.assertNotNull(serviceClass);
}
public Context getContext() {
return mContext;
}
@Before
public void setUp() throws Exception {
mMockHelper = new MockHelper();
mPackageManager = createPackageManager();
mApplication = createApplication(mPackageManager);
mContext = createContext(mApplication);
mNotificationManager = mMockHelper.createNotificationManager(mContext);
mServiceInstance = createService(mServiceClass, mApplication, mNotificationManager);
}
@After
public void tearDown() throws Exception {
if (mServiceInstance != null) {
stopService();
}
}
public void startService(Intent intent) {
if (!wasStarted) {
wasStarted = true;
mServiceInstance.onCreate();
}
mServiceInstance.onStartCommand(intent, Service.START_FLAG_REDELIVERY, ID);
}
public void stopService() {
mServiceInstance.onDestroy();
mServiceInstance = null;
}
protected Application createApplication(PackageManager packageManager) {
return new GBMockApplication(packageManager);
}
protected PackageManager createPackageManager() {
return new GBMockPackageManager();
}
protected Application getApplication() {
return mApplication;
}
protected Context createContext(final Application application) {
return new GBMockContext(application);
}
private T createService(Class<T> serviceClass, Application application, NotificationManager notificationManager) throws Exception {
T service = mMockHelper.createService(serviceClass, application);
mMockHelper.addSystemServiceTo(service, Context.NOTIFICATION_SERVICE, getNotificationService());
return service;
}
private NotificationManager getNotificationService() {
return mNotificationManager;
}
}

View File

@ -0,0 +1,37 @@
package nodomain.freeyourgadget.gadgetbridge.service;
import android.test.ServiceTestCase;
import junit.framework.TestCase;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import nodomain.freeyourgadget.gadgetbridge.test.TestDeviceSupport;
public class DeviceCommunicationServiceTestCase extends AbstractServiceTestCase<DeviceCommunicationService> {
private static final java.lang.String TEST_DEVICE_ADDRESS = TestDeviceSupport.class.getName();
private TestDeviceService mDeviceService;
public DeviceCommunicationServiceTestCase() {
super(DeviceCommunicationService.class);
}
@Before
public void setUp() throws Exception {
super.setUp();
mDeviceService = new TestDeviceService(this);
}
@Test
public void testStart() {
mDeviceService.start();
}
@Test
public void testConnect() {
mDeviceService.connect(TEST_DEVICE_ADDRESS);
}
}

View File

@ -0,0 +1,25 @@
package nodomain.freeyourgadget.gadgetbridge.service;
import android.content.Intent;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceService;
import nodomain.freeyourgadget.gadgetbridge.test.GBMockIntent;
public class TestDeviceService extends GBDeviceService {
private final AbstractServiceTestCase<?> mTestCase;
public TestDeviceService(AbstractServiceTestCase<?> testCase) throws Exception {
super(testCase.getContext());
mTestCase = testCase;
}
@Override
protected Intent createIntent() {
return new GBMockIntent();
}
@Override
protected void invokeService(Intent intent) {
mTestCase.startService(intent);
}
}

View File

@ -0,0 +1,27 @@
package nodomain.freeyourgadget.gadgetbridge.test;
import android.content.Context;
import android.content.pm.PackageManager;
import android.test.mock.MockApplication;
import nodomain.freeyourgadget.gadgetbridge.GBEnvironment;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class GBMockApplication extends MockApplication {
private final PackageManager mPackageManager;
public GBMockApplication(PackageManager packageManager) {
GB.environment = GBEnvironment.createDeviceEnvironment().createLocalTestEnvironment();
mPackageManager = packageManager;
}
@Override
public Context getApplicationContext() {
return this;
}
@Override
public PackageManager getPackageManager() {
return mPackageManager;
}
}

View File

@ -0,0 +1,24 @@
package nodomain.freeyourgadget.gadgetbridge.test;
import android.app.Application;
import android.content.Context;
import android.content.pm.PackageManager;
import android.test.mock.MockContext;
public class GBMockContext extends MockContext {
private final Application mApplication;
public GBMockContext(Application application) {
mApplication = application;
}
@Override
public Context getApplicationContext() {
return mApplication;
}
@Override
public PackageManager getPackageManager() {
return mApplication.getPackageManager();
}
}

View File

@ -0,0 +1,387 @@
package nodomain.freeyourgadget.gadgetbridge.test;
import android.content.Intent;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class GBMockIntent extends Intent {
private String mAction;
private Map<String,Object> extras = new HashMap<>();
@NonNull
@Override
public Intent setAction(String action) {
mAction = action;
return this;
}
@Override
public String getAction() {
return mAction;
}
@NonNull
@Override
public Intent putExtra(String name, boolean value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, byte value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, char value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, short value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, int value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, long value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, float value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, double value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, String value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, CharSequence value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, Parcelable value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, Parcelable[] value) {
extras.put(name, value);
return this;
}
@Override
public Intent putParcelableArrayListExtra(String name, ArrayList<? extends Parcelable> value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putIntegerArrayListExtra(String name, ArrayList<Integer> value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putStringArrayListExtra(String name, ArrayList<String> value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putCharSequenceArrayListExtra(String name, ArrayList<CharSequence> value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, Serializable value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, boolean[] value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, byte[] value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, short[] value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, char[] value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, int[] value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, long[] value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, float[] value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, double[] value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, String[] value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, CharSequence[] value) {
extras.put(name, value);
return this;
}
@NonNull
@Override
public Intent putExtra(String name, Bundle value) {
extras.put(name, value);
return this;
}
@Override
public boolean getBooleanExtra(String name, boolean defaultValue) {
if (extras.containsKey(name)) {
return (boolean) extras.get(name);
}
return defaultValue;
}
@Override
public byte getByteExtra(String name, byte defaultValue) {
if (extras.containsKey(name)) {
return (byte) extras.get(name);
}
return defaultValue;
}
@Override
public short getShortExtra(String name, short defaultValue) {
if (extras.containsKey(name)) {
return (short) extras.get(name);
}
return defaultValue;
}
@Override
public char getCharExtra(String name, char defaultValue) {
if (extras.containsKey(name)) {
return (char) extras.get(name);
}
return defaultValue;
}
@Override
public int getIntExtra(String name, int defaultValue) {
if (extras.containsKey(name)) {
return (int) extras.get(name);
}
return defaultValue;
}
@Override
public long getLongExtra(String name, long defaultValue) {
if (extras.containsKey(name)) {
return (long) extras.get(name);
}
return defaultValue;
}
@Override
public float getFloatExtra(String name, float defaultValue) {
if (extras.containsKey(name)) {
return (float) extras.get(name);
}
return defaultValue;
}
@Override
public double getDoubleExtra(String name, double defaultValue) {
if (extras.containsKey(name)) {
return (double) extras.get(name);
}
return defaultValue;
}
@Override
public CharSequence getCharSequenceExtra(String name) {
return (CharSequence) extras.get(name);
}
@Override
public <T extends Parcelable> T getParcelableExtra(String name) {
return (T) extras.get(name);
}
@Override
public Parcelable[] getParcelableArrayExtra(String name) {
return (Parcelable[]) extras.get(name);
}
@Override
public <T extends Parcelable> ArrayList<T> getParcelableArrayListExtra(String name) {
return (ArrayList<T>) extras.get(name);
}
@Override
public Serializable getSerializableExtra(String name) {
return (Serializable) extras.get(name);
}
@Override
public ArrayList<Integer> getIntegerArrayListExtra(String name) {
return (ArrayList<Integer>) extras.get(name);
}
@Override
public ArrayList<String> getStringArrayListExtra(String name) {
return (ArrayList<String>) extras.get(name);
}
@Override
public ArrayList<CharSequence> getCharSequenceArrayListExtra(String name) {
return (ArrayList<CharSequence>) extras.get(name);
}
@Override
public boolean[] getBooleanArrayExtra(String name) {
return (boolean[]) extras.get(name);
}
@Override
public byte[] getByteArrayExtra(String name) {
return (byte[]) extras.get(name);
}
@Override
public short[] getShortArrayExtra(String name) {
return (short[]) extras.get(name);
}
@Override
public char[] getCharArrayExtra(String name) {
return (char[]) extras.get(name);
}
@Override
public int[] getIntArrayExtra(String name) {
return (int[]) extras.get(name);
}
@Override
public long[] getLongArrayExtra(String name) {
return (long[]) extras.get(name);
}
@Override
public float[] getFloatArrayExtra(String name) {
return (float[]) extras.get(name);
}
@Override
public double[] getDoubleArrayExtra(String name) {
return (double[]) extras.get(name);
}
@Override
public String[] getStringArrayExtra(String name) {
return (String[]) extras.get(name);
}
@Override
public CharSequence[] getCharSequenceArrayExtra(String name) {
return (CharSequence[]) extras.get(name);
}
@Override
public String getStringExtra(String name) {
return (String) extras.get(name);
}
@Override
public String toString() {
return "GBMockIntent: " + mAction;
}
}

View File

@ -0,0 +1,11 @@
package nodomain.freeyourgadget.gadgetbridge.test;
import android.content.ComponentName;
import android.test.mock.MockPackageManager;
public class GBMockPackageManager extends MockPackageManager {
@Override
public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags) {
// do nothing
}
}

View File

@ -0,0 +1,35 @@
package nodomain.freeyourgadget.gadgetbridge.test;
import android.app.Application;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import junit.framework.Assert;
import org.mockito.Mockito;
import java.lang.reflect.Constructor;
public class MockHelper {
public <T extends Service> NotificationManager createNotificationManager(Context mContext) throws Exception {
Constructor<?>[] constructors = NotificationManager.class.getDeclaredConstructors();
constructors[0].setAccessible(true);
Class<?>[] parameterTypes = constructors[0].getParameterTypes();
return (NotificationManager) constructors[0].newInstance();
}
public <T extends Service> T createService(Class<T> serviceClass, Application application) throws Exception {
Constructor<T> constructor = serviceClass.getConstructor();
Assert.assertNotNull(constructor);
T realService = constructor.newInstance();
T mockedService = Mockito.spy(realService);
Mockito.when(mockedService.getApplicationContext()).thenReturn(application);
Mockito.when(mockedService.getPackageManager()).thenReturn(application.getPackageManager());
return mockedService;
}
public void addSystemServiceTo(Context context, String serviceName, Object service) {
Mockito.when(context.getSystemService(serviceName)).thenReturn(service);
}
}

View File

@ -0,0 +1,122 @@
package nodomain.freeyourgadget.gadgetbridge.test;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.Nullable;
import java.util.ArrayList;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
public class TestDeviceSupport extends AbstractDeviceSupport {
public TestDeviceSupport() {
}
@Override
public void setContext(GBDevice gbDevice, BluetoothAdapter btAdapter, Context context) {
gbDevice = new GBDevice(getClass().getName(), "Test Device", DeviceType.TEST);
super.setContext(gbDevice, btAdapter, context);
}
@Override
public boolean connect() {
return false;
}
@Override
public void dispose() {
}
@Override
public boolean useAutoConnect() {
return false;
}
@Override
public void pair() {
}
@Override
public void onSMS(String from, String body) {
}
@Override
public void onEmail(String from, String subject, String body) {
}
@Override
public void onGenericNotification(String title, String details) {
}
@Override
public void onSetTime() {
}
@Override
public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
}
@Override
public void onSetCallState(@Nullable String number, @Nullable String name, ServiceCommand command) {
}
@Override
public void onSetMusicInfo(String artist, String album, String track) {
}
@Override
public void onInstallApp(Uri uri) {
}
@Override
public void onAppInfoReq() {
}
@Override
public void onAppStart(UUID uuid) {
}
@Override
public void onAppDelete(UUID uuid) {
}
@Override
public void onFetchActivityData() {
}
@Override
public void onReboot() {
}
@Override
public void onFindDevice(boolean start) {
}
@Override
public void onScreenshotReq() {
}
}