You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

893 lines
33 KiB

  1. # -*- coding: utf-8 -*-
  2. import nilmdb.server
  3. from nilmdb.utils.printf import *
  4. import nilmdb.cmdline
  5. from nilmdb.utils import datetime_tz
  6. import unittest
  7. from nose.tools import *
  8. from nose.tools import assert_raises
  9. import itertools
  10. import os
  11. import re
  12. import sys
  13. import StringIO
  14. import shlex
  15. from testutil.helpers import *
  16. testdb = "tests/cmdline-testdb"
  17. def server_start(max_results = None, bulkdata_args = {}):
  18. global test_server, test_db
  19. # Start web app on a custom port
  20. test_db = nilmdb.utils.serializer_proxy(nilmdb.server.NilmDB)(
  21. testdb, sync = False,
  22. max_results = max_results,
  23. bulkdata_args = bulkdata_args)
  24. test_server = nilmdb.server.Server(test_db, host = "127.0.0.1",
  25. port = 32180, stoppable = False,
  26. fast_shutdown = True,
  27. force_traceback = False)
  28. test_server.start(blocking = False)
  29. def server_stop():
  30. global test_server, test_db
  31. # Close web app
  32. test_server.stop()
  33. test_db.close()
  34. def setup_module():
  35. global test_server, test_db
  36. # Clear out DB
  37. recursive_unlink(testdb)
  38. server_start()
  39. def teardown_module():
  40. server_stop()
  41. # Add an encoding property to StringIO so Python will convert Unicode
  42. # properly when writing or reading.
  43. class UTF8StringIO(StringIO.StringIO):
  44. encoding = 'utf-8'
  45. class TestCmdline(object):
  46. def run(self, arg_string, infile=None, outfile=None):
  47. """Run a cmdline client with the specified argument string,
  48. passing the given input. Returns a tuple with the output and
  49. exit code"""
  50. # printf("TZ=UTC ./nilmtool.py %s\n", arg_string)
  51. os.environ['NILMDB_URL'] = "http://localhost:32180/"
  52. class stdio_wrapper:
  53. def __init__(self, stdin, stdout, stderr):
  54. self.io = (stdin, stdout, stderr)
  55. def __enter__(self):
  56. self.saved = ( sys.stdin, sys.stdout, sys.stderr )
  57. ( sys.stdin, sys.stdout, sys.stderr ) = self.io
  58. def __exit__(self, type, value, traceback):
  59. ( sys.stdin, sys.stdout, sys.stderr ) = self.saved
  60. # Empty input if none provided
  61. if infile is None:
  62. infile = UTF8StringIO("")
  63. # Capture stderr
  64. errfile = UTF8StringIO()
  65. if outfile is None:
  66. # If no output file, capture stdout with stderr
  67. outfile = errfile
  68. with stdio_wrapper(infile, outfile, errfile) as s:
  69. try:
  70. # shlex doesn't support Unicode very well. Encode the
  71. # string as UTF-8 explicitly before splitting.
  72. args = shlex.split(arg_string.encode('utf-8'))
  73. nilmdb.cmdline.Cmdline(args).run()
  74. sys.exit(0)
  75. except SystemExit as e:
  76. exitcode = e.code
  77. captured = outfile.getvalue()
  78. self.captured = captured
  79. self.exitcode = exitcode
  80. def ok(self, arg_string, infile = None):
  81. self.run(arg_string, infile)
  82. if self.exitcode != 0:
  83. self.dump()
  84. eq_(self.exitcode, 0)
  85. def fail(self, arg_string, infile = None,
  86. exitcode = None, require_error = True):
  87. self.run(arg_string, infile)
  88. if exitcode is not None and self.exitcode != exitcode:
  89. # Wrong exit code
  90. self.dump()
  91. eq_(self.exitcode, exitcode)
  92. if self.exitcode == 0:
  93. # Success, when we wanted failure
  94. self.dump()
  95. ne_(self.exitcode, 0)
  96. # Make sure the output contains the word "error" at the
  97. # beginning of a line, but only if an exitcode wasn't
  98. # specified.
  99. if require_error and not re.search("^error",
  100. self.captured, re.MULTILINE):
  101. raise AssertionError("command failed, but output doesn't "
  102. "contain the string 'error'")
  103. def contain(self, checkstring):
  104. in_(checkstring, self.captured)
  105. def match(self, checkstring):
  106. eq_(checkstring, self.captured)
  107. def matchfile(self, file):
  108. # Captured data should match file contents exactly
  109. with open(file) as f:
  110. contents = f.read()
  111. if contents != self.captured:
  112. print "--- reference file (first 1000 bytes):\n"
  113. print contents[0:1000] + "\n"
  114. print "--- captured data (first 1000 bytes):\n"
  115. print self.captured[0:1000] + "\n"
  116. zipped = itertools.izip_longest(contents, self.captured)
  117. for (n, (a, b)) in enumerate(zipped):
  118. if a != b:
  119. print "--- first difference is at offset", n
  120. print "--- reference:", repr(a)
  121. print "--- captured:", repr(b)
  122. break
  123. raise AssertionError("captured data doesn't match " + file)
  124. def matchfilecount(self, file):
  125. # Last line of captured data should match the number of
  126. # non-commented lines in file
  127. count = 0
  128. with open(file) as f:
  129. for line in f:
  130. if line[0] != '#':
  131. count += 1
  132. eq_(self.captured.splitlines()[-1], sprintf("%d", count))
  133. def dump(self):
  134. printf("-----dump start-----\n%s-----dump end-----\n", self.captured)
  135. def test_01_basic(self):
  136. # help
  137. self.ok("--help")
  138. self.contain("usage:")
  139. # fail for no args
  140. self.fail("")
  141. # fail for no such option
  142. self.fail("--nosuchoption")
  143. # fail for bad command
  144. self.fail("badcommand")
  145. # try some URL constructions
  146. self.fail("--url http://nosuchurl/ info")
  147. self.contain("error connecting to server")
  148. self.fail("--url nosuchurl info")
  149. self.contain("error connecting to server")
  150. self.fail("-u nosuchurl/foo info")
  151. self.contain("error connecting to server")
  152. self.fail("-u localhost:1 info")
  153. self.contain("error connecting to server")
  154. self.ok("-u localhost:32180 info")
  155. self.ok("info")
  156. # Duplicated arguments should fail, but this isn't implemented
  157. # due to it being kind of a pain with argparse.
  158. if 0:
  159. self.fail("-u url1 -u url2 info")
  160. self.contain("duplicated argument")
  161. self.fail("list --detail --detail")
  162. self.contain("duplicated argument")
  163. self.fail("list --detail --path path1 --path path2")
  164. self.contain("duplicated argument")
  165. self.fail("extract --start 2000-01-01 --start 2001-01-02")
  166. self.contain("duplicated argument")
  167. # Verify that "help command" and "command --help" are identical
  168. # for all commands.
  169. self.fail("")
  170. m = re.search(r"{(.*)}", self.captured)
  171. for command in [""] + m.group(1).split(','):
  172. self.ok(command + " --help")
  173. cap1 = self.captured
  174. self.ok("help " + command)
  175. cap2 = self.captured
  176. self.ok("help " + command + " asdf --url --zxcv -")
  177. cap3 = self.captured
  178. eq_(cap1, cap2)
  179. eq_(cap2, cap3)
  180. def test_02_parsetime(self):
  181. os.environ['TZ'] = "America/New_York"
  182. test = datetime_tz.datetime_tz.now()
  183. parse_time = nilmdb.utils.time.parse_time
  184. eq_(parse_time(str(test)), test)
  185. test = datetime_tz.datetime_tz.smartparse("20120405 1400-0400")
  186. eq_(parse_time("hi there 20120405 1400-0400 testing! 123"), test)
  187. eq_(parse_time("20120405 1800 UTC"), test)
  188. eq_(parse_time("20120405 1400-0400 UTC"), test)
  189. for badtime in [ "20120405 1400-9999", "hello", "-", "", "4:00" ]:
  190. with assert_raises(ValueError):
  191. x = parse_time(badtime)
  192. x = parse_time("now")
  193. eq_(parse_time("snapshot-20120405-140000.raw.gz"), test)
  194. eq_(parse_time("prep-20120405T1400"), test)
  195. def test_03_info(self):
  196. self.ok("info")
  197. self.contain("Server URL: http://localhost:32180/")
  198. self.contain("Client version: " + nilmdb.__version__)
  199. self.contain("Server version: " + test_server.version)
  200. self.contain("Server database path")
  201. self.contain("Server database size")
  202. self.contain("Server database free space")
  203. def test_04_createlist(self):
  204. # Basic stream tests, like those in test_client.
  205. # No streams
  206. self.ok("list")
  207. self.match("")
  208. # Bad paths
  209. self.fail("create foo/bar/baz float32_8")
  210. self.contain("paths must start with /")
  211. self.fail("create /foo float32_8")
  212. self.contain("invalid path")
  213. # Bad layout type
  214. self.fail("create /newton/prep NoSuchLayout")
  215. self.contain("no such layout")
  216. self.fail("create /newton/prep float32_0")
  217. self.contain("no such layout")
  218. self.fail("create /newton/prep float33_1")
  219. self.contain("no such layout")
  220. # Create a few streams
  221. self.ok("create /newton/zzz/rawnotch uint16_9")
  222. self.ok("create /newton/prep float32_8")
  223. self.ok("create /newton/raw uint16_6")
  224. # Should not be able to create a stream with another stream as
  225. # its parent
  226. self.fail("create /newton/prep/blah float32_8")
  227. self.contain("path is subdir of existing node")
  228. # Should not be able to create a stream at a location that
  229. # has other nodes as children
  230. self.fail("create /newton/zzz float32_8")
  231. self.contain("subdirs of this path already exist")
  232. # Verify we got those 3 streams and they're returned in
  233. # alphabetical order.
  234. self.ok("list")
  235. self.match("/newton/prep float32_8\n"
  236. "/newton/raw uint16_6\n"
  237. "/newton/zzz/rawnotch uint16_9\n")
  238. # Match just one type or one path. Also check
  239. # that --path is optional
  240. self.ok("list --path /newton/raw")
  241. self.match("/newton/raw uint16_6\n")
  242. self.ok("list /newton/raw")
  243. self.match("/newton/raw uint16_6\n")
  244. self.fail("list -p /newton/raw /newton/raw")
  245. self.contain("too many paths")
  246. self.ok("list --layout uint16_6")
  247. self.match("/newton/raw uint16_6\n")
  248. # Wildcard matches
  249. self.ok("list --layout uint16*")
  250. self.match("/newton/raw uint16_6\n"
  251. "/newton/zzz/rawnotch uint16_9\n")
  252. self.ok("list --path *zzz* --layout uint16*")
  253. self.match("/newton/zzz/rawnotch uint16_9\n")
  254. self.ok("list *zzz* --layout uint16*")
  255. self.match("/newton/zzz/rawnotch uint16_9\n")
  256. self.ok("list --path *zzz* --layout float32*")
  257. self.match("")
  258. # reversed range
  259. self.fail("list /newton/prep --start 2020-01-01 --end 2000-01-01")
  260. self.contain("start must precede end")
  261. def test_05_metadata(self):
  262. # Set / get metadata
  263. self.fail("metadata")
  264. self.fail("metadata --get")
  265. self.ok("metadata /newton/prep")
  266. self.match("")
  267. self.ok("metadata /newton/raw --get")
  268. self.match("")
  269. self.ok("metadata /newton/prep --set "
  270. "'description=The Data' "
  271. "v_scale=1.234")
  272. self.ok("metadata /newton/raw --update "
  273. "'description=The Data'")
  274. self.ok("metadata /newton/raw --update "
  275. "v_scale=1.234")
  276. # various parsing tests
  277. self.ok("metadata /newton/raw --update foo=")
  278. self.fail("metadata /newton/raw --update =bar")
  279. self.fail("metadata /newton/raw --update foo==bar")
  280. self.fail("metadata /newton/raw --update foo;bar")
  281. # errors
  282. self.fail("metadata /newton/nosuchstream foo=bar")
  283. self.contain("unrecognized arguments")
  284. self.fail("metadata /newton/nosuchstream")
  285. self.contain("No stream at path")
  286. self.fail("metadata /newton/nosuchstream --set foo=bar")
  287. self.contain("No stream at path")
  288. self.ok("metadata /newton/prep")
  289. self.match("description=The Data\nv_scale=1.234\n")
  290. self.ok("metadata /newton/prep --get")
  291. self.match("description=The Data\nv_scale=1.234\n")
  292. self.ok("metadata /newton/prep --get descr")
  293. self.match("descr=\n")
  294. self.ok("metadata /newton/prep --get description")
  295. self.match("description=The Data\n")
  296. self.ok("metadata /newton/prep --get description v_scale")
  297. self.match("description=The Data\nv_scale=1.234\n")
  298. self.ok("metadata /newton/prep --set "
  299. "'description=The Data'")
  300. self.ok("metadata /newton/prep --get")
  301. self.match("description=The Data\n")
  302. self.fail("metadata /newton/nosuchpath")
  303. self.contain("No stream at path /newton/nosuchpath")
  304. def test_06_insert(self):
  305. self.ok("insert --help")
  306. self.fail("insert -s 2000 -e 2001 /foo/bar baz")
  307. self.contain("error getting stream info")
  308. self.fail("insert -s 2000 -e 2001 /newton/prep baz")
  309. self.match("error opening input file baz\n")
  310. self.fail("insert /newton/prep --timestamp -f -r 120")
  311. self.contain("error extracting start time")
  312. self.fail("insert /newton/prep --timestamp -r 120")
  313. self.contain("need --start or --filename")
  314. self.fail("insert /newton/prep "
  315. "tests/data/prep-20120323T1000")
  316. # insert pre-timestamped data, with bad times (non-monotonic)
  317. os.environ['TZ'] = "UTC"
  318. with open("tests/data/prep-20120323T1004-badtimes") as input:
  319. self.fail("insert -s 20120323T1004 -e 20120323T1006 /newton/prep",
  320. input)
  321. self.contain("error parsing input data")
  322. self.contain("line 7:")
  323. self.contain("timestamp is not monotonically increasing")
  324. # insert pre-timestamped data, from stdin
  325. os.environ['TZ'] = "UTC"
  326. with open("tests/data/prep-20120323T1004-timestamped") as input:
  327. self.ok("insert -s 20120323T1004 -e 20120323T1006 /newton/prep",
  328. input)
  329. # insert data with normal timestamper from filename
  330. os.environ['TZ'] = "UTC"
  331. self.ok("insert --timestamp -f --rate 120 /newton/prep "
  332. "tests/data/prep-20120323T1000")
  333. self.fail("insert -t --filename /newton/prep "
  334. "tests/data/prep-20120323T1002")
  335. self.contain("rate is needed")
  336. self.ok("insert -t --filename --rate 120 /newton/prep "
  337. "tests/data/prep-20120323T1002")
  338. # overlap
  339. os.environ['TZ'] = "UTC"
  340. self.fail("insert --timestamp -f --rate 120 /newton/prep "
  341. "tests/data/prep-20120323T1004")
  342. self.contain("overlap")
  343. # Just to help test more situations -- stop and restart
  344. # the server now. This tests nilmdb's interval caching,
  345. # at the very least.
  346. server_stop()
  347. server_start()
  348. # still an overlap if we specify a different start
  349. os.environ['TZ'] = "America/New_York"
  350. self.fail("insert -t -r 120 --start '03/23/2012 06:05:00' /newton/prep"
  351. " tests/data/prep-20120323T1004")
  352. self.contain("overlap")
  353. # wrong format
  354. os.environ['TZ'] = "UTC"
  355. self.fail("insert -t -r 120 -f /newton/raw "
  356. "tests/data/prep-20120323T1004")
  357. self.contain("error parsing input data")
  358. # empty data does nothing
  359. self.ok("insert -t -r 120 --start '03/23/2012 06:05:00' /newton/prep "
  360. "/dev/null")
  361. # bad start time
  362. self.fail("insert -t -r 120 --start 'whatever' /newton/prep /dev/null")
  363. def test_07_detail_extent(self):
  364. # Just count the number of lines, it's probably fine
  365. self.ok("list --detail")
  366. lines_(self.captured, 8)
  367. self.ok("list --detail --path *prep")
  368. lines_(self.captured, 4)
  369. self.ok("list --detail --path *prep --start='23 Mar 2012 10:02'")
  370. lines_(self.captured, 3)
  371. self.ok("list --detail --path *prep --start='23 Mar 2012 10:05'")
  372. lines_(self.captured, 2)
  373. self.ok("list --detail --path *prep --start='23 Mar 2012 10:05:15'")
  374. lines_(self.captured, 2)
  375. self.contain("10:05:15.000")
  376. self.ok("list --detail --path *prep --start='23 Mar 2012 10:05:15.50'")
  377. lines_(self.captured, 2)
  378. self.contain("10:05:15.500")
  379. self.ok("list --detail --path *prep --start='23 Mar 2012 19:05:15.50'")
  380. lines_(self.captured, 2)
  381. self.contain("no intervals")
  382. self.ok("list --detail --path *prep --start='23 Mar 2012 10:05:15.50'"
  383. + " --end='23 Mar 2012 10:05:15.51'")
  384. lines_(self.captured, 2)
  385. self.contain("10:05:15.500")
  386. self.ok("list --detail")
  387. lines_(self.captured, 8)
  388. # Verify the "raw timestamp" output
  389. self.ok("list --detail --path *prep --timestamp-raw "
  390. "--start='23 Mar 2012 10:05:15.50'")
  391. lines_(self.captured, 2)
  392. self.contain("[ 1332497115.500000 -> 1332497160.000000 ]")
  393. # bad time
  394. self.fail("list --detail --path *prep -T --start='9332497115.612'")
  395. # good time
  396. self.ok("list --detail --path *prep -T --start='1332497115.612'")
  397. lines_(self.captured, 2)
  398. self.contain("[ 1332497115.612000 -> 1332497160.000000 ]")
  399. # Check --extent output
  400. self.ok("list --extent")
  401. lines_(self.captured, 6)
  402. self.ok("list -E -T")
  403. self.contain(" extent: 1332496800.000000 -> 1332497160.000000")
  404. self.contain(" extent: (no data)")
  405. # Misc
  406. self.fail("list --extent --start='23 Mar 2012 10:05:15.50'")
  407. self.contain("--start and --end only make sense with --detail")
  408. def test_08_extract(self):
  409. # nonexistent stream
  410. self.fail("extract /no/such/foo --start 2000-01-01 --end 2020-01-01")
  411. self.contain("error getting stream info")
  412. # reversed range
  413. self.fail("extract -a /newton/prep --start 2020-01-01 --end 2000-01-01")
  414. self.contain("start is after end")
  415. # empty ranges return error 2
  416. self.fail("extract -a /newton/prep " +
  417. "--start '23 Mar 2012 20:00:30' " +
  418. "--end '23 Mar 2012 20:00:31'",
  419. exitcode = 2, require_error = False)
  420. self.contain("no data")
  421. self.fail("extract -a /newton/prep " +
  422. "--start '23 Mar 2012 20:00:30.000001' " +
  423. "--end '23 Mar 2012 20:00:30.000002'",
  424. exitcode = 2, require_error = False)
  425. self.contain("no data")
  426. self.fail("extract -a /newton/prep " +
  427. "--start '23 Mar 2022 10:00:30' " +
  428. "--end '23 Mar 2022 10:00:31'",
  429. exitcode = 2, require_error = False)
  430. self.contain("no data")
  431. # but are ok if we're just counting results
  432. self.ok("extract --count /newton/prep " +
  433. "--start '23 Mar 2012 20:00:30' " +
  434. "--end '23 Mar 2012 20:00:31'")
  435. self.match("0\n")
  436. self.ok("extract -c /newton/prep " +
  437. "--start '23 Mar 2012 20:00:30.000001' " +
  438. "--end '23 Mar 2012 20:00:30.000002'")
  439. self.match("0\n")
  440. # Check various dumps against stored copies of how they should appear
  441. def test(file, start, end, extra=""):
  442. self.ok("extract " + extra + " /newton/prep " +
  443. "--start '23 Mar 2012 " + start + "' " +
  444. "--end '23 Mar 2012 " + end + "'")
  445. self.matchfile("tests/data/extract-" + str(file))
  446. self.ok("extract --count " + extra + " /newton/prep " +
  447. "--start '23 Mar 2012 " + start + "' " +
  448. "--end '23 Mar 2012 " + end + "'")
  449. self.matchfilecount("tests/data/extract-" + str(file))
  450. test(1, "10:00:30", "10:00:31", extra="-a")
  451. test(1, "10:00:30.000000", "10:00:31", extra="-a")
  452. test(2, "10:00:30.000001", "10:00:31")
  453. test(2, "10:00:30.008333", "10:00:31")
  454. test(3, "10:00:30.008333", "10:00:30.008334")
  455. test(3, "10:00:30.008333", "10:00:30.016667")
  456. test(4, "10:00:30.008333", "10:00:30.025")
  457. test(5, "10:00:30", "10:00:31", extra="--annotate --bare")
  458. test(6, "10:00:30", "10:00:31", extra="-b")
  459. test(7, "10:00:30", "10:00:30.999", extra="-a -T")
  460. test(7, "10:00:30", "10:00:30.999", extra="-a --timestamp-raw")
  461. # all data put in by tests
  462. self.ok("extract -a /newton/prep --start 2000-01-01 --end 2020-01-01")
  463. lines_(self.captured, 43204)
  464. self.ok("extract -c /newton/prep --start 2000-01-01 --end 2020-01-01")
  465. self.match("43200\n")
  466. def test_09_truncated(self):
  467. # Test truncated responses by overriding the nilmdb max_results
  468. server_stop()
  469. server_start(max_results = 2)
  470. self.ok("list --detail")
  471. lines_(self.captured, 8)
  472. server_stop()
  473. server_start()
  474. def test_10_remove(self):
  475. # Removing data
  476. # Try nonexistent stream
  477. self.fail("remove /no/such/foo --start 2000-01-01 --end 2020-01-01")
  478. self.contain("No stream at path")
  479. # empty or backward ranges return errors
  480. self.fail("remove /newton/prep --start 2020-01-01 --end 2000-01-01")
  481. self.contain("start must precede end")
  482. self.fail("remove /newton/prep " +
  483. "--start '23 Mar 2012 10:00:30' " +
  484. "--end '23 Mar 2012 10:00:30'")
  485. self.contain("start must precede end")
  486. self.fail("remove /newton/prep " +
  487. "--start '23 Mar 2012 10:00:30.000001' " +
  488. "--end '23 Mar 2012 10:00:30.000001'")
  489. self.contain("start must precede end")
  490. self.fail("remove /newton/prep " +
  491. "--start '23 Mar 2022 10:00:30' " +
  492. "--end '23 Mar 2022 10:00:30'")
  493. self.contain("start must precede end")
  494. # Verbose
  495. self.ok("remove -c /newton/prep " +
  496. "--start '23 Mar 2022 20:00:30' " +
  497. "--end '23 Mar 2022 20:00:31'")
  498. self.match("0\n")
  499. self.ok("remove --count /newton/prep " +
  500. "--start '23 Mar 2022 20:00:30' " +
  501. "--end '23 Mar 2022 20:00:31'")
  502. self.match("0\n")
  503. # Make sure we have the data we expect
  504. self.ok("list --detail /newton/prep")
  505. self.match("/newton/prep float32_8\n" +
  506. " [ Fri, 23 Mar 2012 10:00:00.000000 +0000"
  507. " -> Fri, 23 Mar 2012 10:01:59.991668 +0000 ]\n"
  508. " [ Fri, 23 Mar 2012 10:02:00.000000 +0000"
  509. " -> Fri, 23 Mar 2012 10:03:59.991668 +0000 ]\n"
  510. " [ Fri, 23 Mar 2012 10:04:00.000000 +0000"
  511. " -> Fri, 23 Mar 2012 10:06:00.000000 +0000 ]\n")
  512. # Remove various chunks of prep data and make sure
  513. # they're gone.
  514. self.ok("remove -c /newton/prep " +
  515. "--start '23 Mar 2012 10:00:30' " +
  516. "--end '23 Mar 2012 10:00:40'")
  517. self.match("1200\n")
  518. self.ok("remove -c /newton/prep " +
  519. "--start '23 Mar 2012 10:00:10' " +
  520. "--end '23 Mar 2012 10:00:20'")
  521. self.match("1200\n")
  522. self.ok("remove -c /newton/prep " +
  523. "--start '23 Mar 2012 10:00:05' " +
  524. "--end '23 Mar 2012 10:00:25'")
  525. self.match("1200\n")
  526. self.ok("remove -c /newton/prep " +
  527. "--start '23 Mar 2012 10:03:50' " +
  528. "--end '23 Mar 2012 10:06:50'")
  529. self.match("15600\n")
  530. self.ok("extract -c /newton/prep --start 2000-01-01 --end 2020-01-01")
  531. self.match("24000\n")
  532. # See the missing chunks in list output
  533. self.ok("list --detail /newton/prep")
  534. self.match("/newton/prep float32_8\n" +
  535. " [ Fri, 23 Mar 2012 10:00:00.000000 +0000"
  536. " -> Fri, 23 Mar 2012 10:00:05.000000 +0000 ]\n"
  537. " [ Fri, 23 Mar 2012 10:00:25.000000 +0000"
  538. " -> Fri, 23 Mar 2012 10:00:30.000000 +0000 ]\n"
  539. " [ Fri, 23 Mar 2012 10:00:40.000000 +0000"
  540. " -> Fri, 23 Mar 2012 10:01:59.991668 +0000 ]\n"
  541. " [ Fri, 23 Mar 2012 10:02:00.000000 +0000"
  542. " -> Fri, 23 Mar 2012 10:03:50.000000 +0000 ]\n")
  543. # Remove all data, verify it's missing
  544. self.ok("remove /newton/prep --start 2000-01-01 --end 2020-01-01")
  545. self.match("") # no count requested this time
  546. self.ok("list --detail /newton/prep")
  547. self.match("/newton/prep float32_8\n" +
  548. " (no intervals)\n")
  549. # Reinsert some data, to verify that no overlaps with deleted
  550. # data are reported
  551. os.environ['TZ'] = "UTC"
  552. self.ok("insert --timestamp -f --rate 120 /newton/prep "
  553. "tests/data/prep-20120323T1000")
  554. self.ok("insert -t --filename --rate 120 /newton/prep "
  555. "tests/data/prep-20120323T1002")
  556. def test_11_destroy(self):
  557. # Delete records
  558. self.ok("destroy --help")
  559. self.fail("destroy")
  560. self.contain("too few arguments")
  561. self.fail("destroy /no/such/stream")
  562. self.contain("No stream at path")
  563. self.fail("destroy asdfasdf")
  564. self.contain("No stream at path")
  565. # From previous tests, we have:
  566. self.ok("list")
  567. self.match("/newton/prep float32_8\n"
  568. "/newton/raw uint16_6\n"
  569. "/newton/zzz/rawnotch uint16_9\n")
  570. # Notice how they're not empty
  571. self.ok("list --detail")
  572. lines_(self.captured, 7)
  573. # Delete some
  574. self.ok("destroy /newton/prep")
  575. self.ok("list")
  576. self.match("/newton/raw uint16_6\n"
  577. "/newton/zzz/rawnotch uint16_9\n")
  578. self.ok("destroy /newton/zzz/rawnotch")
  579. self.ok("list")
  580. self.match("/newton/raw uint16_6\n")
  581. self.ok("destroy /newton/raw")
  582. self.ok("create /newton/raw uint16_6")
  583. self.ok("destroy /newton/raw")
  584. self.ok("list")
  585. self.match("")
  586. # Re-create a previously deleted location, and some new ones
  587. rebuild = [ "/newton/prep", "/newton/zzz",
  588. "/newton/raw", "/newton/asdf/qwer" ]
  589. for path in rebuild:
  590. # Create the path
  591. self.ok("create " + path + " float32_8")
  592. self.ok("list")
  593. self.contain(path)
  594. # Make sure it was created empty
  595. self.ok("list --detail --path " + path)
  596. self.contain("(no intervals)")
  597. def test_12_unicode(self):
  598. # Unicode paths.
  599. self.ok("destroy /newton/asdf/qwer")
  600. self.ok("destroy /newton/prep")
  601. self.ok("destroy /newton/raw")
  602. self.ok("destroy /newton/zzz")
  603. self.ok(u"create /düsseldorf/raw uint16_6")
  604. self.ok("list --detail")
  605. self.contain(u"/düsseldorf/raw uint16_6")
  606. self.contain("(no intervals)")
  607. # Unicode metadata
  608. self.ok(u"metadata /düsseldorf/raw --set α=beta 'γ=δ'")
  609. self.ok(u"metadata /düsseldorf/raw --update 'α=β ε τ α'")
  610. self.ok(u"metadata /düsseldorf/raw")
  611. self.match(u"α=β ε τ α\nγ=δ\n")
  612. self.ok(u"destroy /düsseldorf/raw")
  613. def test_13_files(self):
  614. # Test BulkData's ability to split into multiple files,
  615. # by forcing the file size to be really small.
  616. server_stop()
  617. server_start(bulkdata_args = { "file_size" : 920, # 23 rows per file
  618. "files_per_dir" : 3 })
  619. # Fill data
  620. self.ok("create /newton/prep float32_8")
  621. os.environ['TZ'] = "UTC"
  622. with open("tests/data/prep-20120323T1004-timestamped") as input:
  623. self.ok("insert -s 20120323T1004 -e 20120323T1006 /newton/prep",
  624. input)
  625. # Extract it
  626. self.ok("extract /newton/prep --start '2000-01-01' " +
  627. "--end '2012-03-23 10:04:01'")
  628. lines_(self.captured, 120)
  629. self.ok("extract /newton/prep --start '2000-01-01' " +
  630. "--end '2022-03-23 10:04:01'")
  631. lines_(self.captured, 14400)
  632. # Make sure there were lots of files generated in the database
  633. # dir
  634. nfiles = 0
  635. for (dirpath, dirnames, filenames) in os.walk(testdb):
  636. nfiles += len(filenames)
  637. assert(nfiles > 500)
  638. # Make sure we can restart the server with a different file
  639. # size and have it still work
  640. server_stop()
  641. server_start()
  642. self.ok("extract /newton/prep --start '2000-01-01' " +
  643. "--end '2022-03-23 10:04:01'")
  644. lines_(self.captured, 14400)
  645. # Now recreate the data one more time and make sure there are
  646. # fewer files.
  647. self.ok("destroy /newton/prep")
  648. self.fail("destroy /newton/prep") # already destroyed
  649. self.ok("create /newton/prep float32_8")
  650. os.environ['TZ'] = "UTC"
  651. with open("tests/data/prep-20120323T1004-timestamped") as input:
  652. self.ok("insert -s 20120323T1004 -e 20120323T1006 /newton/prep",
  653. input)
  654. nfiles = 0
  655. for (dirpath, dirnames, filenames) in os.walk(testdb):
  656. nfiles += len(filenames)
  657. lt_(nfiles, 50)
  658. self.ok("destroy /newton/prep") # destroy again
  659. def test_14_remove_files(self):
  660. # Test BulkData's ability to remove when data is split into
  661. # multiple files. Should be a fairly comprehensive test of
  662. # remove functionality.
  663. server_stop()
  664. server_start(bulkdata_args = { "file_size" : 920, # 23 rows per file
  665. "files_per_dir" : 3 })
  666. # Insert data. Just for fun, insert out of order
  667. self.ok("create /newton/prep float32_8")
  668. os.environ['TZ'] = "UTC"
  669. self.ok("insert -t --filename --rate 120 /newton/prep "
  670. "tests/data/prep-20120323T1002")
  671. self.ok("insert -t --filename --rate 120 /newton/prep "
  672. "tests/data/prep-20120323T1000")
  673. # Should take up about 2.8 MB here (including directory entries)
  674. du_before = nilmdb.utils.diskusage.du(testdb)
  675. # Make sure we have the data we expect
  676. self.ok("list --detail")
  677. self.match("/newton/prep float32_8\n" +
  678. " [ Fri, 23 Mar 2012 10:00:00.000000 +0000"
  679. " -> Fri, 23 Mar 2012 10:01:59.991668 +0000 ]\n"
  680. " [ Fri, 23 Mar 2012 10:02:00.000000 +0000"
  681. " -> Fri, 23 Mar 2012 10:03:59.991668 +0000 ]\n")
  682. # Remove various chunks of prep data and make sure
  683. # they're gone.
  684. self.ok("extract -c /newton/prep --start 2000-01-01 --end 2020-01-01")
  685. self.match("28800\n")
  686. self.ok("remove -c /newton/prep " +
  687. "--start '23 Mar 2012 10:00:30' " +
  688. "--end '23 Mar 2012 10:03:30'")
  689. self.match("21600\n")
  690. self.ok("remove -c /newton/prep " +
  691. "--start '23 Mar 2012 10:00:10' " +
  692. "--end '23 Mar 2012 10:00:20'")
  693. self.match("1200\n")
  694. self.ok("remove -c /newton/prep " +
  695. "--start '23 Mar 2012 10:00:05' " +
  696. "--end '23 Mar 2012 10:00:25'")
  697. self.match("1200\n")
  698. self.ok("remove -c /newton/prep " +
  699. "--start '23 Mar 2012 10:03:50' " +
  700. "--end '23 Mar 2012 10:06:50'")
  701. self.match("1200\n")
  702. self.ok("extract -c /newton/prep --start 2000-01-01 --end 2020-01-01")
  703. self.match("3600\n")
  704. # See the missing chunks in list output
  705. self.ok("list --detail")
  706. self.match("/newton/prep float32_8\n" +
  707. " [ Fri, 23 Mar 2012 10:00:00.000000 +0000"
  708. " -> Fri, 23 Mar 2012 10:00:05.000000 +0000 ]\n"
  709. " [ Fri, 23 Mar 2012 10:00:25.000000 +0000"
  710. " -> Fri, 23 Mar 2012 10:00:30.000000 +0000 ]\n"
  711. " [ Fri, 23 Mar 2012 10:03:30.000000 +0000"
  712. " -> Fri, 23 Mar 2012 10:03:50.000000 +0000 ]\n")
  713. # We have 1/8 of the data that we had before, so the file size
  714. # should have dropped below 1/4 of what it used to be
  715. du_after = nilmdb.utils.diskusage.du(testdb)
  716. lt_(du_after, (du_before / 4))
  717. # Remove anything that came from the 10:02 data file
  718. self.ok("remove /newton/prep " +
  719. "--start '23 Mar 2012 10:02:00' --end '2020-01-01'")
  720. # Re-insert 19 lines from that file, then remove them again.
  721. # With the specific file_size above, this will cause the last
  722. # file in the bulk data storage to be exactly file_size large,
  723. # so removing the data should also remove that last file.
  724. self.ok("insert --timestamp -f --rate 120 /newton/prep " +
  725. "tests/data/prep-20120323T1002-first19lines")
  726. self.ok("remove /newton/prep " +
  727. "--start '23 Mar 2012 10:02:00' --end '2020-01-01'")
  728. # Shut down and restart server, to force nrows to get refreshed.
  729. server_stop()
  730. server_start()
  731. # Re-add the full 10:02 data file. This tests adding new data once
  732. # we removed data near the end.
  733. self.ok("insert -t -f -r 120 /newton/prep "
  734. "tests/data/prep-20120323T1002")
  735. # See if we can extract it all
  736. self.ok("extract /newton/prep --start 2000-01-01 --end 2020-01-01")
  737. lines_(self.captured, 15600)