Running Python unit tests on Android
This commit is contained in:
parent
589e0fbb95
commit
1a32468e72
11 changed files with 380 additions and 39 deletions
|
@ -6,7 +6,7 @@
|
|||
*.egg-info
|
||||
|
||||
# unit test
|
||||
unittest/*
|
||||
#unittest/*
|
||||
|
||||
# python config
|
||||
config/makesetup
|
||||
|
|
|
@ -118,6 +118,8 @@
|
|||
{% endfor %}
|
||||
<service android:name="{{ args.package }}.LbrynetService"
|
||||
android:process=":service_lbrynet" />
|
||||
<service android:name="{{ args.package }}.LbrynetTestRunnerService"
|
||||
android:process=":service_lbrynet_testrunner" />
|
||||
|
||||
{% if args.billing_pubkey %}
|
||||
<service android:name="org.kivy.android.billing.BillingReceiver"
|
||||
|
|
|
@ -33,7 +33,6 @@
|
|||
/>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_start_stop"
|
||||
android:layout_width="120dp"
|
||||
|
@ -42,4 +41,47 @@
|
|||
android:layout_marginTop="12dp"
|
||||
android:text="@string/start"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginTop="24dp"
|
||||
>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:weightSum="3">
|
||||
<TextView
|
||||
android:layout_weight="2"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="20sp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:text="@string/unit_tests"
|
||||
/>
|
||||
<Button
|
||||
android:id="@+id/btn_run_tests"
|
||||
android:layout_weight="1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="right|center_vertical"
|
||||
android:text="@string/run_tests"/>
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_marginTop="12dp"
|
||||
android:padding="6dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<TextView
|
||||
android:id="@+id/test_runner_output"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="12sp"
|
||||
/>
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
|
|
@ -10,4 +10,7 @@
|
|||
<string name="stopped">Stopped</string>
|
||||
<string name="start">START</string>
|
||||
<string name="stop">STOP</string>
|
||||
|
||||
<string name="unit_tests">Unit tests</string>
|
||||
<string name="run_tests">RUN</string>
|
||||
</resources>
|
||||
|
|
20
recipes/unqlite/__init__.py
Normal file
20
recipes/unqlite/__init__.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
|
||||
import glob
|
||||
from pythonforandroid.toolchain import (
|
||||
CythonRecipe,
|
||||
Recipe,
|
||||
current_directory,
|
||||
info,
|
||||
shprint,
|
||||
)
|
||||
from os.path import join
|
||||
import sh
|
||||
|
||||
|
||||
class UnqliteRecipe(CythonRecipe):
|
||||
version = '0.6.0'
|
||||
url = 'https://pypi.python.org/packages/cb/4e/e1f64a3d0f6462167805940b4c72f47bafc1129e363fc4c0f79a1cdc5dd1/unqlite-{version}.tar.gz'
|
||||
|
||||
call_hostpython_via_targetpython = False
|
||||
|
||||
recipe = UnqliteRecipe()
|
|
@ -1,19 +1,20 @@
|
|||
package io.lbry.lbrynet;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.Context;
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import java.io.InputStream;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.File;
|
||||
import android.os.Bundle;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.kivy.android.PythonService;
|
||||
import org.kivy.android.PythonActivity;
|
||||
import org.renpy.android.AssetExtract;
|
||||
import org.renpy.android.ResourceManager;
|
||||
|
||||
|
@ -51,7 +52,8 @@ public class LbrynetService extends PythonService {
|
|||
unpackData("private", app_root_file);
|
||||
|
||||
if (intent == null) {
|
||||
intent = buildIntent(getApplicationContext(), "");
|
||||
intent = ServiceHelper.buildIntent(
|
||||
getApplicationContext(), "", LbrynetService.class, "lbrynetservice");
|
||||
}
|
||||
|
||||
return super.onStartCommand(intent, flags, startId);
|
||||
|
@ -134,28 +136,4 @@ public class LbrynetService extends PythonService {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Intent buildIntent(Context ctx, String pythonServiceArgument) {
|
||||
Intent intent = new Intent(ctx, LbrynetService.class);
|
||||
String argument = ctx.getFilesDir().getAbsolutePath() + "/app";
|
||||
intent.putExtra("androidPrivate", ctx.getFilesDir().getAbsolutePath());
|
||||
intent.putExtra("androidArgument", argument);
|
||||
intent.putExtra("serviceEntrypoint", "./lbrynetservice.py");
|
||||
intent.putExtra("pythonName", "lbrynetservice");
|
||||
intent.putExtra("pythonHome", argument);
|
||||
intent.putExtra("pythonPath", argument + ":" + argument + "/lib");
|
||||
intent.putExtra("pythonServiceArgument", pythonServiceArgument);
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static void start(Context ctx, String pythonServiceArgument) {
|
||||
Intent intent = buildIntent(ctx, pythonServiceArgument);
|
||||
ctx.startService(intent);
|
||||
}
|
||||
|
||||
public static void stop(Context ctx) {
|
||||
Intent intent = new Intent(ctx, LbrynetService.class);
|
||||
ctx.stopService(intent);
|
||||
}
|
||||
}
|
||||
|
|
126
src/main/java/io/lbry/lbrynet/LbrynetTestRunnerService.java
Normal file
126
src/main/java/io/lbry/lbrynet/LbrynetTestRunnerService.java
Normal file
|
@ -0,0 +1,126 @@
|
|||
package io.lbry.lbrynet;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.kivy.android.PythonService;
|
||||
import org.renpy.android.AssetExtract;
|
||||
import org.renpy.android.ResourceManager;
|
||||
|
||||
public class LbrynetTestRunnerService extends PythonService {
|
||||
|
||||
public static String TAG = "LbrynetTestRunnerService";
|
||||
|
||||
public static LbrynetTestRunnerService serviceInstance;
|
||||
|
||||
@Override
|
||||
public boolean canDisplayNotification() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
// Extract files
|
||||
File app_root_file = new File(getAppRoot());
|
||||
unpackData("private", app_root_file);
|
||||
|
||||
if (intent == null) {
|
||||
intent = ServiceHelper.buildIntent(
|
||||
getApplicationContext(), "", LbrynetTestRunnerService.class, "testrunnerservice");
|
||||
}
|
||||
|
||||
serviceInstance = this;
|
||||
return super.onStartCommand(intent, flags, startId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
serviceInstance = null;
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
public void broadcastTestRunnerOutput(String output) {
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(ServiceControlActivity.TEST_RUNNER_OUTPUT);
|
||||
intent.putExtra("output", output);
|
||||
sendBroadcast(intent);
|
||||
}
|
||||
|
||||
public void unpackData(final String resource, File target) {
|
||||
Log.v(TAG, "UNPACKING!!! " + resource + " " + target.getName());
|
||||
|
||||
// The version of data in memory and on disk.
|
||||
ResourceManager resourceManager = new ResourceManager(getApplicationContext());
|
||||
String data_version = resourceManager.getString(resource + "_version");
|
||||
String disk_version = null;
|
||||
|
||||
Log.v(TAG, "Data version is " + data_version);
|
||||
|
||||
// If no version, no unpacking is necessary.
|
||||
if (data_version == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the current disk version, if any.
|
||||
String filesDir = target.getAbsolutePath();
|
||||
String disk_version_fn = filesDir + "/" + resource + ".version";
|
||||
|
||||
try {
|
||||
byte buf[] = new byte[64];
|
||||
InputStream is = new FileInputStream(disk_version_fn);
|
||||
int len = is.read(buf);
|
||||
disk_version = new String(buf, 0, len);
|
||||
is.close();
|
||||
} catch (Exception e) {
|
||||
disk_version = "";
|
||||
}
|
||||
|
||||
// If the disk data is out of date, extract it and write the
|
||||
// version file.
|
||||
// if (! data_version.equals(disk_version)) {
|
||||
if (! data_version.equals(disk_version)) {
|
||||
Log.v(TAG, "Extracting " + resource + " assets.");
|
||||
|
||||
recursiveDelete(target);
|
||||
target.mkdirs();
|
||||
|
||||
AssetExtract ae = new AssetExtract(getApplicationContext());
|
||||
if (!ae.extractTar(resource + ".mp3", target.getAbsolutePath())) {
|
||||
//toastError("Could not extract " + resource + " data.");
|
||||
Log.e(TAG, "Could not extract " + resource + " data.");
|
||||
}
|
||||
|
||||
try {
|
||||
// Write .nomedia.
|
||||
new File(target, ".nomedia").createNewFile();
|
||||
|
||||
// Write version file.
|
||||
FileOutputStream os = new FileOutputStream(disk_version_fn);
|
||||
os.write(data_version.getBytes());
|
||||
os.close();
|
||||
} catch (Exception e) {
|
||||
Log.w("python", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void recursiveDelete(File f) {
|
||||
if (f.isDirectory()) {
|
||||
for (File r : f.listFiles()) {
|
||||
recursiveDelete(r);
|
||||
}
|
||||
}
|
||||
f.delete();
|
||||
}
|
||||
|
||||
public String getAppRoot() {
|
||||
String app_root = getApplicationContext().getFilesDir().getAbsolutePath() + "/app";
|
||||
return app_root;
|
||||
}
|
||||
}
|
|
@ -1,18 +1,34 @@
|
|||
package io.lbry.lbrynet;
|
||||
|
||||
import org.kivy.android.PythonActivity;
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
//import android.support.v7.app.AppCompatActivity;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.text.Html;
|
||||
import android.text.Spanned;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
public class ServiceControlActivity extends Activity {
|
||||
|
||||
public static ServiceControlActivity activityInstance;
|
||||
|
||||
private IntentFilter intentFilter;
|
||||
|
||||
public static String TEST_RUNNER_OUTPUT = "io.lbry.lbrynet.TEST_RUNNER_OUTPUT";
|
||||
|
||||
/**
|
||||
* Flag which indicates whether or not the service is running. Will be updated in the
|
||||
* onResume method.
|
||||
|
@ -24,6 +40,10 @@ public class ServiceControlActivity extends Activity {
|
|||
*/
|
||||
private Button startStopButton;
|
||||
|
||||
private Button runTestsButton;
|
||||
|
||||
private TextView testRunnerOutput;
|
||||
|
||||
/**
|
||||
* Service status text.
|
||||
*/
|
||||
|
@ -33,32 +53,69 @@ public class ServiceControlActivity extends Activity {
|
|||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_service_control);
|
||||
intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(TEST_RUNNER_OUTPUT);
|
||||
|
||||
startStopButton = (Button) findViewById(R.id.btn_start_stop);
|
||||
runTestsButton = (Button) findViewById(R.id.btn_run_tests);
|
||||
serviceStatusText = (TextView) findViewById(R.id.text_service_status);
|
||||
testRunnerOutput = (TextView) findViewById(R.id.test_runner_output);
|
||||
|
||||
startStopButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (serviceRunning) {
|
||||
LbrynetService.stop(ServiceControlActivity.this);
|
||||
ServiceHelper.stop(ServiceControlActivity.this, LbrynetService.class);
|
||||
} else {
|
||||
LbrynetService.start(ServiceControlActivity.this, "");
|
||||
ServiceHelper.start(ServiceControlActivity.this, "", LbrynetService.class, "lbrynetservice");
|
||||
}
|
||||
|
||||
serviceRunning = isServiceRunning(LbrynetService.class);
|
||||
updateServiceStatus();
|
||||
}
|
||||
});
|
||||
|
||||
runTestsButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
boolean testsRunning = isServiceRunning(LbrynetTestRunnerService.class);
|
||||
if (!testsRunning) {
|
||||
ServiceHelper.start(
|
||||
ServiceControlActivity.this, "", LbrynetTestRunnerService.class, "testrunnerservice");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
registerReceiver(testRunnerOutputReceiver, intentFilter);
|
||||
|
||||
activityInstance = this;
|
||||
serviceRunning = isServiceRunning(LbrynetService.class);
|
||||
updateServiceStatus();
|
||||
}
|
||||
|
||||
private BroadcastReceiver testRunnerOutputReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (TEST_RUNNER_OUTPUT.equals(intent.getAction())) {
|
||||
String output = intent.getStringExtra("output");
|
||||
updateTestRunnerOutput(output);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
unregisterReceiver(testRunnerOutputReceiver);
|
||||
// set the activity instance to null on pause in order to prevent NullPointerException
|
||||
// if the activity shuts down prematurely, for example
|
||||
activityInstance = null;
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
public void updateServiceStatus() {
|
||||
new Handler().post(new Runnable() {
|
||||
@Override
|
||||
|
@ -76,6 +133,10 @@ public class ServiceControlActivity extends Activity {
|
|||
});
|
||||
}
|
||||
|
||||
public void updateTestRunnerOutput(String output) {
|
||||
testRunnerOutput.setText(formatTestRunnerOutput(output));
|
||||
}
|
||||
|
||||
private boolean isServiceRunning(Class<?> serviceClass) {
|
||||
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
|
||||
for (ActivityManager.RunningServiceInfo serviceInfo : manager.getRunningServices(Integer.MAX_VALUE)) {
|
||||
|
@ -86,4 +147,12 @@ public class ServiceControlActivity extends Activity {
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Spanned formatTestRunnerOutput(String output) {
|
||||
output = output.replace("[OK]", "<font color=\"#008000\">[OK]</font>");
|
||||
output = output.replace("[ERROR]", "<font color=\"#ff0000\">[ERROR]</font>");
|
||||
output = output.replace("[FAILURE]", "<font color=\"#cc0000\">[FAILURE]</font>");
|
||||
|
||||
return Html.fromHtml(output);
|
||||
}
|
||||
}
|
||||
|
|
30
src/main/java/io/lbry/lbrynet/ServiceHelper.java
Normal file
30
src/main/java/io/lbry/lbrynet/ServiceHelper.java
Normal file
|
@ -0,0 +1,30 @@
|
|||
package io.lbry.lbrynet;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.Context;
|
||||
|
||||
public class ServiceHelper {
|
||||
public static Intent buildIntent(Context ctx, String pythonServiceArgument, Class serviceClass, String pythonName) {
|
||||
Intent intent = new Intent(ctx, serviceClass);
|
||||
String argument = ctx.getFilesDir().getAbsolutePath() + "/app";
|
||||
intent.putExtra("androidPrivate", ctx.getFilesDir().getAbsolutePath());
|
||||
intent.putExtra("androidArgument", argument);
|
||||
intent.putExtra("serviceEntrypoint", "./" + pythonName + ".py");
|
||||
intent.putExtra("pythonName", pythonName);
|
||||
intent.putExtra("pythonHome", argument);
|
||||
intent.putExtra("pythonPath", argument + ":" + argument + "/lib");
|
||||
intent.putExtra("pythonServiceArgument", pythonServiceArgument);
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static void start(Context ctx, String pythonServiceArgument, Class serviceClass, String pythonName) {
|
||||
Intent intent = buildIntent(ctx, pythonServiceArgument, serviceClass, pythonName);
|
||||
ctx.startService(intent);
|
||||
}
|
||||
|
||||
public static void stop(Context ctx, Class serviceClass) {
|
||||
Intent intent = new Intent(ctx, serviceClass);
|
||||
ctx.stopService(intent);
|
||||
}
|
||||
}
|
|
@ -54,7 +54,8 @@ lbrynet.daemon.auth.util.save_api_keys = save_api_keys
|
|||
lbrynet.daemon.auth.util.initialize_api_key_file = initialize_api_key_file
|
||||
|
||||
import logging.handlers
|
||||
|
||||
if __name__ == '__main__':
|
||||
ServiceApp().run()
|
||||
from lbrynet.core import log_support
|
||||
from twisted.internet import defer, reactor
|
||||
from jsonrpc.proxy import JSONRPCProxy
|
||||
|
|
70
src/main/python/testrunnerservice.py
Normal file
70
src/main/python/testrunnerservice.py
Normal file
|
@ -0,0 +1,70 @@
|
|||
import sys
|
||||
import StringIO
|
||||
|
||||
from twisted.trial.runner import (
|
||||
TestLoader,
|
||||
TrialRunner
|
||||
)
|
||||
from twisted.trial.reporter import VerboseTextReporter
|
||||
from twisted.plugin import getPlugins, IPlugin
|
||||
from jnius import autoclass
|
||||
import tests
|
||||
|
||||
str_stream = StringIO.StringIO()
|
||||
|
||||
serviceClass = autoclass('io.lbry.lbrynet.LbrynetTestRunnerService')
|
||||
|
||||
def update_output_in_activity(str):
|
||||
service = serviceClass.serviceInstance
|
||||
if service is not None:
|
||||
service.broadcastTestRunnerOutput(str)
|
||||
|
||||
class AndroidTestReporter(VerboseTextReporter):
|
||||
def startTest(self, tm):
|
||||
self._write('%s ... ', tm.id())
|
||||
super(AndroidTestReporter, self).startTest(tm)
|
||||
update_output_in_activity(self._stream.getvalue())
|
||||
|
||||
def addSuccess(self, test):
|
||||
super(AndroidTestReporter, self).addSuccess(test)
|
||||
update_output_in_activity(self._stream.getvalue())
|
||||
|
||||
def addError(self, *args):
|
||||
super(AndroidTestReporter, self).addError(*args)
|
||||
update_output_in_activity(self._stream.getvalue())
|
||||
|
||||
def addFailure(self, *args):
|
||||
super(AndroidTestReporter, self).addFailure(*args)
|
||||
update_output_in_activity(self._stream.getvalue())
|
||||
|
||||
def addSkip(self, *args):
|
||||
super(VerboseTextReporter, self).addSkip(*args)
|
||||
update_output_in_activity(self._stream.getvalue())
|
||||
|
||||
def addExpectedFailure(self, *args):
|
||||
super(AndroidTestReporter, self).addExpectedFailure(*args)
|
||||
update_output_in_activity(self._stream.getvalue())
|
||||
|
||||
def addUnexpectedSuccess(self, *args):
|
||||
super(AndroidTestReporter, self).addUnexpectedSuccess(*args)
|
||||
update_output_in_activity(self._stream.getvalue())
|
||||
|
||||
def stopTest(self, test):
|
||||
super(AndroidTestReporter, self).stopTest(test)
|
||||
# TODO: Use appendLine here
|
||||
self._write("<br>"); # html output
|
||||
update_output_in_activity(self._stream.getvalue())
|
||||
|
||||
|
||||
def run():
|
||||
loader = TestLoader();
|
||||
suite = loader.loadPackage(tests, True)
|
||||
runner = TrialRunner(AndroidTestReporter)
|
||||
runner.stream = str_stream
|
||||
passFail = not runner.run(suite).wasSuccessful()
|
||||
|
||||
print str_stream.getvalue()
|
||||
sys.exit(passFail)
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
Loading…
Reference in a new issue