1
0
Fork 0
mirror of https://github.com/syncthing/syncthing-android.git synced 2025-01-11 04:25:53 +00:00

Added various unit tests.

This commit is contained in:
Felix Ableitner 2014-08-23 14:12:12 +02:00
parent 2e83305b93
commit 7b3d1b4052
20 changed files with 412 additions and 33 deletions

View file

@ -20,6 +20,7 @@ repositories {
dependencies { dependencies {
compile 'com.android.support:appcompat-v7:19.1.+' compile 'com.android.support:appcompat-v7:19.1.+'
androidTestCompile 'com.squareup.okhttp:mockwebserver:2.0.0'
compile project(':android-support-v4-preferencefragment') compile project(':android-support-v4-preferencefragment')
} }

View file

@ -1,12 +1,15 @@
package com.nutomic.syncthingandroid.test; package com.nutomic.syncthingandroid.test;
import android.content.BroadcastReceiver;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.Resources;
import android.test.mock.MockContext; import android.test.mock.MockContext;
import junit.framework.Test; import junit.framework.Test;
import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -16,6 +19,7 @@ import java.util.List;
public class TestContext extends MockContext { public class TestContext extends MockContext {
private Context mContext; private Context mContext;
private ArrayList<Intent> mReceivedIntents = new ArrayList<>(); private ArrayList<Intent> mReceivedIntents = new ArrayList<>();
/** /**
@ -50,4 +54,26 @@ public class TestContext extends MockContext {
mReceivedIntents.clear(); mReceivedIntents.clear();
} }
private BroadcastReceiver mLastUnregistered;
@Override
public void unregisterReceiver(BroadcastReceiver receiver) {
mLastUnregistered = receiver;
}
public BroadcastReceiver getLastUnregistered() {
return mLastUnregistered;
}
@Override
public File getFilesDir() {
File testFilesDir = new File(mContext.getFilesDir(), "test/");
testFilesDir.mkdir();
return testFilesDir;
}
@Override
public Resources getResources() {
return mContext.getResources();
}
} }

View file

@ -41,7 +41,7 @@ public class BatteryReceiverTest extends AndroidTestCase {
assertEquals(1, mContext.getReceivedIntents().size()); assertEquals(1, mContext.getReceivedIntents().size());
Intent receivedIntent = mContext.getReceivedIntents().get(0); Intent receivedIntent = mContext.getReceivedIntents().get(0);
assertEquals(receivedIntent.getComponent().getClassName(), SyncthingService.class.getName()); assertEquals(SyncthingService.class.getName(), receivedIntent.getComponent().getClassName());
assertFalse(receivedIntent.getBooleanExtra(DeviceStateHolder.EXTRA_IS_CHARGING, true)); assertFalse(receivedIntent.getBooleanExtra(DeviceStateHolder.EXTRA_IS_CHARGING, true));
mContext.clearReceivedIntents(); mContext.clearReceivedIntents();
} }

View file

@ -30,7 +30,7 @@ public class BootReceiverTest extends AndroidTestCase {
assertEquals(1, mContext.getReceivedIntents().size()); assertEquals(1, mContext.getReceivedIntents().size());
Intent receivedIntent = mContext.getReceivedIntents().get(0); Intent receivedIntent = mContext.getReceivedIntents().get(0);
assertEquals(receivedIntent.getComponent().getClassName(), SyncthingService.class.getName()); assertEquals(SyncthingService.class.getName(), receivedIntent.getComponent().getClassName());
mContext.clearReceivedIntents(); mContext.clearReceivedIntents();
} }
} }

View file

@ -0,0 +1,64 @@
package com.nutomic.syncthingandroid.test.syncthing;
import android.content.Intent;
import android.os.BatteryManager;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
import com.nutomic.syncthingandroid.syncthing.DeviceStateHolder;
import com.nutomic.syncthingandroid.test.TestContext;
public class DeviceStateHolderTest extends AndroidTestCase {
private DeviceStateHolder mReceiver;
private TestContext mContext;
@Override
protected void setUp() throws Exception {
super.setUp();
mReceiver = new DeviceStateHolder(getContext());
mContext = new TestContext(null);
}
@MediumTest
public void testIsCharging() {
Intent i = new Intent();
i.putExtra(DeviceStateHolder.EXTRA_IS_CHARGING, false);
mReceiver.update(i);
assertFalse(mReceiver.isCharging());
i.putExtra(DeviceStateHolder.EXTRA_IS_CHARGING, true);
mReceiver.update(i);
assertTrue(mReceiver.isCharging());
}
@MediumTest
public void testWifiConnected() {
Intent i = new Intent();
i.putExtra(DeviceStateHolder.EXTRA_HAS_WIFI, false);
mReceiver.update(i);
assertFalse(mReceiver.isWifiConnected());
i.putExtra(DeviceStateHolder.EXTRA_HAS_WIFI, true);
mReceiver.update(i);
assertTrue(mReceiver.isWifiConnected());
}
@MediumTest
public void testonReceiveInitialChargingState() {
Intent i = new Intent();
mReceiver.onReceive(mContext, i);
assertFalse(mReceiver.isCharging());
assertEquals(mContext.getLastUnregistered(), mReceiver);
i.putExtra(BatteryManager.EXTRA_PLUGGED, 0);
mReceiver.onReceive(mContext, i);
assertFalse(mReceiver.isCharging());
i.putExtra(BatteryManager.EXTRA_PLUGGED, 1);
mReceiver.onReceive(mContext, i);
assertTrue(mReceiver.isCharging());
}
}

View file

@ -0,0 +1,75 @@
package com.nutomic.syncthingandroid.test.syncthing;
import android.net.Uri;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.MediumTest;
import android.util.Log;
import com.nutomic.syncthingandroid.syncthing.GetTask;
import com.nutomic.syncthingandroid.syncthing.RestApi;
import com.squareup.okhttp.mockwebserver.MockResponse;
import com.squareup.okhttp.mockwebserver.MockWebServer;
import com.squareup.okhttp.mockwebserver.RecordedRequest;
import java.io.IOException;
public class GetTaskTest extends AndroidTestCase {
private MockWebServer mServer;
private static final String RESPONSE = "the response";
private static final String API_KEY = "the key";
private static final String PARAM_KEY_ONE = "first-param";
private static final String PARAM_VALUE_ONE = "first param value";
@Override
protected void setUp() throws Exception {
super.setUp();
mServer = new MockWebServer();
mServer.enqueue(new MockResponse().setBody(RESPONSE));
mServer.play();
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
// TODO: causes problems, see https://github.com/square/okhttp/issues/1033
//mServer.shutdown();
}
@MediumTest
public void testGetNoParams() throws IOException, InterruptedException {
new GetTask() {
@Override
protected void onPostExecute(String s) {
assertEquals(RESPONSE, s);
}
}.execute(mServer.getUrl("").toString(), GetTask.URI_CONFIG, API_KEY);
RecordedRequest request = mServer.takeRequest();
assertEquals(API_KEY, request.getHeader(RestApi.HEADER_API_KEY));
Uri uri = Uri.parse(request.getPath());
assertEquals(GetTask.URI_CONFIG, uri.getPath());
}
@MediumTest
public void testGetParams() throws IOException, InterruptedException {
new GetTask() {
@Override
protected void onPostExecute(String s) {
assertEquals(RESPONSE, s);
}
}.execute(mServer.getUrl("").toString(), GetTask.URI_CONFIG, API_KEY, PARAM_KEY_ONE,
PARAM_VALUE_ONE);
RecordedRequest request = mServer.takeRequest();
assertEquals(API_KEY, request.getHeader(RestApi.HEADER_API_KEY));
Uri uri = Uri.parse(request.getPath());
assertEquals(GetTask.URI_CONFIG, uri.getPath());
assertEquals(PARAM_VALUE_ONE, uri.getQueryParameter(PARAM_KEY_ONE));
}
}

View file

@ -34,7 +34,7 @@ public class NetworkReceiverTest extends AndroidTestCase {
assertEquals(1, mContext.getReceivedIntents().size()); assertEquals(1, mContext.getReceivedIntents().size());
Intent receivedIntent = mContext.getReceivedIntents().get(0); Intent receivedIntent = mContext.getReceivedIntents().get(0);
assertEquals(receivedIntent.getComponent().getClassName(), SyncthingService.class.getName()); assertEquals(SyncthingService.class.getName(), receivedIntent.getComponent().getClassName());
assertNull(receivedIntent.getAction()); assertNull(receivedIntent.getAction());
assertTrue(receivedIntent.hasExtra(DeviceStateHolder.EXTRA_HAS_WIFI)); assertTrue(receivedIntent.hasExtra(DeviceStateHolder.EXTRA_HAS_WIFI));
mContext.clearReceivedIntents(); mContext.clearReceivedIntents();

View file

@ -0,0 +1,58 @@
package com.nutomic.syncthingandroid.test.syncthing;
import android.net.Uri;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.MediumTest;
import android.util.Log;
import com.nutomic.syncthingandroid.syncthing.GetTask;
import com.nutomic.syncthingandroid.syncthing.PostTask;
import com.nutomic.syncthingandroid.syncthing.RestApi;
import com.squareup.okhttp.mockwebserver.MockResponse;
import com.squareup.okhttp.mockwebserver.MockWebServer;
import com.squareup.okhttp.mockwebserver.RecordedRequest;
import java.io.IOException;
public class PostTaskTest extends AndroidTestCase {
private MockWebServer mServer;
private static final String RESPONSE = "the response";
private static final String API_KEY = "the key";
@Override
protected void setUp() throws Exception {
super.setUp();
mServer = new MockWebServer();
mServer.enqueue(new MockResponse().setBody(RESPONSE));
mServer.play();
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
// TODO: causes problems, see https://github.com/square/okhttp/issues/1033
//mServer.shutdown();
}
@MediumTest
public void testGetNoContent() throws IOException, InterruptedException {
new GetTask() {
@Override
protected void onPostExecute(String s) {
assertEquals(RESPONSE, s);
}
}.execute(mServer.getUrl("").toString(), PostTask.URI_CONFIG, API_KEY);
RecordedRequest request = mServer.takeRequest();
assertEquals(API_KEY, request.getHeader(RestApi.HEADER_API_KEY));
Uri uri = Uri.parse(request.getPath());
assertEquals(PostTask.URI_CONFIG, uri.getPath());
}
// TODO: add a test with post content (but request.getUtf8Body() is always empty)
}

View file

@ -0,0 +1,25 @@
package com.nutomic.syncthingandroid.test.syncthing;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import com.nutomic.syncthingandroid.syncthing.SyncthingRunnable;
import com.nutomic.syncthingandroid.test.TestContext;
import java.io.File;
public class SyncthingRunnableTest extends AndroidTestCase {
@SmallTest
public void testRunning() throws InterruptedException {
TestContext context = new TestContext(getContext());
File testFile = new File(context.getFilesDir(), "was_running");
assertFalse(testFile.exists());
String x = testFile.getAbsolutePath();
// Inject a differenct command instead of the syncthing binary for testing.
new SyncthingRunnable(context, "touch " + testFile.getAbsolutePath() + "\n").run();
assertTrue(testFile.exists());
testFile.delete();
}
}

View file

@ -1,7 +1,18 @@
package com.nutomic.syncthingandroid.test.syncthing; package com.nutomic.syncthingandroid.test.syncthing;
/** import android.test.AndroidTestCase;
* Created by felix on 22.08.14. import android.test.suitebuilder.annotation.SmallTest;
*/
public class SyncthingServiceBinderTest { import com.nutomic.syncthingandroid.syncthing.SyncthingService;
import com.nutomic.syncthingandroid.syncthing.SyncthingServiceBinder;
public class SyncthingServiceBinderTest extends AndroidTestCase {
@SmallTest
public void testBinder() {
SyncthingService service = new SyncthingService();
SyncthingServiceBinder binder = new SyncthingServiceBinder(service);
assertEquals(service, binder.getService());
}
} }

View file

@ -0,0 +1,71 @@
package com.nutomic.syncthingandroid.test.util;
import android.content.Intent;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
import com.nutomic.syncthingandroid.syncthing.RestApi;
import com.nutomic.syncthingandroid.syncthing.SyncthingRunnable;
import com.nutomic.syncthingandroid.syncthing.SyncthingService;
import com.nutomic.syncthingandroid.test.TestContext;
import com.nutomic.syncthingandroid.util.ConfigXml;
import java.io.File;
public class ConfigXmlTest extends AndroidTestCase {
private TestContext mContext;
private ConfigXml mConfig;
@Override
protected void setUp() throws Exception {
super.setUp();
mContext = new TestContext(getContext());
assertFalse(ConfigXml.getConfigFile(mContext).exists());
mConfig = new ConfigXml(mContext);
assertTrue(ConfigXml.getConfigFile(mContext).exists());
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
ConfigXml.getConfigFile(mContext).delete();
}
@SmallTest
public void testGetWebGuiUrl() {
assertEquals("http://127.0.0.1:8080", mConfig.getWebGuiUrl());
}
/**
* Just make sure the file is actually changed.
*
* This is not ideal, but way less complicated than starting up syncthing and accessing the API.
*/
@SmallTest
public void testCreateCameraRepo() {
long oldTime = ConfigXml.getConfigFile(mContext).lastModified();
long oldSize = ConfigXml.getConfigFile(mContext).length();
mConfig.createCameraRepo();
assertNotSame(oldTime, ConfigXml.getConfigFile(mContext).lastModified());
assertNotSame(oldSize, ConfigXml.getConfigFile(mContext).lastModified());
}
/**
* Same as {@link #testCreateCameraRepo()}.
*/
@MediumTest
public void testUpdateIfNeeded() {
long oldTime = ConfigXml.getConfigFile(mContext).lastModified();
long oldSize = ConfigXml.getConfigFile(mContext).length();
mConfig.updateIfNeeded();
assertNotSame(oldTime, ConfigXml.getConfigFile(mContext).lastModified());
assertNotSame(oldSize, ConfigXml.getConfigFile(mContext).lastModified());
assertNotNull(mConfig.getApiKey());
}
}

View file

@ -0,0 +1,16 @@
package com.nutomic.syncthingandroid.test.util;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import com.nutomic.syncthingandroid.util.ExtendedCheckBoxPreference;
public class ExtendedCheckBoxPreferenceTest extends AndroidTestCase {
@SmallTest
public void testExtendedCheckBoxPreference() {
Object o = new Object();
ExtendedCheckBoxPreference cb = new ExtendedCheckBoxPreference(getContext(), o);
assertEquals(cb.getObject(), o);
}
}

View file

@ -0,0 +1,18 @@
<configuration version="2">
<gui enabled="true">
<address>127.0.0.1:8080</address>
</gui>
<options>
<listenAddress>0.0.0.0:22000</listenAddress>
<globalAnnounceServer>194.126.249.5:22026</globalAnnounceServer>
<globalAnnounceEnabled>true</globalAnnounceEnabled>
<localAnnounceEnabled>true</localAnnounceEnabled>
<parallelRequests>16</parallelRequests>
<maxSendKbps>0</maxSendKbps>
<rescanIntervalS>60</rescanIntervalS>
<reconnectionIntervalS>60</reconnectionIntervalS>
<maxChangeKbps>1000</maxChangeKbps>
<startBrowser>false</startBrowser>
<upnpEnabled>true</upnpEnabled>
</options>
</configuration>

View file

@ -25,27 +25,25 @@ public class DeviceStateHolder extends BroadcastReceiver {
*/ */
public static final String EXTRA_IS_CHARGING = "is_charging"; public static final String EXTRA_IS_CHARGING = "is_charging";
private boolean mIsInitialized = false;
private boolean mIsWifiConnected = false; private boolean mIsWifiConnected = false;
private boolean mIsCharging = false; private boolean mIsCharging = false;
private SyncthingService mService; public DeviceStateHolder(Context context) {
public DeviceStateHolder(SyncthingService service) {
mService = service;
ConnectivityManager cm = (ConnectivityManager) ConnectivityManager cm = (ConnectivityManager)
mService.getSystemService(Context.CONNECTIVITY_SERVICE); context.getSystemService(Context.CONNECTIVITY_SERVICE);
mIsWifiConnected = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI).isConnected(); mIsWifiConnected = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI).isConnected();
} }
/**
* Receiver for {@link Intent#ACTION_BATTERY_CHANGED}, which is used to determine the initial
* charging state.
*/
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
context.unregisterReceiver(this); context.unregisterReceiver(this);
int status = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); int status = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
mIsCharging = status != 0; mIsCharging = status != 0;
mIsInitialized = true;
} }
public boolean isCharging() { public boolean isCharging() {

View file

@ -56,7 +56,7 @@ public class GetTask extends AsyncTask<String, Void, String> {
fullUri += "?" + URLEncodedUtils.format(urlParams, "utf-8"); fullUri += "?" + URLEncodedUtils.format(urlParams, "utf-8");
} }
HttpGet get = new HttpGet(fullUri); HttpGet get = new HttpGet(fullUri);
get.addHeader(new BasicHeader("X-API-Key", params[2])); get.addHeader(new BasicHeader(RestApi.HEADER_API_KEY, params[2]));
try { try {
HttpResponse response = httpclient.execute(get); HttpResponse response = httpclient.execute(get);

View file

@ -35,7 +35,7 @@ public class PostTask extends AsyncTask<String, Void, Void> {
String fullUri = params[0] + params[1]; String fullUri = params[0] + params[1];
HttpClient httpclient = new DefaultHttpClient(); HttpClient httpclient = new DefaultHttpClient();
HttpPost post = new HttpPost(fullUri); HttpPost post = new HttpPost(fullUri);
post.addHeader(new BasicHeader("X-API-Key", params[2])); post.addHeader(new BasicHeader(RestApi.HEADER_API_KEY, params[2]));
try { try {
if (params.length > 3) { if (params.length > 3) {

View file

@ -48,6 +48,11 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener {
*/ */
public static final String TYPE_GUI = "GUI"; public static final String TYPE_GUI = "GUI";
/**
* The name of the HTTP header used for the syncthing API key.
*/
public static final String HEADER_API_KEY = "X-API-Key";
/** /**
* Key of the map element containing connection info for the local node, in the return * Key of the map element containing connection info for the local node, in the return
* value of {@link #getConnections} * value of {@link #getConnections}

View file

@ -24,17 +24,20 @@ public class SyncthingRunnable implements Runnable {
private static final String TAG_NATIVE = "SyncthingNativeCode"; private static final String TAG_NATIVE = "SyncthingNativeCode";
/**
* Path to the native, integrated syncthing binary, relative to the data folder
*/
private static final String BINARY_NAME = "lib/libsyncthing.so";
private Handler mHandler; private Handler mHandler;
private String mCommand;
private Context mContext; private Context mContext;
public SyncthingRunnable(Context context) { /**
* Constructs instance.
*
* @param command The exact command to be executed on the shell.
*/
public SyncthingRunnable(Context context, String command) {
mContext = context; mContext = context;
mCommand = command;
mHandler = new Handler(); mHandler = new Handler();
} }
@ -49,8 +52,7 @@ public class SyncthingRunnable implements Runnable {
// Set home directory to data folder for syncthing to use. // Set home directory to data folder for syncthing to use.
dos.writeBytes("HOME=" + mContext.getFilesDir() + " "); dos.writeBytes("HOME=" + mContext.getFilesDir() + " ");
// Call syncthing with -home (as it would otherwise use "~/.config/syncthing/". // Call syncthing with -home (as it would otherwise use "~/.config/syncthing/".
dos.writeBytes(mContext.getApplicationInfo().dataDir + "/" + BINARY_NAME + " " + dos.writeBytes(mCommand + " -home " + mContext.getFilesDir() + "\n");
"-home " + mContext.getFilesDir() + "\n");
dos.writeBytes("exit\n"); dos.writeBytes("exit\n");
dos.flush(); dos.flush();
@ -68,7 +70,8 @@ public class SyncthingRunnable implements Runnable {
process.destroy(); process.destroy();
final int retVal = ret; final int retVal = ret;
if (ret != 0) { if (ret != 0) {
Log.w(TAG_NATIVE, "Syncthing binary crashed with error code " + Integer.toString(retVal)); Log.w(TAG_NATIVE, "Syncthing binary crashed with error code " +
Integer.toString(retVal));
postCrashDialog(retVal); postCrashDialog(retVal);
} }
} }
@ -88,15 +91,13 @@ public class SyncthingRunnable implements Runnable {
.setPositiveButton(android.R.string.ok, .setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() { new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialogInterface, public void onClick(DialogInterface dialogInterface, int i) {
int i) {
System.exit(0); System.exit(0);
} }
} }
) )
.create(); .create();
dialog.getWindow() dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
.setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
dialog.show(); dialog.show();
} }
}); });

View file

@ -70,6 +70,11 @@ public class SyncthingService extends Service {
*/ */
private static final String PRIVATE_KEY_FILE = "key.pem"; private static final String PRIVATE_KEY_FILE = "key.pem";
/**
* Path to the native, integrated syncthing binary, relative to the data folder
*/
private static final String BINARY_NAME = "lib/libsyncthing.so";
private RestApi mApi; private RestApi mApi;
private final SyncthingServiceBinder mBinder = new SyncthingServiceBinder(this); private final SyncthingServiceBinder mBinder = new SyncthingServiceBinder(this);
@ -165,7 +170,8 @@ public class SyncthingService extends Service {
mCurrentState = State.STARTING; mCurrentState = State.STARTING;
registerOnWebGuiAvailableListener(mApi); registerOnWebGuiAvailableListener(mApi);
new PollWebGuiAvailableTask().execute(); new PollWebGuiAvailableTask().execute();
new Thread(new SyncthingRunnable(this)).start(); new Thread(new SyncthingRunnable(
this, getApplicationInfo().dataDir + "/" + BINARY_NAME)).start();
} }
// Stop syncthing. // Stop syncthing.
else { else {

View file

@ -45,7 +45,7 @@ public class ConfigXml {
private Document mConfig; private Document mConfig;
public ConfigXml(Context context) { public ConfigXml(Context context) {
mConfigFile = new File(context.getFilesDir(), CONFIG_FILE); mConfigFile = getConfigFile(context);
if (!mConfigFile.exists()) { if (!mConfigFile.exists()) {
copyDefaultConfig(context); copyDefaultConfig(context);
} }
@ -57,6 +57,10 @@ public class ConfigXml {
} }
} }
public static File getConfigFile(Context context) {
return new File(context.getFilesDir(), CONFIG_FILE);
}
public String getWebGuiUrl() { public String getWebGuiUrl() {
return "http://" + getGuiElement().getElementsByTagName("address").item(0).getTextContent(); return "http://" + getGuiElement().getElementsByTagName("address").item(0).getTextContent();
} }