[Piglit] [PATCH 8/9] backend/junit.py: Make writes atomic

Dylan Baker baker.dylan.c at gmail.com
Tue Sep 23 17:55:55 PDT 2014


This patch similarly changes the junit backend to have atomic writes,
like the json backend.

Signed-off-by: Dylan Baker <dylanx.c.baker at intel.com>
---
 framework/backends/junit.py       | 64 +++++++++++++++++++++++++++++----------
 framework/programs/run.py         |  4 ---
 framework/tests/backends_tests.py | 23 ++++++++++++++
 3 files changed, 71 insertions(+), 20 deletions(-)

diff --git a/framework/backends/junit.py b/framework/backends/junit.py
index 10f65b3..ed21471 100644
--- a/framework/backends/junit.py
+++ b/framework/backends/junit.py
@@ -23,6 +23,8 @@
 import os
 import re
 import posixpath
+import itertools
+import shutil
 try:
     from lxml import etree
 except ImportError:
@@ -43,25 +45,53 @@ class JUnitBackend(FSyncMixin, Backend):
     """
     _REPLACE = re.compile(r'[/\\]')
 
-    def __init__(self, dest, junit_test_suffix='', **options):
-        self._file = open(os.path.join(dest, 'results.xml'), 'w')
+    def __init__(self, dest, junit_test_suffix='', start_count=0, **options):
         FSyncMixin.__init__(self, **options)
+        self._dest = dest
         self._test_suffix = junit_test_suffix
+        self._counter = itertools.count(start_count)
 
     def initialize(self, metadata):
-        # Write initial headers and other data that etree cannot write for us
-        self._file.write('<?xml version="1.0" encoding="UTF-8" ?>\n')
-        self._file.write('<testsuites>\n')
-        self._file.write(
-            '<testsuite name="piglit" tests="{}">\n'.format(
-                metadata['test_count']))
-        self._fsync(self._file)
+        """ Do nothing
+
+        Junit doesn't support restore, and doesn't have an initial metadata
+        block to write, so all this method does is create the tests directory
+
+        """
+        tests = os.path.join(self._dest, 'tests')
+        if os.path.exists(tests):
+            shutil.rmtree(tests)
+        os.mkdir(tests)
 
     def finalize(self, metadata=None):
-        self._file.write('</testsuite>\n')
-        self._file.write('</testsuites>\n')
-        self._fsync(self._file)
-        self._file.close()
+        """ Scoop up all of the individual peices and put them together """
+        root = etree.Element('testsuites')
+        piglit = etree.Element('testsuite', name='piglit')
+        root.append(piglit)
+        for each in os.listdir(os.path.join(self._dest, 'tests')):
+            with open(os.path.join(self._dest, 'tests', each), 'r') as f:
+                # parse returns an element tree, and that's not what we want,
+                # we want the first (and only) Element node
+                # If the element cannot be properly parsed then consider it a
+                # failed transaction and ignore it.
+                try:
+                    piglit.append(etree.parse(f).getroot())
+                except etree.XMLSyntaxError:
+                    continue
+
+        # set the test count by counting the number of tests.
+        # This must be bytes or unicode
+        piglit.attrib['tests'] = str(len(piglit))
+
+        with open(os.path.join(self._dest, 'results.xml'), 'w') as f:
+            f.write("<?xml version='1.0' encoding='utf-8'?>\n")
+            # lxml has a pretty print we want to use
+            if etree.__name__ == 'lxml.etree':
+                f.write(etree.tostring(root, pretty_print=True))
+            else:
+                f.write(etree.tostring(root))
+
+        shutil.rmtree(os.path.join(self._dest, 'tests'))
 
     def write_test(self, name, data):
         # Split the name of the test and the group (what junit refers to as
@@ -102,6 +132,8 @@ class JUnitBackend(FSyncMixin, Backend):
         elif data['result'] == 'crash':
             etree.SubElement(element, 'error')
 
-        self._file.write(etree.tostring(element))
-        self._file.write('\n')
-        self._fsync(self._file)
+        t = os.path.join(self._dest, 'tests',
+                         '{}.xml'.format(self._counter.next()))
+        with open(t, 'w') as f:
+            f.write(etree.tostring(element))
+            self._fsync(f)
diff --git a/framework/programs/run.py b/framework/programs/run.py
index 749fb07..e7ec65f 100644
--- a/framework/programs/run.py
+++ b/framework/programs/run.py
@@ -250,10 +250,6 @@ def run(input_):
         options['platform'] = args.platform
     options['name'] = results.name
     options['env'] = core.collect_system_info()
-    # FIXME: this should be the actual count, but profile needs to be
-    # refactored to make that possible because of the flattening pass that is
-    # part of profile.run
-    options['test_count'] = 0
 
     backend = backends.get_backend(args.backend)(
         args.results_path,
diff --git a/framework/tests/backends_tests.py b/framework/tests/backends_tests.py
index d1b3d23..177225d 100644
--- a/framework/tests/backends_tests.py
+++ b/framework/tests/backends_tests.py
@@ -272,3 +272,26 @@ class TestJSONTestFinalize(utils.StaticDirectory):
             test = json.load(f)
 
         nt.assert_equal(baseline, test)
+
+
+def test_junit_skips_bad_tests():
+    """ backends.JUnitBackend skips illformed tests """
+    with utils.tempdir() as tdir:
+        test = backends.JUnitBackend(tdir)
+        test.initialize(BACKEND_INITIAL_META)
+        test.write_test(
+            'a/test/group/test1',
+            results.TestResult({
+                'time': 1.2345,
+                'result': 'pass',
+                'out': 'this is stdout',
+                'err': 'this is stderr',
+            })
+        )
+        with open(os.path.join(tdir, 'tests', '1.xml'), 'w') as f:
+            f.write('bad data')
+
+        try:
+            test.finalize()
+        except etree.XMLSyntaxError as e:
+            raise AssertionError(e)
-- 
2.1.1



More information about the Piglit mailing list