Skip to content

Commit

Permalink
Extract tags for each scenario by the Feature.
Browse files Browse the repository at this point in the history
The previous option (using a regex search in the whole
 file to look for tags in each new scenario) was quite
 slow.

This commit is especially interesting for projects with
 feature file of more than 20K.

This commit closes the issue gabrielfalcao#369.
  • Loading branch information
hectord committed Aug 11, 2013
1 parent 3f25deb commit 889b0ba
Showing 1 changed file with 22 additions and 55 deletions.
77 changes: 22 additions & 55 deletions lettuce/core.py
Expand Up @@ -45,7 +45,8 @@ class REP(object):
within_double_quotes = re.compile(r'("[^"]+")')
within_single_quotes = re.compile(r"('[^']+')")
only_whitespace = re.compile('^\s*$')
tag_extraction_regex = re.compile(r'(?:(?:^|\s+)[@]([^@\s]+))')
last_tag_extraction_regex = re.compile(ur'(?:\s|^)[@](\S+)\s*$')
first_tag_extraction_regex = re.compile(ur'^\s*[@](\S+)(?:\s|$)')
tag_strip_regex = re.compile(ur'(?:(?:^\s*|\s+)[@]\S+\s*)+$', re.DOTALL)
comment_strip1 = re.compile(ur'(^[^\'"]*)[#]([^\'"]*)$')
comment_strip2 = re.compile(ur'(^[^\'"]+)[#](.*)$')
Expand Down Expand Up @@ -542,14 +543,15 @@ def __init__(self, name, remaining_lines, keys, outlines,
with_file=None,
original_string=None,
language=None,
previous_scenario=None):
tags=None):

self.feature = None
if not language:
language = language()

self.name = name
self.language = language
self.tags = tags
self.remaining_lines = remaining_lines
self.steps = self._parse_remaining_lines(remaining_lines,
with_file,
Expand All @@ -559,8 +561,6 @@ def __init__(self, name, remaining_lines, keys, outlines,
self.with_file = with_file
self.original_string = original_string

self.previous_scenario = previous_scenario

if with_file and original_string:
scenario_definition = ScenarioDescription(self, with_file,
original_string,
Expand All @@ -571,11 +571,6 @@ def __init__(self, name, remaining_lines, keys, outlines,
self.steps, self.outlines, with_file, original_string))
self._add_myself_to_steps()

if original_string and '@' in self.original_string:
self.tags = self._find_tags_in(original_string)
else:
self.tags = []

@property
def max_length(self):
if self.outlines:
Expand Down Expand Up @@ -740,40 +735,6 @@ def _add_myself_to_steps(self):
for step in self.solved_steps:
step.scenario = self

def _find_tags_in(self, original_string):
broad_regex = re.compile(ur"([@].*)%s: (%s)" % (
self.language.scenario_separator,
re.escape(self.name)), re.DOTALL)

regexes = []
if not self.previous_scenario:
regexes.append(broad_regex)

else:
regexes.append(re.compile(ur"(?:%s: %s.*)([@]?.*)%s: (%s)\s*\n" % (
self.language.non_capturable_scenario_separator,
re.escape(self.previous_scenario.name),
self.language.scenario_separator,
re.escape(self.name)), re.DOTALL))

def try_finding_with(regex):
found = regex.search(original_string)

if found:
tag_lines = found.group().splitlines()
tags = list(chain(*map(self._extract_tag, tag_lines)))
return tags

for regex in regexes:
found = try_finding_with(regex)
if found:
return found

return []

def _extract_tag(self, item):
return REP.tag_extraction_regex.findall(item)

def _resolve_steps(self, steps, outlines, with_file, original_string):
for outline in outlines:
for step in steps:
Expand Down Expand Up @@ -831,7 +792,7 @@ def from_string(new_scenario, string,
with_file=None,
original_string=None,
language=None,
previous_scenario=None):
tags=None):
""" Creates a new scenario from string"""
# ignoring comments
string = "\n".join(strings.get_stripped_lines(string, ignore_lines_starting_with='#'))
Expand Down Expand Up @@ -865,7 +826,7 @@ def from_string(new_scenario, string,
with_file=with_file,
original_string=original_string,
language=language,
previous_scenario=previous_scenario,
tags=tags,
)

return scenario
Expand Down Expand Up @@ -1011,7 +972,7 @@ def try_finding_with(regex):

if found:
tag_lines = found.group().splitlines()
tags = set(chain(*map(self._extract_tag, tag_lines)))
tags = list(chain(*map(self._extract_tag, tag_lines)))
return tags

for regex in regexes:
Expand Down Expand Up @@ -1094,6 +1055,15 @@ def from_file(new_feature, filename):

def _set_definition(self, definition):
self.described_at = definition

def _extract_tags(self, string, extract_regex=REP.last_tag_extraction_regex):
tags = []
while True:
m = extract_regex.search(string)
if not m:
return tags, string
tags.insert(0, m.groups()[0])
string = extract_regex.sub('', string)

def _strip_next_scenario_tags(self, string):
stripped = REP.tag_strip_regex.sub('', string)
Expand Down Expand Up @@ -1143,6 +1113,7 @@ def _parse_remaining_lines(self, lines, original_string, with_file=None):

description = u""
background = None
tags_scenario = []

if not re.search("^" + scenario_prefix, joined):
if not parts:
Expand All @@ -1151,8 +1122,8 @@ def _parse_remaining_lines(self, lines, original_string, with_file=None):
(u"Features must have scenarios.\n"
"Please refer to the documentation available at http://lettuce.it for more information.")
)

description, background_lines = self._extract_desc_and_bg(parts[0])
tags_scenario, description_and_background = self._extract_tags(parts[0])
description, background_lines = self._extract_desc_and_bg(description_and_background)

background = background_lines and Background.from_string(
background_lines,
Expand All @@ -1176,22 +1147,18 @@ def _parse_remaining_lines(self, lines, original_string, with_file=None):

scenarios = []
while upcoming_scenarios:
tags_next_scenario, current = self._extract_tags(upcoming_scenarios[0])
current = self._strip_next_scenario_tags(upcoming_scenarios.pop(0))

previous_scenario = None
has_previous = len(scenarios) > 0

if has_previous:
previous_scenario = scenarios[-1]

params = dict(
previous_scenario=previous_scenario,
tags=tags_scenario,
)

params.update(kw)
current_scenario = Scenario.from_string(current, **params)
current_scenario.background = background
scenarios.append(current_scenario)
tags_scenario = tags_next_scenario

return background, scenarios, description

Expand Down

0 comments on commit 889b0ba

Please sign in to comment.