diff options
author | Richard Maw <richard.maw@codethink.co.uk> | 2014-02-05 15:37:01 +0000 |
---|---|---|
committer | Richard Maw <richard.maw@codethink.co.uk> | 2014-02-06 15:14:46 +0000 |
commit | 87691992e1e880eec49fc2847cdca6529e0e4e7c (patch) | |
tree | ef1e26372b003550552145c43c10992673dc785c | |
parent | ae271f8b5030c6993971d0a5dc008bc1747987a9 (diff) | |
download | cmdtest-87691992e1e880eec49fc2847cdca6529e0e4e7c.tar.gz |
Factor scenario step and implementation connector into yarnlib
Rather than passing allow-missing-steps in, `missing_step_cb` can be
passed to the ScenarioStepConnector, which if it returns true, will
exclude that scenario, rather than raising an exception.
This was chosen, as it also works as a status update callback.
-rwxr-xr-x | yarn | 56 | ||||
-rw-r--r-- | yarnlib/__init__.py | 5 | ||||
-rw-r--r-- | yarnlib/scenario_step_connector.py | 111 |
3 files changed, 125 insertions, 47 deletions
@@ -178,44 +178,18 @@ class YarnRunner(cliapp.Application): return block_parser.scenarios, block_parser.implementations def connect_implementations(self, scenarios, implementations): - new_list = [] - for scenario in scenarios: - missing_step = False - for step in scenario.steps: - self.connect_implementation( - scenario, step, implementations) - if step.implementation is None: - missing_step = True - if not missing_step: - new_list.append(scenario) - return new_list - - def connect_implementation(self, scenario, step, implementations): - matching = [i for i in implementations - if step.what == i.what and - self.implements_matches_step(i, step)] - - if len(matching) == 0: - if self.settings['allow-missing-steps']: + if self.settings['allow-missing-steps']: + def warn_missing(scenario, step): self.warning( 'Scenario %s has missing step %s %s' % (scenario.name, step.what, step.text)) - return - raise cliapp.AppException( - 'Scenario "%s", step "%s %s" has no matching ' - 'implementation' % - (scenario.name, step.what, step.text)) - if len(matching) > 1: - s = '\n'.join( - 'IMPLEMENTS %s %s' % (i.what, i.regexp) - for i in matching) - raise cliapp.AppException( - 'Scenario "%s", step "%s %s" has more than one ' - 'matching implementations:\n%s' % - (scenario.name, step.what, step.text, s)) + return True + step_connector = yarnlib.ScenarioStepConnector( + implementations, missing_step_cb=warn_missing) + else: + step_connector = yarnlib.ScenarioStepConnector(implementations) - assert step.implementation is None - step.implementation = matching[0] + return step_connector.connect_implementations(scenarios) def load_shell_libraries(self): if not self.settings['shell-library']: @@ -366,7 +340,7 @@ class YarnRunner(cliapp.Application): self.ts['step_name'] = '%s %s' % (step.what, step.text) self.steps_run += 1 - m = self.implements_matches_step(step.implementation, step) + m = yarnlib.implements_matches_step(step.implementation, step) assert m is not None env = self.clean_env() env['DATADIR'] = datadir @@ -435,18 +409,6 @@ class YarnRunner(cliapp.Application): nice = ''.join(nice) return nice - def implements_matches_step(self, implements, step): - '''Return re.Match if implements matches the step. - - Otherwise, return None. - - ''' - - m = re.match(implements.regexp, step.text, re.I) - if m and m.end() != len(step.text): - return None - return m - def indent(self, s): return ''.join(' %s\n' % line for line in s.splitlines()) diff --git a/yarnlib/__init__.py b/yarnlib/__init__.py index 33cdc46..0d36668 100644 --- a/yarnlib/__init__.py +++ b/yarnlib/__init__.py @@ -21,5 +21,10 @@ from elements import Scenario, ScenarioStep, Implementation from block_parser import BlockParser, BlockError from scenario_validator import (NoScenariosError, DuplicateScenariosError, NoThensError, ScenarioValidator) +from scenario_step_connector import (implements_matches_step, + ScenarioStepConnector, + StepNotImplementedError, + StepMultipleImplementationsError) + import shell_libraries diff --git a/yarnlib/scenario_step_connector.py b/yarnlib/scenario_step_connector.py new file mode 100644 index 0000000..e0a29f0 --- /dev/null +++ b/yarnlib/scenario_step_connector.py @@ -0,0 +1,111 @@ +# Copyright 2014 Codethink Limited +# +# 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 <http://www.gnu.org/licenses/>. +# +# =*= License: GPL-3+ =*= + + +import cliapp +import re + + +def implements_matches_step(implements, step): + '''Return re.Match if implements matches the step. + + Otherwise, return None. + + ''' + + m = re.match(implements.regexp, step.text, re.I) + if m and m.end() != len(step.text): + return None + return m + + +class StepNotImplementedError(cliapp.AppException): + + def __init__(self, scenario, step): + cliapp.AppException.__init__( + self, 'Scenario "%s", step "%s %s" has no matching ' + 'implementation' % + (scenario.name, step.what, step.text)) + + +class StepMultipleImplementationsError(cliapp.AppException): + + def __init__(self, scenario, step, matching): + s = '\n'.join( + 'IMPLEMENTS %s %s' % (i.what, i.regexp) + for i in matching) + cliapp.AppException.__init__( + self, 'Scenario "%s", step "%s %s" has more than one ' + 'matching implementations:\n%s' % + (scenario.name, step.what, step.text, s)) + + +class ScenarioStepConnector(object): + '''Connect scenario steps to their implementations.''' + + def __init__(self, implementations, missing_step_cb=lambda sc, st: None): + self.implementations = implementations + self.missing_step_cb = missing_step_cb + + def connect_implementations(self, scenarios): + '''Connect scenario steps, returning implemented scenarios. + + For each scenario given, connect known implementations to + the steps. + + This returns only scenarios that were fully implemented. + Unless ``missing_step_cb`` is provided and returns true for all + missing steps, an exception is raised instead. + + ''' + implemented_scenarios = [] + for scenario in scenarios: + missing_step = False + for step in scenario.steps: + self.connect_implementation(scenario, step) + if step.implementation is None: + missing_step = True + if not missing_step: + implemented_scenarios.append(scenario) + return implemented_scenarios + + def connect_implementation(self, scenario, step): + '''Connect the step of a scenario to its implementation. + + Always raises an exception if there are multiple matching + implementations. + + Raises an exception if there are no matching implementations + unless ``missing_step_cb`` returns true. + + ''' + matching = [i for i in self.implementations + if step.what == i.what and + implements_matches_step(i, step)] + + if len(matching) == 0: + if self.missing_step_cb(scenario, step): + return + raise StepNotImplementedError(scenario, step) + if len(matching) > 1: + raise StepMultipleImplementationsError(scenario, step, matching) + + assert step.implementation is None + step.implementation = matching[0] + + +# vim: set ts=4 sw=4 et: |