helpers.py (11828:36b064696175) helpers.py (12575:16ada03839d9)
1#!/usr/bin/env python2
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
1#!/usr/bin/env python2
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
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
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
190 print "SUCCESS!"
192 print("SUCCESS!")