pytester.py 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413
  1. # -*- coding: utf-8 -*-
  2. """(disabled by default) support for testing pytest and pytest plugins."""
  3. from __future__ import absolute_import
  4. from __future__ import division
  5. from __future__ import print_function
  6. import codecs
  7. import gc
  8. import os
  9. import platform
  10. import re
  11. import subprocess
  12. import sys
  13. import time
  14. import traceback
  15. from fnmatch import fnmatch
  16. from weakref import WeakKeyDictionary
  17. import py
  18. import six
  19. import pytest
  20. from _pytest._code import Source
  21. from _pytest._io.saferepr import saferepr
  22. from _pytest.assertion.rewrite import AssertionRewritingHook
  23. from _pytest.capture import MultiCapture
  24. from _pytest.capture import SysCapture
  25. from _pytest.compat import safe_str
  26. from _pytest.compat import Sequence
  27. from _pytest.main import EXIT_INTERRUPTED
  28. from _pytest.main import EXIT_OK
  29. from _pytest.main import Session
  30. from _pytest.monkeypatch import MonkeyPatch
  31. from _pytest.pathlib import Path
  32. IGNORE_PAM = [ # filenames added when obtaining details about the current user
  33. u"/var/lib/sss/mc/passwd"
  34. ]
  35. def pytest_addoption(parser):
  36. parser.addoption(
  37. "--lsof",
  38. action="store_true",
  39. dest="lsof",
  40. default=False,
  41. help="run FD checks if lsof is available",
  42. )
  43. parser.addoption(
  44. "--runpytest",
  45. default="inprocess",
  46. dest="runpytest",
  47. choices=("inprocess", "subprocess"),
  48. help=(
  49. "run pytest sub runs in tests using an 'inprocess' "
  50. "or 'subprocess' (python -m main) method"
  51. ),
  52. )
  53. parser.addini(
  54. "pytester_example_dir", help="directory to take the pytester example files from"
  55. )
  56. def pytest_configure(config):
  57. if config.getvalue("lsof"):
  58. checker = LsofFdLeakChecker()
  59. if checker.matching_platform():
  60. config.pluginmanager.register(checker)
  61. config.addinivalue_line(
  62. "markers",
  63. "pytester_example_path(*path_segments): join the given path "
  64. "segments to `pytester_example_dir` for this test.",
  65. )
  66. def raise_on_kwargs(kwargs):
  67. __tracebackhide__ = True
  68. if kwargs: # pragma: no branch
  69. raise TypeError(
  70. "Unexpected keyword arguments: {}".format(", ".join(sorted(kwargs)))
  71. )
  72. class LsofFdLeakChecker(object):
  73. def get_open_files(self):
  74. out = self._exec_lsof()
  75. open_files = self._parse_lsof_output(out)
  76. return open_files
  77. def _exec_lsof(self):
  78. pid = os.getpid()
  79. # py3: use subprocess.DEVNULL directly.
  80. with open(os.devnull, "wb") as devnull:
  81. return subprocess.check_output(
  82. ("lsof", "-Ffn0", "-p", str(pid)), stderr=devnull
  83. ).decode()
  84. def _parse_lsof_output(self, out):
  85. def isopen(line):
  86. return line.startswith("f") and (
  87. "deleted" not in line
  88. and "mem" not in line
  89. and "txt" not in line
  90. and "cwd" not in line
  91. )
  92. open_files = []
  93. for line in out.split("\n"):
  94. if isopen(line):
  95. fields = line.split("\0")
  96. fd = fields[0][1:]
  97. filename = fields[1][1:]
  98. if filename in IGNORE_PAM:
  99. continue
  100. if filename.startswith("/"):
  101. open_files.append((fd, filename))
  102. return open_files
  103. def matching_platform(self):
  104. try:
  105. subprocess.check_output(("lsof", "-v"))
  106. except (OSError, subprocess.CalledProcessError):
  107. return False
  108. else:
  109. return True
  110. @pytest.hookimpl(hookwrapper=True, tryfirst=True)
  111. def pytest_runtest_protocol(self, item):
  112. lines1 = self.get_open_files()
  113. yield
  114. if hasattr(sys, "pypy_version_info"):
  115. gc.collect()
  116. lines2 = self.get_open_files()
  117. new_fds = {t[0] for t in lines2} - {t[0] for t in lines1}
  118. leaked_files = [t for t in lines2 if t[0] in new_fds]
  119. if leaked_files:
  120. error = []
  121. error.append("***** %s FD leakage detected" % len(leaked_files))
  122. error.extend([str(f) for f in leaked_files])
  123. error.append("*** Before:")
  124. error.extend([str(f) for f in lines1])
  125. error.append("*** After:")
  126. error.extend([str(f) for f in lines2])
  127. error.append(error[0])
  128. error.append("*** function %s:%s: %s " % item.location)
  129. error.append("See issue #2366")
  130. item.warn(pytest.PytestWarning("\n".join(error)))
  131. # used at least by pytest-xdist plugin
  132. @pytest.fixture
  133. def _pytest(request):
  134. """Return a helper which offers a gethookrecorder(hook) method which
  135. returns a HookRecorder instance which helps to make assertions about called
  136. hooks.
  137. """
  138. return PytestArg(request)
  139. class PytestArg(object):
  140. def __init__(self, request):
  141. self.request = request
  142. def gethookrecorder(self, hook):
  143. hookrecorder = HookRecorder(hook._pm)
  144. self.request.addfinalizer(hookrecorder.finish_recording)
  145. return hookrecorder
  146. def get_public_names(values):
  147. """Only return names from iterator values without a leading underscore."""
  148. return [x for x in values if x[0] != "_"]
  149. class ParsedCall(object):
  150. def __init__(self, name, kwargs):
  151. self.__dict__.update(kwargs)
  152. self._name = name
  153. def __repr__(self):
  154. d = self.__dict__.copy()
  155. del d["_name"]
  156. return "<ParsedCall %r(**%r)>" % (self._name, d)
  157. class HookRecorder(object):
  158. """Record all hooks called in a plugin manager.
  159. This wraps all the hook calls in the plugin manager, recording each call
  160. before propagating the normal calls.
  161. """
  162. def __init__(self, pluginmanager):
  163. self._pluginmanager = pluginmanager
  164. self.calls = []
  165. def before(hook_name, hook_impls, kwargs):
  166. self.calls.append(ParsedCall(hook_name, kwargs))
  167. def after(outcome, hook_name, hook_impls, kwargs):
  168. pass
  169. self._undo_wrapping = pluginmanager.add_hookcall_monitoring(before, after)
  170. def finish_recording(self):
  171. self._undo_wrapping()
  172. def getcalls(self, names):
  173. if isinstance(names, str):
  174. names = names.split()
  175. return [call for call in self.calls if call._name in names]
  176. def assert_contains(self, entries):
  177. __tracebackhide__ = True
  178. i = 0
  179. entries = list(entries)
  180. backlocals = sys._getframe(1).f_locals
  181. while entries:
  182. name, check = entries.pop(0)
  183. for ind, call in enumerate(self.calls[i:]):
  184. if call._name == name:
  185. print("NAMEMATCH", name, call)
  186. if eval(check, backlocals, call.__dict__):
  187. print("CHECKERMATCH", repr(check), "->", call)
  188. else:
  189. print("NOCHECKERMATCH", repr(check), "-", call)
  190. continue
  191. i += ind + 1
  192. break
  193. print("NONAMEMATCH", name, "with", call)
  194. else:
  195. pytest.fail("could not find %r check %r" % (name, check))
  196. def popcall(self, name):
  197. __tracebackhide__ = True
  198. for i, call in enumerate(self.calls):
  199. if call._name == name:
  200. del self.calls[i]
  201. return call
  202. lines = ["could not find call %r, in:" % (name,)]
  203. lines.extend([" %s" % x for x in self.calls])
  204. pytest.fail("\n".join(lines))
  205. def getcall(self, name):
  206. values = self.getcalls(name)
  207. assert len(values) == 1, (name, values)
  208. return values[0]
  209. # functionality for test reports
  210. def getreports(self, names="pytest_runtest_logreport pytest_collectreport"):
  211. return [x.report for x in self.getcalls(names)]
  212. def matchreport(
  213. self,
  214. inamepart="",
  215. names="pytest_runtest_logreport pytest_collectreport",
  216. when=None,
  217. ):
  218. """return a testreport whose dotted import path matches"""
  219. values = []
  220. for rep in self.getreports(names=names):
  221. if not when and rep.when != "call" and rep.passed:
  222. # setup/teardown passing reports - let's ignore those
  223. continue
  224. if when and rep.when != when:
  225. continue
  226. if not inamepart or inamepart in rep.nodeid.split("::"):
  227. values.append(rep)
  228. if not values:
  229. raise ValueError(
  230. "could not find test report matching %r: "
  231. "no test reports at all!" % (inamepart,)
  232. )
  233. if len(values) > 1:
  234. raise ValueError(
  235. "found 2 or more testreports matching %r: %s" % (inamepart, values)
  236. )
  237. return values[0]
  238. def getfailures(self, names="pytest_runtest_logreport pytest_collectreport"):
  239. return [rep for rep in self.getreports(names) if rep.failed]
  240. def getfailedcollections(self):
  241. return self.getfailures("pytest_collectreport")
  242. def listoutcomes(self):
  243. passed = []
  244. skipped = []
  245. failed = []
  246. for rep in self.getreports("pytest_collectreport pytest_runtest_logreport"):
  247. if rep.passed:
  248. if rep.when == "call":
  249. passed.append(rep)
  250. elif rep.skipped:
  251. skipped.append(rep)
  252. else:
  253. assert rep.failed, "Unexpected outcome: {!r}".format(rep)
  254. failed.append(rep)
  255. return passed, skipped, failed
  256. def countoutcomes(self):
  257. return [len(x) for x in self.listoutcomes()]
  258. def assertoutcome(self, passed=0, skipped=0, failed=0):
  259. realpassed, realskipped, realfailed = self.listoutcomes()
  260. assert passed == len(realpassed)
  261. assert skipped == len(realskipped)
  262. assert failed == len(realfailed)
  263. def clear(self):
  264. self.calls[:] = []
  265. @pytest.fixture
  266. def linecomp(request):
  267. return LineComp()
  268. @pytest.fixture(name="LineMatcher")
  269. def LineMatcher_fixture(request):
  270. return LineMatcher
  271. @pytest.fixture
  272. def testdir(request, tmpdir_factory):
  273. return Testdir(request, tmpdir_factory)
  274. @pytest.fixture
  275. def _sys_snapshot():
  276. snappaths = SysPathsSnapshot()
  277. snapmods = SysModulesSnapshot()
  278. yield
  279. snapmods.restore()
  280. snappaths.restore()
  281. @pytest.fixture
  282. def _config_for_test():
  283. from _pytest.config import get_config
  284. config = get_config()
  285. yield config
  286. config._ensure_unconfigure() # cleanup, e.g. capman closing tmpfiles.
  287. rex_outcome = re.compile(r"(\d+) ([\w-]+)")
  288. class RunResult(object):
  289. """The result of running a command.
  290. Attributes:
  291. :ret: the return value
  292. :outlines: list of lines captured from stdout
  293. :errlines: list of lines captures from stderr
  294. :stdout: :py:class:`LineMatcher` of stdout, use ``stdout.str()`` to
  295. reconstruct stdout or the commonly used ``stdout.fnmatch_lines()``
  296. method
  297. :stderr: :py:class:`LineMatcher` of stderr
  298. :duration: duration in seconds
  299. """
  300. def __init__(self, ret, outlines, errlines, duration):
  301. self.ret = ret
  302. self.outlines = outlines
  303. self.errlines = errlines
  304. self.stdout = LineMatcher(outlines)
  305. self.stderr = LineMatcher(errlines)
  306. self.duration = duration
  307. def __repr__(self):
  308. return (
  309. "<RunResult ret=%r len(stdout.lines)=%d len(stderr.lines)=%d duration=%.2fs>"
  310. % (self.ret, len(self.stdout.lines), len(self.stderr.lines), self.duration)
  311. )
  312. def parseoutcomes(self):
  313. """Return a dictionary of outcomestring->num from parsing the terminal
  314. output that the test process produced.
  315. """
  316. for line in reversed(self.outlines):
  317. if "seconds" in line:
  318. outcomes = rex_outcome.findall(line)
  319. if outcomes:
  320. d = {}
  321. for num, cat in outcomes:
  322. d[cat] = int(num)
  323. return d
  324. raise ValueError("Pytest terminal report not found")
  325. def assert_outcomes(
  326. self, passed=0, skipped=0, failed=0, error=0, xpassed=0, xfailed=0
  327. ):
  328. """Assert that the specified outcomes appear with the respective
  329. numbers (0 means it didn't occur) in the text output from a test run.
  330. """
  331. d = self.parseoutcomes()
  332. obtained = {
  333. "passed": d.get("passed", 0),
  334. "skipped": d.get("skipped", 0),
  335. "failed": d.get("failed", 0),
  336. "error": d.get("error", 0),
  337. "xpassed": d.get("xpassed", 0),
  338. "xfailed": d.get("xfailed", 0),
  339. }
  340. expected = {
  341. "passed": passed,
  342. "skipped": skipped,
  343. "failed": failed,
  344. "error": error,
  345. "xpassed": xpassed,
  346. "xfailed": xfailed,
  347. }
  348. assert obtained == expected
  349. class CwdSnapshot(object):
  350. def __init__(self):
  351. self.__saved = os.getcwd()
  352. def restore(self):
  353. os.chdir(self.__saved)
  354. class SysModulesSnapshot(object):
  355. def __init__(self, preserve=None):
  356. self.__preserve = preserve
  357. self.__saved = dict(sys.modules)
  358. def restore(self):
  359. if self.__preserve:
  360. self.__saved.update(
  361. (k, m) for k, m in sys.modules.items() if self.__preserve(k)
  362. )
  363. sys.modules.clear()
  364. sys.modules.update(self.__saved)
  365. class SysPathsSnapshot(object):
  366. def __init__(self):
  367. self.__saved = list(sys.path), list(sys.meta_path)
  368. def restore(self):
  369. sys.path[:], sys.meta_path[:] = self.__saved
  370. class Testdir(object):
  371. """Temporary test directory with tools to test/run pytest itself.
  372. This is based on the ``tmpdir`` fixture but provides a number of methods
  373. which aid with testing pytest itself. Unless :py:meth:`chdir` is used all
  374. methods will use :py:attr:`tmpdir` as their current working directory.
  375. Attributes:
  376. :tmpdir: The :py:class:`py.path.local` instance of the temporary directory.
  377. :plugins: A list of plugins to use with :py:meth:`parseconfig` and
  378. :py:meth:`runpytest`. Initially this is an empty list but plugins can
  379. be added to the list. The type of items to add to the list depends on
  380. the method using them so refer to them for details.
  381. """
  382. CLOSE_STDIN = object
  383. class TimeoutExpired(Exception):
  384. pass
  385. def __init__(self, request, tmpdir_factory):
  386. self.request = request
  387. self._mod_collections = WeakKeyDictionary()
  388. name = request.function.__name__
  389. self.tmpdir = tmpdir_factory.mktemp(name, numbered=True)
  390. self.test_tmproot = tmpdir_factory.mktemp("tmp-" + name, numbered=True)
  391. self.plugins = []
  392. self._cwd_snapshot = CwdSnapshot()
  393. self._sys_path_snapshot = SysPathsSnapshot()
  394. self._sys_modules_snapshot = self.__take_sys_modules_snapshot()
  395. self.chdir()
  396. self.request.addfinalizer(self.finalize)
  397. method = self.request.config.getoption("--runpytest")
  398. if method == "inprocess":
  399. self._runpytest_method = self.runpytest_inprocess
  400. elif method == "subprocess":
  401. self._runpytest_method = self.runpytest_subprocess
  402. mp = self.monkeypatch = MonkeyPatch()
  403. mp.setenv("PYTEST_DEBUG_TEMPROOT", str(self.test_tmproot))
  404. # Ensure no unexpected caching via tox.
  405. mp.delenv("TOX_ENV_DIR", raising=False)
  406. # Discard outer pytest options.
  407. mp.delenv("PYTEST_ADDOPTS", raising=False)
  408. # Environment (updates) for inner runs.
  409. tmphome = str(self.tmpdir)
  410. self._env_run_update = {"HOME": tmphome, "USERPROFILE": tmphome}
  411. def __repr__(self):
  412. return "<Testdir %r>" % (self.tmpdir,)
  413. def __str__(self):
  414. return str(self.tmpdir)
  415. def finalize(self):
  416. """Clean up global state artifacts.
  417. Some methods modify the global interpreter state and this tries to
  418. clean this up. It does not remove the temporary directory however so
  419. it can be looked at after the test run has finished.
  420. """
  421. self._sys_modules_snapshot.restore()
  422. self._sys_path_snapshot.restore()
  423. self._cwd_snapshot.restore()
  424. self.monkeypatch.undo()
  425. def __take_sys_modules_snapshot(self):
  426. # some zope modules used by twisted-related tests keep internal state
  427. # and can't be deleted; we had some trouble in the past with
  428. # `zope.interface` for example
  429. def preserve_module(name):
  430. return name.startswith("zope")
  431. return SysModulesSnapshot(preserve=preserve_module)
  432. def make_hook_recorder(self, pluginmanager):
  433. """Create a new :py:class:`HookRecorder` for a PluginManager."""
  434. pluginmanager.reprec = reprec = HookRecorder(pluginmanager)
  435. self.request.addfinalizer(reprec.finish_recording)
  436. return reprec
  437. def chdir(self):
  438. """Cd into the temporary directory.
  439. This is done automatically upon instantiation.
  440. """
  441. self.tmpdir.chdir()
  442. def _makefile(self, ext, args, kwargs, encoding="utf-8"):
  443. items = list(kwargs.items())
  444. def to_text(s):
  445. return s.decode(encoding) if isinstance(s, bytes) else six.text_type(s)
  446. if args:
  447. source = u"\n".join(to_text(x) for x in args)
  448. basename = self.request.function.__name__
  449. items.insert(0, (basename, source))
  450. ret = None
  451. for basename, value in items:
  452. p = self.tmpdir.join(basename).new(ext=ext)
  453. p.dirpath().ensure_dir()
  454. source = Source(value)
  455. source = u"\n".join(to_text(line) for line in source.lines)
  456. p.write(source.strip().encode(encoding), "wb")
  457. if ret is None:
  458. ret = p
  459. return ret
  460. def makefile(self, ext, *args, **kwargs):
  461. r"""Create new file(s) in the testdir.
  462. :param str ext: The extension the file(s) should use, including the dot, e.g. `.py`.
  463. :param list[str] args: All args will be treated as strings and joined using newlines.
  464. The result will be written as contents to the file. The name of the
  465. file will be based on the test function requesting this fixture.
  466. :param kwargs: Each keyword is the name of a file, while the value of it will
  467. be written as contents of the file.
  468. Examples:
  469. .. code-block:: python
  470. testdir.makefile(".txt", "line1", "line2")
  471. testdir.makefile(".ini", pytest="[pytest]\naddopts=-rs\n")
  472. """
  473. return self._makefile(ext, args, kwargs)
  474. def makeconftest(self, source):
  475. """Write a contest.py file with 'source' as contents."""
  476. return self.makepyfile(conftest=source)
  477. def makeini(self, source):
  478. """Write a tox.ini file with 'source' as contents."""
  479. return self.makefile(".ini", tox=source)
  480. def getinicfg(self, source):
  481. """Return the pytest section from the tox.ini config file."""
  482. p = self.makeini(source)
  483. return py.iniconfig.IniConfig(p)["pytest"]
  484. def makepyfile(self, *args, **kwargs):
  485. """Shortcut for .makefile() with a .py extension."""
  486. return self._makefile(".py", args, kwargs)
  487. def maketxtfile(self, *args, **kwargs):
  488. """Shortcut for .makefile() with a .txt extension."""
  489. return self._makefile(".txt", args, kwargs)
  490. def syspathinsert(self, path=None):
  491. """Prepend a directory to sys.path, defaults to :py:attr:`tmpdir`.
  492. This is undone automatically when this object dies at the end of each
  493. test.
  494. """
  495. if path is None:
  496. path = self.tmpdir
  497. self.monkeypatch.syspath_prepend(str(path))
  498. def mkdir(self, name):
  499. """Create a new (sub)directory."""
  500. return self.tmpdir.mkdir(name)
  501. def mkpydir(self, name):
  502. """Create a new python package.
  503. This creates a (sub)directory with an empty ``__init__.py`` file so it
  504. gets recognised as a python package.
  505. """
  506. p = self.mkdir(name)
  507. p.ensure("__init__.py")
  508. return p
  509. def copy_example(self, name=None):
  510. import warnings
  511. from _pytest.warning_types import PYTESTER_COPY_EXAMPLE
  512. warnings.warn(PYTESTER_COPY_EXAMPLE, stacklevel=2)
  513. example_dir = self.request.config.getini("pytester_example_dir")
  514. if example_dir is None:
  515. raise ValueError("pytester_example_dir is unset, can't copy examples")
  516. example_dir = self.request.config.rootdir.join(example_dir)
  517. for extra_element in self.request.node.iter_markers("pytester_example_path"):
  518. assert extra_element.args
  519. example_dir = example_dir.join(*extra_element.args)
  520. if name is None:
  521. func_name = self.request.function.__name__
  522. maybe_dir = example_dir / func_name
  523. maybe_file = example_dir / (func_name + ".py")
  524. if maybe_dir.isdir():
  525. example_path = maybe_dir
  526. elif maybe_file.isfile():
  527. example_path = maybe_file
  528. else:
  529. raise LookupError(
  530. "{} cant be found as module or package in {}".format(
  531. func_name, example_dir.bestrelpath(self.request.config.rootdir)
  532. )
  533. )
  534. else:
  535. example_path = example_dir.join(name)
  536. if example_path.isdir() and not example_path.join("__init__.py").isfile():
  537. example_path.copy(self.tmpdir)
  538. return self.tmpdir
  539. elif example_path.isfile():
  540. result = self.tmpdir.join(example_path.basename)
  541. example_path.copy(result)
  542. return result
  543. else:
  544. raise LookupError(
  545. 'example "{}" is not found as a file or directory'.format(example_path)
  546. )
  547. Session = Session
  548. def getnode(self, config, arg):
  549. """Return the collection node of a file.
  550. :param config: :py:class:`_pytest.config.Config` instance, see
  551. :py:meth:`parseconfig` and :py:meth:`parseconfigure` to create the
  552. configuration
  553. :param arg: a :py:class:`py.path.local` instance of the file
  554. """
  555. session = Session(config)
  556. assert "::" not in str(arg)
  557. p = py.path.local(arg)
  558. config.hook.pytest_sessionstart(session=session)
  559. res = session.perform_collect([str(p)], genitems=False)[0]
  560. config.hook.pytest_sessionfinish(session=session, exitstatus=EXIT_OK)
  561. return res
  562. def getpathnode(self, path):
  563. """Return the collection node of a file.
  564. This is like :py:meth:`getnode` but uses :py:meth:`parseconfigure` to
  565. create the (configured) pytest Config instance.
  566. :param path: a :py:class:`py.path.local` instance of the file
  567. """
  568. config = self.parseconfigure(path)
  569. session = Session(config)
  570. x = session.fspath.bestrelpath(path)
  571. config.hook.pytest_sessionstart(session=session)
  572. res = session.perform_collect([x], genitems=False)[0]
  573. config.hook.pytest_sessionfinish(session=session, exitstatus=EXIT_OK)
  574. return res
  575. def genitems(self, colitems):
  576. """Generate all test items from a collection node.
  577. This recurses into the collection node and returns a list of all the
  578. test items contained within.
  579. """
  580. session = colitems[0].session
  581. result = []
  582. for colitem in colitems:
  583. result.extend(session.genitems(colitem))
  584. return result
  585. def runitem(self, source):
  586. """Run the "test_func" Item.
  587. The calling test instance (class containing the test method) must
  588. provide a ``.getrunner()`` method which should return a runner which
  589. can run the test protocol for a single item, e.g.
  590. :py:func:`_pytest.runner.runtestprotocol`.
  591. """
  592. # used from runner functional tests
  593. item = self.getitem(source)
  594. # the test class where we are called from wants to provide the runner
  595. testclassinstance = self.request.instance
  596. runner = testclassinstance.getrunner()
  597. return runner(item)
  598. def inline_runsource(self, source, *cmdlineargs):
  599. """Run a test module in process using ``pytest.main()``.
  600. This run writes "source" into a temporary file and runs
  601. ``pytest.main()`` on it, returning a :py:class:`HookRecorder` instance
  602. for the result.
  603. :param source: the source code of the test module
  604. :param cmdlineargs: any extra command line arguments to use
  605. :return: :py:class:`HookRecorder` instance of the result
  606. """
  607. p = self.makepyfile(source)
  608. values = list(cmdlineargs) + [p]
  609. return self.inline_run(*values)
  610. def inline_genitems(self, *args):
  611. """Run ``pytest.main(['--collectonly'])`` in-process.
  612. Runs the :py:func:`pytest.main` function to run all of pytest inside
  613. the test process itself like :py:meth:`inline_run`, but returns a
  614. tuple of the collected items and a :py:class:`HookRecorder` instance.
  615. """
  616. rec = self.inline_run("--collect-only", *args)
  617. items = [x.item for x in rec.getcalls("pytest_itemcollected")]
  618. return items, rec
  619. def inline_run(self, *args, **kwargs):
  620. """Run ``pytest.main()`` in-process, returning a HookRecorder.
  621. Runs the :py:func:`pytest.main` function to run all of pytest inside
  622. the test process itself. This means it can return a
  623. :py:class:`HookRecorder` instance which gives more detailed results
  624. from that run than can be done by matching stdout/stderr from
  625. :py:meth:`runpytest`.
  626. :param args: command line arguments to pass to :py:func:`pytest.main`
  627. :param plugins: (keyword-only) extra plugin instances the
  628. ``pytest.main()`` instance should use
  629. :return: a :py:class:`HookRecorder` instance
  630. """
  631. plugins = kwargs.pop("plugins", [])
  632. no_reraise_ctrlc = kwargs.pop("no_reraise_ctrlc", None)
  633. raise_on_kwargs(kwargs)
  634. finalizers = []
  635. try:
  636. # Do not load user config (during runs only).
  637. mp_run = MonkeyPatch()
  638. for k, v in self._env_run_update.items():
  639. mp_run.setenv(k, v)
  640. finalizers.append(mp_run.undo)
  641. # When running pytest inline any plugins active in the main test
  642. # process are already imported. So this disables the warning which
  643. # will trigger to say they can no longer be rewritten, which is
  644. # fine as they have already been rewritten.
  645. orig_warn = AssertionRewritingHook._warn_already_imported
  646. def revert_warn_already_imported():
  647. AssertionRewritingHook._warn_already_imported = orig_warn
  648. finalizers.append(revert_warn_already_imported)
  649. AssertionRewritingHook._warn_already_imported = lambda *a: None
  650. # Any sys.module or sys.path changes done while running pytest
  651. # inline should be reverted after the test run completes to avoid
  652. # clashing with later inline tests run within the same pytest test,
  653. # e.g. just because they use matching test module names.
  654. finalizers.append(self.__take_sys_modules_snapshot().restore)
  655. finalizers.append(SysPathsSnapshot().restore)
  656. # Important note:
  657. # - our tests should not leave any other references/registrations
  658. # laying around other than possibly loaded test modules
  659. # referenced from sys.modules, as nothing will clean those up
  660. # automatically
  661. rec = []
  662. class Collect(object):
  663. def pytest_configure(x, config):
  664. rec.append(self.make_hook_recorder(config.pluginmanager))
  665. plugins.append(Collect())
  666. ret = pytest.main(list(args), plugins=plugins)
  667. if len(rec) == 1:
  668. reprec = rec.pop()
  669. else:
  670. class reprec(object):
  671. pass
  672. reprec.ret = ret
  673. # typically we reraise keyboard interrupts from the child run
  674. # because it's our user requesting interruption of the testing
  675. if ret == EXIT_INTERRUPTED and not no_reraise_ctrlc:
  676. calls = reprec.getcalls("pytest_keyboard_interrupt")
  677. if calls and calls[-1].excinfo.type == KeyboardInterrupt:
  678. raise KeyboardInterrupt()
  679. return reprec
  680. finally:
  681. for finalizer in finalizers:
  682. finalizer()
  683. def runpytest_inprocess(self, *args, **kwargs):
  684. """Return result of running pytest in-process, providing a similar
  685. interface to what self.runpytest() provides.
  686. """
  687. syspathinsert = kwargs.pop("syspathinsert", False)
  688. if syspathinsert:
  689. self.syspathinsert()
  690. now = time.time()
  691. capture = MultiCapture(Capture=SysCapture)
  692. capture.start_capturing()
  693. try:
  694. try:
  695. reprec = self.inline_run(*args, **kwargs)
  696. except SystemExit as e:
  697. class reprec(object):
  698. ret = e.args[0]
  699. except Exception:
  700. traceback.print_exc()
  701. class reprec(object):
  702. ret = 3
  703. finally:
  704. out, err = capture.readouterr()
  705. capture.stop_capturing()
  706. sys.stdout.write(out)
  707. sys.stderr.write(err)
  708. res = RunResult(reprec.ret, out.split("\n"), err.split("\n"), time.time() - now)
  709. res.reprec = reprec
  710. return res
  711. def runpytest(self, *args, **kwargs):
  712. """Run pytest inline or in a subprocess, depending on the command line
  713. option "--runpytest" and return a :py:class:`RunResult`.
  714. """
  715. args = self._ensure_basetemp(args)
  716. return self._runpytest_method(*args, **kwargs)
  717. def _ensure_basetemp(self, args):
  718. args = list(args)
  719. for x in args:
  720. if safe_str(x).startswith("--basetemp"):
  721. break
  722. else:
  723. args.append("--basetemp=%s" % self.tmpdir.dirpath("basetemp"))
  724. return args
  725. def parseconfig(self, *args):
  726. """Return a new pytest Config instance from given commandline args.
  727. This invokes the pytest bootstrapping code in _pytest.config to create
  728. a new :py:class:`_pytest.core.PluginManager` and call the
  729. pytest_cmdline_parse hook to create a new
  730. :py:class:`_pytest.config.Config` instance.
  731. If :py:attr:`plugins` has been populated they should be plugin modules
  732. to be registered with the PluginManager.
  733. """
  734. args = self._ensure_basetemp(args)
  735. import _pytest.config
  736. config = _pytest.config._prepareconfig(args, self.plugins)
  737. # we don't know what the test will do with this half-setup config
  738. # object and thus we make sure it gets unconfigured properly in any
  739. # case (otherwise capturing could still be active, for example)
  740. self.request.addfinalizer(config._ensure_unconfigure)
  741. return config
  742. def parseconfigure(self, *args):
  743. """Return a new pytest configured Config instance.
  744. This returns a new :py:class:`_pytest.config.Config` instance like
  745. :py:meth:`parseconfig`, but also calls the pytest_configure hook.
  746. """
  747. config = self.parseconfig(*args)
  748. config._do_configure()
  749. self.request.addfinalizer(config._ensure_unconfigure)
  750. return config
  751. def getitem(self, source, funcname="test_func"):
  752. """Return the test item for a test function.
  753. This writes the source to a python file and runs pytest's collection on
  754. the resulting module, returning the test item for the requested
  755. function name.
  756. :param source: the module source
  757. :param funcname: the name of the test function for which to return a
  758. test item
  759. """
  760. items = self.getitems(source)
  761. for item in items:
  762. if item.name == funcname:
  763. return item
  764. assert 0, "%r item not found in module:\n%s\nitems: %s" % (
  765. funcname,
  766. source,
  767. items,
  768. )
  769. def getitems(self, source):
  770. """Return all test items collected from the module.
  771. This writes the source to a python file and runs pytest's collection on
  772. the resulting module, returning all test items contained within.
  773. """
  774. modcol = self.getmodulecol(source)
  775. return self.genitems([modcol])
  776. def getmodulecol(self, source, configargs=(), withinit=False):
  777. """Return the module collection node for ``source``.
  778. This writes ``source`` to a file using :py:meth:`makepyfile` and then
  779. runs the pytest collection on it, returning the collection node for the
  780. test module.
  781. :param source: the source code of the module to collect
  782. :param configargs: any extra arguments to pass to
  783. :py:meth:`parseconfigure`
  784. :param withinit: whether to also write an ``__init__.py`` file to the
  785. same directory to ensure it is a package
  786. """
  787. if isinstance(source, Path):
  788. path = self.tmpdir.join(str(source))
  789. assert not withinit, "not supported for paths"
  790. else:
  791. kw = {self.request.function.__name__: Source(source).strip()}
  792. path = self.makepyfile(**kw)
  793. if withinit:
  794. self.makepyfile(__init__="#")
  795. self.config = config = self.parseconfigure(path, *configargs)
  796. return self.getnode(config, path)
  797. def collect_by_name(self, modcol, name):
  798. """Return the collection node for name from the module collection.
  799. This will search a module collection node for a collection node
  800. matching the given name.
  801. :param modcol: a module collection node; see :py:meth:`getmodulecol`
  802. :param name: the name of the node to return
  803. """
  804. if modcol not in self._mod_collections:
  805. self._mod_collections[modcol] = list(modcol.collect())
  806. for colitem in self._mod_collections[modcol]:
  807. if colitem.name == name:
  808. return colitem
  809. def popen(
  810. self,
  811. cmdargs,
  812. stdout=subprocess.PIPE,
  813. stderr=subprocess.PIPE,
  814. stdin=CLOSE_STDIN,
  815. **kw
  816. ):
  817. """Invoke subprocess.Popen.
  818. This calls subprocess.Popen making sure the current working directory
  819. is in the PYTHONPATH.
  820. You probably want to use :py:meth:`run` instead.
  821. """
  822. env = os.environ.copy()
  823. env["PYTHONPATH"] = os.pathsep.join(
  824. filter(None, [os.getcwd(), env.get("PYTHONPATH", "")])
  825. )
  826. env.update(self._env_run_update)
  827. kw["env"] = env
  828. if stdin is Testdir.CLOSE_STDIN:
  829. kw["stdin"] = subprocess.PIPE
  830. elif isinstance(stdin, bytes):
  831. kw["stdin"] = subprocess.PIPE
  832. else:
  833. kw["stdin"] = stdin
  834. popen = subprocess.Popen(cmdargs, stdout=stdout, stderr=stderr, **kw)
  835. if stdin is Testdir.CLOSE_STDIN:
  836. popen.stdin.close()
  837. elif isinstance(stdin, bytes):
  838. popen.stdin.write(stdin)
  839. return popen
  840. def run(self, *cmdargs, **kwargs):
  841. """Run a command with arguments.
  842. Run a process using subprocess.Popen saving the stdout and stderr.
  843. :param args: the sequence of arguments to pass to `subprocess.Popen()`
  844. :param timeout: the period in seconds after which to timeout and raise
  845. :py:class:`Testdir.TimeoutExpired`
  846. :param stdin: optional standard input. Bytes are being send, closing
  847. the pipe, otherwise it is passed through to ``popen``.
  848. Defaults to ``CLOSE_STDIN``, which translates to using a pipe
  849. (``subprocess.PIPE``) that gets closed.
  850. Returns a :py:class:`RunResult`.
  851. """
  852. __tracebackhide__ = True
  853. timeout = kwargs.pop("timeout", None)
  854. stdin = kwargs.pop("stdin", Testdir.CLOSE_STDIN)
  855. raise_on_kwargs(kwargs)
  856. cmdargs = [
  857. str(arg) if isinstance(arg, py.path.local) else arg for arg in cmdargs
  858. ]
  859. p1 = self.tmpdir.join("stdout")
  860. p2 = self.tmpdir.join("stderr")
  861. print("running:", *cmdargs)
  862. print(" in:", py.path.local())
  863. f1 = codecs.open(str(p1), "w", encoding="utf8")
  864. f2 = codecs.open(str(p2), "w", encoding="utf8")
  865. try:
  866. now = time.time()
  867. popen = self.popen(
  868. cmdargs,
  869. stdin=stdin,
  870. stdout=f1,
  871. stderr=f2,
  872. close_fds=(sys.platform != "win32"),
  873. )
  874. if isinstance(stdin, bytes):
  875. popen.stdin.close()
  876. def handle_timeout():
  877. __tracebackhide__ = True
  878. timeout_message = (
  879. "{seconds} second timeout expired running:"
  880. " {command}".format(seconds=timeout, command=cmdargs)
  881. )
  882. popen.kill()
  883. popen.wait()
  884. raise self.TimeoutExpired(timeout_message)
  885. if timeout is None:
  886. ret = popen.wait()
  887. elif not six.PY2:
  888. try:
  889. ret = popen.wait(timeout)
  890. except subprocess.TimeoutExpired:
  891. handle_timeout()
  892. else:
  893. end = time.time() + timeout
  894. resolution = min(0.1, timeout / 10)
  895. while True:
  896. ret = popen.poll()
  897. if ret is not None:
  898. break
  899. if time.time() > end:
  900. handle_timeout()
  901. time.sleep(resolution)
  902. finally:
  903. f1.close()
  904. f2.close()
  905. f1 = codecs.open(str(p1), "r", encoding="utf8")
  906. f2 = codecs.open(str(p2), "r", encoding="utf8")
  907. try:
  908. out = f1.read().splitlines()
  909. err = f2.read().splitlines()
  910. finally:
  911. f1.close()
  912. f2.close()
  913. self._dump_lines(out, sys.stdout)
  914. self._dump_lines(err, sys.stderr)
  915. return RunResult(ret, out, err, time.time() - now)
  916. def _dump_lines(self, lines, fp):
  917. try:
  918. for line in lines:
  919. print(line, file=fp)
  920. except UnicodeEncodeError:
  921. print("couldn't print to %s because of encoding" % (fp,))
  922. def _getpytestargs(self):
  923. return sys.executable, "-mpytest"
  924. def runpython(self, script):
  925. """Run a python script using sys.executable as interpreter.
  926. Returns a :py:class:`RunResult`.
  927. """
  928. return self.run(sys.executable, script)
  929. def runpython_c(self, command):
  930. """Run python -c "command", return a :py:class:`RunResult`."""
  931. return self.run(sys.executable, "-c", command)
  932. def runpytest_subprocess(self, *args, **kwargs):
  933. """Run pytest as a subprocess with given arguments.
  934. Any plugins added to the :py:attr:`plugins` list will be added using the
  935. ``-p`` command line option. Additionally ``--basetemp`` is used to put
  936. any temporary files and directories in a numbered directory prefixed
  937. with "runpytest-" to not conflict with the normal numbered pytest
  938. location for temporary files and directories.
  939. :param args: the sequence of arguments to pass to the pytest subprocess
  940. :param timeout: the period in seconds after which to timeout and raise
  941. :py:class:`Testdir.TimeoutExpired`
  942. Returns a :py:class:`RunResult`.
  943. """
  944. __tracebackhide__ = True
  945. timeout = kwargs.pop("timeout", None)
  946. raise_on_kwargs(kwargs)
  947. p = py.path.local.make_numbered_dir(
  948. prefix="runpytest-", keep=None, rootdir=self.tmpdir
  949. )
  950. args = ("--basetemp=%s" % p,) + args
  951. plugins = [x for x in self.plugins if isinstance(x, str)]
  952. if plugins:
  953. args = ("-p", plugins[0]) + args
  954. args = self._getpytestargs() + args
  955. return self.run(*args, timeout=timeout)
  956. def spawn_pytest(self, string, expect_timeout=10.0):
  957. """Run pytest using pexpect.
  958. This makes sure to use the right pytest and sets up the temporary
  959. directory locations.
  960. The pexpect child is returned.
  961. """
  962. basetemp = self.tmpdir.mkdir("temp-pexpect")
  963. invoke = " ".join(map(str, self._getpytestargs()))
  964. cmd = "%s --basetemp=%s %s" % (invoke, basetemp, string)
  965. return self.spawn(cmd, expect_timeout=expect_timeout)
  966. def spawn(self, cmd, expect_timeout=10.0):
  967. """Run a command using pexpect.
  968. The pexpect child is returned.
  969. """
  970. pexpect = pytest.importorskip("pexpect", "3.0")
  971. if hasattr(sys, "pypy_version_info") and "64" in platform.machine():
  972. pytest.skip("pypy-64 bit not supported")
  973. if sys.platform.startswith("freebsd"):
  974. pytest.xfail("pexpect does not work reliably on freebsd")
  975. logfile = self.tmpdir.join("spawn.out").open("wb")
  976. # Do not load user config.
  977. env = os.environ.copy()
  978. env.update(self._env_run_update)
  979. child = pexpect.spawn(cmd, logfile=logfile, env=env)
  980. self.request.addfinalizer(logfile.close)
  981. child.timeout = expect_timeout
  982. return child
  983. def getdecoded(out):
  984. try:
  985. return out.decode("utf-8")
  986. except UnicodeDecodeError:
  987. return "INTERNAL not-utf8-decodeable, truncated string:\n%s" % (saferepr(out),)
  988. class LineComp(object):
  989. def __init__(self):
  990. self.stringio = py.io.TextIO()
  991. def assert_contains_lines(self, lines2):
  992. """Assert that lines2 are contained (linearly) in lines1.
  993. Return a list of extralines found.
  994. """
  995. __tracebackhide__ = True
  996. val = self.stringio.getvalue()
  997. self.stringio.truncate(0)
  998. self.stringio.seek(0)
  999. lines1 = val.split("\n")
  1000. return LineMatcher(lines1).fnmatch_lines(lines2)
  1001. class LineMatcher(object):
  1002. """Flexible matching of text.
  1003. This is a convenience class to test large texts like the output of
  1004. commands.
  1005. The constructor takes a list of lines without their trailing newlines, i.e.
  1006. ``text.splitlines()``.
  1007. """
  1008. def __init__(self, lines):
  1009. self.lines = lines
  1010. self._log_output = []
  1011. def str(self):
  1012. """Return the entire original text."""
  1013. return "\n".join(self.lines)
  1014. def _getlines(self, lines2):
  1015. if isinstance(lines2, str):
  1016. lines2 = Source(lines2)
  1017. if isinstance(lines2, Source):
  1018. lines2 = lines2.strip().lines
  1019. return lines2
  1020. def fnmatch_lines_random(self, lines2):
  1021. """Check lines exist in the output using in any order.
  1022. Lines are checked using ``fnmatch.fnmatch``. The argument is a list of
  1023. lines which have to occur in the output, in any order.
  1024. """
  1025. self._match_lines_random(lines2, fnmatch)
  1026. def re_match_lines_random(self, lines2):
  1027. """Check lines exist in the output using ``re.match``, in any order.
  1028. The argument is a list of lines which have to occur in the output, in
  1029. any order.
  1030. """
  1031. self._match_lines_random(lines2, lambda name, pat: re.match(pat, name))
  1032. def _match_lines_random(self, lines2, match_func):
  1033. """Check lines exist in the output.
  1034. The argument is a list of lines which have to occur in the output, in
  1035. any order. Each line can contain glob whildcards.
  1036. """
  1037. lines2 = self._getlines(lines2)
  1038. for line in lines2:
  1039. for x in self.lines:
  1040. if line == x or match_func(x, line):
  1041. self._log("matched: ", repr(line))
  1042. break
  1043. else:
  1044. self._log("line %r not found in output" % line)
  1045. raise ValueError(self._log_text)
  1046. def get_lines_after(self, fnline):
  1047. """Return all lines following the given line in the text.
  1048. The given line can contain glob wildcards.
  1049. """
  1050. for i, line in enumerate(self.lines):
  1051. if fnline == line or fnmatch(line, fnline):
  1052. return self.lines[i + 1 :]
  1053. raise ValueError("line %r not found in output" % fnline)
  1054. def _log(self, *args):
  1055. self._log_output.append(" ".join(str(x) for x in args))
  1056. @property
  1057. def _log_text(self):
  1058. return "\n".join(self._log_output)
  1059. def fnmatch_lines(self, lines2):
  1060. """Search captured text for matching lines using ``fnmatch.fnmatch``.
  1061. The argument is a list of lines which have to match and can use glob
  1062. wildcards. If they do not match a pytest.fail() is called. The
  1063. matches and non-matches are also printed on stdout.
  1064. """
  1065. __tracebackhide__ = True
  1066. self._match_lines(lines2, fnmatch, "fnmatch")
  1067. def re_match_lines(self, lines2):
  1068. """Search captured text for matching lines using ``re.match``.
  1069. The argument is a list of lines which have to match using ``re.match``.
  1070. If they do not match a pytest.fail() is called.
  1071. The matches and non-matches are also printed on stdout.
  1072. """
  1073. __tracebackhide__ = True
  1074. self._match_lines(lines2, lambda name, pat: re.match(pat, name), "re.match")
  1075. def _match_lines(self, lines2, match_func, match_nickname):
  1076. """Underlying implementation of ``fnmatch_lines`` and ``re_match_lines``.
  1077. :param list[str] lines2: list of string patterns to match. The actual
  1078. format depends on ``match_func``
  1079. :param match_func: a callable ``match_func(line, pattern)`` where line
  1080. is the captured line from stdout/stderr and pattern is the matching
  1081. pattern
  1082. :param str match_nickname: the nickname for the match function that
  1083. will be logged to stdout when a match occurs
  1084. """
  1085. assert isinstance(lines2, Sequence)
  1086. lines2 = self._getlines(lines2)
  1087. lines1 = self.lines[:]
  1088. nextline = None
  1089. extralines = []
  1090. __tracebackhide__ = True
  1091. for line in lines2:
  1092. nomatchprinted = False
  1093. while lines1:
  1094. nextline = lines1.pop(0)
  1095. if line == nextline:
  1096. self._log("exact match:", repr(line))
  1097. break
  1098. elif match_func(nextline, line):
  1099. self._log("%s:" % match_nickname, repr(line))
  1100. self._log(" with:", repr(nextline))
  1101. break
  1102. else:
  1103. if not nomatchprinted:
  1104. self._log("nomatch:", repr(line))
  1105. nomatchprinted = True
  1106. self._log(" and:", repr(nextline))
  1107. extralines.append(nextline)
  1108. else:
  1109. self._log("remains unmatched: %r" % (line,))
  1110. pytest.fail(self._log_text)