summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Maw <richard.maw@codethink.co.uk>2014-02-05 15:37:01 +0000
committerRichard Maw <richard.maw@codethink.co.uk>2014-02-06 15:14:46 +0000
commit87691992e1e880eec49fc2847cdca6529e0e4e7c (patch)
treeef1e26372b003550552145c43c10992673dc785c
parentae271f8b5030c6993971d0a5dc008bc1747987a9 (diff)
downloadcmdtest-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-xyarn56
-rw-r--r--yarnlib/__init__.py5
-rw-r--r--yarnlib/scenario_step_connector.py111
3 files changed, 125 insertions, 47 deletions
diff --git a/yarn b/yarn
index d01b2af..12e01dd 100755
--- a/yarn
+++ b/yarn
@@ -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: