native_tests.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. from __future__ import print_function
  2. import os
  3. import subprocess
  4. import sys
  5. SOURCE_ROOT = os.path.abspath(
  6. os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
  7. VENDOR_DIR = os.path.join(SOURCE_ROOT, 'vendor')
  8. PYYAML_LIB_DIR = os.path.join(VENDOR_DIR, 'pyyaml', 'lib')
  9. sys.path.append(PYYAML_LIB_DIR)
  10. import yaml #pylint: disable=wrong-import-position,wrong-import-order
  11. try:
  12. basestring # Python 2
  13. except NameError: # Python 3
  14. basestring = str # pylint: disable=redefined-builtin
  15. class Verbosity:
  16. CHATTY = 'chatty' # stdout and stderr
  17. ERRORS = 'errors' # stderr only
  18. SILENT = 'silent' # no output
  19. @staticmethod
  20. def get_all():
  21. return Verbosity.__get_all_in_order()
  22. @staticmethod
  23. def __get_all_in_order():
  24. return [Verbosity.SILENT, Verbosity.ERRORS, Verbosity.CHATTY]
  25. @staticmethod
  26. def __get_indices(*values):
  27. ordered = Verbosity.__get_all_in_order()
  28. indices = map(ordered.index, values)
  29. return indices
  30. @staticmethod
  31. def ge(a, b):
  32. """Greater or equal"""
  33. a_index, b_index = Verbosity.__get_indices(a, b)
  34. return a_index >= b_index
  35. @staticmethod
  36. def le(a, b):
  37. """Less or equal"""
  38. a_index, b_index = Verbosity.__get_indices(a, b)
  39. return a_index <= b_index
  40. class DisabledTestsPolicy:
  41. DISABLE = 'disable' # Disabled tests are disabled. Wow. Much sense.
  42. ONLY = 'only' # Only disabled tests should be run.
  43. INCLUDE = 'include' # Do not disable any tests.
  44. class Platform:
  45. LINUX = 'linux'
  46. MAC = 'mac'
  47. WINDOWS = 'windows'
  48. @staticmethod
  49. def get_current():
  50. platform = sys.platform
  51. if platform in ('linux', 'linux2'):
  52. return Platform.LINUX
  53. if platform == 'darwin':
  54. return Platform.MAC
  55. if platform in ('cygwin', 'win32'):
  56. return Platform.WINDOWS
  57. assert False, "unexpected current platform '{}'".format(platform)
  58. @staticmethod
  59. def get_all():
  60. return [Platform.LINUX, Platform.MAC, Platform.WINDOWS]
  61. @staticmethod
  62. def is_valid(platform):
  63. return platform in Platform.get_all()
  64. class TestsList():
  65. def __init__(self, config_path, tests_dir):
  66. self.config_path = config_path
  67. self.tests_dir = tests_dir
  68. # A dict with binary names (e.g. 'base_unittests') as keys
  69. # and various test data as values of dict type.
  70. self.tests = TestsList.__get_tests_list(config_path)
  71. def __len__(self):
  72. return len(self.tests)
  73. def get_for_current_platform(self):
  74. all_binaries = self.tests.keys()
  75. supported_binaries = filter(self.__platform_supports, all_binaries)
  76. return supported_binaries
  77. def run(self, binaries, output_dir=None, verbosity=Verbosity.CHATTY,
  78. disabled_tests_policy=DisabledTestsPolicy.DISABLE):
  79. # Don't run anything twice.
  80. binaries = set(binaries)
  81. # First check that all names are present in the config.
  82. for binary_name in binaries:
  83. if binary_name not in self.tests:
  84. raise Exception("binary {0} not found in config '{1}'".format(
  85. binary_name, self.config_path))
  86. # Respect the "platform" setting.
  87. for binary_name in binaries:
  88. if not self.__platform_supports(binary_name):
  89. raise Exception(
  90. "binary {0} cannot be run on {1}, check the config".format(
  91. binary_name, Platform.get_current()))
  92. suite_returncode = sum(
  93. [self.__run(binary, output_dir, verbosity, disabled_tests_policy)
  94. for binary in binaries])
  95. return suite_returncode
  96. def run_only(self, binary_name, output_dir=None, verbosity=Verbosity.CHATTY,
  97. disabled_tests_policy=DisabledTestsPolicy.DISABLE):
  98. return self.run([binary_name], output_dir, verbosity,
  99. disabled_tests_policy)
  100. def run_all(self, output_dir=None, verbosity=Verbosity.CHATTY,
  101. disabled_tests_policy=DisabledTestsPolicy.DISABLE):
  102. return self.run(self.get_for_current_platform(), output_dir, verbosity,
  103. disabled_tests_policy)
  104. @staticmethod
  105. def __get_tests_list(config_path):
  106. tests_list = {}
  107. config_data = TestsList.__get_config_data(config_path)
  108. for data_item in config_data['tests']:
  109. (binary_name, test_data) = TestsList.__get_test_data(data_item)
  110. tests_list[binary_name] = test_data
  111. return tests_list
  112. @staticmethod
  113. def __get_config_data(config_path):
  114. with open(config_path, 'r') as stream:
  115. return yaml.load(stream)
  116. @staticmethod
  117. def __expand_shorthand(value):
  118. """ Treat a string as {'string_value': None}."""
  119. if isinstance(value, dict):
  120. return value
  121. if isinstance(value, basestring):
  122. return {value: None}
  123. assert False, "unexpected shorthand type: {}".format(type(value))
  124. @staticmethod
  125. def __make_a_list(value):
  126. """Make a list if not already a list."""
  127. if isinstance(value, list):
  128. return value
  129. return [value]
  130. @staticmethod
  131. def __merge_nested_lists(value):
  132. """Converts a dict of lists to a list."""
  133. if isinstance(value, list):
  134. return value
  135. if isinstance(value, dict):
  136. # It looks ugly as hell, but it does the job.
  137. return [list_item for key in value for list_item in value[key]]
  138. assert False, "unexpected type for list merging: {}".format(type(value))
  139. def __platform_supports(self, binary_name):
  140. return Platform.get_current() in self.tests[binary_name]['platforms']
  141. @staticmethod
  142. def __get_test_data(data_item):
  143. data_item = TestsList.__expand_shorthand(data_item)
  144. binary_name = data_item.keys()[0]
  145. test_data = {
  146. 'excluded_tests': [],
  147. 'platforms': Platform.get_all()
  148. }
  149. configs = data_item[binary_name]
  150. if configs is not None:
  151. # List of excluded tests.
  152. if 'disabled' in configs:
  153. excluded_tests = TestsList.__merge_nested_lists(configs['disabled'])
  154. test_data['excluded_tests'] = excluded_tests
  155. # List of platforms to run the tests on.
  156. if 'platform' in configs:
  157. platforms = TestsList.__make_a_list(configs['platform'])
  158. for platform in platforms:
  159. assert Platform.is_valid(platform), \
  160. "platform '{0}' is not supported, check {1} config" \
  161. .format(platform, binary_name)
  162. test_data['platforms'] = platforms
  163. return (binary_name, test_data)
  164. def __run(self, binary_name, output_dir, verbosity,
  165. disabled_tests_policy):
  166. binary_path = os.path.join(self.tests_dir, binary_name)
  167. test_binary = TestBinary(binary_path)
  168. test_data = self.tests[binary_name]
  169. included_tests = []
  170. excluded_tests = test_data['excluded_tests']
  171. if disabled_tests_policy == DisabledTestsPolicy.ONLY:
  172. if len(excluded_tests) == 0:
  173. # There is nothing to run.
  174. return 0
  175. included_tests, excluded_tests = excluded_tests, included_tests
  176. if disabled_tests_policy == DisabledTestsPolicy.INCLUDE:
  177. excluded_tests = []
  178. output_file_path = TestsList.__get_output_path(binary_name, output_dir)
  179. return test_binary.run(included_tests=included_tests,
  180. excluded_tests=excluded_tests,
  181. output_file_path=output_file_path,
  182. verbosity=verbosity)
  183. @staticmethod
  184. def __get_output_path(binary_name, output_dir=None):
  185. if output_dir is None:
  186. return None
  187. return os.path.join(output_dir, "results_{}.xml".format(binary_name))
  188. class TestBinary():
  189. # Is only used when writing to a file.
  190. output_format = 'xml'
  191. def __init__(self, binary_path):
  192. self.binary_path = binary_path
  193. def run(self, included_tests=None, excluded_tests=None,
  194. output_file_path=None, verbosity=Verbosity.CHATTY):
  195. gtest_filter = TestBinary.__get_gtest_filter(included_tests,
  196. excluded_tests)
  197. gtest_output = TestBinary.__get_gtest_output(output_file_path)
  198. args = [self.binary_path, gtest_filter, gtest_output]
  199. stdout, stderr = TestBinary.__get_stdout_and_stderr(verbosity)
  200. returncode = 0
  201. try:
  202. returncode = subprocess.call(args, stdout=stdout, stderr=stderr)
  203. except Exception as exception:
  204. if Verbosity.ge(verbosity, Verbosity.ERRORS):
  205. print("An error occurred while running '{}':".format(self.binary_path),
  206. '\n', exception, file=sys.stderr)
  207. returncode = 1
  208. return returncode
  209. @staticmethod
  210. def __get_gtest_filter(included_tests, excluded_tests):
  211. included_tests_string = TestBinary.__list_tests(included_tests)
  212. excluded_tests_string = TestBinary.__list_tests(excluded_tests)
  213. gtest_filter = "--gtest_filter={}-{}".format(included_tests_string,
  214. excluded_tests_string)
  215. return gtest_filter
  216. @staticmethod
  217. def __get_gtest_output(output_file_path):
  218. gtest_output = ""
  219. if output_file_path is not None:
  220. gtest_output = "--gtest_output={0}:{1}".format(TestBinary.output_format,
  221. output_file_path)
  222. return gtest_output
  223. @staticmethod
  224. def __list_tests(tests):
  225. if tests is None:
  226. return ''
  227. return ':'.join(tests)
  228. @staticmethod
  229. def __get_stdout_and_stderr(verbosity):
  230. stdout = stderr = None
  231. if Verbosity.le(verbosity, Verbosity.ERRORS):
  232. devnull = open(os.devnull, 'w')
  233. stdout = devnull
  234. if verbosity == Verbosity.SILENT:
  235. stderr = devnull
  236. return (stdout, stderr)