Initial work on mechanism, imports, and metadata * Metadata 'template_version' is now version data type * Unicode support for version * Better Unicode support for validation issues
Project: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/commit/087666dd Tree: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/tree/087666dd Diff: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/diff/087666dd Branch: refs/heads/ARIA-1-parser-test-suite Commit: 087666ddd37ac9ad56da0fefe36c797be20287d0 Parents: 126d4e8 Author: Tal Liron <[email protected]> Authored: Thu Aug 17 17:50:27 2017 -0500 Committer: Tal Liron <[email protected]> Committed: Mon Sep 11 13:51:58 2017 -0500 ---------------------------------------------------------------------- aria/modeling/service_common.py | 6 +- aria/parser/validation/issue.py | 4 +- .../simple_v1_0/data_types.py | 63 ++++---- .../aria_extension_tosca/simple_v1_0/misc.py | 9 +- .../simple_v1_0/presentation/field_getters.py | 6 +- tests/extensions/__init__.py | 14 ++ .../extensions/aria_extension_tosca/__init__.py | 14 ++ .../simple_v1_0/__init__.py | 14 ++ .../simple_v1_0/conftest.py | 28 ++++ .../simple_v1_0/test_imports.py | 152 +++++++++++++++++++ .../simple_v1_0/test_metadata.py | 124 +++++++++++++++ tests/mechanisms/__init__.py | 14 ++ tests/mechanisms/parsing/__init__.py | 55 +++++++ tests/mechanisms/parsing/aria.py | 63 ++++++++ tests/mechanisms/web_server.py | 75 +++++++++ tests/requirements.txt | 1 + 16 files changed, 600 insertions(+), 42 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/087666dd/aria/modeling/service_common.py ---------------------------------------------------------------------- diff --git a/aria/modeling/service_common.py b/aria/modeling/service_common.py index d1f6b00..c813416 100644 --- a/aria/modeling/service_common.py +++ b/aria/modeling/service_common.py @@ -22,7 +22,8 @@ ARIA modeling service common module from sqlalchemy import ( Column, Text, - Boolean + Boolean, + PickleType ) from sqlalchemy.ext.declarative import declared_attr @@ -587,12 +588,11 @@ class MetadataBase(TemplateModelMixin): :ivar name: name :vartype name: basestring :ivar value: value - :vartype value: basestring """ __tablename__ = 'metadata' - value = Column(Text) + value = Column(PickleType) @property def as_raw(self): http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/087666dd/aria/parser/validation/issue.py ---------------------------------------------------------------------- diff --git a/aria/parser/validation/issue.py b/aria/parser/validation/issue.py index 42fc580..cc68737 100644 --- a/aria/parser/validation/issue.py +++ b/aria/parser/validation/issue.py @@ -66,9 +66,9 @@ class Issue(object): def __init__(self, message=None, exception=None, location=None, line=None, column=None, locator=None, snippet=None, level=0): if message is not None: - self.message = str(message) + self.message = unicode(message) elif exception is not None: - self.message = str(exception) + self.message = unicode(exception) else: self.message = 'unknown issue' http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/087666dd/extensions/aria_extension_tosca/simple_v1_0/data_types.py ---------------------------------------------------------------------- diff --git a/extensions/aria_extension_tosca/simple_v1_0/data_types.py b/extensions/aria_extension_tosca/simple_v1_0/data_types.py index 216f1e4..640ed33 100644 --- a/extensions/aria_extension_tosca/simple_v1_0/data_types.py +++ b/extensions/aria_extension_tosca/simple_v1_0/data_types.py @@ -41,7 +41,7 @@ class Timezone(tzinfo): return self._offset def tzname(self, dt): # pylint: disable=unused-argument - return str(self._offset) + return unicode(self._offset) def dst(self, dt): # pylint: disable=unused-argument return Timezone._ZERO @@ -75,7 +75,7 @@ class Timestamp(object): CANONICAL = '%Y-%m-%dT%H:%M:%S' def __init__(self, entry_schema, constraints, value, aspect): # pylint: disable=unused-argument - value = str(value) + value = unicode(value) match = re.match(Timestamp.REGULAR_SHORT, value) if match is not None: # Parse short form @@ -116,8 +116,8 @@ class Timestamp(object): Timezone(tzhour, tzminute)) else: raise ValueError( - 'timestamp must be formatted as YAML ISO8601 variant or "YYYY-MM-DD": %s' - % safe_repr(value)) + 'timestamp must be formatted as YAML ISO8601 variant or "YYYY-MM-DD": {0}' + .format(safe_repr(value))) @property def as_datetime_utc(self): @@ -129,8 +129,8 @@ class Timestamp(object): def __str__(self): the_datetime = self.as_datetime_utc - return '%s%sZ' \ - % (the_datetime.strftime(Timestamp.CANONICAL), Timestamp._fraction_as_str(the_datetime)) + return '{0}{1}Z'.format(the_datetime.strftime(Timestamp.CANONICAL), + Timestamp._fraction_as_str(the_datetime)) def __repr__(self): return repr(self.__str__()) @@ -165,7 +165,7 @@ class Version(object): REGEX = \ r'^(?P<major>\d+)\.(?P<minor>\d+)(\.(?P<fix>\d+)' + \ - r'((\.(?P<qualifier>\d+))(\-(?P<build>\d+))?)?)?$' + r'((\.(?P<qualifier>\w+))(\-(?P<build>\d+))?)?)?$' @staticmethod def key(version): @@ -175,13 +175,12 @@ class Version(object): return (version.major, version.minor, version.fix, version.qualifier, version.build) def __init__(self, entry_schema, constraints, value, aspect): # pylint: disable=unused-argument - str_value = str(value) - match = re.match(Version.REGEX, str_value) + str_value = unicode(value) + match = re.match(Version.REGULAR, str_value, flags=re.UNICODE) if match is None: raise ValueError( 'version must be formatted as <major_version>.<minor_version>' - '[.<fix_version>[.<qualifier>[-<build_version]]]: %s' - % safe_repr(value)) + '[.<fix_version>[.<qualifier>[-<build_version]]]: {0}'.format(safe_repr(value))) self.value = str_value @@ -193,8 +192,6 @@ class Version(object): if self.fix is not None: self.fix = int(self.fix) self.qualifier = match.group('qualifier') - if self.qualifier is not None: - self.qualifier = int(self.qualifier) self.build = match.group('build') if self.build is not None: self.build = int(self.build) @@ -215,6 +212,7 @@ class Version(object): return (self.major, self.minor, self.fix, self.qualifier, self.build) == \ (version.major, version.minor, version.fix, version.qualifier, version.build) + @implements_specification('3.2.2.1', 'tosca-simple-1.0') def __lt__(self, version): if self.major < version.major: return True @@ -225,9 +223,7 @@ class Version(object): if self.fix < version.fix: return True elif self.fix == version.fix: - if self.qualifier < version.qualifier: - return True - elif self.qualifier == version.qualifier: + if self.qualifier == version.qualifier: if self.build < version.build: return True return False @@ -246,26 +242,27 @@ class Range(object): def __init__(self, entry_schema, constraints, value, aspect): # pylint: disable=unused-argument if not isinstance(value, list): - raise ValueError('range value is not a list: %s' % safe_repr(value)) + raise ValueError('range value is not a list: {0}'.format(safe_repr(value))) if len(value) != 2: - raise ValueError('range value does not have exactly 2 elements: %s' % safe_repr(value)) + raise ValueError('range value does not have exactly 2 elements: {0}' + .format(safe_repr(value))) def is_int(v): return isinstance(v, int) and (not isinstance(v, bool)) # In Python bool is an int if not is_int(value[0]): - raise ValueError('lower bound of range is not a valid integer: %s' - % safe_repr(value[0])) + raise ValueError('lower bound of range is not a valid integer: {0}' + .format(safe_repr(value[0]))) if value[1] != 'UNBOUNDED': if not is_int(value[1]): - raise ValueError('upper bound of range is not a valid integer or "UNBOUNDED": %s' - % safe_repr(value[0])) + raise ValueError('upper bound of range is not a valid integer or "UNBOUNDED": {0}' + .format(safe_repr(value[0]))) if value[0] >= value[1]: raise ValueError( - 'upper bound of range is not greater than the lower bound: %s >= %s' - % (safe_repr(value[0]), safe_repr(value[1]))) + 'upper bound of range is not greater than the lower bound: {0} >= {1}' + .format(safe_repr(value[0]), safe_repr(value[1]))) self.value = value @@ -296,7 +293,7 @@ class List(list): @staticmethod def _create(context, presentation, entry_schema, constraints, value, aspect): # pylint: disable=unused-argument if not isinstance(value, list): - raise ValueError('"list" data type value is not a list: %s' % safe_repr(value)) + raise ValueError('"list" data type value is not a list: {0}'.format(safe_repr(value))) entry_schema_type = entry_schema._get_type(context) entry_schema_constraints = entry_schema.constraints @@ -330,7 +327,7 @@ class Map(StrictDict): @staticmethod def _create(context, presentation, entry_schema, constraints, value, aspect): # pylint: disable=unused-argument if not isinstance(value, dict): - raise ValueError('"map" data type value is not a dict: %s' % safe_repr(value)) + raise ValueError('"map" data type value is not a dict: {0}'.format(safe_repr(value))) if entry_schema is None: raise ValueError('"map" data type does not define "entry_schema"') @@ -375,14 +372,15 @@ class Scalar(object): return scalar.value def __init__(self, entry_schema, constraints, value, aspect): # pylint: disable=unused-argument - str_value = str(value) - match = re.match(self.REGEX, str_value) # pylint: disable=no-member + str_value = unicode(value) + match = re.match(self.REGULAR, str_value, flags=re.UNICODE) # pylint: disable=no-member if match is None: - raise ValueError('scalar must be formatted as <scalar> <unit>: %s' % safe_repr(value)) + raise ValueError('scalar must be formatted as <scalar> <unit>: {0}' + .format(safe_repr(value))) self.factor = float(match.group('scalar')) if self.factor < 0: - raise ValueError('scalar is negative: %s' % safe_repr(self.factor)) + raise ValueError('scalar is negative: {0}'.format(safe_repr(self.factor))) self.unit = match.group('unit') @@ -394,7 +392,8 @@ class Scalar(object): unit_size = v break if unit_size is None: - raise ValueError('scalar specified with unsupported unit: %s' % safe_repr(self.unit)) + raise ValueError('scalar specified with unsupported unit: {0}' + .format(safe_repr(self.unit))) self.value = self.TYPE(self.factor * unit_size) # pylint: disable=no-member @@ -407,7 +406,7 @@ class Scalar(object): ('unit_size', self.UNITS[self.unit]))) # pylint: disable=no-member def __str__(self): - return '%s %s' % (self.value, self.UNIT) # pylint: disable=no-member + return '{0} {1}'.format(self.value, self.UNIT) # pylint: disable=no-member def __repr__(self): return repr(self.__str__()) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/087666dd/extensions/aria_extension_tosca/simple_v1_0/misc.py ---------------------------------------------------------------------- diff --git a/extensions/aria_extension_tosca/simple_v1_0/misc.py b/extensions/aria_extension_tosca/simple_v1_0/misc.py index 221163c..418fe31 100644 --- a/extensions/aria_extension_tosca/simple_v1_0/misc.py +++ b/extensions/aria_extension_tosca/simple_v1_0/misc.py @@ -20,14 +20,16 @@ from aria.parser import implements_specification from aria.parser.presentation import (AsIsPresentation, has_fields, allow_unknown_fields, short_form_field, primitive_field, primitive_list_field, primitive_dict_unknown_fields, object_field, - object_list_field, object_dict_field, field_validator, - type_validator) + object_list_field, object_dict_field, field_getter, + field_validator, type_validator) +from .data_types import Version from .modeling.data_types import (get_data_type, get_data_type_value, get_property_constraints, apply_constraint_to_value) from .modeling.substitution_mappings import (validate_substitution_mappings_requirement, validate_substitution_mappings_capability) from .presentation.extensible import ExtensiblePresentation +from .presentation.field_getters import data_type_class_getter from .presentation.field_validators import (constraint_clause_field_validator, constraint_clause_in_range_validator, constraint_clause_valid_values_validator, @@ -79,6 +81,7 @@ class MetaData(ExtensiblePresentation): as a single-line string value. """ + @field_getter(data_type_class_getter(Version)) @primitive_field(str) @implements_specification('3.9.3.5', 'tosca-simple-1.0') def template_version(self): @@ -87,7 +90,7 @@ class MetaData(ExtensiblePresentation): service template as a single-line string value. """ - @primitive_dict_unknown_fields() + @primitive_dict_unknown_fields(str) def custom(self): """ :type: dict http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/087666dd/extensions/aria_extension_tosca/simple_v1_0/presentation/field_getters.py ---------------------------------------------------------------------- diff --git a/extensions/aria_extension_tosca/simple_v1_0/presentation/field_getters.py b/extensions/aria_extension_tosca/simple_v1_0/presentation/field_getters.py index f14164a..34dacd6 100644 --- a/extensions/aria_extension_tosca/simple_v1_0/presentation/field_getters.py +++ b/extensions/aria_extension_tosca/simple_v1_0/presentation/field_getters.py @@ -14,6 +14,7 @@ # limitations under the License. from aria.utils.formatting import safe_repr +from aria.utils.type import full_type_name from aria.parser.exceptions import InvalidValueError @@ -31,7 +32,8 @@ def data_type_class_getter(cls): return cls(None, None, raw, None) except ValueError as e: raise InvalidValueError( - '%s is not a valid "%s" in "%s": %s' - % (field.full_name, field.full_cls_name, presentation._name, safe_repr(raw)), + '{0} is not a valid "{1}" in "{2}": {3}' + .format(field.full_name, full_type_name(cls), presentation._name, + safe_repr(raw)), cause=e, locator=field.get_locator(raw)) return getter http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/087666dd/tests/extensions/__init__.py ---------------------------------------------------------------------- diff --git a/tests/extensions/__init__.py b/tests/extensions/__init__.py new file mode 100644 index 0000000..ae1e83e --- /dev/null +++ b/tests/extensions/__init__.py @@ -0,0 +1,14 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/087666dd/tests/extensions/aria_extension_tosca/__init__.py ---------------------------------------------------------------------- diff --git a/tests/extensions/aria_extension_tosca/__init__.py b/tests/extensions/aria_extension_tosca/__init__.py new file mode 100644 index 0000000..ae1e83e --- /dev/null +++ b/tests/extensions/aria_extension_tosca/__init__.py @@ -0,0 +1,14 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/087666dd/tests/extensions/aria_extension_tosca/simple_v1_0/__init__.py ---------------------------------------------------------------------- diff --git a/tests/extensions/aria_extension_tosca/simple_v1_0/__init__.py b/tests/extensions/aria_extension_tosca/simple_v1_0/__init__.py new file mode 100644 index 0000000..ae1e83e --- /dev/null +++ b/tests/extensions/aria_extension_tosca/simple_v1_0/__init__.py @@ -0,0 +1,14 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/087666dd/tests/extensions/aria_extension_tosca/simple_v1_0/conftest.py ---------------------------------------------------------------------- diff --git a/tests/extensions/aria_extension_tosca/simple_v1_0/conftest.py b/tests/extensions/aria_extension_tosca/simple_v1_0/conftest.py new file mode 100644 index 0000000..86bbc3f --- /dev/null +++ b/tests/extensions/aria_extension_tosca/simple_v1_0/conftest.py @@ -0,0 +1,28 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 pytest + +from ....mechanisms.parsing.aria import AriaParser + + +def pytest_report_header(config): + return 'parser: ARIA' + + [email protected](scope='session') +def parser(): + with AriaParser() as p: + yield p http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/087666dd/tests/extensions/aria_extension_tosca/simple_v1_0/test_imports.py ---------------------------------------------------------------------- diff --git a/tests/extensions/aria_extension_tosca/simple_v1_0/test_imports.py b/tests/extensions/aria_extension_tosca/simple_v1_0/test_imports.py new file mode 100644 index 0000000..4d78f40 --- /dev/null +++ b/tests/extensions/aria_extension_tosca/simple_v1_0/test_imports.py @@ -0,0 +1,152 @@ +# -*- coding: utf-8 -*- +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 pytest + +from ....mechanisms.web_server import WebServer + + +NODE_TYPE_IMPORT = """ +node_types: + MyNode: + derived_from: tosca.nodes.Root +""" + +BAD_IMPORT = """ +node_types: + MyNode: + derived_from: not.a.node.type +""" + [email protected](scope='session') +def repository(): + repository = WebServer() + repository.add_text_yaml('/imports/node-type.yaml', NODE_TYPE_IMPORT) + repository.add_text_yaml('/imports/{0}.yaml'.format(WebServer.escape('è© å調')), + NODE_TYPE_IMPORT) + repository.add_text_yaml('/imports/bad.yaml', BAD_IMPORT) + repository.start() + yield repository.root + repository.stop() + + +# Syntax + [email protected]('value', ('null', 'a_string', '123', '0.123', '{}')) +def test_imports_wrong_yaml_type(parser, value): + parser.parse_literal(""" +tosca_definitions_version: tosca_simple_yaml_1_0 +imports: {{ value }} +""", dict(value=value)).assert_failure() + + +def test_imports_empty_list(parser): + parser.parse_literal(""" +tosca_definitions_version: tosca_simple_yaml_1_0 +imports: [] +""").assert_success() + + +# Variants + +def test_import_single_short_form(parser, repository): + parser.parse_literal(""" +tosca_definitions_version: tosca_simple_yaml_1_0 +imports: + - {{ repository }}/imports/node-type.yaml +topology_template: + node_templates: + my_node: + type: MyNode +""", dict(repository=repository)).assert_success() + + +def test_import_single_short_form_unicode(parser, repository): + parser.parse_literal(u""" +tosca_definitions_version: tosca_simple_yaml_1_0 +imports: + - {{ repository }}/imports/è© å調.yaml +topology_template: + node_templates: + my_node: + type: MyNode +""", dict(repository=repository)).assert_success() + + +def test_import_single_long_form(parser, repository): + parser.parse_literal(""" +tosca_definitions_version: tosca_simple_yaml_1_0 +imports: + - file: {{ repository }}/imports/node-type.yaml +topology_template: + node_templates: + my_node: + type: MyNode +""", dict(repository=repository)).assert_success() + + [email protected](reason='not yet supported') +def test_import_single_repository(parser, repository): + parser.parse_literal(""" +tosca_definitions_version: tosca_simple_yaml_1_0 +repositories: + myrepository: + url: {{ repository }}/imports/ +imports: + - file: node-type.yaml + repository: myrepository +topology_template: + node_templates: + my_node: + type: MyNode +""", dict(repository=repository)).assert_success() + + [email protected](reason='not yet supported') +def test_import_single_namespace(parser, repository): + parser.parse_literal(""" +tosca_definitions_version: tosca_simple_yaml_1_0 +imports: + - file: {{ repository }}/imports/node-type.yaml + namespace_uri: + namespace_prefix: my_namespace +topology_template: + node_templates: + my_node: + type: my_namespace.MyNode +""", dict(repository=repository)).assert_success() + + +# Failures + +def test_import_not_found(parser): + parser.parse_literal(""" +tosca_definitions_version: tosca_simple_yaml_1_0 +imports: + - does_not_exist +""").assert_failure() + + +def test_import_bad(parser, repository): + parser.parse_literal(""" +tosca_definitions_version: tosca_simple_yaml_1_0 +imports: + - {{ repository }}/imports/bad.yaml +topology_template: + node_templates: + my_node: + type: MyNode +""", dict(repository=repository)).assert_failure() http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/087666dd/tests/extensions/aria_extension_tosca/simple_v1_0/test_metadata.py ---------------------------------------------------------------------- diff --git a/tests/extensions/aria_extension_tosca/simple_v1_0/test_metadata.py b/tests/extensions/aria_extension_tosca/simple_v1_0/test_metadata.py new file mode 100644 index 0000000..dae5631 --- /dev/null +++ b/tests/extensions/aria_extension_tosca/simple_v1_0/test_metadata.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 pytest + + +# Syntax + [email protected]('value', ('null', 'a_string', '123', '0.123', '[]')) +def test_metadata_wrong_yaml_type(parser, value): + parser.parse_literal(""" +tosca_definitions_version: tosca_simple_yaml_1_0 +metadata: {{ value }} +""", dict(value=value)).assert_failure() + + [email protected]('field,value', ( + ('template_name', '123'), + ('template_name', '0.123'), + ('template_name', '[]'), + ('template_name', '{}'), + ('template_author', '123'), + ('template_author', '0.123'), + ('template_author', '[]'), + ('template_author', '{}'), + ('template_version', '123'), + ('template_version', '0.123'), + ('template_version', '[]'), + ('template_version', '{}'))) +def test_metadata_normative_wrong_yaml_type(parser, field, value): + parser.parse_literal(""" +tosca_definitions_version: tosca_simple_yaml_1_0 +metadata: + {{ field }}: {{ value }} +""", dict(field=field, value=value)).assert_failure() + + [email protected]('value', ('123', '0.123', '[]', '{}')) +def test_metadata_non_normative_wrong_yaml_type(parser, value): + parser.parse_literal(""" +tosca_definitions_version: tosca_simple_yaml_1_0 +metadata: + non_normative: {{ value }} +""", dict(value=value)).assert_failure() + + +def test_metadata_empty_dict(parser): + parser.parse_literal(""" +tosca_definitions_version: tosca_simple_yaml_1_0 +metadata: {} +""").assert_success() + + +# Normative + [email protected]('value', ('null', 'a_string', '1.2.3.4.5')) +def test_metadata_normative_template_bad_version(parser, value): + parser.parse_literal(""" +tosca_definitions_version: tosca_simple_yaml_1_0 +metadata: + template_version: {{ value }} +""", dict(value=value)).assert_failure() + + [email protected]('value', ("'6.1'", '2.0.1', '3.1.0.beta', "'1.0.0.alpha-10'")) +def test_metadata_normative_template_version(parser, value): + parser.parse_literal(""" +tosca_definitions_version: tosca_simple_yaml_1_0 +metadata: + template_version: {{ value }} +""", dict(value=value)).assert_success() + +# Non-normative + +def test_metadata_with_non_normative_fields(parser): + parser.parse_literal(""" +tosca_definitions_version: tosca_simple_yaml_1_0 +metadata: + template_name: name + template_author: author + template_version: 1.0.0.beta + non_normative1: non_normative1 + non_normative2: non_normative2 + non_normative3: non_normative3 +""").assert_success() + + +def test_metadata_with_non_normative_fields_nulls(parser): + parser.parse_literal(""" +tosca_definitions_version: tosca_simple_yaml_1_0 +metadata: + template_name: null + template_author: null + template_version: 1.0.0.beta + non_normative1: null + non_normative2: null + non_normative3: null +""").assert_success() + + +def test_metadata_with_non_normative_fields_unicode(parser): + parser.parse_literal(u""" +tosca_definitions_version: tosca_simple_yaml_1_0 +metadata: + template_name: è© å調 + template_author: è© å調 + template_version: 1.0.0.è© å調 + non_normative1: è© å調 + non_normative2: è© å調 + non_normative3: è© å調 +""").assert_success() http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/087666dd/tests/mechanisms/__init__.py ---------------------------------------------------------------------- diff --git a/tests/mechanisms/__init__.py b/tests/mechanisms/__init__.py new file mode 100644 index 0000000..ae1e83e --- /dev/null +++ b/tests/mechanisms/__init__.py @@ -0,0 +1,14 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/087666dd/tests/mechanisms/parsing/__init__.py ---------------------------------------------------------------------- diff --git a/tests/mechanisms/parsing/__init__.py b/tests/mechanisms/parsing/__init__.py new file mode 100644 index 0000000..c1525a8 --- /dev/null +++ b/tests/mechanisms/parsing/__init__.py @@ -0,0 +1,55 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 pytest +from jinja2 import Template + + +class Parsed(object): + def __init__(self): + self.issues = [] + self.text = '' + + def assert_success(self): + __tracebackhide__ = True # pylint: disable=unused-variable + if len(self.issues) > 0: + pytest.fail(u'did not expect parsing errors\n\n{0}\n\n{1}' + .format(self.text.strip(), u'\n'.join(self.issues))) + + def assert_failure(self): + __tracebackhide__ = True # pylint: disable=unused-variable + if len(self.issues) > 0: + pass + else: + pytest.fail(u'expected parsing errors but got none\n\n{0}' + .format(self.text.strip())) + + +class Parser(object): + def parse_literal(self, text, context=None): + text = render(text, context) + return self._parse_literal(text) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + pass + + +def render(template, context=None): + template = Template(template) + template = template.render(context or {}) + return template http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/087666dd/tests/mechanisms/parsing/aria.py ---------------------------------------------------------------------- diff --git a/tests/mechanisms/parsing/aria.py b/tests/mechanisms/parsing/aria.py new file mode 100644 index 0000000..c02d387 --- /dev/null +++ b/tests/mechanisms/parsing/aria.py @@ -0,0 +1,63 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +from __future__ import absolute_import # so we can import root 'aria' + +from aria.parser.loading import LiteralLocation +from aria.parser.consumption import ( + ConsumptionContext, + ConsumerChain, + Read, + Validate, + ServiceTemplate +) +from aria.utils.imports import import_fullname + +from . import Parser, Parsed + + +class AriaParser(Parser): + def _parse_literal(self, text): + context = AriaParser.create_context() + context.presentation.location = LiteralLocation(text) + consumer = AriaParser.create_consumer(context) + consumer.consume() + parsed = Parsed() + parsed.text = text + for issue in context.validation.issues: + parsed.issues.append(unicode(issue)) + return parsed + + @staticmethod + def create_context(loader_source='aria.parser.loading.DefaultLoaderSource', + reader_source='aria.parser.reading.DefaultReaderSource', + presenter_source='aria.parser.presentation.DefaultPresenterSource', + presenter=None, + debug=False): + context = ConsumptionContext() + context.loading.loader_source = import_fullname(loader_source)() + context.reading.reader_source = import_fullname(reader_source)() + context.presentation.presenter_source = import_fullname(presenter_source)() + context.presentation.presenter_class = import_fullname(presenter) + context.presentation.print_exceptions = debug + return context + + @staticmethod + def create_consumer(context): + return ConsumerChain(context, ( + Read, + Validate, + ServiceTemplate + )) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/087666dd/tests/mechanisms/web_server.py ---------------------------------------------------------------------- diff --git a/tests/mechanisms/web_server.py b/tests/mechanisms/web_server.py new file mode 100644 index 0000000..7db901e --- /dev/null +++ b/tests/mechanisms/web_server.py @@ -0,0 +1,75 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 logging +import threading + +import tornado.web +import tornado.ioloop +import tornado.netutil + + +logging.getLogger('tornado.access').disabled = True + + +class WebServer(threading.Thread): + def __init__(self): + super(WebServer, self).__init__() + self.daemon = True + + self.content = [] + + # Arbitrary free socket + self.sockets = tornado.netutil.bind_sockets(0, '') + for s in self.sockets: + name = s.getsockname() + if name[0] == '0.0.0.0': # IPv4 (IPv6 would be '::') + self.port = name[1] + break + + @property + def root(self): + return 'http://localhost:{0}'.format(self.port) + + def add_text(self, url, content, content_type): + self.content.append((url, TextHandler, dict(content=content, content_type=content_type))) + + def add_text_yaml(self, url, content): + self.add_text(url, content, 'application/x-yaml') + + def stop(self): + self.ioloop.add_callback(self.ioloop.stop) + + def run(self): # Thread override + application = tornado.web.Application(self.content) + server = tornado.httpserver.HTTPServer(application) + server.add_sockets(self.sockets) + self.ioloop = tornado.ioloop.IOLoop.current() + print 'Tornado starting' + self.ioloop.start() + print 'Tornado stopped' + + @staticmethod + def escape(segment): + return tornado.escape.url_escape(segment) + +class TextHandler(tornado.web.RequestHandler): + def initialize(self, content, content_type): # pylint: disable=arguments-differ + self.content = content + self.content_type = content_type + + def get(self): + self.write(self.content) + self.set_header('Content-Type', self.content_type) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/087666dd/tests/requirements.txt ---------------------------------------------------------------------- diff --git a/tests/requirements.txt b/tests/requirements.txt index 56a7bf5..bdd5e2c 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -13,6 +13,7 @@ testtools==2.3.0 fasteners==0.14.1 sh==1.12.14 +tornado==4.3 # last release to support Python 2.6 psutil==5.2.2 mock==2.0.0 pylint==1.6.5
