import inspect import textwrap import pytest from _pytest.compat import MODULE_NOT_FOUND_ERROR from _pytest.doctest import _get_checker from _pytest.doctest import _is_mocked from _pytest.doctest import _patch_unwrap_mock_aware from _pytest.doctest import DoctestItem from _pytest.doctest import DoctestModule from _pytest.doctest import DoctestTextfile class TestDoctests: def test_collect_testtextfile(self, testdir): w = testdir.maketxtfile(whatever="") checkfile = testdir.maketxtfile( test_something=""" alskdjalsdk >>> i = 5 >>> i-1 4 """ ) for x in (testdir.tmpdir, checkfile): # print "checking that %s returns custom items" % (x,) items, reprec = testdir.inline_genitems(x) assert len(items) == 1 assert isinstance(items[0], DoctestItem) assert isinstance(items[0].parent, DoctestTextfile) # Empty file has no items. items, reprec = testdir.inline_genitems(w) assert len(items) == 0 def test_collect_module_empty(self, testdir): path = testdir.makepyfile(whatever="#") for p in (path, testdir.tmpdir): items, reprec = testdir.inline_genitems(p, "--doctest-modules") assert len(items) == 0 def test_collect_module_single_modulelevel_doctest(self, testdir): path = testdir.makepyfile(whatever='""">>> pass"""') for p in (path, testdir.tmpdir): items, reprec = testdir.inline_genitems(p, "--doctest-modules") assert len(items) == 1 assert isinstance(items[0], DoctestItem) assert isinstance(items[0].parent, DoctestModule) def test_collect_module_two_doctest_one_modulelevel(self, testdir): path = testdir.makepyfile( whatever=""" '>>> x = None' def my_func(): ">>> magic = 42 " """ ) for p in (path, testdir.tmpdir): items, reprec = testdir.inline_genitems(p, "--doctest-modules") assert len(items) == 2 assert isinstance(items[0], DoctestItem) assert isinstance(items[1], DoctestItem) assert isinstance(items[0].parent, DoctestModule) assert items[0].parent is items[1].parent def test_collect_module_two_doctest_no_modulelevel(self, testdir): path = testdir.makepyfile( whatever=""" '# Empty' def my_func(): ">>> magic = 42 " def unuseful(): ''' # This is a function # >>> # it doesn't have any doctest ''' def another(): ''' # This is another function >>> import os # this one does have a doctest ''' """ ) for p in (path, testdir.tmpdir): items, reprec = testdir.inline_genitems(p, "--doctest-modules") assert len(items) == 2 assert isinstance(items[0], DoctestItem) assert isinstance(items[1], DoctestItem) assert isinstance(items[0].parent, DoctestModule) assert items[0].parent is items[1].parent def test_simple_doctestfile(self, testdir): p = testdir.maketxtfile( test_doc=""" >>> x = 1 >>> x == 1 False """ ) reprec = testdir.inline_run(p) reprec.assertoutcome(failed=1) def test_new_pattern(self, testdir): p = testdir.maketxtfile( xdoc=""" >>> x = 1 >>> x == 1 False """ ) reprec = testdir.inline_run(p, "--doctest-glob=x*.txt") reprec.assertoutcome(failed=1) def test_multiple_patterns(self, testdir): """Test support for multiple --doctest-glob arguments (#1255). """ testdir.maketxtfile( xdoc=""" >>> 1 1 """ ) testdir.makefile( ".foo", test=""" >>> 1 1 """, ) testdir.maketxtfile( test_normal=""" >>> 1 1 """ ) expected = {"xdoc.txt", "test.foo", "test_normal.txt"} assert {x.basename for x in testdir.tmpdir.listdir()} == expected args = ["--doctest-glob=xdoc*.txt", "--doctest-glob=*.foo"] result = testdir.runpytest(*args) result.stdout.fnmatch_lines(["*test.foo *", "*xdoc.txt *", "*2 passed*"]) result = testdir.runpytest() result.stdout.fnmatch_lines(["*test_normal.txt *", "*1 passed*"]) @pytest.mark.parametrize( " test_string, encoding", [("foo", "ascii"), ("öäü", "latin1"), ("öäü", "utf-8")], ) def test_encoding(self, testdir, test_string, encoding): """Test support for doctest_encoding ini option. """ testdir.makeini( """ [pytest] doctest_encoding={} """.format( encoding ) ) doctest = """ >>> "{}" {} """.format( test_string, repr(test_string) ) testdir._makefile(".txt", [doctest], {}, encoding=encoding) result = testdir.runpytest() result.stdout.fnmatch_lines(["*1 passed*"]) def test_doctest_unexpected_exception(self, testdir): testdir.maketxtfile( """ >>> i = 0 >>> 0 / i 2 """ ) result = testdir.runpytest("--doctest-modules") result.stdout.fnmatch_lines( [ "*unexpected_exception*", "*>>> i = 0*", "*>>> 0 / i*", "*UNEXPECTED*ZeroDivision*", ] ) def test_doctest_skip(self, testdir): testdir.maketxtfile( """ >>> 1 1 >>> import pytest >>> pytest.skip("") """ ) result = testdir.runpytest("--doctest-modules") result.stdout.fnmatch_lines(["*1 skipped*"]) def test_docstring_partial_context_around_error(self, testdir): """Test that we show some context before the actual line of a failing doctest. """ testdir.makepyfile( ''' def foo(): """ text-line-1 text-line-2 text-line-3 text-line-4 text-line-5 text-line-6 text-line-7 text-line-8 text-line-9 text-line-10 text-line-11 >>> 1 + 1 3 text-line-after """ ''' ) result = testdir.runpytest("--doctest-modules") result.stdout.fnmatch_lines( [ "*docstring_partial_context_around_error*", "005*text-line-3", "006*text-line-4", "013*text-line-11", "014*>>> 1 + 1", "Expected:", " 3", "Got:", " 2", ] ) # lines below should be trimmed out assert "text-line-2" not in result.stdout.str() assert "text-line-after" not in result.stdout.str() def test_docstring_full_context_around_error(self, testdir): """Test that we show the whole context before the actual line of a failing doctest, provided that the context is up to 10 lines long. """ testdir.makepyfile( ''' def foo(): """ text-line-1 text-line-2 >>> 1 + 1 3 """ ''' ) result = testdir.runpytest("--doctest-modules") result.stdout.fnmatch_lines( [ "*docstring_full_context_around_error*", "003*text-line-1", "004*text-line-2", "006*>>> 1 + 1", "Expected:", " 3", "Got:", " 2", ] ) def test_doctest_linedata_missing(self, testdir): testdir.tmpdir.join("hello.py").write( textwrap.dedent( """\ class Fun(object): @property def test(self): ''' >>> a = 1 >>> 1/0 ''' """ ) ) result = testdir.runpytest("--doctest-modules") result.stdout.fnmatch_lines( [ "*hello*", "*EXAMPLE LOCATION UNKNOWN, not showing all tests of that example*", "*1/0*", "*UNEXPECTED*ZeroDivision*", "*1 failed*", ] ) def test_doctest_unex_importerror_only_txt(self, testdir): testdir.maketxtfile( """ >>> import asdalsdkjaslkdjasd >>> """ ) result = testdir.runpytest() # doctest is never executed because of error during hello.py collection result.stdout.fnmatch_lines( [ "*>>> import asdals*", "*UNEXPECTED*{e}*".format(e=MODULE_NOT_FOUND_ERROR), "{e}: No module named *asdal*".format(e=MODULE_NOT_FOUND_ERROR), ] ) def test_doctest_unex_importerror_with_module(self, testdir): testdir.tmpdir.join("hello.py").write( textwrap.dedent( """\ import asdalsdkjaslkdjasd """ ) ) testdir.maketxtfile( """ >>> import hello >>> """ ) result = testdir.runpytest("--doctest-modules") # doctest is never executed because of error during hello.py collection result.stdout.fnmatch_lines( [ "*ERROR collecting hello.py*", "*{e}: No module named *asdals*".format(e=MODULE_NOT_FOUND_ERROR), "*Interrupted: 1 errors during collection*", ] ) def test_doctestmodule(self, testdir): p = testdir.makepyfile( """ ''' >>> x = 1 >>> x == 1 False ''' """ ) reprec = testdir.inline_run(p, "--doctest-modules") reprec.assertoutcome(failed=1) def test_doctestmodule_external_and_issue116(self, testdir): p = testdir.mkpydir("hello") p.join("__init__.py").write( textwrap.dedent( """\ def somefunc(): ''' >>> i = 0 >>> i + 1 2 ''' """ ) ) result = testdir.runpytest(p, "--doctest-modules") result.stdout.fnmatch_lines( [ "003 *>>> i = 0", "004 *>>> i + 1", "*Expected:", "* 2", "*Got:", "* 1", "*:4: DocTestFailure", ] ) def test_txtfile_failing(self, testdir): p = testdir.maketxtfile( """ >>> i = 0 >>> i + 1 2 """ ) result = testdir.runpytest(p, "-s") result.stdout.fnmatch_lines( [ "001 >>> i = 0", "002 >>> i + 1", "Expected:", " 2", "Got:", " 1", "*test_txtfile_failing.txt:2: DocTestFailure", ] ) def test_txtfile_with_fixtures(self, testdir): p = testdir.maketxtfile( """ >>> dir = getfixture('tmpdir') >>> type(dir).__name__ 'LocalPath' """ ) reprec = testdir.inline_run(p) reprec.assertoutcome(passed=1) def test_txtfile_with_usefixtures_in_ini(self, testdir): testdir.makeini( """ [pytest] usefixtures = myfixture """ ) testdir.makeconftest( """ import pytest @pytest.fixture def myfixture(monkeypatch): monkeypatch.setenv("HELLO", "WORLD") """ ) p = testdir.maketxtfile( """ >>> import os >>> os.environ["HELLO"] 'WORLD' """ ) reprec = testdir.inline_run(p) reprec.assertoutcome(passed=1) def test_doctestmodule_with_fixtures(self, testdir): p = testdir.makepyfile( """ ''' >>> dir = getfixture('tmpdir') >>> type(dir).__name__ 'LocalPath' ''' """ ) reprec = testdir.inline_run(p, "--doctest-modules") reprec.assertoutcome(passed=1) def test_doctestmodule_three_tests(self, testdir): p = testdir.makepyfile( """ ''' >>> dir = getfixture('tmpdir') >>> type(dir).__name__ 'LocalPath' ''' def my_func(): ''' >>> magic = 42 >>> magic - 42 0 ''' def unuseful(): pass def another(): ''' >>> import os >>> os is os True ''' """ ) reprec = testdir.inline_run(p, "--doctest-modules") reprec.assertoutcome(passed=3) def test_doctestmodule_two_tests_one_fail(self, testdir): p = testdir.makepyfile( """ class MyClass(object): def bad_meth(self): ''' >>> magic = 42 >>> magic 0 ''' def nice_meth(self): ''' >>> magic = 42 >>> magic - 42 0 ''' """ ) reprec = testdir.inline_run(p, "--doctest-modules") reprec.assertoutcome(failed=1, passed=1) def test_ignored_whitespace(self, testdir): testdir.makeini( """ [pytest] doctest_optionflags = ELLIPSIS NORMALIZE_WHITESPACE """ ) p = testdir.makepyfile( """ class MyClass(object): ''' >>> a = "foo " >>> print(a) foo ''' pass """ ) reprec = testdir.inline_run(p, "--doctest-modules") reprec.assertoutcome(passed=1) def test_non_ignored_whitespace(self, testdir): testdir.makeini( """ [pytest] doctest_optionflags = ELLIPSIS """ ) p = testdir.makepyfile( """ class MyClass(object): ''' >>> a = "foo " >>> print(a) foo ''' pass """ ) reprec = testdir.inline_run(p, "--doctest-modules") reprec.assertoutcome(failed=1, passed=0) def test_ignored_whitespace_glob(self, testdir): testdir.makeini( """ [pytest] doctest_optionflags = ELLIPSIS NORMALIZE_WHITESPACE """ ) p = testdir.maketxtfile( xdoc=""" >>> a = "foo " >>> print(a) foo """ ) reprec = testdir.inline_run(p, "--doctest-glob=x*.txt") reprec.assertoutcome(passed=1) def test_non_ignored_whitespace_glob(self, testdir): testdir.makeini( """ [pytest] doctest_optionflags = ELLIPSIS """ ) p = testdir.maketxtfile( xdoc=""" >>> a = "foo " >>> print(a) foo """ ) reprec = testdir.inline_run(p, "--doctest-glob=x*.txt") reprec.assertoutcome(failed=1, passed=0) def test_contains_unicode(self, testdir): """Fix internal error with docstrings containing non-ascii characters. """ testdir.makepyfile( '''\ def foo(): """ >>> name = 'с' # not letter 'c' but instead Cyrillic 's'. 'anything' """ ''' ) result = testdir.runpytest("--doctest-modules") result.stdout.fnmatch_lines(["Got nothing", "* 1 failed in*"]) def test_ignore_import_errors_on_doctest(self, testdir): p = testdir.makepyfile( """ import asdf def add_one(x): ''' >>> add_one(1) 2 ''' return x + 1 """ ) reprec = testdir.inline_run( p, "--doctest-modules", "--doctest-ignore-import-errors" ) reprec.assertoutcome(skipped=1, failed=1, passed=0) def test_junit_report_for_doctest(self, testdir): """ #713: Fix --junit-xml option when used with --doctest-modules. """ p = testdir.makepyfile( """ def foo(): ''' >>> 1 + 1 3 ''' pass """ ) reprec = testdir.inline_run(p, "--doctest-modules", "--junit-xml=junit.xml") reprec.assertoutcome(failed=1) def test_unicode_doctest(self, testdir): """ Test case for issue 2434: DecodeError on Python 2 when doctest contains non-ascii characters. """ p = testdir.maketxtfile( test_unicode_doctest=""" .. doctest:: >>> print( ... "Hi\\n\\nByé") Hi ... Byé >>> 1/0 # Byé 1 """ ) result = testdir.runpytest(p) result.stdout.fnmatch_lines( ["*UNEXPECTED EXCEPTION: ZeroDivisionError*", "*1 failed*"] ) def test_unicode_doctest_module(self, testdir): """ Test case for issue 2434: DecodeError on Python 2 when doctest docstring contains non-ascii characters. """ p = testdir.makepyfile( test_unicode_doctest_module=""" def fix_bad_unicode(text): ''' >>> print(fix_bad_unicode('único')) único ''' return "único" """ ) result = testdir.runpytest(p, "--doctest-modules") result.stdout.fnmatch_lines(["* 1 passed *"]) def test_print_unicode_value(self, testdir): """ Test case for issue 3583: Printing Unicode in doctest under Python 2.7 doesn't work """ p = testdir.maketxtfile( test_print_unicode_value=r""" Here is a doctest:: >>> print('\xE5\xE9\xEE\xF8\xFC') åéîøü """ ) result = testdir.runpytest(p) result.stdout.fnmatch_lines(["* 1 passed *"]) def test_reportinfo(self, testdir): """ Test case to make sure that DoctestItem.reportinfo() returns lineno. """ p = testdir.makepyfile( test_reportinfo=""" def foo(x): ''' >>> foo('a') 'b' ''' return 'c' """ ) items, reprec = testdir.inline_genitems(p, "--doctest-modules") reportinfo = items[0].reportinfo() assert reportinfo[1] == 1 def test_valid_setup_py(self, testdir): """ Test to make sure that pytest ignores valid setup.py files when ran with --doctest-modules """ p = testdir.makepyfile( setup=""" from setuptools import setup, find_packages setup(name='sample', version='0.0', description='description', packages=find_packages() ) """ ) result = testdir.runpytest(p, "--doctest-modules") result.stdout.fnmatch_lines(["*collected 0 items*"]) def test_invalid_setup_py(self, testdir): """ Test to make sure that pytest reads setup.py files that are not used for python packages when ran with --doctest-modules """ p = testdir.makepyfile( setup=""" def test_foo(): return 'bar' """ ) result = testdir.runpytest(p, "--doctest-modules") result.stdout.fnmatch_lines(["*collected 1 item*"]) class TestLiterals: @pytest.mark.parametrize("config_mode", ["ini", "comment"]) def test_allow_unicode(self, testdir, config_mode): """Test that doctests which output unicode work in all python versions tested by pytest when the ALLOW_UNICODE option is used (either in the ini file or by an inline comment). """ if config_mode == "ini": testdir.makeini( """ [pytest] doctest_optionflags = ALLOW_UNICODE """ ) comment = "" else: comment = "#doctest: +ALLOW_UNICODE" testdir.maketxtfile( test_doc=""" >>> b'12'.decode('ascii') {comment} '12' """.format( comment=comment ) ) testdir.makepyfile( foo=""" def foo(): ''' >>> b'12'.decode('ascii') {comment} '12' ''' """.format( comment=comment ) ) reprec = testdir.inline_run("--doctest-modules") reprec.assertoutcome(passed=2) @pytest.mark.parametrize("config_mode", ["ini", "comment"]) def test_allow_bytes(self, testdir, config_mode): """Test that doctests which output bytes work in all python versions tested by pytest when the ALLOW_BYTES option is used (either in the ini file or by an inline comment)(#1287). """ if config_mode == "ini": testdir.makeini( """ [pytest] doctest_optionflags = ALLOW_BYTES """ ) comment = "" else: comment = "#doctest: +ALLOW_BYTES" testdir.maketxtfile( test_doc=""" >>> b'foo' {comment} 'foo' """.format( comment=comment ) ) testdir.makepyfile( foo=""" def foo(): ''' >>> b'foo' {comment} 'foo' ''' """.format( comment=comment ) ) reprec = testdir.inline_run("--doctest-modules") reprec.assertoutcome(passed=2) def test_unicode_string(self, testdir): """Test that doctests which output unicode fail in Python 2 when the ALLOW_UNICODE option is not used. The same test should pass in Python 3. """ testdir.maketxtfile( test_doc=""" >>> b'12'.decode('ascii') '12' """ ) reprec = testdir.inline_run() reprec.assertoutcome(passed=1) def test_bytes_literal(self, testdir): """Test that doctests which output bytes fail in Python 3 when the ALLOW_BYTES option is not used. (#1287). """ testdir.maketxtfile( test_doc=""" >>> b'foo' 'foo' """ ) reprec = testdir.inline_run() reprec.assertoutcome(failed=1) def test_number_re(self): for s in [ "1.", "+1.", "-1.", ".1", "+.1", "-.1", "0.1", "+0.1", "-0.1", "1e5", "+1e5", "1e+5", "+1e+5", "1e-5", "+1e-5", "-1e-5", "1.2e3", "-1.2e-3", ]: print(s) m = _get_checker()._number_re.match(s) assert m is not None assert float(m.group()) == pytest.approx(float(s)) for s in ["1", "abc"]: print(s) assert _get_checker()._number_re.match(s) is None @pytest.mark.parametrize("config_mode", ["ini", "comment"]) def test_number_precision(self, testdir, config_mode): """Test the NUMBER option.""" if config_mode == "ini": testdir.makeini( """ [pytest] doctest_optionflags = NUMBER """ ) comment = "" else: comment = "#doctest: +NUMBER" testdir.maketxtfile( test_doc=""" Scalars: >>> import math >>> math.pi {comment} 3.141592653589793 >>> math.pi {comment} 3.1416 >>> math.pi {comment} 3.14 >>> -math.pi {comment} -3.14 >>> math.pi {comment} 3. >>> 3. {comment} 3.0 >>> 3. {comment} 3. >>> 3. {comment} 3.01 >>> 3. {comment} 2.99 >>> .299 {comment} .3 >>> .301 {comment} .3 >>> 951. {comment} 1e3 >>> 1049. {comment} 1e3 >>> -1049. {comment} -1e3 >>> 1e3 {comment} 1e3 >>> 1e3 {comment} 1000. Lists: >>> [3.1415, 0.097, 13.1, 7, 8.22222e5, 0.598e-2] {comment} [3.14, 0.1, 13., 7, 8.22e5, 6.0e-3] >>> [[0.333, 0.667], [0.999, 1.333]] {comment} [[0.33, 0.667], [0.999, 1.333]] >>> [[[0.101]]] {comment} [[[0.1]]] Doesn't barf on non-numbers: >>> 'abc' {comment} 'abc' >>> None {comment} """.format( comment=comment ) ) reprec = testdir.inline_run() reprec.assertoutcome(passed=1) @pytest.mark.parametrize( "expression,output", [ # ints shouldn't match floats: ("3.0", "3"), ("3e0", "3"), ("1e3", "1000"), ("3", "3.0"), # Rounding: ("3.1", "3.0"), ("3.1", "3.2"), ("3.1", "4.0"), ("8.22e5", "810000.0"), # Only the actual output is rounded up, not the expected output: ("3.0", "2.98"), ("1e3", "999"), # The current implementation doesn't understand that numbers inside # strings shouldn't be treated as numbers: pytest.param("'3.1416'", "'3.14'", marks=pytest.mark.xfail), ], ) def test_number_non_matches(self, testdir, expression, output): testdir.maketxtfile( test_doc=""" >>> {expression} #doctest: +NUMBER {output} """.format( expression=expression, output=output ) ) reprec = testdir.inline_run() reprec.assertoutcome(passed=0, failed=1) def test_number_and_allow_unicode(self, testdir): testdir.maketxtfile( test_doc=""" >>> from collections import namedtuple >>> T = namedtuple('T', 'a b c') >>> T(a=0.2330000001, b=u'str', c=b'bytes') # doctest: +ALLOW_UNICODE, +ALLOW_BYTES, +NUMBER T(a=0.233, b=u'str', c='bytes') """ ) reprec = testdir.inline_run() reprec.assertoutcome(passed=1) class TestDoctestSkips: """ If all examples in a doctest are skipped due to the SKIP option, then the tests should be SKIPPED rather than PASSED. (#957) """ @pytest.fixture(params=["text", "module"]) def makedoctest(self, testdir, request): def makeit(doctest): mode = request.param if mode == "text": testdir.maketxtfile(doctest) else: assert mode == "module" testdir.makepyfile('"""\n%s"""' % doctest) return makeit def test_one_skipped(self, testdir, makedoctest): makedoctest( """ >>> 1 + 1 # doctest: +SKIP 2 >>> 2 + 2 4 """ ) reprec = testdir.inline_run("--doctest-modules") reprec.assertoutcome(passed=1) def test_one_skipped_failed(self, testdir, makedoctest): makedoctest( """ >>> 1 + 1 # doctest: +SKIP 2 >>> 2 + 2 200 """ ) reprec = testdir.inline_run("--doctest-modules") reprec.assertoutcome(failed=1) def test_all_skipped(self, testdir, makedoctest): makedoctest( """ >>> 1 + 1 # doctest: +SKIP 2 >>> 2 + 2 # doctest: +SKIP 200 """ ) reprec = testdir.inline_run("--doctest-modules") reprec.assertoutcome(skipped=1) def test_vacuous_all_skipped(self, testdir, makedoctest): makedoctest("") reprec = testdir.inline_run("--doctest-modules") reprec.assertoutcome(passed=0, skipped=0) def test_continue_on_failure(self, testdir): testdir.maketxtfile( test_something=""" >>> i = 5 >>> def foo(): ... raise ValueError('error1') >>> foo() >>> i >>> i + 2 7 >>> i + 1 """ ) result = testdir.runpytest("--doctest-modules", "--doctest-continue-on-failure") result.assert_outcomes(passed=0, failed=1) # The lines that contains the failure are 4, 5, and 8. The first one # is a stack trace and the other two are mismatches. result.stdout.fnmatch_lines( ["*4: UnexpectedException*", "*5: DocTestFailure*", "*8: DocTestFailure*"] ) class TestDoctestAutoUseFixtures: SCOPES = ["module", "session", "class", "function"] def test_doctest_module_session_fixture(self, testdir): """Test that session fixtures are initialized for doctest modules (#768) """ # session fixture which changes some global data, which will # be accessed by doctests in a module testdir.makeconftest( """ import pytest import sys @pytest.yield_fixture(autouse=True, scope='session') def myfixture(): assert not hasattr(sys, 'pytest_session_data') sys.pytest_session_data = 1 yield del sys.pytest_session_data """ ) testdir.makepyfile( foo=""" import sys def foo(): ''' >>> assert sys.pytest_session_data == 1 ''' def bar(): ''' >>> assert sys.pytest_session_data == 1 ''' """ ) result = testdir.runpytest("--doctest-modules") result.stdout.fnmatch_lines(["*2 passed*"]) @pytest.mark.parametrize("scope", SCOPES) @pytest.mark.parametrize("enable_doctest", [True, False]) def test_fixture_scopes(self, testdir, scope, enable_doctest): """Test that auto-use fixtures work properly with doctest modules. See #1057 and #1100. """ testdir.makeconftest( """ import pytest @pytest.fixture(autouse=True, scope="{scope}") def auto(request): return 99 """.format( scope=scope ) ) testdir.makepyfile( test_1=''' def test_foo(): """ >>> getfixture('auto') + 1 100 """ def test_bar(): assert 1 ''' ) params = ("--doctest-modules",) if enable_doctest else () passes = 3 if enable_doctest else 2 result = testdir.runpytest(*params) result.stdout.fnmatch_lines(["*=== %d passed in *" % passes]) @pytest.mark.parametrize("scope", SCOPES) @pytest.mark.parametrize("autouse", [True, False]) @pytest.mark.parametrize("use_fixture_in_doctest", [True, False]) def test_fixture_module_doctest_scopes( self, testdir, scope, autouse, use_fixture_in_doctest ): """Test that auto-use fixtures work properly with doctest files. See #1057 and #1100. """ testdir.makeconftest( """ import pytest @pytest.fixture(autouse={autouse}, scope="{scope}") def auto(request): return 99 """.format( scope=scope, autouse=autouse ) ) if use_fixture_in_doctest: testdir.maketxtfile( test_doc=""" >>> getfixture('auto') 99 """ ) else: testdir.maketxtfile( test_doc=""" >>> 1 + 1 2 """ ) result = testdir.runpytest("--doctest-modules") assert "FAILURES" not in str(result.stdout.str()) result.stdout.fnmatch_lines(["*=== 1 passed in *"]) @pytest.mark.parametrize("scope", SCOPES) def test_auto_use_request_attributes(self, testdir, scope): """Check that all attributes of a request in an autouse fixture behave as expected when requested for a doctest item. """ testdir.makeconftest( """ import pytest @pytest.fixture(autouse=True, scope="{scope}") def auto(request): if "{scope}" == 'module': assert request.module is None if "{scope}" == 'class': assert request.cls is None if "{scope}" == 'function': assert request.function is None return 99 """.format( scope=scope ) ) testdir.maketxtfile( test_doc=""" >>> 1 + 1 2 """ ) result = testdir.runpytest("--doctest-modules") assert "FAILURES" not in str(result.stdout.str()) result.stdout.fnmatch_lines(["*=== 1 passed in *"]) class TestDoctestNamespaceFixture: SCOPES = ["module", "session", "class", "function"] @pytest.mark.parametrize("scope", SCOPES) def test_namespace_doctestfile(self, testdir, scope): """ Check that inserting something into the namespace works in a simple text file doctest """ testdir.makeconftest( """ import pytest import contextlib @pytest.fixture(autouse=True, scope="{scope}") def add_contextlib(doctest_namespace): doctest_namespace['cl'] = contextlib """.format( scope=scope ) ) p = testdir.maketxtfile( """ >>> print(cl.__name__) contextlib """ ) reprec = testdir.inline_run(p) reprec.assertoutcome(passed=1) @pytest.mark.parametrize("scope", SCOPES) def test_namespace_pyfile(self, testdir, scope): """ Check that inserting something into the namespace works in a simple Python file docstring doctest """ testdir.makeconftest( """ import pytest import contextlib @pytest.fixture(autouse=True, scope="{scope}") def add_contextlib(doctest_namespace): doctest_namespace['cl'] = contextlib """.format( scope=scope ) ) p = testdir.makepyfile( """ def foo(): ''' >>> print(cl.__name__) contextlib ''' """ ) reprec = testdir.inline_run(p, "--doctest-modules") reprec.assertoutcome(passed=1) class TestDoctestReportingOption: def _run_doctest_report(self, testdir, format): testdir.makepyfile( """ def foo(): ''' >>> foo() a b 0 1 4 1 2 4 2 3 6 ''' print(' a b\\n' '0 1 4\\n' '1 2 5\\n' '2 3 6') """ ) return testdir.runpytest("--doctest-modules", "--doctest-report", format) @pytest.mark.parametrize("format", ["udiff", "UDIFF", "uDiFf"]) def test_doctest_report_udiff(self, testdir, format): result = self._run_doctest_report(testdir, format) result.stdout.fnmatch_lines( [" 0 1 4", " -1 2 4", " +1 2 5", " 2 3 6"] ) def test_doctest_report_cdiff(self, testdir): result = self._run_doctest_report(testdir, "cdiff") result.stdout.fnmatch_lines( [ " a b", " 0 1 4", " ! 1 2 4", " 2 3 6", " --- 1,4 ----", " a b", " 0 1 4", " ! 1 2 5", " 2 3 6", ] ) def test_doctest_report_ndiff(self, testdir): result = self._run_doctest_report(testdir, "ndiff") result.stdout.fnmatch_lines( [ " a b", " 0 1 4", " - 1 2 4", " ? ^", " + 1 2 5", " ? ^", " 2 3 6", ] ) @pytest.mark.parametrize("format", ["none", "only_first_failure"]) def test_doctest_report_none_or_only_first_failure(self, testdir, format): result = self._run_doctest_report(testdir, format) result.stdout.fnmatch_lines( [ "Expected:", " a b", " 0 1 4", " 1 2 4", " 2 3 6", "Got:", " a b", " 0 1 4", " 1 2 5", " 2 3 6", ] ) def test_doctest_report_invalid(self, testdir): result = self._run_doctest_report(testdir, "obviously_invalid_format") result.stderr.fnmatch_lines( [ "*error: argument --doctest-report: invalid choice: 'obviously_invalid_format' (choose from*" ] ) @pytest.mark.parametrize("mock_module", ["mock", "unittest.mock"]) def test_doctest_mock_objects_dont_recurse_missbehaved(mock_module, testdir): pytest.importorskip(mock_module) testdir.makepyfile( """ from {mock_module} import call class Example(object): ''' >>> 1 + 1 2 ''' """.format( mock_module=mock_module ) ) result = testdir.runpytest("--doctest-modules") result.stdout.fnmatch_lines(["* 1 passed *"]) class Broken: def __getattr__(self, _): raise KeyError("This should be an AttributeError") @pytest.mark.parametrize( # pragma: no branch (lambdas are not called) "stop", [None, _is_mocked, lambda f: None, lambda f: False, lambda f: True] ) def test_warning_on_unwrap_of_broken_object(stop): bad_instance = Broken() assert inspect.unwrap.__module__ == "inspect" with _patch_unwrap_mock_aware(): assert inspect.unwrap.__module__ != "inspect" with pytest.warns( pytest.PytestWarning, match="^Got KeyError.* when unwrapping" ): with pytest.raises(KeyError): inspect.unwrap(bad_instance, stop=stop) assert inspect.unwrap.__module__ == "inspect"