You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ariatosca.apache.org by em...@apache.org on 2017/11/24 18:23:00 UTC
incubator-ariatosca git commit: Fixes
Repository: incubator-ariatosca
Updated Branches:
refs/heads/ARIA-1-parser-test-suite ce4a18371 -> 0cf1deafc
Fixes
Project: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/commit/0cf1deaf
Tree: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/tree/0cf1deaf
Diff: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/diff/0cf1deaf
Branch: refs/heads/ARIA-1-parser-test-suite
Commit: 0cf1deafc25bb21377be5f0153ae639f877af8a3
Parents: ce4a183
Author: Tal Liron <ta...@gmail.com>
Authored: Fri Nov 24 12:22:52 2017 -0600
Committer: Tal Liron <ta...@gmail.com>
Committed: Fri Nov 24 12:22:52 2017 -0600
----------------------------------------------------------------------
aria/__init__.py | 5 +-
aria/modeling/service_instance.py | 2 +-
aria/parser/consumption/presentation.py | 50 +-
aria/parser/presentation/presentation.py | 1 +
aria/parser/reading/yaml.py | 11 +-
aria/utils/collections.py | 25 +-
.../simple_v1_0/assignments.py | 19 +-
.../simple_v1_0/modeling/__init__.py | 14 +-
.../simple_v1_0/modeling/data_types.py | 6 +-
test_ssh.py | 528 -------------------
.../extensions/aria_extension_tosca/conftest.py | 3 +
tests/mechanisms/parsing/__init__.py | 10 +-
tests/mechanisms/utils.py | 15 +-
13 files changed, 110 insertions(+), 579 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0cf1deaf/aria/__init__.py
----------------------------------------------------------------------
diff --git a/aria/__init__.py b/aria/__init__.py
index acaf81b..980a2bb 100644
--- a/aria/__init__.py
+++ b/aria/__init__.py
@@ -57,9 +57,8 @@ def install_aria_extensions(strict=True):
if module_name.startswith('aria_extension_'):
loader.find_module(module_name).load_module(module_name)
for entry_point in pkg_resources.iter_entry_points(group='aria_extension'):
- # It should be possible to enable non strict loading - use the package
- # that is already installed inside the environment, and forego the
- # version demand
+ # It should be possible to enable non strict loading - use the package that is already
+ # installed inside the environment, and forgo the version demand
if strict:
entry_point.load()
else:
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0cf1deaf/aria/modeling/service_instance.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_instance.py b/aria/modeling/service_instance.py
index b0e426c..21c1029 100644
--- a/aria/modeling/service_instance.py
+++ b/aria/modeling/service_instance.py
@@ -510,7 +510,7 @@ class NodeBase(InstanceModelMixin):
@classmethod
def determine_state(cls, op_name, is_transitional):
"""
- :returns the state the node should be in as a result of running the operation on this node.
+ :return: the state the node should be in as a result of running the operation on this node.
E.g. if we are running tosca.interfaces.node.lifecycle.Standard.create, then
the resulting state should either 'creating' (if the task just started) or 'created'
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0cf1deaf/aria/parser/consumption/presentation.py
----------------------------------------------------------------------
diff --git a/aria/parser/consumption/presentation.py b/aria/parser/consumption/presentation.py
index b1f943d..0f0b380 100644
--- a/aria/parser/consumption/presentation.py
+++ b/aria/parser/consumption/presentation.py
@@ -46,19 +46,19 @@ class Read(Consumer):
def consume(self):
# Present the main location and all imports recursively
- main, results = self._present_all()
+ main_result, all_results = self._present_all()
# Merge presentations
- main.merge(results, self.context)
+ main_result.merge(all_results, self.context)
# Cache merged presentations
if self.context.presentation.cache:
- for result in results:
+ for result in all_results:
result.cache()
- self.context.presentation.presenter = main.presentation
- if main.canonical_location is not None:
- self.context.presentation.location = main.canonical_location
+ self.context.presentation.presenter = main_result.presentation
+ if main_result.canonical_location is not None:
+ self.context.presentation.location = main_result.canonical_location
def dump(self):
if self.context.has_arg_switch('yaml'):
@@ -73,11 +73,18 @@ class Read(Consumer):
self.context.presentation.presenter._dump(self.context)
def _handle_exception(self, e):
- if isinstance(e, _Skip):
+ if isinstance(e, _CancelPresentation):
return
super(Read, self)._handle_exception(e)
def _present_all(self):
+ """
+ Presents all locations, including all nested imports, from the main location. Uses a thread
+ pool executor for best performance.
+
+ The main presentation is returned separately for easier access.
+ """
+
location = self.context.presentation.location
if location is None:
@@ -87,7 +94,7 @@ class Read(Consumer):
executor = self.context.presentation.create_executor()
try:
# This call may recursively submit tasks to the executor if there are imports
- main = self._present(location, None, None, executor)
+ main_result = self._present(location, None, None, executor)
# Wait for all tasks to complete
executor.drain()
@@ -96,15 +103,22 @@ class Read(Consumer):
for e in executor.exceptions:
self._handle_exception(e)
- results = executor.returns or []
+ all_results = executor.returns or []
finally:
executor.close()
- results.insert(0, main)
+ all_results.insert(0, main_result)
- return main, results
+ return main_result, all_results
def _present(self, location, origin_canonical_location, origin_presenter_class, executor):
+ """
+ Presents a single location. If the location has imports, those are submitted to the thread
+ pool executor.
+
+ Supports a presentation cache based on the canonical location as cache key.
+ """
+
# Link the context to this thread
self.context.set_thread_local()
@@ -118,7 +132,7 @@ class Read(Consumer):
# Skip self imports
if canonical_location == origin_canonical_location:
- raise _Skip()
+ raise _CancelPresentation()
if self.context.presentation.cache:
# Is the presentation in the global cache?
@@ -154,9 +168,10 @@ class Read(Consumer):
loader = self.context.loading.loader_source.get_loader(self.context.loading, location,
origin_canonical_location)
- canonical_location = None
-
if origin_canonical_location is not None:
+ # The cache key is is a combination of the canonical location of the origin, which is
+ # globally absolute and never changes, and our location, which might be relative to
+ # the origin's location
cache_key = (origin_canonical_location, location)
try:
canonical_location = CANONICAL_LOCATION_CACHE[cache_key]
@@ -210,6 +225,11 @@ class Read(Consumer):
class _Result(object):
+ """
+ The result of a :meth:`Read._present` call. Contains the read presentation itself, as well as
+ extra fields to help caching and keep track of merging.
+ """
+
def __init__(self, presentation, canonical_location, origin_canonical_location):
self.presentation = presentation
self.canonical_location = canonical_location
@@ -261,5 +281,5 @@ class _Result(object):
PRESENTATION_CACHE[self.canonical_location] = self.presentation
-class _Skip(Exception):
+class _CancelPresentation(Exception):
pass
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0cf1deaf/aria/parser/presentation/presentation.py
----------------------------------------------------------------------
diff --git a/aria/parser/presentation/presentation.py b/aria/parser/presentation/presentation.py
index e1104e5..762ecba 100644
--- a/aria/parser/presentation/presentation.py
+++ b/aria/parser/presentation/presentation.py
@@ -199,6 +199,7 @@ class Presentation(PresentationBase):
"""
def _validate(self, context):
+ # Allow the skipping of normative type validation (for improved performance)
if (not context.presentation.configuration.get('validate_normative', True)) \
and self._get_extension('normative'):
return
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0cf1deaf/aria/parser/reading/yaml.py
----------------------------------------------------------------------
diff --git a/aria/parser/reading/yaml.py b/aria/parser/reading/yaml.py
index 77f8144..d843859 100644
--- a/aria/parser/reading/yaml.py
+++ b/aria/parser/reading/yaml.py
@@ -10,8 +10,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from ...utils.yaml import yaml # @UnresolvedImport
-
+from ...utils.yaml import yaml
from ...utils.collections import OrderedDict
from .reader import Reader
from .locator import Locator
@@ -19,9 +18,12 @@ from .exceptions import ReaderSyntaxError
from .locator import (LocatableString, LocatableInt, LocatableFloat)
-MERGE_TAG = u'tag:yaml.org,2002:merge'
+# YAML mapping tag
MAP_TAG = u'tag:yaml.org,2002:map'
+# This is an internal tag used by ruamel.yaml for merging nodes
+MERGE_TAG = u'tag:yaml.org,2002:merge'
+
# Add our types to RoundTripRepresenter
yaml.representer.RoundTripRepresenter.add_representer(
@@ -33,6 +35,9 @@ yaml.representer.RoundTripRepresenter.add_representer(
def construct_yaml_map(self, node):
+ """
+ Replacement for ruamel.yaml's constructor that uses OrderedDict instead of dict.
+ """
data = OrderedDict()
yield data
value = self.construct_mapping(node)
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0cf1deaf/aria/utils/collections.py
----------------------------------------------------------------------
diff --git a/aria/utils/collections.py b/aria/utils/collections.py
index cfd8fda..2126d59 100644
--- a/aria/utils/collections.py
+++ b/aria/utils/collections.py
@@ -34,7 +34,7 @@ except ImportError:
def cls_name(cls):
module = str(cls.__module__)
name = str(cls.__name__)
- return name if module == '__builtin__' else '%s.%s' % (module, name)
+ return name if module == '__builtin__' else '{0}.{1}'.format(module, name)
class FrozenList(list):
@@ -145,7 +145,8 @@ class StrictList(list):
def _wrap(self, value):
if (self.value_class is not None) and (not isinstance(value, self.value_class)):
- raise TypeError('value must be a "%s": %s' % (cls_name(self.value_class), repr(value)))
+ raise TypeError('value must be a "{0}": {1}'
+ .format(cls_name(self.value_class), repr(value)))
if self.wrapper_function is not None:
value = self.wrapper_function(value)
return value
@@ -209,7 +210,8 @@ class StrictDict(OrderedDict):
def __getitem__(self, key):
if (self.key_class is not None) and (not isinstance(key, self.key_class)):
- raise TypeError('key must be a "%s": %s' % (cls_name(self.key_class), repr(key)))
+ raise TypeError('key must be a "{0}": {1}'
+ .format(cls_name(self.key_class), repr(key)))
value = super(StrictDict, self).__getitem__(key)
if self.unwrapper_function is not None:
value = self.unwrapper_function(value)
@@ -217,9 +219,11 @@ class StrictDict(OrderedDict):
def __setitem__(self, key, value, **_):
if (self.key_class is not None) and (not isinstance(key, self.key_class)):
- raise TypeError('key must be a "%s": %s' % (cls_name(self.key_class), repr(key)))
+ raise TypeError('key must be a "{0}": {1}'
+ .format(cls_name(self.key_class), repr(key)))
if (self.value_class is not None) and (not isinstance(value, self.value_class)):
- raise TypeError('value must be a "%s": %s' % (cls_name(self.value_class), repr(value)))
+ raise TypeError('value must be a "{0}": {1}'
+ .format(cls_name(self.value_class), repr(value)))
if self.wrapper_function is not None:
value = self.wrapper_function(value)
return super(StrictDict, self).__setitem__(key, value)
@@ -228,6 +232,14 @@ class StrictDict(OrderedDict):
def merge(dict_a, dict_b, copy=True, strict=False, path=None):
"""
Merges dicts, recursively.
+
+ :param dict_a: target dict (will be modified)
+ :param dict_b: source dict (will not be modified)
+ :param copy: if True, will use :func:`deepcopy_fast` on each merged element
+ :param strict: if True, will raise a ValueError if there are key conflicts, otherwise will
+ override exiting values
+ :param path: for internal use in strict mode
+ :return: dict_a, after the merge
"""
# TODO: a.add_yaml_merge(b),
@@ -244,7 +256,8 @@ def merge(dict_a, dict_b, copy=True, strict=False, path=None):
merge(value_a, value_b, copy, strict, path)
elif value_a != value_b:
if strict:
- raise ValueError('dict merge conflict at %s' % '.'.join(path + [str(key)]))
+ raise ValueError('dict merge conflict at {0}'
+ .format('.'.join(path + [str(key)])))
else:
dict_a[key] = deepcopy_fast(value_b) if copy else value_b
else:
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0cf1deaf/extensions/aria_extension_tosca/simple_v1_0/assignments.py
----------------------------------------------------------------------
diff --git a/extensions/aria_extension_tosca/simple_v1_0/assignments.py b/extensions/aria_extension_tosca/simple_v1_0/assignments.py
index 55b7e8d..6248483 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/assignments.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/assignments.py
@@ -146,14 +146,12 @@ class InterfaceAssignment(ExtensiblePresentation):
if isinstance(self._container._container, RequirementAssignment):
# In RequirementAssignment
- requirement_definition = self._container._container._get_definition(context)
- if requirement_definition is not None:
- relationship_definition = requirement_definition.relationship
- if relationship_definition is not None:
- interface_definitions = relationship_definition.interfaces
- if interface_definitions is not None:
- if self._name in interface_definitions:
- return interface_definitions[self._name]._get_type(context)
+ relationship_definition = \
+ self._container._container._get_relationship_definition(context)
+ interface_definitions = relationship_definition.interfaces \
+ if relationship_definition is not None else None
+ if (interface_definitions is not None) and (self._name in interface_definitions):
+ return interface_definitions[self._name]._get_type(context)
interface_definitions = the_type._get_interfaces(context) \
if the_type is not None else None
@@ -312,6 +310,11 @@ class RequirementAssignment(ExtensiblePresentation):
return None
@cachedmethod
+ def _get_relationship_definition(self, context):
+ requirement_definition = self._get_definition(context)
+ return requirement_definition.relationship if requirement_definition is not None else None
+
+ @cachedmethod
def _get_capability(self, context):
capability = self.capability
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0cf1deaf/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py
----------------------------------------------------------------------
diff --git a/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py b/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py
index 17b94fc..6c305c3 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py
@@ -664,18 +664,18 @@ def create_constraint(context, node_filter, constraint_clause, property_name, ca
constraint_key = constraint_clause._raw.keys()[0]
- the_type = constraint_clause._get_type(context)
+ value_type = constraint_clause._get_type(context)
- def coerce_constraint(constraint, the_type=the_type):
- if the_type is not None:
- return coerce_value(context, node_filter, the_type, None, None, constraint,
+ def coerce_constraint(constraint, value_type=value_type):
+ if value_type is not None:
+ return coerce_value(context, node_filter, value_type, None, None, constraint,
constraint_key)
else:
return constraint
- def coerce_constraints(constraints, the_type=the_type):
- if the_type is not None:
- return tuple(coerce_constraint(constraint, the_type) for constraint in constraints)
+ def coerce_constraints(constraints, value_type=value_type):
+ if value_type is not None:
+ return tuple(coerce_constraint(constraint, value_type) for constraint in constraints)
else:
return constraints
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0cf1deaf/extensions/aria_extension_tosca/simple_v1_0/modeling/data_types.py
----------------------------------------------------------------------
diff --git a/extensions/aria_extension_tosca/simple_v1_0/modeling/data_types.py b/extensions/aria_extension_tosca/simple_v1_0/modeling/data_types.py
index 25e53c6..31865b9 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/modeling/data_types.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/modeling/data_types.py
@@ -320,9 +320,9 @@ def apply_constraint_to_value(context, presentation, constraint_clause, value):
def get_data_type_value(context, presentation, field_name, type_name):
value = getattr(presentation, field_name)
if value is not None:
- the_type = get_type_by_name(context, type_name, 'data_types')
- if the_type is not None:
- return coerce_data_type_value(context, presentation, the_type, None, None, value, None)
+ data_type = get_type_by_name(context, type_name, 'data_types')
+ if data_type is not None:
+ return coerce_data_type_value(context, presentation, data_type, None, None, value, None)
else:
context.validation.report(u'field "{0}" in "{1}" refers to unknown data type "{2}"'
.format(field_name, presentation._fullname, type_name),
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0cf1deaf/test_ssh.py
----------------------------------------------------------------------
diff --git a/test_ssh.py b/test_ssh.py
deleted file mode 100644
index 5256cf8..0000000
--- a/test_ssh.py
+++ /dev/null
@@ -1,528 +0,0 @@
-# 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 contextlib
-import json
-import logging
-import os
-
-import pytest
-
-import fabric.api
-from fabric.contrib import files
-from fabric import context_managers
-
-from aria.modeling import models
-from aria.orchestrator import events
-from aria.orchestrator import workflow
-from aria.orchestrator.workflows import api
-from aria.orchestrator.workflows.executor import process
-from aria.orchestrator.workflows.core import engine, graph_compiler
-from aria.orchestrator.workflows.exceptions import ExecutorException
-from aria.orchestrator.exceptions import TaskAbortException, TaskRetryException
-from aria.orchestrator.execution_plugin import operations
-from aria.orchestrator.execution_plugin import constants
-from aria.orchestrator.execution_plugin.exceptions import ProcessException, TaskException
-from aria.orchestrator.execution_plugin.ssh import operations as ssh_operations
-
-from tests import mock, storage, resources
-from tests.orchestrator.workflows.helpers import events_collector
-
-_CUSTOM_BASE_DIR = '/tmp/new-aria-ctx'
-
-import tests
-KEY_FILENAME = os.path.join(tests.ROOT_DIR, 'tests/resources/keys/test')
-
-_FABRIC_ENV = {
- 'disable_known_hosts': True,
- 'user': 'test',
- 'key_filename': KEY_FILENAME
-}
-
-
-import mockssh
-@pytest.fixture(scope='session')
-def server():
- with mockssh.Server({'test': KEY_FILENAME}) as s:
- yield s
-
-
-#@pytest.mark.skipif(not os.environ.get('TRAVIS'), reason='actual ssh server required')
-class TestWithActualSSHServer(object):
-
- def test_run_script_basic(self):
- expected_attribute_value = 'some_value'
- props = self._execute(env={'test_value': expected_attribute_value})
- assert props['test_value'].value == expected_attribute_value
-
- @pytest.mark.skip(reason='sudo privileges are required')
- def test_run_script_as_sudo(self):
- self._execute(use_sudo=True)
- with self._ssh_env():
- assert files.exists('/opt/test_dir')
- fabric.api.sudo('rm -rf /opt/test_dir')
-
- def test_run_script_default_base_dir(self):
- props = self._execute()
- assert props['work_dir'].value == '{0}/work'.format(constants.DEFAULT_BASE_DIR)
-
- @pytest.mark.skip(reason='Re-enable once output from process executor can be captured')
- @pytest.mark.parametrize('hide_groups', [[], ['everything']])
- def test_run_script_with_hide(self, hide_groups):
- self._execute(hide_output=hide_groups)
- output = 'TODO'
- expected_log_message = ('[localhost] run: source {0}/scripts/'
- .format(constants.DEFAULT_BASE_DIR))
- if hide_groups:
- assert expected_log_message not in output
- else:
- assert expected_log_message in output
-
- def test_run_script_process_config(self):
- expected_env_value = 'test_value_env'
- expected_arg1_value = 'test_value_arg1'
- expected_arg2_value = 'test_value_arg2'
- expected_cwd = '/tmp'
- expected_base_dir = _CUSTOM_BASE_DIR
- props = self._execute(
- env={'test_value_env': expected_env_value},
- process={
- 'args': [expected_arg1_value, expected_arg2_value],
- 'cwd': expected_cwd,
- 'base_dir': expected_base_dir
- })
- assert props['env_value'].value == expected_env_value
- assert len(props['bash_version'].value) > 0
- assert props['arg1_value'].value == expected_arg1_value
- assert props['arg2_value'].value == expected_arg2_value
- assert props['cwd'].value == expected_cwd
- assert props['ctx_path'].value == '{0}/ctx'.format(expected_base_dir)
-
- def test_run_script_command_prefix(self):
- props = self._execute(process={'command_prefix': 'bash -i'})
- assert 'i' in props['dollar_dash'].value
-
- def test_run_script_reuse_existing_ctx(self):
- expected_test_value_1 = 'test_value_1'
- expected_test_value_2 = 'test_value_2'
- props = self._execute(
- test_operations=['{0}_1'.format(self.test_name),
- '{0}_2'.format(self.test_name)],
- env={'test_value1': expected_test_value_1,
- 'test_value2': expected_test_value_2})
- assert props['test_value1'].value == expected_test_value_1
- assert props['test_value2'].value == expected_test_value_2
-
- def test_run_script_download_resource_plain(self, tmpdir):
- resource = tmpdir.join('resource')
- resource.write('content')
- self._upload(str(resource), 'test_resource')
- props = self._execute()
- assert props['test_value'].value == 'content'
-
- def test_run_script_download_resource_and_render(self, tmpdir):
- resource = tmpdir.join('resource')
- resource.write('{{ctx.service.name}}')
- self._upload(str(resource), 'test_resource')
- props = self._execute()
- assert props['test_value'].value == self._workflow_context.service.name
-
- @pytest.mark.parametrize('value', ['string-value', [1, 2, 3], {'key': 'value'}])
- def test_run_script_inputs_as_env_variables_no_override(self, value):
- props = self._execute(custom_input=value)
- return_value = props['test_value'].value
- expected = return_value if isinstance(value, basestring) else json.loads(return_value)
- assert value == expected
-
- @pytest.mark.parametrize('value', ['string-value', [1, 2, 3], {'key': 'value'}])
- def test_run_script_inputs_as_env_variables_process_env_override(self, value):
- props = self._execute(custom_input='custom-input-value',
- env={'custom_env_var': value})
- return_value = props['test_value'].value
- expected = return_value if isinstance(value, basestring) else json.loads(return_value)
- assert value == expected
-
- def test_run_script_error_in_script(self):
- exception = self._execute_and_get_task_exception()
- assert isinstance(exception, TaskException)
-
- def test_run_script_abort_immediate(self):
- exception = self._execute_and_get_task_exception()
- assert isinstance(exception, TaskAbortException)
- assert exception.message == 'abort-message'
-
- def test_run_script_retry(self):
- exception = self._execute_and_get_task_exception()
- assert isinstance(exception, TaskRetryException)
- assert exception.message == 'retry-message'
-
- def test_run_script_abort_error_ignored_by_script(self):
- exception = self._execute_and_get_task_exception()
- assert isinstance(exception, TaskAbortException)
- assert exception.message == 'abort-message'
-
- def test_run_commands(self):
- temp_file_path = '/tmp/very_temporary_file'
- with self._ssh_env():
- if files.exists(temp_file_path):
- fabric.api.run('rm {0}'.format(temp_file_path))
- self._execute(commands=['touch {0}'.format(temp_file_path)])
- with self._ssh_env():
- assert files.exists(temp_file_path)
- fabric.api.run('rm {0}'.format(temp_file_path))
-
- @pytest.fixture(autouse=True)
- def _setup(self, request, workflow_context, executor, capfd, server):
- print 'HI!!!!!!!!!!', server.port
- self._workflow_context = workflow_context
- self._executor = executor
- self._capfd = capfd
- self.test_name = request.node.originalname or request.node.name
- with self._ssh_env(server):
- for directory in [constants.DEFAULT_BASE_DIR, _CUSTOM_BASE_DIR]:
- if files.exists(directory):
- fabric.api.run('rm -rf {0}'.format(directory))
-
- @contextlib.contextmanager
- def _ssh_env(self, server):
- with self._capfd.disabled():
- with context_managers.settings(fabric.api.hide('everything'),
- host_string='localhost:{0}'.format(server.port),
- **_FABRIC_ENV):
- yield
-
- def _execute(self,
- env=None,
- use_sudo=False,
- hide_output=None,
- process=None,
- custom_input='',
- test_operations=None,
- commands=None):
- process = process or {}
- if env:
- process.setdefault('env', {}).update(env)
-
- test_operations = test_operations or [self.test_name]
-
- local_script_path = os.path.join(resources.DIR, 'scripts', 'test_ssh.sh')
- script_path = os.path.basename(local_script_path)
- self._upload(local_script_path, script_path)
-
- if commands:
- operation = operations.run_commands_with_ssh
- else:
- operation = operations.run_script_with_ssh
-
- node = self._workflow_context.model.node.get_by_name(mock.models.DEPENDENCY_NODE_NAME)
- arguments = {
- 'script_path': script_path,
- 'fabric_env': _FABRIC_ENV,
- 'process': process,
- 'use_sudo': use_sudo,
- 'custom_env_var': custom_input,
- 'test_operation': '',
- }
- if hide_output:
- arguments['hide_output'] = hide_output
- if commands:
- arguments['commands'] = commands
- interface = mock.models.create_interface(
- node.service,
- 'test',
- 'op',
- operation_kwargs=dict(
- function='{0}.{1}'.format(
- operations.__name__,
- operation.__name__),
- arguments=arguments)
- )
- node.interfaces[interface.name] = interface
-
- @workflow
- def mock_workflow(ctx, graph):
- ops = []
- for test_operation in test_operations:
- op_arguments = arguments.copy()
- op_arguments['test_operation'] = test_operation
- ops.append(api.task.OperationTask(
- node,
- interface_name='test',
- operation_name='op',
- arguments=op_arguments))
-
- graph.sequence(*ops)
- return graph
- tasks_graph = mock_workflow(ctx=self._workflow_context) # pylint: disable=no-value-for-parameter
- graph_compiler.GraphCompiler(
- self._workflow_context, self._executor.__class__).compile(tasks_graph)
- eng = engine.Engine({self._executor.__class__: self._executor})
- eng.execute(self._workflow_context)
- return self._workflow_context.model.node.get_by_name(
- mock.models.DEPENDENCY_NODE_NAME).attributes
-
- def _execute_and_get_task_exception(self, *args, **kwargs):
- signal = events.on_failure_task_signal
- with events_collector(signal) as collected:
- with pytest.raises(ExecutorException):
- self._execute(*args, **kwargs)
- return collected[signal][0]['kwargs']['exception']
-
- def _upload(self, source, path):
- self._workflow_context.resource.service.upload(
- entry_id=str(self._workflow_context.service.id),
- source=source,
- path=path)
-
- @pytest.fixture
- def executor(self):
- result = process.ProcessExecutor()
- try:
- yield result
- finally:
- result.close()
-
- @pytest.fixture
- def workflow_context(self, tmpdir):
- workflow_context = mock.context.simple(str(tmpdir))
- workflow_context.states = []
- workflow_context.exception = None
- yield workflow_context
- storage.release_sqlite_storage(workflow_context.model)
-
-
-class TestFabricEnvHideGroupsAndRunCommands(object):
-
- def test_fabric_env_default_override(self):
- # first sanity for no override
- self._run()
- assert self.mock.settings_merged['timeout'] == constants.FABRIC_ENV_DEFAULTS['timeout']
- # now override
- invocation_fabric_env = self.default_fabric_env.copy()
- timeout = 1000000
- invocation_fabric_env['timeout'] = timeout
- self._run(fabric_env=invocation_fabric_env)
- assert self.mock.settings_merged['timeout'] == timeout
-
- def test_implicit_host_string(self, mocker):
- expected_host_address = '1.1.1.1'
- mocker.patch.object(self._Ctx.task.actor, 'host')
- mocker.patch.object(self._Ctx.task.actor.host, 'host_address', expected_host_address)
- fabric_env = self.default_fabric_env.copy()
- del fabric_env['host_string']
- self._run(fabric_env=fabric_env)
- assert self.mock.settings_merged['host_string'] == expected_host_address
-
- def test_explicit_host_string(self):
- fabric_env = self.default_fabric_env.copy()
- host_string = 'explicit_host_string'
- fabric_env['host_string'] = host_string
- self._run(fabric_env=fabric_env)
- assert self.mock.settings_merged['host_string'] == host_string
-
- def test_override_warn_only(self):
- fabric_env = self.default_fabric_env.copy()
- self._run(fabric_env=fabric_env)
- assert self.mock.settings_merged['warn_only'] is True
- fabric_env = self.default_fabric_env.copy()
- fabric_env['warn_only'] = False
- self._run(fabric_env=fabric_env)
- assert self.mock.settings_merged['warn_only'] is False
-
- def test_missing_host_string(self):
- with pytest.raises(TaskAbortException) as exc_ctx:
- fabric_env = self.default_fabric_env.copy()
- del fabric_env['host_string']
- self._run(fabric_env=fabric_env)
- assert '`host_string` not supplied' in str(exc_ctx.value)
-
- def test_missing_user(self):
- with pytest.raises(TaskAbortException) as exc_ctx:
- fabric_env = self.default_fabric_env.copy()
- del fabric_env['user']
- self._run(fabric_env=fabric_env)
- assert '`user` not supplied' in str(exc_ctx.value)
-
- def test_missing_key_or_password(self):
- with pytest.raises(TaskAbortException) as exc_ctx:
- fabric_env = self.default_fabric_env.copy()
- del fabric_env['key_filename']
- self._run(fabric_env=fabric_env)
- assert 'Access credentials not supplied' in str(exc_ctx.value)
-
- def test_hide_in_settings_and_non_viable_groups(self):
- groups = ('running', 'stdout')
- self._run(hide_output=groups)
- assert set(self.mock.settings_merged['hide_output']) == set(groups)
- with pytest.raises(TaskAbortException) as exc_ctx:
- self._run(hide_output=('running', 'bla'))
- assert '`hide_output` must be a subset of' in str(exc_ctx.value)
-
- def test_run_commands(self):
- def test(use_sudo):
- commands = ['command1', 'command2']
- self._run(
- commands=commands,
- use_sudo=use_sudo)
- assert all(item in self.mock.settings_merged.items() for
- item in self.default_fabric_env.items())
- assert self.mock.settings_merged['warn_only'] is True
- assert self.mock.settings_merged['use_sudo'] == use_sudo
- assert self.mock.commands == commands
- self.mock.settings_merged = {}
- self.mock.commands = []
- test(use_sudo=False)
- test(use_sudo=True)
-
- def test_failed_command(self):
- with pytest.raises(ProcessException) as exc_ctx:
- self._run(commands=['fail'])
- exception = exc_ctx.value
- assert exception.stdout == self.MockCommandResult.stdout
- assert exception.stderr == self.MockCommandResult.stderr
- assert exception.command == self.MockCommandResult.command
- assert exception.exit_code == self.MockCommandResult.return_code
-
- class MockCommandResult(object):
- stdout = 'mock_stdout'
- stderr = 'mock_stderr'
- command = 'mock_command'
- return_code = 1
-
- def __init__(self, failed):
- self.failed = failed
-
- class MockFabricApi(object):
-
- def __init__(self):
- self.commands = []
- self.settings_merged = {}
-
- @contextlib.contextmanager
- def settings(self, *args, **kwargs):
- self.settings_merged.update(kwargs)
- if args:
- groups = args[0]
- self.settings_merged.update({'hide_output': groups})
- yield
-
- def run(self, command):
- self.commands.append(command)
- self.settings_merged['use_sudo'] = False
- return TestFabricEnvHideGroupsAndRunCommands.MockCommandResult(command == 'fail')
-
- def sudo(self, command):
- self.commands.append(command)
- self.settings_merged['use_sudo'] = True
- return TestFabricEnvHideGroupsAndRunCommands.MockCommandResult(command == 'fail')
-
- def hide(self, *groups):
- return groups
-
- def exists(self, *args, **kwargs):
- raise RuntimeError
-
- class _Ctx(object):
- INSTRUMENTATION_FIELDS = ()
-
- class Task(object):
- @staticmethod
- def abort(message=None):
- models.Task.abort(message)
- actor = None
-
- class Actor(object):
- host = None
-
- class Model(object):
- @contextlib.contextmanager
- def instrument(self, *args, **kwargs):
- yield
- task = Task
- task.actor = Actor
- model = Model()
- logger = logging.getLogger()
-
- @staticmethod
- @contextlib.contextmanager
- def _mock_self_logging(*args, **kwargs):
- yield
- _Ctx.logging_handlers = _mock_self_logging
-
- @pytest.fixture(autouse=True)
- def _setup(self, mocker):
- self.default_fabric_env = {
- 'host_string': 'test',
- 'user': 'test',
- 'key_filename': 'test',
- }
- self.mock = self.MockFabricApi()
- mocker.patch('fabric.api', self.mock)
-
- def _run(self,
- commands=(),
- fabric_env=None,
- process=None,
- use_sudo=False,
- hide_output=None):
- operations.run_commands_with_ssh(
- ctx=self._Ctx,
- commands=commands,
- process=process,
- fabric_env=fabric_env or self.default_fabric_env,
- use_sudo=use_sudo,
- hide_output=hide_output)
-
-
-class TestUtilityFunctions(object):
-
- def test_paths(self):
- base_dir = '/path'
- local_script_path = '/local/script/path.py'
- paths = ssh_operations._Paths(base_dir=base_dir,
- local_script_path=local_script_path)
- assert paths.local_script_path == local_script_path
- assert paths.remote_ctx_dir == base_dir
- assert paths.base_script_path == 'path.py'
- assert paths.remote_ctx_path == '/path/ctx'
- assert paths.remote_scripts_dir == '/path/scripts'
- assert paths.remote_work_dir == '/path/work'
- assert paths.remote_env_script_path.startswith('/path/scripts/env-path.py-')
- assert paths.remote_script_path.startswith('/path/scripts/path.py-')
-
- def test_write_environment_script_file(self):
- base_dir = '/path'
- local_script_path = '/local/script/path.py'
- paths = ssh_operations._Paths(base_dir=base_dir,
- local_script_path=local_script_path)
- env = {'one': "'1'"}
- local_socket_url = 'local_socket_url'
- remote_socket_url = 'remote_socket_url'
- env_script_lines = set([l for l in ssh_operations._write_environment_script_file(
- process={'env': env},
- paths=paths,
- local_socket_url=local_socket_url,
- remote_socket_url=remote_socket_url
- ).getvalue().split('\n') if l])
- expected_env_script_lines = set([
- 'export PATH=/path:$PATH',
- 'export PYTHONPATH=/path:$PYTHONPATH',
- 'chmod +x /path/ctx',
- 'chmod +x {0}'.format(paths.remote_script_path),
- 'export CTX_SOCKET_URL={0}'.format(remote_socket_url),
- 'export LOCAL_CTX_SOCKET_URL={0}'.format(local_socket_url),
- 'export one=\'1\''
- ])
- assert env_script_lines == expected_env_script_lines
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0cf1deaf/tests/extensions/aria_extension_tosca/conftest.py
----------------------------------------------------------------------
diff --git a/tests/extensions/aria_extension_tosca/conftest.py b/tests/extensions/aria_extension_tosca/conftest.py
index a2020b7..fdb2d4c 100644
--- a/tests/extensions/aria_extension_tosca/conftest.py
+++ b/tests/extensions/aria_extension_tosca/conftest.py
@@ -17,6 +17,9 @@
PyTest configuration module.
Add support for a "--tosca-parser" CLI option.
+
+For more information on PyTest hooks, see the `PyTest documentation
+<https://docs.pytest.org/en/latest/writing_plugins.html#pytest-hook-reference>`__.
"""
import pytest
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0cf1deaf/tests/mechanisms/parsing/__init__.py
----------------------------------------------------------------------
diff --git a/tests/mechanisms/parsing/__init__.py b/tests/mechanisms/parsing/__init__.py
index 2a860dc..a50abc4 100644
--- a/tests/mechanisms/parsing/__init__.py
+++ b/tests/mechanisms/parsing/__init__.py
@@ -27,7 +27,10 @@ class Parsed(object):
self.verbose = False
def assert_success(self):
- __tracebackhide__ = True # pylint: disable=unused-variable
+ # See: https://docs.pytest.org/en/latest/example/simple.html
+ # #writing-well-integrated-assertion-helpers
+ __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)))
@@ -37,7 +40,10 @@ class Parsed(object):
print self.text.strip()
def assert_failure(self):
- __tracebackhide__ = True # pylint: disable=unused-variable
+ # See: https://docs.pytest.org/en/latest/example/simple.html
+ # #writing-well-integrated-assertion-helpers
+ __tracebackhide__ = True # pylint: disable=unused-variable
+
if len(self.issues) > 0:
if self.verbose:
print LINE_BREAK
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0cf1deaf/tests/mechanisms/utils.py
----------------------------------------------------------------------
diff --git a/tests/mechanisms/utils.py b/tests/mechanisms/utils.py
index 3475206..45a442c 100644
--- a/tests/mechanisms/utils.py
+++ b/tests/mechanisms/utils.py
@@ -24,10 +24,13 @@ def matrix(*iterables, **kwargs):
with the added ability to "flatten" each value by breaking up tuples and recombining them into a
final flat value.
- To do such recombination, use the ``counts`` argument (tuple) to specify the number of elements
- per value in order. Any count greater than 1 (the default) enables recombination of that value.
+ To do such recombination, use the ``counts`` argument (tuple of integers) to specify the number
+ of elements per value in each iterable in order. Any count greater than 1 (the default) enables
+ recombination of the iterable's values. So, if you are combining three different iterables, then
+ you want ``counts`` to be a tuple of three integers. The first integer in the ``counts`` tuple
+ will be the number of elements in the values of the first iterable, etc.
- Example::
+ Detailed example::
x = ('hello', 'goodbye')
y = ('Linus', 'Richard')
@@ -38,12 +41,18 @@ def matrix(*iterables, **kwargs):
('goodbye', 'Richard')
y = (('Linus', 'Torvalds'), ('Richard', 'Stallman'))
+
+ # Without flattening:
+
matrix(x, y) ->
('hello', ('Linus', 'Torvalds')),
('hello', ('Richard', 'Stallman')),
('goodbye', ('Linus', 'Torvalds')),
('goodbye', ('Richard', 'Stallman'))
+ # The values in our second iterable, y, have two elements that we want to flatten, so we will
+ # set the second "count" value to 2:
+
matrix(x, y, counts=(1, 2)) ->
('hello', 'Linus', 'Torvalds'),
('hello', 'Richard', 'Stallman'),