/thirdparty/breakpad/third_party/protobuf/protobuf/gtest/test/run_tests_util.py

http://github.com/tomahawk-player/tomahawk · Python · 466 lines · 372 code · 24 blank · 70 comment · 16 complexity · 6daa7919257f56f406d12730c1b5f0e6 MD5 · raw file

  1. # Copyright 2008 Google Inc. All Rights Reserved.
  2. #
  3. # Redistribution and use in source and binary forms, with or without
  4. # modification, are permitted provided that the following conditions are
  5. # met:
  6. #
  7. # * Redistributions of source code must retain the above copyright
  8. # notice, this list of conditions and the following disclaimer.
  9. # * Redistributions in binary form must reproduce the above
  10. # copyright notice, this list of conditions and the following disclaimer
  11. # in the documentation and/or other materials provided with the
  12. # distribution.
  13. # * Neither the name of Google Inc. nor the names of its
  14. # contributors may be used to endorse or promote products derived from
  15. # this software without specific prior written permission.
  16. #
  17. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  18. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  19. # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  20. # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  21. # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  22. # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  23. # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  24. # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  25. # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  26. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  27. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. """Provides facilities for running SCons-built Google Test/Mock tests."""
  29. import optparse
  30. import os
  31. import re
  32. import sets
  33. import sys
  34. try:
  35. # subrocess module is a preferable way to invoke subprocesses but it may
  36. # not be available on MacOS X 10.4.
  37. # Suppresses the 'Import not at the top of the file' lint complaint.
  38. # pylint: disable-msg=C6204
  39. import subprocess
  40. except ImportError:
  41. subprocess = None
  42. HELP_MSG = """Runs the specified tests for %(proj)s.
  43. SYNOPSIS
  44. run_tests.py [OPTION]... [BUILD_DIR]... [TEST]...
  45. DESCRIPTION
  46. Runs the specified tests (either binary or Python), and prints a
  47. summary of the results. BUILD_DIRS will be used to search for the
  48. binaries. If no TESTs are specified, all binary tests found in
  49. BUILD_DIRs and all Python tests found in the directory test/ (in the
  50. %(proj)s root) are run.
  51. TEST is a name of either a binary or a Python test. A binary test is
  52. an executable file named *_test or *_unittest (with the .exe
  53. extension on Windows) A Python test is a script named *_test.py or
  54. *_unittest.py.
  55. OPTIONS
  56. -h, --help
  57. Print this help message.
  58. -c CONFIGURATIONS
  59. Specify build directories via build configurations.
  60. CONFIGURATIONS is either a comma-separated list of build
  61. configurations or 'all'. Each configuration is equivalent to
  62. adding 'scons/build/<configuration>/%(proj)s/scons' to BUILD_DIRs.
  63. Specifying -c=all is equivalent to providing all directories
  64. listed in KNOWN BUILD DIRECTORIES section below.
  65. -a
  66. Equivalent to -c=all
  67. -b
  68. Equivalent to -c=all with the exception that the script will not
  69. fail if some of the KNOWN BUILD DIRECTORIES do not exists; the
  70. script will simply not run the tests there. 'b' stands for
  71. 'built directories'.
  72. RETURN VALUE
  73. Returns 0 if all tests are successful; otherwise returns 1.
  74. EXAMPLES
  75. run_tests.py
  76. Runs all tests for the default build configuration.
  77. run_tests.py -a
  78. Runs all tests with binaries in KNOWN BUILD DIRECTORIES.
  79. run_tests.py -b
  80. Runs all tests in KNOWN BUILD DIRECTORIES that have been
  81. built.
  82. run_tests.py foo/
  83. Runs all tests in the foo/ directory and all Python tests in
  84. the directory test. The Python tests are instructed to look
  85. for binaries in foo/.
  86. run_tests.py bar_test.exe test/baz_test.exe foo/ bar/
  87. Runs foo/bar_test.exe, bar/bar_test.exe, foo/baz_test.exe, and
  88. bar/baz_test.exe.
  89. run_tests.py foo bar test/foo_test.py
  90. Runs test/foo_test.py twice instructing it to look for its
  91. test binaries in the directories foo and bar,
  92. correspondingly.
  93. KNOWN BUILD DIRECTORIES
  94. run_tests.py knows about directories where the SCons build script
  95. deposits its products. These are the directories where run_tests.py
  96. will be looking for its binaries. Currently, %(proj)s's SConstruct file
  97. defines them as follows (the default build directory is the first one
  98. listed in each group):
  99. On Windows:
  100. <%(proj)s root>/scons/build/win-dbg8/%(proj)s/scons/
  101. <%(proj)s root>/scons/build/win-opt8/%(proj)s/scons/
  102. On Mac:
  103. <%(proj)s root>/scons/build/mac-dbg/%(proj)s/scons/
  104. <%(proj)s root>/scons/build/mac-opt/%(proj)s/scons/
  105. On other platforms:
  106. <%(proj)s root>/scons/build/dbg/%(proj)s/scons/
  107. <%(proj)s root>/scons/build/opt/%(proj)s/scons/"""
  108. IS_WINDOWS = os.name == 'nt'
  109. IS_MAC = os.name == 'posix' and os.uname()[0] == 'Darwin'
  110. IS_CYGWIN = os.name == 'posix' and 'CYGWIN' in os.uname()[0]
  111. # Definition of CONFIGS must match that of the build directory names in the
  112. # SConstruct script. The first list item is the default build configuration.
  113. if IS_WINDOWS:
  114. CONFIGS = ('win-dbg8', 'win-opt8')
  115. elif IS_MAC:
  116. CONFIGS = ('mac-dbg', 'mac-opt')
  117. else:
  118. CONFIGS = ('dbg', 'opt')
  119. if IS_WINDOWS or IS_CYGWIN:
  120. PYTHON_TEST_REGEX = re.compile(r'_(unit)?test\.py$', re.IGNORECASE)
  121. BINARY_TEST_REGEX = re.compile(r'_(unit)?test(\.exe)?$', re.IGNORECASE)
  122. BINARY_TEST_SEARCH_REGEX = re.compile(r'_(unit)?test\.exe$', re.IGNORECASE)
  123. else:
  124. PYTHON_TEST_REGEX = re.compile(r'_(unit)?test\.py$')
  125. BINARY_TEST_REGEX = re.compile(r'_(unit)?test$')
  126. BINARY_TEST_SEARCH_REGEX = BINARY_TEST_REGEX
  127. def _GetGtestBuildDir(injected_os, script_dir, config):
  128. """Calculates path to the Google Test SCons build directory."""
  129. return injected_os.path.normpath(injected_os.path.join(script_dir,
  130. 'scons/build',
  131. config,
  132. 'gtest/scons'))
  133. def _GetConfigFromBuildDir(build_dir):
  134. """Extracts the configuration name from the build directory."""
  135. # We don't want to depend on build_dir containing the correct path
  136. # separators.
  137. m = re.match(r'.*[\\/]([^\\/]+)[\\/][^\\/]+[\\/]scons[\\/]?$', build_dir)
  138. if m:
  139. return m.group(1)
  140. else:
  141. print >>sys.stderr, ('%s is an invalid build directory that does not '
  142. 'correspond to any configuration.' % (build_dir,))
  143. return ''
  144. # All paths in this script are either absolute or relative to the current
  145. # working directory, unless otherwise specified.
  146. class TestRunner(object):
  147. """Provides facilities for running Python and binary tests for Google Test."""
  148. def __init__(self,
  149. script_dir,
  150. build_dir_var_name='GTEST_BUILD_DIR',
  151. injected_os=os,
  152. injected_subprocess=subprocess,
  153. injected_build_dir_finder=_GetGtestBuildDir):
  154. """Initializes a TestRunner instance.
  155. Args:
  156. script_dir: File path to the calling script.
  157. build_dir_var_name: Name of the env variable used to pass the
  158. the build directory path to the invoked
  159. tests.
  160. injected_os: standard os module or a mock/stub for
  161. testing.
  162. injected_subprocess: standard subprocess module or a mock/stub
  163. for testing
  164. injected_build_dir_finder: function that determines the path to
  165. the build directory.
  166. """
  167. self.os = injected_os
  168. self.subprocess = injected_subprocess
  169. self.build_dir_finder = injected_build_dir_finder
  170. self.build_dir_var_name = build_dir_var_name
  171. self.script_dir = script_dir
  172. def _GetBuildDirForConfig(self, config):
  173. """Returns the build directory for a given configuration."""
  174. return self.build_dir_finder(self.os, self.script_dir, config)
  175. def _Run(self, args):
  176. """Runs the executable with given args (args[0] is the executable name).
  177. Args:
  178. args: Command line arguments for the process.
  179. Returns:
  180. Process's exit code if it exits normally, or -signal if the process is
  181. killed by a signal.
  182. """
  183. if self.subprocess:
  184. return self.subprocess.Popen(args).wait()
  185. else:
  186. return self.os.spawnv(self.os.P_WAIT, args[0], args)
  187. def _RunBinaryTest(self, test):
  188. """Runs the binary test given its path.
  189. Args:
  190. test: Path to the test binary.
  191. Returns:
  192. Process's exit code if it exits normally, or -signal if the process is
  193. killed by a signal.
  194. """
  195. return self._Run([test])
  196. def _RunPythonTest(self, test, build_dir):
  197. """Runs the Python test script with the specified build directory.
  198. Args:
  199. test: Path to the test's Python script.
  200. build_dir: Path to the directory where the test binary is to be found.
  201. Returns:
  202. Process's exit code if it exits normally, or -signal if the process is
  203. killed by a signal.
  204. """
  205. old_build_dir = self.os.environ.get(self.build_dir_var_name)
  206. try:
  207. self.os.environ[self.build_dir_var_name] = build_dir
  208. # If this script is run on a Windows machine that has no association
  209. # between the .py extension and a python interpreter, simply passing
  210. # the script name into subprocess.Popen/os.spawn will not work.
  211. print 'Running %s . . .' % (test,)
  212. return self._Run([sys.executable, test])
  213. finally:
  214. if old_build_dir is None:
  215. del self.os.environ[self.build_dir_var_name]
  216. else:
  217. self.os.environ[self.build_dir_var_name] = old_build_dir
  218. def _FindFilesByRegex(self, directory, regex):
  219. """Returns files in a directory whose names match a regular expression.
  220. Args:
  221. directory: Path to the directory to search for files.
  222. regex: Regular expression to filter file names.
  223. Returns:
  224. The list of the paths to the files in the directory.
  225. """
  226. return [self.os.path.join(directory, file_name)
  227. for file_name in self.os.listdir(directory)
  228. if re.search(regex, file_name)]
  229. # TODO(vladl@google.com): Implement parsing of scons/SConscript to run all
  230. # tests defined there when no tests are specified.
  231. # TODO(vladl@google.com): Update the docstring after the code is changed to
  232. # try to test all builds defined in scons/SConscript.
  233. def GetTestsToRun(self,
  234. args,
  235. named_configurations,
  236. built_configurations,
  237. available_configurations=CONFIGS,
  238. python_tests_to_skip=None):
  239. """Determines what tests should be run.
  240. Args:
  241. args: The list of non-option arguments from the command line.
  242. named_configurations: The list of configurations specified via -c or -a.
  243. built_configurations: True if -b has been specified.
  244. available_configurations: a list of configurations available on the
  245. current platform, injectable for testing.
  246. python_tests_to_skip: a collection of (configuration, python test name)s
  247. that need to be skipped.
  248. Returns:
  249. A tuple with 2 elements: the list of Python tests to run and the list of
  250. binary tests to run.
  251. """
  252. if named_configurations == 'all':
  253. named_configurations = ','.join(available_configurations)
  254. normalized_args = [self.os.path.normpath(arg) for arg in args]
  255. # A final list of build directories which will be searched for the test
  256. # binaries. First, add directories specified directly on the command
  257. # line.
  258. build_dirs = filter(self.os.path.isdir, normalized_args)
  259. # Adds build directories specified via their build configurations using
  260. # the -c or -a options.
  261. if named_configurations:
  262. build_dirs += [self._GetBuildDirForConfig(config)
  263. for config in named_configurations.split(',')]
  264. # Adds KNOWN BUILD DIRECTORIES if -b is specified.
  265. if built_configurations:
  266. build_dirs += [self._GetBuildDirForConfig(config)
  267. for config in available_configurations
  268. if self.os.path.isdir(self._GetBuildDirForConfig(config))]
  269. # If no directories were specified either via -a, -b, -c, or directly, use
  270. # the default configuration.
  271. elif not build_dirs:
  272. build_dirs = [self._GetBuildDirForConfig(available_configurations[0])]
  273. # Makes sure there are no duplications.
  274. build_dirs = sets.Set(build_dirs)
  275. errors_found = False
  276. listed_python_tests = [] # All Python tests listed on the command line.
  277. listed_binary_tests = [] # All binary tests listed on the command line.
  278. test_dir = self.os.path.normpath(self.os.path.join(self.script_dir, 'test'))
  279. # Sifts through non-directory arguments fishing for any Python or binary
  280. # tests and detecting errors.
  281. for argument in sets.Set(normalized_args) - build_dirs:
  282. if re.search(PYTHON_TEST_REGEX, argument):
  283. python_path = self.os.path.join(test_dir,
  284. self.os.path.basename(argument))
  285. if self.os.path.isfile(python_path):
  286. listed_python_tests.append(python_path)
  287. else:
  288. sys.stderr.write('Unable to find Python test %s' % argument)
  289. errors_found = True
  290. elif re.search(BINARY_TEST_REGEX, argument):
  291. # This script also accepts binary test names prefixed with test/ for
  292. # the convenience of typing them (can use path completions in the
  293. # shell). Strips test/ prefix from the binary test names.
  294. listed_binary_tests.append(self.os.path.basename(argument))
  295. else:
  296. sys.stderr.write('%s is neither test nor build directory' % argument)
  297. errors_found = True
  298. if errors_found:
  299. return None
  300. user_has_listed_tests = listed_python_tests or listed_binary_tests
  301. if user_has_listed_tests:
  302. selected_python_tests = listed_python_tests
  303. else:
  304. selected_python_tests = self._FindFilesByRegex(test_dir,
  305. PYTHON_TEST_REGEX)
  306. # TODO(vladl@google.com): skip unbuilt Python tests when -b is specified.
  307. python_test_pairs = []
  308. for directory in build_dirs:
  309. for test in selected_python_tests:
  310. config = _GetConfigFromBuildDir(directory)
  311. file_name = os.path.basename(test)
  312. if python_tests_to_skip and (config, file_name) in python_tests_to_skip:
  313. print ('NOTE: %s is skipped for configuration %s, as it does not '
  314. 'work there.' % (file_name, config))
  315. else:
  316. python_test_pairs.append((directory, test))
  317. binary_test_pairs = []
  318. for directory in build_dirs:
  319. if user_has_listed_tests:
  320. binary_test_pairs.extend(
  321. [(directory, self.os.path.join(directory, test))
  322. for test in listed_binary_tests])
  323. else:
  324. tests = self._FindFilesByRegex(directory, BINARY_TEST_SEARCH_REGEX)
  325. binary_test_pairs.extend([(directory, test) for test in tests])
  326. return (python_test_pairs, binary_test_pairs)
  327. def RunTests(self, python_tests, binary_tests):
  328. """Runs Python and binary tests and reports results to the standard output.
  329. Args:
  330. python_tests: List of Python tests to run in the form of tuples
  331. (build directory, Python test script).
  332. binary_tests: List of binary tests to run in the form of tuples
  333. (build directory, binary file).
  334. Returns:
  335. The exit code the program should pass into sys.exit().
  336. """
  337. if python_tests or binary_tests:
  338. results = []
  339. for directory, test in python_tests:
  340. results.append((directory,
  341. test,
  342. self._RunPythonTest(test, directory) == 0))
  343. for directory, test in binary_tests:
  344. results.append((directory,
  345. self.os.path.basename(test),
  346. self._RunBinaryTest(test) == 0))
  347. failed = [(directory, test)
  348. for (directory, test, success) in results
  349. if not success]
  350. print
  351. print '%d tests run.' % len(results)
  352. if failed:
  353. print 'The following %d tests failed:' % len(failed)
  354. for (directory, test) in failed:
  355. print '%s in %s' % (test, directory)
  356. return 1
  357. else:
  358. print 'All tests passed!'
  359. else: # No tests defined
  360. print 'Nothing to test - no tests specified!'
  361. return 0
  362. def ParseArgs(project_name, argv=None, help_callback=None):
  363. """Parses the options run_tests.py uses."""
  364. # Suppresses lint warning on unused arguments. These arguments are
  365. # required by optparse, even though they are unused.
  366. # pylint: disable-msg=W0613
  367. def PrintHelp(option, opt, value, parser):
  368. print HELP_MSG % {'proj': project_name}
  369. sys.exit(1)
  370. parser = optparse.OptionParser()
  371. parser.add_option('-c',
  372. action='store',
  373. dest='configurations',
  374. default=None)
  375. parser.add_option('-a',
  376. action='store_const',
  377. dest='configurations',
  378. default=None,
  379. const='all')
  380. parser.add_option('-b',
  381. action='store_const',
  382. dest='built_configurations',
  383. default=False,
  384. const=True)
  385. # Replaces the built-in help with ours.
  386. parser.remove_option('-h')
  387. parser.add_option('-h', '--help',
  388. action='callback',
  389. callback=help_callback or PrintHelp)
  390. return parser.parse_args(argv)