Revision: 8e88ebe1e31e
Author: Mikko Korpela <[email protected]>
Date: Fri Jun 3 01:44:04 2011
Log: new Reporter for report and log generation
http://code.google.com/p/robotframework/source/detail?r=8e88ebe1e31e
Added:
/utest/resources/golden_suite/output2.xml
/utest/serializing/test_reporting.py
Modified:
/src/robot/__init__.py
/src/robot/serializing/jsparser.py
/src/robot/serializing/testoutput.py
/utest/resources/__init__.py
/utest/serializing/test_parser.py
=======================================
--- /dev/null
+++ /utest/resources/golden_suite/output2.xml Fri Jun 3 01:44:04 2011
@@ -0,0 +1,206 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<robot generated="20110603 11:15:42.552" generator="Robot trunk 20110527
(Python 2.6.5 on linux2)">
+<suite
source="/home/mkorpela/workspace/robot/utest/resources/golden_suite"
name="Golden Suite">
+<doc>root docs
+with new line, several spaces " " and a <b>bold
tag</b>.</doc>
+<metadata>
+<item name="root">rocks</item>
+</metadata>
+<kw type="setup" name="BuiltIn.Log" timeout="">
+<doc>Logs the given message with the given level.</doc>
+<arguments>
+<arg>Rock on</arg>
+</arguments>
+<msg timestamp="20110603 11:15:42.603" level="INFO">Rock on</msg>
+<status status="PASS" endtime="20110603 11:15:42.603" starttime="20110603
11:15:42.603"></status>
+</kw>
+<suite
source="/home/mkorpela/workspace/robot/utest/resources/golden_suite/all_settings.txt"
name="All Settings">
+<doc>Suite docs
+with new line, several spaces " " and a <b>bold
tag</b>.</doc>
+<metadata>
+<item name="meta">rulez with <b>escaped</b></item>
+<item name="version">alpha</item>
+</metadata>
+<kw type="setup" name="BuiltIn.Log" timeout="">
+<doc>Logs the given message with the given level.</doc>
+<arguments>
+<arg>suite msg</arg>
+</arguments>
+<msg timestamp="20110603 11:15:42.606" level="INFO">suite msg</msg>
+<status status="PASS" endtime="20110603 11:15:42.606" starttime="20110603
11:15:42.605"></status>
+</kw>
+<test name="My test" timeout="1 minute">
+<doc>Test docs
+with new line, several spaces " " and a <b>bold
tag</b>.</doc>
+<kw type="setup" name="BuiltIn.Log" timeout="">
+<doc>Logs the given message with the given level.</doc>
+<arguments>
+<arg>Test setup msg</arg>
+</arguments>
+<msg timestamp="20110603 11:15:42.607" level="INFO">Test setup msg</msg>
+<status status="PASS" endtime="20110603 11:15:42.608" starttime="20110603
11:15:42.607"></status>
+</kw>
+<kw type="kw" name="My kw" timeout="">
+<doc>Kw docs</doc>
+<arguments>
+<arg>This is my _non html_ message\nwith new line, several spaces " \ \
\ " and a <b>bold tag</b>.</arg>
+</arguments>
+<kw type="kw" name="BuiltIn.Log" timeout="">
+<doc>Logs the given message with the given level.</doc>
+<arguments>
+<arg>${arg}</arg>
+<arg>${level}</arg>
+</arguments>
+<msg timestamp="20110603 11:15:42.614" level="WARN">This is my _non html_
message
+with new line, several spaces " " and a <b>bold
tag</b>.</msg>
+<status status="PASS" endtime="20110603 11:15:42.616" starttime="20110603
11:15:42.612"></status>
+</kw>
+<kw type="teardown" name="BuiltIn.Log" timeout="">
+<doc>Logs the given message with the given level.</doc>
+<arguments>
+<arg>keyword teardown</arg>
+</arguments>
+<msg timestamp="20110603 11:15:42.617" level="INFO">keyword teardown</msg>
+<status status="PASS" endtime="20110603 11:15:42.618" starttime="20110603
11:15:42.616"></status>
+</kw>
+<status status="PASS" endtime="20110603 11:15:42.618" starttime="20110603
11:15:42.609"></status>
+</kw>
+<kw type="kw" name="My kw" timeout="">
+<doc>Kw docs</doc>
+<arguments>
+<arg>This is my <blink>HTML</blink> message\nwith new line,
several spaces " \ \ \ " and a <b>bold tag</b>.</arg>
+<arg>HTML</arg>
+</arguments>
+<kw type="kw" name="BuiltIn.Log" timeout="">
+<doc>Logs the given message with the given level.</doc>
+<arguments>
+<arg>${arg}</arg>
+<arg>${level}</arg>
+</arguments>
+<msg timestamp="20110603 11:15:42.621" html="yes" level="INFO">This is my
<blink>HTML</blink> message
+with new line, several spaces " " and a <b>bold
tag</b>.</msg>
+<status status="PASS" endtime="20110603 11:15:42.622" starttime="20110603
11:15:42.620"></status>
+</kw>
+<kw type="teardown" name="BuiltIn.Log" timeout="">
+<doc>Logs the given message with the given level.</doc>
+<arguments>
+<arg>keyword teardown</arg>
+</arguments>
+<msg timestamp="20110603 11:15:42.623" level="INFO">keyword teardown</msg>
+<status status="PASS" endtime="20110603 11:15:42.623" starttime="20110603
11:15:42.622"></status>
+</kw>
+<status status="PASS" endtime="20110603 11:15:42.624" starttime="20110603
11:15:42.619"></status>
+</kw>
+<kw type="teardown" name="BuiltIn.Log" timeout="">
+<doc>Logs the given message with the given level.</doc>
+<arguments>
+<arg>Test teardown msg</arg>
+</arguments>
+<msg timestamp="20110603 11:15:42.624" level="INFO">Test teardown msg</msg>
+<status status="PASS" endtime="20110603 11:15:42.624" starttime="20110603
11:15:42.624"></status>
+</kw>
+<tags>
+<tag>someothertag</tag>
+<tag>sometag</tag>
+</tags>
+<status status="PASS" endtime="20110603 11:15:42.625" critical="yes"
starttime="20110603 11:15:42.606"></status>
+</test>
+<kw type="teardown" name="BuiltIn.Log" timeout="">
+<doc>Logs the given message with the given level.</doc>
+<arguments>
+<arg>suite teardown msg</arg>
+</arguments>
+<msg timestamp="20110603 11:15:42.625" level="INFO">suite teardown
msg</msg>
+<status status="PASS" endtime="20110603 11:15:42.626" starttime="20110603
11:15:42.625"></status>
+</kw>
+<status status="PASS" endtime="20110603 11:15:42.626" starttime="20110603
11:15:42.603"></status>
+</suite>
+<suite
source="/home/mkorpela/workspace/robot/utest/resources/golden_suite/failing_suite.txt"
name="Failing Suite">
+<doc></doc>
+<metadata>
+</metadata>
+<test name="This fails at test" timeout="">
+<doc></doc>
+<kw type="kw" name="BuiltIn.Fail" timeout="">
+<doc>Fails the test immediately with the given (optional) message.</doc>
+<arguments>
+<arg>Failure msg</arg>
+</arguments>
+<msg timestamp="20110603 11:15:42.633" level="FAIL">Failure msg</msg>
+<status status="FAIL" endtime="20110603 11:15:42.634" starttime="20110603
11:15:42.631"></status>
+</kw>
+<tags>
+</tags>
+<status status="FAIL" endtime="20110603 11:15:42.634" critical="yes"
starttime="20110603 11:15:42.630">Failure msg</status>
+</test>
+<test name="This fails at kw" timeout="">
+<doc></doc>
+<kw type="kw" name="Lets fail at keyword" timeout="">
+<doc></doc>
+<arguments>
+</arguments>
+<kw type="kw" name="BuiltIn.Fail" timeout="">
+<doc>Fails the test immediately with the given (optional) message.</doc>
+<arguments>
+<arg>Failure msg</arg>
+</arguments>
+<msg timestamp="20110603 11:15:42.636" level="FAIL">Failure msg</msg>
+<status status="FAIL" endtime="20110603 11:15:42.636" starttime="20110603
11:15:42.635"></status>
+</kw>
+<status status="FAIL" endtime="20110603 11:15:42.636" starttime="20110603
11:15:42.635"></status>
+</kw>
+<tags>
+</tags>
+<status status="FAIL" endtime="20110603 11:15:42.636" critical="yes"
starttime="20110603 11:15:42.634">Failure msg</status>
+</test>
+<test name="This Errors" timeout="">
+<doc></doc>
+<kw type="kw" name="This does not exist" timeout="">
+<doc></doc>
+<arguments>
+</arguments>
+<msg timestamp="20110603 11:15:42.638" level="FAIL">No keyword with
name 'This does not exist' found.</msg>
+<status status="FAIL" endtime="20110603 11:15:42.638" starttime="20110603
11:15:42.637"></status>
+</kw>
+<tags>
+</tags>
+<status status="FAIL" endtime="20110603 11:15:42.638" critical="yes"
starttime="20110603 11:15:42.637">No keyword with name 'This does not
exist' found.</status>
+</test>
+<status status="FAIL" endtime="20110603 11:15:42.639" starttime="20110603
11:15:42.626"></status>
+</suite>
+<kw type="teardown" name="BuiltIn.Fail" timeout="">
+<doc>Fails the test immediately with the given (optional) message.</doc>
+<arguments>
+</arguments>
+<msg timestamp="20110603 11:15:42.640" level="FAIL">AssertionError</msg>
+<status status="FAIL" endtime="20110603 11:15:42.640" starttime="20110603
11:15:42.639"></status>
+</kw>
+<status status="FAIL" endtime="20110603 11:15:42.640" starttime="20110603
11:15:42.564">Suite teardown failed:
+AssertionError</status>
+</suite>
+<statistics>
+<total>
+<stat fail="4" doc="" pass="0">Critical Tests</stat>
+<stat fail="4" doc="" pass="0">All Tests</stat>
+</total>
+<tag>
+<stat info="" fail="1" pass="0" links="" doc="">someothertag</stat>
+<stat info="" fail="1" pass="0" links="" doc="">sometag</stat>
+</tag>
+<suite>
+<stat fail="4" doc="Golden Suite" pass="0">Golden Suite</stat>
+<stat fail="1" doc="Golden Suite.All Settings" pass="0">Golden Suite.All
Settings</stat>
+<stat fail="3" doc="Golden Suite.Failing Suite" pass="0">Golden
Suite.Failing Suite</stat>
+</suite>
+</statistics>
+<errors>
+<msg linkable="yes" timestamp="20110603 11:15:42.614" level="WARN">This is
my _non html_ message
+with new line, several spaces " " and a <b>bold
tag</b>.</msg>
+<msg timestamp="20110603 11:15:42.629" level="ERROR">Invalid syntax in
file '/home/mkorpela/workspace/robot/utest/resources/golden_suite/failing_suite.txt'
in table 'Settings': Importing test library 'Idontexist' failed:
ImportError: No module named Idontexist
+PYTHONPATH:
['/usr/local/lib/python2.6/dist-packages/robot/libraries', '/usr/local/lib/python2.6/dist-packages/docutils-0.7-py2.6.egg', '/usr/local/lib/python2.6/dist-packages/decorator-3.2.0-py2.6.egg', '/usr/local/lib/python2.6/dist-packages/robotframework_seleniumlibrary-2.4-py2.6.egg', '/usr/local/lib/python2.6/dist-packages/mock-0.7.0b4-py2.6.egg', '/usr/local/lib/python2.6/dist-packages/nose-0.11.3-py2.6.egg', '/usr/local/lib/python2.6/dist-packages/guppy-0.1.5-py2.6-linux-x86_64.egg', '/usr/local/lib/python2.6/dist-packages/Pygments-1.4-py2.6.egg', '/usr/lib/python2.6', '/usr/lib/python2.6/plat-linux2', '/usr/lib/python2.6/lib-tk', '/usr/lib/python2.6/lib-old', '/usr/lib/python2.6/lib-dynload', '/usr/lib/python2.6/dist-packages', '/usr/lib/python2.6/dist-packages/PIL', '/usr/lib/python2.6/dist-packages/gst-0.10', '/usr/lib/pymodules/python2.6', '/usr/lib/python2.6/dist-packages/gtk-2.0', '/usr/lib/pymodules/python2.6/gtk-2.0', '/usr/lib/python2.6/dist-packages/wx-2.8-gtk2-unicode', '/usr/local/lib/python2.6/dist-packages', '.']
+Traceback (most recent call last):
+ File "/usr/local/lib/python2.6/dist-packages/robot/utils/importing.py",
line 85, in _non_dotted_import
+ module = __import__(name)</msg>
+<msg timestamp="20110603 11:15:42.629" level="ERROR">Invalid syntax in
file '/home/mkorpela/workspace/robot/utest/resources/golden_suite/failing_suite.txt'
in table 'Settings': Resource file 'And I'm not here' does not exist.</msg>
+</errors>
+</robot>
=======================================
--- /dev/null
+++ /utest/serializing/test_reporting.py Fri Jun 3 01:44:04 2011
@@ -0,0 +1,126 @@
+import unittest
+from robot.output.readers import ExecutionErrors
+import resources
+from robot.common.model import BaseTestSuite
+from robot.serializing.testoutput import Reporter
+import robot.serializing.testoutput
+
+def set_serialize_log_mock():
+ results = {'log_path':None}
+ def serialize_log(test_output_datamodel, log_path, title=None):
+ results['log_path'] = log_path
+ results['title'] = title
+ robot.serializing.testoutput.serialize_log = serialize_log
+ return results
+
+def set_serialize_report_mock():
+ results = {'report_path':None}
+ def serialize_report(test_output_datamodel, report_path, title=None,
background=None, logpath=None):
+ results['report_path'] = report_path
+ results['title'] = title
+ results['background'] = background
+ results['logpath'] = logpath
+ robot.serializing.testoutput.serialize_report = serialize_report
+ return results
+
+def set_process_outputs_mock():
+ results = {'paths':None}
+ def process_outputs(paths, settings):
+ results['paths'] = paths
+ results['settings'] = settings
+ suite = BaseTestSuite('Suite')
+ suite.starttime = 7
+ suite.endtime = 42
+ return suite, ExecutionErrors(None)
+ robot.serializing.testoutput.process_outputs = process_outputs
+ return results
+
+class TestReporting(unittest.TestCase):
+
+ def setUp(self):
+ self._reporter = Reporter()
+ self._settings = {
+ 'Report': 'NONE',
+ 'Log': 'NONE',
+ 'LogTitle': None,
+ 'ReportTitle': None,
+ 'ReportBackground': None,
+ 'SuiteStatLevel': None,
+ 'TagStatInclude': None,
+ 'TagStatExclude': None,
+ 'TagStatCombine': None,
+ 'TagDoc': None,
+ 'TagStatLink': None,
+ 'SetTag': None,
+ 'SuiteNames': None,
+ 'TestNames': None,
+ 'Include': None,
+ 'Exclude': None,
+ 'StartTime': 0,
+ 'Name': None,
+ 'Doc': None,
+ 'Metadata': {},
+ 'Critical': None,
+ 'NonCritical': None,
+ 'NoStatusRC': None,
+ 'EndTime': 0,
+ 'LogLevel': 'INFO'
+ }
+ self._log_results = set_serialize_log_mock()
+ self._report_results = set_serialize_report_mock()
+ #self._process_outputs_results = set_process_outputs_mock()
+
+ def test_generate_report_and_log(self):
+ self._settings['Log'] = 'log.html'
+ self._settings['Report'] = 'report.html'
+ self._reporter.execute(self._settings, resources.GOLDEN_OUTPUT)
+ self._assert_expected_log('log.html')
+ self._assert_expected_report('report.html')
+ self._assert_log_link_in_report('log.html')
+
+ def test_no_generation(self):
+ self._reporter.execute(self._settings, resources.GOLDEN_OUTPUT)
+ self._assert_no_log()
+ self._assert_no_report()
+
+ def test_only_log(self):
+ self._settings['Log'] = 'only-log.html'
+ self._reporter.execute(self._settings, resources.GOLDEN_OUTPUT)
+ self._assert_expected_log('only-log.html')
+ self._assert_no_report()
+
+ def test_only_report(self):
+ self._settings['Report'] = 'reports-only.html'
+ self._reporter.execute(self._settings, resources.GOLDEN_OUTPUT)
+ self._assert_no_log()
+ self._assert_expected_report('reports-only.html')
+ self._assert_no_log_links_in_report()
+
+ def test_multiple_outputs(self):
+ self._settings['Log'] = 'log.html'
+ self._settings['Report'] = 'report.html'
+ self._reporter.execute(self._settings, *[resources.GOLDEN_OUTPUT,
resources.GOLDEN_OUTPUT2])
+ self._assert_expected_log('log.html')
+ self._assert_expected_report('report.html')
+
+ def _assert_expected_log(self, expected_file_name):
+ self.assertEquals(self._log_results['log_path'],
expected_file_name)
+
+ def _assert_expected_report(self, expected_file_name):
+ self.assertEquals(self._report_results['report_path'],
expected_file_name)
+
+ def _assert_log_link_in_report(self, expected_log_link):
+ self.assertEquals(self._report_results['logpath'],
expected_log_link)
+
+ def _assert_no_log_links_in_report(self):
+ self._assert_log_link_in_report(None)
+
+ def _assert_no_log(self):
+ self._assert_expected_log(None)
+
+ def _assert_no_report(self):
+ self._assert_expected_report(None)
+
+
+if __name__ == '__main__':
+ unittest.main()
=======================================
--- /src/robot/__init__.py Mon May 30 04:58:01 2011
+++ /src/robot/__init__.py Fri Jun 3 01:44:04 2011
@@ -14,6 +14,7 @@
import sys
import os
+from robot.serializing.testoutput import Reporter
if __name__ == '__main__':
sys.stderr.write("Use 'runner' or 'rebot' for executing.\n")
@@ -128,13 +129,10 @@
output.close(suite)
output_src = settings['Output']
if settings.is_rebot_needed():
- datasources, settings =
settings.get_rebot_datasources_and_settings()
+ _, settings = settings.get_rebot_datasources_and_settings()
if settings['SplitOutputs'] > 0:
raise Exception('Splitting? No way!')
- testoutput = SplitIndexTestOutput(suite, datasources[0],
settings)
- else:
- testoutput = RebotTestOutput(datasources, settings)
- testoutput.serialize(settings, output=output_src)
+ Reporter().execute(settings, output_src)
LOGGER.close()
return suite
@@ -157,8 +155,7 @@
settings = RebotSettings(options)
LOGGER.register_console_logger(colors=settings['MonitorColors'])
LOGGER.disable_message_cache()
- testoutput = RebotTestOutput(datasources, settings)
- testoutput.serialize(settings, generator='Rebot')
+ Reporter().execute(settings, *datasources)
LOGGER.close()
return testoutput.suite
=======================================
--- /src/robot/serializing/jsparser.py Thu Jun 2 21:39:47 2011
+++ /src/robot/serializing/jsparser.py Fri Jun 3 01:44:04 2011
@@ -38,6 +38,8 @@
return self._texts.dump()
def timestamp(self, time):
+ if time == 'N/A':
+ return -1
dt = datetime.strptime(time+"000", "%Y%m%d %H:%M:%S.%f")
millis = int(mktime(dt.timetuple())*1000+dt.microsecond/1000)
if self.basemillis is None:
@@ -360,7 +362,7 @@
def __init__(self, context, attrs):
_Handler.__init__(self, context, attrs)
self._name = attrs.getValue('name')
- self._source = attrs.getValue('source')
+ self._source = attrs.get('source') or ''
self.context.start_suite(self._name)
self.context.collect_stats()
=======================================
--- /src/robot/serializing/testoutput.py Mon May 30 04:58:01 2011
+++ /src/robot/serializing/testoutput.py Fri Jun 3 01:44:04 2011
@@ -32,6 +32,28 @@
from robot.serializing import jsparser
+class Reporter(object):
+
+ def execute(self, settings, *data_sources):
+ if len(data_sources) > 1:
+ suite, exec_errors = process_outputs(data_sources, settings)
+ suite.set_options(settings)
+ RobotTestOutput(suite, exec_errors,
settings).serialize_output('foo.xml')
+ data_sources = ['foo.xml']
+ data_model = jsparser.create_datamodel_from(data_sources[0])
+ report_path = self._parse_file(settings['Report'])
+ log_path = self._parse_file(settings['Log'])
+ if report_path:
+ serialize_report(data_model, report_path,
settings['ReportTitle'], settings['ReportBackground'], log_path)
+ LOGGER.output_file('Report', report_path)
+ if log_path:
+ serialize_log(data_model, log_path, settings['LogTitle'])
+ LOGGER.output_file('Log', log_path)
+
+ def _parse_file(self, string):
+ return string if string != 'NONE' else None
+
+
class RobotTestOutput:
def __init__(self, suite, exec_errors, settings=None):
=======================================
--- /utest/resources/__init__.py Tue May 31 06:04:41 2011
+++ /utest/resources/__init__.py Fri Jun 3 01:44:04 2011
@@ -2,5 +2,6 @@
THIS_PATH = os.path.dirname(__file__)
GOLDEN_OUTPUT = os.path.join(THIS_PATH, 'golden_suite', 'output.xml')
+GOLDEN_OUTPUT2 = os.path.join(THIS_PATH, 'golden_suite', 'output2.xml')
GOLDEN_JS = os.path.join(THIS_PATH, 'golden_suite', 'expected.js')
=======================================
--- /utest/serializing/test_parser.py Sun May 29 23:54:31 2011
+++ /utest/serializing/test_parser.py Fri Jun 3 01:44:04 2011
@@ -1,5 +1,13 @@
import robot.serializing.jsparser as jsparser
+def test_timestamp():
+ context = jsparser.Context()
+ time = context.timestamp('20110603 12:00:00.000')
+ assert time == 0
+ time = context.timestamp('N/A')
+ assert time == -1
+ time = context.timestamp('20110603 12:00:01.000')
+ assert time == 1000
def test_stats_when_failing_suite_teardown():
context = jsparser.Context()