Browse Source

Add test framework, and initial test for nilm-copy

tags/nilmtools-2.0.0
Jim Paris 3 years ago
parent
commit
0f9f0ded71
12 changed files with 43574 additions and 5 deletions
  1. +12
    -0
      .coveragerc
  2. +1
    -0
      .gitignore
  3. +6
    -4
      nilmtools/filter.py
  4. +1
    -1
      nilmtools/trainola.py
  5. +14401
    -0
      tests/data/prep-20120323T1000
  6. +14400
    -0
      tests/data/prep-20120323T1002
  7. +14400
    -0
      tests/data/prep-20120323T1004
  8. +49
    -0
      tests/runtests.py
  9. +3
    -0
      tests/test.order
  10. +236
    -0
      tests/test.py
  11. +1
    -0
      tests/testutil/__init__.py
  12. +64
    -0
      tests/testutil/helpers.py

+ 12
- 0
.coveragerc 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
- 0
.gitignore View File

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

+ 6
- 4
nilmtools/filter.py View File

@@ -1,7 +1,5 @@
#!/usr/bin/python #!/usr/bin/python




import nilmdb.client import nilmdb.client
from nilmdb.client import Client from nilmdb.client import Client
from nilmdb.client.numpyclient import NumpyClient from nilmdb.client.numpyclient import NumpyClient
@@ -15,6 +13,7 @@ import nilmtools
import itertools import itertools
import time import time
import sys import sys
import os
import re import re
import argparse import argparse
import numpy as np import numpy as np
@@ -177,6 +176,7 @@ class Filter(object):
if parser_description is not None: if parser_description is not None:
self.setup_parser(parser_description) self.setup_parser(parser_description)
self.parse_args() self.parse_args()
self.def_url = os.environ.get("NILMDB_URL", "http://localhost/nilmdb/")


@property @property
def client_src(self): def client_src(self):
@@ -193,11 +193,10 @@ class Filter(object):
def setup_parser(self, description = "Filter data", skip_paths = False): def setup_parser(self, description = "Filter data", skip_paths = False):
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
formatter_class = argparse.RawDescriptionHelpFormatter, formatter_class = argparse.RawDescriptionHelpFormatter,
version = nilmtools.__version__,
description = description) description = description)
group = parser.add_argument_group("General filter arguments") group = parser.add_argument_group("General filter arguments")
group.add_argument("-u", "--url", action="store", group.add_argument("-u", "--url", action="store",
default="http://localhost/nilmdb/",
default=self.def_url,
help="Server URL (default: %(default)s)") help="Server URL (default: %(default)s)")
group.add_argument("-U", "--dest-url", action="store", group.add_argument("-U", "--dest-url", action="store",
help="Destination server URL " help="Destination server URL "
@@ -218,6 +217,9 @@ class Filter(object):
metavar="TIME", type=self.arg_time, metavar="TIME", type=self.arg_time,
help="Ending timestamp for intervals " help="Ending timestamp for intervals "
"(free-form, noninclusive)") "(free-form, noninclusive)")
group.add_argument("-v", "--version", action="version",
version = nilmtools.__version__)

if not skip_paths: if not skip_paths:
# Individual filter scripts might want to add these arguments # Individual filter scripts might want to add these arguments
# themselves, to include multiple sources in a different order # themselves, to include multiple sources in a different order


+ 1
- 1
nilmtools/trainola.py View File

@@ -7,7 +7,7 @@ import nilmtools.math
from nilmdb.utils.time import (timestamp_to_human, from nilmdb.utils.time import (timestamp_to_human,
timestamp_to_seconds, timestamp_to_seconds,
seconds_to_timestamp) seconds_to_timestamp)
from nilmdb.utils import datetime_tz
import datetime_tz
from nilmdb.utils.interval import Interval from nilmdb.utils.interval import Interval


import numpy as np import numpy as np


+ 14401
- 0
tests/data/prep-20120323T1000
File diff suppressed because it is too large
View File


+ 14400
- 0
tests/data/prep-20120323T1002
File diff suppressed because it is too large
View File


+ 14400
- 0
tests/data/prep-20120323T1004
File diff suppressed because it is too large
View File


+ 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.py

test*.py

+ 236
- 0
tests/test.py 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

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

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

+ 64
- 0
tests/testutil/helpers.py 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


Loading…
Cancel
Save