diff --git a/grafana_dashboards/builder.py b/grafana_dashboards/builder.py index a8ef618..9f284c6 100644 --- a/grafana_dashboards/builder.py +++ b/grafana_dashboards/builder.py @@ -45,8 +45,11 @@ class Builder(object): self.grafana = Grafana(CONF.grafana.url, CONF.grafana.apikey) self.parser = YamlParser() - def update_dashboard(self, path): + def load_files(self, path): self.parser.parse(path) + + def update_dashboard(self, path): + self.load_files(path) dashboards = self.parser.data.get('dashboard', {}) for item in dashboards: data, md5 = self.parser.get_dashboard(item) diff --git a/grafana_dashboards/cmd.py b/grafana_dashboards/cmd.py index 65e7da5..a34971b 100644 --- a/grafana_dashboards/cmd.py +++ b/grafana_dashboards/cmd.py @@ -42,6 +42,14 @@ class Commands(object): def update(self, path): self.builder.update_dashboard(path) + def validate(self, path): + try: + self.builder.load_files(path) + print('SUCCESS!') + except Exception as e: + print('%s: ERROR: %s' % (path, e)) + sys.exit(1) + def add_command_parsers(subparsers): parser_update = subparsers.add_parser('update') @@ -49,6 +57,11 @@ def add_command_parsers(subparsers): 'path', help='colon-separated list of paths to YAML files or' ' directories') + parser_validate = subparsers.add_parser('validate') + parser_validate.add_argument( + 'path', help='colon-separated list of paths to YAML files or' + ' directories') + command_opt = cfg.SubCommandOpt('action', handler=add_command_parsers) diff --git a/tests/base.py b/tests/base.py index ed89d4f..f679be7 100644 --- a/tests/base.py +++ b/tests/base.py @@ -16,12 +16,37 @@ # License for the specific language governing permissions and limitations # under the License. +import os +import re + import fixtures import testtools from tests import conf_fixture +def get_scenarios(fixtures_path, in_ext='yaml', out_ext='json'): + scenarios = [] + files = [] + for dirpath, dirs, fs in os.walk(fixtures_path): + files.extend([os.path.join(dirpath, f) for f in fs]) + + input_files = [f for f in files if re.match(r'.*\.{0}$'.format(in_ext), f)] + + for input_filename in input_files: + output_candidate = re.sub( + r'\.{0}$'.format(in_ext), '.{0}'.format(out_ext), input_filename) + if output_candidate not in files: + output_candidate = None + + scenarios.append((input_filename, { + 'in_filename': input_filename, + 'out_filename': output_candidate, + })) + + return scenarios + + class TestCase(testtools.TestCase): """Test case base class for all unit tests.""" diff --git a/tests/cmd/__init__.py b/tests/cmd/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/cmd/base.py b/tests/cmd/base.py new file mode 100644 index 0000000..e33ca3e --- /dev/null +++ b/tests/cmd/base.py @@ -0,0 +1,55 @@ +# Copyright 2015 Red Hat, Inc. +# +# 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. + +import sys + +import fixtures +import six + +from grafana_dashboards import cmd +from tests.base import TestCase + + +class TestCase(TestCase): + + def setUp(self): + super(TestCase, self).setUp() + + def clear(): + cmd.CONF.reset() + cmd.CONF.unregister_opt(cmd.command_opt) + cmd.CONF.reset() + self.addCleanup(clear) + + def shell(self, argstr, exitcodes=(0,)): + orig = sys.stdout + orig_stderr = sys.stderr + try: + sys.stdout = six.StringIO() + sys.stderr = six.StringIO() + argv = ['grafana-dashboards'] + argv += argstr.split() + self.useFixture(fixtures.MonkeyPatch('sys.argv', argv)) + cmd.main() + except SystemExit: + exc_type, exc_value, exc_trackback = sys.exc_info() + self.assertIn(exc_value.code, exitcodes) + finally: + stdout = sys.stdout.getvalue() + sys.stdout.close() + sys.stdout = orig + stderr = sys.stderr.getvalue() + sys.stderr.close() + sys.stderr = orig_stderr + return (stdout, stderr) diff --git a/tests/cmd/test_validate.py b/tests/cmd/test_validate.py new file mode 100644 index 0000000..a15e13a --- /dev/null +++ b/tests/cmd/test_validate.py @@ -0,0 +1,71 @@ +# Copyright 2015 Red Hat, Inc. +# +# 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. + +import os +import re + +from testscenarios.testcase import TestWithScenarios +from testtools import matchers + +from tests.base import get_scenarios +from tests.cmd.base import TestCase + + +class TestCaseValidateScenarios(TestWithScenarios, TestCase): + fixtures_path = os.path.join( + os.path.dirname(__file__), '../fixtures/cmd/validate') + scenarios = get_scenarios(fixtures_path) + + def test_command(self): + if os.path.basename(self.in_filename).startswith('good-'): + self._validate_success() + else: + self._validate_failure() + + def _validate_failure(self): + required = [ + '%s: ERROR:' % self.in_filename, + ] + stdout, stderr = self.shell( + 'validate %s' % self.in_filename, exitcodes=[1]) + for r in required: + self.assertThat( + (stdout + stderr), + matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE)) + + def _validate_success(self): + required = [ + 'SUCCESS!', + ] + stdout, stderr = self.shell( + 'validate %s' % self.in_filename, exitcodes=[0]) + for r in required: + self.assertThat( + (stdout + stderr), + matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE)) + + +class TestCaseValidate(TestCase): + + def test_validate_without_path(self): + required = [ + '.*?^usage: grafana-dashboards validate \[-h\] path', + '.*?^grafana-dashboards validate: error: (too few arguments|the ' + 'following arguments are required: path)', + ] + stdout, stderr = self.shell('validate', exitcodes=[2]) + for r in required: + self.assertThat( + (stdout + stderr), + matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE)) diff --git a/tests/fixtures/cmd/validate/bad-dashboard-0001.yaml b/tests/fixtures/cmd/validate/bad-dashboard-0001.yaml new file mode 100644 index 0000000..34b98af --- /dev/null +++ b/tests/fixtures/cmd/validate/bad-dashboard-0001.yaml @@ -0,0 +1,2 @@ +board: + title: New dashboard diff --git a/tests/fixtures/cmd/validate/good-dashboard-0001.yaml b/tests/fixtures/cmd/validate/good-dashboard-0001.yaml new file mode 100644 index 0000000..2d47eec --- /dev/null +++ b/tests/fixtures/cmd/validate/good-dashboard-0001.yaml @@ -0,0 +1,2 @@ +dashboard: + title: New dashboard diff --git a/tests/schema/base.py b/tests/schema/base.py index c499338..1843796 100644 --- a/tests/schema/base.py +++ b/tests/schema/base.py @@ -17,8 +17,6 @@ # under the License. import json -import os -import re import doctest import testtools @@ -26,28 +24,6 @@ import testtools from grafana_dashboards.parser import YamlParser -def get_scenarios(fixtures_path, in_ext='yaml', out_ext='json'): - scenarios = [] - files = [] - for dirpath, dirs, fs in os.walk(fixtures_path): - files.extend([os.path.join(dirpath, f) for f in fs]) - - input_files = [f for f in files if re.match(r'.*\.{0}$'.format(in_ext), f)] - - for input_filename in input_files: - output_candidate = re.sub( - r'\.{0}$'.format(in_ext), '.{0}'.format(out_ext), input_filename) - if output_candidate not in files: - output_candidate = None - - scenarios.append((input_filename, { - 'in_filename': input_filename, - 'out_filename': output_candidate, - })) - - return scenarios - - class TestCase(object): """Test case base class for all unit tests.""" diff --git a/tests/schema/test_dashboard.py b/tests/schema/test_dashboard.py index af40e14..76c9172 100644 --- a/tests/schema/test_dashboard.py +++ b/tests/schema/test_dashboard.py @@ -17,7 +17,7 @@ import os from testscenarios.testcase import TestWithScenarios from testtools import TestCase -from tests.schema.base import get_scenarios +from tests.base import get_scenarios from tests.schema.base import TestCase as BaseTestCase diff --git a/tests/test_cmd.py b/tests/test_cmd.py index f3d79d3..73b9e32 100644 --- a/tests/test_cmd.py +++ b/tests/test_cmd.py @@ -13,44 +13,14 @@ # under the License. import re -import sys -import fixtures -import six from testtools import matchers -from grafana_dashboards import cmd -from tests.base import TestCase +from tests.cmd.base import TestCase class TestCaseCmd(TestCase): - def setUp(self): - super(TestCaseCmd, self).setUp() - cmd.CONF.reset() - - def shell(self, argstr, exitcodes=(0,)): - orig = sys.stdout - orig_stderr = sys.stderr - try: - sys.stdout = six.StringIO() - sys.stderr = six.StringIO() - argv = ['grafana-dashboards'] - argv += argstr.split() - self.useFixture(fixtures.MonkeyPatch('sys.argv', argv)) - cmd.main() - except SystemExit: - exc_type, exc_value, exc_trackback = sys.exc_info() - self.assertIn(exc_value.code, exitcodes) - finally: - stdout = sys.stdout.getvalue() - sys.stdout.close() - sys.stdout = orig - stderr = sys.stderr.getvalue() - sys.stderr.close() - sys.stderr = orig_stderr - return (stdout, stderr) - def test_update_without_path(self): required = [ '.*?^usage: grafana-dashboards update \[-h\] path',