Skip to content

Commit

Permalink
#455: Restore backward compatibility to Cucumber style RegexMatcher (…
Browse files Browse the repository at this point in the history
…due to #280, #282)
  • Loading branch information
jenisys committed Oct 1, 2016
1 parent 2fe72f4 commit 95a0cdf
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ CHANGES:
FIXED:

* pull #476: scenario.status when scenario without steps is skipped (provided by: ar45, jenisys)
* issue #455: Restore backward compatibility to Cucumber style RegexMatcher (submitted by: avabramov)
* issue #416: JUnit report messages cut off (submitted by: remcowesterhoud, provided by: bittner)
* issue #414: Support for Jython 2.7 (submitted by: gabtwi...)
* issue #384: Active Tags fail with ScenarioOutline (submitted by: BRevzin)
Expand Down
43 changes: 37 additions & 6 deletions behave/matchers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

from __future__ import absolute_import, print_function, with_statement
import copy
import os.path
import re
import parse
import six
Expand Down Expand Up @@ -279,10 +278,7 @@ def step_impl(context, amount):
class RegexMatcher(Matcher):
def __init__(self, func, string, step_type=None):
super(RegexMatcher, self).__init__(func, string, step_type)
assert not (string.startswith("^") or string.endswith("$")), \
"Regular expression should not use begin/end-markers: "+ string
expression = "^%s$" % self.string
self.regex = re.compile(expression)
self.regex = re.compile(self.string)

def check_match(self, step):
m = self.regex.match(step)
Expand All @@ -299,11 +295,46 @@ def check_match(self, step):

return args

class SimplifiedRegexMatcher(RegexMatcher):
"""Simplified regular expression step-matcher that automatically adds
start-of-line/end-of-line matcher symbols to string:
.. code-block:: python
@when(u'a step passes') # re.pattern = "^a step passes$"
def step_impl(context): pass
"""

def __init__(self, func, string, step_type=None):
assert not (string.startswith("^") or string.endswith("$")), \
"Regular expression should not use begin/end-markers: "+ string
expression = "^%s$" % string
super(SimplifiedRegexMatcher, self).__init__(func, expression, step_type)
self.string = string


class CucumberRegexMatcher(RegexMatcher):
"""Compatible to (old) Cucumber style regular expressions.
Text must contain start-of-line/end-of-line matcher symbols to string:
.. code-block:: python
@when(u'^a step passes$') # re.pattern = "^a step passes$"
def step_impl(context): pass
"""

matcher_mapping = {
"parse": ParseMatcher,
"cfparse": CFParseMatcher,
"re": RegexMatcher,
"re": SimplifiedRegexMatcher,

# -- BACKWARD-COMPATIBLE REGEX MATCHER: Old Cucumber compatible style.
# To make it the default step-matcher use the following snippet:
# # -- FILE: features/environment.py
# from behave import use_step_matcher
# def before_all(context):
# use_step_matcher("re0")
"re0": CucumberRegexMatcher,
}
current_matcher = ParseMatcher # pylint: disable=invalid-name

Expand Down
55 changes: 49 additions & 6 deletions test/test_matchers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
from mock import Mock, patch
from nose.tools import * # pylint: disable=wildcard-import, unused-wildcard-import
import parse
from behave.matchers import Match, Matcher, ParseMatcher, RegexMatcher
from behave.matchers import Match, Matcher, ParseMatcher, RegexMatcher, \
SimplifiedRegexMatcher, CucumberRegexMatcher
from behave import matchers, runner


Expand Down Expand Up @@ -105,15 +106,18 @@ def test_positional_arguments(self):

class TestRegexMatcher(object):
# pylint: disable=invalid-name, no-self-use
MATCHER_CLASS = RegexMatcher

def test_returns_none_if_regex_does_not_match(self):
RegexMatcher = self.MATCHER_CLASS
matcher = RegexMatcher(None, 'a string')
regex = Mock()
regex.match.return_value = None
matcher.regex = regex
assert matcher.match('just a random step') is None

def test_returns_arguments_based_on_groups(self):
RegexMatcher = self.MATCHER_CLASS
func = lambda x: -x
matcher = RegexMatcher(func, 'foo')

Expand Down Expand Up @@ -149,14 +153,19 @@ def test_returns_arguments_based_on_groups(self):
have = [(a.start, a.end, a.original, a.value, a.name) for a in args]
eq_(have, expected)



class TestSimplifiedRegexMatcher(TestRegexMatcher):
MATCHER_CLASS = SimplifiedRegexMatcher

def test_steps_with_same_prefix_are_not_ordering_sensitive(self):
# -- RELATED-TO: issue #280
# pylint: disable=unused-argument
def step_func1(context): pass # pylint: disable=multiple-statements
def step_func2(context): pass # pylint: disable=multiple-statements
# pylint: enable=unused-argument
matcher1 = RegexMatcher(step_func1, "I do something")
matcher2 = RegexMatcher(step_func2, "I do something more")
matcher1 = SimplifiedRegexMatcher(step_func1, "I do something")
matcher2 = SimplifiedRegexMatcher(step_func2, "I do something more")

# -- CHECK: ORDERING SENSITIVITY
matched1 = matcher1.match(matcher2.string)
Expand All @@ -172,15 +181,49 @@ def step_func2(context): pass # pylint: disable=multiple-statements

@raises(AssertionError)
def test_step_should_not_use_regex_begin_marker(self):
RegexMatcher(None, "^I do something")
SimplifiedRegexMatcher(None, "^I do something")

@raises(AssertionError)
def test_step_should_not_use_regex_end_marker(self):
RegexMatcher(None, "I do something$")
SimplifiedRegexMatcher(None, "I do something$")

@raises(AssertionError)
def test_step_should_not_use_regex_begin_and_end_marker(self):
RegexMatcher(None, "^I do something$")
SimplifiedRegexMatcher(None, "^I do something$")


class TestCucumberRegexMatcher(TestRegexMatcher):
MATCHER_CLASS = CucumberRegexMatcher

def test_steps_with_same_prefix_are_not_ordering_sensitive(self):
# -- RELATED-TO: issue #280
# pylint: disable=unused-argument
def step_func1(context): pass # pylint: disable=multiple-statements
def step_func2(context): pass # pylint: disable=multiple-statements
# pylint: enable=unused-argument
matcher1 = CucumberRegexMatcher(step_func1, "^I do something$")
matcher2 = CucumberRegexMatcher(step_func2, "^I do something more$")

# -- CHECK: ORDERING SENSITIVITY
matched1 = matcher1.match(matcher2.string[1:-1])
matched2 = matcher2.match(matcher1.string[1:-1])
assert matched1 is None
assert matched2 is None

# -- CHECK: Can match itself (if step text is simple)
matched1 = matcher1.match(matcher1.string[1:-1])
matched2 = matcher2.match(matcher2.string[1:-1])
assert isinstance(matched1, Match)
assert isinstance(matched2, Match)

def test_step_should_use_regex_begin_marker(self):
CucumberRegexMatcher(None, "^I do something")

def test_step_should_use_regex_end_marker(self):
CucumberRegexMatcher(None, "I do something$")

def test_step_should_use_regex_begin_and_end_marker(self):
CucumberRegexMatcher(None, "^I do something$")


def test_step_matcher_current_matcher():
Expand Down

0 comments on commit 95a0cdf

Please sign in to comment.