changeset 550:6a8dcbffdce2

Added support for nunit (applied patch from silk in #348)
author dfraser
date Mon, 06 Apr 2009 08:20:40 +0000
parents cb137f2121b9
children 6b4def531a4e
files bitten/build/monotools.py bitten/build/tests/__init__.py bitten/build/tests/monotools.py doc/commands.txt setup.py
diffstat 5 files changed, 288 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/bitten/build/monotools.py
@@ -0,0 +1,97 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# Copyright (C) 2006 Matthew Good <matt@matt-good.net>
+# Copyright (C) 2007 Edgewall Software
+# Copyright (C) 2009 Grzegorz Sobanski <silk@boktor.net>
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://bitten.edgewall.org/wiki/License.
+
+"""Recipe commands for tools commonly used in Mono projects."""
+
+from glob import glob
+import logging
+import os
+import posixpath
+import shlex
+import tempfile
+
+from bitten.build import CommandLine
+from bitten.util import xmlio
+
+log = logging.getLogger('bitten.build.monotools')
+
+__docformat__ = 'restructuredtext en'
+
+
+def _parse_suite(element):
+    for child in element.children('results'):
+        testcases = list(child.children('test-case'))
+        if testcases:
+            yield element, testcases
+
+        for xmlsuite in child.children('test-suite'):
+            for suite in _parse_suite(xmlsuite):
+                yield suite
+
+
+def _get_cases(fileobj):
+    for testsuite in xmlio.parse(fileobj).children('test-suite'):
+        for suite in _parse_suite(testsuite):
+            yield suite
+
+
+def nunit(ctxt, file_=None):
+    """Extract test results from a NUnit XML report.
+    
+    :param ctxt: the build context
+    :type ctxt: `Context`
+    :param file\_: path to the NUnit XML test results; may contain globbing
+                  wildcards for matching multiple results files
+    """
+    assert file_, 'Missing required attribute "file"'
+    try:
+        total, failed = 0, 0
+        results = xmlio.Fragment()
+        for path in glob(ctxt.resolve(file_)):
+            fileobj = file(path, 'r')
+            try:
+                for suite, testcases in _get_cases(fileobj):
+                    for testcase in testcases:
+                        test = xmlio.Element('test')
+                        test.attr['fixture'] = suite.attr['name']
+                        if 'time' in testcase.attr:
+                            test.attr['duration'] = testcase.attr['time']
+                        if testcase.attr['executed'] == 'True':
+                            if testcase.attr['success'] != 'True':
+                                test.attr['status'] = 'failure'
+                                failure = list(testcase.children('failure'))
+                                if failure:
+                                    stacktraceNode = list(failure[0].children('stack-trace'))
+                                    if stacktraceNode:
+                                        test.append(xmlio.Element('traceback')[
+                                            stacktraceNode[0].gettext()
+                                        ])
+                                    failed += 1
+                            else:
+                                test.attr['status'] = 'success'
+                        else:
+                            test.attr['status'] = 'ignored'                            
+
+                        results.append(test)
+                        total += 1
+            finally:
+                fileobj.close()
+        if failed:
+            ctxt.error('%d of %d test%s failed' % (failed, total,
+                       total != 1 and 's' or ''))
+        ctxt.report('test', results)
+    except IOError, e:
+        log.warning('Error opening NUnit results file (%s)', e)
+    except xmlio.ParseError, e:
+        log.warning('Error parsing NUnit results file (%s)', e)
+
+
--- a/bitten/build/tests/__init__.py
+++ b/bitten/build/tests/__init__.py
@@ -10,7 +10,8 @@
 
 import unittest
 
-from bitten.build.tests import api, config, ctools, phptools, pythontools, \
+from bitten.build.tests import api, config, ctools, \
+                               monotools, phptools, pythontools, \
                                xmltools
 
 def suite():
@@ -18,6 +19,7 @@
     suite.addTest(api.suite())
     suite.addTest(config.suite())
     suite.addTest(ctools.suite())
+    suite.addTest(monotools.suite())
     suite.addTest(phptools.suite())
     suite.addTest(pythontools.suite())
     suite.addTest(xmltools.suite())
new file mode 100644
--- /dev/null
+++ b/bitten/build/tests/monotools.py
@@ -0,0 +1,150 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# Copyright (C) 2008 Matt Good <matt@matt-good.net>
+# Copyright (C) 2008 Edgewall Software
+# Copyright (C) 2009 Grzegorz Sobanski <silk@boktor.net>
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://bitten.edgewall.org/wiki/License.
+
+import os
+import cPickle as pickle
+import shutil
+import tempfile
+import unittest
+
+from bitten.build import monotools
+from bitten.build import FileSet
+from bitten.recipe import Context, Recipe
+
+
+
+class NUnitTestCase(unittest.TestCase):
+
+    def setUp(self):
+        self.basedir = os.path.realpath(tempfile.mkdtemp())
+        self.ctxt = Context(self.basedir)
+        self.results_xml = open(os.path.join(self.basedir, 'test-results.xml'), 'w')
+
+    def tearDown(self):
+        shutil.rmtree(self.basedir)
+
+    def test_missing_file_param(self):
+        self.results_xml.close()
+        self.assertRaises(AssertionError, monotools.nunit, self.ctxt)
+
+    def test_empty_results(self):
+        self.results_xml.write('<?xml version="1.0"?>'
+                              '<test-results>'
+                              '</test-results>')
+        self.results_xml.close()
+        monotools.nunit(self.ctxt, self.results_xml.name)
+        type, category, generator, xml = self.ctxt.output.pop()
+        self.assertEqual(Recipe.REPORT, type)
+        self.assertEqual('test', category)
+        self.assertEqual(0, len(xml.children))
+
+    def test_successful_test_simple(self):
+        self.results_xml.write(
+'<?xml version="1.0" encoding="utf-8" standalone="no"?>'
+'<!--This file represents the results of running a test suite-->'
+'<test-results name="Test.dll" total="3" failures="1" not-run="0" date="2009-01-05" time="16:32:46">'
+'  <environment nunit-version="2.4.7.0" clr-version="2.0.50727.42" os-version="Unix 2.6.26.1" platform="Unix" cwd="/home/silk/devel/trac/testcheckout" machine-name="tango" user="silk" user-domain="tango" />'
+'  <culture-info current-culture="en-US" current-uiculture="en-US" />'
+'  <test-suite name="Test.dll" success="False" time="0.081" asserts="0">'
+'    <results>'
+'      <test-case name="Lib.Test.KlasaTests.Test2" executed="True" success="True" time="0.001" asserts="1" />'
+'    </results>'
+'  </test-suite>'
+'</test-results>'
+)
+        self.results_xml.close()
+        monotools.nunit(self.ctxt, self.results_xml.name)
+        type, category, generator, xml = self.ctxt.output.pop()
+        self.assertEqual(1, len(xml.children))
+        test_elem = xml.children[0]
+        self.assertEqual('test', test_elem.name)
+        self.assertEqual('0.001', test_elem.attr['duration'])
+        self.assertEqual('success', test_elem.attr['status'])
+        self.assertEqual('Test.dll', test_elem.attr['fixture'])
+
+
+    def test_failure_test_simple(self):
+        self.results_xml.write(
+'<?xml version="1.0" encoding="utf-8" standalone="no"?>'
+'<!--This file represents the results of running a test suite-->'
+'<test-results name="Test.dll" total="3" failures="1" not-run="0" date="2009-01-05" time="16:32:46">'
+'  <environment nunit-version="2.4.7.0" clr-version="2.0.50727.42" os-version="Unix 2.6.26.1" platform="Unix" cwd="/home/silk/devel/trac/testcheckout" machine-name="tango" user="silk" user-domain="tango" />'
+'  <culture-info current-culture="en-US" current-uiculture="en-US" />'
+'  <test-suite name="Test.dll" success="False" time="0.081" asserts="0">'
+'    <results>'
+'      <test-case name="Lib.Test.KlasaTests.Test2" executed="True" success="False" time="0.001" asserts="1">'
+'                    <failure>'
+'                      <message><![CDATA[  Expected: 2'
+'  But was:  3'
+']]></message>'
+'                      <stack-trace><![CDATA[at Lib.Test.KlasaTests.Test1 () [0x00000]'
+'at (wrapper managed-to-native) System.Reflection.MonoMethod:InternalInvoke (object,object[],System.Exception&)'
+'at System.Reflection.MonoMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00000]'
+']]></stack-trace>'
+'                    </failure>'
+'      </test-case>'
+'    </results>'
+'  </test-suite>'
+'</test-results>'
+)
+        self.results_xml.close()
+        monotools.nunit(self.ctxt, self.results_xml.name)
+        type, category, generator, xml = self.ctxt.output.pop()
+        self.assertEqual(1, len(xml.children))
+        test_elem = xml.children[0]
+        self.assertEqual('test', test_elem.name)
+        self.assertEqual('0.001', test_elem.attr['duration'])
+        self.assertEqual('failure', test_elem.attr['status'])
+        self.assertEqual('Test.dll', test_elem.attr['fixture'])
+        self.assertEqual(1, len(test_elem.children))
+
+
+    def test_successful_test_recursive(self):
+        self.results_xml.write(
+'<?xml version="1.0" encoding="utf-8" standalone="no"?>'
+'<!--This file represents the results of running a test suite-->'
+'<test-results name="Test.dll" total="3" failures="1" not-run="0" date="2009-01-05" time="16:32:46">'
+'  <environment nunit-version="2.4.7.0" clr-version="2.0.50727.42" os-version="Unix 2.6.26.1" platform="Unix" cwd="/home/silk/devel/trac/testcheckout" machine-name="tango" user="silk" user-domain="tango" />'
+'  <culture-info current-culture="en-US" current-uiculture="en-US" />'
+'  <test-suite name="Test.dll" success="False" time="0.081" asserts="0">'
+'    <results>'
+    '  <test-suite name="Lib" success="False" time="0.078" asserts="0">'
+    '    <results>'
+        '  <test-suite name="Test" success="False" time="0.078" asserts="0">'
+        '    <results>'
+        '      <test-case name="Lib.Test.KlasaTests.Test2" executed="True" success="True" time="0.001" asserts="1" />'
+    '    </results>'
+    '  </test-suite>'
+    '    </results>'
+    '  </test-suite>'
+'    </results>'
+'  </test-suite>'
+'</test-results>'
+)
+        self.results_xml.close()
+        monotools.nunit(self.ctxt, self.results_xml.name)
+        type, category, generator, xml = self.ctxt.output.pop()
+        self.assertEqual(1, len(xml.children))
+        test_elem = xml.children[0]
+        self.assertEqual('test', test_elem.name)
+        self.assertEqual('0.001', test_elem.attr['duration'])
+        self.assertEqual('success', test_elem.attr['status'])
+        self.assertEqual('Test', test_elem.attr['fixture'])
+
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(NUnitTestCase, 'test'))
+    return suite
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='suite')
--- a/doc/commands.txt
+++ b/doc/commands.txt
@@ -428,6 +428,43 @@
 files to Java source files in the directory `src/tests`.
 
 
+Mono Tools
+==========
+
+A bundle of recipe commands that support tools commonly used by Mono/.NET projects.
+
+:Namespace: ``http://bitten.cmlenz.net/tools/mono``
+:Common prefix: ``mono``
+
+----------------
+``<mono:nunit>``
+----------------
+
+Extracts information about unit test results from a files in NUnit_ XML format.
+
+.. _nunit: http://nunit.org/
+
+Parameters
+----------
+
++----------------+-----------------------------------------------------------+
+| Name           | Description                                               |
++================+===========================================================+
+| ``file``       | Path to the NUnit XML test results file. This can include |
+|                | wildcards, in which case all the file matching the        |
+|                | pattern will be included.                                 |
++----------------+-----------------------------------------------------------+
+
+The ``file`` attribute is required.
+
+Examples
+--------
+
+.. code-block:: xml
+
+  <mono:nunit file="build/tests/TestResult.xml" />
+
+
 PHP Tools
 =========
 
--- a/setup.py
+++ b/setup.py
@@ -71,6 +71,7 @@
             NS + 'c#cunit = bitten.build.ctools:cunit',
             NS + 'c#gcov = bitten.build.ctools:gcov',
             NS + 'c#make = bitten.build.ctools:make',
+            NS + 'mono#nunit = bitten.build.monotools:nunit',
             NS + 'java#ant = bitten.build.javatools:ant',
             NS + 'java#junit = bitten.build.javatools:junit',
             NS + 'java#cobertura = bitten.build.javatools:cobertura',
Copyright (C) 2012-2017 Edgewall Software