2 # Copyright (c) 2011 The Native Client Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
6 """Testing Library For Nacl.
22 # Windows does not fully implement os.times functionality. If
23 # _GetTimesPosix were used, the fields for CPU time used in user and
24 # in kernel mode by the process will both be zero. Instead, we must
25 # use the ctypes module and access windll's kernel32 interface to
26 # extract the CPU usage information.
28 if sys.platform[:3] == 'win':
32 class SubprocessCpuTimer:
33 """Timer used to measure user and kernel CPU time expended by a subprocess.
35 A new object of this class should be instantiated just before the
36 subprocess is created, and after the subprocess is finished and
37 waited for (via the wait method of the Popen object), the elapsed
38 time can be obtained by invoking the ElapsedCpuTime of the
39 SubprocessCpuTimer instance.
43 WIN32_PROCESS_TIMES_TICKS_PER_SECOND = 1.0e7
45 # use a class variable to avoid slicing at run-time
46 _use_proc_handle = sys.platform[:3] == 'win'
54 # This works around a bug in the calling conventions for the
55 # times() system call on Linux. This syscall returns a number
56 # of clock ticks since an arbitrary time in the past, but if
57 # this happens to be between -4095 and -1, it is interpreted as
58 # an errno value, and we get an exception here.
59 # Returning 0 as a dummy value may result in ElapsedCpuTime()
60 # below returning a negative value. This is OK for our use
61 # because a test that takes too long is likely to be caught
63 if sys.platform == "linux2":
71 def _GetTimeWindows(proc_handle):
72 if proc_handle is None:
74 creation_time = ctypes.c_ulonglong()
75 exit_time = ctypes.c_ulonglong()
76 kernel_time = ctypes.c_ulonglong()
77 user_time = ctypes.c_ulonglong()
78 rc = ctypes.windll.kernel32.GetProcessTimes(
79 int(proc_handle._handle),
80 ctypes.byref(creation_time),
81 ctypes.byref(exit_time),
82 ctypes.byref(kernel_time),
83 ctypes.byref(user_time))
85 print >>sys.stderr, 'Could not obtain process time'
87 return ((kernel_time.value + user_time.value)
88 / SubprocessCpuTimer.WIN32_PROCESS_TIMES_TICKS_PER_SECOND)
92 def _GetTime(proc_handle):
93 if SubprocessCpuTimer._use_proc_handle:
94 return SubprocessCpuTimer._GetTimeWindows(proc_handle)
95 return SubprocessCpuTimer._GetTimePosix()
99 self._start_time = self._GetTime(None)
102 def ElapsedCpuTime(self, proc_handle):
103 return self._GetTime(proc_handle) - self._start_time
108 def RunTestWithInput(cmd, input_data):
109 """Run a test where we only care about the return code."""
110 assert type(cmd) == list
112 timer = SubprocessCpuTimer()
115 sys.stdout.flush() # Make sure stdout stays in sync on the bots.
116 if type(input_data) == str:
117 p = subprocess.Popen(cmd,
118 bufsize=PopenBufSize(),
119 stdin=subprocess.PIPE)
120 p.communicate(input_data)
122 p = subprocess.Popen(cmd,
123 bufsize=PopenBufSize(),
128 print 'exception: ' + str(sys.exc_info()[1])
134 return (timer.ElapsedCpuTime(p), retcode, failed)
137 def RunTestWithInputOutput(cmd, input_data, capture_stderr=True):
138 """Run a test where we also care about stdin/stdout/stderr.
140 NOTE: this function may have problems with arbitrarily
141 large input or output, especially on windows
142 NOTE: input_data can be either a string or or a file like object,
143 file like objects may be better for large input/output
145 assert type(cmd) == list
151 timer = SubprocessCpuTimer()
153 # Python on windows does not include any notion of SIGPIPE. On
154 # Linux and OSX, Python installs a signal handler for SIGPIPE that
155 # sets the handler to SIG_IGN so that syscalls return -1 with
156 # errno equal to EPIPE, and translates those to exceptions;
157 # unfortunately, the subprocess module fails to reset the handler
158 # for SIGPIPE to SIG_DFL, and the SIG_IGN behavior is inherited.
159 # subprocess.Popen's preexec_fn is apparently okay to use on
160 # Windows, as long as its value is None.
162 if hasattr(signal, 'SIGPIPE'):
163 no_pipe = lambda : signal.signal(signal.SIGPIPE, signal.SIG_DFL)
167 # Only capture stderr if capture_stderr is true
168 stderr = subprocess.PIPE if capture_stderr else None
170 if type(input_data) == str:
171 p = subprocess.Popen(cmd,
172 bufsize=PopenBufSize(),
173 stdin=subprocess.PIPE,
175 stdout=subprocess.PIPE,
176 preexec_fn = no_pipe)
177 stdout, stderr = p.communicate(input_data)
179 # input_data is a file like object
180 p = subprocess.Popen(cmd,
181 bufsize=PopenBufSize(),
184 stdout=subprocess.PIPE,
185 preexec_fn = no_pipe)
186 stdout, stderr = p.communicate()
190 print '@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@'
191 print 'ignoring exception', str(sys.exc_info()[1])
192 print 'return code NOT checked'
193 print 'this seems to be a windows issue'
194 print '@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@'
198 print 'exception: ' + str(sys.exc_info()[1])
202 cpu_time_consumed = 0
204 cpu_time_consumed = timer.ElapsedCpuTime(p)
205 return (cpu_time_consumed, retcode, failed, stdout, stderr)
208 def DiffStringsIgnoringWhiteSpace(a, b):
211 # NOTE: the whitespace stuff seems to be broken in python
212 cruncher = difflib.SequenceMatcher(lambda x: x in ' \t\r', a, b)
214 for group in cruncher.get_grouped_opcodes():
216 for tag, i1, i2, j1, j2 in group:
221 i1, i2, j1, j2 = group[0][1], group[-1][2], group[0][3], group[-1][4]
222 yield '@@ -%d,%d +%d,%d @@\n' % (i1+1, i2-i1, j1+1, j2-j1)
224 for tag, i1, i2, j1, j2 in group:
226 for line in a[i1:i2]:
227 yield ' [' + line + ']'
229 if tag == 'replace' or tag == 'delete':
230 for line in a[i1:i2]:
231 yield '-[' + repr(line) + ']'
232 if tag == 'replace' or tag == 'insert':
233 for line in b[j1:j2]:
234 yield '+[' + repr(line) + ']'
237 def RegexpFilterLines(regexp, inverse, group_only, lines):
238 """Apply regexp to filter lines of text, keeping only those lines
241 Any carriage return / newline sequence is turned into a newline.
244 regexp: A regular expression, only lines that match are kept
245 inverse: Only keep lines that do not match
246 group_only: replace matching lines with the regexp groups,
247 text outside the groups are omitted, useful for
248 eliminating file names that might change, etc).
250 lines: A string containing newline-separated lines of text
253 Filtered lines of text, newline separated.
257 nfa = re.compile(regexp)
258 for line in lines.split('\n'):
259 if line.endswith('\r'):
261 mobj = nfa.search(line)
264 if not mobj and not inverse:
269 for s in mobj.groups():
271 matched_strings.append(s)
272 result.append(''.join(matched_strings))
276 return '\n'.join(result)
279 def MakeTempDir(env, **kwargs):
280 """Create a temporary directory and arrange to clean it up on exit.
282 Passes arguments through to tempfile.mkdtemp
284 temporary_dir = tempfile.mkdtemp(**kwargs)
287 # Try to remove the dir but only if it exists. Some tests may clean up
289 if os.path.exists(temporary_dir):
290 shutil.rmtree(temporary_dir)
291 except BaseException as e:
292 sys.stderr.write('Unable to delete dir %s on exit: %s\n' % (
294 atexit.register(Cleanup)
297 def MakeTempFile(env, **kwargs):
298 """Create a temporary file and arrange to clean it up on exit.
300 Passes arguments through to tempfile.mkstemp
302 handle, path = tempfile.mkstemp()
305 # Try to remove the file but only if it exists. Some tests may clean up
307 if os.path.exists(path):
309 except BaseException as e:
310 sys.stderr.write('Unable to delete file %s on exit: %s\n' % (
312 atexit.register(Cleanup)