Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Increased Test Coverage #23

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ cache:

env:
- TOXENV=py27-tornado2
- TOXENV=py27-tornado3
- TOXENV=py27-tornado4
- TOXENV=docs

before_install:
Expand Down
5 changes: 2 additions & 3 deletions sickmuse/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop, PeriodicCallback
from tornado.process import task_id
from tornado.options import define, parse_command_line, options
from tornado.web import Application, url

Expand Down Expand Up @@ -40,7 +39,7 @@ def __init__(self, **kwargs):
super(APIApplication, self).__init__(handlers, **settings)
rrd_directory = os.path.abspath(self.settings['rrd_directory'])
# From base directory: host/plugin/instance.rrd
self.plugin_info = {} # Host --> Plugins --> Instances
self.plugin_info = {} # Host --> Plugins --> Instances
for name in glob.glob(u"%s/*/*/*.rrd" % rrd_directory):
name = name.replace(u"%s/" % rrd_directory, '')
host, plugin = os.path.split(os.path.dirname(name))
Expand Down Expand Up @@ -113,5 +112,5 @@ def main():
IOLoop.instance().start()


if __name__ == "__main__":
if __name__ == "__main__": # pragma: no cover
main()
18 changes: 10 additions & 8 deletions sickmuse/handlers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import glob
import os

import rrdtool
Expand Down Expand Up @@ -50,13 +49,13 @@ def get_template_namespace(self):


class RootHandler(TemplateHandler):

def get(self):
self.render("index.html")


class HostHandler(TemplateHandler):

def get(self, host_name):
if host_name not in self.application.plugin_info:
raise HTTPError(404, 'Host not found')
Expand All @@ -69,7 +68,7 @@ def get(self, host_name):


class MetricAPIHandler(RequestHandler):

def get(self, host, metric):
if host not in self.application.plugin_info:
raise HTTPError(404, 'Host not found')
Expand All @@ -92,16 +91,19 @@ def get(self, host, metric):
))
start = str(date_range['start'])
res = str(date_range['resolution'])
period, metrics, data = rrdtool.fetch(load_file, 'AVERAGE', '--start', start, '--resolution', res)
period, metrics, data = rrdtool.fetch(
load_file, 'AVERAGE', '--start', start, '--resolution', res)
start, end, resolution = period
default = {'start': start, 'end': end, 'resolution': resolution, 'timeline': []}
default = {'start': start, 'end': end, 'resolution': resolution}
if len(metrics) == 1:
key = instance
instance_data[key] = default
instance_data[key] = default.copy()
instance_data[key]['timeline'] = []
else:
for name in metrics:
key = '%s-%s' % (instance, name)
instance_data[key] = default
instance_data[key] = default.copy()
instance_data[key]['timeline'] = []
for item in data:
for i, name in enumerate(metrics):
if len(metrics) == 1:
Expand Down
5 changes: 5 additions & 0 deletions sickmuse/tests/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
import shutil
import tempfile

try:
from unittest.mock import patch, Mock
except ImportError:
from mock import patch, Mock # noqa

from ..app import APIApplication


Expand Down
20 changes: 19 additions & 1 deletion sickmuse/tests/test_app.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import os
import unittest

from .base import ApplicationMixin
from ..app import shutdown
from .base import ApplicationMixin, Mock


class ApplicationTest(ApplicationMixin, unittest.TestCase):
Expand Down Expand Up @@ -77,3 +78,20 @@ def test_non_rrd_file(self):
self.assertTrue(test_plugin in plugins)
self.assertTrue(test_instance in plugins[test_plugin])
self.assertFalse(other_instance in plugins[test_plugin])


class ShutdownTestCase(unittest.TestCase):
"""Testing application shutdown."""

def test_graceful_shutdown(self):
"""Graceful shutdown is the default."""
server = Mock()
shutdown(server)
server.stop.assert_called_once_with()

def test_forceful_shutdown(self):
"""Immediately shutdown the server."""
server = Mock()
with self.assertRaises(SystemExit):
shutdown(server, graceful=False)
server.stop.assert_called_once_with()
194 changes: 192 additions & 2 deletions sickmuse/tests/test_handlers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import json
import os

from tornado.testing import LogTrapTestCase, AsyncHTTPTestCase

from .base import ApplicationMixin
from .base import ApplicationMixin, patch


class BaseHandlerTest(ApplicationMixin, LogTrapTestCase, AsyncHTTPTestCase):
Expand All @@ -19,7 +22,7 @@ def test_render(self):
class HostHandlerTest(BaseHandlerTest):

def get_plugins(self):
return {'test-host': {'plugins': {'foo': 'bar'}}}
return {'test-host': {'plugins': {'foo': ['bar', ]}}}

def test_valid_hostname(self):
"Render valid host info"
Expand All @@ -33,3 +36,190 @@ def test_invalid_hostname(self):
self.http_client.fetch(self.get_url('/host/invalid-host'), self.stop)
response = self.wait()
self.assertEqual(response.code, 404)


class MetricHandlerTest(BaseHandlerTest):

def get_plugins(self):
return {
'test-host': {
'plugins': {
'foo': ['bar', ],
'xxx': ['yyy', 'zzz', ]
}
}
}

def test_get_metrics(self):
"""Get metric info for a host/plugin."""
with patch('sickmuse.handlers.rrdtool') as mock_rrdtool:
mock_rrdtool.fetch.return_value = [
# Start, End, Resolution
(1492183000, 1492186640, 70),
# Metrics
('baz', ),
# Data
[(1, ), (2, ), (3, ), ]
]
self.http_client.fetch(self.get_url('/api/test-host/foo'), self.stop)
response = self.wait()
file_path = os.path.join(self.rrd_directory, 'test-host', 'foo', 'bar.rrd')
mock_rrdtool.fetch.assert_called_once_with(
file_path, 'AVERAGE', '--start', '-1h', '--resolution', '60'
)
self.assertEqual(response.code, 200)
self.assertEqual(response.headers['Content-Type'], 'application/json; charset=UTF-8')
result = json.loads(response.body)
expected = {
'units': None,
'instances': {
'bar': {
'start': 1492183000,
'end': 1492186640,
'resolution': 70,
'timeline': [1, 2, 3]
}
}
}
self.assertEqual(result, expected)

def test_plugin_with_mutliple_instances(self):
"""Get more complex plugin info."""
with patch('sickmuse.handlers.rrdtool') as mock_rrdtool:
mock_rrdtool.fetch.return_value = [
# Start, End, Resolution
(1492183000, 1492186640, 70),
# Metrics
('baz', ),
# Data
[(1, ), (2, ), (3, ), ]
]
self.http_client.fetch(self.get_url('/api/test-host/xxx'), self.stop)
response = self.wait()
file_path = os.path.join(self.rrd_directory, 'test-host', 'xxx', 'yyy.rrd')
mock_rrdtool.fetch.assert_any_call(
file_path, 'AVERAGE', '--start', '-1h', '--resolution', '60'
)
file_path = os.path.join(self.rrd_directory, 'test-host', 'xxx', 'zzz.rrd')
mock_rrdtool.fetch.assert_any_call(
file_path, 'AVERAGE', '--start', '-1h', '--resolution', '60'
)
self.assertEqual(response.code, 200)
self.assertEqual(response.headers['Content-Type'], 'application/json; charset=UTF-8')
result = json.loads(response.body)
expected = {
'units': None,
'instances': {
'yyy': {
'start': 1492183000,
'end': 1492186640,
'resolution': 70,
'timeline': [1, 2, 3]
},
'zzz': {
'start': 1492183000,
'end': 1492186640,
'resolution': 70,
'timeline': [1, 2, 3]
}
}
}
self.assertEqual(result, expected)

def test_plugin_with_multiple_metrics(self):
"""Get more complex metric info."""
with patch('sickmuse.handlers.rrdtool') as mock_rrdtool:
mock_rrdtool.fetch.return_value = [
# Start, End, Resolution
(1492183000, 1492186640, 70),
# Metrics
('blip', 'blah', ),
# Data
[(1, 4, ), (2, 5, ), (3, 6), ]
]
self.http_client.fetch(self.get_url('/api/test-host/foo'), self.stop)
response = self.wait()
file_path = os.path.join(self.rrd_directory, 'test-host', 'foo', 'bar.rrd')
mock_rrdtool.fetch.assert_called_once_with(
file_path, 'AVERAGE', '--start', '-1h', '--resolution', '60'
)
self.assertEqual(response.code, 200)
self.assertEqual(response.headers['Content-Type'], 'application/json; charset=UTF-8')
result = json.loads(response.body)
expected = {
'units': None,
'instances': {
'bar-blip': {
'start': 1492183000,
'end': 1492186640,
'resolution': 70,
'timeline': [1, 2, 3]
},
'bar-blah': {
'start': 1492183000,
'end': 1492186640,
'resolution': 70,
'timeline': [4, 5, 6]
}
}
}
self.assertEqual(result, expected)

def test_get_time_range(self):
"""Optionally change the time range for the metric data."""
with patch('sickmuse.handlers.rrdtool') as mock_rrdtool:
mock_rrdtool.fetch.return_value = [
# Start, End, Resolution
(1492183000, 1492186640, 70),
# Metrics
('baz', ),
# Data
[(1, ), (2, ), (3, ), ]
]
tests = (
# Parameter, (Start, Resolution)
('1hr', ('-1h', '60')),
('3hr', ('-3h', '3600')),
('6hr', ('-6h', '3600')),
('12hr', ('-12h', '3600')),
('24hr', ('-1d', '86400')),
('1week', ('-1w', '604800')),
('1mon', ('-1mon', '2678400')),
('3mon', ('-3mon', '2678400')),
('6mon', ('-6mon', '2678400')),
('1year', ('-1y', '31622400')),
)
for param, (start, resolution) in tests:
url = self.get_url('/api/test-host/foo') + '?range=' + param
self.http_client.fetch(url, self.stop)
self.wait()
file_path = os.path.join(self.rrd_directory, 'test-host', 'foo', 'bar.rrd')
mock_rrdtool.fetch.assert_called_once_with(
file_path, 'AVERAGE', '--start', start, '--resolution', resolution
)
mock_rrdtool.fetch.reset_mock()

def test_invalid_host(self):
"""Try to fetch metrics for an invalid host."""
with patch('sickmuse.handlers.rrdtool') as mock_rrdtool:
self.http_client.fetch(self.get_url('/api/missing-host/foo'), self.stop)
response = self.wait()
self.assertFalse(mock_rrdtool.fetch.called)
self.assertEqual(response.code, 404)

def test_invalid_plugin(self):
"""Try to fetch metrics for an invalid plugin."""
with patch('sickmuse.handlers.rrdtool') as mock_rrdtool:
self.http_client.fetch(self.get_url('/api/test-host/missing'), self.stop)
response = self.wait()
self.assertFalse(mock_rrdtool.fetch.called)
self.assertEqual(response.code, 404)

def test_invalid_range(self):
"""Handle invalid range parameters."""
with patch('sickmuse.handlers.rrdtool') as mock_rrdtool:
url = self.get_url('/api/test-host/foo') + '?range=6b'
self.http_client.fetch(url, self.stop)
response = self.wait()
self.assertFalse(mock_rrdtool.fetch.called)
self.assertEqual(response.code, 400)
7 changes: 5 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
[tox]
envlist = py{27}-tornado{2},docs
envlist = py{27}-tornado{2,3,4},docs

[testenv]
basepython =
py27: python2.7
commands = coverage run setup.py test
commands = coverage run -m unittest discover
deps =
coverage>=4.3,<4.4
py27: mock>=2.0,<2.1
tornado2: tornado>=2.4,<3.0
tornado3: tornado>=3.0,<4.0
tornado4: tornado>=4.0,<5.0
passenv =
CI
TRAVIS
Expand Down