@@ -7,4 +7,4 @@ | |||
exclude_lines = | |||
pragma: no cover | |||
if 0: | |||
omit = scripts,src/_version.py | |||
omit = scripts,nilmrun/_version.py |
@@ -1,18 +1,15 @@ | |||
URL="http://localhost/nilmdb" | |||
# By default, run the tests. | |||
all: test | |||
all: | |||
ifeq ($(INSIDE_EMACS), t) | |||
@make test | |||
else | |||
@echo "Try 'make install'" | |||
endif | |||
test: | |||
src/trainola.py data.js | |||
test2: | |||
nilmrun/trainola.py data.js | |||
version: | |||
python setup.py version | |||
build: | |||
python setup.py build_ext --inplace | |||
dist: sdist | |||
sdist: | |||
python setup.py sdist | |||
@@ -23,11 +20,29 @@ install: | |||
develop: | |||
python setup.py develop | |||
docs: | |||
make -C docs | |||
lint: | |||
pylint --rcfile=.pylintrc nilmdb | |||
test: | |||
ifeq ($(INSIDE_EMACS), t) | |||
# Use the slightly more flexible script | |||
python setup.py build_ext --inplace | |||
python tests/runtests.py | |||
else | |||
# Let setup.py check dependencies, build stuff, and run the test | |||
python setup.py nosetests | |||
endif | |||
clean:: | |||
rm -f .coverage | |||
find . -name '*pyc' | xargs rm -f | |||
rm -rf nilmtools.egg-info/ build/ MANIFEST.in | |||
make -C docs clean | |||
gitclean:: | |||
git clean -dXf | |||
.PHONY: all test version dist sdist install clean gitclean | |||
.PHONY: all version dist sdist install docs lint test clean gitclean |
@@ -1,3 +1,4 @@ | |||
import nilmrun.threadmanager | |||
from ._version import get_versions | |||
__version__ = get_versions()['version'] |
@@ -181,7 +181,7 @@ def versions_from_parentdir(parentdir_prefix, versionfile_source, verbose=False) | |||
tag_prefix = "nilmrun-" | |||
parentdir_prefix = "nilmrun-" | |||
versionfile_source = "src/_version.py" | |||
versionfile_source = "nilmrun/_version.py" | |||
def get_versions(default={"version": "unknown", "full": ""}, verbose=False): | |||
variables = { "refnames": git_refnames, "full": git_full } |
@@ -41,7 +41,7 @@ class App(object): | |||
def index(self): | |||
cherrypy.response.headers['Content-Type'] = 'text/plain' | |||
msg = sprintf("This is NilmRun version %s, running on host %s.\n", | |||
nilmdb.__version__, socket.getfqdn()) | |||
nilmrun.__version__, socket.getfqdn()) | |||
return msg | |||
# /favicon.ico | |||
@@ -81,6 +81,12 @@ class AppThread(object): | |||
self.manager[pid].clear_log() | |||
return status | |||
# /thread/list | |||
@cherrypy.expose | |||
@cherrypy.tools.json_out() | |||
def list(self): | |||
return list(self.manager) | |||
# /thread/kill | |||
@cherrypy.expose | |||
@cherrypy.tools.json_in() | |||
@@ -97,14 +103,17 @@ class AppThread(object): | |||
class AppFilter(object): | |||
def __init__(self, manager): | |||
self.manager = manager | |||
# /filter/trainola | |||
@cherrypy.expose | |||
@cherrypy.tools.json_in() | |||
@cherrypy.tools.json_out() | |||
@exception_to_httperror(KeyError, ValueError, NilmDBError) | |||
@exception_to_httperror(KeyError, ValueError) | |||
@cherrypy.tools.CORS_allow(methods = ["POST"]) | |||
def trainola(self, data): | |||
return nilmrun.trainola.trainola(data) | |||
return self.manager.run("trainola", nilmrun.trainola.trainola, data) | |||
class Server(object): | |||
def __init__(self, host = '127.0.0.1', port = 8080, | |||
@@ -156,9 +165,10 @@ class Server(object): | |||
cherrypy._cperror._ie_friendly_error_sizes = {} | |||
# Build up the application and mount it | |||
manager = nilmrun.threadmanager.ThreadManager() | |||
root = App() | |||
root.thread = AppThread() | |||
root.filter = AppFilter() | |||
root.thread = AppThread(manager) | |||
root.filter = AppFilter(manager) | |||
cherrypy.tree.apps = {} | |||
cherrypy.tree.mount(root, basepath, config = { "/" : app_config }) | |||
@@ -0,0 +1,46 @@ | |||
#!/usr/bin/python | |||
from nilmdb.utils.printf import * | |||
import threading | |||
class Thread(object): | |||
def __init__(self, name, function, parameters): | |||
self.parameters = parameters | |||
self.start_time = None | |||
self.log = '' | |||
self._terminate = threading.Event() | |||
self._thread = threading.Thread(target = function, | |||
name = name, | |||
args = ( parameters, | |||
self._terminate )) | |||
self._thread.daemon = True | |||
self._thread.start() | |||
def terminate(self, timeout = 60): | |||
self._terminate.set() | |||
self._thread.join(timeout = 60) | |||
@property | |||
def pid(self): | |||
return self._thread.ident | |||
@property | |||
def name(self): | |||
return self._thread.name | |||
@property | |||
def alive(self): | |||
return self._thread.is_alive() | |||
class ThreadManager(object): | |||
def __init__(self): | |||
self.threads = {} | |||
def __iter__(self): | |||
return iter(self.threads.keys()) | |||
def run(self, name, function, parameters): | |||
new = Thread(name, function, parameters) | |||
self.threads[new.pid] = new | |||
return new.pid |
@@ -0,0 +1,22 @@ | |||
[aliases] | |||
test = nosetests | |||
[nosetests] | |||
# Note: values must be set to 1, and have no comments on the same line, | |||
# for "python setup.py nosetests" to work correctly. | |||
nocapture=1 | |||
# Comment this out to see CherryPy logs on failure: | |||
nologcapture=1 | |||
with-coverage=1 | |||
cover-inclusive=1 | |||
cover-package=nilmrun | |||
cover-erase=1 | |||
# this works, puts html output in cover/ dir: | |||
# cover-html=1 | |||
# need nose 1.1.3 for this: | |||
# cover-branches=1 | |||
#debug=nose | |||
#debug-log=nose.log | |||
stop=1 | |||
verbosity=2 | |||
tests=tests |
@@ -30,7 +30,7 @@ except ImportError: | |||
# Versioneer manages version numbers from git tags. | |||
# https://github.com/warner/python-versioneer | |||
import versioneer | |||
versioneer.versionfile_source = 'src/_version.py' | |||
versioneer.versionfile_source = 'nilmrun/_version.py' | |||
versioneer.versionfile_build = 'nilmrun/_version.py' | |||
versioneer.tag_prefix = 'nilmrun-' | |||
versioneer.parentdir_prefix = 'nilmrun-' | |||
@@ -69,7 +69,7 @@ setup(name='nilmrun', | |||
packages = [ 'nilmrun', | |||
'nilmrun.scripts', | |||
], | |||
package_dir = { 'nilmrun': 'src', | |||
package_dir = { 'nilmrun': 'nilmrun', | |||
'nilmrun.scripts': 'scripts', | |||
}, | |||
entry_points = { | |||
@@ -1,11 +0,0 @@ | |||
#!/usr/bin/python | |||
from nilmdb.utils.printf import * | |||
import threading | |||
class ThreadManager(object): | |||
def __init__(self): | |||
self.threads = {} | |||
@@ -0,0 +1,49 @@ | |||
#!/usr/bin/python | |||
import nose | |||
import os | |||
import sys | |||
import glob | |||
from collections import OrderedDict | |||
# Change into parent dir | |||
os.chdir(os.path.dirname(os.path.realpath(__file__)) + "/..") | |||
class JimOrderPlugin(nose.plugins.Plugin): | |||
"""When searching for tests and encountering a directory that | |||
contains a 'test.order' file, run tests listed in that file, in the | |||
order that they're listed. Globs are OK in that file and duplicates | |||
are removed.""" | |||
name = 'jimorder' | |||
score = 10000 | |||
def prepareTestLoader(self, loader): | |||
def wrap(func): | |||
def wrapper(name, *args, **kwargs): | |||
addr = nose.selector.TestAddress( | |||
name, workingDir=loader.workingDir) | |||
try: | |||
order = os.path.join(addr.filename, "test.order") | |||
except Exception: | |||
order = None | |||
if order and os.path.exists(order): | |||
files = [] | |||
for line in open(order): | |||
line = line.split('#')[0].strip() | |||
if not line: | |||
continue | |||
fn = os.path.join(addr.filename, line.strip()) | |||
files.extend(sorted(glob.glob(fn)) or [fn]) | |||
files = list(OrderedDict.fromkeys(files)) | |||
tests = [ wrapper(fn, *args, **kwargs) for fn in files ] | |||
return loader.suiteClass(tests) | |||
return func(name, *args, **kwargs) | |||
return wrapper | |||
loader.loadTestsFromName = wrap(loader.loadTestsFromName) | |||
return loader | |||
# Use setup.cfg for most of the test configuration. Adding | |||
# --with-jimorder here means that a normal "nosetests" run will | |||
# still work, it just won't support test.order. | |||
nose.main(addplugins = [ JimOrderPlugin() ], | |||
argv = sys.argv + ["--with-jimorder"]) |
@@ -0,0 +1,3 @@ | |||
test_client.py | |||
test_*.py |
@@ -0,0 +1,103 @@ | |||
# -*- coding: utf-8 -*- | |||
import nilmrun.server | |||
from nilmdb.client.httpclient import HTTPClient, ClientError | |||
from nilmdb.utils.printf import * | |||
from nose.plugins.skip import SkipTest | |||
from nose.tools import * | |||
from nose.tools import assert_raises | |||
import itertools | |||
import distutils.version | |||
import os | |||
import sys | |||
import threading | |||
import cStringIO | |||
import simplejson as json | |||
import unittest | |||
import warnings | |||
import time | |||
import re | |||
import urllib2 | |||
from urllib2 import urlopen, HTTPError | |||
import requests | |||
from testutil.helpers import * | |||
testurl = "http://localhost:32181/" | |||
def setup_module(): | |||
global test_server | |||
print dir(nilmrun) | |||
# Start web app on a custom port | |||
test_server = nilmrun.server.Server(host = "127.0.0.1", | |||
port = 32181, | |||
force_traceback = True) | |||
test_server.start(blocking = False) | |||
def teardown_module(): | |||
global test_server | |||
# Close web app | |||
test_server.stop() | |||
class TestClient(object): | |||
def test_client_01_basic(self): | |||
client = HTTPClient(baseurl = testurl) | |||
version = client.get("/version") | |||
eq_(distutils.version.LooseVersion(version), | |||
distutils.version.LooseVersion(nilmrun.__version__)) | |||
in_("This is NilmRun", client.get("/")) | |||
with assert_raises(ClientError): | |||
client.get("/favicon.ico") | |||
def test_client_02_manager(self): | |||
client = HTTPClient(baseurl = testurl) | |||
eq_(client.get("/thread/list"), []) | |||
with assert_raises(ClientError): | |||
client.get("/thread/status", { "pid": 12345 }) | |||
with assert_raises(ClientError): | |||
client.get("/thread/kill", { "pid": 12345 }) | |||
def test_client_03_trainola(self): | |||
client = HTTPClient(baseurl = testurl) | |||
data = { "url": "http://bucket.mit.edu/nilmdb", | |||
"stream": "/sharon/prep-a", | |||
"start": 1366111383280463, | |||
"end": 1366126163457797, | |||
"columns": [ { "name": "P1", "index": 0 }, | |||
{ "name": "Q1", "index": 1 }, | |||
{ "name": "P3", "index": 2 } ], | |||
"exemplars": [ | |||
{ "name": "Boiler Pump ON", | |||
"url": "http://bucket.mit.edu/nilmdb", | |||
"stream": "/sharon/prep-a", | |||
"start": 1366260494269078, | |||
"end": 1366260608185031, | |||
"columns": [ { "name": "P1", "index": 0 }, | |||
{ "name": "Q1", "index": 1 } | |||
] | |||
}, | |||
{ "name": "Boiler Pump OFF", | |||
"url": "http://bucket.mit.edu/nilmdb", | |||
"stream": "/sharon/prep-a", | |||
"start": 1366260864215764, | |||
"end": 1366260870882998, | |||
"columns": [ { "name": "P1", "index": 0 }, | |||
{ "name": "Q1", "index": 1 } | |||
] | |||
} | |||
] | |||
} | |||
client.post("/filter/trainola", { "data": data }) | |||
@@ -0,0 +1 @@ | |||
# empty |
@@ -0,0 +1,37 @@ | |||
# Just some helpers for test functions | |||
def myrepr(x): | |||
if isinstance(x, basestring): | |||
return '"' + x + '"' | |||
else: | |||
return repr(x) | |||
def eq_(a, b): | |||
if not a == b: | |||
raise AssertionError("%s != %s" % (myrepr(a), myrepr(b))) | |||
def lt_(a, b): | |||
if not a < b: | |||
raise AssertionError("%s is not less than %s" % (myrepr(a), myrepr(b))) | |||
def in_(a, b): | |||
if a not in b: | |||
raise AssertionError("%s not in %s" % (myrepr(a), myrepr(b))) | |||
def in2_(a1, a2, b): | |||
if a1 not in b and a2 not in b: | |||
raise AssertionError("(%s or %s) not in %s" % (myrepr(a1), myrepr(a2), | |||
myrepr(b))) | |||
def ne_(a, b): | |||
if not a != b: | |||
raise AssertionError("unexpected %s == %s" % (myrepr(a), myrepr(b))) | |||
def lines_(a, n): | |||
l = a.count('\n') | |||
if not l == n: | |||
if len(a) > 5000: | |||
a = a[0:5000] + " ... truncated" | |||
raise AssertionError("wanted %d lines, got %d in output: '%s'" | |||
% (n, l, a)) | |||