Running Python unit tests on Android

This commit is contained in:
Akinwale Ariwodola 2017-09-11 23:14:34 +01:00
parent 589e0fbb95
commit 1a32468e72
11 changed files with 380 additions and 39 deletions

View file

@ -6,7 +6,7 @@
*.egg-info
# unit test
unittest/*
#unittest/*
# python config
config/makesetup

View file

@ -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"

View file

@ -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>

View file

@ -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>

View 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()

View file

@ -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);
}
}

View 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;
}
}

View file

@ -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);
}
}

View 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);
}
}

View file

@ -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

View 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()