| 
					
				 | 
			
			
				@@ -11,7 +11,6 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 # See the License for the specific language governing permissions and 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 # limitations under the License. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 """Run a group of subprocesses and then finish.""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 from __future__ import print_function 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -28,11 +27,9 @@ import tempfile 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import time 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import errno 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 # cpu cost measurement 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 measure_cpu_costs = False 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 _DEFAULT_MAX_JOBS = 16 * multiprocessing.cpu_count() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 _MAX_RESULT_SIZE = 8192 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -42,63 +39,60 @@ _MAX_RESULT_SIZE = 8192 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 # characters to the PR description, which leak into the environment here 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 # and cause failures. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 def strip_non_ascii_chars(s): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  return ''.join(c for c in s if ord(c) < 128) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    return ''.join(c for c in s if ord(c) < 128) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 def sanitized_environment(env): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  sanitized = {} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  for key, value in env.items(): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    sanitized[strip_non_ascii_chars(key)] = strip_non_ascii_chars(value) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  return sanitized 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    sanitized = {} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    for key, value in env.items(): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        sanitized[strip_non_ascii_chars(key)] = strip_non_ascii_chars(value) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    return sanitized 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 def platform_string(): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  if platform.system() == 'Windows': 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    return 'windows' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  elif platform.system()[:7] == 'MSYS_NT': 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    return 'windows' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  elif platform.system() == 'Darwin': 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    return 'mac' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  elif platform.system() == 'Linux': 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    return 'linux' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    return 'posix' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if platform.system() == 'Windows': 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return 'windows' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    elif platform.system()[:7] == 'MSYS_NT': 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return 'windows' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    elif platform.system() == 'Darwin': 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return 'mac' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    elif platform.system() == 'Linux': 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return 'linux' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return 'posix' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 # setup a signal handler so that signal.pause registers 'something' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 # when a child finishes 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 # not using futures and threading to avoid a dependency on subprocess32 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 if platform_string() == 'windows': 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  pass 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  def alarm_handler(unused_signum, unused_frame): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     pass 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  signal.signal(signal.SIGCHLD, lambda unused_signum, unused_frame: None) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  signal.signal(signal.SIGALRM, alarm_handler) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def alarm_handler(unused_signum, unused_frame): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        pass 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    signal.signal(signal.SIGCHLD, lambda unused_signum, unused_frame: None) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    signal.signal(signal.SIGALRM, alarm_handler) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 _SUCCESS = object() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 _FAILURE = object() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 _RUNNING = object() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 _KILLED = object() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 _COLORS = { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    'red': [ 31, 0 ], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    'green': [ 32, 0 ], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    'yellow': [ 33, 0 ], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    'lightgray': [ 37, 0], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    'gray': [ 30, 1 ], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    'purple': [ 35, 0 ], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    'cyan': [ 36, 0 ] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    'red': [31, 0], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    'green': [32, 0], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    'yellow': [33, 0], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    'lightgray': [37, 0], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    'gray': [30, 1], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    'purple': [35, 0], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    'cyan': [36, 0] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 _BEGINNING_OF_LINE = '\x1b[0G' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 _CLEAR_LINE = '\x1b[2K' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 _TAG_COLOR = { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     'FAILED': 'red', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     'FLAKE': 'purple', 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -111,392 +105,436 @@ _TAG_COLOR = { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     'SUCCESS': 'green', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     'IDLE': 'gray', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     'SKIPPED': 'cyan' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 _FORMAT = '%(asctime)-15s %(message)s' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 logging.basicConfig(level=logging.INFO, format=_FORMAT) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 def eintr_be_gone(fn): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  """Run fn until it doesn't stop because of EINTR""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  while True: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    try: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      return fn() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    except IOError, e: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      if e.errno != errno.EINTR: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        raise 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    """Run fn until it doesn't stop because of EINTR""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    while True: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        try: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return fn() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        except IOError, e: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if e.errno != errno.EINTR: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                raise 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 def message(tag, msg, explanatory_text=None, do_newline=False): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  if message.old_tag == tag and message.old_msg == msg and not explanatory_text: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    return 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  message.old_tag = tag 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  message.old_msg = msg 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  while True: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    try: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      if platform_string() == 'windows' or not sys.stdout.isatty(): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        if explanatory_text: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          logging.info(explanatory_text) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        logging.info('%s: %s', tag, msg) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        sys.stdout.write('%s%s%s\x1b[%d;%dm%s\x1b[0m: %s%s' % ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            _BEGINNING_OF_LINE, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            _CLEAR_LINE, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            '\n%s' % explanatory_text if explanatory_text is not None else '', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            _COLORS[_TAG_COLOR[tag]][1], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            _COLORS[_TAG_COLOR[tag]][0], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            tag, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            msg, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            '\n' if do_newline or explanatory_text is not None else '')) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      sys.stdout.flush() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      return 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    except IOError, e: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      if e.errno != errno.EINTR: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        raise 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if message.old_tag == tag and message.old_msg == msg and not explanatory_text: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    message.old_tag = tag 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    message.old_msg = msg 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    while True: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        try: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if platform_string() == 'windows' or not sys.stdout.isatty(): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if explanatory_text: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    logging.info(explanatory_text) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                logging.info('%s: %s', tag, msg) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                sys.stdout.write('%s%s%s\x1b[%d;%dm%s\x1b[0m: %s%s' % ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    _BEGINNING_OF_LINE, _CLEAR_LINE, '\n%s' % explanatory_text 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    if explanatory_text is not None else '', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    _COLORS[_TAG_COLOR[tag]][1], _COLORS[_TAG_COLOR[tag]][0], 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    tag, msg, '\n' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    if do_newline or explanatory_text is not None else '')) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            sys.stdout.flush() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        except IOError, e: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if e.errno != errno.EINTR: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                raise 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 message.old_tag = '' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 message.old_msg = '' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 def which(filename): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  if '/' in filename: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    return filename 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  for path in os.environ['PATH'].split(os.pathsep): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    if os.path.exists(os.path.join(path, filename)): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      return os.path.join(path, filename) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  raise Exception('%s not found' % filename) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if '/' in filename: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return filename 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    for path in os.environ['PATH'].split(os.pathsep): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if os.path.exists(os.path.join(path, filename)): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return os.path.join(path, filename) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    raise Exception('%s not found' % filename) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 class JobSpec(object): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  """Specifies what to run for a job.""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  def __init__(self, cmdline, shortname=None, environ=None, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-               cwd=None, shell=False, timeout_seconds=5*60, flake_retries=0, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-               timeout_retries=0, kill_handler=None, cpu_cost=1.0, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-               verbose_success=False): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    """ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    """Specifies what to run for a job.""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def __init__(self, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                 cmdline, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                 shortname=None, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                 environ=None, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                 cwd=None, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                 shell=False, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                 timeout_seconds=5 * 60, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                 flake_retries=0, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                 timeout_retries=0, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                 kill_handler=None, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                 cpu_cost=1.0, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                 verbose_success=False): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        """ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     Arguments: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       cmdline: a list of arguments to pass as the command line 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       environ: a dictionary of environment variables to set in the child process 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       kill_handler: a handler that will be called whenever job.kill() is invoked 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       cpu_cost: number of cores per second this job needs 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     """ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    if environ is None: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      environ = {} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self.cmdline = cmdline 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self.environ = environ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self.shortname = cmdline[0] if shortname is None else shortname 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self.cwd = cwd 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self.shell = shell 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self.timeout_seconds = timeout_seconds 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self.flake_retries = flake_retries 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self.timeout_retries = timeout_retries 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self.kill_handler = kill_handler 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self.cpu_cost = cpu_cost 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self.verbose_success = verbose_success 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  def identity(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    return '%r %r' % (self.cmdline, self.environ) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  def __hash__(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    return hash(self.identity()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  def __cmp__(self, other): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    return self.identity() == other.identity() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  def __repr__(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    return 'JobSpec(shortname=%s, cmdline=%s)' % (self.shortname, self.cmdline) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  def __str__(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    return '%s: %s %s' % (self.shortname, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                          ' '.join('%s=%s' % kv for kv in self.environ.items()), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                          ' '.join(self.cmdline)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if environ is None: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            environ = {} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.cmdline = cmdline 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.environ = environ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.shortname = cmdline[0] if shortname is None else shortname 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.cwd = cwd 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.shell = shell 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.timeout_seconds = timeout_seconds 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.flake_retries = flake_retries 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.timeout_retries = timeout_retries 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.kill_handler = kill_handler 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.cpu_cost = cpu_cost 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.verbose_success = verbose_success 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def identity(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return '%r %r' % (self.cmdline, self.environ) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def __hash__(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return hash(self.identity()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def __cmp__(self, other): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return self.identity() == other.identity() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def __repr__(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return 'JobSpec(shortname=%s, cmdline=%s)' % (self.shortname, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                                      self.cmdline) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def __str__(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return '%s: %s %s' % (self.shortname, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                              ' '.join('%s=%s' % kv 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                       for kv in self.environ.items()), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                              ' '.join(self.cmdline)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 class JobResult(object): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  def __init__(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self.state = 'UNKNOWN' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self.returncode = -1 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self.elapsed_time = 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self.num_failures = 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self.retries = 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self.message = '' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self.cpu_estimated = 1 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self.cpu_measured = 1 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def __init__(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.state = 'UNKNOWN' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.returncode = -1 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.elapsed_time = 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.num_failures = 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.retries = 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.message = '' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.cpu_estimated = 1 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.cpu_measured = 1 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 def read_from_start(f): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  f.seek(0) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  return f.read() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    f.seek(0) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    return f.read() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 class Job(object): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  """Manages one job.""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  def __init__(self, spec, newline_on_success, travis, add_env, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-               quiet_success=False): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self._spec = spec 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self._newline_on_success = newline_on_success 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self._travis = travis 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self._add_env = add_env.copy() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self._retries = 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self._timeout_retries = 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self._suppress_failure_message = False 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self._quiet_success = quiet_success 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    if not self._quiet_success: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      message('START', spec.shortname, do_newline=self._travis) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self.result = JobResult() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self.start() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  def GetSpec(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    return self._spec 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  def start(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self._tempfile = tempfile.TemporaryFile() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    env = dict(os.environ) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    env.update(self._spec.environ) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    env.update(self._add_env) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    env = sanitized_environment(env) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self._start = time.time() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    cmdline = self._spec.cmdline 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    # The Unix time command is finicky when used with MSBuild, so we don't use it 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    # with jobs that run MSBuild. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    global measure_cpu_costs 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    if measure_cpu_costs and not 'vsprojects\\build' in cmdline[0]: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      cmdline = ['time', '-p'] + cmdline 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      measure_cpu_costs = False 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    try_start = lambda: subprocess.Popen(args=cmdline, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                                         stderr=subprocess.STDOUT, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                                         stdout=self._tempfile, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                                         cwd=self._spec.cwd, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                                         shell=self._spec.shell, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                                         env=env) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    delay = 0.3 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    for i in range(0, 4): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      try: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        self._process = try_start() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        break 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      except OSError: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        message('WARNING', 'Failed to start %s, retrying in %f seconds' % (self._spec.shortname, delay)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        time.sleep(delay) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        delay *= 2 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      self._process = try_start() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self._state = _RUNNING 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  def state(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    """Poll current state of the job. Prints messages at completion.""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    def stdout(self=self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      stdout = read_from_start(self._tempfile) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      self.result.message = stdout[-_MAX_RESULT_SIZE:] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      return stdout 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    if self._state == _RUNNING and self._process.poll() is not None: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      elapsed = time.time() - self._start 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      self.result.elapsed_time = elapsed 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      if self._process.returncode != 0: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        if self._retries < self._spec.flake_retries: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          message('FLAKE', '%s [ret=%d, pid=%d]' % ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            self._spec.shortname, self._process.returncode, self._process.pid), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            stdout(), do_newline=True) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          self._retries += 1 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          self.result.num_failures += 1 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          self.result.retries = self._timeout_retries + self._retries 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          # NOTE: job is restarted regardless of jobset's max_time setting 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          self.start() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          self._state = _FAILURE 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          if not self._suppress_failure_message: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            message('FAILED', '%s [ret=%d, pid=%d, time=%.1fsec]' % ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                self._spec.shortname, self._process.returncode, self._process.pid, elapsed), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                stdout(), do_newline=True) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          self.result.state = 'FAILED' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          self.result.num_failures += 1 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          self.result.returncode = self._process.returncode 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        self._state = _SUCCESS 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        measurement = '' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        if measure_cpu_costs: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          m = re.search(r'real\s+([0-9.]+)\nuser\s+([0-9.]+)\nsys\s+([0-9.]+)', stdout()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          real = float(m.group(1)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          user = float(m.group(2)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          sys = float(m.group(3)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          if real > 0.5: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            cores = (user + sys) / real 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            self.result.cpu_measured = float('%.01f' % cores) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            self.result.cpu_estimated = float('%.01f' % self._spec.cpu_cost) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            measurement = '; cpu_cost=%.01f; estimated=%.01f' % (self.result.cpu_measured, self.result.cpu_estimated) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    """Manages one job.""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def __init__(self, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                 spec, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                 newline_on_success, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                 travis, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                 add_env, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                 quiet_success=False): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._spec = spec 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._newline_on_success = newline_on_success 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._travis = travis 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._add_env = add_env.copy() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._retries = 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._timeout_retries = 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._suppress_failure_message = False 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._quiet_success = quiet_success 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         if not self._quiet_success: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          message('PASSED', '%s [time=%.1fsec, retries=%d:%d%s]' % ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              self._spec.shortname, elapsed, self._retries, self._timeout_retries, measurement), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              stdout() if self._spec.verbose_success else None, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              do_newline=self._newline_on_success or self._travis) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        self.result.state = 'PASSED' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    elif (self._state == _RUNNING and 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          self._spec.timeout_seconds is not None and 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          time.time() - self._start > self._spec.timeout_seconds): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      elapsed = time.time() - self._start 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      self.result.elapsed_time = elapsed 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      if self._timeout_retries < self._spec.timeout_retries: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        message('TIMEOUT_FLAKE', '%s [pid=%d]' % (self._spec.shortname, self._process.pid), stdout(), do_newline=True) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        self._timeout_retries += 1 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        self.result.num_failures += 1 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        self.result.retries = self._timeout_retries + self._retries 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        if self._spec.kill_handler: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          self._spec.kill_handler(self) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        self._process.terminate() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        # NOTE: job is restarted regardless of jobset's max_time setting 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            message('START', spec.shortname, do_newline=self._travis) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.result = JobResult() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         self.start() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        message('TIMEOUT', '%s [pid=%d, time=%.1fsec]' % (self._spec.shortname, self._process.pid, elapsed), stdout(), do_newline=True) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        self.kill() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        self.result.state = 'TIMEOUT' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        self.result.num_failures += 1 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    return self._state 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  def kill(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    if self._state == _RUNNING: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      self._state = _KILLED 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      if self._spec.kill_handler: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        self._spec.kill_handler(self) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      self._process.terminate() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  def suppress_failure_message(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self._suppress_failure_message = True 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def GetSpec(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return self._spec 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def start(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._tempfile = tempfile.TemporaryFile() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        env = dict(os.environ) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        env.update(self._spec.environ) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        env.update(self._add_env) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        env = sanitized_environment(env) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._start = time.time() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        cmdline = self._spec.cmdline 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        # The Unix time command is finicky when used with MSBuild, so we don't use it 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        # with jobs that run MSBuild. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        global measure_cpu_costs 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if measure_cpu_costs and not 'vsprojects\\build' in cmdline[0]: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            cmdline = ['time', '-p'] + cmdline 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            measure_cpu_costs = False 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        try_start = lambda: subprocess.Popen(args=cmdline, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                             stderr=subprocess.STDOUT, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                             stdout=self._tempfile, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                             cwd=self._spec.cwd, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                             shell=self._spec.shell, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                             env=env) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        delay = 0.3 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        for i in range(0, 4): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            try: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                self._process = try_start() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                break 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            except OSError: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                message('WARNING', 'Failed to start %s, retrying in %f seconds' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        % (self._spec.shortname, delay)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                time.sleep(delay) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                delay *= 2 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            self._process = try_start() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._state = _RUNNING 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def state(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        """Poll current state of the job. Prints messages at completion.""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        def stdout(self=self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            stdout = read_from_start(self._tempfile) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            self.result.message = stdout[-_MAX_RESULT_SIZE:] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return stdout 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if self._state == _RUNNING and self._process.poll() is not None: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            elapsed = time.time() - self._start 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            self.result.elapsed_time = elapsed 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if self._process.returncode != 0: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if self._retries < self._spec.flake_retries: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    message( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        'FLAKE', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        '%s [ret=%d, pid=%d]' % 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        (self._spec.shortname, self._process.returncode, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                         self._process.pid), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        stdout(), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        do_newline=True) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    self._retries += 1 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    self.result.num_failures += 1 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    self.result.retries = self._timeout_retries + self._retries 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    # NOTE: job is restarted regardless of jobset's max_time setting 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    self.start() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    self._state = _FAILURE 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    if not self._suppress_failure_message: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        message( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            'FAILED', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            '%s [ret=%d, pid=%d, time=%.1fsec]' % 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            (self._spec.shortname, self._process.returncode, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                             self._process.pid, elapsed), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            stdout(), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            do_newline=True) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    self.result.state = 'FAILED' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    self.result.num_failures += 1 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    self.result.returncode = self._process.returncode 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                self._state = _SUCCESS 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                measurement = '' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if measure_cpu_costs: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    m = re.search( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        r'real\s+([0-9.]+)\nuser\s+([0-9.]+)\nsys\s+([0-9.]+)', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        stdout()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    real = float(m.group(1)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    user = float(m.group(2)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    sys = float(m.group(3)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    if real > 0.5: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        cores = (user + sys) / real 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        self.result.cpu_measured = float('%.01f' % cores) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        self.result.cpu_estimated = float('%.01f' % 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                                          self._spec.cpu_cost) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        measurement = '; cpu_cost=%.01f; estimated=%.01f' % ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            self.result.cpu_measured, self.result.cpu_estimated) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if not self._quiet_success: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    message( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        'PASSED', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        '%s [time=%.1fsec, retries=%d:%d%s]' % 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        (self._spec.shortname, elapsed, self._retries, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                         self._timeout_retries, measurement), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        stdout() if self._spec.verbose_success else None, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        do_newline=self._newline_on_success or self._travis) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                self.result.state = 'PASSED' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        elif (self._state == _RUNNING and 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              self._spec.timeout_seconds is not None and 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+              time.time() - self._start > self._spec.timeout_seconds): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            elapsed = time.time() - self._start 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            self.result.elapsed_time = elapsed 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if self._timeout_retries < self._spec.timeout_retries: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                message( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    'TIMEOUT_FLAKE', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    '%s [pid=%d]' % (self._spec.shortname, self._process.pid), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    stdout(), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    do_newline=True) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                self._timeout_retries += 1 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                self.result.num_failures += 1 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                self.result.retries = self._timeout_retries + self._retries 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if self._spec.kill_handler: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    self._spec.kill_handler(self) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                self._process.terminate() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                # NOTE: job is restarted regardless of jobset's max_time setting 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                self.start() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                message( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    'TIMEOUT', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    '%s [pid=%d, time=%.1fsec]' % 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    (self._spec.shortname, self._process.pid, elapsed), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    stdout(), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    do_newline=True) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                self.kill() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                self.result.state = 'TIMEOUT' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                self.result.num_failures += 1 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return self._state 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def kill(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if self._state == _RUNNING: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            self._state = _KILLED 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if self._spec.kill_handler: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                self._spec.kill_handler(self) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            self._process.terminate() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def suppress_failure_message(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._suppress_failure_message = True 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 class Jobset(object): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  """Manages one run of jobs.""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  def __init__(self, check_cancelled, maxjobs, maxjobs_cpu_agnostic, newline_on_success, travis, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-               stop_on_failure, add_env, quiet_success, max_time): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self._running = set() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self._check_cancelled = check_cancelled 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self._cancelled = False 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self._failures = 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self._completed = 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self._maxjobs = maxjobs 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self._maxjobs_cpu_agnostic = maxjobs_cpu_agnostic 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self._newline_on_success = newline_on_success 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self._travis = travis 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self._stop_on_failure = stop_on_failure 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self._add_env = add_env 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self._quiet_success = quiet_success 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self._max_time = max_time 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self.resultset = {} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self._remaining = None 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self._start_time = time.time() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  def set_remaining(self, remaining): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self._remaining = remaining 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  def get_num_failures(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    return self._failures 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  def cpu_cost(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    c = 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    for job in self._running: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      c += job._spec.cpu_cost 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    return c 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  def start(self, spec): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    """Start a job. Return True on success, False on failure.""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    while True: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      if self._max_time > 0 and time.time() - self._start_time > self._max_time: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        skipped_job_result = JobResult() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        skipped_job_result.state = 'SKIPPED' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        message('SKIPPED', spec.shortname, do_newline=True) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        self.resultset[spec.shortname] = [skipped_job_result] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    """Manages one run of jobs.""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def __init__(self, check_cancelled, maxjobs, maxjobs_cpu_agnostic, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                 newline_on_success, travis, stop_on_failure, add_env, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                 quiet_success, max_time): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._running = set() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._check_cancelled = check_cancelled 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._cancelled = False 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._failures = 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._completed = 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._maxjobs = maxjobs 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._maxjobs_cpu_agnostic = maxjobs_cpu_agnostic 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._newline_on_success = newline_on_success 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._travis = travis 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._stop_on_failure = stop_on_failure 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._add_env = add_env 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._quiet_success = quiet_success 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._max_time = max_time 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self.resultset = {} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._remaining = None 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._start_time = time.time() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def set_remaining(self, remaining): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._remaining = remaining 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def get_num_failures(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return self._failures 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def cpu_cost(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        c = 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        for job in self._running: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            c += job._spec.cpu_cost 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return c 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def start(self, spec): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        """Start a job. Return True on success, False on failure.""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        while True: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if self._max_time > 0 and time.time( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            ) - self._start_time > self._max_time: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                skipped_job_result = JobResult() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                skipped_job_result.state = 'SKIPPED' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                message('SKIPPED', spec.shortname, do_newline=True) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                self.resultset[spec.shortname] = [skipped_job_result] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                return True 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if self.cancelled(): return False 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            current_cpu_cost = self.cpu_cost() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if current_cpu_cost == 0: break 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if current_cpu_cost + spec.cpu_cost <= self._maxjobs: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if len(self._running) < self._maxjobs_cpu_agnostic: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    break 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            self.reap(spec.shortname, spec.cpu_cost) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if self.cancelled(): return False 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        job = Job(spec, self._newline_on_success, self._travis, self._add_env, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  self._quiet_success) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._running.add(job) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if job.GetSpec().shortname not in self.resultset: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            self.resultset[job.GetSpec().shortname] = [] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         return True 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      if self.cancelled(): return False 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      current_cpu_cost = self.cpu_cost() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      if current_cpu_cost == 0: break 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      if current_cpu_cost + spec.cpu_cost <= self._maxjobs: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        if len(self._running) < self._maxjobs_cpu_agnostic: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          break 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      self.reap(spec.shortname, spec.cpu_cost) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    if self.cancelled(): return False 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    job = Job(spec, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              self._newline_on_success, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              self._travis, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              self._add_env, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              self._quiet_success) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self._running.add(job) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    if job.GetSpec().shortname not in self.resultset: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      self.resultset[job.GetSpec().shortname] = [] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    return True 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  def reap(self, waiting_for=None, waiting_for_cost=None): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    """Collect the dead jobs.""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    while self._running: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      dead = set() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      for job in self._running: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        st = eintr_be_gone(lambda: job.state()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        if st == _RUNNING: continue 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        if st == _FAILURE or st == _KILLED: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          self._failures += 1 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          if self._stop_on_failure: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            self._cancelled = True 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def reap(self, waiting_for=None, waiting_for_cost=None): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        """Collect the dead jobs.""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        while self._running: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            dead = set() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             for job in self._running: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              job.kill() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        dead.add(job) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        break 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      for job in dead: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        self._completed += 1 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        if not self._quiet_success or job.result.state != 'PASSED': 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          self.resultset[job.GetSpec().shortname].append(job.result) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        self._running.remove(job) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      if dead: return 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      if not self._travis and platform_string() != 'windows': 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        rstr = '' if self._remaining is None else '%d queued, ' % self._remaining 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        if self._remaining is not None and self._completed > 0: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          now = time.time() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          sofar = now - self._start_time 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          remaining = sofar / self._completed * (self._remaining + len(self._running)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          rstr = 'ETA %.1f sec; %s' % (remaining, rstr) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        if waiting_for is not None: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          wstr = ' next: %s @ %.2f cpu' % (waiting_for, waiting_for_cost) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-          wstr = '' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        message('WAITING', '%s%d jobs running, %d complete, %d failed (load %.2f)%s' % ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            rstr, len(self._running), self._completed, self._failures, self.cpu_cost(), wstr)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      if platform_string() == 'windows': 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        time.sleep(0.1) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        signal.alarm(10) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        signal.pause() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  def cancelled(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    """Poll for cancellation.""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    if self._cancelled: return True 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    if not self._check_cancelled(): return False 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    for job in self._running: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      job.kill() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    self._cancelled = True 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    return True 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  def finish(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    while self._running: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      if self.cancelled(): pass  # poll cancellation 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      self.reap() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    if platform_string() != 'windows': 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      signal.alarm(0) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    return not self.cancelled() and self._failures == 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                st = eintr_be_gone(lambda: job.state()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if st == _RUNNING: continue 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if st == _FAILURE or st == _KILLED: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    self._failures += 1 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    if self._stop_on_failure: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        self._cancelled = True 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        for job in self._running: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            job.kill() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                dead.add(job) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                break 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            for job in dead: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                self._completed += 1 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if not self._quiet_success or job.result.state != 'PASSED': 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    self.resultset[job.GetSpec().shortname].append(job.result) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                self._running.remove(job) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if dead: return 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if not self._travis and platform_string() != 'windows': 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                rstr = '' if self._remaining is None else '%d queued, ' % self._remaining 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if self._remaining is not None and self._completed > 0: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    now = time.time() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    sofar = now - self._start_time 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    remaining = sofar / self._completed * ( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        self._remaining + len(self._running)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    rstr = 'ETA %.1f sec; %s' % (remaining, rstr) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if waiting_for is not None: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    wstr = ' next: %s @ %.2f cpu' % (waiting_for, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                                     waiting_for_cost) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    wstr = '' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                message( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    'WAITING', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    '%s%d jobs running, %d complete, %d failed (load %.2f)%s' % 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    (rstr, len(self._running), self._completed, self._failures, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                     self.cpu_cost(), wstr)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if platform_string() == 'windows': 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                time.sleep(0.1) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            else: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                signal.alarm(10) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                signal.pause() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def cancelled(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        """Poll for cancellation.""" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if self._cancelled: return True 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if not self._check_cancelled(): return False 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        for job in self._running: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            job.kill() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        self._cancelled = True 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return True 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    def finish(self): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        while self._running: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if self.cancelled(): pass  # poll cancellation 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            self.reap() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if platform_string() != 'windows': 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            signal.alarm(0) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return not self.cancelled() and self._failures == 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 def _never_cancelled(): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  return False 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    return False 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 def tag_remaining(xs): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  staging = [] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  for x in xs: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    staging.append(x) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    if len(staging) > 5000: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      yield (staging.pop(0), None) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  n = len(staging) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  for i, x in enumerate(staging): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    yield (x, n - i - 1) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    staging = [] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    for x in xs: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        staging.append(x) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if len(staging) > 5000: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            yield (staging.pop(0), None) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    n = len(staging) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    for i, x in enumerate(staging): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        yield (x, n - i - 1) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 def run(cmdlines, 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -511,23 +549,23 @@ def run(cmdlines, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         skip_jobs=False, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         quiet_success=False, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         max_time=-1): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  if skip_jobs: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    resultset = {} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    skipped_job_result = JobResult() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    skipped_job_result.state = 'SKIPPED' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    for job in cmdlines: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      message('SKIPPED', job.shortname, do_newline=True) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      resultset[job.shortname] = [skipped_job_result] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    return 0, resultset 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  js = Jobset(check_cancelled, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              maxjobs if maxjobs is not None else _DEFAULT_MAX_JOBS, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              maxjobs_cpu_agnostic if maxjobs_cpu_agnostic is not None else _DEFAULT_MAX_JOBS, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              newline_on_success, travis, stop_on_failure, add_env, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              quiet_success, max_time) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  for cmdline, remaining in tag_remaining(cmdlines): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    if not js.start(cmdline): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      break 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    if remaining is not None: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      js.set_remaining(remaining) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  js.finish() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  return js.get_num_failures(), js.resultset 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    if skip_jobs: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        resultset = {} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        skipped_job_result = JobResult() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        skipped_job_result.state = 'SKIPPED' 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        for job in cmdlines: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            message('SKIPPED', job.shortname, do_newline=True) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            resultset[job.shortname] = [skipped_job_result] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return 0, resultset 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    js = Jobset(check_cancelled, maxjobs if maxjobs is not None else 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                _DEFAULT_MAX_JOBS, maxjobs_cpu_agnostic 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if maxjobs_cpu_agnostic is not None else _DEFAULT_MAX_JOBS, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                newline_on_success, travis, stop_on_failure, add_env, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                quiet_success, max_time) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    for cmdline, remaining in tag_remaining(cmdlines): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if not js.start(cmdline): 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            break 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if remaining is not None: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            js.set_remaining(remaining) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    js.finish() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    return js.get_num_failures(), js.resultset 
			 |