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())