summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-29 04:26:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-29 04:26:28 +0000
commitbbbeb2d07d4f7fd0191032c219b40565fd83454f (patch)
tree3c08f1e09ed89a004867762ab40f3b610f6c0fa1 /tests
parentInitial commit. (diff)
downloadflit-bbbeb2d07d4f7fd0191032c219b40565fd83454f.tar.xz
flit-bbbeb2d07d4f7fd0191032c219b40565fd83454f.zip
Adding upstream version 3.8.0.upstream/3.8.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tests')
-rw-r--r--tests/__init__.py0
-rw-r--r--tests/conftest.py15
-rw-r--r--tests/samples/EG_README.rst4
-rw-r--r--tests/samples/altdistname/package1/__init__.py6
-rw-r--r--tests/samples/altdistname/package1/data_dir/foo.sh2
-rw-r--r--tests/samples/altdistname/package1/foo.py1
-rw-r--r--tests/samples/altdistname/package1/subpkg/__init__.py0
-rw-r--r--tests/samples/altdistname/package1/subpkg/sp_data_dir/test.json1
-rw-r--r--tests/samples/altdistname/package1/subpkg2/__init__.py0
-rw-r--r--tests/samples/altdistname/pyproject.toml11
-rw-r--r--tests/samples/bad-description-ext.toml9
-rw-r--r--tests/samples/entrypoints_conflict/console_entry_points.txt2
-rw-r--r--tests/samples/entrypoints_conflict/package1/__init__.py6
-rw-r--r--tests/samples/entrypoints_conflict/package1/data_dir/foo.sh2
-rw-r--r--tests/samples/entrypoints_conflict/package1/foo.py1
-rw-r--r--tests/samples/entrypoints_conflict/package1/subpkg/__init__.py0
-rw-r--r--tests/samples/entrypoints_conflict/package1/subpkg/sp_data_dir/test.json1
-rw-r--r--tests/samples/entrypoints_conflict/package1/subpkg2/__init__.py0
-rw-r--r--tests/samples/entrypoints_conflict/pyproject.toml16
-rw-r--r--tests/samples/entrypoints_valid/package1/__init__.py6
-rw-r--r--tests/samples/entrypoints_valid/package1/data_dir/foo.sh2
-rw-r--r--tests/samples/entrypoints_valid/package1/foo.py1
-rw-r--r--tests/samples/entrypoints_valid/package1/subpkg/__init__.py0
-rw-r--r--tests/samples/entrypoints_valid/package1/subpkg/sp_data_dir/test.json1
-rw-r--r--tests/samples/entrypoints_valid/package1/subpkg2/__init__.py0
-rw-r--r--tests/samples/entrypoints_valid/pyproject.toml15
-rw-r--r--tests/samples/extras-dev-conflict.toml13
-rw-r--r--tests/samples/extras/module1.py3
-rw-r--r--tests/samples/extras/pyproject.toml13
-rw-r--r--tests/samples/invalid_classifier.toml14
-rw-r--r--tests/samples/invalid_version1.py3
-rw-r--r--tests/samples/missing-description-file.toml9
-rw-r--r--tests/samples/module1.py3
-rw-r--r--tests/samples/module1_ini/flit.ini5
-rw-r--r--tests/samples/module1_ini/module1.py3
-rw-r--r--tests/samples/module1_toml/EG_README.rst4
-rw-r--r--tests/samples/module1_toml/module1.py3
-rw-r--r--tests/samples/module1_toml/pyproject.toml12
-rw-r--r--tests/samples/module2.py5
-rw-r--r--tests/samples/module3/LICENSE1
-rw-r--r--tests/samples/module3/pyproject.toml10
-rw-r--r--tests/samples/module3/src/module3.py3
-rw-r--r--tests/samples/moduleunimportable.py8
-rw-r--r--tests/samples/modulewithconstructedversion.py4
-rw-r--r--tests/samples/modulewithlocalversion/modulewithlocalversion.py5
-rw-r--r--tests/samples/modulewithlocalversion/pyproject.toml10
-rw-r--r--tests/samples/my-description.rst1
-rw-r--r--tests/samples/no_docstring-pkg.toml12
-rw-r--r--tests/samples/no_docstring.py1
-rw-r--r--tests/samples/ns1-pkg-mod/ns1/module.py5
-rw-r--r--tests/samples/ns1-pkg-mod/pyproject.toml7
-rw-r--r--tests/samples/ns1-pkg/EG_README.rst4
-rw-r--r--tests/samples/ns1-pkg/ns1/pkg/__init__.py8
-rw-r--r--tests/samples/ns1-pkg/pyproject.toml10
-rw-r--r--tests/samples/ns1-pkg2/EG_README.rst4
-rw-r--r--tests/samples/ns1-pkg2/ns1/pkg2/__init__.py8
-rw-r--r--tests/samples/ns1-pkg2/pyproject.toml10
-rw-r--r--tests/samples/package1/my-description.rst1
-rw-r--r--tests/samples/package1/package1/__init__.py6
-rw-r--r--tests/samples/package1/package1/data_dir/foo.sh2
-rw-r--r--tests/samples/package1/package1/foo.py1
-rw-r--r--tests/samples/package1/package1/subpkg/__init__.py0
-rw-r--r--tests/samples/package1/package1/subpkg/sp_data_dir/test.json1
-rw-r--r--tests/samples/package1/package1/subpkg2/__init__.py0
-rw-r--r--tests/samples/package1/pyproject.toml13
-rw-r--r--tests/samples/package2/package2-pkg.ini8
-rw-r--r--tests/samples/package2/pyproject.toml12
-rw-r--r--tests/samples/package2/src/package2/__init__.py6
-rw-r--r--tests/samples/package2/src/package2/foo.py1
-rw-r--r--tests/samples/packageinsrc/pyproject.toml10
-rw-r--r--tests/samples/packageinsrc/src/module1.py3
-rw-r--r--tests/samples/pep517/module1.py3
-rw-r--r--tests/samples/pep517/pyproject.toml13
-rw-r--r--tests/samples/requires-dev.toml11
-rw-r--r--tests/samples/requires-envmark/module1.py3
-rw-r--r--tests/samples/requires-envmark/pyproject.toml12
-rw-r--r--tests/samples/requires-extra-envmark/module1.py3
-rw-r--r--tests/samples/requires-extra-envmark/pyproject.toml11
-rw-r--r--tests/samples/requires-requests.toml10
-rw-r--r--tests/samples/with_flit_ini/flit.ini9
-rw-r--r--tests/samples/with_flit_ini/package1/__init__.py6
-rw-r--r--tests/samples/with_flit_ini/package1/foo.py1
-rw-r--r--tests/samples/with_flit_ini/package1/subpkg/__init__.py0
-rw-r--r--tests/samples/with_flit_ini/some_entry_points.txt2
-rw-r--r--tests/test_build.py84
-rw-r--r--tests/test_command.py13
-rw-r--r--tests/test_config.py10
-rw-r--r--tests/test_find_python_executable.py30
-rw-r--r--tests/test_init.py255
-rw-r--r--tests/test_install.py365
-rw-r--r--tests/test_sdist.py152
-rw-r--r--tests/test_tomlify.py32
-rw-r--r--tests/test_upload.py168
-rw-r--r--tests/test_validate.py243
-rw-r--r--tests/test_vcs.py27
-rw-r--r--tests/test_wheel.py221
96 files changed, 2051 insertions, 0 deletions
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/__init__.py
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..4c5ecef
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,15 @@
+from pathlib import Path
+import pytest
+from shutil import copytree
+
+samples_dir = Path(__file__).parent / 'samples'
+
+@pytest.fixture
+def copy_sample(tmp_path):
+ """Copy a subdirectory from the samples dir to a temp dir"""
+ def copy(dirname):
+ dst = tmp_path / dirname
+ copytree(str(samples_dir / dirname), str(dst))
+ return dst
+
+ return copy
diff --git a/tests/samples/EG_README.rst b/tests/samples/EG_README.rst
new file mode 100644
index 0000000..a742974
--- /dev/null
+++ b/tests/samples/EG_README.rst
@@ -0,0 +1,4 @@
+This is an example long description for tests to load.
+
+This file is `valid reStructuredText
+<http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html>`_.
diff --git a/tests/samples/altdistname/package1/__init__.py b/tests/samples/altdistname/package1/__init__.py
new file mode 100644
index 0000000..07978d8
--- /dev/null
+++ b/tests/samples/altdistname/package1/__init__.py
@@ -0,0 +1,6 @@
+"""A sample package"""
+
+__version__ = '0.1'
+
+def main():
+ print("package1 main")
diff --git a/tests/samples/altdistname/package1/data_dir/foo.sh b/tests/samples/altdistname/package1/data_dir/foo.sh
new file mode 100644
index 0000000..92abcfb
--- /dev/null
+++ b/tests/samples/altdistname/package1/data_dir/foo.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+echo "Example data file"
diff --git a/tests/samples/altdistname/package1/foo.py b/tests/samples/altdistname/package1/foo.py
new file mode 100644
index 0000000..1337a53
--- /dev/null
+++ b/tests/samples/altdistname/package1/foo.py
@@ -0,0 +1 @@
+a = 1
diff --git a/tests/samples/altdistname/package1/subpkg/__init__.py b/tests/samples/altdistname/package1/subpkg/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/samples/altdistname/package1/subpkg/__init__.py
diff --git a/tests/samples/altdistname/package1/subpkg/sp_data_dir/test.json b/tests/samples/altdistname/package1/subpkg/sp_data_dir/test.json
new file mode 100644
index 0000000..f77d03c
--- /dev/null
+++ b/tests/samples/altdistname/package1/subpkg/sp_data_dir/test.json
@@ -0,0 +1 @@
+{"example": true}
diff --git a/tests/samples/altdistname/package1/subpkg2/__init__.py b/tests/samples/altdistname/package1/subpkg2/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/samples/altdistname/package1/subpkg2/__init__.py
diff --git a/tests/samples/altdistname/pyproject.toml b/tests/samples/altdistname/pyproject.toml
new file mode 100644
index 0000000..c2d08ca
--- /dev/null
+++ b/tests/samples/altdistname/pyproject.toml
@@ -0,0 +1,11 @@
+[build-system]
+requires = ["flit_core >=2,<4"]
+build-backend = "flit_core.buildapi"
+
+[tool.flit.metadata]
+module = "package1"
+author = "Sir Robin"
+author-email = "robin@camelot.uk"
+home-page = "http://github.com/sirrobin/package1"
+dist-name = "package-Dist1"
+
diff --git a/tests/samples/bad-description-ext.toml b/tests/samples/bad-description-ext.toml
new file mode 100644
index 0000000..1062829
--- /dev/null
+++ b/tests/samples/bad-description-ext.toml
@@ -0,0 +1,9 @@
+[build-system]
+requires = ["flit"]
+
+[tool.flit.metadata]
+module = "module1"
+author = "Sir Robin"
+author-email = "robin@camelot.uk"
+home-page = "http://github.com/sirrobin/module1"
+description-file = "module1.py" # WRONG
diff --git a/tests/samples/entrypoints_conflict/console_entry_points.txt b/tests/samples/entrypoints_conflict/console_entry_points.txt
new file mode 100644
index 0000000..eb47371
--- /dev/null
+++ b/tests/samples/entrypoints_conflict/console_entry_points.txt
@@ -0,0 +1,2 @@
+[console_scripts]
+foo=bar:baz \ No newline at end of file
diff --git a/tests/samples/entrypoints_conflict/package1/__init__.py b/tests/samples/entrypoints_conflict/package1/__init__.py
new file mode 100644
index 0000000..07978d8
--- /dev/null
+++ b/tests/samples/entrypoints_conflict/package1/__init__.py
@@ -0,0 +1,6 @@
+"""A sample package"""
+
+__version__ = '0.1'
+
+def main():
+ print("package1 main")
diff --git a/tests/samples/entrypoints_conflict/package1/data_dir/foo.sh b/tests/samples/entrypoints_conflict/package1/data_dir/foo.sh
new file mode 100644
index 0000000..92abcfb
--- /dev/null
+++ b/tests/samples/entrypoints_conflict/package1/data_dir/foo.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+echo "Example data file"
diff --git a/tests/samples/entrypoints_conflict/package1/foo.py b/tests/samples/entrypoints_conflict/package1/foo.py
new file mode 100644
index 0000000..1337a53
--- /dev/null
+++ b/tests/samples/entrypoints_conflict/package1/foo.py
@@ -0,0 +1 @@
+a = 1
diff --git a/tests/samples/entrypoints_conflict/package1/subpkg/__init__.py b/tests/samples/entrypoints_conflict/package1/subpkg/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/samples/entrypoints_conflict/package1/subpkg/__init__.py
diff --git a/tests/samples/entrypoints_conflict/package1/subpkg/sp_data_dir/test.json b/tests/samples/entrypoints_conflict/package1/subpkg/sp_data_dir/test.json
new file mode 100644
index 0000000..f77d03c
--- /dev/null
+++ b/tests/samples/entrypoints_conflict/package1/subpkg/sp_data_dir/test.json
@@ -0,0 +1 @@
+{"example": true}
diff --git a/tests/samples/entrypoints_conflict/package1/subpkg2/__init__.py b/tests/samples/entrypoints_conflict/package1/subpkg2/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/samples/entrypoints_conflict/package1/subpkg2/__init__.py
diff --git a/tests/samples/entrypoints_conflict/pyproject.toml b/tests/samples/entrypoints_conflict/pyproject.toml
new file mode 100644
index 0000000..506a4eb
--- /dev/null
+++ b/tests/samples/entrypoints_conflict/pyproject.toml
@@ -0,0 +1,16 @@
+[build-system]
+requires = ["flit_core >=2,<4"]
+build-backend = "flit_core.buildapi"
+
+[tool.flit.metadata]
+module = "package1"
+author = "Sir Robin"
+author-email = "robin@camelot.uk"
+home-page = "http://github.com/sirrobin/package1"
+
+# The sections below conflict
+[tool.flit.scripts]
+pkg_script = "package1:main"
+
+[tool.flit.entrypoints.console_scripts]
+foo = "bar:baz"
diff --git a/tests/samples/entrypoints_valid/package1/__init__.py b/tests/samples/entrypoints_valid/package1/__init__.py
new file mode 100644
index 0000000..07978d8
--- /dev/null
+++ b/tests/samples/entrypoints_valid/package1/__init__.py
@@ -0,0 +1,6 @@
+"""A sample package"""
+
+__version__ = '0.1'
+
+def main():
+ print("package1 main")
diff --git a/tests/samples/entrypoints_valid/package1/data_dir/foo.sh b/tests/samples/entrypoints_valid/package1/data_dir/foo.sh
new file mode 100644
index 0000000..92abcfb
--- /dev/null
+++ b/tests/samples/entrypoints_valid/package1/data_dir/foo.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+echo "Example data file"
diff --git a/tests/samples/entrypoints_valid/package1/foo.py b/tests/samples/entrypoints_valid/package1/foo.py
new file mode 100644
index 0000000..1337a53
--- /dev/null
+++ b/tests/samples/entrypoints_valid/package1/foo.py
@@ -0,0 +1 @@
+a = 1
diff --git a/tests/samples/entrypoints_valid/package1/subpkg/__init__.py b/tests/samples/entrypoints_valid/package1/subpkg/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/samples/entrypoints_valid/package1/subpkg/__init__.py
diff --git a/tests/samples/entrypoints_valid/package1/subpkg/sp_data_dir/test.json b/tests/samples/entrypoints_valid/package1/subpkg/sp_data_dir/test.json
new file mode 100644
index 0000000..f77d03c
--- /dev/null
+++ b/tests/samples/entrypoints_valid/package1/subpkg/sp_data_dir/test.json
@@ -0,0 +1 @@
+{"example": true}
diff --git a/tests/samples/entrypoints_valid/package1/subpkg2/__init__.py b/tests/samples/entrypoints_valid/package1/subpkg2/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/samples/entrypoints_valid/package1/subpkg2/__init__.py
diff --git a/tests/samples/entrypoints_valid/pyproject.toml b/tests/samples/entrypoints_valid/pyproject.toml
new file mode 100644
index 0000000..d89da6a
--- /dev/null
+++ b/tests/samples/entrypoints_valid/pyproject.toml
@@ -0,0 +1,15 @@
+[build-system]
+requires = ["flit_core >=2,<4"]
+build-backend = "flit_core.buildapi"
+
+[tool.flit.metadata]
+module = "package1"
+author = "Sir Robin"
+author-email = "robin@camelot.uk"
+home-page = "http://github.com/sirrobin/package1"
+
+[tool.flit.scripts]
+pkg_script = "package1:main"
+
+[tool.flit.entrypoints.myplugins]
+package1 = "package1:main"
diff --git a/tests/samples/extras-dev-conflict.toml b/tests/samples/extras-dev-conflict.toml
new file mode 100644
index 0000000..0fe249d
--- /dev/null
+++ b/tests/samples/extras-dev-conflict.toml
@@ -0,0 +1,13 @@
+[build-system]
+requires = ["flit"]
+
+[tool.flit.metadata]
+module = "module1"
+author = "Sir Robin"
+author-email = "robin@camelot.uk"
+home-page = "http://github.com/sirrobin/module1"
+description-file = "EG_README.rst"
+dev-requires = ["apackage"]
+
+[tool.flit.metadata.requires-extra]
+dev = ["anotherpackage"]
diff --git a/tests/samples/extras/module1.py b/tests/samples/extras/module1.py
new file mode 100644
index 0000000..87f0370
--- /dev/null
+++ b/tests/samples/extras/module1.py
@@ -0,0 +1,3 @@
+"""Example module"""
+
+__version__ = '0.1'
diff --git a/tests/samples/extras/pyproject.toml b/tests/samples/extras/pyproject.toml
new file mode 100644
index 0000000..557ba2a
--- /dev/null
+++ b/tests/samples/extras/pyproject.toml
@@ -0,0 +1,13 @@
+[build-system]
+requires = ["flit"]
+
+[tool.flit.metadata]
+module = "module1"
+author = "Sir Robin"
+author-email = "robin@camelot.uk"
+home-page = "http://github.com/sirrobin/module1"
+requires = ["toml"]
+
+[tool.flit.metadata.requires-extra]
+test = ["pytest"]
+custom = ["requests"]
diff --git a/tests/samples/invalid_classifier.toml b/tests/samples/invalid_classifier.toml
new file mode 100644
index 0000000..931d72f
--- /dev/null
+++ b/tests/samples/invalid_classifier.toml
@@ -0,0 +1,14 @@
+[build-system]
+requires = ["flit_core >=2,<4"]
+build-backend = "flit_core.buildapi"
+
+[tool.flit.metadata]
+module = "package1"
+author = "Sir Robin"
+author-email = "robin@camelot.uk"
+description-file = "my-description.rst"
+home-page = "http://github.com/sirrobin/package1"
+classifiers = [
+ "License :: OSI Approved :: BSD License",
+ "Intended Audience :: Pacman",
+]
diff --git a/tests/samples/invalid_version1.py b/tests/samples/invalid_version1.py
new file mode 100644
index 0000000..dd3268a
--- /dev/null
+++ b/tests/samples/invalid_version1.py
@@ -0,0 +1,3 @@
+"""Sample module with invalid __version__ string"""
+
+__version__ = "not starting with a number" \ No newline at end of file
diff --git a/tests/samples/missing-description-file.toml b/tests/samples/missing-description-file.toml
new file mode 100644
index 0000000..00fae72
--- /dev/null
+++ b/tests/samples/missing-description-file.toml
@@ -0,0 +1,9 @@
+[build-system]
+requires = ["flit"]
+
+[tool.flit.metadata]
+module = "missingdescriptionfile"
+author = "Sir Robin"
+author-email = "robin@camelot.uk"
+home-page = "http://github.com/sirrobin/missingdescriptionfile"
+description-file = "definitely-missing.rst"
diff --git a/tests/samples/module1.py b/tests/samples/module1.py
new file mode 100644
index 0000000..87f0370
--- /dev/null
+++ b/tests/samples/module1.py
@@ -0,0 +1,3 @@
+"""Example module"""
+
+__version__ = '0.1'
diff --git a/tests/samples/module1_ini/flit.ini b/tests/samples/module1_ini/flit.ini
new file mode 100644
index 0000000..9bbfc4e
--- /dev/null
+++ b/tests/samples/module1_ini/flit.ini
@@ -0,0 +1,5 @@
+[metadata]
+module=module1
+author=Sir Robin
+author-email=robin@camelot.uk
+home-page=http://github.com/sirrobin/module1
diff --git a/tests/samples/module1_ini/module1.py b/tests/samples/module1_ini/module1.py
new file mode 100644
index 0000000..87f0370
--- /dev/null
+++ b/tests/samples/module1_ini/module1.py
@@ -0,0 +1,3 @@
+"""Example module"""
+
+__version__ = '0.1'
diff --git a/tests/samples/module1_toml/EG_README.rst b/tests/samples/module1_toml/EG_README.rst
new file mode 100644
index 0000000..a742974
--- /dev/null
+++ b/tests/samples/module1_toml/EG_README.rst
@@ -0,0 +1,4 @@
+This is an example long description for tests to load.
+
+This file is `valid reStructuredText
+<http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html>`_.
diff --git a/tests/samples/module1_toml/module1.py b/tests/samples/module1_toml/module1.py
new file mode 100644
index 0000000..87f0370
--- /dev/null
+++ b/tests/samples/module1_toml/module1.py
@@ -0,0 +1,3 @@
+"""Example module"""
+
+__version__ = '0.1'
diff --git a/tests/samples/module1_toml/pyproject.toml b/tests/samples/module1_toml/pyproject.toml
new file mode 100644
index 0000000..740ec87
--- /dev/null
+++ b/tests/samples/module1_toml/pyproject.toml
@@ -0,0 +1,12 @@
+[build-system]
+requires = ["flit"]
+
+[tool.flit.metadata]
+module = "module1"
+author = "Sir Robin"
+author-email = "robin@camelot.uk"
+home-page = "http://github.com/sirrobin/module1"
+description-file = "EG_README.rst"
+
+[tool.flit.metadata.urls]
+Documentation = "https://example.com/module1"
diff --git a/tests/samples/module2.py b/tests/samples/module2.py
new file mode 100644
index 0000000..cc83e39
--- /dev/null
+++ b/tests/samples/module2.py
@@ -0,0 +1,5 @@
+"""
+Docstring formatted like this.
+"""
+
+__version__ = '7.0'
diff --git a/tests/samples/module3/LICENSE b/tests/samples/module3/LICENSE
new file mode 100644
index 0000000..dfd033f
--- /dev/null
+++ b/tests/samples/module3/LICENSE
@@ -0,0 +1 @@
+Dummy license - check that it gets packaged
diff --git a/tests/samples/module3/pyproject.toml b/tests/samples/module3/pyproject.toml
new file mode 100644
index 0000000..95d8a80
--- /dev/null
+++ b/tests/samples/module3/pyproject.toml
@@ -0,0 +1,10 @@
+[build-system]
+requires = ["flit_core >=2,<4"]
+build-backend = "flit_core.buildapi"
+
+[tool.flit.metadata]
+module = "module3"
+author = "Sir Robin"
+author-email = "robin@camelot.uk"
+home-page = "http://github.com/sirrobin/module3"
+
diff --git a/tests/samples/module3/src/module3.py b/tests/samples/module3/src/module3.py
new file mode 100644
index 0000000..87f0370
--- /dev/null
+++ b/tests/samples/module3/src/module3.py
@@ -0,0 +1,3 @@
+"""Example module"""
+
+__version__ = '0.1'
diff --git a/tests/samples/moduleunimportable.py b/tests/samples/moduleunimportable.py
new file mode 100644
index 0000000..147d26e
--- /dev/null
+++ b/tests/samples/moduleunimportable.py
@@ -0,0 +1,8 @@
+
+"""
+A sample unimportable module
+"""
+
+raise ImportError()
+
+__version__ = "0.1"
diff --git a/tests/samples/modulewithconstructedversion.py b/tests/samples/modulewithconstructedversion.py
new file mode 100644
index 0000000..5d9ec93
--- /dev/null
+++ b/tests/samples/modulewithconstructedversion.py
@@ -0,0 +1,4 @@
+
+"""This module has a __version__ that requires runtime interpretation"""
+
+__version__ = ".".join(["1", "2", "3"])
diff --git a/tests/samples/modulewithlocalversion/modulewithlocalversion.py b/tests/samples/modulewithlocalversion/modulewithlocalversion.py
new file mode 100644
index 0000000..4d11be9
--- /dev/null
+++ b/tests/samples/modulewithlocalversion/modulewithlocalversion.py
@@ -0,0 +1,5 @@
+"""
+A module with a local version specifier
+"""
+
+__version__ = "0.1.dev0+test"
diff --git a/tests/samples/modulewithlocalversion/pyproject.toml b/tests/samples/modulewithlocalversion/pyproject.toml
new file mode 100644
index 0000000..bb80669
--- /dev/null
+++ b/tests/samples/modulewithlocalversion/pyproject.toml
@@ -0,0 +1,10 @@
+[build-system]
+requires = ["flit_core >=2,<4"]
+build-backend = "flit_core.buildapi"
+
+[tool.flit.metadata]
+module = "modulewithlocalversion"
+author = "Sir Robin"
+author-email = "robin@camelot.uk"
+home-page = "http://github.com/sirrobin/modulewithlocalversion"
+
diff --git a/tests/samples/my-description.rst b/tests/samples/my-description.rst
new file mode 100644
index 0000000..623cb1d
--- /dev/null
+++ b/tests/samples/my-description.rst
@@ -0,0 +1 @@
+Sample description for test.
diff --git a/tests/samples/no_docstring-pkg.toml b/tests/samples/no_docstring-pkg.toml
new file mode 100644
index 0000000..b68827f
--- /dev/null
+++ b/tests/samples/no_docstring-pkg.toml
@@ -0,0 +1,12 @@
+[build-system]
+requires = ["flit"]
+
+[tool.flit.metadata]
+module = "no_docstring"
+author = "Sir Robin"
+author-email = "robin@camelot.uk"
+home-page = "http://github.com/sirrobin/no_docstring"
+description-file = "EG_README.rst"
+
+[tool.flit.metadata.urls]
+Documentation = "https://example.com/no_docstring"
diff --git a/tests/samples/no_docstring.py b/tests/samples/no_docstring.py
new file mode 100644
index 0000000..29524eb
--- /dev/null
+++ b/tests/samples/no_docstring.py
@@ -0,0 +1 @@
+__version__ = '7.0'
diff --git a/tests/samples/ns1-pkg-mod/ns1/module.py b/tests/samples/ns1-pkg-mod/ns1/module.py
new file mode 100644
index 0000000..4e02147
--- /dev/null
+++ b/tests/samples/ns1-pkg-mod/ns1/module.py
@@ -0,0 +1,5 @@
+"""An example single file module in a namespace package
+"""
+
+__version__ = '0.1'
+
diff --git a/tests/samples/ns1-pkg-mod/pyproject.toml b/tests/samples/ns1-pkg-mod/pyproject.toml
new file mode 100644
index 0000000..215732a
--- /dev/null
+++ b/tests/samples/ns1-pkg-mod/pyproject.toml
@@ -0,0 +1,7 @@
+[build-system]
+requires = ["flit_core >=3.5,<4"]
+build-backend = "flit_core.buildapi"
+
+[project]
+name = "ns1.module"
+dynamic = ["version", "description"]
diff --git a/tests/samples/ns1-pkg/EG_README.rst b/tests/samples/ns1-pkg/EG_README.rst
new file mode 100644
index 0000000..a742974
--- /dev/null
+++ b/tests/samples/ns1-pkg/EG_README.rst
@@ -0,0 +1,4 @@
+This is an example long description for tests to load.
+
+This file is `valid reStructuredText
+<http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html>`_.
diff --git a/tests/samples/ns1-pkg/ns1/pkg/__init__.py b/tests/samples/ns1-pkg/ns1/pkg/__init__.py
new file mode 100644
index 0000000..445afbb
--- /dev/null
+++ b/tests/samples/ns1-pkg/ns1/pkg/__init__.py
@@ -0,0 +1,8 @@
+"""
+==================
+ns1.pkg
+==================
+"""
+
+__version__ = '0.1'
+
diff --git a/tests/samples/ns1-pkg/pyproject.toml b/tests/samples/ns1-pkg/pyproject.toml
new file mode 100644
index 0000000..acbabb1
--- /dev/null
+++ b/tests/samples/ns1-pkg/pyproject.toml
@@ -0,0 +1,10 @@
+[build-system]
+requires = ["flit_core >=3.5,<4"]
+build-backend = "flit_core.buildapi"
+
+[tool.flit.metadata]
+module = "ns1.pkg"
+author = "Sir Robin"
+author-email = "robin@camelot.uk"
+home-page = "http://github.com/sirrobin/module1"
+description-file = "EG_README.rst"
diff --git a/tests/samples/ns1-pkg2/EG_README.rst b/tests/samples/ns1-pkg2/EG_README.rst
new file mode 100644
index 0000000..a742974
--- /dev/null
+++ b/tests/samples/ns1-pkg2/EG_README.rst
@@ -0,0 +1,4 @@
+This is an example long description for tests to load.
+
+This file is `valid reStructuredText
+<http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html>`_.
diff --git a/tests/samples/ns1-pkg2/ns1/pkg2/__init__.py b/tests/samples/ns1-pkg2/ns1/pkg2/__init__.py
new file mode 100644
index 0000000..dbe87a4
--- /dev/null
+++ b/tests/samples/ns1-pkg2/ns1/pkg2/__init__.py
@@ -0,0 +1,8 @@
+"""
+==================
+ns1.pkg2
+==================
+"""
+
+__version__ = '0.1'
+
diff --git a/tests/samples/ns1-pkg2/pyproject.toml b/tests/samples/ns1-pkg2/pyproject.toml
new file mode 100644
index 0000000..d792a97
--- /dev/null
+++ b/tests/samples/ns1-pkg2/pyproject.toml
@@ -0,0 +1,10 @@
+[build-system]
+requires = ["flit_core >=3.5,<4"]
+build-backend = "flit_core.buildapi"
+
+[tool.flit.metadata]
+module = "ns1.pkg2"
+author = "Sir Robin"
+author-email = "robin@camelot.uk"
+home-page = "http://github.com/sirrobin/module1"
+description-file = "EG_README.rst"
diff --git a/tests/samples/package1/my-description.rst b/tests/samples/package1/my-description.rst
new file mode 100644
index 0000000..623cb1d
--- /dev/null
+++ b/tests/samples/package1/my-description.rst
@@ -0,0 +1 @@
+Sample description for test.
diff --git a/tests/samples/package1/package1/__init__.py b/tests/samples/package1/package1/__init__.py
new file mode 100644
index 0000000..07978d8
--- /dev/null
+++ b/tests/samples/package1/package1/__init__.py
@@ -0,0 +1,6 @@
+"""A sample package"""
+
+__version__ = '0.1'
+
+def main():
+ print("package1 main")
diff --git a/tests/samples/package1/package1/data_dir/foo.sh b/tests/samples/package1/package1/data_dir/foo.sh
new file mode 100644
index 0000000..92abcfb
--- /dev/null
+++ b/tests/samples/package1/package1/data_dir/foo.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+echo "Example data file"
diff --git a/tests/samples/package1/package1/foo.py b/tests/samples/package1/package1/foo.py
new file mode 100644
index 0000000..1337a53
--- /dev/null
+++ b/tests/samples/package1/package1/foo.py
@@ -0,0 +1 @@
+a = 1
diff --git a/tests/samples/package1/package1/subpkg/__init__.py b/tests/samples/package1/package1/subpkg/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/samples/package1/package1/subpkg/__init__.py
diff --git a/tests/samples/package1/package1/subpkg/sp_data_dir/test.json b/tests/samples/package1/package1/subpkg/sp_data_dir/test.json
new file mode 100644
index 0000000..f77d03c
--- /dev/null
+++ b/tests/samples/package1/package1/subpkg/sp_data_dir/test.json
@@ -0,0 +1 @@
+{"example": true}
diff --git a/tests/samples/package1/package1/subpkg2/__init__.py b/tests/samples/package1/package1/subpkg2/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/samples/package1/package1/subpkg2/__init__.py
diff --git a/tests/samples/package1/pyproject.toml b/tests/samples/package1/pyproject.toml
new file mode 100644
index 0000000..c4c4130
--- /dev/null
+++ b/tests/samples/package1/pyproject.toml
@@ -0,0 +1,13 @@
+[build-system]
+requires = ["flit_core >=2,<4"]
+build-backend = "flit_core.buildapi"
+
+[tool.flit.metadata]
+module = "package1"
+author = "Sir Robin"
+author-email = "robin@camelot.uk"
+description-file = "my-description.rst"
+home-page = "http://github.com/sirrobin/package1"
+
+[tool.flit.scripts]
+pkg_script = "package1:main"
diff --git a/tests/samples/package2/package2-pkg.ini b/tests/samples/package2/package2-pkg.ini
new file mode 100644
index 0000000..3b0864d
--- /dev/null
+++ b/tests/samples/package2/package2-pkg.ini
@@ -0,0 +1,8 @@
+[metadata]
+module=package2
+author=Sir Robin
+author-email=robin@camelot.uk
+home-page=http://github.com/sirrobin/package2
+
+[scripts]
+pkg_script=package2:main
diff --git a/tests/samples/package2/pyproject.toml b/tests/samples/package2/pyproject.toml
new file mode 100644
index 0000000..6119bbb
--- /dev/null
+++ b/tests/samples/package2/pyproject.toml
@@ -0,0 +1,12 @@
+[build-system]
+requires = ["flit_core >=2,<4"]
+build-backend = "flit_core.buildapi"
+
+[tool.flit.metadata]
+module = "package2"
+author = "Sir Robin"
+author-email = "robin@camelot.uk"
+home-page = "http://github.com/sirrobin/package2"
+
+[scripts]
+pkg_script = "package2:main"
diff --git a/tests/samples/package2/src/package2/__init__.py b/tests/samples/package2/src/package2/__init__.py
new file mode 100644
index 0000000..07978d8
--- /dev/null
+++ b/tests/samples/package2/src/package2/__init__.py
@@ -0,0 +1,6 @@
+"""A sample package"""
+
+__version__ = '0.1'
+
+def main():
+ print("package1 main")
diff --git a/tests/samples/package2/src/package2/foo.py b/tests/samples/package2/src/package2/foo.py
new file mode 100644
index 0000000..1337a53
--- /dev/null
+++ b/tests/samples/package2/src/package2/foo.py
@@ -0,0 +1 @@
+a = 1
diff --git a/tests/samples/packageinsrc/pyproject.toml b/tests/samples/packageinsrc/pyproject.toml
new file mode 100644
index 0000000..b70209f
--- /dev/null
+++ b/tests/samples/packageinsrc/pyproject.toml
@@ -0,0 +1,10 @@
+[build-system]
+requires = ["flit"]
+build-backend = "flit.buildapi"
+
+[tool.flit.metadata]
+module = "module1"
+author = "Sir Robin"
+author-email = "robin@camelot.uk"
+home-page = "http://github.com/sirrobin/module1"
+requires = []
diff --git a/tests/samples/packageinsrc/src/module1.py b/tests/samples/packageinsrc/src/module1.py
new file mode 100644
index 0000000..87f0370
--- /dev/null
+++ b/tests/samples/packageinsrc/src/module1.py
@@ -0,0 +1,3 @@
+"""Example module"""
+
+__version__ = '0.1'
diff --git a/tests/samples/pep517/module1.py b/tests/samples/pep517/module1.py
new file mode 100644
index 0000000..87f0370
--- /dev/null
+++ b/tests/samples/pep517/module1.py
@@ -0,0 +1,3 @@
+"""Example module"""
+
+__version__ = '0.1'
diff --git a/tests/samples/pep517/pyproject.toml b/tests/samples/pep517/pyproject.toml
new file mode 100644
index 0000000..6b4fa15
--- /dev/null
+++ b/tests/samples/pep517/pyproject.toml
@@ -0,0 +1,13 @@
+[build-system]
+requires = ["flit"]
+build-backend = "flit.buildapi"
+
+[tool.flit.metadata]
+module = "module1"
+author = "Sir Robin"
+author-email = "robin@camelot.uk"
+home-page = "http://github.com/sirrobin/module1"
+requires = [
+ "requests >= 2.18",
+ "docutils",
+]
diff --git a/tests/samples/requires-dev.toml b/tests/samples/requires-dev.toml
new file mode 100644
index 0000000..46e3170
--- /dev/null
+++ b/tests/samples/requires-dev.toml
@@ -0,0 +1,11 @@
+[build-system]
+requires = ["flit"]
+
+[tool.flit.metadata]
+module = "module1"
+author = "Sir Robin"
+author-email = "robin@camelot.uk"
+home-page = "http://github.com/sirrobin/module1"
+description-file = "EG_README.rst"
+# This should generate a warning tell you to use requires-extra.dev
+dev-requires = ["apackage"]
diff --git a/tests/samples/requires-envmark/module1.py b/tests/samples/requires-envmark/module1.py
new file mode 100644
index 0000000..87f0370
--- /dev/null
+++ b/tests/samples/requires-envmark/module1.py
@@ -0,0 +1,3 @@
+"""Example module"""
+
+__version__ = '0.1'
diff --git a/tests/samples/requires-envmark/pyproject.toml b/tests/samples/requires-envmark/pyproject.toml
new file mode 100644
index 0000000..e97c5f0
--- /dev/null
+++ b/tests/samples/requires-envmark/pyproject.toml
@@ -0,0 +1,12 @@
+[build-system]
+requires = ["flit"]
+
+[tool.flit.metadata]
+module = "module1"
+author = "Sir Robin"
+author-email = "robin@camelot.uk"
+home-page = "http://github.com/sirrobin/module1"
+requires = [
+ "requests",
+ "pathlib2; python_version == '2.7'",
+]
diff --git a/tests/samples/requires-extra-envmark/module1.py b/tests/samples/requires-extra-envmark/module1.py
new file mode 100644
index 0000000..87f0370
--- /dev/null
+++ b/tests/samples/requires-extra-envmark/module1.py
@@ -0,0 +1,3 @@
+"""Example module"""
+
+__version__ = '0.1'
diff --git a/tests/samples/requires-extra-envmark/pyproject.toml b/tests/samples/requires-extra-envmark/pyproject.toml
new file mode 100644
index 0000000..fe6975e
--- /dev/null
+++ b/tests/samples/requires-extra-envmark/pyproject.toml
@@ -0,0 +1,11 @@
+[build-system]
+requires = ["flit"]
+
+[tool.flit.metadata]
+module = "module1"
+author = "Sir Robin"
+author-email = "robin@camelot.uk"
+home-page = "http://github.com/sirrobin/module1"
+
+[tool.flit.metadata.requires-extra]
+test = ["pathlib2; python_version == \"2.7\""]
diff --git a/tests/samples/requires-requests.toml b/tests/samples/requires-requests.toml
new file mode 100644
index 0000000..bf26ac5
--- /dev/null
+++ b/tests/samples/requires-requests.toml
@@ -0,0 +1,10 @@
+[build-system]
+requires = ["flit"]
+
+[tool.flit.metadata]
+module = "module1"
+author = "Sir Robin"
+author-email = "robin@camelot.uk"
+home-page = "http://github.com/sirrobin/module1"
+description-file = "EG_README.rst"
+requires = ["requests"]
diff --git a/tests/samples/with_flit_ini/flit.ini b/tests/samples/with_flit_ini/flit.ini
new file mode 100644
index 0000000..0637840
--- /dev/null
+++ b/tests/samples/with_flit_ini/flit.ini
@@ -0,0 +1,9 @@
+[metadata]
+module=package1
+author=Sir Robin
+author-email=robin@camelot.uk
+home-page=http://github.com/sirrobin/package1
+entry-points-file=some_entry_points.txt
+
+[scripts]
+pkg_script=package1:main
diff --git a/tests/samples/with_flit_ini/package1/__init__.py b/tests/samples/with_flit_ini/package1/__init__.py
new file mode 100644
index 0000000..07978d8
--- /dev/null
+++ b/tests/samples/with_flit_ini/package1/__init__.py
@@ -0,0 +1,6 @@
+"""A sample package"""
+
+__version__ = '0.1'
+
+def main():
+ print("package1 main")
diff --git a/tests/samples/with_flit_ini/package1/foo.py b/tests/samples/with_flit_ini/package1/foo.py
new file mode 100644
index 0000000..1337a53
--- /dev/null
+++ b/tests/samples/with_flit_ini/package1/foo.py
@@ -0,0 +1 @@
+a = 1
diff --git a/tests/samples/with_flit_ini/package1/subpkg/__init__.py b/tests/samples/with_flit_ini/package1/subpkg/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/samples/with_flit_ini/package1/subpkg/__init__.py
diff --git a/tests/samples/with_flit_ini/some_entry_points.txt b/tests/samples/with_flit_ini/some_entry_points.txt
new file mode 100644
index 0000000..317be93
--- /dev/null
+++ b/tests/samples/with_flit_ini/some_entry_points.txt
@@ -0,0 +1,2 @@
+[myplugins]
+package1=package1:main \ No newline at end of file
diff --git a/tests/test_build.py b/tests/test_build.py
new file mode 100644
index 0000000..59dd90e
--- /dev/null
+++ b/tests/test_build.py
@@ -0,0 +1,84 @@
+from pathlib import Path
+import pytest
+import shutil
+import sys
+from tempfile import TemporaryDirectory
+from testpath import assert_isdir, MockCommand
+
+from flit_core import common
+from flit import build
+
+samples_dir = Path(__file__).parent / 'samples'
+
+LIST_FILES_TEMPLATE = """\
+#!{python}
+import sys
+from os.path import join
+if '--deleted' not in sys.argv:
+ files = ['pyproject.toml', '{module}', 'EG_README.rst']
+ print('\\0'.join(files), end='\\0')
+"""
+
+def test_build_main(copy_sample):
+ td = copy_sample('module1_toml')
+ (td / '.git').mkdir() # Fake a git repo
+
+ with MockCommand('git', LIST_FILES_TEMPLATE.format(
+ python=sys.executable, module='module1.py')):
+ res = build.main(td / 'pyproject.toml')
+ assert res.wheel.file.suffix == '.whl'
+ assert res.sdist.file.name.endswith('.tar.gz')
+
+ assert_isdir(td / 'dist')
+
+def test_build_sdist_only(copy_sample):
+ td = copy_sample('module1_toml')
+ (td / '.git').mkdir() # Fake a git repo
+
+ with MockCommand('git', LIST_FILES_TEMPLATE.format(
+ python=sys.executable, module='module1.py')):
+ res = build.main(td / 'pyproject.toml', formats={'sdist'})
+ assert res.wheel is None
+
+ # Compare str path to work around pathlib/pathlib2 mismatch on Py 3.5
+ assert [str(p) for p in (td / 'dist').iterdir()] == [str(res.sdist.file)]
+
+def test_build_wheel_only(copy_sample):
+ td = copy_sample('module1_toml')
+ (td / '.git').mkdir() # Fake a git repo
+
+ with MockCommand('git', LIST_FILES_TEMPLATE.format(
+ python=sys.executable, module='module1.py')):
+ res = build.main(td / 'pyproject.toml', formats={'wheel'})
+ assert res.sdist is None
+
+ # Compare str path to work around pathlib/pathlib2 mismatch on Py 3.5
+ assert [str(p) for p in (td / 'dist').iterdir()] == [str(res.wheel.file)]
+
+def test_build_ns_main(copy_sample):
+ td = copy_sample('ns1-pkg')
+ (td / '.git').mkdir() # Fake a git repo
+
+ with MockCommand('git', LIST_FILES_TEMPLATE.format(
+ python=sys.executable, module='ns1/pkg/__init__.py')):
+ res = build.main(td / 'pyproject.toml')
+ assert res.wheel.file.suffix == '.whl'
+ assert res.sdist.file.name.endswith('.tar.gz')
+
+ assert_isdir(td / 'dist')
+
+
+def test_build_module_no_docstring():
+ with TemporaryDirectory() as td:
+ pyproject = Path(td, 'pyproject.toml')
+ shutil.copy(str(samples_dir / 'no_docstring-pkg.toml'), str(pyproject))
+ shutil.copy(str(samples_dir / 'no_docstring.py'), td)
+ shutil.copy(str(samples_dir / 'EG_README.rst'), td)
+ Path(td, '.git').mkdir() # Fake a git repo
+
+
+ with MockCommand('git', LIST_FILES_TEMPLATE.format(
+ python=sys.executable, module='no_docstring.py')):
+ with pytest.raises(common.NoDocstringError) as exc_info:
+ build.main(pyproject)
+ assert 'no_docstring.py' in str(exc_info.value)
diff --git a/tests/test_command.py b/tests/test_command.py
new file mode 100644
index 0000000..1cec17d
--- /dev/null
+++ b/tests/test_command.py
@@ -0,0 +1,13 @@
+from subprocess import Popen, PIPE, STDOUT
+import sys
+
+def test_flit_help():
+ p = Popen([sys.executable, '-m', 'flit', '--help'], stdout=PIPE, stderr=STDOUT)
+ out, _ = p.communicate()
+ assert 'Build wheel' in out.decode('utf-8', 'replace')
+
+def test_flit_usage():
+ p = Popen([sys.executable, '-m', 'flit'], stdout=PIPE, stderr=STDOUT)
+ out, _ = p.communicate()
+ assert 'Build wheel' in out.decode('utf-8', 'replace')
+ assert p.poll() == 1
diff --git a/tests/test_config.py b/tests/test_config.py
new file mode 100644
index 0000000..214cd17
--- /dev/null
+++ b/tests/test_config.py
@@ -0,0 +1,10 @@
+from pathlib import Path
+import pytest
+
+from flit.config import read_flit_config, ConfigError
+
+samples_dir = Path(__file__).parent / 'samples'
+
+def test_invalid_classifier():
+ with pytest.raises(ConfigError):
+ read_flit_config(samples_dir / 'invalid_classifier.toml')
diff --git a/tests/test_find_python_executable.py b/tests/test_find_python_executable.py
new file mode 100644
index 0000000..161dc7a
--- /dev/null
+++ b/tests/test_find_python_executable.py
@@ -0,0 +1,30 @@
+import os
+import re
+import sys
+
+import pytest
+
+from flit import PythonNotFoundError, find_python_executable
+
+
+def test_default():
+ assert find_python_executable(None) == sys.executable
+
+
+def test_self():
+ assert find_python_executable(sys.executable) == sys.executable
+
+
+def test_abs():
+ assert find_python_executable("/usr/bin/python") == "/usr/bin/python"
+
+
+def test_find_in_path():
+ assert os.path.isabs(find_python_executable("python"))
+
+
+@pytest.mark.parametrize("bad_python_name", ["pyhton", "ls", "."])
+def test_exception(bad_python_name: str):
+ """Test that an appropriate exception (that contains the error string) is raised."""
+ with pytest.raises(PythonNotFoundError, match=re.escape(bad_python_name)):
+ find_python_executable(bad_python_name)
diff --git a/tests/test_init.py b/tests/test_init.py
new file mode 100644
index 0000000..832343f
--- /dev/null
+++ b/tests/test_init.py
@@ -0,0 +1,255 @@
+import builtins
+from contextlib import contextmanager
+from pathlib import Path
+from tempfile import TemporaryDirectory
+from testpath import assert_isfile
+from unittest.mock import patch
+import pytest
+
+try:
+ import tomllib
+except ImportError:
+ import tomli as tomllib
+
+from flit import init
+
+
+@contextmanager
+def patch_data_dir():
+ with TemporaryDirectory() as td:
+ with patch.object(init, 'get_data_dir', lambda: Path(td)):
+ yield td
+
+def test_store_defaults():
+ with patch_data_dir():
+ assert init.get_defaults() == {}
+ d = {'author': 'Test'}
+ init.store_defaults(d)
+ assert init.get_defaults() == d
+
+def fake_input(entries):
+ it = iter(entries)
+ def inner(prompt):
+ try:
+ return next(it)
+ except StopIteration:
+ raise EOFError
+
+ return inner
+
+def faking_input(entries):
+ return patch.object(builtins, 'input', fake_input(entries))
+
+def test_prompt_options():
+ ti = init.TerminalIniter()
+ with faking_input(['4', '1']):
+ res = ti.prompt_options('Pick one', [('A', 'Apple'), ('B', 'Banana')])
+ assert res == 'A'
+
+ # Test with a default
+ with faking_input(['']):
+ res = ti.prompt_options('Pick one', [('A', 'Apple'), ('B', 'Banana')],
+ default='B')
+ assert res == 'B'
+
+@contextmanager
+def make_dir(files=(), dirs=()):
+ with TemporaryDirectory() as td:
+ tdp = Path(td)
+ for d in dirs:
+ (tdp / d).mkdir()
+ for f in files:
+ (tdp / f).touch()
+ yield td
+
+def test_guess_module_name():
+ with make_dir(['foo.py', 'foo-bar.py', 'test_foo.py', 'setup.py']) as td:
+ ib = init.IniterBase(td)
+ assert ib.guess_module_name() == 'foo'
+
+ with make_dir(['baz/__init__.py', 'tests/__init__.py'], ['baz', 'tests']) as td:
+ ib = init.IniterBase(td)
+ assert ib.guess_module_name() == 'baz'
+
+ with make_dir(['src/foo.py', 'src/foo-bar.py', 'test_foo.py', 'setup.py'],
+ ['src',]) as td:
+ ib = init.IniterBase(td)
+ assert ib.guess_module_name() == 'foo'
+
+ with make_dir(['src/baz/__init__.py', 'tests/__init__.py'], ['src', 'src/baz', 'tests']) as td:
+ ib = init.IniterBase(td)
+ assert ib.guess_module_name() == 'baz'
+
+ with make_dir(['foo.py', 'bar.py']) as td:
+ ib = init.IniterBase(td)
+ assert ib.guess_module_name() is None
+
+ with make_dir(['src/foo.py', 'src/bar.py'], ['src']) as td:
+ ib = init.IniterBase(td)
+ assert ib.guess_module_name() is None
+
+def test_write_license():
+ with TemporaryDirectory() as td:
+ ib = init.IniterBase(td)
+ ib.write_license('mit', 'Thomas Kluyver')
+ assert_isfile(Path(td, 'LICENSE'))
+
+def test_init():
+ responses = ['foo', # Module name
+ 'Test Author', # Author
+ 'test@example.com', # Author email
+ 'http://example.com/', # Home page
+ '1' # License (1 -> MIT)
+ ]
+ with TemporaryDirectory() as td, \
+ patch_data_dir(), \
+ faking_input(responses):
+ ti = init.TerminalIniter(td)
+ ti.initialise()
+
+ generated = Path(td) / 'pyproject.toml'
+ assert_isfile(generated)
+ with generated.open('rb') as f:
+ data = tomllib.load(f)
+ assert data['project']['authors'][0]['email'] == "test@example.com"
+ license = Path(td) / 'LICENSE'
+ assert data['project']['license']['file'] == 'LICENSE'
+ assert_isfile(license)
+ with license.open() as f:
+ license_text = f.read()
+ assert license_text.startswith("The MIT License (MIT)")
+ assert "{year}" not in license_text
+ assert "Test Author" in license_text
+
+def test_init_homepage_and_license_are_optional():
+ responses = ['test_module_name',
+ 'Test Author',
+ 'test_email@example.com',
+ '', # Home page omitted
+ '4', # Skip - choose a license later
+ ]
+ with TemporaryDirectory() as td, \
+ patch_data_dir(), \
+ faking_input(responses):
+ ti = init.TerminalIniter(td)
+ ti.initialise()
+ with Path(td, 'pyproject.toml').open('rb') as f:
+ data = tomllib.load(f)
+ assert not Path(td, 'LICENSE').exists()
+ assert data['project'] == {
+ 'authors': [{'name': 'Test Author', 'email': 'test_email@example.com'}],
+ 'name': 'test_module_name',
+ 'dynamic': ['version', 'description'],
+ }
+
+def test_init_homepage_validator():
+ responses = ['test_module_name',
+ 'Test Author',
+ 'test_email@example.com',
+ 'www.uh-oh-spagghetti-o.com', # fails validation
+ 'https://www.example.org', # passes
+ '4', # Skip - choose a license later
+ ]
+ with TemporaryDirectory() as td, \
+ patch_data_dir(), \
+ faking_input(responses):
+ ti = init.TerminalIniter(td)
+ ti.initialise()
+ with Path(td, 'pyproject.toml').open('rb') as f:
+ data = tomllib.load(f)
+ assert data['project'] == {
+ 'authors': [{'name': 'Test Author', 'email': 'test_email@example.com'}],
+ 'name': 'test_module_name',
+ 'urls': {'Home': 'https://www.example.org'},
+ 'dynamic': ['version', 'description'],
+ }
+
+def test_author_email_field_is_optional():
+ responses = ['test_module_name',
+ 'Test Author',
+ '', # Author-email field is skipped
+ 'https://www.example.org',
+ '4',
+ ]
+ with TemporaryDirectory() as td, \
+ patch_data_dir(), \
+ faking_input(responses):
+ ti = init.TerminalIniter(td)
+ ti.initialise()
+ with Path(td, 'pyproject.toml').open('rb') as f:
+ data = tomllib.load(f)
+ assert not Path(td, 'LICENSE').exists()
+
+ assert data['project'] == {
+ 'authors': [{'name': 'Test Author'}],
+ 'name': 'test_module_name',
+ 'urls': {'Home': 'https://www.example.org'},
+ 'dynamic': ['version', 'description'],
+ }
+
+
+@pytest.mark.parametrize(
+ "readme_file",
+ ["readme.md", "README.MD", "README.md",
+ "Readme.md", "readme.MD", "readme.rst",
+ "readme.txt"])
+def test_find_readme(readme_file):
+ with make_dir([readme_file]) as td:
+ ib = init.IniterBase(td)
+ assert ib.find_readme() == readme_file
+
+
+def test_find_readme_not_found():
+ with make_dir() as td:
+ ib = init.IniterBase(td)
+ assert ib.find_readme() is None
+
+
+def test_init_readme_found_yes_choosen():
+ responses = ['test_module_name',
+ 'Test Author',
+ 'test_email@example.com',
+ '', # Home page omitted
+ '4', # Skip - choose a license later
+ ]
+ with make_dir(["readme.md"]) as td, \
+ patch_data_dir(), \
+ faking_input(responses):
+ ti = init.TerminalIniter(td)
+ ti.initialise()
+ with Path(td, 'pyproject.toml').open('rb') as f:
+ data = tomllib.load(f)
+
+ assert data['project'] == {
+ 'authors': [{'name': 'Test Author', 'email': 'test_email@example.com'}],
+ 'name': 'test_module_name',
+ 'readme': 'readme.md',
+ 'dynamic': ['version', 'description'],
+ }
+
+
+def test_init_non_ascii_author_name():
+ responses = ['foo', # Module name
+ 'Test Authôr', # Author
+ '', # Author email omitted
+ '', # Home page omitted
+ '1' # License (1 -> MIT)
+ ]
+ with TemporaryDirectory() as td, \
+ patch_data_dir(), \
+ faking_input(responses):
+ ti = init.TerminalIniter(td)
+ ti.initialise()
+
+ generated = Path(td) / 'pyproject.toml'
+ assert_isfile(generated)
+ with generated.open('r', encoding='utf-8') as f:
+ raw_text = f.read()
+ print(raw_text)
+ assert "Test Authôr" in raw_text
+ assert "\\u00f4" not in raw_text
+ license = Path(td) / 'LICENSE'
+ assert_isfile(license)
+ with license.open(encoding='utf-8') as f:
+ license_text = f.read()
+ assert "Test Authôr" in license_text
diff --git a/tests/test_install.py b/tests/test_install.py
new file mode 100644
index 0000000..b4e9068
--- /dev/null
+++ b/tests/test_install.py
@@ -0,0 +1,365 @@
+import json
+import os
+import pathlib
+import sys
+import tempfile
+from unittest import TestCase, SkipTest
+from unittest.mock import patch
+
+import pytest
+from testpath import (
+ assert_isfile, assert_isdir, assert_islink, assert_not_path_exists, MockCommand
+)
+
+from flit import install
+from flit.install import Installer, _requires_dist_to_pip_requirement, DependencyError
+import flit_core.tests
+
+samples_dir = pathlib.Path(__file__).parent / 'samples'
+core_samples_dir = pathlib.Path(flit_core.tests.__file__).parent / 'samples'
+
+class InstallTests(TestCase):
+ def setUp(self):
+ td = tempfile.TemporaryDirectory()
+ self.addCleanup(td.cleanup)
+ self.get_dirs_patch = patch('flit.install.get_dirs',
+ return_value={
+ 'scripts': os.path.join(td.name, 'scripts'),
+ 'purelib': os.path.join(td.name, 'site-packages'),
+ 'data': os.path.join(td.name, 'data'),
+ })
+ self.get_dirs_patch.start()
+ self.tmpdir = pathlib.Path(td.name)
+
+ def tearDown(self):
+ self.get_dirs_patch.stop()
+
+ def _assert_direct_url(self, directory, package, version, expected_editable):
+ direct_url_file = (
+ self.tmpdir
+ / 'site-packages'
+ / '{}-{}.dist-info'.format(package, version)
+ / 'direct_url.json'
+ )
+ assert_isfile(direct_url_file)
+ with direct_url_file.open() as f:
+ direct_url = json.load(f)
+ assert direct_url['url'].startswith('file:///')
+ assert direct_url['url'] == directory.as_uri()
+ assert direct_url['dir_info'].get('editable') is expected_editable
+
+ def test_install_module(self):
+ Installer.from_ini_path(samples_dir / 'module1_toml' / 'pyproject.toml').install_directly()
+ assert_isfile(self.tmpdir / 'site-packages' / 'module1.py')
+ assert_isdir(self.tmpdir / 'site-packages' / 'module1-0.1.dist-info')
+ self._assert_direct_url(
+ samples_dir / 'module1_toml', 'module1', '0.1', expected_editable=False
+ )
+
+ def test_install_module_pep621(self):
+ Installer.from_ini_path(
+ core_samples_dir / 'pep621_nodynamic' / 'pyproject.toml',
+ ).install_directly()
+ assert_isfile(self.tmpdir / 'site-packages' / 'module1.py')
+ assert_isdir(self.tmpdir / 'site-packages' / 'module1-0.3.dist-info')
+ self._assert_direct_url(
+ core_samples_dir / 'pep621_nodynamic', 'module1', '0.3',
+ expected_editable=False
+ )
+
+ def test_install_package(self):
+ oldcwd = os.getcwd()
+ os.chdir(str(samples_dir / 'package1'))
+ try:
+ Installer.from_ini_path(pathlib.Path('pyproject.toml')).install_directly()
+ finally:
+ os.chdir(oldcwd)
+ assert_isdir(self.tmpdir / 'site-packages' / 'package1')
+ assert_isdir(self.tmpdir / 'site-packages' / 'package1-0.1.dist-info')
+ assert_isfile(self.tmpdir / 'scripts' / 'pkg_script')
+ with (self.tmpdir / 'scripts' / 'pkg_script').open() as f:
+ assert f.readline().strip() == "#!" + sys.executable
+ self._assert_direct_url(
+ samples_dir / 'package1', 'package1', '0.1', expected_editable=False
+ )
+
+ def test_install_module_in_src(self):
+ oldcwd = os.getcwd()
+ os.chdir(samples_dir / 'packageinsrc')
+ try:
+ Installer.from_ini_path(pathlib.Path('pyproject.toml')).install_directly()
+ finally:
+ os.chdir(oldcwd)
+ assert_isfile(self.tmpdir / 'site-packages' / 'module1.py')
+ assert_isdir(self.tmpdir / 'site-packages' / 'module1-0.1.dist-info')
+
+ def test_install_ns_package_native(self):
+ Installer.from_ini_path(samples_dir / 'ns1-pkg' / 'pyproject.toml').install_directly()
+ assert_isdir(self.tmpdir / 'site-packages' / 'ns1')
+ assert_isfile(self.tmpdir / 'site-packages' / 'ns1' / 'pkg' / '__init__.py')
+ assert_not_path_exists(self.tmpdir / 'site-packages' / 'ns1' / '__init__.py')
+ assert_isdir(self.tmpdir / 'site-packages' / 'ns1_pkg-0.1.dist-info')
+
+ def test_install_ns_package_module_native(self):
+ Installer.from_ini_path(samples_dir / 'ns1-pkg-mod' / 'pyproject.toml').install_directly()
+ assert_isfile(self.tmpdir / 'site-packages' / 'ns1' / 'module.py')
+ assert_not_path_exists(self.tmpdir / 'site-packages' / 'ns1' / '__init__.py')
+
+ def test_install_ns_package_native_symlink(self):
+ if os.name == 'nt':
+ raise SkipTest('symlink')
+ Installer.from_ini_path(
+ samples_dir / 'ns1-pkg' / 'pyproject.toml', symlink=True
+ ).install_directly()
+ Installer.from_ini_path(
+ samples_dir / 'ns1-pkg2' / 'pyproject.toml', symlink=True
+ ).install_directly()
+ Installer.from_ini_path(
+ samples_dir / 'ns1-pkg-mod' / 'pyproject.toml', symlink=True
+ ).install_directly()
+ assert_isdir(self.tmpdir / 'site-packages' / 'ns1')
+ assert_isdir(self.tmpdir / 'site-packages' / 'ns1' / 'pkg')
+ assert_islink(self.tmpdir / 'site-packages' / 'ns1' / 'pkg',
+ to=str(samples_dir / 'ns1-pkg' / 'ns1' / 'pkg'))
+ assert_isdir(self.tmpdir / 'site-packages' / 'ns1_pkg-0.1.dist-info')
+
+ assert_isdir(self.tmpdir / 'site-packages' / 'ns1' / 'pkg2')
+ assert_islink(self.tmpdir / 'site-packages' / 'ns1' / 'pkg2',
+ to=str(samples_dir / 'ns1-pkg2' / 'ns1' / 'pkg2'))
+ assert_isdir(self.tmpdir / 'site-packages' / 'ns1_pkg2-0.1.dist-info')
+
+ assert_islink(self.tmpdir / 'site-packages' / 'ns1' / 'module.py',
+ to=samples_dir / 'ns1-pkg-mod' / 'ns1' / 'module.py')
+ assert_isdir(self.tmpdir / 'site-packages' / 'ns1_module-0.1.dist-info')
+
+ def test_install_ns_package_pth_file(self):
+ Installer.from_ini_path(
+ samples_dir / 'ns1-pkg' / 'pyproject.toml', pth=True
+ ).install_directly()
+
+ pth_file = self.tmpdir / 'site-packages' / 'ns1.pkg.pth'
+ assert_isfile(pth_file)
+ assert pth_file.read_text('utf-8').strip() == str(samples_dir / 'ns1-pkg')
+
+ def test_symlink_package(self):
+ if os.name == 'nt':
+ raise SkipTest("symlink")
+ Installer.from_ini_path(samples_dir / 'package1' / 'pyproject.toml', symlink=True).install()
+ assert_islink(self.tmpdir / 'site-packages' / 'package1',
+ to=samples_dir / 'package1' / 'package1')
+ assert_isfile(self.tmpdir / 'scripts' / 'pkg_script')
+ with (self.tmpdir / 'scripts' / 'pkg_script').open() as f:
+ assert f.readline().strip() == "#!" + sys.executable
+ self._assert_direct_url(
+ samples_dir / 'package1', 'package1', '0.1', expected_editable=True
+ )
+
+ def test_symlink_module_pep621(self):
+ if os.name == 'nt':
+ raise SkipTest("symlink")
+ Installer.from_ini_path(
+ core_samples_dir / 'pep621_nodynamic' / 'pyproject.toml', symlink=True
+ ).install_directly()
+ assert_islink(self.tmpdir / 'site-packages' / 'module1.py',
+ to=core_samples_dir / 'pep621_nodynamic' / 'module1.py')
+ assert_isdir(self.tmpdir / 'site-packages' / 'module1-0.3.dist-info')
+ self._assert_direct_url(
+ core_samples_dir / 'pep621_nodynamic', 'module1', '0.3',
+ expected_editable=True
+ )
+
+ def test_symlink_module_in_src(self):
+ if os.name == 'nt':
+ raise SkipTest("symlink")
+ oldcwd = os.getcwd()
+ os.chdir(samples_dir / 'packageinsrc')
+ try:
+ Installer.from_ini_path(
+ pathlib.Path('pyproject.toml'), symlink=True
+ ).install_directly()
+ finally:
+ os.chdir(oldcwd)
+ assert_islink(self.tmpdir / 'site-packages' / 'module1.py',
+ to=(samples_dir / 'packageinsrc' / 'src' / 'module1.py'))
+ assert_isdir(self.tmpdir / 'site-packages' / 'module1-0.1.dist-info')
+
+ def test_pth_package(self):
+ Installer.from_ini_path(samples_dir / 'package1' / 'pyproject.toml', pth=True).install()
+ assert_isfile(self.tmpdir / 'site-packages' / 'package1.pth')
+ with open(str(self.tmpdir / 'site-packages' / 'package1.pth')) as f:
+ assert f.read() == str(samples_dir / 'package1')
+ assert_isfile(self.tmpdir / 'scripts' / 'pkg_script')
+ self._assert_direct_url(
+ samples_dir / 'package1', 'package1', '0.1', expected_editable=True
+ )
+
+ def test_pth_module_in_src(self):
+ oldcwd = os.getcwd()
+ os.chdir(samples_dir / 'packageinsrc')
+ try:
+ Installer.from_ini_path(
+ pathlib.Path('pyproject.toml'), pth=True
+ ).install_directly()
+ finally:
+ os.chdir(oldcwd)
+ pth_path = self.tmpdir / 'site-packages' / 'module1.pth'
+ assert_isfile(pth_path)
+ assert pth_path.read_text('utf-8').strip() == str(
+ samples_dir / 'packageinsrc' / 'src'
+ )
+ assert_isdir(self.tmpdir / 'site-packages' / 'module1-0.1.dist-info')
+
+ def test_dist_name(self):
+ Installer.from_ini_path(samples_dir / 'altdistname' / 'pyproject.toml').install_directly()
+ assert_isdir(self.tmpdir / 'site-packages' / 'package1')
+ assert_isdir(self.tmpdir / 'site-packages' / 'package_dist1-0.1.dist-info')
+
+ def test_entry_points(self):
+ Installer.from_ini_path(samples_dir / 'entrypoints_valid' / 'pyproject.toml').install_directly()
+ assert_isfile(self.tmpdir / 'site-packages' / 'package1-0.1.dist-info' / 'entry_points.txt')
+
+ def test_pip_install(self):
+ ins = Installer.from_ini_path(samples_dir / 'package1' / 'pyproject.toml', python='mock_python',
+ user=False)
+
+ with MockCommand('mock_python') as mock_py:
+ ins.install()
+
+ calls = mock_py.get_calls()
+ assert len(calls) == 1
+ cmd = calls[0]['argv']
+ assert cmd[1:4] == ['-m', 'pip', 'install']
+ assert cmd[4].endswith('package1')
+
+ def test_symlink_other_python(self):
+ if os.name == 'nt':
+ raise SkipTest('symlink')
+ (self.tmpdir / 'site-packages2').mkdir()
+ (self.tmpdir / 'scripts2').mkdir()
+
+ # Called by Installer._auto_user() :
+ script1 = ("#!{python}\n"
+ "import sysconfig\n"
+ "print(True)\n" # site.ENABLE_USER_SITE
+ "print({purelib!r})" # sysconfig.get_path('purelib')
+ ).format(python=sys.executable,
+ purelib=str(self.tmpdir / 'site-packages2'))
+
+ # Called by Installer._get_dirs() :
+ script2 = ("#!{python}\n"
+ "import json, sys\n"
+ "json.dump({{'purelib': {purelib!r}, 'scripts': {scripts!r}, 'data': {data!r} }}, "
+ "sys.stdout)"
+ ).format(python=sys.executable,
+ purelib=str(self.tmpdir / 'site-packages2'),
+ scripts=str(self.tmpdir / 'scripts2'),
+ data=str(self.tmpdir / 'data'),
+ )
+
+ with MockCommand('mock_python', content=script1):
+ ins = Installer.from_ini_path(samples_dir / 'package1' / 'pyproject.toml', python='mock_python',
+ symlink=True)
+ with MockCommand('mock_python', content=script2):
+ ins.install()
+
+ assert_islink(self.tmpdir / 'site-packages2' / 'package1',
+ to=samples_dir / 'package1' / 'package1')
+ assert_isfile(self.tmpdir / 'scripts2' / 'pkg_script')
+ with (self.tmpdir / 'scripts2' / 'pkg_script').open() as f:
+ assert f.readline().strip() == "#!mock_python"
+
+ def test_install_requires(self):
+ ins = Installer.from_ini_path(samples_dir / 'requires-requests.toml',
+ user=False, python='mock_python')
+
+ with MockCommand('mock_python') as mockpy:
+ ins.install_requirements()
+ calls = mockpy.get_calls()
+ assert len(calls) == 1
+ assert calls[0]['argv'][1:5] == ['-m', 'pip', 'install', '-r']
+
+ def test_install_reqs_my_python_if_needed_pep621(self):
+ ins = Installer.from_ini_path(
+ core_samples_dir / 'pep621_nodynamic' / 'pyproject.toml',
+ deps='none',
+ )
+
+ # This shouldn't try to get version & docstring from the module
+ ins.install_reqs_my_python_if_needed()
+
+ def test_extras_error(self):
+ with pytest.raises(DependencyError):
+ Installer.from_ini_path(samples_dir / 'requires-requests.toml',
+ user=False, deps='none', extras='dev')
+
+ def test_install_data_dir(self):
+ Installer.from_ini_path(
+ core_samples_dir / 'with_data_dir' / 'pyproject.toml',
+ ).install_directly()
+ assert_isfile(self.tmpdir / 'site-packages' / 'module1.py')
+ assert_isfile(self.tmpdir / 'data' / 'share' / 'man' / 'man1' / 'foo.1')
+
+ def test_symlink_data_dir(self):
+ if os.name == 'nt':
+ raise SkipTest("symlink")
+ Installer.from_ini_path(
+ core_samples_dir / 'with_data_dir' / 'pyproject.toml', symlink=True
+ ).install_directly()
+ assert_isfile(self.tmpdir / 'site-packages' / 'module1.py')
+ assert_islink(
+ self.tmpdir / 'data' / 'share' / 'man' / 'man1' / 'foo.1',
+ to=core_samples_dir / 'with_data_dir' / 'data' / 'share' / 'man' / 'man1' / 'foo.1'
+ )
+
+@pytest.mark.parametrize(('deps', 'extras', 'installed'), [
+ ('none', [], set()),
+ ('develop', [], {'pytest ;', 'toml ;'}),
+ ('production', [], {'toml ;'}),
+ ('all', [], {'toml ;', 'pytest ;', 'requests ;'}),
+])
+def test_install_requires_extra(deps, extras, installed):
+ it = InstallTests()
+ try:
+ it.setUp()
+ ins = Installer.from_ini_path(samples_dir / 'extras' / 'pyproject.toml', python='mock_python',
+ user=False, deps=deps, extras=extras)
+
+ cmd = MockCommand('mock_python')
+ get_reqs = (
+ "#!{python}\n"
+ "import sys\n"
+ "with open({recording_file!r}, 'wb') as w, open(sys.argv[-1], 'rb') as r:\n"
+ " w.write(r.read())"
+ ).format(python=sys.executable, recording_file=cmd.recording_file)
+ cmd.content = get_reqs
+
+ with cmd as mock_py:
+ ins.install_requirements()
+ with open(mock_py.recording_file) as f:
+ str_deps = f.read()
+ deps = str_deps.split('\n') if str_deps else []
+
+ assert set(deps) == installed
+ finally:
+ it.tearDown()
+
+def test_requires_dist_to_pip_requirement():
+ rd = 'pathlib2 (>=2.3); python_version == "2.7"'
+ assert _requires_dist_to_pip_requirement(rd) == \
+ 'pathlib2>=2.3 ; python_version == "2.7"'
+
+def test_test_writable_dir_win():
+ with tempfile.TemporaryDirectory() as td:
+ assert install._test_writable_dir_win(td) is True
+
+ # Ironically, I don't know how to make a non-writable dir on Windows,
+ # so although the functionality is for Windows, the test is for Posix
+ if os.name != 'posix':
+ return
+
+ # Remove write permissions from the directory
+ os.chmod(td, 0o444)
+ try:
+ assert install._test_writable_dir_win(td) is False
+ finally:
+ os.chmod(td, 0o644)
diff --git a/tests/test_sdist.py b/tests/test_sdist.py
new file mode 100644
index 0000000..0ddcb82
--- /dev/null
+++ b/tests/test_sdist.py
@@ -0,0 +1,152 @@
+import ast
+from os.path import join as pjoin
+from pathlib import Path
+import pytest
+from shutil import which, copy, copytree
+import sys
+import tarfile
+from tempfile import TemporaryDirectory
+from testpath import assert_isfile, MockCommand
+
+from flit import sdist, common
+
+samples_dir = Path(__file__).parent / 'samples'
+
+def test_auto_packages():
+ module = common.Module('package1', samples_dir / 'package1')
+ packages, pkg_data = sdist.auto_packages(module)
+ assert packages == ['package1', 'package1.subpkg', 'package1.subpkg2']
+ assert pkg_data == {'': ['*'],
+ 'package1': ['data_dir/*'],
+ 'package1.subpkg': ['sp_data_dir/*'],
+ }
+
+def test_make_sdist():
+ # Smoke test of making a complete sdist
+ if not which('git'):
+ pytest.skip("requires git")
+ builder = sdist.SdistBuilder.from_ini_path(samples_dir / 'package1' / 'pyproject.toml')
+ with TemporaryDirectory() as td:
+ td = Path(td)
+ builder.build(td)
+ sdist_file = td / 'package1-0.1.tar.gz'
+ assert_isfile(sdist_file)
+
+ with tarfile.open(str(sdist_file)) as tf:
+ assert 'package1-0.1/setup.py' in tf.getnames()
+
+
+def test_sdist_no_setup_py():
+ # Smoke test of making a complete sdist
+ if not which('git'):
+ pytest.skip("requires git")
+ builder = sdist.SdistBuilder.from_ini_path(samples_dir / 'package1' / 'pyproject.toml')
+ with TemporaryDirectory() as td:
+ td = Path(td)
+ builder.build(td, gen_setup_py=False)
+ sdist_file = td / 'package1-0.1.tar.gz'
+ assert_isfile(sdist_file)
+
+ with tarfile.open(str(sdist_file)) as tf:
+ assert 'package1-0.1/setup.py' not in tf.getnames()
+
+
+LIST_FILES = """\
+#!{python}
+import sys
+from os.path import join
+if '--deleted' not in sys.argv:
+ files = [
+ 'foo',
+ join('dir1', 'bar'),
+ join('dir1', 'subdir', 'qux'),
+ join('dir2', 'abc'),
+ join('dist', 'def'),
+ ]
+ mode = '{vcs}'
+ if mode == 'git':
+ print('\\0'.join(files), end='\\0')
+ elif mode == 'hg':
+ for f in files:
+ print(f)
+"""
+
+LIST_FILES_GIT = LIST_FILES.format(python=sys.executable, vcs='git')
+LIST_FILES_HG = LIST_FILES.format(python=sys.executable, vcs='hg')
+
+
+def test_get_files_list_git(copy_sample):
+ td = copy_sample('module1_toml')
+ (td / '.git').mkdir()
+
+ builder = sdist.SdistBuilder.from_ini_path(td / 'pyproject.toml')
+ with MockCommand('git', LIST_FILES_GIT):
+ files = builder.select_files()
+
+ assert set(files) == {
+ 'foo', pjoin('dir1', 'bar'), pjoin('dir1', 'subdir', 'qux'),
+ pjoin('dir2', 'abc')
+ }
+
+def test_get_files_list_hg(tmp_path):
+ dir1 = tmp_path / 'dir1'
+ copytree(str(samples_dir / 'module1_toml'), str(dir1))
+ (tmp_path / '.hg').mkdir()
+ builder = sdist.SdistBuilder.from_ini_path(dir1 / 'pyproject.toml')
+ with MockCommand('hg', LIST_FILES_HG):
+ files = builder.select_files()
+
+ assert set(files) == {
+ 'bar', pjoin('subdir', 'qux')
+ }
+
+def get_setup_assigns(setup):
+ """Parse setup.py, execute assignments, return the namespace"""
+ setup_ast = ast.parse(setup)
+ # Select only assignment statements
+ setup_ast.body = [n for n in setup_ast.body if isinstance(n, ast.Assign)]
+ ns = {}
+ exec(compile(setup_ast, filename="setup.py", mode="exec"), ns)
+ return ns
+
+def test_make_setup_py():
+ builder = sdist.SdistBuilder.from_ini_path(samples_dir / 'package1' / 'pyproject.toml')
+ ns = get_setup_assigns(builder.make_setup_py())
+ assert ns['packages'] == ['package1', 'package1.subpkg', 'package1.subpkg2']
+ assert 'install_requires' not in ns
+ assert ns['entry_points'] == \
+ {'console_scripts': ['pkg_script = package1:main']}
+
+def test_make_setup_py_reqs():
+ builder = sdist.SdistBuilder.from_ini_path(samples_dir / 'extras' / 'pyproject.toml')
+ ns = get_setup_assigns(builder.make_setup_py())
+ assert ns['install_requires'] == ['toml']
+ assert ns['extras_require'] == {'test': ['pytest'], 'custom': ['requests']}
+
+def test_make_setup_py_reqs_envmark():
+ builder = sdist.SdistBuilder.from_ini_path(samples_dir / 'requires-envmark' / 'pyproject.toml')
+ ns = get_setup_assigns(builder.make_setup_py())
+ assert ns['install_requires'] == ['requests']
+ assert ns['extras_require'] == {":python_version == '2.7'": ['pathlib2']}
+
+def test_make_setup_py_reqs_extra_envmark():
+ builder = sdist.SdistBuilder.from_ini_path(samples_dir / 'requires-extra-envmark' / 'pyproject.toml')
+ ns = get_setup_assigns(builder.make_setup_py())
+ assert ns['extras_require'] == {'test:python_version == "2.7"': ['pathlib2']}
+
+def test_make_setup_py_package_dir_src():
+ builder = sdist.SdistBuilder.from_ini_path(samples_dir / 'packageinsrc' / 'pyproject.toml')
+ ns = get_setup_assigns(builder.make_setup_py())
+ assert ns['package_dir'] == {'': 'src'}
+
+def test_make_setup_py_ns_pkg():
+ builder = sdist.SdistBuilder.from_ini_path(samples_dir / 'ns1-pkg' / 'pyproject.toml')
+ setup = builder.make_setup_py()
+ ns = get_setup_assigns(setup)
+ assert ns['packages'] == ['ns1', 'ns1.pkg']
+
+def test_make_setup_py_ns_pkg_mod():
+ builder = sdist.SdistBuilder.from_ini_path(samples_dir / 'ns1-pkg-mod' / 'pyproject.toml')
+ setup = builder.make_setup_py()
+ ns = get_setup_assigns(setup)
+ assert ns['packages'] == ['ns1']
diff --git a/tests/test_tomlify.py b/tests/test_tomlify.py
new file mode 100644
index 0000000..65e6178
--- /dev/null
+++ b/tests/test_tomlify.py
@@ -0,0 +1,32 @@
+import os
+from pathlib import Path
+try:
+ import tomllib
+except ImportError:
+ import tomli as tomllib
+from shutil import copy
+from testpath import assert_isfile
+
+from flit import tomlify
+
+samples_dir = Path(__file__).parent / 'samples'
+
+def test_tomlify(copy_sample, monkeypatch):
+ td = copy_sample('with_flit_ini')
+ monkeypatch.chdir(td)
+
+ tomlify.main(argv=[])
+
+ pyproject_toml = (td / 'pyproject.toml')
+ assert_isfile(pyproject_toml)
+
+ with pyproject_toml.open('rb') as f:
+ content = tomllib.load(f)
+
+ assert 'build-system' in content
+ assert 'tool' in content
+ assert 'flit' in content['tool']
+ flit_table = content['tool']['flit']
+ assert 'metadata' in flit_table
+ assert 'scripts' in flit_table
+ assert 'entrypoints' in flit_table
diff --git a/tests/test_upload.py b/tests/test_upload.py
new file mode 100644
index 0000000..73ede36
--- /dev/null
+++ b/tests/test_upload.py
@@ -0,0 +1,168 @@
+from contextlib import contextmanager
+from tempfile import NamedTemporaryFile
+import os
+import io
+import pathlib
+import sys
+
+import pytest
+import responses
+from testpath import modified_env
+from unittest.mock import patch
+
+from flit import upload
+from flit.build import ALL_FORMATS
+
+samples_dir = pathlib.Path(__file__).parent / 'samples'
+
+repo_settings = {'url': upload.PYPI,
+ 'username': 'user',
+ 'password': 'pw',
+ 'is_warehouse': True,
+ }
+
+pypirc1 = """
+[distutils]
+index-servers =
+ pypi
+
+[pypi]
+username: fred
+password: s3cret
+"""
+# That's not a real password. Well, hopefully not.
+
+@contextmanager
+def temp_pypirc(content):
+ try:
+ temp_file = NamedTemporaryFile("w+", delete=False)
+ temp_file.write(content)
+ temp_file.close()
+ yield temp_file.name
+ finally:
+ os.unlink(temp_file.name)
+
+
+@responses.activate
+def test_upload(copy_sample):
+ responses.add(responses.POST, upload.PYPI, status=200)
+ td = copy_sample('module1_toml')
+
+ with temp_pypirc(pypirc1) as pypirc, \
+ patch('flit.upload.get_repository', return_value=repo_settings):
+ upload.main(td / 'pyproject.toml', repo_name='pypi', pypirc_path=pypirc)
+
+ assert len(responses.calls) == 2
+
+def test_get_repository():
+ with temp_pypirc(pypirc1) as pypirc:
+ repo = upload.get_repository(pypirc_path=pypirc)
+ assert repo['url'] == upload.PYPI
+ assert repo['username'] == 'fred'
+ assert repo['password'] == 's3cret'
+
+def test_get_repository_env():
+ with temp_pypirc(pypirc1) as pypirc, \
+ modified_env({
+ 'FLIT_INDEX_URL': 'https://pypi.example.com',
+ 'FLIT_USERNAME': 'alice',
+ 'FLIT_PASSWORD': 'p4ssword', # Also not a real password
+ }):
+ repo = upload.get_repository(pypirc_path=pypirc)
+ # Because we haven't specified a repo name, environment variables should
+ # have higher priority than the config file.
+ assert repo['url'] == 'https://pypi.example.com'
+ assert repo['username'] == 'alice'
+ assert repo['password'] == 'p4ssword'
+
+@contextmanager
+def _fake_keyring(pw):
+ class FakeKeyring:
+ @staticmethod
+ def get_password(service_name, username):
+ return pw
+
+ class FakeKeyringErrMod:
+ class KeyringError(Exception):
+ pass
+
+ with patch.dict('sys.modules', {
+ 'keyring': FakeKeyring(), 'keyring.errors': FakeKeyringErrMod(),
+ }):
+ yield
+
+pypirc2 = """
+[distutils]
+index-servers =
+ pypi
+
+[pypi]
+username: fred
+"""
+
+def test_get_repository_keyring():
+ with modified_env({'FLIT_PASSWORD': None}), \
+ _fake_keyring('tops3cret'):
+ repo = upload.get_repository(pypirc_path=io.StringIO(pypirc2))
+
+ assert repo['username'] == 'fred'
+ assert repo['password'] == 'tops3cret'
+
+
+pypirc3_repo = "https://invalid-repo.inv"
+pypirc3_user = "test"
+pypirc3_pass = "not_a_real_password"
+pypirc3 = f"""
+[distutils] =
+index-servers =
+ test123
+
+[test123]
+repository: {pypirc3_repo}
+username: {pypirc3_user}
+password: {pypirc3_pass}
+"""
+
+
+def test_upload_pypirc_file(copy_sample):
+ with temp_pypirc(pypirc3) as pypirc, \
+ patch("flit.upload.upload_file") as upload_file:
+ td = copy_sample("module1_toml")
+ formats = list(ALL_FORMATS)[:1]
+ upload.main(
+ td / "pyproject.toml",
+ formats=set(formats),
+ repo_name="test123",
+ pypirc_path=pypirc,
+ )
+ _, _, repo = upload_file.call_args[0]
+
+ assert repo["url"] == pypirc3_repo
+ assert repo["username"] == pypirc3_user
+ assert repo["password"] == pypirc3_pass
+
+
+def test_upload_invalid_pypirc_file(copy_sample):
+ with patch("flit.upload.upload_file"):
+ td = copy_sample("module1_toml")
+ formats = list(ALL_FORMATS)[:1]
+ with pytest.raises(FileNotFoundError):
+ upload.main(
+ td / "pyproject.toml",
+ formats=set(formats),
+ repo_name="test123",
+ pypirc_path="./file.invalid",
+ )
+
+def test_upload_default_pypirc_file(copy_sample):
+ with patch("flit.upload.do_upload") as do_upload:
+ td = copy_sample("module1_toml")
+ formats = list(ALL_FORMATS)[:1]
+ upload.main(
+ td / "pyproject.toml",
+ formats=set(formats),
+ repo_name="test123",
+ )
+
+ file = do_upload.call_args[0][2]
+ assert file == "~/.pypirc"
diff --git a/tests/test_validate.py b/tests/test_validate.py
new file mode 100644
index 0000000..21b918c
--- /dev/null
+++ b/tests/test_validate.py
@@ -0,0 +1,243 @@
+import errno
+import pytest
+import responses
+
+from flit import validate as fv
+
+def test_validate_entrypoints():
+ assert fv.validate_entrypoints(
+ {'console_scripts': {'flit': 'flit:main'}}) == []
+ assert fv.validate_entrypoints(
+ {'some.group': {'flit': 'flit.buildapi'}}) == []
+
+ res = fv.validate_entrypoints({'some.group': {'flit': 'a:b:c'}})
+ assert len(res) == 1
+
+def test_validate_name():
+ def check(name):
+ return fv.validate_name({'name': name})
+
+ assert check('foo.bar_baz') == []
+ assert check('5minus6') == []
+
+ assert len(check('_foo')) == 1 # Must start with alphanumeric
+ assert len(check('foo.')) == 1 # Must end with alphanumeric
+ assert len(check('Bücher')) == 1 # ASCII only
+
+def test_validate_requires_python():
+ assert fv.validate_requires_python({}) == [] # Not required
+
+ def check(spec):
+ return fv.validate_requires_python({'requires_python': spec})
+
+ assert check('>=3') == []
+ assert check('>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*') == []
+
+ assert len(check('3')) == 1
+ assert len(check('@12')) == 1
+ assert len(check('>=2.7; !=3.0.*')) == 1 # Comma separated, not semicolon
+
+def test_validate_requires_dist():
+ assert fv.validate_requires_dist({}) == [] # Not required
+
+ def check(spec):
+ return fv.validate_requires_dist({'requires_dist': [spec]})
+
+ assert check('requests') == []
+ assert check('requests[extra-foo]') == []
+ assert check('requests (>=2.14)') == [] # parentheses allowed but not recommended
+ assert check('requests >=2.14') == []
+ assert check('pexpect; sys_platform == "win32"') == []
+ # Altogether now
+ assert check('requests[extra-foo] >=2.14; python_version < "3.0"') == []
+
+ # URL specifier
+ assert check('requests @ https://example.com/requests.tar.gz') == []
+ assert check(
+ 'requests @ https://example.com/requests.tar.gz ; python_version < "3.8"'
+ ) == []
+
+ # Problems
+ assert len(check('Bücher')) == 1
+ assert len(check('requests 2.14')) == 1
+ assert len(check('pexpect; sys.platform == "win32"')) == 1 # '.' -> '_'
+ assert len(check('requests >=2.14 @ https://example.com/requests.tar.gz')) == 1
+ # Several problems in one requirement
+ assert len(check('pexpect[_foo] =3; sys.platform == "win32"')) == 3
+
+def test_validate_environment_marker():
+ vem = fv.validate_environment_marker
+
+ assert vem('python_version >= "3" and os_name == \'posix\'') == []
+
+ res = vem('python_version >= "3') # Unclosed string
+ assert len(res) == 1
+ assert res[0].startswith("Invalid string")
+
+ res = vem('python_verson >= "3"') # Misspelled name
+ assert len(res) == 1
+ assert res[0].startswith("Invalid variable")
+
+ res = vem("os_name is 'posix'") # No 'is' comparisons
+ assert len(res) == 1
+ assert res[0].startswith("Invalid expression")
+
+ res = vem("'2' < python_version < '4'") # No chained comparisons
+ assert len(res) == 1
+ assert res[0].startswith("Invalid expression")
+
+ assert len(vem('os.name == "linux\'')) == 2
+
+def test_validate_url():
+ vurl = fv.validate_url
+ assert vurl("https://github.com/pypa/flit") == []
+
+ assert len(vurl("github.com/pypa/flit")) == 1
+ assert len(vurl("https://")) == 1
+
+
+def test_validate_project_urls():
+ vpu = fv.validate_project_urls
+
+ def check(prurl):
+ return vpu({'project_urls': [prurl]})
+ assert vpu({}) == [] # Not required
+ assert check('Documentation, https://flit.readthedocs.io/') == []
+
+ # Missing https://
+ assert len(check('Documentation, flit.readthedocs.io')) == 1
+ # Double comma
+ assert len(check('A, B, flit.readthedocs.io')) == 1
+ # No name
+ assert len(check(', https://flit.readthedocs.io/')) == 1
+ # Name longer than 32 chars
+ assert len(check('Supercalifragilisticexpialidocious, https://flit.readthedocs.io/')) == 1
+
+
+def test_read_classifiers_cached(monkeypatch, tmp_path):
+
+ def mock_get_cache_dir():
+ tmp_file = tmp_path / "classifiers.lst"
+ with tmp_file.open("w") as fh:
+ fh.write("A\nB\nC")
+ return tmp_path
+
+ monkeypatch.setattr(fv, "get_cache_dir", mock_get_cache_dir)
+
+ classifiers = fv._read_classifiers_cached()
+
+ assert classifiers == {'A', 'B', 'C'}
+
+
+@responses.activate
+def test_download_and_cache_classifiers(monkeypatch, tmp_path):
+ responses.add(
+ responses.GET,
+ 'https://pypi.org/pypi?%3Aaction=list_classifiers',
+ body="A\nB\nC")
+
+ def mock_get_cache_dir():
+ return tmp_path
+
+ monkeypatch.setattr(fv, "get_cache_dir", mock_get_cache_dir)
+
+ classifiers = fv._download_and_cache_classifiers()
+
+ assert classifiers == {"A", "B", "C"}
+
+
+def test_validate_classifiers_private(monkeypatch):
+ """
+ Test that `Private :: Do Not Upload` considered a valid classifier.
+ This is a special case because it is not listed in a trove classifier
+ but it is a way to make sure that a private package is not get uploaded
+ on PyPI by accident.
+
+ Implementation on PyPI side:
+ https://github.com/pypa/warehouse/pull/5440
+ Issue about officially documenting the trick:
+ https://github.com/pypa/packaging.python.org/issues/643
+ """
+ monkeypatch.setattr(fv, "_read_classifiers_cached", lambda: set())
+
+ actual = fv.validate_classifiers({'invalid'})
+ assert actual == ["Unrecognised classifier: 'invalid'"]
+
+ assert fv.validate_classifiers({'Private :: Do Not Upload'}) == []
+
+
+@responses.activate
+@pytest.mark.parametrize("error", [PermissionError, OSError(errno.EROFS, "")])
+def test_download_and_cache_classifiers_with_unacessible_dir(monkeypatch, error):
+ responses.add(
+ responses.GET,
+ 'https://pypi.org/pypi?%3Aaction=list_classifiers',
+ body="A\nB\nC")
+
+ class MockCacheDir:
+ def mkdir(self, parents):
+ raise error
+ def __truediv__(self, other):
+ raise error
+
+ monkeypatch.setattr(fv, "get_cache_dir", MockCacheDir)
+
+ classifiers = fv._download_and_cache_classifiers()
+
+ assert classifiers == {"A", "B", "C"}
+
+
+def test_verify_classifiers_valid_classifiers():
+ classifiers = {"A"}
+ valid_classifiers = {"A", "B"}
+
+ problems = fv._verify_classifiers(classifiers, valid_classifiers)
+
+ assert problems == []
+
+def test_verify_classifiers_invalid_classifiers():
+ classifiers = {"A", "B"}
+ valid_classifiers = {"A"}
+
+ problems = fv._verify_classifiers(classifiers, valid_classifiers)
+
+ assert problems == ["Unrecognised classifier: 'B'"]
+
+def test_validate_readme_rst():
+ metadata = {
+ 'description_content_type': 'text/x-rst',
+ 'description': "Invalid ``rst'",
+ }
+ problems = fv.validate_readme_rst(metadata)
+
+ assert len(problems) == 2 # 1 message that rst is invalid + 1 with details
+ assert "valid rst" in problems[0]
+
+ # Markdown should be ignored
+ metadata = {
+ 'description_content_type': 'text/markdown',
+ 'description': "Invalid `rst'",
+ }
+ problems = fv.validate_readme_rst(metadata)
+
+ assert problems == []
+
+RST_WITH_CODE = """
+Code snippet:
+
+.. code-block:: python
+
+ a = [i ** 2 for i in range(5)]
+"""
+
+def test_validate_readme_rst_code():
+ # Syntax highlighting shouldn't require pygments
+ metadata = {
+ 'description_content_type': 'text/x-rst',
+ 'description': RST_WITH_CODE,
+ }
+ problems = fv.validate_readme_rst(metadata)
+ for p in problems:
+ print(p)
+
+ assert problems == []
diff --git a/tests/test_vcs.py b/tests/test_vcs.py
new file mode 100644
index 0000000..4ed5ac3
--- /dev/null
+++ b/tests/test_vcs.py
@@ -0,0 +1,27 @@
+from contextlib import contextmanager
+import os
+from pathlib import Path
+from tempfile import TemporaryDirectory
+
+from flit import vcs
+
+@contextmanager
+def cwd(path):
+ if isinstance(path, Path):
+ path = str(path)
+ old_wd = os.getcwd()
+ os.chdir(path)
+ try:
+ yield
+ finally:
+ os.chdir(old_wd)
+
+def test_identify_git_parent():
+ with TemporaryDirectory() as td:
+ td = Path(td)
+ (td / '.git').mkdir()
+ subdir = (td / 'subdir')
+ subdir.mkdir()
+ with cwd(subdir):
+ assert vcs.identify_vcs(Path('.')).name == 'git'
+
diff --git a/tests/test_wheel.py b/tests/test_wheel.py
new file mode 100644
index 0000000..3b88391
--- /dev/null
+++ b/tests/test_wheel.py
@@ -0,0 +1,221 @@
+import configparser
+import os
+from pathlib import Path
+import tempfile
+from unittest import skipIf
+import zipfile
+
+import pytest
+from testpath import assert_isfile, assert_isdir, assert_not_path_exists
+
+from flit.wheel import WheelBuilder, make_wheel_in
+from flit.config import EntryPointsConflict
+
+samples_dir = Path(__file__).parent / 'samples'
+
+
+def unpack(path):
+ z = zipfile.ZipFile(str(path))
+ t = tempfile.TemporaryDirectory()
+ z.extractall(t.name)
+ return t
+
+def test_wheel_module(copy_sample):
+ td = copy_sample('module1_toml')
+ make_wheel_in(td / 'pyproject.toml', td)
+ assert_isfile(td / 'module1-0.1-py2.py3-none-any.whl')
+
+def test_editable_wheel_module(copy_sample):
+ td = copy_sample('module1_toml')
+ make_wheel_in(td / 'pyproject.toml', td, editable=True)
+ whl_file = td / 'module1-0.1-py2.py3-none-any.whl'
+ assert_isfile(whl_file)
+ with unpack(whl_file) as unpacked:
+ pth_path = Path(unpacked, 'module1.pth')
+ assert_isfile(pth_path)
+ assert pth_path.read_text() == str(td)
+ assert_isdir(Path(unpacked, 'module1-0.1.dist-info'))
+
+def test_editable_wheel_has_absolute_pth(copy_sample):
+ td = copy_sample('module1_toml')
+ oldcwd = os.getcwd()
+ os.chdir(str(td))
+ try:
+ make_wheel_in(Path('pyproject.toml'), Path('.'), editable=True)
+ whl_file = 'module1-0.1-py2.py3-none-any.whl'
+ assert_isfile(whl_file)
+ with unpack(whl_file) as unpacked:
+ pth_path = Path(unpacked, 'module1.pth')
+ assert_isfile(pth_path)
+ assert Path(pth_path.read_text()).is_absolute()
+ assert pth_path.read_text() == str(td.resolve())
+ assert_isdir(Path(unpacked, 'module1-0.1.dist-info'))
+ finally:
+ os.chdir(oldcwd)
+
+def test_wheel_package(copy_sample):
+ td = copy_sample('package1')
+ make_wheel_in(td / 'pyproject.toml', td)
+ assert_isfile(td / 'package1-0.1-py2.py3-none-any.whl')
+
+def test_editable_wheel_package(copy_sample):
+ td = copy_sample('package1')
+ make_wheel_in(td / 'pyproject.toml', td, editable=True)
+ whl_file = td / 'package1-0.1-py2.py3-none-any.whl'
+ assert_isfile(whl_file)
+ with unpack(whl_file) as unpacked:
+ pth_path = Path(unpacked, 'package1.pth')
+ assert_isfile(pth_path)
+ assert pth_path.read_text() == str(td)
+ assert_isdir(Path(unpacked, 'package1-0.1.dist-info'))
+
+def test_editable_wheel_namespace_package(copy_sample):
+ td = copy_sample('ns1-pkg')
+ make_wheel_in(td / 'pyproject.toml', td, editable=True)
+ whl_file = td / 'ns1_pkg-0.1-py2.py3-none-any.whl'
+ assert_isfile(whl_file)
+ with unpack(whl_file) as unpacked:
+ pth_path = Path(unpacked, 'ns1.pkg.pth')
+ assert_isfile(pth_path)
+ assert pth_path.read_text() == str(td)
+ assert_isdir(Path(unpacked, 'ns1_pkg-0.1.dist-info'))
+
+def test_wheel_src_module(copy_sample):
+ td = copy_sample('module3')
+ make_wheel_in(td / 'pyproject.toml', td)
+
+ whl_file = td / 'module3-0.1-py2.py3-none-any.whl'
+ assert_isfile(whl_file)
+ with unpack(whl_file) as unpacked:
+ assert_isfile(Path(unpacked, 'module3.py'))
+ assert_isdir(Path(unpacked, 'module3-0.1.dist-info'))
+ assert_isfile(Path(unpacked, 'module3-0.1.dist-info', 'LICENSE'))
+
+def test_editable_wheel_src_module(copy_sample):
+ td = copy_sample('module3')
+ make_wheel_in(td / 'pyproject.toml', td, editable=True)
+ whl_file = td / 'module3-0.1-py2.py3-none-any.whl'
+ assert_isfile(whl_file)
+ with unpack(whl_file) as unpacked:
+ pth_path = Path(unpacked, 'module3.pth')
+ assert_isfile(pth_path)
+ assert pth_path.read_text() == str(td / "src")
+ assert_isdir(Path(unpacked, 'module3-0.1.dist-info'))
+
+def test_wheel_src_package(copy_sample):
+ td = copy_sample('package2')
+ make_wheel_in(td / 'pyproject.toml', td)
+
+ whl_file = td / 'package2-0.1-py2.py3-none-any.whl'
+ assert_isfile(whl_file)
+ with unpack(whl_file) as unpacked:
+ print(os.listdir(unpacked))
+ assert_isfile(Path(unpacked, 'package2', '__init__.py'))
+
+def test_editable_wheel_src_package(copy_sample):
+ td = copy_sample('package2')
+ make_wheel_in(td / 'pyproject.toml', td, editable=True)
+ whl_file = td / 'package2-0.1-py2.py3-none-any.whl'
+ assert_isfile(whl_file)
+ with unpack(whl_file) as unpacked:
+ pth_path = Path(unpacked, 'package2.pth')
+ assert_isfile(pth_path)
+ assert pth_path.read_text() == str(td / "src")
+ assert_isdir(Path(unpacked, 'package2-0.1.dist-info'))
+
+
+def test_wheel_ns_package(copy_sample):
+ td = copy_sample('ns1-pkg')
+ res = make_wheel_in(td / 'pyproject.toml', td)
+ assert res.file == td / 'ns1_pkg-0.1-py2.py3-none-any.whl'
+ assert_isfile(res.file)
+ with unpack(res.file) as td_unpack:
+ assert_isdir(Path(td_unpack, 'ns1_pkg-0.1.dist-info'))
+ assert_isfile(Path(td_unpack, 'ns1', 'pkg', '__init__.py'))
+ assert_not_path_exists(Path(td_unpack, 'ns1', '__init__.py'))
+
+def test_dist_name(copy_sample):
+ td = copy_sample('altdistname')
+ make_wheel_in(td / 'pyproject.toml', td)
+ res = td / 'package_dist1-0.1-py2.py3-none-any.whl'
+ assert_isfile(res)
+ with unpack(res) as td_unpack:
+ assert_isdir(Path(td_unpack, 'package_dist1-0.1.dist-info'))
+
+def test_entry_points(copy_sample):
+ td = copy_sample('entrypoints_valid')
+ make_wheel_in(td / 'pyproject.toml', td)
+ assert_isfile(td / 'package1-0.1-py2.py3-none-any.whl')
+ with unpack(td / 'package1-0.1-py2.py3-none-any.whl') as td_unpack:
+ entry_points = Path(td_unpack, 'package1-0.1.dist-info', 'entry_points.txt')
+ assert_isfile(entry_points)
+ cp = configparser.ConfigParser()
+ cp.read(str(entry_points))
+ assert 'console_scripts' in cp.sections()
+ assert 'myplugins' in cp.sections()
+
+def test_entry_points_conflict(copy_sample):
+ td = copy_sample('entrypoints_conflict')
+ with pytest.raises(EntryPointsConflict):
+ make_wheel_in(td / 'pyproject.toml', td)
+
+def test_wheel_builder():
+ # Slightly lower level interface
+ with tempfile.TemporaryDirectory() as td:
+ target = Path(td, 'sample.whl')
+ with target.open('wb') as f:
+ wb = WheelBuilder.from_ini_path(samples_dir / 'package1' / 'pyproject.toml', f)
+ wb.build()
+
+ assert zipfile.is_zipfile(str(target))
+ assert wb.wheel_filename == 'package1-0.1-py2.py3-none-any.whl'
+
+@skipIf(os.name == 'nt', 'Windows does not preserve necessary permissions')
+def test_permissions_normed(copy_sample):
+ td = copy_sample('module1_toml')
+
+ (td / 'module1.py').chmod(0o620)
+ make_wheel_in(td / 'pyproject.toml', td)
+
+ whl = td / 'module1-0.1-py2.py3-none-any.whl'
+ assert_isfile(whl)
+ with zipfile.ZipFile(str(whl)) as zf:
+ info = zf.getinfo('module1.py')
+ perms = (info.external_attr >> 16) & 0o777
+ assert perms == 0o644, oct(perms)
+ whl.unlink()
+
+ # This time with executable bit set
+ (td / 'module1.py').chmod(0o720)
+ make_wheel_in(td / 'pyproject.toml', td)
+
+ assert_isfile(whl)
+ with zipfile.ZipFile(str(whl)) as zf:
+ info = zf.getinfo('module1.py')
+ perms = (info.external_attr >> 16) & 0o777
+ assert perms == 0o755, oct(perms)
+
+ info = zf.getinfo('module1-0.1.dist-info/METADATA')
+ perms = (info.external_attr >> 16) & 0o777
+ assert perms == 0o644, oct(perms)
+
+def test_compression(tmp_path):
+ info = make_wheel_in(samples_dir / 'module1_toml' / 'pyproject.toml', tmp_path)
+ assert_isfile(info.file)
+ with zipfile.ZipFile(str(info.file)) as zf:
+ for name in [
+ 'module1.py',
+ 'module1-0.1.dist-info/METADATA',
+ ]:
+ assert zf.getinfo(name).compress_type == zipfile.ZIP_DEFLATED
+
+def test_wheel_module_local_version(copy_sample):
+ """Test if a local version specifier is preserved in wheel filename and dist-info dir name"""
+ td = copy_sample('modulewithlocalversion')
+ make_wheel_in(td / 'pyproject.toml', td)
+
+ whl_file = td / 'modulewithlocalversion-0.1.dev0+test-py2.py3-none-any.whl'
+ assert_isfile(whl_file)
+ with unpack(whl_file) as unpacked:
+ assert_isfile(Path(unpacked, 'modulewithlocalversion.py'))
+ assert_isdir(Path(unpacked, 'modulewithlocalversion-0.1.dev0+test.dist-info'))