Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 
 
 

426 wiersze
12 KiB

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