From bffd2963bbc9c321670eea659d30178000a7bae7 Mon Sep 17 00:00:00 2001 From: Daniele Nicolodi Date: Wed, 18 Dec 2024 13:38:09 +0100 Subject: [PATCH 1/2] Move build_archive() from test_sdist to common helpers module This will allow to reuse it in later commits. Change the function to return a pathlib.Path instead of a string because that is what is used internally and because it is easy to turn that into a string when needed, but it is a bit more cumbersome to do the opposite. --- tests/helpers.py | 29 +++++++++++++++++++++++++ tests/test_sdist.py | 53 +++++++++++---------------------------------- 2 files changed, 42 insertions(+), 40 deletions(-) diff --git a/tests/helpers.py b/tests/helpers.py index da28a336..b1ea8068 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -13,8 +13,12 @@ # limitations under the License. """Test functions useful across twine's tests.""" +import io import os import pathlib +import tarfile +import textwrap +import zipfile TESTS_DIR = pathlib.Path(__file__).parent FIXTURES_DIR = os.path.join(TESTS_DIR, "fixtures") @@ -22,3 +26,28 @@ WHEEL_FIXTURE = os.path.join(FIXTURES_DIR, "twine-1.5.0-py2.py3-none-any.whl") NEW_SDIST_FIXTURE = os.path.join(FIXTURES_DIR, "twine-1.6.5.tar.gz") NEW_WHEEL_FIXTURE = os.path.join(FIXTURES_DIR, "twine-1.6.5-py2.py3-none-any.whl") + + +def build_archive(path, name, archive_format, files): + filepath = path / f"{name}.{archive_format}" + + if archive_format == "tar.gz": + with tarfile.open(filepath, "x:gz") as archive: + for mname, content in files.items(): + if isinstance(content, tarfile.TarInfo): + content.name = mname + archive.addfile(content) + else: + data = textwrap.dedent(content).encode("utf8") + member = tarfile.TarInfo(mname) + member.size = len(data) + archive.addfile(member, io.BytesIO(data)) + return filepath + + if archive_format == "zip": + with zipfile.ZipFile(filepath, mode="w") as archive: + for mname, content in files.items(): + archive.writestr(mname, textwrap.dedent(content)) + return filepath + + raise ValueError(format) diff --git a/tests/test_sdist.py b/tests/test_sdist.py index eb50b979..a619dfef 100644 --- a/tests/test_sdist.py +++ b/tests/test_sdist.py @@ -1,9 +1,6 @@ -import io import os import pathlib import tarfile -import textwrap -import zipfile import pytest @@ -11,6 +8,7 @@ from twine import sdist from .helpers import TESTS_DIR +from .helpers import build_archive @pytest.fixture( @@ -29,31 +27,6 @@ def archive_format(request): return request.param -def build_archive(path, name, archive_format, files): - filepath = path / f"{name}.{archive_format}" - - if archive_format == "tar.gz": - with tarfile.open(filepath, "x:gz") as archive: - for mname, content in files.items(): - if isinstance(content, tarfile.TarInfo): - content.name = mname - archive.addfile(content) - else: - data = textwrap.dedent(content).encode("utf8") - member = tarfile.TarInfo(mname) - member.size = len(data) - archive.addfile(member, io.BytesIO(data)) - return str(filepath) - - if archive_format == "zip": - with zipfile.ZipFile(filepath, mode="w") as archive: - for mname, content in files.items(): - archive.writestr(mname, textwrap.dedent(content)) - return str(filepath) - - raise ValueError(format) - - def test_read_example(example_sdist): """Parse metadata from a valid sdist file.""" metadata = example_sdist.read() @@ -78,7 +51,7 @@ def test_formar_not_supported(): def test_read(archive_format, tmp_path): """Read PKG-INFO from a valid sdist.""" - filename = build_archive( + filepath = build_archive( tmp_path, "test-1.2.3", archive_format, @@ -92,7 +65,7 @@ def test_read(archive_format, tmp_path): }, ) - metadata = sdist.SDist(filename).read() + metadata = sdist.SDist(str(filepath)).read() assert b"Metadata-Version: 1.1" in metadata assert b"Name: test" in metadata assert b"Version: 1.2.3" in metadata @@ -100,7 +73,7 @@ def test_read(archive_format, tmp_path): def test_missing_pkg_info(archive_format, tmp_path): """Raise an exception when sdist does not contain PKG-INFO.""" - filename = build_archive( + filepath = build_archive( tmp_path, "test-1.2.3", archive_format, @@ -110,12 +83,12 @@ def test_missing_pkg_info(archive_format, tmp_path): ) with pytest.raises(exceptions.InvalidDistribution, match="No PKG-INFO in archive"): - sdist.SDist(filename).read() + sdist.SDist(str(filepath)).read() def test_invalid_pkg_info(archive_format, tmp_path): """Raise an exception when PKG-INFO does not contain ``Metadata-Version``.""" - filename = build_archive( + filepath = build_archive( tmp_path, "test-1.2.3", archive_format, @@ -129,12 +102,12 @@ def test_invalid_pkg_info(archive_format, tmp_path): ) with pytest.raises(exceptions.InvalidDistribution, match="No PKG-INFO in archive"): - sdist.SDist(filename).read() + sdist.SDist(str(filepath)).read() def test_pkg_info_directory(archive_format, tmp_path): """Raise an exception when PKG-INFO is a directory.""" - filename = build_archive( + filepath = build_archive( tmp_path, "test-1.2.3", archive_format, @@ -149,7 +122,7 @@ def test_pkg_info_directory(archive_format, tmp_path): ) with pytest.raises(exceptions.InvalidDistribution, match="No PKG-INFO in archive"): - sdist.SDist(filename).read() + sdist.SDist(str(filepath)).read() def test_pkg_info_not_regular_file(tmp_path): @@ -158,7 +131,7 @@ def test_pkg_info_not_regular_file(tmp_path): link.type = tarfile.LNKTYPE link.linkname = "README" - filename = build_archive( + filepath = build_archive( tmp_path, "test-1.2.3", "tar.gz", @@ -169,12 +142,12 @@ def test_pkg_info_not_regular_file(tmp_path): ) with pytest.raises(exceptions.InvalidDistribution, match="PKG-INFO is not a reg"): - sdist.SDist(filename).read() + sdist.SDist(str(filepath)).read() def test_multiple_top_level(archive_format, tmp_path): """Raise an exception when there are too many top-level members.""" - filename = build_archive( + filepath = build_archive( tmp_path, "test-1.2.3", archive_format, @@ -190,7 +163,7 @@ def test_multiple_top_level(archive_format, tmp_path): ) with pytest.raises(exceptions.InvalidDistribution, match="Too many top-level"): - sdist.SDist(filename).read() + sdist.SDist(str(filepath)).read() def test_py_version(example_sdist): From 2ca55db34c537bbcb00e157e407320c1e5f8f08b Mon Sep 17 00:00:00 2001 From: Daniele Nicolodi Date: Thu, 19 Dec 2024 15:56:56 +0100 Subject: [PATCH 2/2] Simplify generation of test packages used in test_check These tests used `build` to invoke the `setuptools.build_meta` build backend on a package directory generated on the fly. The tests are only interested in the content of the package metadata and use variations of `setup.cfg` to generate the desired metadata. This can be simplified to the direct generation of sdist archives with synthetic `PKG-INFO` metadata files. This makes the actual metadata the tests are checking obvious and avoid two test dependencies. The tests simplification highlighted that two tests are actually testing the exact same thing. Remove one. --- tests/test_check.py | 200 ++++++++++++++------------------------------ tox.ini | 5 -- 2 files changed, 62 insertions(+), 143 deletions(-) diff --git a/tests/test_check.py b/tests/test_check.py index ca62d2c1..74e0b1ce 100644 --- a/tests/test_check.py +++ b/tests/test_check.py @@ -12,9 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. import logging -import textwrap -import build import pretend import pytest @@ -50,45 +48,30 @@ def test_fails_no_distributions(caplog): ] -def build_package(src_path, project_files, distribution="sdist"): - """ - Build a source distribution similar to `python3 -m build --sdist`. - - Returns the absolute path of the built distribution. - """ - project_files = { - "pyproject.toml": ( - """ - [build-system] - requires = ["setuptools"] - build-backend = "setuptools.build_meta" - """ - ), - **project_files, - } - - for filename, content in project_files.items(): - (src_path / filename).write_text(textwrap.dedent(content)) - - builder = build.ProjectBuilder(src_path) - return builder.build(distribution, str(src_path / "dist")) +def build_sdist_with_metadata(path, metadata): + name = "test" + version = "1.2.3" + sdist = helpers.build_archive( + path, + f"{name}-{version}", + "tar.gz", + { + f"{name}-{version}/README": "README", + f"{name}-{version}/PKG-INFO": metadata, + }, + ) + return str(sdist) -@pytest.mark.parametrize("distribution", ["sdist", "wheel"]) @pytest.mark.parametrize("strict", [False, True]) -def test_warns_missing_description(distribution, strict, tmp_path, capsys, caplog): - sdist = build_package( +def test_warns_missing_description(strict, tmp_path, capsys, caplog): + sdist = build_sdist_with_metadata( tmp_path, - { - "setup.cfg": ( - """ - [metadata] - name = test-package - version = 0.0.1 - """ - ), - }, - distribution=distribution, + """\ + Metadata-Version: 2.1 + Name: test + Version: 1.2.3 + """, ) assert check.check([sdist], strict=strict) is strict @@ -111,54 +94,19 @@ def test_warns_missing_description(distribution, strict, tmp_path, capsys, caplo ] -def test_warns_missing_file(tmp_path, capsys, caplog): - sdist = build_package( +def test_fails_rst_syntax_error(tmp_path, capsys, caplog): + sdist = build_sdist_with_metadata( tmp_path, - { - "setup.cfg": ( - """ - [metadata] - name = test-package - version = 0.0.1 - long_description = file:README.rst - long_description_content_type = text/x-rst - """ - ), - }, - ) + """\ + Metadata-Version: 2.1 + Name: test-package + Version: 1.2.3 + Description-Content-Type: text/x-rst - assert not check.check([sdist]) - assert capsys.readouterr().out == f"Checking {sdist}: PASSED with warnings\n" + ============ - assert caplog.record_tuples == [ - ( - "twine.commands.check", - logging.WARNING, - "`long_description` missing.", - ), - ] - - -def test_fails_rst_syntax_error(tmp_path, capsys, caplog): - sdist = build_package( - tmp_path, - { - "setup.cfg": ( - """ - [metadata] - name = test-package - version = 0.0.1 - long_description = file:README.rst - long_description_content_type = text/x-rst - """ - ), - "README.rst": ( - """ - ============ - """ - ), - }, + """, ) assert check.check([sdist]) @@ -177,25 +125,17 @@ def test_fails_rst_syntax_error(tmp_path, capsys, caplog): def test_fails_rst_no_content(tmp_path, capsys, caplog): - sdist = build_package( + sdist = build_sdist_with_metadata( tmp_path, - { - "setup.cfg": ( - """ - [metadata] - name = test-package - version = 0.0.1 - long_description = file:README.rst - long_description_content_type = text/x-rst - """ - ), - "README.rst": ( - """ - test-package - ============ - """ - ), - }, + """\ + Metadata-Version: 2.1 + Name: test-package + Version: 1.2.3 + Description-Content-Type: text/x-rst + + test-package + ============ + """, ) assert check.check([sdist]) @@ -214,27 +154,19 @@ def test_fails_rst_no_content(tmp_path, capsys, caplog): def test_passes_rst_description(tmp_path, capsys, caplog): - sdist = build_package( + sdist = build_sdist_with_metadata( tmp_path, - { - "setup.cfg": ( - """ - [metadata] - name = test-package - version = 0.0.1 - long_description = file:README.rst - long_description_content_type = text/x-rst - """ - ), - "README.rst": ( - """ - test-package - ============ - - A test package. - """ - ), - }, + """\ + Metadata-Version: 2.1 + Name: test-package + Version: 1.2.3 + Description-Content-Type: text/x-rst + + test-package + ============ + + A test package. + """, ) assert not check.check([sdist]) @@ -246,26 +178,18 @@ def test_passes_rst_description(tmp_path, capsys, caplog): @pytest.mark.parametrize("content_type", ["text/markdown", "text/plain"]) def test_passes_markdown_description(content_type, tmp_path, capsys, caplog): - sdist = build_package( + sdist = build_sdist_with_metadata( tmp_path, - { - "setup.cfg": ( - f""" - [metadata] - name = test-package - version = 0.0.1 - long_description = file:README.md - long_description_content_type = {content_type} - """ - ), - "README.md": ( - """ - # test-package - - A test package. - """ - ), - }, + f"""\ + Metadata-Version: 2.1 + Name: test-package + Version: 1.2.3 + Description-Content-Type: {content_type} + + # test-package + + A test package. + """, ) assert not check.check([sdist]) diff --git a/tox.ini b/tox.ini index e5c71aab..bf36c319 100644 --- a/tox.ini +++ b/tox.ini @@ -8,12 +8,7 @@ deps = pretend pytest pytest-socket - build coverage - # Needed on 3.12 and newer due to setuptools not being pre-installed - # in fresh venvs. - # See: https://github.com/python/cpython/issues/95299 - setuptools passenv = PYTEST_ADDOPTS setenv =