diff options
Diffstat (limited to 'tests')
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')) |