diff --git a/CHANGES b/CHANGES index 073d436d0..0429a595c 100644 --- a/CHANGES +++ b/CHANGES @@ -55,6 +55,11 @@ Bugs fixed Testing ------- +* #11577: pytest: Fail tests on "XPASS". +* #11577: pytest: Use "importlib" import mode. +* #11577: pytest: Set PYTHONWARNINGS=error. +* #11577: pytest: Set strict config and strict markers. + Release 7.1.2 (released Aug 02, 2023) ===================================== diff --git a/pyproject.toml b/pyproject.toml index 7675100a1..a7e0702a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -91,8 +91,9 @@ lint = [ test = [ "pytest>=4.6", "html5lib", - "cython", - "filelock" + "cython>=3.0", + "setuptools>=67.0", # for Cython compilation + "filelock", ] [[project.authors]] @@ -346,6 +347,14 @@ module = [ disallow_any_generics = false [tool.pytest.ini_options] +minversion = 4.6 +addopts = [ + "--import-mode=importlib", + "--pythonwarnings=error", + "--strict-config", + "--strict-markers", +] +empty_parameter_set_mark = "xfail" filterwarnings = [ "all", "ignore::DeprecationWarning:docutils.io", @@ -356,6 +365,7 @@ markers = [ "apidoc", ] testpaths = ["tests"] +xfail_strict = true [tool.coverage.run] branch = true diff --git a/sphinx/pycode/parser.py b/sphinx/pycode/parser.py index 70115642e..9ca18e8db 100644 --- a/sphinx/pycode/parser.py +++ b/sphinx/pycode/parser.py @@ -44,7 +44,7 @@ def get_lvar_names(node: ast.AST, self: ast.arg | None = None) -> list[str]: self_id = self.arg node_name = node.__class__.__name__ - if node_name in ('Index', 'Num', 'Slice', 'Str', 'Subscript'): + if node_name in ('Constant', 'Index', 'Slice', 'Subscript'): raise TypeError('%r does not create new variable' % node) if node_name == 'Name': if self is None or node.id == self_id: # type: ignore @@ -403,15 +403,15 @@ class VariableCommentPicker(ast.NodeVisitor): def visit_Expr(self, node: ast.Expr) -> None: """Handles Expr node and pick up a comment if string.""" if (isinstance(self.previous, (ast.Assign, ast.AnnAssign)) and - isinstance(node.value, ast.Str)): + isinstance(node.value, ast.Constant) and isinstance(node.value.value, str)): try: targets = get_assign_targets(self.previous) varnames = get_lvar_names(targets[0], self.get_self()) for varname in varnames: - if isinstance(node.value.s, str): - docstring = node.value.s + if isinstance(node.value.value, str): + docstring = node.value.value else: - docstring = node.value.s.decode(self.encoding or 'utf-8') + docstring = node.value.value.decode(self.encoding or 'utf-8') self.add_variable_comment(varname, dedent_docstring(docstring)) self.add_entry(varname) diff --git a/sphinx/testing/restructuredtext.py b/sphinx/testing/restructuredtext.py index 9deabb74a..1f89336db 100644 --- a/sphinx/testing/restructuredtext.py +++ b/sphinx/testing/restructuredtext.py @@ -18,10 +18,18 @@ def parse(app: Sphinx, text: str, docname: str = 'index') -> nodes.document: parser = RSTParser() parser.set_application(app) with sphinx_domains(app.env): - return publish_doctree(text, path.join(app.srcdir, docname + '.rst'), - reader=reader, - parser=parser, - settings_overrides={'env': app.env, - 'gettext_compact': True}) + return publish_doctree( + text, + path.join(app.srcdir, docname + '.rst'), + reader=reader, + parser=parser, + settings_overrides={ + 'env': app.env, + 'gettext_compact': True, + 'input_encoding': 'utf-8', + 'output_encoding': 'unicode', + 'traceback': True, + }, + ) finally: app.env.temp_data.pop('docname', None) diff --git a/sphinx/testing/util.py b/sphinx/testing/util.py index 4d034006b..64463fb6f 100644 --- a/sphinx/testing/util.py +++ b/sphinx/testing/util.py @@ -93,7 +93,7 @@ class SphinxTestApp(application.Sphinx): self.docutils_conf_path = srcdir / 'docutils.conf' if docutilsconf is not None: - self.docutils_conf_path.write_text(docutilsconf) + self.docutils_conf_path.write_text(docutilsconf, encoding='utf8') if builddir is None: builddir = srcdir / '_build' diff --git a/tests/test_build.py b/tests/test_build.py index 546798b02..ed4bc4389 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -2,7 +2,6 @@ import os import shutil -import sys from unittest import mock import pytest @@ -50,7 +49,6 @@ nonascii file name page ) @mock.patch('sphinx.builders.linkcheck.requests.head', side_effect=request_session_head) -@pytest.mark.xfail(sys.platform == 'win32', reason="Not working on windows") def test_build_all(requests_head, make_app, nonascii_srcdir, buildername): app = make_app(buildername, srcdir=nonascii_srcdir) app.build() diff --git a/tests/test_config.py b/tests/test_config.py index 09729ef71..41b737e01 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -456,7 +456,7 @@ def source_date_year(request, monkeypatch): sde = request.param with monkeypatch.context() as m: if sde: - m.setenv('SOURCE_DATE_EPOCH', sde) + m.setenv('SOURCE_DATE_EPOCH', str(sde)) yield time.gmtime(sde).tm_year else: m.delenv('SOURCE_DATE_EPOCH', raising=False) diff --git a/tests/test_directive_code.py b/tests/test_directive_code.py index 3892540a8..10acb2bda 100644 --- a/tests/test_directive_code.py +++ b/tests/test_directive_code.py @@ -1,6 +1,6 @@ """Test the code-block directive.""" -import os +import os.path import pytest from docutils import nodes @@ -23,7 +23,6 @@ def literal_inc_path(testroot): return testroot / 'literal.inc' -@pytest.mark.xfail(os.name != 'posix', reason="Not working on windows") def test_LiteralIncludeReader(literal_inc_path): options = {'lineno-match': True} reader = LiteralIncludeReader(literal_inc_path, options, DUMMY_CONFIG) @@ -33,7 +32,6 @@ def test_LiteralIncludeReader(literal_inc_path): assert reader.lineno_start == 1 -@pytest.mark.xfail(os.name != 'posix', reason="Not working on windows") def test_LiteralIncludeReader_lineno_start(literal_inc_path): options = {'lineno-start': 4} reader = LiteralIncludeReader(literal_inc_path, options, DUMMY_CONFIG) @@ -43,7 +41,6 @@ def test_LiteralIncludeReader_lineno_start(literal_inc_path): assert reader.lineno_start == 4 -@pytest.mark.xfail(os.name != 'posix', reason="Not working on windows") def test_LiteralIncludeReader_pyobject1(literal_inc_path): options = {'lineno-match': True, 'pyobject': 'Foo'} reader = LiteralIncludeReader(literal_inc_path, options, DUMMY_CONFIG) @@ -53,7 +50,6 @@ def test_LiteralIncludeReader_pyobject1(literal_inc_path): assert reader.lineno_start == 5 -@pytest.mark.xfail(os.name != 'posix', reason="Not working on windows") def test_LiteralIncludeReader_pyobject2(literal_inc_path): options = {'pyobject': 'Bar'} reader = LiteralIncludeReader(literal_inc_path, options, DUMMY_CONFIG) @@ -64,7 +60,6 @@ def test_LiteralIncludeReader_pyobject2(literal_inc_path): assert reader.lineno_start == 1 # no lineno-match -@pytest.mark.xfail(os.name != 'posix', reason="Not working on windows") def test_LiteralIncludeReader_pyobject3(literal_inc_path): options = {'pyobject': 'Bar.baz'} reader = LiteralIncludeReader(literal_inc_path, options, DUMMY_CONFIG) @@ -73,7 +68,6 @@ def test_LiteralIncludeReader_pyobject3(literal_inc_path): " pass\n") -@pytest.mark.xfail(os.name != 'posix', reason="Not working on windows") def test_LiteralIncludeReader_pyobject_and_lines(literal_inc_path): options = {'pyobject': 'Bar', 'lines': '2-'} reader = LiteralIncludeReader(literal_inc_path, options, DUMMY_CONFIG) @@ -82,7 +76,6 @@ def test_LiteralIncludeReader_pyobject_and_lines(literal_inc_path): " pass\n") -@pytest.mark.xfail(os.name != 'posix', reason="Not working on windows") def test_LiteralIncludeReader_lines1(literal_inc_path): options = {'lines': '1-3'} reader = LiteralIncludeReader(literal_inc_path, options, DUMMY_CONFIG) @@ -92,7 +85,6 @@ def test_LiteralIncludeReader_lines1(literal_inc_path): "foo = \"Including Unicode characters: üöä\"\n") -@pytest.mark.xfail(os.name != 'posix', reason="Not working on windows") def test_LiteralIncludeReader_lines2(literal_inc_path): options = {'lines': '1,3,5'} reader = LiteralIncludeReader(literal_inc_path, options, DUMMY_CONFIG) @@ -102,7 +94,6 @@ def test_LiteralIncludeReader_lines2(literal_inc_path): "class Foo:\n") -@pytest.mark.xfail(os.name != 'posix', reason="Not working on windows") def test_LiteralIncludeReader_lines_and_lineno_match1(literal_inc_path): options = {'lines': '3-5', 'lineno-match': True} reader = LiteralIncludeReader(literal_inc_path, options, DUMMY_CONFIG) @@ -129,7 +120,6 @@ def test_LiteralIncludeReader_lines_and_lineno_match3(literal_inc_path, app, sta reader.read() -@pytest.mark.xfail(os.name != 'posix', reason="Not working on windows") def test_LiteralIncludeReader_start_at(literal_inc_path): options = {'lineno-match': True, 'start-at': 'Foo', 'end-at': 'Bar'} reader = LiteralIncludeReader(literal_inc_path, options, DUMMY_CONFIG) @@ -141,7 +131,6 @@ def test_LiteralIncludeReader_start_at(literal_inc_path): assert reader.lineno_start == 5 -@pytest.mark.xfail(os.name != 'posix', reason="Not working on windows") def test_LiteralIncludeReader_start_after(literal_inc_path): options = {'lineno-match': True, 'start-after': 'Foo', 'end-before': 'Bar'} reader = LiteralIncludeReader(literal_inc_path, options, DUMMY_CONFIG) @@ -151,7 +140,6 @@ def test_LiteralIncludeReader_start_after(literal_inc_path): assert reader.lineno_start == 6 -@pytest.mark.xfail(os.name != 'posix', reason="Not working on windows") def test_LiteralIncludeReader_start_after_and_lines(literal_inc_path): options = {'lineno-match': True, 'lines': '6-', 'start-after': 'Literally', 'end-before': 'comment'} @@ -165,7 +153,6 @@ def test_LiteralIncludeReader_start_after_and_lines(literal_inc_path): assert reader.lineno_start == 7 -@pytest.mark.xfail(os.name != 'posix', reason="Not working on windows") def test_LiteralIncludeReader_start_at_and_lines(literal_inc_path): options = {'lines': '2, 3, 5', 'start-at': 'foo', 'end-before': '#'} reader = LiteralIncludeReader(literal_inc_path, options, DUMMY_CONFIG) @@ -206,7 +193,6 @@ def test_LiteralIncludeReader_end_before(literal_inc_path): "\n") -@pytest.mark.xfail(os.name != 'posix', reason="Not working on windows") def test_LiteralIncludeReader_prepend(literal_inc_path): options = {'lines': '1', 'prepend': 'Hello', 'append': 'Sphinx'} reader = LiteralIncludeReader(literal_inc_path, options, DUMMY_CONFIG) @@ -216,7 +202,6 @@ def test_LiteralIncludeReader_prepend(literal_inc_path): "Sphinx\n") -@pytest.mark.xfail(os.name != 'posix', reason="Not working on windows") def test_LiteralIncludeReader_dedent(literal_inc_path): # dedent: 2 options = {'lines': '9-11', 'dedent': 2} @@ -251,7 +236,6 @@ def test_LiteralIncludeReader_dedent(literal_inc_path): "\n") -@pytest.mark.xfail(os.name != 'posix', reason="Not working on windows") def test_LiteralIncludeReader_dedent_and_append_and_prepend(literal_inc_path): # dedent: 2 options = {'lines': '9-11', 'dedent': 2, 'prepend': 'class Foo:', 'append': '# comment'} @@ -264,7 +248,6 @@ def test_LiteralIncludeReader_dedent_and_append_and_prepend(literal_inc_path): "# comment\n") -@pytest.mark.xfail(os.name != 'posix', reason="Not working on windows") def test_LiteralIncludeReader_tabwidth(testroot): # tab-width: 4 options = {'tab-width': 4, 'pyobject': 'Qux'} @@ -283,7 +266,6 @@ def test_LiteralIncludeReader_tabwidth(testroot): " pass\n") -@pytest.mark.xfail(os.name != 'posix', reason="Not working on windows") def test_LiteralIncludeReader_tabwidth_dedent(testroot): options = {'tab-width': 4, 'dedent': 4, 'pyobject': 'Qux.quux'} reader = LiteralIncludeReader(testroot / 'target.py', options, DUMMY_CONFIG) @@ -292,13 +274,12 @@ def test_LiteralIncludeReader_tabwidth_dedent(testroot): " pass\n") -@pytest.mark.xfail(os.name != 'posix', reason="Not working on windows") def test_LiteralIncludeReader_diff(testroot, literal_inc_path): options = {'diff': testroot / 'literal-diff.inc'} reader = LiteralIncludeReader(literal_inc_path, options, DUMMY_CONFIG) content, lines = reader.read() - assert content == ("--- " + str(testroot) + "/literal-diff.inc\n" - "+++ " + str(testroot) + "/literal.inc\n" + assert content == ("--- " + os.path.join(testroot, 'literal-diff.inc') + "\n" + "+++ " + os.path.join(testroot, 'literal.inc') + "\n" "@@ -6,8 +6,8 @@\n" " pass\n" " \n" diff --git a/tests/test_ext_intersphinx.py b/tests/test_ext_intersphinx.py index ae869159a..0bb778436 100644 --- a/tests/test_ext_intersphinx.py +++ b/tests/test_ext_intersphinx.py @@ -352,7 +352,6 @@ def test_missing_reference_disabled_domain(tmp_path, app, status, warning): case(term=False, doc=False, py=False) -@pytest.mark.xfail(os.name != 'posix', reason="Path separator mismatch issue") def test_inventory_not_having_version(tmp_path, app, status, warning): inv_file = tmp_path / 'inventory' inv_file.write_bytes(inventory_v2_not_having_version) diff --git a/tests/test_intl.py b/tests/test_intl.py index a72492333..c02036af1 100644 --- a/tests/test_intl.py +++ b/tests/test_intl.py @@ -116,7 +116,6 @@ def test_text_warning_node(app): @sphinx_intl @pytest.mark.sphinx('text') @pytest.mark.test_params(shared_result='test_intl_basic') -@pytest.mark.xfail(os.name != 'posix', reason="Not working on windows") def test_text_title_underline(app): app.build() # --- simple translation; check title underlines @@ -1352,7 +1351,6 @@ SUBSTITUTED IMAGE [image: SUBST_EPILOG_2 TRANSLATED][image] HERE. srcdir='test_intl_images', confoverrides={'language': 'xx'}, ) -@pytest.mark.xfail(os.name != 'posix', reason="Not working on windows") def test_image_glob_intl(app): app.build() @@ -1399,7 +1397,6 @@ def test_image_glob_intl(app): 'figure_language_filename': '{root}{ext}.{language}', }, ) -@pytest.mark.xfail(os.name != 'posix', reason="Not working on windows") def test_image_glob_intl_using_figure_language_filename(app): app.build() diff --git a/tests/test_util_fileutil.py b/tests/test_util_fileutil.py index fea21309a..9c238214a 100644 --- a/tests/test_util_fileutil.py +++ b/tests/test_util_fileutil.py @@ -20,7 +20,7 @@ def test_copy_asset_file(tmp_path): # copy normal file src = (tmp_path / 'asset.txt') - src.write_text('# test data') + src.write_text('# test data', encoding='utf8') dest = (tmp_path / 'output.txt') copy_asset_file(src, dest) @@ -29,7 +29,7 @@ def test_copy_asset_file(tmp_path): # copy template file src = (tmp_path / 'asset.txt_t') - src.write_text('# {{var1}} data') + src.write_text('# {{var1}} data', encoding='utf8') dest = (tmp_path / 'output.txt_t') copy_asset_file(str(src), str(dest), {'var1': 'template'}, renderer) @@ -39,7 +39,7 @@ def test_copy_asset_file(tmp_path): # copy template file to subdir src = (tmp_path / 'asset.txt_t') - src.write_text('# {{var1}} data') + src.write_text('# {{var1}} data', encoding='utf8') subdir1 = (tmp_path / 'subdir') subdir1.mkdir(parents=True, exist_ok=True)