3 # Copyright (c) 2017 Orange and others.
5 # All rights reserved. This program and the accompanying materials
6 # are made available under the terms of the Apache License, Version 2.0
7 # which accompanies this distribution, and is available at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 """Define classes required to run any Robot suites."""
12 from __future__ import division
17 from io import StringIO
19 from robot.errors import RobotError
20 from robot.reporting import resultwriter
22 from robot.utils.robottime import timestamp_to_secs
24 from xtesting.core import testcase
26 __author__ = "Cedric Ollivier <cedric.ollivier@orange.com>"
29 class ResultVisitor(robot.api.ResultVisitor):
30 """Visitor to get result details."""
35 def visit_test(self, test):
37 output['name'] = test.name
38 output['parent'] = test.parent.name
39 output['status'] = test.status
40 output['starttime'] = test.starttime
41 output['endtime'] = test.endtime
42 output['text'] = test.message
43 output['elapsedtime'] = test.elapsedtime
44 self._data.append(output)
47 """Get the details of the result."""
51 class RobotFramework(testcase.TestCase):
52 """RobotFramework runner."""
54 __logger = logging.getLogger(__name__)
56 def __init__(self, **kwargs):
57 super().__init__(**kwargs)
58 self.xml_file = os.path.join(self.res_dir, 'output.xml')
59 self.deny_skipping = kwargs.get("deny_skipping", False)
61 def parse_results(self):
62 """Parse output.xml and get the details in it."""
63 result = robot.api.ExecutionResult(self.xml_file)
64 visitor = ResultVisitor()
67 if self.deny_skipping:
69 result.suite.statistics.passed /
70 result.suite.statistics.total)
72 self.result = 100 * ((
73 result.suite.statistics.passed +
74 result.suite.statistics.skipped) /
75 result.suite.statistics.total)
76 except ZeroDivisionError:
77 self.__logger.error("No test has been run")
78 self.start_time = timestamp_to_secs(result.suite.starttime)
79 self.stop_time = timestamp_to_secs(result.suite.endtime)
81 self.details['description'] = result.suite.name
82 self.details['tests'] = visitor.get_data()
84 def generate_report(self):
85 """Generate html and xunit outputs"""
86 result = robot.api.ExecutionResult(self.xml_file)
87 writer = resultwriter.ResultWriter(result)
88 return writer.write_results(
89 report=f'{self.res_dir}/report.html',
90 log=f'{self.res_dir}/log.html',
91 xunit=f'{self.res_dir}/xunit.xml')
93 def run(self, **kwargs):
94 """Run the RobotFramework suites
97 * create the output directories if required,
98 * get the results in output.xml,
99 * delete temporary files.
102 kwargs: Arbitrary keyword arguments.
105 EX_OK if all suites ran well.
106 EX_RUN_ERROR otherwise.
109 suites = kwargs.pop("suites")
111 self.__logger.exception("Mandatory args were not passed")
112 return self.EX_RUN_ERROR
113 if not os.path.exists(self.res_dir):
115 os.makedirs(self.res_dir)
116 except Exception: # pylint: disable=broad-except
117 self.__logger.exception("Cannot create %s", self.res_dir)
118 return self.EX_RUN_ERROR
120 kwargs["output"] = self.xml_file
121 kwargs["log"] = "NONE"
122 kwargs["report"] = "NONE"
123 kwargs["stdout"] = stream
124 robot.run(*suites, **kwargs)
125 self.__logger.info("\n%s", stream.getvalue())
128 self.__logger.info("Results were successfully parsed")
129 self.generate_report()
130 self.__logger.info("Results were successfully generated")
131 except RobotError as ex:
132 self.__logger.error("Run suites before publishing: %s", ex.message)
133 return self.EX_RUN_ERROR
134 except Exception: # pylint: disable=broad-except
135 self.__logger.exception("Cannot parse results")
136 return self.EX_RUN_ERROR