Robot Framework
merger.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 robot.errors import DataError
17 from robot.model import SuiteVisitor
18 from robot.utils import html_escape, test_or_task
19 
20 
22 
23  def __init__(self, result, rpa=False):
24  self.resultresult = result
25  self.currentcurrent = None
26  self.rparpa = rpa
27 
28  def merge(self, merged):
29  self.resultresult.set_execution_mode(merged)
30  merged.suite.visit(self)
31  self.resultresult.errors.add(merged.errors)
32 
33  def start_suite(self, suite):
34  if self.currentcurrent is None:
35  old = self._find_root_find_root(suite.name)
36  else:
37  old = self._find_find(self.currentcurrent.suites, suite.name)
38  if old is not None:
39  old.starttime = old.endtime = None
40  old.doc = suite.doc
41  old.metadata.update(suite.metadata)
42  old.setup = suite.setup
43  old.teardown = suite.teardown
44  self.currentcurrent = old
45  else:
46  suite.message = self._create_add_message_create_add_message(suite, suite=True)
47  self.currentcurrent.suites.append(suite)
48  return old is not None
49 
50  def _find_root(self, name):
51  root = self.resultresult.suite
52  if root.name != name:
53  raise DataError(f"Cannot merge outputs containing different root suites. "
54  f"Original suite is '{root.name}' and merged is '{name}'.")
55  return root
56 
57  def _find(self, items, name):
58  for item in items:
59  if item.name == name:
60  return item
61  return None
62 
63  def end_suite(self, suite):
64  self.currentcurrent = self.currentcurrent.parent
65 
66  def visit_test(self, test):
67  old = self._find_find(self.currentcurrent.tests, test.name)
68  if old is None:
69  test.message = self._create_add_message_create_add_message(test)
70  self.currentcurrent.tests.append(test)
71  elif test.skipped:
72  old.message = self._create_skip_message_create_skip_message(old, test)
73  else:
74  test.message = self._create_merge_message_create_merge_message(test, old)
75  index = self.currentcurrent.tests.index(old)
76  self.currentcurrent.tests[index] = test
77 
78  def _create_add_message(self, item, suite=False):
79  item_type = 'Suite' if suite else test_or_task('Test', self.rparpa)
80  prefix = f'*HTML* {item_type} added from merged output.'
81  if not item.message:
82  return prefix
83  return ''.join([prefix, '<hr>', self._html_html(item.message)])
84 
85  def _html(self, message):
86  if message.startswith('*HTML*'):
87  return message[6:].lstrip()
88  return html_escape(message)
89 
90  def _create_merge_message(self, new, old):
91  header = (f'*HTML* <span class="merge">{test_or_task("Test", self.rpa)} '
92  f'has been re-executed and results merged.</span>')
93  return ''.join([
94  header,
95  '<hr>',
96  self._format_status_and_message_format_status_and_message('New', new),
97  '<hr>',
98  self._format_old_status_and_message_format_old_status_and_message(old, header)
99  ])
100 
101  def _format_status_and_message(self, state, test):
102  msg = f'{self._status_header(state)} {self._status_text(test.status)}<br>'
103  if test.message:
104  msg += f'{self._message_header(state)} {self._html(test.message)}<br>'
105  return msg
106 
107  def _status_header(self, state):
108  return f'<span class="{state.lower()}-status">{state} status:</span>'
109 
110  def _status_text(self, status):
111  return f'<span class="{status.lower()}">{status}</span>'
112 
113  def _message_header(self, state):
114  return f'<span class="{state.lower()}-message">{state} message:</span>'
115 
116  def _format_old_status_and_message(self, test, merge_header):
117  if not test.message.startswith(merge_header):
118  return self._format_status_and_message_format_status_and_message('Old', test)
119  status_and_message = test.message.split('<hr>', 1)[1]
120  return (
121  status_and_message
122  .replace(self._status_header_status_header('New'), self._status_header_status_header('Old'))
123  .replace(self._message_header_message_header('New'), self._message_header_message_header('Old'))
124  )
125 
126  def _create_skip_message(self, test, new):
127  msg = (f'*HTML* {test_or_task("Test", self.rpa)} has been re-executed and '
128  f'results merged. Latter result had {self._status_text("SKIP")} status '
129  f'and was ignored. Message:\n{self._html(new.message)}')
130  if test.message:
131  msg += f'<hr>Original message:\n{self._html(test.message)}'
132  return msg
Interface to ease traversing through a test suite structure.
Definition: visitor.py:85
def _create_add_message(self, item, suite=False)
Definition: merger.py:78
def _find_root(self, name)
Definition: merger.py:50
def start_suite(self, suite)
Called when a suite starts.
Definition: merger.py:33
def merge(self, merged)
Definition: merger.py:28
def _find(self, items, name)
Definition: merger.py:57
def _message_header(self, state)
Definition: merger.py:113
def visit_test(self, test)
Implements traversing through tests.
Definition: merger.py:66
def _status_text(self, status)
Definition: merger.py:110
def _format_old_status_and_message(self, test, merge_header)
Definition: merger.py:116
def _status_header(self, state)
Definition: merger.py:107
def _html(self, message)
Definition: merger.py:85
def _create_skip_message(self, test, new)
Definition: merger.py:126
def _create_merge_message(self, new, old)
Definition: merger.py:90
def __init__(self, result, rpa=False)
Definition: merger.py:23
def _format_status_and_message(self, state, test)
Definition: merger.py:101
def end_suite(self, suite)
Called when a suite ends.
Definition: merger.py:63
def html_escape(text, linkify=True)
Definition: markuputils.py:44
def test_or_task(str text, bool rpa)
Replace 'test' with 'task' in the given text depending on rpa.
Definition: misc.py:105