Add a script to parse the make test output into XML file so it can be consumed by CITE/Sponge.

Change-Id: I3870d7522d1d25a09575f82e6ef6a29988cbf5fb
diff --git a/Makefile b/Makefile
index add2bfd..edb2d06 100644
--- a/Makefile
+++ b/Makefile
@@ -14,8 +14,8 @@
 	wvtest/wvtestrun $(MAKE) runtests
 
 test_with_output:
-	$(MAKE) runtests > result.txt
-	result_parser.py result.txt result.xml
+	$(MAKE) runtests &> result.txt
+	wvtest_result_converter.py result.txt result.xml
 
 #TODO(apenwarr): use a smarter allocator.
 # We could enable parallelism by depending on $(addsuffix ...) instead of
diff --git a/result_parser.py b/wvtest_result_converter.py
similarity index 60%
rename from result_parser.py
rename to wvtest_result_converter.py
index 01c6c0a..4463f7b 100755
--- a/result_parser.py
+++ b/wvtest_result_converter.py
@@ -1,9 +1,29 @@
 #!/usr/bin/python
-"""Parse the wvtest results from the output of 'make test_with_output'.
+"""Convert the wvtest results into standard gunit xml output.'.
 
 ^Testing ".+" in .+:$ marks starts of a test file
 ^! .+$ marks a test case status
 
+Examples:
+Testing "cwmpd" in unknown:
+  ! unknown:0  running cwmpd  ok
+  ! unknown:0  grep X_CATAWAMPUS-ORG_CATAWAMPUS  ok
+  ! unknown:0  NOT(grep NON_EXISTENT_NAME)  ok
+  make[2]: Leaving directory
+  `/usr/local/google/brucefan/bruno-git/vendor/google/test'
+  make[2]: Entering directory
+  `/usr/local/google/brucefan/bruno-git/vendor/google/test'
+  echo "Testing 002-pytest.py"
+  Testing 002-pytest.py
+  ssh -l root 192.168.1.3 \
+    'cd /tmp/tests && python ./wvtest/wvtest.py 002-pytest.py' </dev/null
+Importing: 002-pytest
+
+Testing "TestBasicPython" in 002-pytest.py:
+  ! 002-pytest.py:14   True ok
+  ! 002-pytest.py:15   1 == 1 ok
+  ! 002-pytest.py:16   1 != 2 ok
+
 The test result will be saved to a .xml file that can be used by our dashboard
 system to stream the data into Sponge easily.
 """
@@ -11,50 +31,12 @@
 __author__ = 'brucefan@google.com (Chun Fan)'
 
 
+from lxml import etree
 import os
 import re
 import sys
 
 
-SUITES_TEMPLATE = """
-<testsuites disabled="0" errors="{num_of_errors}"
-  failures="{num_of_failures}" tests="{num_of_tests}"
-  name="{suites_name}" time="0.0">
-  {test_suites}
-</testsuites>
-"""
-
-SUITE_TEMPLATE = """
-<testsuite disabled="0" errors="{suite_errors}"
-  failures="{suite_failures}" name="{suite_name}"
-  tests="{suite_tests}" time="0.0">
-  {test_cases}
-</testsuite>
-"""
-
-TEST_CASE_PASS_TEMPLATE = """
-<testcase classname="{class_name}" name="{test_case_name}"
-  status="run" time="0.0"/>
-"""
-
-TEST_CASE_FAILURE_TEMPLATE = """
-<testcase classname="{class_name}" name="{test_case_name}"
-  status="run" time="0.0">
-  <failure>
-    {result_msg}
-  </failure>
-</testcase>
-"""
-
-TEST_CASE_ERROR_TEMPLATE = """
-<testcase classname="%(class_name)s" name="%(test_case_name)s"
-  status="run" time="0.0">
-  <error>
-    {result_msg}
-  </error>
-</testcase>
-"""
-
 class TestSuitesResult(object):
 
   def __init__(self, name):
@@ -65,18 +47,20 @@
     self.test_suite_results.append(test_suite_result)
 
   def ToXml(self):
-    self.test_suites_xml = '\n'.join(
-        [ts.ToXml() for ts in self.test_suite_results])
+    el = etree.Element('testsuites')
+    el.set('name', self.name)
+    el.set('disabled', '0')
+    el.set('time', '0.0')
+    el.set('errors', '0')
+    for ts in self.test_suite_results:
+      el.append(ts.ToXml())
     self.total_tests = sum(
         [ts.total_tests for ts in self.test_suite_results])
     self.failures = sum(
         [ts.failures for ts in self.test_suite_results])
-    return SUITES_TEMPLATE.format(
-        suites_name=self.name,
-        num_of_errors=0,
-        num_of_failures=self.failures,
-        num_of_tests=self.total_tests,
-        test_suites=self.test_suites_xml)
+    el.set('failures', str(self.failures))
+    el.set('tests', str(self.total_tests))
+    return el
 
 
 class TestSuiteResult(object):
@@ -89,21 +73,23 @@
     self.test_case_results.append(test_case_result)
 
   def ToXml(self):
-    self.test_cases_xml = '\n'.join(
-        [tc.ToXml() for tc in self.test_case_results])
+    el = etree.Element('testsuite')
+    el.set('name', self.name)
+    el.set('disabled', '0')
+    el.set('time', '0.0')
+    el.set('errors', '0')
+    for tc in self.test_case_results:
+      el.append(tc.ToXml())
     self.total_tests = len(self.test_case_results)
     self.failures = len([tc for tc in self.test_case_results if tc.result])
-    return SUITE_TEMPLATE.format(
-        suite_name=self.name,
-        suite_errors=0,
-        suite_failures=self.failures,
-        suite_tests=self.total_tests,
-        test_cases=self.test_cases_xml)
+    el.set('failures', str(self.failures))
+    el.set('tests', str(self.total_tests))
+    return el
 
 
 class TestCaseResult(object):
 
-  PASS = 0
+  PASSED = 0
   FAILED = 1
   ERROR = 2
 
@@ -112,23 +98,21 @@
     self.class_name = class_name
     self.result = result
     self.result_msg = result_msg
-    self.result_template_map = {
-        TestCaseResult.PASS: TEST_CASE_PASS_TEMPLATE,
-        TestCaseResult.FAILED: TEST_CASE_FAILURE_TEMPLATE,
-        TestCaseResult.ERROR: TEST_CASE_ERROR_TEMPLATE}
 
   def ToXml(self):
-    template = self.result_template_map.get(
-        self.result, TEST_CASE_FAILURE_TEMPLATE)
+    el = etree.Element('testcase')
+    el.set('name', self.name)
+    el.set('classname', self.class_name)
+    el.set('time', '0')
+    el.set('status', 'run')
     if self.result:
       print 'Test case: ', self.name, 'FAILED with result code', self.result
-      return template.format(class_name=self.class_name,
-                             test_case_name=self.name,
-                             result_msg=self.result_msg)
+      failure = etree.Element('failure')
+      failure.text = self.result_msg
+      el.append(failure)
     else:
       print 'Test case: ', self.name, 'PASSED with result code', self.result
-      return template.format(class_name=self.class_name,
-                             test_case_name=self.name)
+    return el
 
 
 def ParseTestResult(result_file, output_xml_file):
@@ -158,7 +142,7 @@
         test_case_name = ' '.join(parts[2:-2]).strip()
         test_case_class_name = '%s-%s' % (
             current_test_suite.name, test_case_name)
-        test_case_result = TestCaseResult.PASS
+        test_case_result = TestCaseResult.PASSED
         test_case_result_msg = ''
         if parts[-1] != 'ok':
           test_case_result = TestCaseResult.FAILED
@@ -172,7 +156,7 @@
                 result_msg=test_case_result_msg))
     # Now it is time to write the xml output
     with open(output_xml_file, 'w') as f:
-      f.write(test_suites.ToXml())
+      f.write(etree.tostring(test_suites.ToXml(), pretty_print=True))
 
 
 def main(argv):