PageRenderTime 1382ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/buildscripts/resmokelib/testing/testcases.py

https://gitlab.com/0072016/0072016-ApplePayDevice-
Python | 418 lines | 375 code | 21 blank | 22 comment | 4 complexity | 48455a57eed4dbac8e6b0bc2abe385af MD5 | raw file
  1. """
  2. Subclasses of unittest.TestCase.
  3. """
  4. from __future__ import absolute_import
  5. import os
  6. import os.path
  7. import shutil
  8. import unittest
  9. from .. import config
  10. from .. import core
  11. from .. import logging
  12. from .. import utils
  13. def make_test_case(test_kind, *args, **kwargs):
  14. """
  15. Factory function for creating TestCase instances.
  16. """
  17. if test_kind not in _TEST_CASES:
  18. raise ValueError("Unknown test kind '%s'" % (test_kind))
  19. return _TEST_CASES[test_kind](*args, **kwargs)
  20. class TestCase(unittest.TestCase):
  21. """
  22. A test case to execute.
  23. """
  24. def __init__(self, logger, test_kind, test_name):
  25. """
  26. Initializes the TestCase with the name of the test.
  27. """
  28. unittest.TestCase.__init__(self, methodName="run_test")
  29. if not isinstance(logger, logging.Logger):
  30. raise TypeError("logger must be a Logger instance")
  31. if not isinstance(test_kind, basestring):
  32. raise TypeError("test_kind must be a string")
  33. if not isinstance(test_name, basestring):
  34. raise TypeError("test_name must be a string")
  35. self.logger = logger
  36. self.test_kind = test_kind
  37. self.test_name = test_name
  38. self.fixture = None
  39. self.return_code = None
  40. def long_name(self):
  41. """
  42. Returns the path to the test, relative to the current working directory.
  43. """
  44. return os.path.relpath(self.test_name)
  45. def basename(self):
  46. """
  47. Returns the basename of the test.
  48. """
  49. return os.path.basename(self.test_name)
  50. def short_name(self):
  51. """
  52. Returns the basename of the test without the file extension.
  53. """
  54. return os.path.splitext(self.basename())[0]
  55. def id(self):
  56. return self.test_name
  57. def shortDescription(self):
  58. return "%s %s" % (self.test_kind, self.test_name)
  59. def configure(self, fixture):
  60. """
  61. Stores 'fixture' as an attribute for later use during execution.
  62. """
  63. self.fixture = fixture
  64. def run_test(self):
  65. """
  66. Runs the specified test.
  67. """
  68. raise NotImplementedError("run_test must be implemented by TestCase subclasses")
  69. def as_command(self):
  70. """
  71. Returns the command invocation used to run the test.
  72. """
  73. return self._make_process().as_command()
  74. def _execute(self, process):
  75. """
  76. Runs the specified process.
  77. """
  78. if config.INTERNAL_EXECUTOR_NAME is not None:
  79. self.logger.info("Starting %s under executor %s...\n%s",
  80. self.shortDescription(),
  81. config.INTERNAL_EXECUTOR_NAME,
  82. process.as_command())
  83. else:
  84. self.logger.info("Starting %s...\n%s", self.shortDescription(), process.as_command())
  85. process.start()
  86. self.logger.info("%s started with pid %s.", self.shortDescription(), process.pid)
  87. self.return_code = process.wait()
  88. if self.return_code != 0:
  89. raise self.failureException("%s failed" % (self.shortDescription()))
  90. self.logger.info("%s finished.", self.shortDescription())
  91. def _make_process(self):
  92. """
  93. Returns a new Process instance that could be used to run the
  94. test or log the command.
  95. """
  96. raise NotImplementedError("_make_process must be implemented by TestCase subclasses")
  97. class CPPUnitTestCase(TestCase):
  98. """
  99. A C++ unit test to execute.
  100. """
  101. def __init__(self,
  102. logger,
  103. program_executable,
  104. program_options=None):
  105. """
  106. Initializes the CPPUnitTestCase with the executable to run.
  107. """
  108. TestCase.__init__(self, logger, "Program", program_executable)
  109. self.program_executable = program_executable
  110. self.program_options = utils.default_if_none(program_options, {}).copy()
  111. def run_test(self):
  112. try:
  113. program = self._make_process()
  114. self._execute(program)
  115. except self.failureException:
  116. raise
  117. except:
  118. self.logger.exception("Encountered an error running C++ unit test %s.", self.basename())
  119. raise
  120. def _make_process(self):
  121. return core.process.Process(self.logger,
  122. [self.program_executable],
  123. **self.program_options)
  124. class CPPIntegrationTestCase(TestCase):
  125. """
  126. A C++ integration test to execute.
  127. """
  128. def __init__(self,
  129. logger,
  130. program_executable,
  131. program_options=None):
  132. """
  133. Initializes the CPPIntegrationTestCase with the executable to run.
  134. """
  135. TestCase.__init__(self, logger, "Program", program_executable)
  136. self.program_executable = program_executable
  137. self.program_options = utils.default_if_none(program_options, {}).copy()
  138. def configure(self, fixture):
  139. TestCase.configure(self, fixture)
  140. self.program_options["connectionString"] = self.fixture.get_connection_string()
  141. def run_test(self):
  142. try:
  143. program = self._make_process()
  144. self._execute(program)
  145. except self.failureException:
  146. raise
  147. except:
  148. self.logger.exception("Encountered an error running C++ integration test %s.",
  149. self.basename())
  150. raise
  151. def _make_process(self):
  152. return core.programs.generic_program(self.logger,
  153. [self.program_executable],
  154. **self.program_options)
  155. class DBTestCase(TestCase):
  156. """
  157. A dbtest to execute.
  158. """
  159. def __init__(self,
  160. logger,
  161. dbtest_suite,
  162. dbtest_executable=None,
  163. dbtest_options=None):
  164. """
  165. Initializes the DBTestCase with the dbtest suite to run.
  166. """
  167. TestCase.__init__(self, logger, "DBTest", dbtest_suite)
  168. # Command line options override the YAML configuration.
  169. self.dbtest_executable = utils.default_if_none(config.DBTEST_EXECUTABLE, dbtest_executable)
  170. self.dbtest_suite = dbtest_suite
  171. self.dbtest_options = utils.default_if_none(dbtest_options, {}).copy()
  172. def configure(self, fixture):
  173. TestCase.configure(self, fixture)
  174. # If a dbpath was specified, then use it as a container for all other dbpaths.
  175. dbpath_prefix = self.dbtest_options.pop("dbpath", DBTestCase._get_dbpath_prefix())
  176. dbpath = os.path.join(dbpath_prefix, "job%d" % (self.fixture.job_num), "unittest")
  177. self.dbtest_options["dbpath"] = dbpath
  178. shutil.rmtree(dbpath, ignore_errors=True)
  179. try:
  180. os.makedirs(dbpath)
  181. except os.error:
  182. # Directory already exists.
  183. pass
  184. def run_test(self):
  185. try:
  186. dbtest = self._make_process()
  187. self._execute(dbtest)
  188. except self.failureException:
  189. raise
  190. except:
  191. self.logger.exception("Encountered an error running dbtest suite %s.", self.basename())
  192. raise
  193. def _make_process(self):
  194. return core.programs.dbtest_program(self.logger,
  195. executable=self.dbtest_executable,
  196. suites=[self.dbtest_suite],
  197. **self.dbtest_options)
  198. @staticmethod
  199. def _get_dbpath_prefix():
  200. """
  201. Returns the prefix of the dbpath to use for the dbtest
  202. executable.
  203. Order of preference:
  204. 1. The --dbpathPrefix specified at the command line.
  205. 2. Value of the TMPDIR environment variable.
  206. 3. Value of the TEMP environment variable.
  207. 4. Value of the TMP environment variable.
  208. 5. The /tmp directory.
  209. """
  210. if config.DBPATH_PREFIX is not None:
  211. return config.DBPATH_PREFIX
  212. for env_var in ("TMPDIR", "TEMP", "TMP"):
  213. if env_var in os.environ:
  214. return os.environ[env_var]
  215. return os.path.normpath("/tmp")
  216. class JSTestCase(TestCase):
  217. """
  218. A jstest to execute.
  219. """
  220. def __init__(self,
  221. logger,
  222. js_filename,
  223. shell_executable=None,
  224. shell_options=None,
  225. test_kind="JSTest"):
  226. "Initializes the JSTestCase with the JS file to run."
  227. TestCase.__init__(self, logger, test_kind, js_filename)
  228. # Command line options override the YAML configuration.
  229. self.shell_executable = utils.default_if_none(config.MONGO_EXECUTABLE, shell_executable)
  230. self.js_filename = js_filename
  231. self.shell_options = utils.default_if_none(shell_options, {}).copy()
  232. def configure(self, fixture):
  233. TestCase.configure(self, fixture)
  234. if self.fixture.port is not None:
  235. self.shell_options["port"] = self.fixture.port
  236. global_vars = self.shell_options.get("global_vars", {}).copy()
  237. data_dir = self._get_data_dir(global_vars)
  238. # Set MongoRunner.dataPath if overridden at command line or not specified in YAML.
  239. if config.DBPATH_PREFIX is not None or "MongoRunner.dataPath" not in global_vars:
  240. # dataPath property is the dataDir property with a trailing slash.
  241. data_path = os.path.join(data_dir, "")
  242. else:
  243. data_path = global_vars["MongoRunner.dataPath"]
  244. global_vars["MongoRunner.dataDir"] = data_dir
  245. global_vars["MongoRunner.dataPath"] = data_path
  246. test_data = global_vars.get("TestData", {}).copy()
  247. test_data["minPort"] = core.network.PortAllocator.min_test_port(fixture.job_num)
  248. test_data["maxPort"] = core.network.PortAllocator.max_test_port(fixture.job_num)
  249. # Marks the main test when multiple test clients are run concurrently, to notify the test
  250. # of any code that should only be run once.
  251. test_data["isMainTest"] = True
  252. global_vars["TestData"] = test_data
  253. self.shell_options["global_vars"] = global_vars
  254. shutil.rmtree(data_dir, ignore_errors=True)
  255. try:
  256. os.makedirs(data_dir)
  257. except os.error:
  258. # Directory already exists.
  259. pass
  260. def _get_data_dir(self, global_vars):
  261. """
  262. Returns the value that the mongo shell should set for the
  263. MongoRunner.dataDir property.
  264. """
  265. # Command line options override the YAML configuration.
  266. data_dir_prefix = utils.default_if_none(config.DBPATH_PREFIX,
  267. global_vars.get("MongoRunner.dataDir"))
  268. data_dir_prefix = utils.default_if_none(data_dir_prefix, config.DEFAULT_DBPATH_PREFIX)
  269. return os.path.join(data_dir_prefix,
  270. "job%d" % (self.fixture.job_num),
  271. config.MONGO_RUNNER_SUBDIR)
  272. def run_test(self):
  273. try:
  274. shell = self._make_process()
  275. self._execute(shell)
  276. except self.failureException:
  277. raise
  278. except:
  279. self.logger.exception("Encountered an error running jstest %s.", self.basename())
  280. raise
  281. def _make_process(self):
  282. return core.programs.mongo_shell_program(self.logger,
  283. executable=self.shell_executable,
  284. filename=self.js_filename,
  285. **self.shell_options)
  286. class MongosTestCase(TestCase):
  287. """
  288. A TestCase which runs a mongos binary with the given parameters.
  289. """
  290. def __init__(self,
  291. logger,
  292. mongos_options):
  293. """
  294. Initializes the mongos test and saves the options.
  295. """
  296. self.mongos_executable = utils.default_if_none(config.MONGOS_EXECUTABLE,
  297. config.DEFAULT_MONGOS_EXECUTABLE)
  298. # Use the executable as the test name.
  299. TestCase.__init__(self, logger, "mongos", self.mongos_executable)
  300. self.options = mongos_options.copy()
  301. def configure(self, fixture):
  302. """
  303. Ensures the --test option is present in the mongos options.
  304. """
  305. TestCase.configure(self, fixture)
  306. # Always specify test option to ensure the mongos will terminate.
  307. if "test" not in self.options:
  308. self.options["test"] = ""
  309. def run_test(self):
  310. try:
  311. mongos = self._make_process()
  312. self._execute(mongos)
  313. except self.failureException:
  314. raise
  315. except:
  316. self.logger.exception("Encountered an error running %s.", mongos.as_command())
  317. raise
  318. def _make_process(self):
  319. return core.programs.mongos_program(self.logger,
  320. executable=self.mongos_executable,
  321. **self.options)
  322. _TEST_CASES = {
  323. "cpp_unit_test": CPPUnitTestCase,
  324. "cpp_integration_test": CPPIntegrationTestCase,
  325. "db_test": DBTestCase,
  326. "js_test": JSTestCase,
  327. "mongos_test": MongosTestCase,
  328. }