blob: e9d0e5ea52d56fad7fb8c5cc772f541dcbcf3277 [file]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""Exercise actual distributions without importing runtime/native dependencies.
Run with ``python dev/test_packaging.py`` after installing the build tools.
The same suite runs on Python 3.6 and modern Python in CI.
"""
import email
import os
from pathlib import Path
import runpy
import shutil
import subprocess
import sys
import tarfile
import tempfile
import unittest
import zipfile
from packaging.markers import default_environment
from packaging.requirements import Requirement
from packaging.utils import canonicalize_name
from packaging.version import Version
try:
import tomllib
except ImportError:
import tomli as tomllib
def command(args, cwd, env=None):
result = subprocess.run(args, cwd=str(cwd), env=env,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if result.returncode:
raise AssertionError(result.stdout.decode("utf-8", errors="replace")[-8000:])
return result.stdout.decode("utf-8")
def wheel_contents(path):
with zipfile.ZipFile(str(path)) as archive:
names = archive.namelist()
metadata = email.message_from_bytes(archive.read(next(
name for name in names if name.endswith(".dist-info/METADATA"))))
full_version = archive.read("pypaimon/_full_version").decode().strip()
return names, metadata, full_version
class PackagingTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.temp = tempfile.TemporaryDirectory(prefix="pypaimon-packaging-")
cls.root = Path(cls.temp.name)
cls.source = cls.root / "source"
project = Path(__file__).resolve().parent.parent
shutil.copytree(str(project), str(cls.source), ignore=shutil.ignore_patterns(
"build", "dist", "*.egg-info", "__pycache__", ".pytest_cache", ".venv"))
cls.env = os.environ.copy()
for key in ("GIT_DIR", "GIT_WORK_TREE", "PYPAIMON_BUILD_VERSION"):
cls.env.pop(key, None)
cls.env.update(GIT_AUTHOR_DATE="2020-01-02T03:04:05+00:00",
GIT_COMMITTER_DATE="2020-01-02T03:04:05+00:00")
cls.commit = cls.init_git(cls.source)
base = runpy.run_path(str(cls.source / "pypaimon/_version.py"))["VERSION"]
cls.version = str(Version(base + "20200102" if base.endswith(".dev") else base))
with (cls.source / "pyproject.toml").open("rb") as config:
cls.config = tomllib.load(config)
cls.dist = cls.root / "custom-output"
cls.build(cls.source, cls.dist)
cls.wheel = next(cls.dist.glob("*.whl"))
cls.sdist = next(cls.dist.glob("*.tar.gz"))
@classmethod
def tearDownClass(cls):
cls.temp.cleanup()
@classmethod
def init_git(cls, root):
for args in (["init", "-q"], ["config", "user.name", "Packaging Test"],
["config", "user.email", "test@example.com"], ["add", "."],
["commit", "-qm", "test source"]):
command(["git"] + args, root, cls.env)
return command(["git", "rev-parse", "HEAD"], root, cls.env).strip()
@classmethod
def build(cls, source, output, extra=(), env=None):
command([sys.executable, "-m", "build", "--no-isolation", "--outdir",
str(output)] + list(extra), source, env or cls.env)
def test_sdist_and_wheel_share_version_and_commit(self):
self.assertEqual(2, len(list(self.dist.iterdir())))
self.assertEqual("pypaimon-{}.tar.gz".format(self.version), self.sdist.name)
self.assertEqual("pypaimon-{}-py3-none-any.whl".format(self.version), self.wheel.name)
_, metadata, full_version = wheel_contents(self.wheel)
self.assertEqual(self.version, metadata["Version"])
self.assertEqual("python-{}-{}".format(self.version, self.commit), full_version)
with tarfile.open(str(self.sdist)) as archive:
prefix = "pypaimon-{}/".format(self.version)
self.assertEqual(full_version, archive.extractfile(
prefix + "pypaimon/_full_version").read().decode().strip())
def test_distribution_contents(self):
names, metadata, _ = wheel_contents(self.wheel)
self.assertEqual(">=3.6", metadata["Requires-Python"])
self.assertFalse(any(name.startswith(("pypaimon/tests/", "pypaimon/acceptance/"))
for name in names))
self.assertFalse(any(name.endswith((".so", ".pyd", ".jar")) for name in names))
for name in ("pypaimon/sample/data/data.jsonl", "pypaimon/_version.py",
"pypaimon/benchmark/act/default_experiment.json"):
self.assertIn(name, names)
for suffix in ("/LICENSE", "/NOTICE"):
self.assertTrue(any(".dist-info/" in name and name.endswith(suffix)
for name in names))
with tarfile.open(str(self.sdist)) as archive:
names = archive.getnames()
prefix = "pypaimon-{}/".format(self.version)
for name in ("pyproject.toml", "setup.py", "LICENSE", "NOTICE", "README.md"):
self.assertIn(prefix + name, names)
self.assertFalse(any("requirements.txt" in name or "requirements-dev.txt" in name
or name.startswith((prefix + "pypaimon/tests/",
prefix + "pypaimon/acceptance/"))
for name in names))
@staticmethod
def active_requirements(values, environment):
result = set()
for value in values:
requirement = Requirement(value)
if requirement.marker is None or requirement.marker.evaluate(environment):
result.add((canonicalize_name(requirement.name), str(requirement.specifier)))
return result
def test_metadata_preserves_core_and_extra_conditions(self):
_, metadata, _ = wheel_contents(self.wheel)
project = self.config["project"]
self.assertEqual(set(project["optional-dependencies"]),
set(metadata.get_all("Provides-Extra")))
for python_version in ("3.6", "3.7", "3.8", "3.11", "3.13"):
for system in ("Linux", "Windows"):
for extra in [""] + list(project["optional-dependencies"]):
with self.subTest(python=python_version, system=system, extra=extra):
environment = default_environment()
environment.update(python_version=python_version,
python_full_version=python_version + ".0",
platform_system=system, extra=extra)
expected = project["dependencies"] + project[
"optional-dependencies"].get(extra, [])
self.assertEqual(self.active_requirements(expected, environment),
self.active_requirements(
metadata.get_all("Requires-Dist"), environment))
def test_dev_group_reuses_product_extras(self):
resolve = runpy.run_path(str(self.source / "dev/install.py"))["resolve_group"]
requirements = [Requirement(value) for value in resolve(
self.config["dependency-groups"], "dev")]
project = next(requirement for requirement in requirements if requirement.name == "pypaimon")
self.assertTrue({"ray", "sql", "vindex", "full-text"}.issubset(project.extras))
# Product constraints belong to extras, not a second developer copy.
names = {requirement.name for requirement in requirements}
self.assertFalse(names.intersection({"datafusion", "paimon-vindex", "requests", "h5py"}))
def test_sdist_rebuild_preserves_provenance_in_another_git_repo(self):
outer = self.root / "downstream"
outer.mkdir()
(outer / "README").write_text("Unrelated downstream repository")
downstream_commit = self.init_git(outer)
self.assertNotEqual(self.commit, downstream_commit)
with tarfile.open(str(self.sdist)) as archive:
# This archive was just built from our test source.
archive.extractall(str(outer))
output = self.root / "rebuilt"
self.build(outer / "pypaimon-{}".format(self.version), output, extra=("--wheel",))
_, metadata, full_version = wheel_contents(next(output.glob("*.whl")))
self.assertEqual(self.version, metadata["Version"])
self.assertEqual("python-{}-{}".format(self.version, self.commit), full_version)
def test_release_override_applies_to_both_formats(self):
env = dict(self.env, PYPAIMON_BUILD_VERSION="2.2rc1")
output = self.root / "release-candidate"
self.build(self.source, output, env=env)
self.assertEqual({"pypaimon-2.2rc1.tar.gz", "pypaimon-2.2rc1-py3-none-any.whl"},
{path.name for path in output.iterdir()})
_, metadata, full_version = wheel_contents(next(output.glob("*.whl")))
self.assertEqual("2.2rc1", metadata["Version"])
self.assertEqual("python-2.2rc1-{}".format(self.commit), full_version)
self.assertEqual("", command(["git", "diff", "--", "pypaimon/_version.py"],
self.source, self.env))
def test_invalid_release_override_fails(self):
env = dict(self.env, PYPAIMON_BUILD_VERSION="not a version")
result = subprocess.run([sys.executable, "-m", "build", "--no-isolation", "--sdist"],
cwd=str(self.source), env=env,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
self.assertNotEqual(0, result.returncode)
if __name__ == "__main__":
unittest.main(verbosity=2)