1#!/usr/bin/env python2.7 2# 3# Copyright (c) 2016 ARM Limited 4# All rights reserved 5# 6# The license below extends only to copyright in the software and shall 7# not be construed as granting a license to any other intellectual 8# property including but not limited to intellectual property relating 9# to a hardware implementation of the functionality of the software 10# licensed hereunder. You may use the software subject to the license 11# terms below provided that you ensure that this notice is replicated 12# unmodified and in its entirety in all distributions of the software, 13# modified or unmodified, in source code or in binary form. 14# 15# Redistribution and use in source and binary forms, with or without 16# modification, are permitted provided that the following conditions are 17# met: redistributions of source code must retain the above copyright 18# notice, this list of conditions and the following disclaimer; 19# redistributions in binary form must reproduce the above copyright 20# notice, this list of conditions and the following disclaimer in the 21# documentation and/or other materials provided with the distribution; 22# neither the name of the copyright holders nor the names of its 23# contributors may be used to endorse or promote products derived from 24# this software without specific prior written permission. 25# 26# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 27# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 28# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 29# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 30# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 31# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 32# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 33# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 34# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 35# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 36# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37# 38# Authors: Andreas Sandberg 39 40from __future__ import print_function 41 42import subprocess 43from threading import Timer 44import time 45import re 46 47class CallTimeoutException(Exception): 48 """Exception that indicates that a process call timed out""" 49 50 def __init__(self, status, stdout, stderr): 51 self.status = status 52 self.stdout = stdout 53 self.stderr = stderr 54 55class ProcessHelper(subprocess.Popen): 56 """Helper class to run child processes. 57 58 This class wraps a subprocess.Popen class and adds support for 59 using it in a with block. When the process goes out of scope, it's 60 automatically terminated. 61 62 with ProcessHelper(["/bin/ls"], stdout=subprocess.PIPE) as p: 63 return p.call() 64 """ 65 def __init__(self, *args, **kwargs): 66 super(ProcessHelper, self).__init__(*args, **kwargs) 67 68 def _terminate_nicely(self, timeout=5): 69 def on_timeout(): 70 self.kill() 71 72 if self.returncode is not None: 73 return self.returncode 74 75 timer = Timer(timeout, on_timeout) 76 self.terminate() 77 status = self.wait() 78 timer.cancel() 79 80 return status 81 82 def __enter__(self): 83 return self 84 85 def __exit__(self, exc_type, exc_value, traceback): 86 if self.returncode is None: 87 self._terminate_nicely() 88 89 def call(self, timeout=0): 90 self._timeout = False 91 def on_timeout(): 92 self._timeout = True 93 self._terminate_nicely() 94 95 status, stdout, stderr = None, None, None 96 timer = Timer(timeout, on_timeout) 97 if timeout: 98 timer.start() 99 100 stdout, stderr = self.communicate() 101 status = self.wait() 102 103 timer.cancel() 104 105 if self._timeout: 106 self._terminate_nicely() 107 raise CallTimeoutException(self.returncode, stdout, stderr) 108 else: 109 return status, stdout, stderr 110 111class FileIgnoreList(object): 112 """Helper class to implement file ignore lists. 113 114 This class implements ignore lists using plain string matching and 115 regular expressions. In the simplest use case, rules are created 116 statically upon initialization: 117 118 ignore_list = FileIgnoreList(name=("ignore_me.txt", ), rex=(r".*~", ) 119 120 Ignores can be queried using in the same ways as normal Python 121 containers: 122 123 if file_name in ignore_list: 124 print "Ignoring %s" % file_name 125 126 127 New rules can be added at runtime by extending the list in the 128 rules attribute: 129 130 ignore_list.rules.append(FileIgnoreList.simple("bar.txt")) 131 """ 132 133 @staticmethod 134 def simple(r): 135 return lambda f: f == r 136 137 @staticmethod 138 def rex(r): 139 re_obj = r if hasattr(r, "search") else re.compile(r) 140 return lambda name: re_obj.search(name) 141 142 def __init__(self, names=(), rex=()): 143 self.rules = [ FileIgnoreList.simple(n) for n in names ] + \ 144 [ FileIgnoreList.rex(r) for r in rex ] 145 146 def __contains__(self, name): 147 for rule in self.rules: 148 if rule(name): 149 return True 150 return False 151 152if __name__ == "__main__": 153 # Run internal self tests to ensure that the helpers are working 154 # properly. The expected output when running this script is 155 # "SUCCESS!". 156 157 cmd_foo = [ "/bin/echo", "-n", "foo" ] 158 cmd_sleep = [ "/bin/sleep", "10" ] 159 160 # Test that things don't break if the process hasn't been started 161 with ProcessHelper(cmd_foo) as p: 162 pass 163 164 with ProcessHelper(cmd_foo, stdout=subprocess.PIPE) as p: 165 status, stdout, stderr = p.call() 166 assert stdout == "foo" 167 assert status == 0 168 169 try: 170 with ProcessHelper(cmd_sleep) as p: 171 status, stdout, stderr = p.call(timeout=1) 172 assert False, "Timeout not triggered" 173 except CallTimeoutException: 174 pass 175 176 ignore_list = FileIgnoreList( 177 names=("ignore.txt", "foo/test.txt"), 178 rex=(r"~$", re.compile("^#"))) 179 180 assert "ignore.txt" in ignore_list 181 assert "bar.txt" not in ignore_list 182 assert "foo/test.txt" in ignore_list 183 assert "test.txt" not in ignore_list 184 assert "file1.c~" in ignore_list 185 assert "file1.c" not in ignore_list 186 assert "#foo" in ignore_list 187 assert "foo#" not in ignore_list 188 189 ignore_list.rules.append(FileIgnoreList.simple("bar.txt")) 190 assert "bar.txt" in ignore_list 191 192 print("SUCCESS!") 193