Add test framework, and initial test for nilm-copy
This commit is contained in:
parent
f507e793a2
commit
0f9f0ded71
12
.coveragerc
Normal file
12
.coveragerc
Normal file
|
@ -0,0 +1,12 @@
|
|||
# -*- conf -*-
|
||||
|
||||
[run]
|
||||
branch = True
|
||||
|
||||
[report]
|
||||
exclude_lines =
|
||||
pragma: no cover
|
||||
if 0:
|
||||
if __name__ == "__main__":
|
||||
omit = nilmtools/_version.py
|
||||
show_missing = True
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -7,4 +7,5 @@ build/
|
|||
dist/
|
||||
*.egg-info/
|
||||
.eggs/
|
||||
tests/testdb
|
||||
MANIFEST
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
|
||||
|
||||
import nilmdb.client
|
||||
from nilmdb.client import Client
|
||||
from nilmdb.client.numpyclient import NumpyClient
|
||||
|
@ -15,6 +13,7 @@ import nilmtools
|
|||
import itertools
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import argparse
|
||||
import numpy as np
|
||||
|
@ -177,6 +176,7 @@ class Filter(object):
|
|||
if parser_description is not None:
|
||||
self.setup_parser(parser_description)
|
||||
self.parse_args()
|
||||
self.def_url = os.environ.get("NILMDB_URL", "http://localhost/nilmdb/")
|
||||
|
||||
@property
|
||||
def client_src(self):
|
||||
|
@ -193,11 +193,10 @@ class Filter(object):
|
|||
def setup_parser(self, description = "Filter data", skip_paths = False):
|
||||
parser = argparse.ArgumentParser(
|
||||
formatter_class = argparse.RawDescriptionHelpFormatter,
|
||||
version = nilmtools.__version__,
|
||||
description = description)
|
||||
group = parser.add_argument_group("General filter arguments")
|
||||
group.add_argument("-u", "--url", action="store",
|
||||
default="http://localhost/nilmdb/",
|
||||
default=self.def_url,
|
||||
help="Server URL (default: %(default)s)")
|
||||
group.add_argument("-U", "--dest-url", action="store",
|
||||
help="Destination server URL "
|
||||
|
@ -218,6 +217,9 @@ class Filter(object):
|
|||
metavar="TIME", type=self.arg_time,
|
||||
help="Ending timestamp for intervals "
|
||||
"(free-form, noninclusive)")
|
||||
group.add_argument("-v", "--version", action="version",
|
||||
version = nilmtools.__version__)
|
||||
|
||||
if not skip_paths:
|
||||
# Individual filter scripts might want to add these arguments
|
||||
# themselves, to include multiple sources in a different order
|
||||
|
|
|
@ -7,7 +7,7 @@ import nilmtools.math
|
|||
from nilmdb.utils.time import (timestamp_to_human,
|
||||
timestamp_to_seconds,
|
||||
seconds_to_timestamp)
|
||||
from nilmdb.utils import datetime_tz
|
||||
import datetime_tz
|
||||
from nilmdb.utils.interval import Interval
|
||||
|
||||
import numpy as np
|
||||
|
|
14401
tests/data/prep-20120323T1000
Normal file
14401
tests/data/prep-20120323T1000
Normal file
File diff suppressed because it is too large
Load Diff
14400
tests/data/prep-20120323T1002
Normal file
14400
tests/data/prep-20120323T1002
Normal file
File diff suppressed because it is too large
Load Diff
14400
tests/data/prep-20120323T1004
Normal file
14400
tests/data/prep-20120323T1004
Normal file
File diff suppressed because it is too large
Load Diff
49
tests/runtests.py
Executable file
49
tests/runtests.py
Executable 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
tests/test.order
Normal file
3
tests/test.order
Normal file
|
@ -0,0 +1,3 @@
|
|||
test.py
|
||||
|
||||
test*.py
|
236
tests/test.py
Normal file
236
tests/test.py
Normal file
|
@ -0,0 +1,236 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import nilmtools.copy_one
|
||||
import nilmtools.cleanup
|
||||
import nilmtools.copy_one
|
||||
import nilmtools.copy_wildcard
|
||||
import nilmtools.decimate_auto
|
||||
import nilmtools.decimate
|
||||
import nilmtools.insert
|
||||
import nilmtools.median
|
||||
import nilmtools.pipewatch
|
||||
import nilmtools.prep
|
||||
import nilmtools.sinefit
|
||||
import nilmtools.trainola
|
||||
|
||||
from nose.tools import assert_raises
|
||||
import unittest
|
||||
|
||||
from testutil.helpers import *
|
||||
|
||||
from nilmtools.filter import ArgumentError
|
||||
|
||||
class CommandTester():
|
||||
|
||||
url = "http://localhost:32182/"
|
||||
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
path = "tests/testdb"
|
||||
recursive_unlink(path)
|
||||
cls.db = nilmdb.utils.serializer_proxy(nilmdb.server.NilmDB)(path)
|
||||
cls.server = nilmdb.server.Server(cls.db, host="127.0.0.1",
|
||||
port=32182, stoppable=True)
|
||||
cls.server.start(blocking=False)
|
||||
|
||||
@classmethod
|
||||
def teardown_class(cls):
|
||||
cls.server.stop()
|
||||
cls.db.close()
|
||||
|
||||
def run(self, arg_string, infile=None, outfile=None):
|
||||
"""Run a cmdline client with the specified argument string,
|
||||
passing the given input. Save the output and exit code."""
|
||||
os.environ['NILMDB_URL'] = self.url
|
||||
class stdio_wrapper:
|
||||
def __init__(self, stdin, stdout, stderr):
|
||||
self.io = (stdin, stdout, stderr)
|
||||
def __enter__(self):
|
||||
self.saved = ( sys.stdin, sys.stdout, sys.stderr )
|
||||
( sys.stdin, sys.stdout, sys.stderr ) = self.io
|
||||
def __exit__(self, type, value, traceback):
|
||||
( sys.stdin, sys.stdout, sys.stderr ) = self.saved
|
||||
# Empty input if none provided
|
||||
if infile is None:
|
||||
infile = io.TextIOWrapper(io.BytesIO(b""))
|
||||
# Capture stderr
|
||||
errfile = io.TextIOWrapper(io.BytesIO())
|
||||
if outfile is None:
|
||||
# If no output file, capture stdout with stderr
|
||||
outfile = errfile
|
||||
with stdio_wrapper(infile, outfile, errfile) as s:
|
||||
try:
|
||||
args = shlex.split(arg_string)
|
||||
sys.argv[0] = "test_runner"
|
||||
self.main(args)
|
||||
sys.exit(0)
|
||||
except SystemExit as e:
|
||||
exitcode = e.code
|
||||
|
||||
# Capture raw binary output, and also try to decode a Unicode
|
||||
# string copy.
|
||||
self.captured_binary = outfile.buffer.getvalue()
|
||||
try:
|
||||
outfile.seek(0)
|
||||
self.captured = outfile.read()
|
||||
except UnicodeDecodeError:
|
||||
self.captured = None
|
||||
|
||||
self.exitcode = exitcode
|
||||
|
||||
def ok(self, arg_string, infile = None):
|
||||
self.run(arg_string, infile)
|
||||
if self.exitcode != 0:
|
||||
self.dump()
|
||||
eq_(self.exitcode, 0)
|
||||
|
||||
def raises(self, exception, arg_string, infile=None):
|
||||
try:
|
||||
self.run(arg_string, infile)
|
||||
except exception as e:
|
||||
self.captured = f"Got correct exception: {e!r}\n"
|
||||
self.captured_binary = b""
|
||||
return
|
||||
self.dump()
|
||||
raise AssertionError(f"Didn't raise exception {exception}")
|
||||
|
||||
def fail(self, arg_string, infile=None,
|
||||
exitcode=None, require_error=True,
|
||||
exception=None):
|
||||
self.run(arg_string, infile)
|
||||
if exitcode is not None and self.exitcode != exitcode:
|
||||
# Wrong exit code
|
||||
self.dump()
|
||||
eq_(self.exitcode, exitcode)
|
||||
if self.exitcode == 0:
|
||||
# Success, when we wanted failure
|
||||
self.dump()
|
||||
ne_(self.exitcode, 0)
|
||||
# Make sure the output contains the word "error" at the
|
||||
# beginning of a line, if requested
|
||||
if require_error and not re.search("^(?:test_runner: )error",
|
||||
self.captured, re.MULTILINE):
|
||||
raise AssertionError("command failed, but output doesn't "
|
||||
"contain the string 'error'")
|
||||
|
||||
def contain(self, checkstring):
|
||||
in_(checkstring, self.captured)
|
||||
|
||||
def match(self, checkstring):
|
||||
eq_(checkstring, self.captured)
|
||||
|
||||
def matchfile(self, file):
|
||||
# Captured data should match file contents exactly
|
||||
with open(file) as f:
|
||||
contents = f.read()
|
||||
if contents != self.captured:
|
||||
print("--- reference file (first 1000 bytes):\n")
|
||||
print(contents[0:1000] + "\n")
|
||||
print("--- captured data (first 1000 bytes):\n")
|
||||
print(self.captured[0:1000] + "\n")
|
||||
zipped = itertools.zip_longest(contents, self.captured)
|
||||
for (n, (a, b)) in enumerate(zipped):
|
||||
if a != b:
|
||||
print("--- first difference is at offset", n)
|
||||
print("--- reference:", repr(a))
|
||||
print("--- captured:", repr(b))
|
||||
break
|
||||
raise AssertionError("captured data doesn't match " + file)
|
||||
|
||||
def matchfilecount(self, file):
|
||||
# Last line of captured data should match the number of
|
||||
# non-commented lines in file
|
||||
count = 0
|
||||
with open(file) as f:
|
||||
for line in f:
|
||||
if line[0] != '#':
|
||||
count += 1
|
||||
eq_(self.captured.splitlines()[-1], sprintf("%d", count))
|
||||
|
||||
def dump(self):
|
||||
printf("-----dump start-----\n%s-----dump end-----\n", self.captured)
|
||||
|
||||
|
||||
class TestAllCommands(CommandTester):
|
||||
|
||||
def load_data(self):
|
||||
client = nilmdb.client.Client(url=self.url)
|
||||
client.stream_create("/newton/prep", "float32_8")
|
||||
client.stream_set_metadata("/newton/prep",
|
||||
{ "description": "newton" })
|
||||
|
||||
for ts in ("20120323T1000", "20120323T1002", "20120323T1004"):
|
||||
start = nilmdb.utils.time.parse_time(ts)
|
||||
fn = f"tests/data/prep-{ts}"
|
||||
data = nilmdb.utils.timestamper.TimestamperRate(fn, start, 120)
|
||||
client.stream_insert("/newton/prep", data);
|
||||
|
||||
def test_copy(self):
|
||||
self.main = nilmtools.copy_one.main
|
||||
|
||||
client = nilmdb.client.Client(url=self.url)
|
||||
|
||||
self.load_data()
|
||||
|
||||
# basic arguments
|
||||
self.fail(f"")
|
||||
self.raises(ArgumentError, f"no-such-src no-such-dest")
|
||||
self.raises(ArgumentError, f"-u {self.url} no-such-src no-such-dest")
|
||||
|
||||
# nonexistent dest
|
||||
self.fail(f"/newton/prep /newton/prep-copy", require_error=False)
|
||||
self.contain("Destination /newton/prep-copy doesn't exist")
|
||||
|
||||
# wrong type
|
||||
client.stream_create("/newton/prep-copy-wrongtype", "uint16_6")
|
||||
self.raises(ValueError, f"/newton/prep /newton/prep-copy-wrongtype")
|
||||
|
||||
# copy with metadata, and compare
|
||||
client.stream_create("/newton/prep-copy", "float32_8")
|
||||
self.ok(f"/newton/prep /newton/prep-copy")
|
||||
a = list(client.stream_extract("/newton/prep"))
|
||||
b = list(client.stream_extract("/newton/prep-copy"))
|
||||
eq_(a, b)
|
||||
a = client.stream_get_metadata("/newton/prep")
|
||||
b = client.stream_get_metadata("/newton/prep-copy")
|
||||
eq_(a, b)
|
||||
|
||||
# copy with no metadata
|
||||
client.stream_create("/newton/prep-copy-nometa", "float32_8")
|
||||
self.ok(f"--nometa /newton/prep /newton/prep-copy-nometa")
|
||||
a = list(client.stream_extract("/newton/prep"))
|
||||
b = list(client.stream_extract("/newton/prep-copy-nometa"))
|
||||
eq_(a, b)
|
||||
a = client.stream_get_metadata("/newton/prep")
|
||||
b = client.stream_get_metadata("/newton/prep-copy-nometa")
|
||||
ne_(a, b)
|
||||
|
||||
def test_copy_wildcard(self):
|
||||
self.main = nilmtools.copy_wildcard.main
|
||||
|
||||
def test_decimate(self):
|
||||
self.main = nilmtools.decimate.main
|
||||
|
||||
def test_decimate_auto(self):
|
||||
self.main = nilmtools.decimate_auto.main
|
||||
|
||||
def test_insert(self):
|
||||
self.main = nilmtools.insert.main
|
||||
|
||||
def test_sinefit(self):
|
||||
self.main = nilmtools.sinefit.main
|
||||
|
||||
def test_cleanup(self):
|
||||
self.main = nilmtools.cleanup.main
|
||||
|
||||
def test_median(self):
|
||||
self.main = nilmtools.median.main
|
||||
|
||||
def test_trainola(self):
|
||||
self.main = nilmtools.trainola.main
|
||||
|
||||
def test_pipewatch(self):
|
||||
self.main = nilmtools.pipewatch.main
|
||||
|
||||
def test_prep(self):
|
||||
self.main = nilmtools.prep.main
|
1
tests/testutil/__init__.py
Normal file
1
tests/testutil/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
# empty
|
64
tests/testutil/helpers.py
Normal file
64
tests/testutil/helpers.py
Normal file
|
@ -0,0 +1,64 @@
|
|||
# Just some helpers for test functions
|
||||
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import shlex
|
||||
import shutil
|
||||
|
||||
import nilmdb.server
|
||||
import nilmdb.utils
|
||||
import nilmdb.utils.timestamper
|
||||
from nilmdb.utils.printf import printf
|
||||
|
||||
def myrepr(x):
|
||||
if isinstance(x, str):
|
||||
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 nin_(a, b):
|
||||
if a in b:
|
||||
raise AssertionError("unexpected %s 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))
|
||||
|
||||
def recursive_unlink(path):
|
||||
try:
|
||||
shutil.rmtree(path)
|
||||
except OSError:
|
||||
pass
|
||||
try:
|
||||
os.unlink(path)
|
||||
except OSError:
|
||||
pass
|
||||
|
Loading…
Reference in New Issue
Block a user