# HG changeset patch # User dfraser # Date 1239006040 0 # Node ID 6a8dcbffdce238e7c2dda8dcea2558c1474c3ab9 # Parent cb137f2121b9c8790ff18a2c80823630efdff0f0 Added support for nunit (applied patch from silk in #348) diff --git a/bitten/build/monotools.py b/bitten/build/monotools.py new file mode 100644 --- /dev/null +++ b/bitten/build/monotools.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2005-2007 Christopher Lenz +# Copyright (C) 2006 Matthew Good +# Copyright (C) 2007 Edgewall Software +# Copyright (C) 2009 Grzegorz Sobanski +# 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) + + diff --git a/bitten/build/tests/__init__.py b/bitten/build/tests/__init__.py --- 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()) diff --git a/bitten/build/tests/monotools.py b/bitten/build/tests/monotools.py 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 +# Copyright (C) 2008 Matt Good +# Copyright (C) 2008 Edgewall Software +# Copyright (C) 2009 Grzegorz Sobanski +# 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('' + '' + '') + 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( +'' +'' +'' +' ' +' ' +' ' +' ' +' ' +' ' +' ' +'' +) + 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( +'' +'' +'' +' ' +' ' +' ' +' ' +' ' +' ' +' ' +' ' +' ' +' ' +' ' +' ' +'' +) + 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( +'' +'' +'' +' ' +' ' +' ' +' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' +' ' +' ' +'' +) + 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') diff --git a/doc/commands.txt b/doc/commands.txt --- 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`` + +---------------- +```` +---------------- + +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 + + + + PHP Tools ========= diff --git a/setup.py b/setup.py --- 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',