From 2a128fc16049a724f9769758d967901aace84348 Mon Sep 17 00:00:00 2001 From: FranciscoCanas Date: Fri, 7 Nov 2014 14:24:56 -0500 Subject: [PATCH] Adds json schema methods to Option and Config Config schema methods generate a json schemas based on the config instance's options. Option schema methods generate schemas based on the type of Option, its default values, and whether its required or not. Also adds schema unit tests for Config and Options, and adds test_float unit test file. Needed for #632 --- src/freeseer/framework/config/core.py | 29 ++++++- src/freeseer/framework/config/options.py | 14 +++- .../framework/config/options/test_boolean.py | 17 +++- .../framework/config/options/test_choice.py | 30 ++++++- .../framework/config/options/test_float.py | 72 ++++++++++++++++ .../framework/config/options/test_folder.py | 17 +++- .../framework/config/options/test_integer.py | 17 +++- .../framework/config/options/test_string.py | 17 +++- src/freeseer/tests/framework/test_config.py | 82 ++++++++++++++++++- 9 files changed, 287 insertions(+), 8 deletions(-) create mode 100644 src/freeseer/tests/framework/config/options/test_float.py diff --git a/src/freeseer/framework/config/core.py b/src/freeseer/framework/config/core.py index 939853e8..8ccdafff 100644 --- a/src/freeseer/framework/config/core.py +++ b/src/freeseer/framework/config/core.py @@ -3,7 +3,7 @@ # freeseer - vga/presentation capture software # -# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre +# Copyright (C) 2011, 2013, 2014 Free and Open Source Software Learning Centre # http://fosslc.org # # This program is free software: you can redistribute it and/or modify @@ -56,6 +56,13 @@ def presentation(self, value): """Returns a modified version of value that will not itself be persisted.""" return value + def schema(self): + """Returns the json schema for an Option.""" + schema = {'type': self.SCHEMA_TYPE} + if self.default != self.NotSpecified: + schema['default'] = self.default + return schema + # Override these! @abc.abstractmethod @@ -193,6 +200,26 @@ def save(self): else: raise StorageNotSetError() + @classmethod + def schema(cls): + """Returns the json schema for this Config instance.""" + required = [] + + schema = { + 'type': 'object', + 'properties': {}, + } + + for name, instance in cls.options.iteritems(): + schema['properties'][name] = instance.schema() + if instance.is_required(): + required.append(name) + + if required: + schema['required'] = required + + return schema + class ConfigStorage(object): """Defines an interface for loading and storing Config instances.""" diff --git a/src/freeseer/framework/config/options.py b/src/freeseer/framework/config/options.py index e3fb8a32..0dd422ae 100644 --- a/src/freeseer/framework/config/options.py +++ b/src/freeseer/framework/config/options.py @@ -3,7 +3,7 @@ # freeseer - vga/presentation capture software # -# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre +# Copyright (C) 2011, 2013, 2014 Free and Open Source Software Learning Centre # http://fosslc.org # # This program is free software: you can redistribute it and/or modify @@ -30,6 +30,7 @@ class StringOption(Option): """Represents a string value.""" + SCHEMA_TYPE = 'string' def is_valid(self, value): return isinstance(value, str) or hasattr(value, '__str__') @@ -43,6 +44,7 @@ def decode(self, value): class IntegerOption(Option): """Represents an integer number value.""" + SCHEMA_TYPE = 'integer' def is_valid(self, value): return isinstance(value, int) @@ -59,6 +61,7 @@ def decode(self, value): class FloatOption(Option): """Represents a floating point number value.""" + SCHEMA_TYPE = 'number' def is_valid(self, value): return isinstance(value, float) @@ -75,6 +78,7 @@ def decode(self, value): class BooleanOption(Option): """Represents a boolean value.""" + SCHEMA_TYPE = 'boolean' def is_valid(self, value): return isinstance(value, bool) @@ -88,6 +92,7 @@ def decode(self, value): class FolderOption(Option): """Represents the path to a folder.""" + SCHEMA_TYPE = 'string' def __init__(self, default=Option.NotSpecified, auto_create=False): self.auto_create = auto_create @@ -119,6 +124,7 @@ def presentation(self, value): class ChoiceOption(StringOption): """Represents a selection from a pre-defined list of strings.""" + SCHEMA_TYPE = 'enum' def __init__(self, choices, default=Option.NotSpecified): self.choices = choices @@ -133,3 +139,9 @@ def decode(self, value): return choice else: raise InvalidDecodeValueError(value) + + def schema(self): + schema = {'enum': self.choices} + if self.default != Option.NotSpecified: + schema['default'] = self.default + return schema diff --git a/src/freeseer/tests/framework/config/options/test_boolean.py b/src/freeseer/tests/framework/config/options/test_boolean.py index 30b2881c..e5028f87 100644 --- a/src/freeseer/tests/framework/config/options/test_boolean.py +++ b/src/freeseer/tests/framework/config/options/test_boolean.py @@ -3,7 +3,7 @@ # freeseer - vga/presentation capture software # -# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre +# Copyright (C) 2011, 2013, 2014 Free and Open Source Software Learning Centre # http://fosslc.org # # This program is free software: you can redistribute it and/or modify @@ -24,6 +24,9 @@ import unittest +from jsonschema import validate +from jsonschema import ValidationError + from freeseer.framework.config.options import BooleanOption from freeseer.tests.framework.config.options import OptionTest @@ -53,6 +56,12 @@ class TestBooleanOptionNoDefault(unittest.TestCase, OptionTest): def setUp(self): self.option = BooleanOption() + def test_schema(self): + """Tests BooleanOption schema method.""" + self.assertRaises(ValidationError, validate, 4, self.option.schema()) + self.assertIsNone(validate(True, self.option.schema())) + self.assertDictEqual(self.option.schema(), {'type': 'boolean'}) + class TestBooleanOptionWithDefault(TestBooleanOptionNoDefault): """Test BooleanOption with a default value.""" @@ -63,3 +72,9 @@ def setUp(self): def test_default(self): """Tests that the default was set correctly.""" self.assertEqual(self.option.default, False) + + def test_schema(self): + """Tests BooleanOption schema method.""" + self.assertRaises(ValidationError, validate, 4, self.option.schema()) + self.assertIsNone(validate(True, self.option.schema())) + self.assertDictEqual(self.option.schema(), {'default': False, 'type': 'boolean'}) diff --git a/src/freeseer/tests/framework/config/options/test_choice.py b/src/freeseer/tests/framework/config/options/test_choice.py index 1ed3abe8..5b26650a 100644 --- a/src/freeseer/tests/framework/config/options/test_choice.py +++ b/src/freeseer/tests/framework/config/options/test_choice.py @@ -3,7 +3,7 @@ # freeseer - vga/presentation capture software # -# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre +# Copyright (C) 2011, 2013, 2014 Free and Open Source Software Learning Centre # http://fosslc.org # # This program is free software: you can redistribute it and/or modify @@ -24,6 +24,9 @@ import unittest +from jsonschema import validate +from jsonschema import ValidationError + from freeseer.framework.config.options import ChoiceOption from freeseer.tests.framework.config.options import OptionTest @@ -52,6 +55,18 @@ def setUp(self): 'world', ]) + def test_schema(self): + """Tests a ChoiceOption schema method.""" + expected = { + 'enum': [ + 'hello', + 'world', + ], + } + self.assertRaises(ValidationError, validate, 'error', self.option.schema()) + self.assertIsNone(validate('world', self.option.schema())) + self.assertDictEqual(self.option.schema(), expected) + class TestChoiceOptionWithDefault(TestChoiceOptionNoDefault): """Tests ChoiceOption with a default value.""" @@ -65,3 +80,16 @@ def setUp(self): def test_default(self): """Tests that the default was set correctly.""" self.assertEqual(self.option.default, 'hello') + + def test_schema(self): + """Tests a ChoiceOption schema method.""" + expected = { + 'default': 'hello', + 'enum': [ + 'hello', + 'world', + ], + } + self.assertRaises(ValidationError, validate, 'error', self.option.schema()) + self.assertIsNone(validate('world', self.option.schema())) + self.assertDictEqual(self.option.schema(), expected) diff --git a/src/freeseer/tests/framework/config/options/test_float.py b/src/freeseer/tests/framework/config/options/test_float.py new file mode 100644 index 00000000..54ba660f --- /dev/null +++ b/src/freeseer/tests/framework/config/options/test_float.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# freeseer - vga/presentation capture software +# +# Copyright (C) 2014 Free and Open Source Software Learning Centre +# http://fosslc.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# For support, questions, suggestions or any other inquiries, visit: +# http://wiki.github.com/Freeseer/freeseer/ + +import unittest + +from jsonschema import validate +from jsonschema import ValidationError + +from freeseer.framework.config.options import FloatOption +from freeseer.tests.framework.config.options import OptionTest + + +class TestFloatOptionNoDefault(unittest.TestCase, OptionTest): + """Tests FloatOption without a default value.""" + + valid_success = [x / 10.0 for x in xrange(-100, 100)] + + encode_success = zip(valid_success, map(str, valid_success)) + + decode_success = zip(map(str, valid_success), valid_success) + decode_failure = [ + 'hello', + '1world', + 'test2', + ] + + def setUp(self): + self.option = FloatOption() + + def test_schema(self): + """Tests FloatOption schema method.""" + self.assertRaises(ValidationError, validate, 'error', self.option.schema()) + self.assertIsNone(validate(5.5, self.option.schema())) + self.assertDictEqual(self.option.schema(), {'type': 'number'}) + + +class TestFloatOptionWithDefault(TestFloatOptionNoDefault): + """Tests FloatOption with a default value.""" + + def setUp(self): + self.option = FloatOption(1234.5) + + def test_default(self): + """Tests that the default was set correctly.""" + self.assertEqual(self.option.default, 1234.5) + + def test_schema(self): + """Tests FloatOption schema method.""" + self.assertRaises(ValidationError, validate, 'error', self.option.schema()) + self.assertIsNone(validate(5.0, self.option.schema())) + self.assertDictEqual(self.option.schema(), {'default': 1234.5, 'type': 'number'}) diff --git a/src/freeseer/tests/framework/config/options/test_folder.py b/src/freeseer/tests/framework/config/options/test_folder.py index fa445786..7dbc540d 100644 --- a/src/freeseer/tests/framework/config/options/test_folder.py +++ b/src/freeseer/tests/framework/config/options/test_folder.py @@ -3,7 +3,7 @@ # freeseer - vga/presentation capture software # -# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre +# Copyright (C) 2011, 2013, 2014 Free and Open Source Software Learning Centre # http://fosslc.org # # This program is free software: you can redistribute it and/or modify @@ -27,6 +27,9 @@ import tempfile import unittest +from jsonschema import validate +from jsonschema import ValidationError + from freeseer.framework.config.options import FolderOption from freeseer.tests.framework.config.options import OptionTest @@ -57,6 +60,12 @@ def test_presentation(self): self.assertEqual(presentation_value, path) self.assertFalse(os.path.exists(presentation_value)) + def test_schema(self): + """Tests StringOption schema method.""" + self.assertRaises(ValidationError, validate, 1, self.option.schema()) + self.assertIsNone(validate('/tmp2', self.option.schema())) + self.assertDictEqual(self.option.schema(), {'type': 'string'}) + class TestFolderOptionAutoCreate(TestFolderOptionNoDefault): """Tests FolderOption without a default value, and with auto_create turned on.""" @@ -88,3 +97,9 @@ def setUp(self): def test_default(self): """Tests that the default was set correctly.""" self.assertEqual(self.option.default, '/tmp') + + def test_schema(self): + """Tests StringOption schema method.""" + self.assertRaises(ValidationError, validate, 1, self.option.schema()) + self.assertIsNone(validate('/tmp2', self.option.schema())) + self.assertDictEqual(self.option.schema(), {'default': '/tmp', 'type': 'string'}) diff --git a/src/freeseer/tests/framework/config/options/test_integer.py b/src/freeseer/tests/framework/config/options/test_integer.py index 81a2db37..237efd33 100644 --- a/src/freeseer/tests/framework/config/options/test_integer.py +++ b/src/freeseer/tests/framework/config/options/test_integer.py @@ -3,7 +3,7 @@ # freeseer - vga/presentation capture software # -# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre +# Copyright (C) 2011, 2013, 2014 Free and Open Source Software Learning Centre # http://fosslc.org # # This program is free software: you can redistribute it and/or modify @@ -24,6 +24,9 @@ import unittest +from jsonschema import validate +from jsonschema import ValidationError + from freeseer.framework.config.options import IntegerOption from freeseer.tests.framework.config.options import OptionTest @@ -45,6 +48,12 @@ class TestIntegerOptionNoDefault(unittest.TestCase, OptionTest): def setUp(self): self.option = IntegerOption() + def test_schema(self): + """Tests IntegerOption schema method.""" + self.assertRaises(ValidationError, validate, 1.0, self.option.schema()) + self.assertIsNone(validate(5, self.option.schema())) + self.assertDictEqual(self.option.schema(), {'type': 'integer'}) + class TestIntegerOptionWithDefault(TestIntegerOptionNoDefault): """Tests IntegerOption with a default value.""" @@ -55,3 +64,9 @@ def setUp(self): def test_default(self): """Tests that the default was set correctly.""" self.assertEqual(self.option.default, 1234) + + def test_schema(self): + """Tests IntegerOption schema method.""" + self.assertRaises(ValidationError, validate, 1.0, self.option.schema()) + self.assertIsNone(validate(5, self.option.schema())) + self.assertDictEqual(self.option.schema(), {'default': 1234, 'type': 'integer'}) diff --git a/src/freeseer/tests/framework/config/options/test_string.py b/src/freeseer/tests/framework/config/options/test_string.py index 1b341dd6..4ef84424 100644 --- a/src/freeseer/tests/framework/config/options/test_string.py +++ b/src/freeseer/tests/framework/config/options/test_string.py @@ -3,7 +3,7 @@ # freeseer - vga/presentation capture software # -# Copyright (C) 2011, 2013 Free and Open Source Software Learning Centre +# Copyright (C) 2011, 2013, 2014 Free and Open Source Software Learning Centre # http://fosslc.org # # This program is free software: you can redistribute it and/or modify @@ -24,6 +24,9 @@ import unittest +from jsonschema import validate +from jsonschema import ValidationError + from freeseer.framework.config.options import StringOption from freeseer.tests.framework.config.options import OptionTest @@ -50,6 +53,12 @@ class TestStringOptionNoDefault(unittest.TestCase, OptionTest): def setUp(self): self.option = StringOption() + def test_schema(self): + """Tests StringOption schema method.""" + self.assertRaises(ValidationError, validate, 1, self.option.schema()) + self.assertIsNone(validate('string_value', self.option.schema())) + self.assertDictEqual(self.option.schema(), {'type': 'string'}) + class TestStringOptionWithDefault(TestStringOptionNoDefault): """Tests StringOption with a default value.""" @@ -60,3 +69,9 @@ def setUp(self): def test_default(self): """Tests that the default was set correctly.""" self.assertEqual(self.option.default, 'testing') + + def test_schema(self): + """Tests StringOption schema method.""" + self.assertRaises(ValidationError, validate, 1, self.option.schema()) + self.assertIsNone(validate('string_value', self.option.schema())) + self.assertDictEqual(self.option.schema(), {'default': 'testing', 'type': 'string'}) diff --git a/src/freeseer/tests/framework/test_config.py b/src/freeseer/tests/framework/test_config.py index 2bb2d167..eb82af9c 100644 --- a/src/freeseer/tests/framework/test_config.py +++ b/src/freeseer/tests/framework/test_config.py @@ -3,7 +3,7 @@ # freeseer - vga/presentation capture software # -# Copyright (C) 2013 Free and Open Source Software Learning Centre +# Copyright (C) 2013, 2014 Free and Open Source Software Learning Centre # http://fosslc.org # # This program is free software: you can redistribute it and/or modify @@ -27,6 +27,9 @@ import tempfile import unittest +from jsonschema import validate +from jsonschema import ValidationError + from freeseer.framework.config.profile import ProfileManager from freeseer import settings @@ -60,3 +63,80 @@ def test_save(self): filepath = self.profile.get_filepath('freeseer.conf') self.config.save() self.assertTrue(os.path.exists(filepath)) + + def test_schema(self): + """Tests that the settings Config returns the correct schema based on all its options.""" + settings_schema = { + 'type': 'object', + 'properties': { + 'videodir': { + 'default': '~/Videos', + 'type': 'string', + }, + 'auto_hide': { + 'default': False, + 'type': 'boolean', + }, + 'enable_audio_recording': { + 'default': True, + 'type': 'boolean', + }, + 'enable_video_recording': { + 'default': True, + 'type': 'boolean', + }, + 'videomixer': { + 'default': 'Video Passthrough', + 'type': 'string', + }, + 'audiomixer': { + 'default': 'Audio Passthrough', + 'type': 'string', + }, + 'record_to_file': { + 'default': True, + 'type': 'boolean', + }, + 'record_to_file_plugin': { + 'default': 'Ogg Output', + 'type': 'string', + }, + 'record_to_stream': { + 'default': False, + 'type': 'boolean', + }, + 'record_to_stream_plugin': { + 'default': 'RTMP Streaming', + 'type': 'string', + }, + 'audio_feedback': { + 'default': False, + 'type': 'boolean', + }, + 'video_preview': { + 'default': True, + 'type': 'boolean', + }, + 'default_language': { + 'default': 'tr_en_US.qm', + 'type': 'string', + }, + }, + } + self.assertDictEqual(self.config.schema(), settings_schema) + + def test_schema_validate(self): + """Tests that schemas validate valid configs.""" + config = { + 'default_language': 'tr_en_US.qm', + 'auto_hide': True + } + self.assertIsNone(validate(config, self.config.schema())) + + def test_schema_invalidate(self): + """Tests that schemas invalidate an invalid config.""" + config = { + 'default_language': False, + 'auto_hide': 5 + } + self.assertRaises(ValidationError, validate, config, self.config.schema())