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 36import atexit 37import os 38import shutil 39import sys 40import tempfile 41import unittest 42_test_module = unittest 43 44# Suppresses the 'Import not at the top of the file' lint complaint. 45# pylint: disable-msg=C6204 46try: 47 import subprocess 48 _SUBPROCESS_MODULE_AVAILABLE = True 49except: 50 import popen2 51 _SUBPROCESS_MODULE_AVAILABLE = False 52# pylint: enable-msg=C6204 53 54GTEST_OUTPUT_VAR_NAME = 'GTEST_OUTPUT' 55 56IS_WINDOWS = os.name == 'nt' 57IS_CYGWIN = os.name == 'posix' and 'CYGWIN' in os.uname()[0] 58 59# The environment variable for specifying the path to the premature-exit file. 60PREMATURE_EXIT_FILE_ENV_VAR = 'TEST_PREMATURE_EXIT_FILE' 61 62environ = os.environ.copy() 63 64 65def 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. 77TestCase = _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 86def _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 115def 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 126def 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 132def 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 140def _RemoveTempDir(): 141 if _temp_dir: 142 shutil.rmtree(_temp_dir, ignore_errors=True) 143 144atexit.register(_RemoveTempDir) 145 146 147def 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 156def 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 187def 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 207class 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 306def 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() 321