From 18792dd22d0b0dde457a883a0f16c782a3e298a0 Mon Sep 17 00:00:00 2001
From: Olivier Martin <olivier@labapart.com>
Date: Tue, 28 Jun 2022 22:54:29 +0200
Subject: [PATCH] xmlrunner: Expose python test class docstrings as comments in
 the XML result file

* xmlrunner: Expose python test class docstrings as comments in the XML result file

These comments could for instance be retrieved by XSLT to add
description to test suite.

* tests: Added Python Docstring to test new support
---
 tests/django_example/app/tests.py |  2 ++
 xmlrunner/result.py               | 26 +++++++++++++++++++++-----
 2 files changed, 23 insertions(+), 5 deletions(-)

diff --git a/tests/django_example/app/tests.py b/tests/django_example/app/tests.py
index 663a858..eb18a41 100644
--- a/tests/django_example/app/tests.py
+++ b/tests/django_example/app/tests.py
@@ -3,6 +3,8 @@
 
 # Create your tests here.
 class DummyTestCase(TestCase):
+    """Collection of dummy test cases"""
+
     def test_pass(self):
         """Test Pass"""
         pass
diff --git a/xmlrunner/result.py b/xmlrunner/result.py
index 96fc675..b338a57 100644
--- a/xmlrunner/result.py
+++ b/xmlrunner/result.py
@@ -170,6 +170,10 @@ def __init__(self, test_result, test_method, outcome=SUCCESS, err=None, subTest=
         self.lineno = lineno
         self.doc = doc
 
+        # 'test_method' actually represents the test class that will lead to a
+        # 'testsuite' XML node
+        self.suite_doc = test_method.__doc__
+
     def id(self):
         return self.test_id
 
@@ -465,8 +469,11 @@ def _get_info_by_testcase(self):
                     test_info = test_info[0]
                 testcase_name = test_info.test_name
                 if testcase_name not in tests_by_testcase:
-                    tests_by_testcase[testcase_name] = []
-                tests_by_testcase[testcase_name].append(test_info)
+                    tests_by_testcase[testcase_name] = {
+                        'suite_doc': test_info.suite_doc,
+                        'tests': []
+                    }
+                tests_by_testcase[testcase_name]['tests'].append(test_info)
 
         return tests_by_testcase
 
@@ -482,7 +489,7 @@ def _report_testsuite_properties(xml_testsuite, xml_document, properties):
 
     _report_testsuite_properties = staticmethod(_report_testsuite_properties)
 
-    def _report_testsuite(suite_name, tests, xml_document, parentElement,
+    def _report_testsuite(suite_name, suite_doc, tests, xml_document, parentElement,
                           properties):
         """
         Appends the testsuite section to the XML document.
@@ -512,6 +519,12 @@ def _report_testsuite(suite_name, tests, xml_document, parentElement,
         skips = filter(lambda e: e.outcome == _TestInfo.SKIP, tests)
         testsuite.setAttribute('skipped', str(len(list(skips))))
 
+        if suite_doc:
+            comment = str(suite_doc)
+            # The use of '--' is forbidden in XML comments
+            comment = comment.replace('--', '&#45;&#45;')
+            testsuite.appendChild(xml_document.createComment(safe_unicode(comment)))
+
         _XMLTestResult._report_testsuite_properties(
             testsuite, xml_document, properties)
 
@@ -633,7 +646,10 @@ def generate_reports(self, test_runner):
             doc.appendChild(testsuite)
             parentElement = testsuite
 
-        for suite, tests in all_results.items():
+        for suite, suite_info in all_results.items():
+            suite_doc = suite_info['suite_doc']
+            tests = suite_info['tests']
+
             if outputHandledAsString:
                 doc = Document()
                 parentElement = doc
@@ -645,7 +661,7 @@ def generate_reports(self, test_runner):
 
             # Build the XML file
             testsuite = _XMLTestResult._report_testsuite(
-                suite_name, tests, doc, parentElement, self.properties
+                suite_name, suite_doc, tests, doc, parentElement, self.properties
             )
 
             if outputHandledAsString: