diff --git a/Makefile b/Makefile index 98bf6b731..61c977a17 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ all: check_dependencies unit functional doctest filename=lettuce-`python -c 'import lettuce;print lettuce.version'`.tar.gz export PYTHONPATH:= ${PWD} -export LETTUCE_DEPENDENCIES:= nose mox sphinx lxml django fuzzywuzzy mock +export LETTUCE_DEPENDENCIES:= nose mox sphinx lxml django fuzzywuzzy mock python-subunit sure check_dependencies: @echo "Checking for dependencies to run tests ..." diff --git a/lettuce/core.py b/lettuce/core.py index c87e5636c..4fb36c645 100644 --- a/lettuce/core.py +++ b/lettuce/core.py @@ -647,30 +647,39 @@ def matches_tags(self, tags): if tags is None: return True - has_exclusionary_tags = any([t.startswith('-') for t in tags]) + exclusionary_tags = [t[1:] for t in tags if t.startswith('-') and not t.startswith('-~')] + inclusionary_tags = [t for t in tags if not t.startswith('-') and not t.startswith('~')] - if not self.tags and not has_exclusionary_tags: - return False + if not isinstance(self.tags, list): + self.tags = [] - matched = [] + # If there are inclusionary tags, at least one must match + if len(inclusionary_tags) > 0: + matches = set(self.tags).intersection(inclusionary_tags) + if len(matches) == 0: + # This scenario did not match any of the inclusionary + # tags, and it must be excluded + return False + + # If there are exclusionary tags, if any match the scenario must + # be thrown out + if len(exclusionary_tags) > 0: + matches = set(self.tags).intersection(exclusionary_tags) + if len(matches) > 0: + # At least one exclusionary tag matches, omit the + # scenario + return False - if isinstance(self.tags, list): - for tag in self.tags: - if tag in tags: - return True - else: - self.tags = [] + matched = [] for tag in tags: exclude = tag.startswith('-') if exclude: tag = tag[1:] - fuzzable = tag.startswith('~') if fuzzable: tag = tag[1:] - result = tag in self.tags if fuzzable: fuzzed = [] for internal_tag in self.tags: @@ -679,14 +688,20 @@ def matches_tags(self, tags): fuzzed.append(ratio <= 80) else: fuzzed.append(ratio > 80) - result = any(fuzzed) + matched.append(result) elif exclude: result = tag not in self.tags + matched.append(result) - matched.append(result) + # Determine if any tags may optionally be included. + # If so, all must fail for the scenario to be omitted + if not all(matched): + return False - return all(matched) + # If the check make it here, there is no reason to + # disclude the test + return True @property def evaluated(self): diff --git a/tests/unit/test_scenario_parsing.py b/tests/unit/test_scenario_parsing.py index 8dec6d777..06c2ee28b 100644 --- a/tests/unit/test_scenario_parsing.py +++ b/tests/unit/test_scenario_parsing.py @@ -527,6 +527,18 @@ def test_scenario_matches_tags_excluding_when_scenario_has_no_tags(): assert scenario.matches_tags(['-nope', '-neither']) +def test_scenario_matches_tags_both_include_and_exclude_tags(): + ("When Scenario#matches_tags is called for a scenario " + "that has an inclusionary and exclusionary tag that matches, " + "the scenario is excluded") + + scenario = Scenario.from_string( + SCENARIO1, + original_string=SCENARIO1.strip(), + tags=['tag1', 'tag2']) + + assert not scenario.matches_tags(['tag1', '-tag2']) + def test_scenario_matches_tags_excluding_fuzzywuzzy(): ("When Scenario#matches_tags is called with a member starting with -~ "