Robot Framework Integrated Development Environment (RIDE)
resultbuilder.py
Go to the documentation of this file.
1 # Copyright 2008-2015 Nokia Networks
2 # Copyright 2016- Robot Framework Foundation
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 
16 from robotide.lib.robot.errors import DataError
17 from robotide.lib.robot.model import SuiteVisitor
18 from robotide.lib.robot.utils import ET, ETSource, get_error_message, unic
19 
20 from .executionresult import Result, CombinedResult
21 from .flattenkeywordmatcher import (FlattenByNameMatcher, FlattenByTypeMatcher,
22  FlattenByTagMatcher)
23 from .merger import Merger
24 from .xmlelementhandlers import XmlElementHandler
25 
26 
27 
43 def ExecutionResult(*sources, **options):
44  if not sources:
45  raise DataError('One or more data source needed.')
46  if options.pop('merge', False):
47  return _merge_results(sources[0], sources[1:], options)
48  if len(sources) > 1:
49  return _combine_results(sources, options)
50  return _single_result(sources[0], options)
51 
52 
53 def _merge_results(original, merged, options):
54  result = ExecutionResult(original, **options)
55  merger = Merger(result)
56  for path in merged:
57  merged = ExecutionResult(path, **options)
58  merger.merge(merged)
59  return result
60 
61 
62 def _combine_results(sources, options):
63  return CombinedResult(ExecutionResult(src, **options) for src in sources)
64 
65 
66 def _single_result(source, options):
67  ets = ETSource(source)
68  result = Result(source, rpa=options.pop('rpa', None))
69  try:
70  return ExecutionResultBuilder(ets, **options).build(result)
71  except IOError as err:
72  error = err.strerror
73  except:
74  error = get_error_message()
75  raise DataError("Reading XML source '%s' failed: %s" % (unic(ets), error))
76 
77 
78 
84 
85 
95  def __init__(self, source, include_keywords=True, flattened_keywords=None):
96  self._source_source = source \
97  if isinstance(source, ETSource) else ETSource(source)
98  self._include_keywords_include_keywords = include_keywords
99  self._flattened_keywords_flattened_keywords = flattened_keywords
100 
101  def build(self, result):
102  # Parsing is performance optimized. Do not change without profiling!
103  handler = XmlElementHandler(result)
104  with self._source_source as source:
105  self._parse_parse(source, handler.start, handler.end)
106  result.handle_suite_teardown_failures()
107  if not self._include_keywords_include_keywords:
108  result.suite.visit(RemoveKeywords())
109  return result
110 
111  def _parse(self, source, start, end):
112  context = ET.iterparse(source, events=('start', 'end'))
113  if not self._include_keywords_include_keywords:
114  context = self._omit_keywords_omit_keywords(context)
115  elif self._flattened_keywords_flattened_keywords:
116  context = self._flatten_keywords_flatten_keywords(context, self._flattened_keywords_flattened_keywords)
117  for event, elem in context:
118  if event == 'start':
119  start(elem)
120  else:
121  end(elem)
122  elem.clear()
123 
124  def _omit_keywords(self, context):
125  omitted_kws = 0
126  for event, elem in context:
127  # Teardowns aren't omitted to allow checking suite teardown status.
128  omit = elem.tag == 'kw' and elem.get('type') != 'teardown'
129  start = event == 'start'
130  if omit and start:
131  omitted_kws += 1
132  if not omitted_kws:
133  yield event, elem
134  elif not start:
135  elem.clear()
136  if omit and not start:
137  omitted_kws -= 1
138 
139  def _flatten_keywords(self, context, flattened):
140  # Performance optimized. Do not change without profiling!
141  name_match, by_name = self._get_matcher_get_matcher(FlattenByNameMatcher, flattened)
142  type_match, by_type = self._get_matcher_get_matcher(FlattenByTypeMatcher, flattened)
143  tags_match, by_tags = self._get_matcher_get_matcher(FlattenByTagMatcher, flattened)
144  started = -1 # if 0 or more, we are flattening
145  tags = []
146  inside_kw = 0 # to make sure we don't read tags from a test
147  seen_doc = False
148  for event, elem in context:
149  tag = elem.tag
150  start = event == 'start'
151  end = not start
152  if start and tag == 'kw':
153  inside_kw += 1
154  if started >= 0:
155  started += 1
156  elif by_name and name_match(elem.get('name', ''), elem.get('library')):
157  started = 0
158  seen_doc = False
159  elif by_type and type_match(elem.get('type', 'kw')):
160  started = 0
161  seen_doc = False
162  elif started < 0 and by_tags and inside_kw:
163  if end and tag == 'tag':
164  tags.append(elem.text or '')
165  elif end and tag == 'tags':
166  if tags_match(tags):
167  started = 0
168  seen_doc = False
169  tags = []
170  if end and tag == 'kw':
171  inside_kw -= 1
172  if started == 0 and not seen_doc:
173  doc = ET.Element('doc')
174  doc.text = '_*Keyword content flattened.*_'
175  yield 'start', doc
176  yield 'end', doc
177  if started == 0 and end and tag == 'doc':
178  seen_doc = True
179  elem.text = ('%s\n\n_*Keyword content flattened.*_'
180  % (elem.text or '')).strip()
181  if started <= 0 or tag == 'msg':
182  yield event, elem
183  else:
184  elem.clear()
185  if started >= 0 and end and tag == 'kw':
186  started -= 1
187 
188  def _get_matcher(self, matcher_class, flattened):
189  matcher = matcher_class(flattened)
190  return matcher.match, bool(matcher)
191 
192 
194 
195  def start_suite(self, suite):
196  suite.keywords = []
197 
198  def visit_test(self, test):
199  test.keywords = []
Used when variable does not exist.
Definition: errors.py:67
Interface to ease traversing through a test suite structure.
Definition: visitor.py:75
Combined results of multiple test executions.
Builds :class:~.executionresult.Result objects based on output files.
def __init__(self, source, include_keywords=True, flattened_keywords=None)
:param source: Path to the XML output file to build :class:~.executionresult.Result objects from.
def visit_test(self, test)
Implements traversing through the test and its keywords.
def start_suite(self, suite)
Called when suite starts.
def _merge_results(original, merged, options)
def ExecutionResult(*sources, **options)
Factory method to constructs :class:~.executionresult.Result objects.
def _combine_results(sources, options)
def get_error_message()
Returns error message of the last occurred exception.
Definition: error.py:41