You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

440 line
13 KiB

  1. # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
  2. # License: MIT. See LICENSE
  3. import frappe
  4. import unittest, json, sys, os
  5. import time
  6. import importlib
  7. from frappe.modules import load_doctype_module, get_module_name
  8. import frappe.utils.scheduler
  9. import cProfile, pstats
  10. from io import StringIO
  11. from importlib import reload
  12. from frappe.model.naming import revert_series_if_last
  13. unittest_runner = unittest.TextTestRunner
  14. SLOW_TEST_THRESHOLD = 2
  15. def xmlrunner_wrapper(output):
  16. """Convenience wrapper to keep method signature unchanged for XMLTestRunner and TextTestRunner"""
  17. try:
  18. import xmlrunner
  19. except ImportError:
  20. print("Development dependencies are required to execute this command. To install run:")
  21. print("$ bench setup requirements --dev")
  22. raise
  23. def _runner(*args, **kwargs):
  24. kwargs['output'] = output
  25. return xmlrunner.XMLTestRunner(*args, **kwargs)
  26. return _runner
  27. def main(app=None, module=None, doctype=None, verbose=False, tests=(),
  28. force=False, profile=False, junit_xml_output=None, ui_tests=False,
  29. doctype_list_path=None, skip_test_records=False, failfast=False, case=None):
  30. global unittest_runner
  31. if doctype_list_path:
  32. app, doctype_list_path = doctype_list_path.split(os.path.sep, 1)
  33. with open(frappe.get_app_path(app, doctype_list_path), 'r') as f:
  34. doctype = f.read().strip().splitlines()
  35. if ui_tests:
  36. print("Selenium testing has been deprecated\nUse bench --site {site_name} run-ui-tests for Cypress tests")
  37. xmloutput_fh = None
  38. if junit_xml_output:
  39. xmloutput_fh = open(junit_xml_output, 'wb')
  40. unittest_runner = xmlrunner_wrapper(xmloutput_fh)
  41. else:
  42. unittest_runner = unittest.TextTestRunner
  43. try:
  44. frappe.flags.print_messages = verbose
  45. frappe.flags.in_test = True
  46. if not frappe.db:
  47. frappe.connect()
  48. # workaround! since there is no separate test db
  49. frappe.clear_cache()
  50. scheduler_disabled_by_user = frappe.utils.scheduler.is_scheduler_disabled()
  51. if not scheduler_disabled_by_user:
  52. frappe.utils.scheduler.disable_scheduler()
  53. set_test_email_config()
  54. frappe.conf.update({'bench_id': 'test_bench', 'use_rq_auth': False})
  55. if not frappe.flags.skip_before_tests:
  56. if verbose:
  57. print('Running "before_tests" hooks')
  58. for fn in frappe.get_hooks("before_tests", app_name=app):
  59. frappe.get_attr(fn)()
  60. if doctype:
  61. ret = run_tests_for_doctype(doctype, verbose, tests, force, profile, failfast=failfast, junit_xml_output=junit_xml_output)
  62. elif module:
  63. ret = run_tests_for_module(module, verbose, tests, profile, failfast=failfast, junit_xml_output=junit_xml_output, case=case)
  64. else:
  65. ret = run_all_tests(app, verbose, profile, ui_tests, failfast=failfast, junit_xml_output=junit_xml_output)
  66. if not scheduler_disabled_by_user:
  67. frappe.utils.scheduler.enable_scheduler()
  68. if frappe.db: frappe.db.commit()
  69. # workaround! since there is no separate test db
  70. frappe.clear_cache()
  71. return ret
  72. finally:
  73. if xmloutput_fh:
  74. xmloutput_fh.flush()
  75. xmloutput_fh.close()
  76. def set_test_email_config():
  77. frappe.conf.update({
  78. "auto_email_id": "test@example.com",
  79. "mail_server": "smtp.example.com",
  80. "mail_login": "test@example.com",
  81. "mail_password": "test",
  82. "admin_password": "admin"
  83. })
  84. class TimeLoggingTestResult(unittest.TextTestResult):
  85. def startTest(self, test):
  86. self._started_at = time.time()
  87. super(TimeLoggingTestResult, self).startTest(test)
  88. def addSuccess(self, test):
  89. elapsed = time.time() - self._started_at
  90. name = self.getDescription(test)
  91. if elapsed >= SLOW_TEST_THRESHOLD:
  92. self.stream.write("\n{} ({:.03}s)\n".format(name, elapsed))
  93. super(TimeLoggingTestResult, self).addSuccess(test)
  94. def run_all_tests(app=None, verbose=False, profile=False, ui_tests=False, failfast=False, junit_xml_output=False):
  95. import os
  96. apps = [app] if app else frappe.get_installed_apps()
  97. test_suite = unittest.TestSuite()
  98. for app in apps:
  99. for path, folders, files in os.walk(frappe.get_pymodule_path(app)):
  100. for dontwalk in ('locals', '.git', 'public', '__pycache__'):
  101. if dontwalk in folders:
  102. folders.remove(dontwalk)
  103. # for predictability
  104. folders.sort()
  105. files.sort()
  106. # print path
  107. for filename in files:
  108. if filename.startswith("test_") and filename.endswith(".py")\
  109. and filename != 'test_runner.py':
  110. # print filename[:-3]
  111. _add_test(app, path, filename, verbose,
  112. test_suite, ui_tests)
  113. if junit_xml_output:
  114. runner = unittest_runner(verbosity=1+(verbose and 1 or 0), failfast=failfast)
  115. else:
  116. runner = unittest_runner(resultclass=TimeLoggingTestResult, verbosity=1+(verbose and 1 or 0), failfast=failfast)
  117. if profile:
  118. pr = cProfile.Profile()
  119. pr.enable()
  120. out = runner.run(test_suite)
  121. if profile:
  122. pr.disable()
  123. s = StringIO()
  124. ps = pstats.Stats(pr, stream=s).sort_stats('cumulative')
  125. ps.print_stats()
  126. print(s.getvalue())
  127. return out
  128. def run_tests_for_doctype(doctypes, verbose=False, tests=(), force=False, profile=False, failfast=False, junit_xml_output=False):
  129. modules = []
  130. if not isinstance(doctypes, (list, tuple)):
  131. doctypes = [doctypes]
  132. for doctype in doctypes:
  133. module = frappe.db.get_value("DocType", doctype, "module")
  134. if not module:
  135. print('Invalid doctype {0}'.format(doctype))
  136. sys.exit(1)
  137. test_module = get_module_name(doctype, module, "test_")
  138. if force:
  139. for name in frappe.db.sql_list("select name from `tab%s`" % doctype):
  140. frappe.delete_doc(doctype, name, force=True)
  141. make_test_records(doctype, verbose=verbose, force=force)
  142. modules.append(importlib.import_module(test_module))
  143. return _run_unittest(modules, verbose=verbose, tests=tests, profile=profile, failfast=failfast, junit_xml_output=junit_xml_output)
  144. def run_tests_for_module(module, verbose=False, tests=(), profile=False, failfast=False, junit_xml_output=False, case=None):
  145. module = importlib.import_module(module)
  146. if hasattr(module, "test_dependencies"):
  147. for doctype in module.test_dependencies:
  148. make_test_records(doctype, verbose=verbose)
  149. frappe.db.commit()
  150. return _run_unittest(module, verbose=verbose, tests=tests, profile=profile, failfast=failfast, junit_xml_output=junit_xml_output, case=case)
  151. def _run_unittest(modules, verbose=False, tests=(), profile=False, failfast=False, junit_xml_output=False, case=None):
  152. frappe.db.begin()
  153. test_suite = unittest.TestSuite()
  154. if not isinstance(modules, (list, tuple)):
  155. modules = [modules]
  156. for module in modules:
  157. if case:
  158. module_test_cases = unittest.TestLoader().loadTestsFromTestCase(getattr(module, case))
  159. else:
  160. module_test_cases = unittest.TestLoader().loadTestsFromModule(module)
  161. if tests:
  162. for each in module_test_cases:
  163. for test_case in each.__dict__["_tests"]:
  164. if test_case.__dict__["_testMethodName"] in tests:
  165. test_suite.addTest(test_case)
  166. else:
  167. test_suite.addTest(module_test_cases)
  168. if junit_xml_output:
  169. runner = unittest_runner(verbosity=1+(verbose and 1 or 0), failfast=failfast)
  170. else:
  171. runner = unittest_runner(resultclass=TimeLoggingTestResult, verbosity=1+(verbose and 1 or 0), failfast=failfast)
  172. if profile:
  173. pr = cProfile.Profile()
  174. pr.enable()
  175. frappe.flags.tests_verbose = verbose
  176. out = runner.run(test_suite)
  177. if profile:
  178. pr.disable()
  179. s = StringIO()
  180. ps = pstats.Stats(pr, stream=s).sort_stats('cumulative')
  181. ps.print_stats()
  182. print(s.getvalue())
  183. return out
  184. def _add_test(app, path, filename, verbose, test_suite=None, ui_tests=False):
  185. import os
  186. if os.path.sep.join(["doctype", "doctype", "boilerplate"]) in path:
  187. # in /doctype/doctype/boilerplate/
  188. return
  189. app_path = frappe.get_pymodule_path(app)
  190. relative_path = os.path.relpath(path, app_path)
  191. if relative_path=='.':
  192. module_name = app
  193. else:
  194. module_name = '{app}.{relative_path}.{module_name}'.format(app=app,
  195. relative_path=relative_path.replace('/', '.'), module_name=filename[:-3])
  196. module = importlib.import_module(module_name)
  197. if hasattr(module, "test_dependencies"):
  198. for doctype in module.test_dependencies:
  199. make_test_records(doctype, verbose=verbose)
  200. is_ui_test = True if hasattr(module, 'TestDriver') else False
  201. if is_ui_test != ui_tests:
  202. return
  203. if not test_suite:
  204. test_suite = unittest.TestSuite()
  205. if os.path.basename(os.path.dirname(path))=="doctype":
  206. txt_file = os.path.join(path, filename[5:].replace(".py", ".json"))
  207. if os.path.exists(txt_file):
  208. with open(txt_file, 'r') as f:
  209. doc = json.loads(f.read())
  210. doctype = doc["name"]
  211. make_test_records(doctype, verbose)
  212. test_suite.addTest(unittest.TestLoader().loadTestsFromModule(module))
  213. def make_test_records(doctype, verbose=0, force=False):
  214. if not frappe.db:
  215. frappe.connect()
  216. if frappe.flags.skip_test_records:
  217. return
  218. for options in get_dependencies(doctype):
  219. if options == "[Select]":
  220. continue
  221. if options not in frappe.local.test_objects:
  222. frappe.local.test_objects[options] = []
  223. make_test_records(options, verbose, force)
  224. make_test_records_for_doctype(options, verbose, force)
  225. def get_modules(doctype):
  226. module = frappe.db.get_value("DocType", doctype, "module")
  227. try:
  228. test_module = load_doctype_module(doctype, module, "test_")
  229. if test_module:
  230. reload(test_module)
  231. except ImportError:
  232. test_module = None
  233. return module, test_module
  234. def get_dependencies(doctype):
  235. module, test_module = get_modules(doctype)
  236. meta = frappe.get_meta(doctype)
  237. link_fields = meta.get_link_fields()
  238. for df in meta.get_table_fields():
  239. link_fields.extend(frappe.get_meta(df.options).get_link_fields())
  240. options_list = [df.options for df in link_fields] + [doctype]
  241. if hasattr(test_module, "test_dependencies"):
  242. options_list += test_module.test_dependencies
  243. options_list = list(set(options_list))
  244. if hasattr(test_module, "test_ignore"):
  245. for doctype_name in test_module.test_ignore:
  246. if doctype_name in options_list:
  247. options_list.remove(doctype_name)
  248. options_list.sort()
  249. return options_list
  250. def make_test_records_for_doctype(doctype, verbose=0, force=False):
  251. if not force and doctype in get_test_record_log():
  252. return
  253. module, test_module = get_modules(doctype)
  254. if verbose:
  255. print("Making for " + doctype)
  256. if hasattr(test_module, "_make_test_records"):
  257. frappe.local.test_objects[doctype] += test_module._make_test_records(verbose)
  258. elif hasattr(test_module, "test_records"):
  259. if doctype in frappe.local.test_objects:
  260. frappe.local.test_objects[doctype] += make_test_objects(doctype, test_module.test_records, verbose, force)
  261. else:
  262. frappe.local.test_objects[doctype] = make_test_objects(doctype, test_module.test_records, verbose, force)
  263. else:
  264. test_records = frappe.get_test_records(doctype)
  265. if test_records:
  266. frappe.local.test_objects[doctype] += make_test_objects(doctype, test_records, verbose, force)
  267. elif verbose:
  268. print_mandatory_fields(doctype)
  269. add_to_test_record_log(doctype)
  270. def make_test_objects(doctype, test_records=None, verbose=None, reset=False):
  271. '''Make test objects from given list of `test_records` or from `test_records.json`'''
  272. records = []
  273. def revert_naming(d):
  274. if getattr(d, 'naming_series', None):
  275. revert_series_if_last(d.naming_series, d.name)
  276. if test_records is None:
  277. test_records = frappe.get_test_records(doctype)
  278. for doc in test_records:
  279. if not doc.get("doctype"):
  280. doc["doctype"] = doctype
  281. d = frappe.copy_doc(doc)
  282. if d.meta.get_field("naming_series"):
  283. if not d.naming_series:
  284. d.naming_series = "_T-" + d.doctype + "-"
  285. if doc.get('name'):
  286. d.name = doc.get('name')
  287. else:
  288. d.set_new_name()
  289. if frappe.db.exists(d.doctype, d.name) and not reset:
  290. frappe.db.rollback()
  291. # do not create test records, if already exists
  292. continue
  293. # submit if docstatus is set to 1 for test record
  294. docstatus = d.docstatus
  295. d.docstatus = 0
  296. try:
  297. d.run_method("before_test_insert")
  298. d.insert(ignore_if_duplicate=True)
  299. if docstatus == 1:
  300. d.submit()
  301. except frappe.NameError:
  302. revert_naming(d)
  303. except Exception as e:
  304. if d.flags.ignore_these_exceptions_in_test and e.__class__ in d.flags.ignore_these_exceptions_in_test:
  305. revert_naming(d)
  306. else:
  307. raise
  308. records.append(d.name)
  309. frappe.db.commit()
  310. return records
  311. def print_mandatory_fields(doctype):
  312. print("Please setup make_test_records for: " + doctype)
  313. print("-" * 60)
  314. meta = frappe.get_meta(doctype)
  315. print("Autoname: " + (meta.autoname or ""))
  316. print("Mandatory Fields: ")
  317. for d in meta.get("fields", {"reqd":1}):
  318. print(d.parent + ":" + d.fieldname + " | " + d.fieldtype + " | " + (d.options or ""))
  319. print()
  320. def add_to_test_record_log(doctype):
  321. '''Add `doctype` to site/.test_log
  322. `.test_log` is a cache of all doctypes for which test records are created'''
  323. test_record_log = get_test_record_log()
  324. if doctype not in test_record_log:
  325. frappe.flags.test_record_log.append(doctype)
  326. with open(frappe.get_site_path('.test_log'), 'w') as f:
  327. f.write('\n'.join(filter(None, frappe.flags.test_record_log)))
  328. def get_test_record_log():
  329. '''Return the list of doctypes for which test records have been created'''
  330. if 'test_record_log' not in frappe.flags:
  331. if os.path.exists(frappe.get_site_path('.test_log')):
  332. with open(frappe.get_site_path('.test_log'), 'r') as f:
  333. frappe.flags.test_record_log = f.read().splitlines()
  334. else:
  335. frappe.flags.test_record_log = []
  336. return frappe.flags.test_record_log