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.
 
 
 

809 lines
29 KiB

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