helpers.py revision 11571:62f97810876a
1#!/usr/bin/env python 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 40import subprocess 41from threading import Timer 42import time 43import re 44 45class CallTimeoutException(Exception): 46 """Exception that indicates that a process call timed out""" 47 48 def __init__(self, status, stdout, stderr): 49 self.status = status 50 self.stdout = stdout 51 self.stderr = stderr 52 53class ProcessHelper(subprocess.Popen): 54 """Helper class to run child processes. 55 56 This class wraps a subprocess.Popen class and adds support for 57 using it in a with block. When the process goes out of scope, it's 58 automatically terminated. 59 60 with ProcessHelper(["/bin/ls"], stdout=subprocess.PIPE) as p: 61 return p.call() 62 """ 63 def __init__(self, *args, **kwargs): 64 super(ProcessHelper, self).__init__(*args, **kwargs) 65 66 def _terminate_nicely(self, timeout=5): 67 def on_timeout(): 68 self.kill() 69 70 if self.returncode is not None: 71 return self.returncode 72 73 timer = Timer(timeout, on_timeout) 74 self.terminate() 75 status = self.wait() 76 timer.cancel() 77 78 return status 79 80 def __enter__(self): 81 return self 82 83 def __exit__(self, exc_type, exc_value, traceback): 84 if self.returncode is None: 85 self._terminate_nicely() 86 87 def call(self, timeout=0): 88 self._timeout = False 89 def on_timeout(): 90 self._timeout = True 91 self._terminate_nicely() 92 93 status, stdout, stderr = None, None, None 94 timer = Timer(timeout, on_timeout) 95 if timeout: 96 timer.start() 97 98 stdout, stderr = self.communicate() 99 status = self.wait() 100 101 timer.cancel() 102 103 if self._timeout: 104 self._terminate_nicely() 105 raise CallTimeoutException(self.returncode, stdout, stderr) 106 else: 107 return status, stdout, stderr 108 109class FileIgnoreList(object): 110 """Helper class to implement file ignore lists. 111 112 This class implements ignore lists using plain string matching and 113 regular expressions. In the simplest use case, rules are created 114 statically upon initialization: 115 116 ignore_list = FileIgnoreList(name=("ignore_me.txt", ), rex=(r".*~", ) 117 118 Ignores can be queried using in the same ways as normal Python 119 containers: 120 121 if file_name in ignore_list: 122 print "Ignoring %s" % file_name 123 124 125 New rules can be added at runtime by extending the list in the 126 rules attribute: 127 128 ignore_list.rules.append(FileIgnoreList.simple("bar.txt")) 129 """ 130 131 @staticmethod 132 def simple(r): 133 return lambda f: f == r 134 135 @staticmethod 136 def rex(r): 137 re_obj = r if hasattr(r, "search") else re.compile(r) 138 return lambda name: re_obj.search(name) 139 140 def __init__(self, names=(), rex=()): 141 self.rules = [ FileIgnoreList.simple(n) for n in names ] + \ 142 [ FileIgnoreList.rex(r) for r in rex ] 143 144 def __contains__(self, name): 145 for rule in self.rules: 146 if rule(name): 147 return True 148 return False 149 150if __name__ == "__main__": 151 # Run internal self tests to ensure that the helpers are working 152 # properly. The expected output when running this script is 153 # "SUCCESS!". 154 155 cmd_foo = [ "/bin/echo", "-n", "foo" ] 156 cmd_sleep = [ "/bin/sleep", "10" ] 157 158 # Test that things don't break if the process hasn't been started 159 with ProcessHelper(cmd_foo) as p: 160 pass 161 162 with ProcessHelper(cmd_foo, stdout=subprocess.PIPE) as p: 163 status, stdout, stderr = p.call() 164 assert stdout == "foo" 165 assert status == 0 166 167 try: 168 with ProcessHelper(cmd_sleep) as p: 169 status, stdout, stderr = p.call(timeout=1) 170 assert False, "Timeout not triggered" 171 except CallTimeoutException: 172 pass 173 174 ignore_list = FileIgnoreList( 175 names=("ignore.txt", "foo/test.txt"), 176 rex=(r"~$", re.compile("^#"))) 177 178 assert "ignore.txt" in ignore_list 179 assert "bar.txt" not in ignore_list 180 assert "foo/test.txt" in ignore_list 181 assert "test.txt" not in ignore_list 182 assert "file1.c~" in ignore_list 183 assert "file1.c" not in ignore_list 184 assert "#foo" in ignore_list 185 assert "foo#" not in ignore_list 186 187 ignore_list.rules.append(FileIgnoreList.simple("bar.txt")) 188 assert "bar.txt" in ignore_list 189 190 print "SUCCESS!" 191