PageRenderTime 43ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/_pytest/pytester.py

https://bitbucket.org/evelyn559/pypy
Python | 685 lines | 664 code | 14 blank | 7 comment | 12 complexity | 6199121cfe26083aaec2e876a44e4097 MD5 | raw file
  1. """ (disabled by default) support for testing py.test and py.test plugins. """
  2. import py, pytest
  3. import sys, os
  4. import re
  5. import inspect
  6. import time
  7. from fnmatch import fnmatch
  8. from _pytest.main import Session, EXIT_OK
  9. from py.builtin import print_
  10. from _pytest.core import HookRelay
  11. def pytest_addoption(parser):
  12. group = parser.getgroup("pylib")
  13. group.addoption('--no-tools-on-path',
  14. action="store_true", dest="notoolsonpath", default=False,
  15. help=("discover tools on PATH instead of going through py.cmdline.")
  16. )
  17. def pytest_configure(config):
  18. # This might be called multiple times. Only take the first.
  19. global _pytest_fullpath
  20. import pytest
  21. try:
  22. _pytest_fullpath
  23. except NameError:
  24. _pytest_fullpath = os.path.abspath(pytest.__file__.rstrip("oc"))
  25. def pytest_funcarg___pytest(request):
  26. return PytestArg(request)
  27. class PytestArg:
  28. def __init__(self, request):
  29. self.request = request
  30. def gethookrecorder(self, hook):
  31. hookrecorder = HookRecorder(hook._pm)
  32. hookrecorder.start_recording(hook._hookspecs)
  33. self.request.addfinalizer(hookrecorder.finish_recording)
  34. return hookrecorder
  35. class ParsedCall:
  36. def __init__(self, name, locals):
  37. assert '_name' not in locals
  38. self.__dict__.update(locals)
  39. self.__dict__.pop('self')
  40. self._name = name
  41. def __repr__(self):
  42. d = self.__dict__.copy()
  43. del d['_name']
  44. return "<ParsedCall %r(**%r)>" %(self._name, d)
  45. class HookRecorder:
  46. def __init__(self, pluginmanager):
  47. self._pluginmanager = pluginmanager
  48. self.calls = []
  49. self._recorders = {}
  50. def start_recording(self, hookspecs):
  51. if not isinstance(hookspecs, (list, tuple)):
  52. hookspecs = [hookspecs]
  53. for hookspec in hookspecs:
  54. assert hookspec not in self._recorders
  55. class RecordCalls:
  56. _recorder = self
  57. for name, method in vars(hookspec).items():
  58. if name[0] != "_":
  59. setattr(RecordCalls, name, self._makecallparser(method))
  60. recorder = RecordCalls()
  61. self._recorders[hookspec] = recorder
  62. self._pluginmanager.register(recorder)
  63. self.hook = HookRelay(hookspecs, pm=self._pluginmanager,
  64. prefix="pytest_")
  65. def finish_recording(self):
  66. for recorder in self._recorders.values():
  67. self._pluginmanager.unregister(recorder)
  68. self._recorders.clear()
  69. def _makecallparser(self, method):
  70. name = method.__name__
  71. args, varargs, varkw, default = py.std.inspect.getargspec(method)
  72. if not args or args[0] != "self":
  73. args.insert(0, 'self')
  74. fspec = py.std.inspect.formatargspec(args, varargs, varkw, default)
  75. # we use exec because we want to have early type
  76. # errors on wrong input arguments, using
  77. # *args/**kwargs delays this and gives errors
  78. # elsewhere
  79. exec (py.code.compile("""
  80. def %(name)s%(fspec)s:
  81. self._recorder.calls.append(
  82. ParsedCall(%(name)r, locals()))
  83. """ % locals()))
  84. return locals()[name]
  85. def getcalls(self, names):
  86. if isinstance(names, str):
  87. names = names.split()
  88. for name in names:
  89. for cls in self._recorders:
  90. if name in vars(cls):
  91. break
  92. else:
  93. raise ValueError("callname %r not found in %r" %(
  94. name, self._recorders.keys()))
  95. l = []
  96. for call in self.calls:
  97. if call._name in names:
  98. l.append(call)
  99. return l
  100. def contains(self, entries):
  101. __tracebackhide__ = True
  102. from py.builtin import print_
  103. i = 0
  104. entries = list(entries)
  105. backlocals = py.std.sys._getframe(1).f_locals
  106. while entries:
  107. name, check = entries.pop(0)
  108. for ind, call in enumerate(self.calls[i:]):
  109. if call._name == name:
  110. print_("NAMEMATCH", name, call)
  111. if eval(check, backlocals, call.__dict__):
  112. print_("CHECKERMATCH", repr(check), "->", call)
  113. else:
  114. print_("NOCHECKERMATCH", repr(check), "-", call)
  115. continue
  116. i += ind + 1
  117. break
  118. print_("NONAMEMATCH", name, "with", call)
  119. else:
  120. py.test.fail("could not find %r check %r" % (name, check))
  121. def popcall(self, name):
  122. __tracebackhide__ = True
  123. for i, call in enumerate(self.calls):
  124. if call._name == name:
  125. del self.calls[i]
  126. return call
  127. lines = ["could not find call %r, in:" % (name,)]
  128. lines.extend([" %s" % str(x) for x in self.calls])
  129. py.test.fail("\n".join(lines))
  130. def getcall(self, name):
  131. l = self.getcalls(name)
  132. assert len(l) == 1, (name, l)
  133. return l[0]
  134. def pytest_funcarg__linecomp(request):
  135. return LineComp()
  136. def pytest_funcarg__LineMatcher(request):
  137. return LineMatcher
  138. def pytest_funcarg__testdir(request):
  139. tmptestdir = TmpTestdir(request)
  140. return tmptestdir
  141. rex_outcome = re.compile("(\d+) (\w+)")
  142. class RunResult:
  143. def __init__(self, ret, outlines, errlines, duration):
  144. self.ret = ret
  145. self.outlines = outlines
  146. self.errlines = errlines
  147. self.stdout = LineMatcher(outlines)
  148. self.stderr = LineMatcher(errlines)
  149. self.duration = duration
  150. def parseoutcomes(self):
  151. for line in reversed(self.outlines):
  152. if 'seconds' in line:
  153. outcomes = rex_outcome.findall(line)
  154. if outcomes:
  155. d = {}
  156. for num, cat in outcomes:
  157. d[cat] = int(num)
  158. return d
  159. class TmpTestdir:
  160. def __init__(self, request):
  161. self.request = request
  162. self.Config = request.config.__class__
  163. self._pytest = request.getfuncargvalue("_pytest")
  164. # XXX remove duplication with tmpdir plugin
  165. basetmp = request.config._tmpdirhandler.ensuretemp("testdir")
  166. name = request.function.__name__
  167. for i in range(100):
  168. try:
  169. tmpdir = basetmp.mkdir(name + str(i))
  170. except py.error.EEXIST:
  171. continue
  172. break
  173. # we need to create another subdir
  174. # because Directory.collect() currently loads
  175. # conftest.py from sibling directories
  176. self.tmpdir = tmpdir.mkdir(name)
  177. self.plugins = []
  178. self._syspathremove = []
  179. self.chdir() # always chdir
  180. self.request.addfinalizer(self.finalize)
  181. def __repr__(self):
  182. return "<TmpTestdir %r>" % (self.tmpdir,)
  183. def finalize(self):
  184. for p in self._syspathremove:
  185. py.std.sys.path.remove(p)
  186. if hasattr(self, '_olddir'):
  187. self._olddir.chdir()
  188. # delete modules that have been loaded from tmpdir
  189. for name, mod in list(sys.modules.items()):
  190. if mod:
  191. fn = getattr(mod, '__file__', None)
  192. if fn and fn.startswith(str(self.tmpdir)):
  193. del sys.modules[name]
  194. def getreportrecorder(self, obj):
  195. if hasattr(obj, 'config'):
  196. obj = obj.config
  197. if hasattr(obj, 'hook'):
  198. obj = obj.hook
  199. assert hasattr(obj, '_hookspecs'), obj
  200. reprec = ReportRecorder(obj)
  201. reprec.hookrecorder = self._pytest.gethookrecorder(obj)
  202. reprec.hook = reprec.hookrecorder.hook
  203. return reprec
  204. def chdir(self):
  205. old = self.tmpdir.chdir()
  206. if not hasattr(self, '_olddir'):
  207. self._olddir = old
  208. def _makefile(self, ext, args, kwargs):
  209. items = list(kwargs.items())
  210. if args:
  211. source = py.builtin._totext("\n").join(
  212. map(py.builtin._totext, args)) + py.builtin._totext("\n")
  213. basename = self.request.function.__name__
  214. items.insert(0, (basename, source))
  215. ret = None
  216. for name, value in items:
  217. p = self.tmpdir.join(name).new(ext=ext)
  218. source = py.builtin._totext(py.code.Source(value)).lstrip()
  219. p.write(source.encode("utf-8"), "wb")
  220. if ret is None:
  221. ret = p
  222. return ret
  223. def makefile(self, ext, *args, **kwargs):
  224. return self._makefile(ext, args, kwargs)
  225. def makeini(self, source):
  226. return self.makefile('cfg', setup=source)
  227. def makeconftest(self, source):
  228. return self.makepyfile(conftest=source)
  229. def makeini(self, source):
  230. return self.makefile('.ini', tox=source)
  231. def getinicfg(self, source):
  232. p = self.makeini(source)
  233. return py.iniconfig.IniConfig(p)['pytest']
  234. def makepyfile(self, *args, **kwargs):
  235. return self._makefile('.py', args, kwargs)
  236. def maketxtfile(self, *args, **kwargs):
  237. return self._makefile('.txt', args, kwargs)
  238. def syspathinsert(self, path=None):
  239. if path is None:
  240. path = self.tmpdir
  241. py.std.sys.path.insert(0, str(path))
  242. self._syspathremove.append(str(path))
  243. def mkdir(self, name):
  244. return self.tmpdir.mkdir(name)
  245. def mkpydir(self, name):
  246. p = self.mkdir(name)
  247. p.ensure("__init__.py")
  248. return p
  249. Session = Session
  250. def getnode(self, config, arg):
  251. session = Session(config)
  252. assert '::' not in str(arg)
  253. p = py.path.local(arg)
  254. x = session.fspath.bestrelpath(p)
  255. config.hook.pytest_sessionstart(session=session)
  256. res = session.perform_collect([x], genitems=False)[0]
  257. config.hook.pytest_sessionfinish(session=session, exitstatus=EXIT_OK)
  258. return res
  259. def getpathnode(self, path):
  260. config = self.parseconfigure(path)
  261. session = Session(config)
  262. x = session.fspath.bestrelpath(path)
  263. config.hook.pytest_sessionstart(session=session)
  264. res = session.perform_collect([x], genitems=False)[0]
  265. config.hook.pytest_sessionfinish(session=session, exitstatus=EXIT_OK)
  266. return res
  267. def genitems(self, colitems):
  268. session = colitems[0].session
  269. result = []
  270. for colitem in colitems:
  271. result.extend(session.genitems(colitem))
  272. return result
  273. def inline_genitems(self, *args):
  274. #config = self.parseconfig(*args)
  275. config = self.parseconfigure(*args)
  276. rec = self.getreportrecorder(config)
  277. session = Session(config)
  278. config.hook.pytest_sessionstart(session=session)
  279. session.perform_collect()
  280. config.hook.pytest_sessionfinish(session=session, exitstatus=EXIT_OK)
  281. return session.items, rec
  282. def runitem(self, source):
  283. # used from runner functional tests
  284. item = self.getitem(source)
  285. # the test class where we are called from wants to provide the runner
  286. testclassinstance = py.builtin._getimself(self.request.function)
  287. runner = testclassinstance.getrunner()
  288. return runner(item)
  289. def inline_runsource(self, source, *cmdlineargs):
  290. p = self.makepyfile(source)
  291. l = list(cmdlineargs) + [p]
  292. return self.inline_run(*l)
  293. def inline_runsource1(self, *args):
  294. args = list(args)
  295. source = args.pop()
  296. p = self.makepyfile(source)
  297. l = list(args) + [p]
  298. reprec = self.inline_run(*l)
  299. reports = reprec.getreports("pytest_runtest_logreport")
  300. assert len(reports) == 1, reports
  301. return reports[0]
  302. def inline_run(self, *args):
  303. args = ("-s", ) + args # otherwise FD leakage
  304. config = self.parseconfig(*args)
  305. reprec = self.getreportrecorder(config)
  306. #config.pluginmanager.do_configure(config)
  307. config.hook.pytest_cmdline_main(config=config)
  308. #config.pluginmanager.do_unconfigure(config)
  309. return reprec
  310. def config_preparse(self):
  311. config = self.Config()
  312. for plugin in self.plugins:
  313. if isinstance(plugin, str):
  314. config.pluginmanager.import_plugin(plugin)
  315. else:
  316. if isinstance(plugin, dict):
  317. plugin = PseudoPlugin(plugin)
  318. if not config.pluginmanager.isregistered(plugin):
  319. config.pluginmanager.register(plugin)
  320. return config
  321. def parseconfig(self, *args):
  322. if not args:
  323. args = (self.tmpdir,)
  324. config = self.config_preparse()
  325. args = list(args)
  326. for x in args:
  327. if str(x).startswith('--basetemp'):
  328. break
  329. else:
  330. args.append("--basetemp=%s" % self.tmpdir.dirpath('basetemp'))
  331. config.parse(args)
  332. return config
  333. def reparseconfig(self, args=None):
  334. """ this is used from tests that want to re-invoke parse(). """
  335. if not args:
  336. args = [self.tmpdir]
  337. oldconfig = getattr(py.test, 'config', None)
  338. try:
  339. c = py.test.config = self.Config()
  340. c.basetemp = py.path.local.make_numbered_dir(prefix="reparse",
  341. keep=0, rootdir=self.tmpdir, lock_timeout=None)
  342. c.parse(args)
  343. c.pluginmanager.do_configure(c)
  344. self.request.addfinalizer(lambda: c.pluginmanager.do_unconfigure(c))
  345. return c
  346. finally:
  347. py.test.config = oldconfig
  348. def parseconfigure(self, *args):
  349. config = self.parseconfig(*args)
  350. config.pluginmanager.do_configure(config)
  351. self.request.addfinalizer(lambda:
  352. config.pluginmanager.do_unconfigure(config))
  353. return config
  354. def getitem(self, source, funcname="test_func"):
  355. for item in self.getitems(source):
  356. if item.name == funcname:
  357. return item
  358. assert 0, "%r item not found in module:\n%s" %(funcname, source)
  359. def getitems(self, source):
  360. modcol = self.getmodulecol(source)
  361. return self.genitems([modcol])
  362. def getmodulecol(self, source, configargs=(), withinit=False):
  363. kw = {self.request.function.__name__: py.code.Source(source).strip()}
  364. path = self.makepyfile(**kw)
  365. if withinit:
  366. self.makepyfile(__init__ = "#")
  367. self.config = config = self.parseconfigure(path, *configargs)
  368. node = self.getnode(config, path)
  369. #config.pluginmanager.do_unconfigure(config)
  370. return node
  371. def collect_by_name(self, modcol, name):
  372. for colitem in modcol._memocollect():
  373. if colitem.name == name:
  374. return colitem
  375. def popen(self, cmdargs, stdout, stderr, **kw):
  376. env = os.environ.copy()
  377. env['PYTHONPATH'] = os.pathsep.join(filter(None, [
  378. str(os.getcwd()), env.get('PYTHONPATH', '')]))
  379. kw['env'] = env
  380. #print "env", env
  381. return py.std.subprocess.Popen(cmdargs, stdout=stdout, stderr=stderr, **kw)
  382. def pytestmain(self, *args, **kwargs):
  383. ret = pytest.main(*args, **kwargs)
  384. if ret == 2:
  385. raise KeyboardInterrupt()
  386. def run(self, *cmdargs):
  387. return self._run(*cmdargs)
  388. def _run(self, *cmdargs):
  389. cmdargs = [str(x) for x in cmdargs]
  390. p1 = self.tmpdir.join("stdout")
  391. p2 = self.tmpdir.join("stderr")
  392. print_("running", cmdargs, "curdir=", py.path.local())
  393. f1 = p1.open("wb")
  394. f2 = p2.open("wb")
  395. now = time.time()
  396. popen = self.popen(cmdargs, stdout=f1, stderr=f2,
  397. close_fds=(sys.platform != "win32"))
  398. ret = popen.wait()
  399. f1.close()
  400. f2.close()
  401. out = p1.read("rb")
  402. out = getdecoded(out).splitlines()
  403. err = p2.read("rb")
  404. err = getdecoded(err).splitlines()
  405. def dump_lines(lines, fp):
  406. try:
  407. for line in lines:
  408. py.builtin.print_(line, file=fp)
  409. except UnicodeEncodeError:
  410. print("couldn't print to %s because of encoding" % (fp,))
  411. dump_lines(out, sys.stdout)
  412. dump_lines(err, sys.stderr)
  413. return RunResult(ret, out, err, time.time()-now)
  414. def runpybin(self, scriptname, *args):
  415. fullargs = self._getpybinargs(scriptname) + args
  416. return self.run(*fullargs)
  417. def _getpybinargs(self, scriptname):
  418. if not self.request.config.getvalue("notoolsonpath"):
  419. # XXX we rely on script refering to the correct environment
  420. # we cannot use "(py.std.sys.executable,script)"
  421. # becaue on windows the script is e.g. a py.test.exe
  422. return (py.std.sys.executable, _pytest_fullpath,)
  423. else:
  424. py.test.skip("cannot run %r with --no-tools-on-path" % scriptname)
  425. def runpython(self, script, prepend=True):
  426. if prepend:
  427. s = self._getsysprepend()
  428. if s:
  429. script.write(s + "\n" + script.read())
  430. return self.run(sys.executable, script)
  431. def _getsysprepend(self):
  432. if self.request.config.getvalue("notoolsonpath"):
  433. s = "import sys;sys.path.insert(0,%r);" % str(py._pydir.dirpath())
  434. else:
  435. s = ""
  436. return s
  437. def runpython_c(self, command):
  438. command = self._getsysprepend() + command
  439. return self.run(py.std.sys.executable, "-c", command)
  440. def runpytest(self, *args):
  441. p = py.path.local.make_numbered_dir(prefix="runpytest-",
  442. keep=None, rootdir=self.tmpdir)
  443. args = ('--basetemp=%s' % p, ) + args
  444. #for x in args:
  445. # if '--confcutdir' in str(x):
  446. # break
  447. #else:
  448. # pass
  449. # args = ('--confcutdir=.',) + args
  450. plugins = [x for x in self.plugins if isinstance(x, str)]
  451. if plugins:
  452. args = ('-p', plugins[0]) + args
  453. return self.runpybin("py.test", *args)
  454. def spawn_pytest(self, string, expect_timeout=10.0):
  455. if self.request.config.getvalue("notoolsonpath"):
  456. py.test.skip("--no-tools-on-path prevents running pexpect-spawn tests")
  457. basetemp = self.tmpdir.mkdir("pexpect")
  458. invoke = " ".join(map(str, self._getpybinargs("py.test")))
  459. cmd = "%s --basetemp=%s %s" % (invoke, basetemp, string)
  460. return self.spawn(cmd, expect_timeout=expect_timeout)
  461. def spawn(self, cmd, expect_timeout=10.0):
  462. pexpect = py.test.importorskip("pexpect", "2.4")
  463. if hasattr(sys, 'pypy_version_info') and '64' in py.std.platform.machine():
  464. pytest.skip("pypy-64 bit not supported")
  465. logfile = self.tmpdir.join("spawn.out")
  466. child = pexpect.spawn(cmd, logfile=logfile.open("w"))
  467. child.timeout = expect_timeout
  468. return child
  469. def getdecoded(out):
  470. try:
  471. return out.decode("utf-8")
  472. except UnicodeDecodeError:
  473. return "INTERNAL not-utf8-decodeable, truncated string:\n%s" % (
  474. py.io.saferepr(out),)
  475. class PseudoPlugin:
  476. def __init__(self, vars):
  477. self.__dict__.update(vars)
  478. class ReportRecorder(object):
  479. def __init__(self, hook):
  480. self.hook = hook
  481. self.pluginmanager = hook._pm
  482. self.pluginmanager.register(self)
  483. def getcall(self, name):
  484. return self.hookrecorder.getcall(name)
  485. def popcall(self, name):
  486. return self.hookrecorder.popcall(name)
  487. def getcalls(self, names):
  488. """ return list of ParsedCall instances matching the given eventname. """
  489. return self.hookrecorder.getcalls(names)
  490. # functionality for test reports
  491. def getreports(self, names="pytest_runtest_logreport pytest_collectreport"):
  492. return [x.report for x in self.getcalls(names)]
  493. def matchreport(self, inamepart="", names="pytest_runtest_logreport pytest_collectreport", when=None):
  494. """ return a testreport whose dotted import path matches """
  495. l = []
  496. for rep in self.getreports(names=names):
  497. if when and getattr(rep, 'when', None) != when:
  498. continue
  499. if not inamepart or inamepart in rep.nodeid.split("::"):
  500. l.append(rep)
  501. if not l:
  502. raise ValueError("could not find test report matching %r: no test reports at all!" %
  503. (inamepart,))
  504. if len(l) > 1:
  505. raise ValueError("found more than one testreport matching %r: %s" %(
  506. inamepart, l))
  507. return l[0]
  508. def getfailures(self, names='pytest_runtest_logreport pytest_collectreport'):
  509. return [rep for rep in self.getreports(names) if rep.failed]
  510. def getfailedcollections(self):
  511. return self.getfailures('pytest_collectreport')
  512. def listoutcomes(self):
  513. passed = []
  514. skipped = []
  515. failed = []
  516. for rep in self.getreports("pytest_runtest_logreport"):
  517. if rep.passed:
  518. if rep.when == "call":
  519. passed.append(rep)
  520. elif rep.skipped:
  521. skipped.append(rep)
  522. elif rep.failed:
  523. failed.append(rep)
  524. return passed, skipped, failed
  525. def countoutcomes(self):
  526. return [len(x) for x in self.listoutcomes()]
  527. def assertoutcome(self, passed=0, skipped=0, failed=0):
  528. realpassed, realskipped, realfailed = self.listoutcomes()
  529. assert passed == len(realpassed)
  530. assert skipped == len(realskipped)
  531. assert failed == len(realfailed)
  532. def clear(self):
  533. self.hookrecorder.calls[:] = []
  534. def unregister(self):
  535. self.pluginmanager.unregister(self)
  536. self.hookrecorder.finish_recording()
  537. class LineComp:
  538. def __init__(self):
  539. self.stringio = py.io.TextIO()
  540. def assert_contains_lines(self, lines2):
  541. """ assert that lines2 are contained (linearly) in lines1.
  542. return a list of extralines found.
  543. """
  544. __tracebackhide__ = True
  545. val = self.stringio.getvalue()
  546. self.stringio.truncate(0)
  547. self.stringio.seek(0)
  548. lines1 = val.split("\n")
  549. return LineMatcher(lines1).fnmatch_lines(lines2)
  550. class LineMatcher:
  551. def __init__(self, lines):
  552. self.lines = lines
  553. def str(self):
  554. return "\n".join(self.lines)
  555. def _getlines(self, lines2):
  556. if isinstance(lines2, str):
  557. lines2 = py.code.Source(lines2)
  558. if isinstance(lines2, py.code.Source):
  559. lines2 = lines2.strip().lines
  560. return lines2
  561. def fnmatch_lines_random(self, lines2):
  562. lines2 = self._getlines(lines2)
  563. for line in lines2:
  564. for x in self.lines:
  565. if line == x or fnmatch(x, line):
  566. print_("matched: ", repr(line))
  567. break
  568. else:
  569. raise ValueError("line %r not found in output" % line)
  570. def fnmatch_lines(self, lines2):
  571. def show(arg1, arg2):
  572. py.builtin.print_(arg1, arg2, file=py.std.sys.stderr)
  573. lines2 = self._getlines(lines2)
  574. lines1 = self.lines[:]
  575. nextline = None
  576. extralines = []
  577. __tracebackhide__ = True
  578. for line in lines2:
  579. nomatchprinted = False
  580. while lines1:
  581. nextline = lines1.pop(0)
  582. if line == nextline:
  583. show("exact match:", repr(line))
  584. break
  585. elif fnmatch(nextline, line):
  586. show("fnmatch:", repr(line))
  587. show(" with:", repr(nextline))
  588. break
  589. else:
  590. if not nomatchprinted:
  591. show("nomatch:", repr(line))
  592. nomatchprinted = True
  593. show(" and:", repr(nextline))
  594. extralines.append(nextline)
  595. else:
  596. py.test.fail("remains unmatched: %r, see stderr" % (line,))