From 6f50ccf32c31cb6e287cdfee149429ac3029bbdb Mon Sep 17 00:00:00 2001 From: vladlosev Date: Mon, 15 Feb 2010 21:31:41 +0000 Subject: [PATCH] Google Test's Python tests now pass on Solaris. --- test/gtest_break_on_failure_unittest.py | 11 +++-- test/gtest_env_var_test.py | 10 ++-- test/gtest_filter_unittest.py | 61 +++++++++++++++++++------ test/gtest_output_test.py | 46 +++++++++++++------ test/gtest_shuffle_test.py | 14 ++---- test/gtest_test_utils.py | 50 +++++++++++++++----- 6 files changed, 133 insertions(+), 59 deletions(-) diff --git a/test/gtest_break_on_failure_unittest.py b/test/gtest_break_on_failure_unittest.py index 218d3713..c8191833 100755 --- a/test/gtest_break_on_failure_unittest.py +++ b/test/gtest_break_on_failure_unittest.py @@ -69,21 +69,24 @@ EXE_PATH = gtest_test_utils.GetTestExecutablePath( # Utilities. +environ = os.environ.copy() + + def SetEnvVar(env_var, value): """Sets an environment variable to a given value; unsets it when the given value is None. """ if value is not None: - os.environ[env_var] = value - elif env_var in os.environ: - del os.environ[env_var] + environ[env_var] = value + elif env_var in environ: + del environ[env_var] def Run(command): """Runs a command; returns 1 if it was killed by a signal, or 0 otherwise.""" - p = gtest_test_utils.Subprocess(command) + p = gtest_test_utils.Subprocess(command, env=environ) if p.terminated_by_signal: return 1 else: diff --git a/test/gtest_env_var_test.py b/test/gtest_env_var_test.py index f8250d4c..bcc0bfd5 100755 --- a/test/gtest_env_var_test.py +++ b/test/gtest_env_var_test.py @@ -42,6 +42,8 @@ IS_LINUX = os.name == 'posix' and os.uname()[0] == 'Linux' COMMAND = gtest_test_utils.GetTestExecutablePath('gtest_env_var_test_') +environ = os.environ.copy() + def AssertEq(expected, actual): if expected != actual: @@ -54,9 +56,9 @@ def SetEnvVar(env_var, value): """Sets the env variable to 'value'; unsets it when 'value' is None.""" if value is not None: - os.environ[env_var] = value - elif env_var in os.environ: - del os.environ[env_var] + environ[env_var] = value + elif env_var in environ: + del environ[env_var] def GetFlag(flag): @@ -65,7 +67,7 @@ def GetFlag(flag): args = [COMMAND] if flag is not None: args += [flag] - return gtest_test_utils.Subprocess(args).output + return gtest_test_utils.Subprocess(args, env=environ).output def TestFlag(flag, test_val, default_val): diff --git a/test/gtest_filter_unittest.py b/test/gtest_filter_unittest.py index a94a5210..89171e06 100755 --- a/test/gtest_filter_unittest.py +++ b/test/gtest_filter_unittest.py @@ -45,11 +45,42 @@ __author__ = 'wan@google.com (Zhanyong Wan)' import os import re import sets +import sys + import gtest_test_utils # Constants. -IS_WINDOWS = os.name == 'nt' +# Checks if this platform can pass empty environment variables to child +# processes. We set an env variable to an empty string and invoke a python +# script in a subprocess to print whether the variable is STILL in +# os.environ. We then use 'eval' to parse the child's output so that an +# exception is thrown if the input is anything other than 'True' nor 'False'. +os.environ['EMPTY_VAR'] = '' +child = gtest_test_utils.Subprocess( + [sys.executable, '-c', 'import os; print \'EMPTY_VAR\' in os.environ']) +CAN_PASS_EMPTY_ENV = eval(child.output) + + +# Check if this platform can unset environment variables in child processes. +# We set an env variable to a non-empty string, unset it, and invoke +# a python script in a subprocess to print whether the variable +# is NO LONGER in os.environ. +# We use 'eval' to parse the child's output so that an exception +# is thrown if the input is neither 'True' nor 'False'. +os.environ['UNSET_VAR'] = 'X' +del os.environ['UNSET_VAR'] +child = gtest_test_utils.Subprocess( + [sys.executable, '-c', 'import os; print \'UNSET_VAR\' not in os.environ']) +CAN_UNSET_ENV = eval(child.output) + + +# Checks if we should test with an empty filter. This doesn't +# make sense on platforms that cannot pass empty env variables (Win32) +# and on platforms that cannot unset variables (since we cannot tell +# the difference between "" and NULL -- Borland and Solaris < 5.10) +CAN_TEST_EMPTY_FILTER = (CAN_PASS_EMPTY_ENV and CAN_UNSET_ENV) + # The environment variable for specifying the test filters. FILTER_ENV_VAR = 'GTEST_FILTER' @@ -119,26 +150,29 @@ param_tests_present = None # Utilities. +environ = os.environ.copy() + def SetEnvVar(env_var, value): """Sets the env variable to 'value'; unsets it when 'value' is None.""" if value is not None: - os.environ[env_var] = value - elif env_var in os.environ: - del os.environ[env_var] + environ[env_var] = value + elif env_var in environ: + del environ[env_var] def RunAndReturnOutput(args = None): """Runs the test program and returns its output.""" - return gtest_test_utils.Subprocess([COMMAND] + (args or [])).output + return gtest_test_utils.Subprocess([COMMAND] + (args or []), + env=environ).output def RunAndExtractTestList(args = None): """Runs the test program and returns its exit code and a list of tests run.""" - p = gtest_test_utils.Subprocess([COMMAND] + (args or [])) + p = gtest_test_utils.Subprocess([COMMAND] + (args or []), env=environ) tests_run = [] test_case = '' test = '' @@ -157,15 +191,12 @@ def RunAndExtractTestList(args = None): def InvokeWithModifiedEnv(extra_env, function, *args, **kwargs): """Runs the given function and arguments in a modified environment.""" try: - original_env = os.environ.copy() - os.environ.update(extra_env) + original_env = environ.copy() + environ.update(extra_env) return function(*args, **kwargs) finally: - for key in extra_env.iterkeys(): - if key in original_env: - os.environ[key] = original_env[key] - else: - del os.environ[key] + environ.clear() + environ.update(original_env) def RunWithSharding(total_shards, shard_index, command): @@ -223,7 +254,7 @@ class GTestFilterUnitTest(gtest_test_utils.TestCase): # we can still test the case when the variable is not supplied (i.e., # gtest_filter is None). # pylint: disable-msg=C6403 - if not IS_WINDOWS or gtest_filter != '': + if CAN_TEST_EMPTY_FILTER or gtest_filter != '': SetEnvVar(FILTER_ENV_VAR, gtest_filter) tests_run = RunAndExtractTestList()[0] SetEnvVar(FILTER_ENV_VAR, None) @@ -265,7 +296,7 @@ class GTestFilterUnitTest(gtest_test_utils.TestCase): # we can still test the case when the variable is not supplied (i.e., # gtest_filter is None). # pylint: disable-msg=C6403 - if not IS_WINDOWS or gtest_filter != '': + if CAN_TEST_EMPTY_FILTER or gtest_filter != '': SetEnvVar(FILTER_ENV_VAR, gtest_filter) partition = [] for i in range(0, total_shards): diff --git a/test/gtest_output_test.py b/test/gtest_output_test.py index 8d9a40b0..a0aa64fd 100755 --- a/test/gtest_output_test.py +++ b/test/gtest_output_test.py @@ -48,6 +48,7 @@ import gtest_test_utils # The flag for generating the golden file GENGOLDEN_FLAG = '--gengolden' +CATCH_EXCEPTIONS_ENV_VAR_NAME = 'GTEST_CATCH_EXCEPTIONS' IS_WINDOWS = os.name == 'nt' @@ -123,6 +124,20 @@ def RemoveTime(output): return re.sub(r'\(\d+ ms', '(? ms', output) +def RemoveTypeInfoDetails(test_output): + """Removes compiler-specific type info from Google Test program's output. + + Args: + test_output: the output of a Google Test program. + + Returns: + output with type information normalized to canonical form. + """ + + # some compilers output the name of type 'unsigned int' as 'unsigned' + return re.sub(r'unsigned int', 'unsigned', test_output) + + def RemoveTestCounts(output): """Removes test counts from a Google Test program's output.""" @@ -184,16 +199,9 @@ def GetShellCommandOutput(env_cmd): # Spawns cmd in a sub-process, and gets its standard I/O file objects. # Set and save the environment properly. - old_env_vars = dict(os.environ) - os.environ.update(env_cmd[0]) - p = gtest_test_utils.Subprocess(env_cmd[1]) - - # Changes made by os.environ.clear are not inheritable by child processes - # until Python 2.6. To produce inheritable changes we have to delete - # environment items with the del statement. - for key in os.environ.keys(): - del os.environ[key] - os.environ.update(old_env_vars) + environ = os.environ.copy() + environ.update(env_cmd[0]) + p = gtest_test_utils.Subprocess(env_cmd[1], env=environ) return p.output @@ -209,8 +217,10 @@ def GetCommandOutput(env_cmd): """ # Disables exception pop-ups on Windows. - os.environ['GTEST_CATCH_EXCEPTIONS'] = '1' - return NormalizeOutput(GetShellCommandOutput(env_cmd)) + environ, cmdline = env_cmd + environ = dict(environ) # Ensures we are modifying a copy. + environ[CATCH_EXCEPTIONS_ENV_VAR_NAME] = '1' + return NormalizeOutput(GetShellCommandOutput((environ, cmdline))) def GetOutputOfAllCommands(): @@ -262,11 +272,17 @@ class GTestOutputTest(gtest_test_utils.TestCase): # We want the test to pass regardless of certain features being # supported or not. + + # We still have to remove type name specifics in all cases. + normalized_actual = RemoveTypeInfoDetails(output) + normalized_golden = RemoveTypeInfoDetails(golden) + if CAN_GENERATE_GOLDEN_FILE: - self.assert_(golden == output) + self.assert_(normalized_golden == normalized_actual) else: - normalized_actual = RemoveTestCounts(output) - normalized_golden = RemoveTestCounts(self.RemoveUnsupportedTests(golden)) + normalized_actual = RemoveTestCounts(normalized_actual) + normalized_golden = RemoveTestCounts(self.RemoveUnsupportedTests( + normalized_golden)) # This code is very handy when debugging golden file differences: if os.getenv('DEBUG_GTEST_OUTPUT_TEST'): diff --git a/test/gtest_shuffle_test.py b/test/gtest_shuffle_test.py index a870a01b..30d0303d 100755 --- a/test/gtest_shuffle_test.py +++ b/test/gtest_shuffle_test.py @@ -78,16 +78,10 @@ def RandomSeedFlag(n): def RunAndReturnOutput(extra_env, args): """Runs the test program and returns its output.""" - try: - original_env = os.environ.copy() - os.environ.update(extra_env) - return gtest_test_utils.Subprocess([COMMAND] + args).output - finally: - for key in extra_env.iterkeys(): - if key in original_env: - os.environ[key] = original_env[key] - else: - del os.environ[key] + environ_copy = os.environ.copy() + environ_copy.update(extra_env) + + return gtest_test_utils.Subprocess([COMMAND] + args, env=environ_copy).output def GetTestsForAllIterations(extra_env, args): diff --git a/test/gtest_test_utils.py b/test/gtest_test_utils.py index 19b5b228..e0f5973e 100755 --- a/test/gtest_test_utils.py +++ b/test/gtest_test_utils.py @@ -194,23 +194,28 @@ def GetExitStatus(exit_code): class Subprocess: - def __init__(self, command, working_dir=None, capture_stderr=True): + def __init__(self, command, working_dir=None, capture_stderr=True, env=None): """Changes into a specified directory, if provided, and executes a command. - Restores the old directory afterwards. Execution results are returned - via the following attributes: - terminated_by_sygnal True iff the child process has been terminated - by a signal. - signal Sygnal that terminated the child process. - exited True iff the child process exited normally. - exit_code The code with which the child proces exited. - output Child process's stdout and stderr output - combined in a string. + + Restores the old directory afterwards. Args: command: The command to run, in the form of sys.argv. working_dir: The directory to change into. capture_stderr: Determines whether to capture stderr in the output member or to discard it. + env: Dictionary with environment to pass to the subprocess. + + Returns: + An object that represents outcome of the executed process. It has the + following attributes: + terminated_by_signal True iff the child process has been terminated + by a signal. + signal Sygnal that terminated the child process. + exited True iff the child process exited normally. + exit_code The code with which the child process exited. + output Child process's stdout and stderr output + combined in a string. """ # The subprocess module is the preferrable way of running programs @@ -228,13 +233,30 @@ class Subprocess: p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=stderr, - cwd=working_dir, universal_newlines=True) + cwd=working_dir, universal_newlines=True, env=env) # communicate returns a tuple with the file obect for the child's # output. self.output = p.communicate()[0] self._return_code = p.returncode else: old_dir = os.getcwd() + + def _ReplaceEnvDict(dest, src): + # Changes made by os.environ.clear are not inheritable by child + # processes until Python 2.6. To produce inheritable changes we have + # to delete environment items with the del statement. + for key in dest: + del dest[key] + dest.update(src) + + # When 'env' is not None, backup the environment variables and replace + # them with the passed 'env'. When 'env' is None, we simply use the + # current 'os.environ' for compatibility with the subprocess.Popen + # semantics used above. + if env is not None: + old_environ = os.environ.copy() + _ReplaceEnvDict(os.environ, env) + try: if working_dir is not None: os.chdir(working_dir) @@ -247,6 +269,12 @@ class Subprocess: ret_code = p.wait() finally: os.chdir(old_dir) + + # Restore the old environment variables + # if they were replaced. + if env is not None: + _ReplaceEnvDict(os.environ, old_environ) + # Converts ret_code to match the semantics of # subprocess.Popen.returncode. if os.WIFSIGNALED(ret_code):