Add test framework, and initial test for nilm-copy

This commit is contained in:
Jim Paris 2020-08-04 13:58:07 -04:00
parent f507e793a2
commit 0f9f0ded71
12 changed files with 43574 additions and 5 deletions

12
.coveragerc Normal file
View 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
View File

@ -7,4 +7,5 @@ build/
dist/
*.egg-info/
.eggs/
tests/testdb
MANIFEST

View File

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

View File

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

File diff suppressed because it is too large Load Diff

14400
tests/data/prep-20120323T1002 Normal file

File diff suppressed because it is too large Load Diff

14400
tests/data/prep-20120323T1004 Normal file

File diff suppressed because it is too large Load Diff

49
tests/runtests.py Executable file
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
tests/test.order Normal file
View File

@ -0,0 +1,3 @@
test.py
test*.py

236
tests/test.py Normal file
View 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

View File

@ -0,0 +1 @@
# empty

64
tests/testutil/helpers.py Normal file
View 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