1 | #!/usr/bin/env python
|
---|
2 | #
|
---|
3 | # Copyright 2006, Google Inc.
|
---|
4 | # All rights reserved.
|
---|
5 | #
|
---|
6 | # Redistribution and use in source and binary forms, with or without
|
---|
7 | # modification, are permitted provided that the following conditions are
|
---|
8 | # met:
|
---|
9 | #
|
---|
10 | # * Redistributions of source code must retain the above copyright
|
---|
11 | # notice, this list of conditions and the following disclaimer.
|
---|
12 | # * Redistributions in binary form must reproduce the above
|
---|
13 | # copyright notice, this list of conditions and the following disclaimer
|
---|
14 | # in the documentation and/or other materials provided with the
|
---|
15 | # distribution.
|
---|
16 | # * Neither the name of Google Inc. nor the names of its
|
---|
17 | # contributors may be used to endorse or promote products derived from
|
---|
18 | # this software without specific prior written permission.
|
---|
19 | #
|
---|
20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
---|
21 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
---|
22 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
---|
23 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
---|
24 | # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
---|
25 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
---|
26 | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
---|
27 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
---|
28 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
---|
29 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
---|
30 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
---|
31 |
|
---|
32 | """Unit test utilities for Google C++ Testing Framework."""
|
---|
33 |
|
---|
34 | __author__ = 'wan@google.com (Zhanyong Wan)'
|
---|
35 |
|
---|
36 | import atexit
|
---|
37 | import os
|
---|
38 | import shutil
|
---|
39 | import sys
|
---|
40 | import tempfile
|
---|
41 | import unittest
|
---|
42 | _test_module = unittest
|
---|
43 |
|
---|
44 | # Suppresses the 'Import not at the top of the file' lint complaint.
|
---|
45 | # pylint: disable-msg=C6204
|
---|
46 | try:
|
---|
47 | import subprocess
|
---|
48 | _SUBPROCESS_MODULE_AVAILABLE = True
|
---|
49 | except:
|
---|
50 | import popen2
|
---|
51 | _SUBPROCESS_MODULE_AVAILABLE = False
|
---|
52 | # pylint: enable-msg=C6204
|
---|
53 |
|
---|
54 | GTEST_OUTPUT_VAR_NAME = 'GTEST_OUTPUT'
|
---|
55 |
|
---|
56 | IS_WINDOWS = os.name == 'nt'
|
---|
57 | IS_CYGWIN = os.name == 'posix' and 'CYGWIN' in os.uname()[0]
|
---|
58 |
|
---|
59 | # The environment variable for specifying the path to the premature-exit file.
|
---|
60 | PREMATURE_EXIT_FILE_ENV_VAR = 'TEST_PREMATURE_EXIT_FILE'
|
---|
61 |
|
---|
62 | environ = os.environ.copy()
|
---|
63 |
|
---|
64 |
|
---|
65 | def SetEnvVar(env_var, value):
|
---|
66 | """Sets/unsets an environment variable to a given value."""
|
---|
67 |
|
---|
68 | if value is not None:
|
---|
69 | environ[env_var] = value
|
---|
70 | elif env_var in environ:
|
---|
71 | del environ[env_var]
|
---|
72 |
|
---|
73 |
|
---|
74 | # Here we expose a class from a particular module, depending on the
|
---|
75 | # environment. The comment suppresses the 'Invalid variable name' lint
|
---|
76 | # complaint.
|
---|
77 | TestCase = _test_module.TestCase # pylint: disable-msg=C6409
|
---|
78 |
|
---|
79 | # Initially maps a flag to its default value. After
|
---|
80 | # _ParseAndStripGTestFlags() is called, maps a flag to its actual value.
|
---|
81 | _flag_map = {'source_dir': os.path.dirname(sys.argv[0]),
|
---|
82 | 'build_dir': os.path.dirname(sys.argv[0])}
|
---|
83 | _gtest_flags_are_parsed = False
|
---|
84 |
|
---|
85 |
|
---|
86 | def _ParseAndStripGTestFlags(argv):
|
---|
87 | """Parses and strips Google Test flags from argv. This is idempotent."""
|
---|
88 |
|
---|
89 | # Suppresses the lint complaint about a global variable since we need it
|
---|
90 | # here to maintain module-wide state.
|
---|
91 | global _gtest_flags_are_parsed # pylint: disable-msg=W0603
|
---|
92 | if _gtest_flags_are_parsed:
|
---|
93 | return
|
---|
94 |
|
---|
95 | _gtest_flags_are_parsed = True
|
---|
96 | for flag in _flag_map:
|
---|
97 | # The environment variable overrides the default value.
|
---|
98 | if flag.upper() in os.environ:
|
---|
99 | _flag_map[flag] = os.environ[flag.upper()]
|
---|
100 |
|
---|
101 | # The command line flag overrides the environment variable.
|
---|
102 | i = 1 # Skips the program name.
|
---|
103 | while i < len(argv):
|
---|
104 | prefix = '--' + flag + '='
|
---|
105 | if argv[i].startswith(prefix):
|
---|
106 | _flag_map[flag] = argv[i][len(prefix):]
|
---|
107 | del argv[i]
|
---|
108 | break
|
---|
109 | else:
|
---|
110 | # We don't increment i in case we just found a --gtest_* flag
|
---|
111 | # and removed it from argv.
|
---|
112 | i += 1
|
---|
113 |
|
---|
114 |
|
---|
115 | def GetFlag(flag):
|
---|
116 | """Returns the value of the given flag."""
|
---|
117 |
|
---|
118 | # In case GetFlag() is called before Main(), we always call
|
---|
119 | # _ParseAndStripGTestFlags() here to make sure the --gtest_* flags
|
---|
120 | # are parsed.
|
---|
121 | _ParseAndStripGTestFlags(sys.argv)
|
---|
122 |
|
---|
123 | return _flag_map[flag]
|
---|
124 |
|
---|
125 |
|
---|
126 | def GetSourceDir():
|
---|
127 | """Returns the absolute path of the directory where the .py files are."""
|
---|
128 |
|
---|
129 | return os.path.abspath(GetFlag('source_dir'))
|
---|
130 |
|
---|
131 |
|
---|
132 | def GetBuildDir():
|
---|
133 | """Returns the absolute path of the directory where the test binaries are."""
|
---|
134 |
|
---|
135 | return os.path.abspath(GetFlag('build_dir'))
|
---|
136 |
|
---|
137 |
|
---|
138 | _temp_dir = None
|
---|
139 |
|
---|
140 | def _RemoveTempDir():
|
---|
141 | if _temp_dir:
|
---|
142 | shutil.rmtree(_temp_dir, ignore_errors=True)
|
---|
143 |
|
---|
144 | atexit.register(_RemoveTempDir)
|
---|
145 |
|
---|
146 |
|
---|
147 | def GetTempDir():
|
---|
148 | """Returns a directory for temporary files."""
|
---|
149 |
|
---|
150 | global _temp_dir
|
---|
151 | if not _temp_dir:
|
---|
152 | _temp_dir = tempfile.mkdtemp()
|
---|
153 | return _temp_dir
|
---|
154 |
|
---|
155 |
|
---|
156 | def GetTestExecutablePath(executable_name, build_dir=None):
|
---|
157 | """Returns the absolute path of the test binary given its name.
|
---|
158 |
|
---|
159 | The function will print a message and abort the program if the resulting file
|
---|
160 | doesn't exist.
|
---|
161 |
|
---|
162 | Args:
|
---|
163 | executable_name: name of the test binary that the test script runs.
|
---|
164 | build_dir: directory where to look for executables, by default
|
---|
165 | the result of GetBuildDir().
|
---|
166 |
|
---|
167 | Returns:
|
---|
168 | The absolute path of the test binary.
|
---|
169 | """
|
---|
170 |
|
---|
171 | path = os.path.abspath(os.path.join(build_dir or GetBuildDir(),
|
---|
172 | executable_name))
|
---|
173 | if (IS_WINDOWS or IS_CYGWIN) and not path.endswith('.exe'):
|
---|
174 | path += '.exe'
|
---|
175 |
|
---|
176 | if not os.path.exists(path):
|
---|
177 | message = (
|
---|
178 | 'Unable to find the test binary. Please make sure to provide path\n'
|
---|
179 | 'to the binary via the --build_dir flag or the BUILD_DIR\n'
|
---|
180 | 'environment variable.')
|
---|
181 | print >> sys.stderr, message
|
---|
182 | sys.exit(1)
|
---|
183 |
|
---|
184 | return path
|
---|
185 |
|
---|
186 |
|
---|
187 | def GetExitStatus(exit_code):
|
---|
188 | """Returns the argument to exit(), or -1 if exit() wasn't called.
|
---|
189 |
|
---|
190 | Args:
|
---|
191 | exit_code: the result value of os.system(command).
|
---|
192 | """
|
---|
193 |
|
---|
194 | if os.name == 'nt':
|
---|
195 | # On Windows, os.WEXITSTATUS() doesn't work and os.system() returns
|
---|
196 | # the argument to exit() directly.
|
---|
197 | return exit_code
|
---|
198 | else:
|
---|
199 | # On Unix, os.WEXITSTATUS() must be used to extract the exit status
|
---|
200 | # from the result of os.system().
|
---|
201 | if os.WIFEXITED(exit_code):
|
---|
202 | return os.WEXITSTATUS(exit_code)
|
---|
203 | else:
|
---|
204 | return -1
|
---|
205 |
|
---|
206 |
|
---|
207 | class Subprocess:
|
---|
208 | def __init__(self, command, working_dir=None, capture_stderr=True, env=None):
|
---|
209 | """Changes into a specified directory, if provided, and executes a command.
|
---|
210 |
|
---|
211 | Restores the old directory afterwards.
|
---|
212 |
|
---|
213 | Args:
|
---|
214 | command: The command to run, in the form of sys.argv.
|
---|
215 | working_dir: The directory to change into.
|
---|
216 | capture_stderr: Determines whether to capture stderr in the output member
|
---|
217 | or to discard it.
|
---|
218 | env: Dictionary with environment to pass to the subprocess.
|
---|
219 |
|
---|
220 | Returns:
|
---|
221 | An object that represents outcome of the executed process. It has the
|
---|
222 | following attributes:
|
---|
223 | terminated_by_signal True iff the child process has been terminated
|
---|
224 | by a signal.
|
---|
225 | signal Sygnal that terminated the child process.
|
---|
226 | exited True iff the child process exited normally.
|
---|
227 | exit_code The code with which the child process exited.
|
---|
228 | output Child process's stdout and stderr output
|
---|
229 | combined in a string.
|
---|
230 | """
|
---|
231 |
|
---|
232 | # The subprocess module is the preferrable way of running programs
|
---|
233 | # since it is available and behaves consistently on all platforms,
|
---|
234 | # including Windows. But it is only available starting in python 2.4.
|
---|
235 | # In earlier python versions, we revert to the popen2 module, which is
|
---|
236 | # available in python 2.0 and later but doesn't provide required
|
---|
237 | # functionality (Popen4) under Windows. This allows us to support Mac
|
---|
238 | # OS X 10.4 Tiger, which has python 2.3 installed.
|
---|
239 | if _SUBPROCESS_MODULE_AVAILABLE:
|
---|
240 | if capture_stderr:
|
---|
241 | stderr = subprocess.STDOUT
|
---|
242 | else:
|
---|
243 | stderr = subprocess.PIPE
|
---|
244 |
|
---|
245 | p = subprocess.Popen(command,
|
---|
246 | stdout=subprocess.PIPE, stderr=stderr,
|
---|
247 | cwd=working_dir, universal_newlines=True, env=env)
|
---|
248 | # communicate returns a tuple with the file obect for the child's
|
---|
249 | # output.
|
---|
250 | self.output = p.communicate()[0]
|
---|
251 | self._return_code = p.returncode
|
---|
252 | else:
|
---|
253 | old_dir = os.getcwd()
|
---|
254 |
|
---|
255 | def _ReplaceEnvDict(dest, src):
|
---|
256 | # Changes made by os.environ.clear are not inheritable by child
|
---|
257 | # processes until Python 2.6. To produce inheritable changes we have
|
---|
258 | # to delete environment items with the del statement.
|
---|
259 | for key in dest.keys():
|
---|
260 | del dest[key]
|
---|
261 | dest.update(src)
|
---|
262 |
|
---|
263 | # When 'env' is not None, backup the environment variables and replace
|
---|
264 | # them with the passed 'env'. When 'env' is None, we simply use the
|
---|
265 | # current 'os.environ' for compatibility with the subprocess.Popen
|
---|
266 | # semantics used above.
|
---|
267 | if env is not None:
|
---|
268 | old_environ = os.environ.copy()
|
---|
269 | _ReplaceEnvDict(os.environ, env)
|
---|
270 |
|
---|
271 | try:
|
---|
272 | if working_dir is not None:
|
---|
273 | os.chdir(working_dir)
|
---|
274 | if capture_stderr:
|
---|
275 | p = popen2.Popen4(command)
|
---|
276 | else:
|
---|
277 | p = popen2.Popen3(command)
|
---|
278 | p.tochild.close()
|
---|
279 | self.output = p.fromchild.read()
|
---|
280 | ret_code = p.wait()
|
---|
281 | finally:
|
---|
282 | os.chdir(old_dir)
|
---|
283 |
|
---|
284 | # Restore the old environment variables
|
---|
285 | # if they were replaced.
|
---|
286 | if env is not None:
|
---|
287 | _ReplaceEnvDict(os.environ, old_environ)
|
---|
288 |
|
---|
289 | # Converts ret_code to match the semantics of
|
---|
290 | # subprocess.Popen.returncode.
|
---|
291 | if os.WIFSIGNALED(ret_code):
|
---|
292 | self._return_code = -os.WTERMSIG(ret_code)
|
---|
293 | else: # os.WIFEXITED(ret_code) should return True here.
|
---|
294 | self._return_code = os.WEXITSTATUS(ret_code)
|
---|
295 |
|
---|
296 | if self._return_code < 0:
|
---|
297 | self.terminated_by_signal = True
|
---|
298 | self.exited = False
|
---|
299 | self.signal = -self._return_code
|
---|
300 | else:
|
---|
301 | self.terminated_by_signal = False
|
---|
302 | self.exited = True
|
---|
303 | self.exit_code = self._return_code
|
---|
304 |
|
---|
305 |
|
---|
306 | def Main():
|
---|
307 | """Runs the unit test."""
|
---|
308 |
|
---|
309 | # We must call _ParseAndStripGTestFlags() before calling
|
---|
310 | # unittest.main(). Otherwise the latter will be confused by the
|
---|
311 | # --gtest_* flags.
|
---|
312 | _ParseAndStripGTestFlags(sys.argv)
|
---|
313 | # The tested binaries should not be writing XML output files unless the
|
---|
314 | # script explicitly instructs them to.
|
---|
315 | # TODO(vladl@google.com): Move this into Subprocess when we implement
|
---|
316 | # passing environment into it as a parameter.
|
---|
317 | if GTEST_OUTPUT_VAR_NAME in os.environ:
|
---|
318 | del os.environ[GTEST_OUTPUT_VAR_NAME]
|
---|
319 |
|
---|
320 | _test_module.main()
|
---|