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.

test_nilmdb.py 9.5 KiB

8 years ago
8 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. import nilmdb.server
  2. from nose.tools import *
  3. from nose.tools import assert_raises
  4. import distutils.version
  5. import simplejson as json
  6. import itertools
  7. import os
  8. import sys
  9. import threading
  10. import urllib.request, urllib.error, urllib.parse
  11. from urllib.request import urlopen
  12. from urllib.error import HTTPError
  13. import io
  14. import time
  15. import requests
  16. from nilmdb.utils import serializer_proxy
  17. testdb = "tests/testdb"
  18. #@atexit.register
  19. #def cleanup():
  20. # os.unlink(testdb)
  21. from testutil.helpers import *
  22. class Test00Nilmdb(object): # named 00 so it runs first
  23. def test_NilmDB(self):
  24. recursive_unlink(testdb)
  25. db = nilmdb.server.NilmDB(testdb)
  26. db.close()
  27. db = nilmdb.server.NilmDB(testdb)
  28. db.close()
  29. db.close()
  30. # test timer, just to get coverage
  31. capture = io.StringIO()
  32. old = sys.stdout
  33. sys.stdout = capture
  34. with nilmdb.utils.Timer("test"):
  35. time.sleep(0.01)
  36. with nilmdb.utils.Timer("test syslog", tosyslog=True):
  37. time.sleep(0.01)
  38. sys.stdout = old
  39. in_("test: ", capture.getvalue())
  40. def test_stream(self):
  41. db = nilmdb.server.NilmDB(testdb)
  42. eq_(db.stream_list(), [])
  43. # Bad path
  44. with assert_raises(ValueError):
  45. db.stream_create("foo/bar/baz", "float32_8")
  46. with assert_raises(ValueError):
  47. db.stream_create("/foo", "float32_8")
  48. # Bad layout type
  49. with assert_raises(ValueError):
  50. db.stream_create("/newton/prep", "NoSuchLayout")
  51. db.stream_create("/newton/prep", "float32_8")
  52. db.stream_create("/newton/raw", "uint16_6")
  53. db.stream_create("/newton/zzz/rawnotch", "uint16_9")
  54. # Verify we got 3 streams
  55. eq_(db.stream_list(), [ ["/newton/prep", "float32_8"],
  56. ["/newton/raw", "uint16_6"],
  57. ["/newton/zzz/rawnotch", "uint16_9"]
  58. ])
  59. # Match just one type or one path
  60. eq_(db.stream_list(layout="uint16_6"), [ ["/newton/raw", "uint16_6"] ])
  61. eq_(db.stream_list(path="/newton/raw"), [ ["/newton/raw", "uint16_6"] ])
  62. # Verify that columns were made right (pytables specific)
  63. if "h5file" in db.data.__dict__:
  64. h5file = db.data.h5file
  65. eq_(len(h5file.getNode("/newton/prep").cols), 9)
  66. eq_(len(h5file.getNode("/newton/raw").cols), 7)
  67. eq_(len(h5file.getNode("/newton/zzz/rawnotch").cols), 10)
  68. assert(not h5file.getNode("/newton/prep").colindexed["timestamp"])
  69. assert(not h5file.getNode("/newton/prep").colindexed["c1"])
  70. # Set / get metadata
  71. eq_(db.stream_get_metadata("/newton/prep"), {})
  72. eq_(db.stream_get_metadata("/newton/raw"), {})
  73. meta1 = { "description": "The Data",
  74. "v_scale": "1.234" }
  75. meta2 = { "description": "The Data" }
  76. meta3 = { "v_scale": "1.234" }
  77. db.stream_set_metadata("/newton/prep", meta1)
  78. db.stream_update_metadata("/newton/prep", {})
  79. db.stream_update_metadata("/newton/raw", meta2)
  80. db.stream_update_metadata("/newton/raw", meta3)
  81. eq_(db.stream_get_metadata("/newton/prep"), meta1)
  82. eq_(db.stream_get_metadata("/newton/raw"), meta1)
  83. # fill in some misc. test coverage
  84. with assert_raises(nilmdb.server.NilmDBError):
  85. db.stream_remove("/newton/prep", 0, 0)
  86. with assert_raises(nilmdb.server.NilmDBError):
  87. db.stream_remove("/newton/prep", 1, 0)
  88. db.stream_remove("/newton/prep", 0, 1)
  89. with assert_raises(nilmdb.server.NilmDBError):
  90. db.stream_extract("/newton/prep", count = True, binary = True)
  91. db.close()
  92. class TestBlockingServer(object):
  93. def setUp(self):
  94. self.db = serializer_proxy(nilmdb.server.NilmDB)(testdb)
  95. def tearDown(self):
  96. self.db.close()
  97. def test_blocking_server(self):
  98. # Server should fail if the database doesn't have a "_thread_safe"
  99. # property.
  100. with assert_raises(KeyError):
  101. nilmdb.server.Server(object())
  102. # Start web app on a custom port
  103. self.server = nilmdb.server.Server(self.db, host = "127.0.0.1",
  104. port = 32180, stoppable = True)
  105. # Run it
  106. event = threading.Event()
  107. def run_server():
  108. self.server.start(blocking = True, event = event)
  109. thread = threading.Thread(target = run_server)
  110. thread.start()
  111. if not event.wait(timeout = 10):
  112. raise AssertionError("server didn't start in 10 seconds")
  113. # Send request to exit.
  114. req = urlopen("http://127.0.0.1:32180/exit/", timeout = 1)
  115. # Wait for it
  116. thread.join()
  117. def geturl(path):
  118. resp = urlopen("http://127.0.0.1:32180" + path, timeout = 10)
  119. body = resp.read()
  120. return body.decode(resp.headers.get_content_charset() or 'utf-8')
  121. def getjson(path):
  122. return json.loads(geturl(path))
  123. class TestServer(object):
  124. def setUp(self):
  125. # Start web app on a custom port
  126. self.db = serializer_proxy(nilmdb.server.NilmDB)(testdb)
  127. self.server = nilmdb.server.Server(self.db, host = "127.0.0.1",
  128. port = 32180, stoppable = False)
  129. self.server.start(blocking = False)
  130. def tearDown(self):
  131. # Close web app
  132. self.server.stop()
  133. self.db.close()
  134. def test_server(self):
  135. # Make sure we can't force an exit, and test other 404 errors
  136. for url in [ "/exit", "/favicon.ico" ]:
  137. with assert_raises(HTTPError) as e:
  138. geturl(url)
  139. eq_(e.exception.code, 404)
  140. # Root page
  141. in_("This is NilmDB", geturl("/"))
  142. # Check version
  143. eq_(distutils.version.LooseVersion(getjson("/version")),
  144. distutils.version.LooseVersion(nilmdb.__version__))
  145. def test_stream_list(self):
  146. # Known streams that got populated by an earlier test (test_nilmdb)
  147. streams = getjson("/stream/list")
  148. eq_(streams, [
  149. ['/newton/prep', 'float32_8'],
  150. ['/newton/raw', 'uint16_6'],
  151. ['/newton/zzz/rawnotch', 'uint16_9'],
  152. ])
  153. streams = getjson("/stream/list?layout=uint16_6")
  154. eq_(streams, [['/newton/raw', 'uint16_6']])
  155. streams = getjson("/stream/list?layout=NoSuchLayout")
  156. eq_(streams, [])
  157. def test_stream_metadata(self):
  158. with assert_raises(HTTPError) as e:
  159. getjson("/stream/get_metadata?path=foo")
  160. eq_(e.exception.code, 404)
  161. data = getjson("/stream/get_metadata?path=/newton/prep")
  162. eq_(data, {'description': 'The Data', 'v_scale': '1.234'})
  163. data = getjson("/stream/get_metadata?path=/newton/prep"
  164. "&key=v_scale")
  165. eq_(data, {'v_scale': '1.234'})
  166. data = getjson("/stream/get_metadata?path=/newton/prep"
  167. "&key=v_scale&key=description")
  168. eq_(data, {'description': 'The Data', 'v_scale': '1.234'})
  169. data = getjson("/stream/get_metadata?path=/newton/prep"
  170. "&key=v_scale&key=foo")
  171. eq_(data, {'foo': None, 'v_scale': '1.234'})
  172. data = getjson("/stream/get_metadata?path=/newton/prep"
  173. "&key=foo")
  174. eq_(data, {'foo': None})
  175. def test_cors_headers(self):
  176. # Test that CORS headers are being set correctly
  177. # Normal GET should send simple response
  178. url = "http://127.0.0.1:32180/stream/list"
  179. r = requests.get(url, headers = { "Origin": "http://google.com/" })
  180. eq_(r.status_code, 200)
  181. if "access-control-allow-origin" not in r.headers:
  182. raise AssertionError("No Access-Control-Allow-Origin (CORS) "
  183. "header in response:\n", r.headers)
  184. eq_(r.headers["access-control-allow-origin"], "http://google.com/")
  185. # OPTIONS without CORS preflight headers should result in 405
  186. r = requests.options(url, headers = {
  187. "Origin": "http://google.com/",
  188. })
  189. eq_(r.status_code, 405)
  190. # OPTIONS with preflight headers should give preflight response
  191. r = requests.options(url, headers = {
  192. "Origin": "http://google.com/",
  193. "Access-Control-Request-Method": "POST",
  194. "Access-Control-Request-Headers": "X-Custom",
  195. })
  196. eq_(r.status_code, 200)
  197. if "access-control-allow-origin" not in r.headers:
  198. raise AssertionError("No Access-Control-Allow-Origin (CORS) "
  199. "header in response:\n", r.headers)
  200. eq_(r.headers["access-control-allow-methods"], "GET, HEAD")
  201. eq_(r.headers["access-control-allow-headers"], "X-Custom")
  202. def test_post_bodies(self):
  203. # Test JSON post bodies
  204. r = requests.post("http://127.0.0.1:32180/stream/set_metadata",
  205. headers = { "Content-Type": "application/json" },
  206. data = '{"hello": 1}')
  207. eq_(r.status_code, 404) # wrong parameters
  208. r = requests.post("http://127.0.0.1:32180/stream/set_metadata",
  209. headers = { "Content-Type": "application/json" },
  210. data = '["hello"]')
  211. eq_(r.status_code, 415) # not a dict
  212. r = requests.post("http://127.0.0.1:32180/stream/set_metadata",
  213. headers = { "Content-Type": "application/json" },
  214. data = '[hello]')
  215. eq_(r.status_code, 400) # badly formatted JSON