Jim Paris
407aedcd20
git-svn-id: https://bucket.mit.edu/svn/nilm/nilmdb@10997 ddd99763-3ecb-0310-9145-efcb8ce7c51f
459 lines
16 KiB
Python
459 lines
16 KiB
Python
import nilmdb
|
|
from nilmdb.printf import *
|
|
import nilmdb.cmdline
|
|
|
|
from nose.tools import *
|
|
from nose.tools import assert_raises
|
|
import itertools
|
|
import datetime_tz
|
|
import os
|
|
import shutil
|
|
import sys
|
|
import threading
|
|
import urllib2
|
|
from urllib2 import urlopen, HTTPError
|
|
import Queue
|
|
import cStringIO
|
|
import shlex
|
|
|
|
from test_helpers import *
|
|
|
|
testdb = "tests/cmdline-testdb"
|
|
|
|
def server_start(max_results = None):
|
|
global test_server, test_db
|
|
# Start web app on a custom port
|
|
test_db = nilmdb.NilmDB(testdb, sync = False, max_results = max_results)
|
|
test_server = nilmdb.Server(test_db, host = "127.0.0.1",
|
|
port = 12380, stoppable = False,
|
|
fast_shutdown = True,
|
|
force_traceback = False)
|
|
test_server.start(blocking = False)
|
|
|
|
def server_stop():
|
|
global test_server, test_db
|
|
# Close web app
|
|
test_server.stop()
|
|
test_db.close()
|
|
|
|
def setup_module():
|
|
global test_server, test_db
|
|
# Clear out DB
|
|
recursive_unlink(testdb)
|
|
server_start()
|
|
|
|
def teardown_module():
|
|
server_stop()
|
|
|
|
class TestCmdline(object):
|
|
|
|
def run(self, arg_string, infile=None, outfile=None):
|
|
"""Run a cmdline client with the specified argument string,
|
|
passing the given input. Returns a tuple with the output and
|
|
exit code"""
|
|
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 = cStringIO.StringIO("")
|
|
# Capture stderr
|
|
errfile = cStringIO.StringIO()
|
|
if outfile is None:
|
|
# If no output file, capture stdout with stderr
|
|
outfile = errfile
|
|
with stdio_wrapper(infile, outfile, errfile) as s:
|
|
try:
|
|
nilmdb.cmdline.Cmdline(shlex.split(arg_string)).run()
|
|
sys.exit(0)
|
|
except SystemExit as e:
|
|
exitcode = e.code
|
|
captured = outfile.getvalue()
|
|
self.captured = captured
|
|
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 fail(self, arg_string, infile = None, exitcode = None):
|
|
self.run(arg_string, infile)
|
|
if exitcode is not None and self.exitcode != exitcode:
|
|
self.dump()
|
|
eq_(self.exitcode, exitcode)
|
|
if self.exitcode == 0:
|
|
self.dump()
|
|
ne_(self.exitcode, 0)
|
|
|
|
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 contents[1:1000] + "\n"
|
|
#print self.captured[1:1000] + "\n"
|
|
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)
|
|
|
|
def test_cmdline_01_basic(self):
|
|
|
|
# help
|
|
self.ok("--help")
|
|
self.contain("usage:")
|
|
|
|
# fail for no args
|
|
self.fail("")
|
|
|
|
# fail for no such option
|
|
self.fail("--nosuchoption")
|
|
|
|
# fail for bad command
|
|
self.fail("badcommand")
|
|
|
|
# try some URL constructions
|
|
self.fail("--url http://nosuchurl/ info")
|
|
self.contain("Couldn't resolve host 'nosuchurl'")
|
|
|
|
self.fail("--url nosuchurl info")
|
|
self.contain("Couldn't resolve host 'nosuchurl'")
|
|
|
|
self.fail("-u nosuchurl/foo info")
|
|
self.contain("Couldn't resolve host 'nosuchurl'")
|
|
|
|
self.fail("-u localhost:0 info")
|
|
self.contain("couldn't connect to host")
|
|
|
|
self.ok("-u localhost:12380 info")
|
|
self.ok("info")
|
|
|
|
# Duplicated arguments should fail, but this isn't implemented
|
|
# due to it being kind of a pain with argparse.
|
|
if 0:
|
|
self.fail("-u url1 -u url2 info")
|
|
self.contain("duplicated argument")
|
|
|
|
self.fail("list --detail --detail")
|
|
self.contain("duplicated argument")
|
|
|
|
self.fail("list --detail --path path1 --path path2")
|
|
self.contain("duplicated argument")
|
|
|
|
self.fail("extract --start 2000-01-01 --start 2001-01-02")
|
|
self.contain("duplicated argument")
|
|
|
|
def test_cmdline_02_info(self):
|
|
self.ok("info")
|
|
self.contain("Server URL: http://localhost:12380/")
|
|
self.contain("Server version: " + test_server.version)
|
|
self.contain("Server database path")
|
|
self.contain("Server database size")
|
|
|
|
def test_cmdline_03_createlist(self):
|
|
# Basic stream tests, like those in test_client.
|
|
|
|
# No streams
|
|
self.ok("list")
|
|
self.match("")
|
|
|
|
# Bad paths
|
|
self.fail("create foo/bar/baz PrepData")
|
|
self.contain("paths must start with /")
|
|
|
|
self.fail("create /foo PrepData")
|
|
self.contain("invalid path")
|
|
|
|
# Bad layout type
|
|
self.fail("create /newton/prep NoSuchLayout")
|
|
self.contain("no such layout")
|
|
|
|
# Create a few streams
|
|
self.ok("create /newton/prep PrepData")
|
|
self.ok("create /newton/raw RawData")
|
|
self.ok("create /newton/zzz/rawnotch RawNotchedData")
|
|
|
|
# Verify we got those 3 streams
|
|
self.ok("list")
|
|
self.match("/newton/prep PrepData\n"
|
|
"/newton/raw RawData\n"
|
|
"/newton/zzz/rawnotch RawNotchedData\n")
|
|
|
|
# Match just one type or one path
|
|
self.ok("list --path /newton/raw")
|
|
self.match("/newton/raw RawData\n")
|
|
|
|
self.ok("list --layout RawData")
|
|
self.match("/newton/raw RawData\n")
|
|
|
|
# Wildcard matches
|
|
self.ok("list --layout Raw*")
|
|
self.match("/newton/raw RawData\n"
|
|
"/newton/zzz/rawnotch RawNotchedData\n")
|
|
|
|
self.ok("list --path *zzz* --layout Raw*")
|
|
self.match("/newton/zzz/rawnotch RawNotchedData\n")
|
|
|
|
self.ok("list --path *zzz* --layout Prep*")
|
|
self.match("")
|
|
|
|
def test_cmdline_04_metadata(self):
|
|
# Set / get metadata
|
|
self.fail("metadata")
|
|
self.fail("metadata --get")
|
|
|
|
self.ok("metadata /newton/prep")
|
|
self.match("")
|
|
|
|
self.ok("metadata /newton/raw --get")
|
|
self.match("")
|
|
|
|
self.ok("metadata /newton/prep --set "
|
|
"'description=The Data' "
|
|
"v_scale=1.234")
|
|
self.ok("metadata /newton/raw --update "
|
|
"'description=The Data'")
|
|
self.ok("metadata /newton/raw --update "
|
|
"v_scale=1.234")
|
|
|
|
# various parsing tests
|
|
self.ok("metadata /newton/raw --update foo=")
|
|
self.fail("metadata /newton/raw --update =bar")
|
|
self.fail("metadata /newton/raw --update foo==bar")
|
|
self.fail("metadata /newton/raw --update foo;bar")
|
|
|
|
# errors
|
|
self.fail("metadata /newton/nosuchstream foo=bar")
|
|
self.contain("unrecognized arguments")
|
|
self.fail("metadata /newton/nosuchstream")
|
|
self.contain("No stream at path")
|
|
self.fail("metadata /newton/nosuchstream --set foo=bar")
|
|
self.contain("No stream at path")
|
|
|
|
self.ok("metadata /newton/prep")
|
|
self.match("description=The Data\nv_scale=1.234\n")
|
|
|
|
self.ok("metadata /newton/prep --get")
|
|
self.match("description=The Data\nv_scale=1.234\n")
|
|
|
|
self.ok("metadata /newton/prep --get descr")
|
|
self.match("descr=\n")
|
|
|
|
self.ok("metadata /newton/prep --get description")
|
|
self.match("description=The Data\n")
|
|
|
|
self.ok("metadata /newton/prep --get description v_scale")
|
|
self.match("description=The Data\nv_scale=1.234\n")
|
|
|
|
self.ok("metadata /newton/prep --set "
|
|
"'description=The Data'")
|
|
|
|
self.ok("metadata /newton/prep --get")
|
|
self.match("description=The Data\n")
|
|
|
|
self.fail("metadata /newton/nosuchpath")
|
|
self.contain("No stream at path /newton/nosuchpath")
|
|
|
|
def test_cmdline_05_parsetime(self):
|
|
os.environ['TZ'] = "America/New_York"
|
|
cmd = nilmdb.cmdline.Cmdline(None)
|
|
test = datetime_tz.datetime_tz.now()
|
|
eq_(cmd.parse_time(str(test)), test)
|
|
test = datetime_tz.datetime_tz.smartparse("20120405 1400-0400")
|
|
eq_(cmd.parse_time("hi there 20120405 1400-0400 testing! 123"), test)
|
|
eq_(cmd.parse_time("20120405 1800 UTC"), test)
|
|
eq_(cmd.parse_time("20120405 1400-0400 UTC"), test)
|
|
with assert_raises(ValueError):
|
|
print cmd.parse_time("20120405 1400-9999")
|
|
with assert_raises(ValueError):
|
|
print cmd.parse_time("hello")
|
|
with assert_raises(ValueError):
|
|
print cmd.parse_time("-")
|
|
with assert_raises(ValueError):
|
|
print cmd.parse_time("")
|
|
with assert_raises(ValueError):
|
|
print cmd.parse_time("14:00")
|
|
eq_(cmd.parse_time("snapshot-20120405-140000.raw.gz"), test)
|
|
eq_(cmd.parse_time("prep-20120405T1400"), test)
|
|
|
|
def test_cmdline_06_insert(self):
|
|
self.ok("insert --help")
|
|
|
|
self.fail("insert /foo/bar baz qwer")
|
|
self.contain("Error getting stream info")
|
|
|
|
self.fail("insert /newton/prep baz qwer")
|
|
self.match("Error opening input file baz\n")
|
|
|
|
self.fail("insert /newton/prep")
|
|
self.contain("Error extracting time")
|
|
|
|
self.fail("insert --start 19801205 /newton/prep 1 2 3 4")
|
|
self.contain("--start can only be used with one input file")
|
|
|
|
self.fail("insert /newton/prep "
|
|
"tests/data/prep-20120323T1000")
|
|
|
|
# insert pre-timestamped data, from stdin
|
|
os.environ['TZ'] = "UTC"
|
|
with open("tests/data/prep-20120323T1004-timestamped") as input:
|
|
self.ok("insert --none /newton/prep", input)
|
|
|
|
# insert data with normal timestamper from filename
|
|
os.environ['TZ'] = "UTC"
|
|
self.ok("insert --rate 120 /newton/prep "
|
|
"tests/data/prep-20120323T1000 "
|
|
"tests/data/prep-20120323T1002")
|
|
|
|
# overlap
|
|
os.environ['TZ'] = "UTC"
|
|
self.fail("insert --rate 120 /newton/prep "
|
|
"tests/data/prep-20120323T1004")
|
|
self.contain("overlap")
|
|
|
|
# Just to help test more situations -- stop and restart
|
|
# the server now. This tests nilmdb's interval caching,
|
|
# at the very least.
|
|
server_stop()
|
|
server_start()
|
|
|
|
# still an overlap if we specify a different start
|
|
os.environ['TZ'] = "America/New_York"
|
|
self.fail("insert --rate 120 --start '03/23/2012 06:05:00' /newton/prep"
|
|
" tests/data/prep-20120323T1004")
|
|
self.contain("overlap")
|
|
|
|
# wrong format
|
|
os.environ['TZ'] = "UTC"
|
|
self.fail("insert --rate 120 /newton/raw "
|
|
"tests/data/prep-20120323T1004")
|
|
self.contain("Error parsing input data")
|
|
|
|
# empty data does nothing
|
|
self.ok("insert --rate 120 --start '03/23/2012 06:05:00' /newton/prep "
|
|
"/dev/null")
|
|
|
|
# bad start time
|
|
self.fail("insert --rate 120 --start 'whatever' /newton/prep /dev/null")
|
|
|
|
def test_cmdline_07_detail(self):
|
|
# Just count the number of lines, it's probably fine
|
|
self.ok("list --detail")
|
|
eq_(self.captured.count('\n'), 11)
|
|
|
|
self.ok("list --detail --path *prep")
|
|
eq_(self.captured.count('\n'), 7)
|
|
|
|
self.ok("list --detail --path *prep --start='23 Mar 2012 10:02'")
|
|
eq_(self.captured.count('\n'), 5)
|
|
|
|
self.ok("list --detail --path *prep --start='23 Mar 2012 10:05'")
|
|
eq_(self.captured.count('\n'), 3)
|
|
|
|
self.ok("list --detail --path *prep --start='23 Mar 2012 10:05:15'")
|
|
eq_(self.captured.count('\n'), 2)
|
|
self.contain("10:05:15.000")
|
|
|
|
self.ok("list --detail --path *prep --start='23 Mar 2012 10:05:15.50'")
|
|
eq_(self.captured.count('\n'), 2)
|
|
self.contain("10:05:15.500")
|
|
|
|
self.ok("list --detail --path *prep --start='23 Mar 2012 19:05:15.50'")
|
|
eq_(self.captured.count('\n'), 2)
|
|
self.contain("no intervals")
|
|
|
|
self.ok("list --detail --path *prep --start='23 Mar 2012 10:05:15.50'"
|
|
+ " --end='23 Mar 2012 10:05:15.50'")
|
|
eq_(self.captured.count('\n'), 2)
|
|
self.contain("10:05:15.500")
|
|
|
|
self.ok("list --detail")
|
|
eq_(self.captured.count('\n'), 11)
|
|
|
|
def test_cmdline_08_extract(self):
|
|
# nonexistent stream
|
|
self.fail("extract /no/such/foo --start 2000-01-01 --end 2020-01-01")
|
|
self.contain("Error getting stream info")
|
|
|
|
# empty ranges return an error
|
|
self.fail("extract -a /newton/prep " +
|
|
"--start '23 Mar 2012 10:00:30' " +
|
|
"--end '23 Mar 2012 10:00:30'", exitcode = 2)
|
|
self.contain("no data")
|
|
self.fail("extract -a /newton/prep " +
|
|
"--start '23 Mar 2012 10:00:30.000001' " +
|
|
"--end '23 Mar 2012 10:00:30.000001'", exitcode = 2)
|
|
self.contain("no data")
|
|
self.fail("extract -a /newton/prep " +
|
|
"--start '23 Mar 2022 10:00:30' " +
|
|
"--end '23 Mar 2022 10:00:30'", exitcode = 2)
|
|
self.contain("no data")
|
|
|
|
# but are ok if we're just counting results
|
|
self.ok("extract --count /newton/prep " +
|
|
"--start '23 Mar 2012 10:00:30' " +
|
|
"--end '23 Mar 2012 10:00:30'")
|
|
self.match("0\n")
|
|
self.ok("extract -c /newton/prep " +
|
|
"--start '23 Mar 2012 10:00:30.000001' " +
|
|
"--end '23 Mar 2012 10:00:30.000001'")
|
|
self.match("0\n")
|
|
|
|
# Check various dumps against stored copies of how they should appear
|
|
def test(file, start, end, extra=""):
|
|
self.ok("extract " + extra + " /newton/prep " +
|
|
"--start '23 Mar 2012 " + start + "' " +
|
|
"--end '23 Mar 2012 " + end + "'")
|
|
self.matchfile("tests/data/extract-" + str(file))
|
|
self.ok("extract --count " + extra + " /newton/prep " +
|
|
"--start '23 Mar 2012 " + start + "' " +
|
|
"--end '23 Mar 2012 " + end + "'")
|
|
self.matchfilecount("tests/data/extract-" + str(file))
|
|
test(1, "10:00:30", "10:00:31", extra="-a")
|
|
test(1, "10:00:30.000000", "10:00:31", extra="-a")
|
|
test(2, "10:00:30.000001", "10:00:31")
|
|
test(2, "10:00:30.008333", "10:00:31")
|
|
test(3, "10:00:30.008333", "10:00:30.008334")
|
|
test(3, "10:00:30.008333", "10:00:30.016667")
|
|
test(4, "10:00:30.008333", "10:00:30.025")
|
|
test(5, "10:00:30", "10:00:31", extra="--annotate --bare")
|
|
test(6, "10:00:30", "10:00:31", extra="-b")
|
|
|
|
# all data put in by tests
|
|
self.ok("extract -a /newton/prep --start 2000-01-01 --end 2020-01-01")
|
|
eq_(self.captured.count('\n'), 43204)
|
|
self.ok("extract -c /newton/prep --start 2000-01-01 --end 2020-01-01")
|
|
self.match("43200\n")
|
|
|
|
def test_cmdline_09_truncated(self):
|
|
# Test truncated responses by overriding the nilmdb max_results
|
|
server_stop()
|
|
server_start(max_results = 2)
|
|
self.ok("list --detail")
|
|
eq_(self.captured.count('\n'), 11)
|
|
server_stop()
|
|
server_start()
|