PageRenderTime 50ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/_pytest/pytester.py

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