Python unit tests (#13)

* Modified build to allow Python unit tests to run
* Added extra dependencies and build recipes for the unit tests
This commit is contained in:
akinwale 2017-10-03 10:16:24 +01:00 committed by GitHub
parent 589e0fbb95
commit 9eeeefa1d2
19 changed files with 546 additions and 41 deletions

View file

@ -36,7 +36,7 @@ version = 0.1
# (list) Application requirements # (list) Application requirements
# comma seperated e.g. requirements = sqlite3,kivy # comma seperated e.g. requirements = sqlite3,kivy
requirements = openssl, sqlite3, hostpython2, pycrypto==2.6.1, android, pyjnius, constantly, incremental, functools32, miniupnpc==1.9, gmpy==1.17, twisted==16.6.0, appdirs==1.4.3, argparse==1.2.1, docopt==0.6.2, base58==0.2.2, colorama==0.3.7, dnspython==1.12.0, ecdsa==0.13, envparse==0.2.0, jsonrpc==1.2, jsonrpclib==0.1.7, jsonschema==2.5.1, pbkdf2==1.3, pycrypto==2.6.1, pyyaml==3.12, qrcode==5.2.2, requests==2.9.1, txrequests==0.9.5, seccure==0.3.1.3, service_identity==16.0.0, six==1.9.0, slowaes==0.1a1, txJSON-RPC==0.5, wsgiref==0.1.2, zope.interface==4.3.3, protobuf==3.2.0, git+https://github.com/lbryio/lbryschema.git#egg=lbryschema, git+https://github.com/lbryio/lbryum.git#egg=lbryum, git+https://github.com/lbryio/lbry.git#egg=lbry requirements = openssl, sqlite3, hostpython2, pycrypto==2.6.1, android, pyjnius, constantly, incremental, functools32, miniupnpc==1.9, gmpy==1.17, twisted==16.6.0, appdirs==1.4.3, argparse==1.2.1, docopt==0.6.2, base58==0.2.2, colorama==0.3.7, dnspython==1.12.0, ecdsa==0.13, envparse==0.2.0, jsonrpc==1.2, jsonrpclib==0.1.7, jsonschema==2.5.1, pbkdf2==1.3, pycrypto==2.6.1, pyyaml==3.12, qrcode==5.2.2, requests==2.9.1, txrequests==0.9.5, seccure==0.3.1.3, service_identity==16.0.0, six==1.9.0, slowaes==0.1a1, txJSON-RPC==0.5, wsgiref==0.1.2, zope.interface==4.3.3, protobuf==3.2.0, git+https://github.com/lbryio/lbryschema.git@v0.0.12rc1#egg=lbryschema, git+https://github.com/lbryio/lbryum.git@v3.1.9rc2#egg=lbryum, git+https://github.com/lbryio/lbry.git#egg=lbrynet, asn1crypto, cryptography==2.0.3, funcsigs, mock, pbr, unqlite
# (str) Custom source folders for requirements # (str) Custom source folders for requirements
# Sets custom source for any requirements with recipes # Sets custom source for any requirements with recipes

View file

@ -36,7 +36,7 @@ version = 0.1
# (list) Application requirements # (list) Application requirements
# comma seperated e.g. requirements = sqlite3,kivy # comma seperated e.g. requirements = sqlite3,kivy
requirements = openssl, sqlite3, hostpython2, pycrypto==2.6.1, android, pyjnius, constantly, incremental, functools32, miniupnpc==1.9, gmpy==1.17, twisted==16.6.0, appdirs==1.4.3, argparse==1.2.1, docopt==0.6.2, base58==0.2.2, colorama==0.3.7, dnspython==1.12.0, ecdsa==0.13, envparse==0.2.0, jsonrpc==1.2, jsonrpclib==0.1.7, jsonschema==2.5.1, pbkdf2==1.3, pycrypto==2.6.1, pyyaml==3.12, qrcode==5.2.2, requests==2.9.1, txrequests==0.9.5, seccure==0.3.1.3, service_identity==16.0.0, six==1.9.0, slowaes==0.1a1, txJSON-RPC==0.5, wsgiref==0.1.2, zope.interface==4.3.3, protobuf==3.2.0, git+https://github.com/lbryio/lbryschema.git#egg=lbryschema, git+https://github.com/lbryio/lbryum.git#egg=lbryum, git+https://github.com/lbryio/lbry.git#egg=lbry requirements = openssl, sqlite3, hostpython2, pycrypto==2.6.1, android, pyjnius, constantly, incremental, functools32, miniupnpc==1.9, gmpy==1.17, twisted==16.6.0, appdirs==1.4.3, argparse==1.2.1, docopt==0.6.2, base58==0.2.2, colorama==0.3.7, dnspython==1.12.0, ecdsa==0.13, envparse==0.2.0, jsonrpc==1.2, jsonrpclib==0.1.7, jsonschema==2.5.1, pbkdf2==1.3, pycrypto==2.6.1, pyyaml==3.12, qrcode==5.2.2, requests==2.9.1, txrequests==0.9.5, seccure==0.3.1.3, service_identity==16.0.0, six==1.9.0, slowaes==0.1a1, txJSON-RPC==0.5, wsgiref==0.1.2, zope.interface==4.3.3, protobuf==3.2.0, git+https://github.com/lbryio/lbryschema.git@v0.0.12rc1#egg=lbryschema, git+https://github.com/lbryio/lbryum.git@v3.1.9rc2#egg=lbryum, git+https://github.com/lbryio/lbry.git#egg=lbrynet, asn1crypto, cryptography==2.0.3, funcsigs, mock, pbr, unqlite
# (str) Custom source folders for requirements # (str) Custom source folders for requirements
# Sets custom source for any requirements with recipes # Sets custom source for any requirements with recipes

View file

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

View file

@ -118,6 +118,8 @@
{% endfor %} {% endfor %}
<service android:name="{{ args.package }}.LbrynetService" <service android:name="{{ args.package }}.LbrynetService"
android:process=":service_lbrynet" /> android:process=":service_lbrynet" />
<service android:name="{{ args.package }}.LbrynetTestRunnerService"
android:process=":service_lbrynet_testrunner" />
{% if args.billing_pubkey %} {% if args.billing_pubkey %}
<service android:name="org.kivy.android.billing.BillingReceiver" <service android:name="org.kivy.android.billing.BillingReceiver"

View file

@ -33,7 +33,6 @@
/> />
</LinearLayout> </LinearLayout>
<Button <Button
android:id="@+id/btn_start_stop" android:id="@+id/btn_start_stop"
android:layout_width="120dp" android:layout_width="120dp"
@ -42,4 +41,47 @@
android:layout_marginTop="12dp" android:layout_marginTop="12dp"
android:text="@string/start"/> 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> </LinearLayout>

View file

@ -10,4 +10,7 @@
<string name="stopped">Stopped</string> <string name="stopped">Stopped</string>
<string name="start">START</string> <string name="start">START</string>
<string name="stop">STOP</string> <string name="stop">STOP</string>
<string name="unit_tests">Unit tests</string>
<string name="run_tests">RUN</string>
</resources> </resources>

36
recipes/cffi/__init__.py Normal file
View file

@ -0,0 +1,36 @@
from pythonforandroid.recipe import CompiledComponentsPythonRecipe, Recipe
from os.path import join
class CffiRecipe(CompiledComponentsPythonRecipe):
name = 'cffi'
version = '1.11.0'
#url = 'https://pypi.python.org/packages/source/c/cffi/cffi-{version}.tar.gz'
url = 'https://pypi.python.org/packages/4e/32/4070bdf32812c89eb635c80880a5caa2e0189aa7999994c265577e5154f3/cffi-{version}.tar.gz'
depends = [('python2', 'python3crystax'), 'setuptools', 'pycparser', 'libffi']
patches = ['disable-pkg-config.patch']
# call_hostpython_via_targetpython = False
install_in_hostpython = True
def get_recipe_env(self, arch=None):
env = super(CffiRecipe, self).get_recipe_env(arch)
libffi = self.get_recipe('libffi', self.ctx)
includes = libffi.get_include_dirs(arch)
env['CFLAGS'] = ' -I'.join([env.get('CFLAGS', '')] + includes)
env['LDFLAGS'] = (env.get('CFLAGS', '') + ' -L' +
self.ctx.get_libs_dir(arch.arch))
env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions'
env['PYTHONPATH'] = ':'.join([self.ctx.get_site_packages_dir(), env['BUILDLIB_PATH']])
target_python = Recipe.get_recipe('python2', self.ctx).get_build_dir(arch.arch)
env['PYTHON_ROOT'] = join(target_python, 'python-install')
env['CFLAGS'] += ' -I' + env['PYTHON_ROOT'] + '/include/python2.7'
env['LDFLAGS'] += ' -L' + env['PYTHON_ROOT'] + '/lib' + ' -lpython2.7'
return env
recipe = CffiRecipe()

View file

@ -0,0 +1,29 @@
diff -Naur cffi-1.4.2/setup.py b/setup.py
--- cffi-1.4.2/setup.py 2015-12-21 12:09:47.000000000 -0600
+++ b/setup.py 2015-12-23 10:20:40.590622524 -0600
@@ -5,8 +5,7 @@
sources = ['c/_cffi_backend.c']
libraries = ['ffi']
-include_dirs = ['/usr/include/ffi',
- '/usr/include/libffi'] # may be changed by pkg-config
+include_dirs = []
define_macros = []
library_dirs = []
extra_compile_args = []
@@ -67,14 +66,7 @@
sys.stderr.write("The above error message can be safely ignored\n")
def use_pkg_config():
- if sys.platform == 'darwin' and os.path.exists('/usr/local/bin/brew'):
- use_homebrew_for_libffi()
-
- _ask_pkg_config(include_dirs, '--cflags-only-I', '-I', sysroot=True)
- _ask_pkg_config(extra_compile_args, '--cflags-only-other')
- _ask_pkg_config(library_dirs, '--libs-only-L', '-L', sysroot=True)
- _ask_pkg_config(extra_link_args, '--libs-only-other')
- _ask_pkg_config(libraries, '--libs-only-l', '-l')
+ pass
def use_homebrew_for_libffi():
# We can build by setting:

View file

@ -0,0 +1,30 @@
from pythonforandroid.recipe import CompiledComponentsPythonRecipe, Recipe
from os.path import dirname, join
class CryptographyRecipe(CompiledComponentsPythonRecipe):
name = 'cryptography'
version = '2.0.3'
url = 'https://github.com/pyca/cryptography/archive/{version}.tar.gz'
depends = [('python2', 'python3crystax'), 'openssl', 'idna', 'pyasn1', 'six', 'setuptools', 'enum34', 'ipaddress', 'cffi']
call_hostpython_via_targetpython = False
def get_recipe_env(self, arch):
env = super(CryptographyRecipe, self).get_recipe_env(arch)
r = self.get_recipe('openssl', self.ctx)
openssl_dir = r.get_build_dir(arch.arch)
target_python = Recipe.get_recipe('python2', self.ctx).get_build_dir(arch.arch)
env['PYTHON_ROOT'] = self.ctx.get_python_install_dir()
env['CFLAGS'] += ' -I' + env['PYTHON_ROOT'] + '/include/python2.7' + \
' -I' + join(openssl_dir, 'include') + \
' -I' + join(target_python, 'Modules/_ctypes/libffi_arm_wince')
# Set linker to use the correct gcc
env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions'
env['LDFLAGS'] += ' -L' + env['PYTHON_ROOT'] + '/lib' + \
' -L' + openssl_dir + \
' -lpython2.7' + \
' -lssl' + r.version + \
' -lcrypto' + r.version
return env
recipe = CryptographyRecipe()

View file

@ -0,0 +1,14 @@
from pythonforandroid.recipe import PythonRecipe
class IpaddressRecipe(PythonRecipe):
name = 'ipaddress'
version = '1.0.16'
url = 'https://pypi.python.org/packages/source/i/ipaddress/ipaddress-{version}.tar.gz'
depends = ['python2']
call_hostpython_via_targetpython = False
recipe = IpaddressRecipe()

View file

@ -0,0 +1,11 @@
from pythonforandroid.toolchain import PythonRecipe
class PyASN1Recipe(PythonRecipe):
version = '0.1.8'
url = 'https://pypi.python.org/packages/source/p/pyasn1/pyasn1-{version}.tar.gz'
depends = ['python2']
call_hostpython_via_targetpython = False
recipe = PyASN1Recipe()

View file

@ -0,0 +1,32 @@
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'
depends = ['python2', 'setuptools']
call_hostpython_via_targetpython = False
patches = ['setup.patch']
def get_recipe_env(self, arch):
env = super(UnqliteRecipe, self).get_recipe_env(arch)
target_python = Recipe.get_recipe('python2', self.ctx).get_build_dir(arch.arch)
env['PYTHON_ROOT'] = join(target_python, 'python-install')
env['CFLAGS'] += ' -I' + env['PYTHON_ROOT'] + '/include/python2.7'
env['LDFLAGS'] += ' -L' + env['PYTHON_ROOT'] + '/lib' + ' -lpython2.7'
return env
recipe = UnqliteRecipe()

View file

@ -0,0 +1,26 @@
--- a/setup.py 2016-08-22 19:45:52.000000000 +0100
+++ b/setup.py 2017-09-16 01:35:36.675202502 +0100
@@ -1,11 +1,7 @@
import glob
-from distutils.core import setup, Extension
-
-try:
- from Cython.Build import cythonize
-except ImportError:
- raise RuntimeError('Cython must be installed to build unqlite-python.')
-
+from setuptools import setup
+from setuptools.extension import Extension
+from setuptools.command.build_ext import build_ext
python_source = 'unqlite.pyx'
library_source = glob.glob('src/*.c')
@@ -45,5 +41,7 @@
'Topic :: Database :: Database Engines/Servers',
'Topic :: Software Development :: Embedded Systems',
'Topic :: Software Development :: Libraries :: Python Modules'],
- ext_modules=cythonize(unqlite_extension)
+ cmdclass = {'build_ext': build_ext},
+ ext_modules=[unqlite_extension]
)
+

View file

@ -1,19 +1,20 @@
package io.lbry.lbrynet; package io.lbry.lbrynet;
import android.app.Activity; import android.app.Activity;
import android.content.Intent;
import android.content.Context;
import android.app.Notification; import android.app.Notification;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Intent;
import android.content.Context;
import android.os.Bundle;
import android.util.Log; import android.util.Log;
import java.io.InputStream;
import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.File; import java.io.InputStream;
import android.os.Bundle;
import org.kivy.android.PythonService; import org.kivy.android.PythonService;
import org.kivy.android.PythonActivity;
import org.renpy.android.AssetExtract; import org.renpy.android.AssetExtract;
import org.renpy.android.ResourceManager; import org.renpy.android.ResourceManager;
@ -51,7 +52,8 @@ public class LbrynetService extends PythonService {
unpackData("private", app_root_file); unpackData("private", app_root_file);
if (intent == null) { if (intent == null) {
intent = buildIntent(getApplicationContext(), ""); intent = ServiceHelper.buildIntent(
getApplicationContext(), "", LbrynetService.class, "lbrynetservice");
} }
return super.onStartCommand(intent, flags, startId); 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; package io.lbry.lbrynet;
import org.kivy.android.PythonActivity;
import android.app.Activity; import android.app.Activity;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.os.Handler; import android.content.Intent;
//import android.support.v7.app.AppCompatActivity; import android.content.IntentFilter;
import android.os.AsyncTask;
import android.os.Bundle; 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.view.View;
import android.widget.Button; import android.widget.Button;
import android.widget.TextView; 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 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 * Flag which indicates whether or not the service is running. Will be updated in the
* onResume method. * onResume method.
@ -24,6 +40,10 @@ public class ServiceControlActivity extends Activity {
*/ */
private Button startStopButton; private Button startStopButton;
private Button runTestsButton;
private TextView testRunnerOutput;
/** /**
* Service status text. * Service status text.
*/ */
@ -33,32 +53,69 @@ public class ServiceControlActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_service_control); setContentView(R.layout.activity_service_control);
intentFilter = new IntentFilter();
intentFilter.addAction(TEST_RUNNER_OUTPUT);
startStopButton = (Button) findViewById(R.id.btn_start_stop); startStopButton = (Button) findViewById(R.id.btn_start_stop);
runTestsButton = (Button) findViewById(R.id.btn_run_tests);
serviceStatusText = (TextView) findViewById(R.id.text_service_status); serviceStatusText = (TextView) findViewById(R.id.text_service_status);
testRunnerOutput = (TextView) findViewById(R.id.test_runner_output);
startStopButton.setOnClickListener(new View.OnClickListener() { startStopButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
if (serviceRunning) { if (serviceRunning) {
LbrynetService.stop(ServiceControlActivity.this); ServiceHelper.stop(ServiceControlActivity.this, LbrynetService.class);
} else { } else {
LbrynetService.start(ServiceControlActivity.this, ""); ServiceHelper.start(ServiceControlActivity.this, "", LbrynetService.class, "lbrynetservice");
} }
serviceRunning = isServiceRunning(LbrynetService.class); serviceRunning = isServiceRunning(LbrynetService.class);
updateServiceStatus(); 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 @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
registerReceiver(testRunnerOutputReceiver, intentFilter);
activityInstance = this;
serviceRunning = isServiceRunning(LbrynetService.class); serviceRunning = isServiceRunning(LbrynetService.class);
updateServiceStatus(); 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() { public void updateServiceStatus() {
new Handler().post(new Runnable() { new Handler().post(new Runnable() {
@Override @Override
@ -76,6 +133,10 @@ public class ServiceControlActivity extends Activity {
}); });
} }
public void updateTestRunnerOutput(String output) {
testRunnerOutput.setText(formatTestRunnerOutput(output));
}
private boolean isServiceRunning(Class<?> serviceClass) { private boolean isServiceRunning(Class<?> serviceClass) {
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningServiceInfo serviceInfo : manager.getRunningServices(Integer.MAX_VALUE)) { for (ActivityManager.RunningServiceInfo serviceInfo : manager.getRunningServices(Integer.MAX_VALUE)) {
@ -86,4 +147,12 @@ public class ServiceControlActivity extends Activity {
return false; 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 lbrynet.daemon.auth.util.initialize_api_key_file = initialize_api_key_file
import logging.handlers import logging.handlers
if __name__ == '__main__':
ServiceApp().run()
from lbrynet.core import log_support from lbrynet.core import log_support
from twisted.internet import defer, reactor from twisted.internet import defer, reactor
from jsonrpc.proxy import JSONRPCProxy from jsonrpc.proxy import JSONRPCProxy

View file

@ -0,0 +1,76 @@
import sys
import StringIO
from twisted.trial.runner import (
TestLoader,
TrialRunner
)
from twisted.trial.reporter import TreeReporter
from twisted.plugin import getPlugins, IPlugin
from jnius import autoclass
import lbrynet.tests
from os import listdir
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(TreeReporter):
def addSuccess(self, test):
super(TreeReporter, self).addSuccess(test)
self.endLine('[OK]', self.SUCCESS)
update_output_in_activity(str_to_basic_html(self._stream.getvalue()))
def addError(self, *args):
super(TreeReporter, self).addError(*args)
self.endLine('[ERROR]', self.ERROR)
update_output_in_activity(str_to_basic_html(self._stream.getvalue()))
def addFailure(self, *args):
super(TreeReporter, self).addFailure(*args)
self.endLine('[FAIL]', self.FAILURE)
update_output_in_activity(str_to_basic_html(self._stream.getvalue()))
def addSkip(self, *args):
super(TreeReporter, self).addSkip(*args)
self.endLine('[SKIPPED]', self.SKIP)
update_output_in_activity(str_to_basic_html(self._stream.getvalue()))
def addExpectedFailure(self, *args):
super(TreeReporter, self).addExpectedFailure(*args)
self.endLine('[TODO]', self.TODO)
update_output_in_activity(str_to_basic_html(self._stream.getvalue()))
def addUnexpectedSuccess(self, *args):
super(TreeReporter, self).addUnexpectedSuccess(*args)
self.endLine('[SUCCESS!?!]', self.TODONE)
update_output_in_activity(str_to_basic_html(self._stream.getvalue()))
def startTest(self, test):
super(AndroidTestReporter, self).startTest(test)
update_output_in_activity(str_to_basic_html(self._stream.getvalue()))
def endLine(self, message, color):
super(AndroidTestReporter, self).endLine(message, color)
update_output_in_activity(str_to_basic_html(self._stream.getvalue()))
def str_to_basic_html(value):
return value.replace("\n", "<br>").replace(" ", '&nbsp;')
def run():
loader = TestLoader();
suite = loader.loadPackage(lbrynet.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()