capture.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850
  1. # -*- coding: utf-8 -*-
  2. """
  3. per-test stdout/stderr capturing mechanism.
  4. """
  5. from __future__ import absolute_import
  6. from __future__ import division
  7. from __future__ import print_function
  8. import collections
  9. import contextlib
  10. import io
  11. import os
  12. import sys
  13. from io import UnsupportedOperation
  14. from tempfile import TemporaryFile
  15. import six
  16. import pytest
  17. from _pytest.compat import _PY3
  18. from _pytest.compat import CaptureIO
  19. patchsysdict = {0: "stdin", 1: "stdout", 2: "stderr"}
  20. def pytest_addoption(parser):
  21. group = parser.getgroup("general")
  22. group._addoption(
  23. "--capture",
  24. action="store",
  25. default="fd" if hasattr(os, "dup") else "sys",
  26. metavar="method",
  27. choices=["fd", "sys", "no"],
  28. help="per-test capturing method: one of fd|sys|no.",
  29. )
  30. group._addoption(
  31. "-s",
  32. action="store_const",
  33. const="no",
  34. dest="capture",
  35. help="shortcut for --capture=no.",
  36. )
  37. @pytest.hookimpl(hookwrapper=True)
  38. def pytest_load_initial_conftests(early_config, parser, args):
  39. ns = early_config.known_args_namespace
  40. if ns.capture == "fd":
  41. _py36_windowsconsoleio_workaround(sys.stdout)
  42. _colorama_workaround()
  43. _readline_workaround()
  44. pluginmanager = early_config.pluginmanager
  45. capman = CaptureManager(ns.capture)
  46. pluginmanager.register(capman, "capturemanager")
  47. # make sure that capturemanager is properly reset at final shutdown
  48. early_config.add_cleanup(capman.stop_global_capturing)
  49. # finally trigger conftest loading but while capturing (issue93)
  50. capman.start_global_capturing()
  51. outcome = yield
  52. capman.suspend_global_capture()
  53. if outcome.excinfo is not None:
  54. out, err = capman.read_global_capture()
  55. sys.stdout.write(out)
  56. sys.stderr.write(err)
  57. class CaptureManager(object):
  58. """
  59. Capture plugin, manages that the appropriate capture method is enabled/disabled during collection and each
  60. test phase (setup, call, teardown). After each of those points, the captured output is obtained and
  61. attached to the collection/runtest report.
  62. There are two levels of capture:
  63. * global: which is enabled by default and can be suppressed by the ``-s`` option. This is always enabled/disabled
  64. during collection and each test phase.
  65. * fixture: when a test function or one of its fixture depend on the ``capsys`` or ``capfd`` fixtures. In this
  66. case special handling is needed to ensure the fixtures take precedence over the global capture.
  67. """
  68. def __init__(self, method):
  69. self._method = method
  70. self._global_capturing = None
  71. self._current_item = None
  72. def __repr__(self):
  73. return "<CaptureManager _method=%r _global_capturing=%r _current_item=%r>" % (
  74. self._method,
  75. self._global_capturing,
  76. self._current_item,
  77. )
  78. def _getcapture(self, method):
  79. if method == "fd":
  80. return MultiCapture(out=True, err=True, Capture=FDCapture)
  81. elif method == "sys":
  82. return MultiCapture(out=True, err=True, Capture=SysCapture)
  83. elif method == "no":
  84. return MultiCapture(out=False, err=False, in_=False)
  85. raise ValueError("unknown capturing method: %r" % method) # pragma: no cover
  86. def is_capturing(self):
  87. if self.is_globally_capturing():
  88. return "global"
  89. capture_fixture = getattr(self._current_item, "_capture_fixture", None)
  90. if capture_fixture is not None:
  91. return (
  92. "fixture %s" % self._current_item._capture_fixture.request.fixturename
  93. )
  94. return False
  95. # Global capturing control
  96. def is_globally_capturing(self):
  97. return self._method != "no"
  98. def start_global_capturing(self):
  99. assert self._global_capturing is None
  100. self._global_capturing = self._getcapture(self._method)
  101. self._global_capturing.start_capturing()
  102. def stop_global_capturing(self):
  103. if self._global_capturing is not None:
  104. self._global_capturing.pop_outerr_to_orig()
  105. self._global_capturing.stop_capturing()
  106. self._global_capturing = None
  107. def resume_global_capture(self):
  108. # During teardown of the python process, and on rare occasions, capture
  109. # attributes can be `None` while trying to resume global capture.
  110. if self._global_capturing is not None:
  111. self._global_capturing.resume_capturing()
  112. def suspend_global_capture(self, in_=False):
  113. cap = getattr(self, "_global_capturing", None)
  114. if cap is not None:
  115. cap.suspend_capturing(in_=in_)
  116. def suspend(self, in_=False):
  117. # Need to undo local capsys-et-al if it exists before disabling global capture.
  118. self.suspend_fixture(self._current_item)
  119. self.suspend_global_capture(in_)
  120. def resume(self):
  121. self.resume_global_capture()
  122. self.resume_fixture(self._current_item)
  123. def read_global_capture(self):
  124. return self._global_capturing.readouterr()
  125. # Fixture Control (it's just forwarding, think about removing this later)
  126. def activate_fixture(self, item):
  127. """If the current item is using ``capsys`` or ``capfd``, activate them so they take precedence over
  128. the global capture.
  129. """
  130. fixture = getattr(item, "_capture_fixture", None)
  131. if fixture is not None:
  132. fixture._start()
  133. def deactivate_fixture(self, item):
  134. """Deactivates the ``capsys`` or ``capfd`` fixture of this item, if any."""
  135. fixture = getattr(item, "_capture_fixture", None)
  136. if fixture is not None:
  137. fixture.close()
  138. def suspend_fixture(self, item):
  139. fixture = getattr(item, "_capture_fixture", None)
  140. if fixture is not None:
  141. fixture._suspend()
  142. def resume_fixture(self, item):
  143. fixture = getattr(item, "_capture_fixture", None)
  144. if fixture is not None:
  145. fixture._resume()
  146. # Helper context managers
  147. @contextlib.contextmanager
  148. def global_and_fixture_disabled(self):
  149. """Context manager to temporarily disable global and current fixture capturing."""
  150. self.suspend()
  151. try:
  152. yield
  153. finally:
  154. self.resume()
  155. @contextlib.contextmanager
  156. def item_capture(self, when, item):
  157. self.resume_global_capture()
  158. self.activate_fixture(item)
  159. try:
  160. yield
  161. finally:
  162. self.deactivate_fixture(item)
  163. self.suspend_global_capture(in_=False)
  164. out, err = self.read_global_capture()
  165. item.add_report_section(when, "stdout", out)
  166. item.add_report_section(when, "stderr", err)
  167. # Hooks
  168. @pytest.hookimpl(hookwrapper=True)
  169. def pytest_make_collect_report(self, collector):
  170. if isinstance(collector, pytest.File):
  171. self.resume_global_capture()
  172. outcome = yield
  173. self.suspend_global_capture()
  174. out, err = self.read_global_capture()
  175. rep = outcome.get_result()
  176. if out:
  177. rep.sections.append(("Captured stdout", out))
  178. if err:
  179. rep.sections.append(("Captured stderr", err))
  180. else:
  181. yield
  182. @pytest.hookimpl(hookwrapper=True)
  183. def pytest_runtest_protocol(self, item):
  184. self._current_item = item
  185. yield
  186. self._current_item = None
  187. @pytest.hookimpl(hookwrapper=True)
  188. def pytest_runtest_setup(self, item):
  189. with self.item_capture("setup", item):
  190. yield
  191. @pytest.hookimpl(hookwrapper=True)
  192. def pytest_runtest_call(self, item):
  193. with self.item_capture("call", item):
  194. yield
  195. @pytest.hookimpl(hookwrapper=True)
  196. def pytest_runtest_teardown(self, item):
  197. with self.item_capture("teardown", item):
  198. yield
  199. @pytest.hookimpl(tryfirst=True)
  200. def pytest_keyboard_interrupt(self, excinfo):
  201. self.stop_global_capturing()
  202. @pytest.hookimpl(tryfirst=True)
  203. def pytest_internalerror(self, excinfo):
  204. self.stop_global_capturing()
  205. capture_fixtures = {"capfd", "capfdbinary", "capsys", "capsysbinary"}
  206. def _ensure_only_one_capture_fixture(request, name):
  207. fixtures = set(request.fixturenames) & capture_fixtures - {name}
  208. if fixtures:
  209. fixtures = sorted(fixtures)
  210. fixtures = fixtures[0] if len(fixtures) == 1 else fixtures
  211. raise request.raiseerror(
  212. "cannot use {} and {} at the same time".format(fixtures, name)
  213. )
  214. @pytest.fixture
  215. def capsys(request):
  216. """Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``.
  217. The captured output is made available via ``capsys.readouterr()`` method
  218. calls, which return a ``(out, err)`` namedtuple.
  219. ``out`` and ``err`` will be ``text`` objects.
  220. """
  221. _ensure_only_one_capture_fixture(request, "capsys")
  222. with _install_capture_fixture_on_item(request, SysCapture) as fixture:
  223. yield fixture
  224. @pytest.fixture
  225. def capsysbinary(request):
  226. """Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``.
  227. The captured output is made available via ``capsysbinary.readouterr()``
  228. method calls, which return a ``(out, err)`` namedtuple.
  229. ``out`` and ``err`` will be ``bytes`` objects.
  230. """
  231. _ensure_only_one_capture_fixture(request, "capsysbinary")
  232. # Currently, the implementation uses the python3 specific `.buffer`
  233. # property of CaptureIO.
  234. if sys.version_info < (3,):
  235. raise request.raiseerror("capsysbinary is only supported on Python 3")
  236. with _install_capture_fixture_on_item(request, SysCaptureBinary) as fixture:
  237. yield fixture
  238. @pytest.fixture
  239. def capfd(request):
  240. """Enable text capturing of writes to file descriptors ``1`` and ``2``.
  241. The captured output is made available via ``capfd.readouterr()`` method
  242. calls, which return a ``(out, err)`` namedtuple.
  243. ``out`` and ``err`` will be ``text`` objects.
  244. """
  245. _ensure_only_one_capture_fixture(request, "capfd")
  246. if not hasattr(os, "dup"):
  247. pytest.skip(
  248. "capfd fixture needs os.dup function which is not available in this system"
  249. )
  250. with _install_capture_fixture_on_item(request, FDCapture) as fixture:
  251. yield fixture
  252. @pytest.fixture
  253. def capfdbinary(request):
  254. """Enable bytes capturing of writes to file descriptors ``1`` and ``2``.
  255. The captured output is made available via ``capfd.readouterr()`` method
  256. calls, which return a ``(out, err)`` namedtuple.
  257. ``out`` and ``err`` will be ``byte`` objects.
  258. """
  259. _ensure_only_one_capture_fixture(request, "capfdbinary")
  260. if not hasattr(os, "dup"):
  261. pytest.skip(
  262. "capfdbinary fixture needs os.dup function which is not available in this system"
  263. )
  264. with _install_capture_fixture_on_item(request, FDCaptureBinary) as fixture:
  265. yield fixture
  266. @contextlib.contextmanager
  267. def _install_capture_fixture_on_item(request, capture_class):
  268. """
  269. Context manager which creates a ``CaptureFixture`` instance and "installs" it on
  270. the item/node of the given request. Used by ``capsys`` and ``capfd``.
  271. The CaptureFixture is added as attribute of the item because it needs to accessed
  272. by ``CaptureManager`` during its ``pytest_runtest_*`` hooks.
  273. """
  274. request.node._capture_fixture = fixture = CaptureFixture(capture_class, request)
  275. capmanager = request.config.pluginmanager.getplugin("capturemanager")
  276. # Need to active this fixture right away in case it is being used by another fixture (setup phase).
  277. # If this fixture is being used only by a test function (call phase), then we wouldn't need this
  278. # activation, but it doesn't hurt.
  279. capmanager.activate_fixture(request.node)
  280. yield fixture
  281. fixture.close()
  282. del request.node._capture_fixture
  283. class CaptureFixture(object):
  284. """
  285. Object returned by :py:func:`capsys`, :py:func:`capsysbinary`, :py:func:`capfd` and :py:func:`capfdbinary`
  286. fixtures.
  287. """
  288. def __init__(self, captureclass, request):
  289. self.captureclass = captureclass
  290. self.request = request
  291. self._capture = None
  292. self._captured_out = self.captureclass.EMPTY_BUFFER
  293. self._captured_err = self.captureclass.EMPTY_BUFFER
  294. def _start(self):
  295. if self._capture is None:
  296. self._capture = MultiCapture(
  297. out=True, err=True, in_=False, Capture=self.captureclass
  298. )
  299. self._capture.start_capturing()
  300. def close(self):
  301. if self._capture is not None:
  302. out, err = self._capture.pop_outerr_to_orig()
  303. self._captured_out += out
  304. self._captured_err += err
  305. self._capture.stop_capturing()
  306. self._capture = None
  307. def readouterr(self):
  308. """Read and return the captured output so far, resetting the internal buffer.
  309. :return: captured content as a namedtuple with ``out`` and ``err`` string attributes
  310. """
  311. captured_out, captured_err = self._captured_out, self._captured_err
  312. if self._capture is not None:
  313. out, err = self._capture.readouterr()
  314. captured_out += out
  315. captured_err += err
  316. self._captured_out = self.captureclass.EMPTY_BUFFER
  317. self._captured_err = self.captureclass.EMPTY_BUFFER
  318. return CaptureResult(captured_out, captured_err)
  319. def _suspend(self):
  320. """Suspends this fixture's own capturing temporarily."""
  321. if self._capture is not None:
  322. self._capture.suspend_capturing()
  323. def _resume(self):
  324. """Resumes this fixture's own capturing temporarily."""
  325. if self._capture is not None:
  326. self._capture.resume_capturing()
  327. @contextlib.contextmanager
  328. def disabled(self):
  329. """Temporarily disables capture while inside the 'with' block."""
  330. capmanager = self.request.config.pluginmanager.getplugin("capturemanager")
  331. with capmanager.global_and_fixture_disabled():
  332. yield
  333. def safe_text_dupfile(f, mode, default_encoding="UTF8"):
  334. """ return an open text file object that's a duplicate of f on the
  335. FD-level if possible.
  336. """
  337. encoding = getattr(f, "encoding", None)
  338. try:
  339. fd = f.fileno()
  340. except Exception:
  341. if "b" not in getattr(f, "mode", "") and hasattr(f, "encoding"):
  342. # we seem to have a text stream, let's just use it
  343. return f
  344. else:
  345. newfd = os.dup(fd)
  346. if "b" not in mode:
  347. mode += "b"
  348. f = os.fdopen(newfd, mode, 0) # no buffering
  349. return EncodedFile(f, encoding or default_encoding)
  350. class EncodedFile(object):
  351. errors = "strict" # possibly needed by py3 code (issue555)
  352. def __init__(self, buffer, encoding):
  353. self.buffer = buffer
  354. self.encoding = encoding
  355. def write(self, obj):
  356. if isinstance(obj, six.text_type):
  357. obj = obj.encode(self.encoding, "replace")
  358. elif _PY3:
  359. raise TypeError(
  360. "write() argument must be str, not {}".format(type(obj).__name__)
  361. )
  362. self.buffer.write(obj)
  363. def writelines(self, linelist):
  364. data = "".join(linelist)
  365. self.write(data)
  366. @property
  367. def name(self):
  368. """Ensure that file.name is a string."""
  369. return repr(self.buffer)
  370. @property
  371. def mode(self):
  372. return self.buffer.mode.replace("b", "")
  373. def __getattr__(self, name):
  374. return getattr(object.__getattribute__(self, "buffer"), name)
  375. CaptureResult = collections.namedtuple("CaptureResult", ["out", "err"])
  376. class MultiCapture(object):
  377. out = err = in_ = None
  378. _state = None
  379. def __init__(self, out=True, err=True, in_=True, Capture=None):
  380. if in_:
  381. self.in_ = Capture(0)
  382. if out:
  383. self.out = Capture(1)
  384. if err:
  385. self.err = Capture(2)
  386. def __repr__(self):
  387. return "<MultiCapture out=%r err=%r in_=%r _state=%r _in_suspended=%r>" % (
  388. self.out,
  389. self.err,
  390. self.in_,
  391. self._state,
  392. getattr(self, "_in_suspended", "<UNSET>"),
  393. )
  394. def start_capturing(self):
  395. self._state = "started"
  396. if self.in_:
  397. self.in_.start()
  398. if self.out:
  399. self.out.start()
  400. if self.err:
  401. self.err.start()
  402. def pop_outerr_to_orig(self):
  403. """ pop current snapshot out/err capture and flush to orig streams. """
  404. out, err = self.readouterr()
  405. if out:
  406. self.out.writeorg(out)
  407. if err:
  408. self.err.writeorg(err)
  409. return out, err
  410. def suspend_capturing(self, in_=False):
  411. self._state = "suspended"
  412. if self.out:
  413. self.out.suspend()
  414. if self.err:
  415. self.err.suspend()
  416. if in_ and self.in_:
  417. self.in_.suspend()
  418. self._in_suspended = True
  419. def resume_capturing(self):
  420. self._state = "resumed"
  421. if self.out:
  422. self.out.resume()
  423. if self.err:
  424. self.err.resume()
  425. if hasattr(self, "_in_suspended"):
  426. self.in_.resume()
  427. del self._in_suspended
  428. def stop_capturing(self):
  429. """ stop capturing and reset capturing streams """
  430. if self._state == "stopped":
  431. raise ValueError("was already stopped")
  432. self._state = "stopped"
  433. if self.out:
  434. self.out.done()
  435. if self.err:
  436. self.err.done()
  437. if self.in_:
  438. self.in_.done()
  439. def readouterr(self):
  440. """ return snapshot unicode value of stdout/stderr capturings. """
  441. return CaptureResult(
  442. self.out.snap() if self.out is not None else "",
  443. self.err.snap() if self.err is not None else "",
  444. )
  445. class NoCapture(object):
  446. EMPTY_BUFFER = None
  447. __init__ = start = done = suspend = resume = lambda *args: None
  448. class FDCaptureBinary(object):
  449. """Capture IO to/from a given os-level filedescriptor.
  450. snap() produces `bytes`
  451. """
  452. EMPTY_BUFFER = b""
  453. _state = None
  454. def __init__(self, targetfd, tmpfile=None):
  455. self.targetfd = targetfd
  456. try:
  457. self.targetfd_save = os.dup(self.targetfd)
  458. except OSError:
  459. self.start = lambda: None
  460. self.done = lambda: None
  461. else:
  462. if targetfd == 0:
  463. assert not tmpfile, "cannot set tmpfile with stdin"
  464. tmpfile = open(os.devnull, "r")
  465. self.syscapture = SysCapture(targetfd)
  466. else:
  467. if tmpfile is None:
  468. f = TemporaryFile()
  469. with f:
  470. tmpfile = safe_text_dupfile(f, mode="wb+")
  471. if targetfd in patchsysdict:
  472. self.syscapture = SysCapture(targetfd, tmpfile)
  473. else:
  474. self.syscapture = NoCapture()
  475. self.tmpfile = tmpfile
  476. self.tmpfile_fd = tmpfile.fileno()
  477. def __repr__(self):
  478. return "<FDCapture %s oldfd=%s _state=%r>" % (
  479. self.targetfd,
  480. getattr(self, "targetfd_save", None),
  481. self._state,
  482. )
  483. def start(self):
  484. """ Start capturing on targetfd using memorized tmpfile. """
  485. try:
  486. os.fstat(self.targetfd_save)
  487. except (AttributeError, OSError):
  488. raise ValueError("saved filedescriptor not valid anymore")
  489. os.dup2(self.tmpfile_fd, self.targetfd)
  490. self.syscapture.start()
  491. self._state = "started"
  492. def snap(self):
  493. self.tmpfile.seek(0)
  494. res = self.tmpfile.read()
  495. self.tmpfile.seek(0)
  496. self.tmpfile.truncate()
  497. return res
  498. def done(self):
  499. """ stop capturing, restore streams, return original capture file,
  500. seeked to position zero. """
  501. targetfd_save = self.__dict__.pop("targetfd_save")
  502. os.dup2(targetfd_save, self.targetfd)
  503. os.close(targetfd_save)
  504. self.syscapture.done()
  505. _attempt_to_close_capture_file(self.tmpfile)
  506. self._state = "done"
  507. def suspend(self):
  508. self.syscapture.suspend()
  509. os.dup2(self.targetfd_save, self.targetfd)
  510. self._state = "suspended"
  511. def resume(self):
  512. self.syscapture.resume()
  513. os.dup2(self.tmpfile_fd, self.targetfd)
  514. self._state = "resumed"
  515. def writeorg(self, data):
  516. """ write to original file descriptor. """
  517. if isinstance(data, six.text_type):
  518. data = data.encode("utf8") # XXX use encoding of original stream
  519. os.write(self.targetfd_save, data)
  520. class FDCapture(FDCaptureBinary):
  521. """Capture IO to/from a given os-level filedescriptor.
  522. snap() produces text
  523. """
  524. EMPTY_BUFFER = str()
  525. def snap(self):
  526. res = super(FDCapture, self).snap()
  527. enc = getattr(self.tmpfile, "encoding", None)
  528. if enc and isinstance(res, bytes):
  529. res = six.text_type(res, enc, "replace")
  530. return res
  531. class SysCapture(object):
  532. EMPTY_BUFFER = str()
  533. _state = None
  534. def __init__(self, fd, tmpfile=None):
  535. name = patchsysdict[fd]
  536. self._old = getattr(sys, name)
  537. self.name = name
  538. if tmpfile is None:
  539. if name == "stdin":
  540. tmpfile = DontReadFromInput()
  541. else:
  542. tmpfile = CaptureIO()
  543. self.tmpfile = tmpfile
  544. def __repr__(self):
  545. return "<SysCapture %s _old=%r, tmpfile=%r _state=%r>" % (
  546. self.name,
  547. self._old,
  548. self.tmpfile,
  549. self._state,
  550. )
  551. def start(self):
  552. setattr(sys, self.name, self.tmpfile)
  553. self._state = "started"
  554. def snap(self):
  555. res = self.tmpfile.getvalue()
  556. self.tmpfile.seek(0)
  557. self.tmpfile.truncate()
  558. return res
  559. def done(self):
  560. setattr(sys, self.name, self._old)
  561. del self._old
  562. _attempt_to_close_capture_file(self.tmpfile)
  563. self._state = "done"
  564. def suspend(self):
  565. setattr(sys, self.name, self._old)
  566. self._state = "suspended"
  567. def resume(self):
  568. setattr(sys, self.name, self.tmpfile)
  569. self._state = "resumed"
  570. def writeorg(self, data):
  571. self._old.write(data)
  572. self._old.flush()
  573. class SysCaptureBinary(SysCapture):
  574. EMPTY_BUFFER = b""
  575. def snap(self):
  576. res = self.tmpfile.buffer.getvalue()
  577. self.tmpfile.seek(0)
  578. self.tmpfile.truncate()
  579. return res
  580. class DontReadFromInput(six.Iterator):
  581. """Temporary stub class. Ideally when stdin is accessed, the
  582. capturing should be turned off, with possibly all data captured
  583. so far sent to the screen. This should be configurable, though,
  584. because in automated test runs it is better to crash than
  585. hang indefinitely.
  586. """
  587. encoding = None
  588. def read(self, *args):
  589. raise IOError("reading from stdin while output is captured")
  590. readline = read
  591. readlines = read
  592. __next__ = read
  593. def __iter__(self):
  594. return self
  595. def fileno(self):
  596. raise UnsupportedOperation("redirected stdin is pseudofile, has no fileno()")
  597. def isatty(self):
  598. return False
  599. def close(self):
  600. pass
  601. @property
  602. def buffer(self):
  603. if sys.version_info >= (3, 0):
  604. return self
  605. else:
  606. raise AttributeError("redirected stdin has no attribute buffer")
  607. def _colorama_workaround():
  608. """
  609. Ensure colorama is imported so that it attaches to the correct stdio
  610. handles on Windows.
  611. colorama uses the terminal on import time. So if something does the
  612. first import of colorama while I/O capture is active, colorama will
  613. fail in various ways.
  614. """
  615. if sys.platform.startswith("win32"):
  616. try:
  617. import colorama # noqa: F401
  618. except ImportError:
  619. pass
  620. def _readline_workaround():
  621. """
  622. Ensure readline is imported so that it attaches to the correct stdio
  623. handles on Windows.
  624. Pdb uses readline support where available--when not running from the Python
  625. prompt, the readline module is not imported until running the pdb REPL. If
  626. running pytest with the --pdb option this means the readline module is not
  627. imported until after I/O capture has been started.
  628. This is a problem for pyreadline, which is often used to implement readline
  629. support on Windows, as it does not attach to the correct handles for stdout
  630. and/or stdin if they have been redirected by the FDCapture mechanism. This
  631. workaround ensures that readline is imported before I/O capture is setup so
  632. that it can attach to the actual stdin/out for the console.
  633. See https://github.com/pytest-dev/pytest/pull/1281
  634. """
  635. if sys.platform.startswith("win32"):
  636. try:
  637. import readline # noqa: F401
  638. except ImportError:
  639. pass
  640. def _py36_windowsconsoleio_workaround(stream):
  641. """
  642. Python 3.6 implemented unicode console handling for Windows. This works
  643. by reading/writing to the raw console handle using
  644. ``{Read,Write}ConsoleW``.
  645. The problem is that we are going to ``dup2`` over the stdio file
  646. descriptors when doing ``FDCapture`` and this will ``CloseHandle`` the
  647. handles used by Python to write to the console. Though there is still some
  648. weirdness and the console handle seems to only be closed randomly and not
  649. on the first call to ``CloseHandle``, or maybe it gets reopened with the
  650. same handle value when we suspend capturing.
  651. The workaround in this case will reopen stdio with a different fd which
  652. also means a different handle by replicating the logic in
  653. "Py_lifecycle.c:initstdio/create_stdio".
  654. :param stream: in practice ``sys.stdout`` or ``sys.stderr``, but given
  655. here as parameter for unittesting purposes.
  656. See https://github.com/pytest-dev/py/issues/103
  657. """
  658. if not sys.platform.startswith("win32") or sys.version_info[:2] < (3, 6):
  659. return
  660. # bail out if ``stream`` doesn't seem like a proper ``io`` stream (#2666)
  661. if not hasattr(stream, "buffer"):
  662. return
  663. buffered = hasattr(stream.buffer, "raw")
  664. raw_stdout = stream.buffer.raw if buffered else stream.buffer
  665. if not isinstance(raw_stdout, io._WindowsConsoleIO):
  666. return
  667. def _reopen_stdio(f, mode):
  668. if not buffered and mode[0] == "w":
  669. buffering = 0
  670. else:
  671. buffering = -1
  672. return io.TextIOWrapper(
  673. open(os.dup(f.fileno()), mode, buffering),
  674. f.encoding,
  675. f.errors,
  676. f.newlines,
  677. f.line_buffering,
  678. )
  679. sys.stdin = _reopen_stdio(sys.stdin, "rb")
  680. sys.stdout = _reopen_stdio(sys.stdout, "wb")
  681. sys.stderr = _reopen_stdio(sys.stderr, "wb")
  682. def _attempt_to_close_capture_file(f):
  683. """Suppress IOError when closing the temporary file used for capturing streams in py27 (#2370)"""
  684. if six.PY2:
  685. try:
  686. f.close()
  687. except IOError:
  688. pass
  689. else:
  690. f.close()