Browse Source

Start test framework; work on server and manager

tags/nilmrun-0.2
Jim Paris 11 years ago
parent
commit
65e48caf5f
16 changed files with 307 additions and 31 deletions
  1. BIN
      .coverage
  2. +1
    -1
      .coveragerc
  3. +26
    -11
      Makefile
  4. +1
    -0
      nilmrun/__init__.py
  5. +1
    -1
      nilmrun/_version.py
  6. +15
    -5
      nilmrun/server.py
  7. +46
    -0
      nilmrun/threadmanager.py
  8. +0
    -0
      nilmrun/trainola.py
  9. +22
    -0
      setup.cfg
  10. +2
    -2
      setup.py
  11. +0
    -11
      src/threadmanager.py
  12. +49
    -0
      tests/runtests.py
  13. +3
    -0
      tests/test.order
  14. +103
    -0
      tests/test_client.py
  15. +1
    -0
      tests/testutil/__init__.py
  16. +37
    -0
      tests/testutil/helpers.py

BIN
.coverage View File


+ 1
- 1
.coveragerc View File

@@ -7,4 +7,4 @@
exclude_lines =
pragma: no cover
if 0:
omit = scripts,src/_version.py
omit = scripts,nilmrun/_version.py

+ 26
- 11
Makefile View File

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

src/__init__.py → nilmrun/__init__.py View File

@@ -1,3 +1,4 @@
import nilmrun.threadmanager

from ._version import get_versions
__version__ = get_versions()['version']

src/_version.py → nilmrun/_version.py View File

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

src/server.py → nilmrun/server.py View File

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


+ 46
- 0
nilmrun/threadmanager.py View File

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

src/trainola.py → nilmrun/trainola.py View File


+ 22
- 0
setup.cfg View File

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

+ 2
- 2
setup.py View File

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


+ 0
- 11
src/threadmanager.py View File

@@ -1,11 +0,0 @@
#!/usr/bin/python

from nilmdb.utils.printf import *

import threading

class ThreadManager(object):
def __init__(self):
self.threads = {}


+ 49
- 0
tests/runtests.py View File

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

+ 3
- 0
tests/test.order View File

@@ -0,0 +1,3 @@
test_client.py

test_*.py

+ 103
- 0
tests/test_client.py View File

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


+ 1
- 0
tests/testutil/__init__.py View File

@@ -0,0 +1 @@
# empty

+ 37
- 0
tests/testutil/helpers.py View File

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


Loading…
Cancel
Save