| 
					
				 | 
			
			
				@@ -34,10 +34,12 @@ import multiprocessing 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import os 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import platform 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import signal 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import string 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import subprocess 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import sys 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import tempfile 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import time 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import xml.etree.cElementTree as ET 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 _DEFAULT_MAX_JOBS = 16 * multiprocessing.cpu_count() 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -159,7 +161,7 @@ class JobSpec(object): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 class Job(object): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   """Manages one job.""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  def __init__(self, spec, bin_hash, newline_on_success, travis): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  def __init__(self, spec, bin_hash, newline_on_success, travis, xml_report): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     self._spec = spec 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     self._bin_hash = bin_hash 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     self._tempfile = tempfile.TemporaryFile() 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -176,19 +178,27 @@ class Job(object): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     self._state = _RUNNING 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     self._newline_on_success = newline_on_success 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     self._travis = travis 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    self._xml_test = ET.SubElement(xml_report, 'testcase', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                   name=self._spec.shortname) if xml_report is not None else None 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     message('START', spec.shortname, do_newline=self._travis) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   def state(self, update_cache): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     """Poll current state of the job. Prints messages at completion.""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     if self._state == _RUNNING and self._process.poll() is not None: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       elapsed = time.time() - self._start 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      self._tempfile.seek(0) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      stdout = self._tempfile.read() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      filtered_stdout = filter(lambda x: x in string.printable, stdout.decode(errors='ignore')) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      if self._xml_test is not None: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._xml_test.set('time', str(elapsed)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        ET.SubElement(self._xml_test, 'system-out').text = filtered_stdout 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       if self._process.returncode != 0: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         self._state = _FAILURE 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        self._tempfile.seek(0) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        stdout = self._tempfile.read() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         message('FAILED', '%s [ret=%d, pid=%d]' % ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             self._spec.shortname, self._process.returncode, self._process.pid), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             stdout, do_newline=True) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if self._xml_test is not None: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          ET.SubElement(self._xml_test, 'failure', message='Failure').text 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         self._state = _SUCCESS 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         message('PASSED', '%s [time=%.1fsec]' % (self._spec.shortname, elapsed), 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -200,6 +210,9 @@ class Job(object): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       stdout = self._tempfile.read() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       message('TIMEOUT', self._spec.shortname, stdout, do_newline=True) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       self.kill() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      if self._xml_test is not None: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        ET.SubElement(self._xml_test, 'system-out').text = stdout 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        ET.SubElement(self._xml_test, 'error', message='Timeout') 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     return self._state 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   def kill(self): 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -212,7 +225,7 @@ class Jobset(object): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   """Manages one run of jobs.""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   def __init__(self, check_cancelled, maxjobs, newline_on_success, travis, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-               stop_on_failure, cache): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+               stop_on_failure, cache, xml_report): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     self._running = set() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     self._check_cancelled = check_cancelled 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     self._cancelled = False 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -224,6 +237,7 @@ class Jobset(object): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     self._cache = cache 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     self._stop_on_failure = stop_on_failure 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     self._hashes = {} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    self._xml_report = xml_report 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   def start(self, spec): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     """Start a job. Return True on success, False on failure.""" 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -250,7 +264,8 @@ class Jobset(object): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         self._running.add(Job(spec, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                               bin_hash, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                               self._newline_on_success, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                              self._travis)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                              self._travis, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                              self._xml_report)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       except: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         message('FAILED', spec.shortname) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         self._cancelled = True 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -324,11 +339,13 @@ def run(cmdlines, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         travis=False, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         infinite_runs=False, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         stop_on_failure=False, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        cache=None): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        cache=None, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        xml_report=None): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   js = Jobset(check_cancelled, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               maxjobs if maxjobs is not None else _DEFAULT_MAX_JOBS, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				               newline_on_success, travis, stop_on_failure, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              cache if cache is not None else NoCache()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              cache if cache is not None else NoCache(), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              xml_report) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   for cmdline in cmdlines: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     if not js.start(cmdline): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       break 
			 |