From f943e47f7064b29878f4d4addf6a9d14abd09f88 Mon Sep 17 00:00:00 2001 From: Major Hayden Date: Tue, 7 Mar 2017 16:34:14 -0600 Subject: [PATCH] Improve test coverage (#4) --- ChangeLog | 3 + monitorstack/cli.py | 52 ++---------- monitorstack/common/__init__.py | 0 monitorstack/common/formatters.py | 67 +++++++++++++++ monitorstack/plugins/kvm.py | 2 +- monitorstack/plugins/uptime.py | 4 +- tests/test_cli.py | 85 +++++++++++++++++++ tests/test_formatters.py | 63 ++++++++++++++ tests/{test_kvm.py => test_plugin_kvm.py} | 3 +- .../{test_uptime.py => test_plugin_uptime.py} | 3 +- 10 files changed, 230 insertions(+), 52 deletions(-) create mode 100644 monitorstack/common/__init__.py create mode 100644 monitorstack/common/formatters.py create mode 100644 tests/test_cli.py create mode 100644 tests/test_formatters.py rename tests/{test_kvm.py => test_plugin_kvm.py} (98%) rename tests/{test_uptime.py => test_plugin_uptime.py} (97%) diff --git a/ChangeLog b/ChangeLog index c244aa2..53e9b32 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,9 @@ CHANGES ======= +* Improve test coverage +* Remove sys from uptime test (#3) +* Increase test coverage for uptime * Added KVM Metric plugin (#2) * Couple of updates: telegraf line protocol, dynamic imports, metadata (#1) * Proof of concept diff --git a/monitorstack/cli.py b/monitorstack/cli.py index 4caac9c..ef0e4f8 100755 --- a/monitorstack/cli.py +++ b/monitorstack/cli.py @@ -14,11 +14,9 @@ # limitations under the License. """Handle all shell commands/arguments/options.""" import importlib -import json import os import pkgutil import sys -import time import click @@ -26,12 +24,6 @@ import click context_settings = dict(auto_envvar_prefix='MonitorStack') -def current_time(): - """Return the current time in nanoseconds""" - - return int(time.time() * 1000000000) - - class Context(object): """Set up a context object that we can pass.""" @@ -42,8 +34,6 @@ class Context(object): def log(self, msg, *args): """Log a message to stderr.""" - if args: - msg %= args click.echo(msg, file=sys.stderr) def vlog(self, msg, *args): @@ -60,6 +50,7 @@ class MonitorStackCLI(click.MultiCommand): @property def cmd_folder(self): + """Get the path to the plugin directory.""" return os.path.abspath( os.path.join( os.path.dirname(__file__), @@ -115,40 +106,13 @@ def cli(ctx, output_format, verbose): @cli.resultcallback(replace=True) def process_result(result, output_format, verbose): """Render the output into the proper format.""" - if output_format == 'json': - click.echo(json.dumps(result, indent=2)) - - elif output_format == 'line': - for key, value in result['variables'].items(): - click.echo("{} {}".format(key, value)) - - elif output_format == 'telegraf': - def line_format(sets, quote=False): - store = list() - for k, v in sets.items(): - k = k.replace(' ', '_') - for v_type in [int, float]: - try: - v = v_type(v) - except ValueError: - pass # v was not a int, float, or long - else: - break - if not isinstance(v, (int, float, bool)) and quote: - store.append('{}="{}"'.format(k, v)) - else: - store.append('{}={}'.format(k, v)) - return ','.join(store).rstrip(',') - - resultant = [result['measurement_name']] - if 'meta' in result: - resultant.append(line_format(sets=result['meta'])) - resultant.append(line_format(sets=result['variables'], quote=True)) - resultant.append(current_time()) - click.echo(' '.join(resultant)) - - elif output_format == 'csv': - pass + module_name = 'monitorstack.common.formatters' + method_name = 'write_{}'.format(output_format) + output_formatter = getattr( + importlib.import_module(module_name), + method_name + ) + output_formatter(result) if __name__ == '__main__': diff --git a/monitorstack/common/__init__.py b/monitorstack/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/monitorstack/common/formatters.py b/monitorstack/common/formatters.py new file mode 100644 index 0000000..f4ca3b7 --- /dev/null +++ b/monitorstack/common/formatters.py @@ -0,0 +1,67 @@ +# Copyright 2017, Major Hayden +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Output methods.""" +import json +import time + +import click + + +def current_time(): + """Return the current time in nanoseconds.""" + return int(time.time() * 1000000000) + + +def write_json(result): + """Output in raw JSON format.""" + output = json.dumps(result, indent=2) + click.echo(output) + return True + + +def write_line(result): + """Output in line format.""" + for key, value in result['variables'].items(): + click.echo("{} {}".format(key, value)) + + return True + + +def write_telegraf(result): + """Output in telegraf format.""" + def line_format(sets, quote=False): + store = list() + for k, v in sets.items(): + k = k.replace(' ', '_') + for v_type in [int, float]: + try: + v = v_type(v) + except ValueError: + pass # v was not a int, float, or long + else: + break + if not isinstance(v, (int, float, bool)) and quote: + store.append('{}="{}"'.format(k, v)) + else: + store.append('{}={}'.format(k, v)) + return ','.join(store).rstrip(',') + + resultant = [result['measurement_name']] + if 'meta' in result: + resultant.append(line_format(sets=result['meta'])) + resultant.append(line_format(sets=result['variables'], quote=True)) + resultant.append(str(current_time())) + click.echo(' '.join(resultant)) + + return True diff --git a/monitorstack/plugins/kvm.py b/monitorstack/plugins/kvm.py index 92c5cb0..93d6171 100644 --- a/monitorstack/plugins/kvm.py +++ b/monitorstack/plugins/kvm.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # Copyright 2017, Kevin Carter # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,6 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +"""Plugin for KVM metrics.""" import platform import socket diff --git a/monitorstack/plugins/uptime.py b/monitorstack/plugins/uptime.py index 3686537..4cc94a3 100644 --- a/monitorstack/plugins/uptime.py +++ b/monitorstack/plugins/uptime.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # Copyright 2017, Major Hayden # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,8 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - -"""Base monitoring class.""" +"""Get system uptime.""" import platform diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..c5e6cfd --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,85 @@ +# Copyright 2017, Major Hayden +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Tests for the base cli module.""" +import click + +from monitorstack.cli import Context +from monitorstack.cli import MonitorStackCLI + +import pytest + + +class TestCLI(object): + """Tests for the base cli module.""" + + def test_context_log(self, monkeypatch): + """Test log() method of Context class.""" + def echofixer(msg, file): + return msg + monkeypatch.setattr(click, 'echo', echofixer) + + context = Context() + result = context.log("TEST", 'test') + assert callable(context.log) + assert result is None + + def test_context_vlog_verbose_disabled(self, monkeypatch): + """Test vlog() method of Context class.""" + def echofixer(msg, file): + return msg + monkeypatch.setattr(click, 'echo', echofixer) + + context = Context() + context.verbose = False + result = context.vlog("TEST", 'test') + assert callable(context.vlog) + assert result is None + + def test_context_vlog_verbose_enabled(self, monkeypatch): + """Test vlog() method of Context class.""" + def echofixer(msg, file): + return msg + monkeypatch.setattr(click, 'echo', echofixer) + + context = Context() + context.verbose = True + result = context.vlog("TEST", 'test') + assert callable(context.vlog) + assert result is None + + def test_get_command_invalid(self): + """Test MonitorStackCLI.get_command().""" + ctx = Context() + cli = MonitorStackCLI() + with pytest.raises(SystemExit) as excinfo: + cli.get_command(ctx, 'silly_rabbit_trix_are_for_kids') + assert 'silly_rabbit_trix_are_for_kids' in str(excinfo.value) + assert 'Not Found' in str(excinfo.value) + + def test_get_command_valid(self): + """Test MonitorStackCLI.get_command().""" + ctx = Context() + cli = MonitorStackCLI() + result = cli.get_command(ctx, 'uptime') + assert isinstance(result, object) + assert callable(result) + + def test_list_commands(self): + """Test MonitorStackCLI.list_commands().""" + ctx = Context() + cli = MonitorStackCLI() + result = cli.list_commands(ctx) + assert isinstance(result, list) + assert len(result) > 1 + assert 'uptime' in result diff --git a/tests/test_formatters.py b/tests/test_formatters.py new file mode 100644 index 0000000..5fb2c41 --- /dev/null +++ b/tests/test_formatters.py @@ -0,0 +1,63 @@ +# Copyright 2017, Major Hayden +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Tests the output methods.""" +import json +import platform + +from monitorstack.common import formatters + +SAMPLE_RESULT = { + 'exit_code': 0, + 'message': 'uptime is ok', + 'measurement_name': 'system_uptime', + 'meta': { + 'platform': platform.platform(), + }, + 'variables': { + 'uptime': '29587.75' + } +} + + +class TestFormatters(object): + """Tests for the base cli module.""" + + def test_current_time(self): + """Test current_time().""" + result = formatters.current_time() + assert isinstance(result, int) + assert result > 0 + + def test_write_json(self, capsys): + """Test write_json() module.""" + formatters.write_json(SAMPLE_RESULT) + out, err = capsys.readouterr() + result_json = json.loads(out) + assert isinstance(result_json, dict) + assert result_json['measurement_name'] == \ + SAMPLE_RESULT['measurement_name'] + + def test_write_line(self, capsys): + """Test write_line() module.""" + formatters.write_line(SAMPLE_RESULT) + out, err = capsys.readouterr() + assert out == "uptime {}\n".format( + SAMPLE_RESULT['variables']['uptime'] + ) + + def test_write_telegraf(self, capsys): + """Test write_telegraf() module.""" + formatters.write_telegraf(SAMPLE_RESULT) + out, err = capsys.readouterr() + assert out.startswith(SAMPLE_RESULT['measurement_name']) diff --git a/tests/test_kvm.py b/tests/test_plugin_kvm.py similarity index 98% rename from tests/test_kvm.py rename to tests/test_plugin_kvm.py index 3bfab83..33e13e4 100644 --- a/tests/test_kvm.py +++ b/tests/test_plugin_kvm.py @@ -11,8 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - -"""Tests for the base class.""" +"""Tests for the KVM plugin.""" import json import sys diff --git a/tests/test_uptime.py b/tests/test_plugin_uptime.py similarity index 97% rename from tests/test_uptime.py rename to tests/test_plugin_uptime.py index 4e418ab..0211a24 100644 --- a/tests/test_uptime.py +++ b/tests/test_plugin_uptime.py @@ -11,8 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - -"""Tests for the base class.""" +"""Tests for the uptime plugin.""" import json